Skip to content

Commit

Permalink
feat: registry entries (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanHope authored Jun 3, 2024
1 parent 9e87088 commit bf95657
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 69 deletions.
4 changes: 2 additions & 2 deletions TODO.org
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
This is a running list of things that need to taken care of at some point:

- [ ] Long subtract function too complex
- [ ] Manifest requires registry key on Windows
- [ ] There has got to be a way to figure out which XCode version is installed
- [ ] The native messaging is broken on browsers installed by snap
- this is an issue with the Firefox Snapcraft in particular: https://forum.snapcraft.io/t/firefox-snapcraft-native-messaging-behavior/40437
10 changes: 8 additions & 2 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ func main() {
// When a browser sends a native message it will send the extension ID as the last argument.
// When we see one of Armaria's extension IDs we switch to native messaging mode.
extensionIds := []string{manifest.FirefoxExtension, manifest.ChromeExtension1, manifest.ChromeExtension2}
lastArg := os.Args[len(os.Args)-1]
if slices.Contains(extensionIds, lastArg) {
hostMode := false
for _, arg := range os.Args {
if slices.Contains(extensionIds, arg) {
hostMode = true
}
}

if hostMode {
if err := messaging.Dispatch(os.Stdin, os.Stdout); err != nil {
fmt.Printf("Unexpected error: %s", err)
os.Exit(1)
Expand Down
64 changes: 3 additions & 61 deletions internal/manifest/install.go
Original file line number Diff line number Diff line change
@@ -1,66 +1,8 @@
package manifest

import (
"encoding/json"
"fmt"
"os"
)

const name = "armaria"
const description = "Armaria is a fast local-first bookmarks manager."
const hostType = "stdio"
const FirefoxExtension = "[email protected]"
const ChromeExtension1 = "chrome-extension://cahkgigfdplmhgjbioakkgennhncioli/"
const ChromeExtension2 = "chrome-extension://fbnilfpngakppdkddndcnckolmlpghdf/"
//go:build !windows

// TODO: Windows needs registry entiries.
package manifest

// InstallManifest installs the app manifest.
func InstallManifest(path string, hostPath string, manifestType ManifestType) error {
var err error
var buffer []byte

if manifestType == ManifestChrome || manifestType == ManifestChromium {
manifest := chromeManifest{
Name: name,
Description: description,
Path: hostPath,
HostType: hostType,
AllowedOrigins: []string{
ChromeExtension1,
ChromeExtension2,
},
}

buffer, err = json.Marshal(manifest)
if err != nil {
return fmt.Errorf("error marshalling manifest while installing manifest: %w", err)
}
} else if manifestType == ManifestFirefox {
manifest := firefoxManifest{
Name: name,
Description: description,
Path: hostPath,
HostType: hostType,
AllowedExtensions: []string{FirefoxExtension},
}

buffer, err = json.Marshal(manifest)
if err != nil {
return fmt.Errorf("error marshalling manifest while installing manifest: %w", err)
}
}

handle, err := os.Create(path)
if err != nil {
return fmt.Errorf("error creating manifest file while installing manifest: %w", err)
}
defer handle.Close()

_, err = handle.Write(buffer)
if err != nil {
return fmt.Errorf("error writing manfiest file contents while installing manifest: %w", err)
}

return nil
return installManifest(path, hostPath, manifestType)
}
64 changes: 64 additions & 0 deletions internal/manifest/install_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package manifest

import (
"encoding/json"
"fmt"
"os"
)

const name = "armaria"
const description = "Armaria is a fast local-first bookmarks manager."
const hostType = "stdio"
const FirefoxExtension = "[email protected]"
const ChromeExtension1 = "chrome-extension://cahkgigfdplmhgjbioakkgennhncioli/"
const ChromeExtension2 = "chrome-extension://fbnilfpngakppdkddndcnckolmlpghdf/"

// installManifest installs the app manifest.
func installManifest(path string, hostPath string, manifestType ManifestType) error {
var err error
var buffer []byte

if manifestType == ManifestChrome || manifestType == ManifestChromium {
manifest := chromeManifest{
Name: name,
Description: description,
Path: hostPath,
HostType: hostType,
AllowedOrigins: []string{
ChromeExtension1,
ChromeExtension2,
},
}

buffer, err = json.Marshal(manifest)
if err != nil {
return fmt.Errorf("error marshalling manifest while installing manifest: %w", err)
}
} else if manifestType == ManifestFirefox {
manifest := firefoxManifest{
Name: name,
Description: description,
Path: hostPath,
HostType: hostType,
AllowedExtensions: []string{FirefoxExtension},
}

buffer, err = json.Marshal(manifest)
if err != nil {
return fmt.Errorf("error marshalling manifest while installing manifest: %w", err)
}
}

handle, err := os.Create(path)
if err != nil {
return fmt.Errorf("error creating manifest file while installing manifest: %w", err)
}
defer handle.Close()

_, err = handle.Write(buffer)
if err != nil {
return fmt.Errorf("error writing manfiest file contents while installing manifest: %w", err)
}

return nil
}
79 changes: 79 additions & 0 deletions internal/manifest/install_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//go:build windows

package manifest

import (
"fmt"
"strings"

"golang.org/x/sys/windows/registry"
)

// InstallManifest installs the app manifest.
func InstallManifest(path string, hostPath string, manifestType ManifestType) error {
err := installManifest(path, hostPath, manifestType)
if err != nil {
return err
}

// Windows (unfortunately) requires registry entries here.
if manifestType == ManifestChrome || manifestType == ManifestChromium {
if err := writeKey(`Software\Google\Chrome\NativeMessagingHosts\armaria`, path); err != nil {
return err
}
} else if manifestType == ManifestFirefox {
if err := writeKey(`Software\Mozilla\NativeMessagingHosts\armaria`, path); err != nil {
return err
}
}

return nil
}

// writeKey writes value to registry key.
// It gets written to both local machine and current user.
func writeKey(path string, value string) error {
localMachineKey, err := openKey(registry.LOCAL_MACHINE, path)
if err != nil {
return fmt.Errorf("error opening local machine key while installing manifest: %w", err)
}
defer localMachineKey.Close()

if err := localMachineKey.SetStringValue("", value); err != nil {
return fmt.Errorf("error writing local machine key while installing manifest: %w", err)
}

userKey, err := openKey(registry.CURRENT_USER, path)
if err != nil {
return fmt.Errorf("error opening current user key while installing manifest: %w", err)
}
defer userKey.Close()

if err := userKey.SetStringValue("", value); err != nil {
return fmt.Errorf("error writing current user key while installing manifest: %w", err)
}

return nil
}

// openKey opens a key for writing.
// It will create any parent keys necessary.
func openKey(root registry.Key, path string) (registry.Key, error) {
tokens := strings.Split(path, `\`)
keys := []registry.Key{root}
for _, token := range tokens {
key, _, err := registry.CreateKey(keys[len(keys)-1], token, registry.WRITE)
if err != nil {
return key, err
}
keys = append(keys, key)
}

for i, key := range keys {
if i != 0 && i != len(keys)-1 {
key.Close()
}
}

return keys[len(keys)-1], nil
}
12 changes: 9 additions & 3 deletions internal/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ func snapOrHome(userHome userHomeFn, getenv getenvFn) (string, error) {
// Host will get the path to the native messaging host.
// The extensions need an absolute path to it in order to work.
func Host() (string, error) {
return hostInternal(os.Getenv, os.Executable, filepath.Dir, filepath.Join)
return hostInternal(runtime.GOOS, os.Getenv, os.Executable, filepath.Dir, filepath.Join)
}

func hostInternal(getenv getenvFn, executable executableFn, dir dirFn, join joinFn) (string, error) {
func hostInternal(goos string, getenv getenvFn, executable executableFn, dir dirFn, join joinFn) (string, error) {
// The snap path needs to be a hard coded special case.
// There doesn't appear to be way to intuit it.
// Additionally snap will namespace the host with "armaria".
Expand All @@ -154,7 +154,13 @@ func hostInternal(getenv getenvFn, executable executableFn, dir dirFn, join join
if err != nil {
return "", fmt.Errorf("error getting current executable while getting host path: %w", err)
}
return join(dir(ex), "armaria"), nil

hostExe := "armaria"
if goos == "windows" {
hostExe = "armaria.exe"
}

return join(dir(ex), hostExe), nil
}

// FirefoxManifest gets the path to the Firefox app manifest.
Expand Down
10 changes: 9 additions & 1 deletion internal/paths/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,19 +224,27 @@ func TestGetFolderPath(t *testing.T) {

func TestHostPath(t *testing.T) {
type test struct {
goos string
snapRealHome string
hostPath string
}

tests := []test{
{
goos: "linux",
snapRealHome: "",
hostPath: "/usr/bin/armaria",
},
{
goos: "linux",
snapRealHome: "/snap",
hostPath: "/snap/bin/armaria",
},
{
goos: "windows",
snapRealHome: "",
hostPath: "/usr/bin/armaria.exe",
},
}

executable := func() (string, error) {
Expand All @@ -257,7 +265,7 @@ func TestHostPath(t *testing.T) {
return tc.snapRealHome
}

got, err := hostInternal(getenv, executable, dir, path.Join)
got, err := hostInternal(tc.goos, getenv, executable, dir, path.Join)
if err != nil {
t.Fatalf("unexpected error: %+v", err)
}
Expand Down

0 comments on commit bf95657

Please sign in to comment.