diff --git a/popup.js b/popup.js index 9d8b28b..9f0bd95 100644 --- a/popup.js +++ b/popup.js @@ -1,11 +1,3 @@ -var lastStatus; - -function browseToURL() { - if (lastStatus && lastStatus.browseToURL) { - chrome.tabs.create({ url: lastStatus.browseToURL }); - } -} - document.addEventListener("DOMContentLoaded", () => { const toggleSlider = document.getElementById("toggleSlider"); const slider = document.querySelector(".slider"); @@ -45,9 +37,15 @@ document.addEventListener("DOMContentLoaded", () => { return; } if (status.needsLogin) { - stateDisplay.innerHTML = status.browseToURL - ? `Log in` - : "Login required; no URL"; + if (status.browseToURL) { + stateDisplay.innerHTML = `Log in`; + stateDisplay.querySelector("a").addEventListener("click", (e) => { + e.preventDefault(); + chrome.tabs.create({ url: status.browseToURL }); + }); + } else { + stateDisplay.innerHTML = "Login required; no URL"; + } return; } if (typeof status === "string" && status === "Disconnected") { diff --git a/registry_other.go b/registry_other.go new file mode 100644 index 0000000..f633555 --- /dev/null +++ b/registry_other.go @@ -0,0 +1,6 @@ +//go:build !windows + +package main + +func installRegistry(browserByte, jsonPath string) error { return nil } +func uninstallRegistry(browserByte string) error { return nil } diff --git a/registry_windows.go b/registry_windows.go new file mode 100644 index 0000000..3ab1d7d --- /dev/null +++ b/registry_windows.go @@ -0,0 +1,29 @@ +//go:build windows + +package main + +import "golang.org/x/sys/windows/registry" + +func registryKeyPath(browserByte string) string { + if browserByte == "F" { + return `Software\Mozilla\NativeMessagingHosts\com.tailscale.browserext.firefox` + } + return `Software\Google\Chrome\NativeMessagingHosts\com.tailscale.browserext.chrome` +} + +func installRegistry(browserByte, jsonPath string) error { + key, _, err := registry.CreateKey(registry.CURRENT_USER, registryKeyPath(browserByte), registry.SET_VALUE) + if err != nil { + return err + } + defer key.Close() + return key.SetStringValue("", jsonPath) +} + +func uninstallRegistry(browserByte string) error { + err := registry.DeleteKey(registry.CURRENT_USER, registryKeyPath(browserByte)) + if err == registry.ErrNotExist { + return nil + } + return err +} diff --git a/syslog_other.go b/syslog_other.go new file mode 100644 index 0000000..dc098ef --- /dev/null +++ b/syslog_other.go @@ -0,0 +1,24 @@ +//go:build !windows + +package main + +import ( + "fmt" + "log" + "log/syslog" + + "tailscale.com/types/logger" +) + +func trySetSyslog(logf *logger.Logf) { + w, err := syslog.Dial("tcp", "localhost:5555", syslog.LOG_INFO, "browser") + if err != nil { + log.Printf("syslog: %v", err) + return + } + log.Printf("syslog dialed") + *logf = func(f string, a ...any) { + fmt.Fprintf(w, f, a...) + } + log.SetOutput(w) +} diff --git a/syslog_windows.go b/syslog_windows.go new file mode 100644 index 0000000..075bd6c --- /dev/null +++ b/syslog_windows.go @@ -0,0 +1,7 @@ +package main + +import "tailscale.com/types/logger" + +func trySetSyslog(logf *logger.Logf) { + // syslog is not available on Windows; use default logging. +} diff --git a/ts-browser-ext.go b/ts-browser-ext.go index fb8cc54..4b790ea 100644 --- a/ts-browser-ext.go +++ b/ts-browser-ext.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "log" - "log/syslog" "net" "net/http" "net/http/httputil" @@ -70,15 +69,7 @@ To register it once, run: h := newHost(os.Stdin, os.Stdout) - if w, err := syslog.Dial("tcp", "localhost:5555", syslog.LOG_INFO, "browser"); err == nil { - log.Printf("syslog dialed") - h.logf = func(f string, a ...any) { - fmt.Fprintf(w, f, a...) - } - log.SetOutput(w) - } else { - log.Printf("syslog: %v", err) - } + trySetSyslog(&h.logf) ln := h.getProxyListener() port := ln.Addr().(*net.TCPAddr).Port @@ -112,8 +103,10 @@ func getTargetDir(browserByte string) (string, error) { } else if browserByte == "F" { dir = filepath.Join(home, "Library", "Application Support", "Mozilla", "NativeMessagingHosts") } + case "windows": + dir = filepath.Join(home, "AppData", "Local", "TailscaleBrowserExt") default: - return "", fmt.Errorf("TODO: implement support for installing on %q", runtime.GOOS) + return "", fmt.Errorf("unsupported OS %q", runtime.GOOS) } if err := os.MkdirAll(dir, 0755); err != nil { return "", err @@ -121,13 +114,20 @@ func getTargetDir(browserByte string) (string, error) { return dir, nil } +func binaryName() string { + if runtime.GOOS == "windows" { + return "ts-browser-ext.exe" + } + return "ts-browser-ext" +} + func uninstall() error { for _, browserByte := range []string{"C", "F"} { targetDir, err := getTargetDir(browserByte) if err != nil { return err } - targetBin := filepath.Join(targetDir, "ts-browser-ext") + targetBin := filepath.Join(targetDir, binaryName()) targetJSON := filepath.Join(targetDir, "com.tailscale.browserext.chrome.json") if browserByte == "F" { targetJSON = filepath.Join(targetDir, "com.tailscale.browserext.firefox.json") @@ -138,6 +138,9 @@ func uninstall() error { if err := os.Remove(targetJSON); err != nil && !os.IsNotExist(err) { return err } + if err := uninstallRegistry(browserByte); err != nil { + return fmt.Errorf("removing registry key: %w", err) + } } return nil } @@ -167,13 +170,17 @@ func install(installArg string) error { if err != nil { return err } - targetBin := filepath.Join(targetDir, "ts-browser-ext") + targetBin := filepath.Join(targetDir, binaryName()) if err := os.WriteFile(targetBin, binary, 0755); err != nil { return err } log.SetFlags(0) log.Printf("copied binary to %v", targetBin) + // Use forward slashes in the JSON path so it works on all platforms + // (Chrome/Firefox on Windows accept forward slashes). + jsonBinPath := filepath.ToSlash(targetBin) + var targetJSON string var jsonConf []byte @@ -188,7 +195,7 @@ func install(installArg string) error { "allowed_origins": [ "chrome-extension://%s/" ] - }`, targetBin, extension) + }`, jsonBinPath, extension) case "F": targetJSON = filepath.Join(targetDir, "com.tailscale.browserext.firefox.json") jsonConf = fmt.Appendf(nil, `{ @@ -199,7 +206,7 @@ func install(installArg string) error { "allowed_extensions": [ "browser-ext@tailscale.com" ] - }`, targetBin) + }`, jsonBinPath) default: return fmt.Errorf("unknown browser prefix byte %q", browserByte) } @@ -207,6 +214,10 @@ func install(installArg string) error { return err } log.Printf("wrote registration to %v", targetJSON) + + if err := installRegistry(browserByte, targetJSON); err != nil { + return fmt.Errorf("writing registry: %w", err) + } return nil }