Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: registry entries #66

Merged
merged 1 commit into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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