diff --git a/chrome.go b/extension.go similarity index 56% rename from chrome.go rename to extension.go index e2db18f..ed8ee9c 100644 --- a/chrome.go +++ b/extension.go @@ -92,59 +92,44 @@ func handleRun(w http.ResponseWriter, r *http.Request) { } } -type chromeExtInstall struct{} +type installExtHostCmd struct{} -func (c *chromeExtInstall) Spec() cli.CommandSpec { +func (c *installExtHostCmd) Spec() cli.CommandSpec { return cli.CommandSpec{ - Name: "install-for-chrome-ext", - Desc: `Installs the chrome native message host manifest. -This allows the sail chrome extension to manage sail.`, + Name: "install-ext-host", + Desc: `Installs the native message host manifest into Chrome and Firefox. +This allows the sail extension to manage sail.`, } } -func (c *chromeExtInstall) Run(fl *flag.FlagSet) { - nativeHostDirs, err := nativeMessageHostManifestDirectories() +func (c *installExtHostCmd) Run(fl *flag.FlagSet) { + binPath, err := os.Executable() if err != nil { - flog.Fatal("failed to get native message host manifest directory: %v", err) + flog.Fatal("failed to get sail binary location") } - for _, dir := range nativeHostDirs { - if dir == "" { - continue - } - - err = os.MkdirAll(dir, 0755) - if err != nil { - flog.Fatal("failed to ensure manifest directory exists: %v", err) - } - err = writeNativeHostManifest(dir) - if err != nil { - flog.Fatal("failed to write native messaging host manifest: %v", err) - } + nativeHostDirsChrome, err := nativeMessageHostManifestDirectoriesChrome() + if err != nil { + flog.Fatal("failed to get chrome native message host manifest directory: %v", err) } -} - -func writeNativeHostManifest(dir string) error { - binPath, err := os.Executable() + err = installManifests(nativeHostDirsChrome, "com.coder.sail.json", chromeManifest(binPath)) if err != nil { - return err + flog.Fatal("failed to write chrome manifest files: %v", err) } - manifest := fmt.Sprintf(`{ - "name": "com.coder.sail", - "description": "sail message host", - "path": "%v", - "type": "stdio", - "allowed_origins": [ - "chrome-extension://deeepphleikpinikcbjplcgojfhkcmna/" - ] - }`, binPath) + nativeHostDirsFirefox, err := nativeMessageHostManifestDirectoriesFirefox() + if err != nil { + flog.Fatal("failed to get firefox native message host manifest directory: %v", err) + } + err = installManifests(nativeHostDirsFirefox, "com.coder.sail.json", firefoxManifest(binPath)) + if err != nil { + flog.Fatal("failed to write firefox manifest files: %v", err) + } - dst := path.Join(dir, "com.coder.sail.json") - return ioutil.WriteFile(dst, []byte(manifest), 0644) + flog.Info("Successfully installed manifests.") } -func nativeMessageHostManifestDirectories() ([]string, error) { +func nativeMessageHostManifestDirectoriesChrome() ([]string, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, xerrors.Errorf("failed to get user home dir: %w", err) @@ -178,3 +163,87 @@ func nativeMessageHostManifestDirectories() ([]string, error) { chromeCanaryDir, }, nil } + +func chromeManifest(binPath string) string { + return fmt.Sprintf(`{ + "name": "com.coder.sail", + "description": "sail message host", + "path": "%v", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://deeepphleikpinikcbjplcgojfhkcmna/" + ] + }`, binPath) +} + +func nativeMessageHostManifestDirectoriesFirefox() ([]string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, xerrors.Errorf("failed to get user home dir: %w", err) + } + + var firefoxDir string + + switch runtime.GOOS { + case "linux": + firefoxDir = path.Join(homeDir, ".mozilla", "native-messaging-hosts") + case "darwin": + firefoxDir = path.Join(homeDir, "Library", "Application Support", "Mozilla", "NativeMessagingHosts") + default: + return nil, xerrors.Errorf("unsupported os %q", runtime.GOOS) + } + + return []string{ + firefoxDir, + }, nil +} + +func firefoxManifest(binPath string) string { + return fmt.Sprintf(`{ + "name": "com.coder.sail", + "description": "sail message host", + "path": "%v", + "type": "stdio", + "allowed_extensions": [ + "sail@coder.com" + ] + }`, binPath) +} + +func installManifests(nativeHostDirs []string, file string, content string) error { + data := []byte(content) + + for _, dir := range nativeHostDirs { + if dir == "" { + continue + } + + err := os.MkdirAll(dir, 0755) + if err != nil { + return xerrors.Errorf("failed to ensure manifest directory exists: %w", err) + } + + dst := path.Join(dir, file) + err = ioutil.WriteFile(dst, data, 0644) + if err != nil { + return xerrors.Errorf("failed to write native messaging host manifest: %w", err) + } + } + + return nil +} + +type chromeExtInstallCmd struct{ + cmd *installExtHostCmd +} + +func (c *chromeExtInstallCmd) Spec() cli.CommandSpec { + return cli.CommandSpec{ + Name: "install-for-chrome-ext", + Desc: "DEPRECATED: alias of install-ext-host.", + } +} + +func (c *chromeExtInstallCmd) Run(fl *flag.FlagSet) { + c.cmd.Run(fl) +} diff --git a/extension/.gitignore b/extension/.gitignore index e8fc348..d18603b 100644 --- a/extension/.gitignore +++ b/extension/.gitignore @@ -1,3 +1,6 @@ -node_modules -out -*.zip \ No newline at end of file +*.xpi +*.zip +node_modules/ +out/ +packed-extensions/ +web-ext-artifacts/ diff --git a/extension/manifest.json b/extension/manifest.json index e2e5250..29c1399 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -6,9 +6,16 @@ "author": "Coder", "description": "Work in immutable, pre-configured development environments.", + "browser_specific_settings": { + "gecko": { + "id": "sail@coder.com", + "strict_min_version": "55.0" + } + }, + "background": { "scripts": [ - "out/background.js" + "background.js" ], "persistent": false }, @@ -18,7 +25,7 @@ "https://*/*" ], "js": [ - "out/content.js" + "content.js" ] } ], @@ -28,7 +35,10 @@ "storage", "tabs" ], - "options_page": "out/config.html", + "icons": { + "128": "logo128.png" + }, + "options_page": "config.html", "icons": { "128": "logo128.png" }, diff --git a/extension/pack.sh b/extension/pack.sh index 86bddc5..8b4e559 100755 --- a/extension/pack.sh +++ b/extension/pack.sh @@ -1,3 +1,25 @@ -#!/bin/bash +#!/usr/bin/env bash -zip -R extension manifest.json out/* logo128.png \ No newline at end of file +set -e + +cd $(dirname "$0") + +VERSION=$(jq -r ".version" ./manifest.json) +SRC_DIR="./out" +OUTPUT_DIR="./packed-extensions" + +mkdir -p "$OUTPUT_DIR" + +# Firefox extension (done first because web-ext verifies manifest) +if [ -z "$WEB_EXT_API_KEY" ]; then + web-ext build --source-dir="$SRC_DIR" --artifacts-dir="$OUTPUT_DIR" --overwrite-dest + mv "$OUTPUT_DIR/sail-$VERSION.zip" "$OUTPUT_DIR/sail-$VERSION.firefox.zip" +else + # Requires $WEB_EXT_API_KEY and $WEB_EXT_API_SECRET from addons.mozilla.org. + web-ext sign --source-dir="$SRC_DIR" --artifacts-dir="$OUTPUT_DIR" --overwrite-dest + mv "$OUTPUT_DIR/sail-$VERSION.xpi" "$OUTPUT_DIR/sail-$VERSION.firefox.xpi" +fi + +# Chrome extension +rm "$OUTPUT_DIR/sail-$VERSION.chrome.zip" || true +zip -R "$OUTPUT_DIR/sail-$VERSION.chrome.zip" "$SRC_DIR/*" diff --git a/extension/src/background.ts b/extension/src/background.ts index f754832..ee54792 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -24,12 +24,13 @@ export class SailConnector { resolve(message.url); }); this.port.onDisconnect.addListener(() => { + this.connectPromise = undefined; + this.port = undefined; if (chrome.runtime.lastError) { - this.connectPromise = undefined; - return reject(chrome.runtime.lastError.message); } - this.port = undefined; + + return reject("Native port disconnected."); }); }); @@ -86,101 +87,105 @@ const doConnection = (socketUrl: string, projectUrl: string, onMessage: (data: W }); }; -chrome.runtime.onMessage.addListener((data: ExtensionMessage, sender, sendResponse: (msg: ExtensionMessage) => void) => { - if (data.type === "sail") { - if (data.projectUrl) { - // Launch a sail connection. - if (!sender.tab) { - // Only allow from content scripts. - return; - } - - // Check that the tab is an approved host, otherwise ask - // the user for permission before launching Sail. - const url = new URL(sender.tab.url); - const host = url.hostname; - getApprovedHosts() - .then((hosts) => { - for (let h of hosts) { - if (h === host || (h.startsWith(".") && (host === h.substr(1) || host.endsWith(h)))) { - // Approved host. - return true; - } - } - - // If not approved, ask for approval. - return new Promise((resolve, reject) => { - chrome.tabs.executeScript(sender.tab.id, { - code: `confirm("Launch Sail? This will add this host to your approved hosts list.")`, - }, (result) => { - if (chrome.runtime.lastError) { - return reject(chrome.runtime.lastError.message); - } +chrome.runtime.onConnect.addListener((port: chrome.runtime.Port): void => { + const sendResponse = (message: ExtensionMessage): void => { + port.postMessage(message); + }; + + port.onMessage.addListener((data: ExtensionMessage): void => { + if (data.type === "sail") { + if (data.projectUrl) { + // Launch a sail connection. + if (!port.sender.tab) { + // Only allow from content scripts. + return; + } - if (result) { - // The user approved the confirm dialog. - addApprovedHost(host) - .then(() => resolve(true)) - .catch(reject); - return; + // Check that the tab is an approved host, otherwise ask + // the user for permission before launching Sail. + const url = new URL(port.sender.tab.url); + const host = url.hostname; + getApprovedHosts() + .then((hosts) => { + for (let h of hosts) { + if (h === host || (h.startsWith(".") && (host === h.substr(1) || host.endsWith(h)))) { + // Approved host. + return true; } + } - return false; + // If not approved, ask for approval. + return new Promise((resolve, reject) => { + chrome.tabs.executeScript(port.sender.tab.id, { + code: `confirm("Launch Sail? This will add this host to your approved hosts list.")`, + }, (result) => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError.message); + } + + if (result) { + // The user approved the confirm dialog. + addApprovedHost(host) + .then(() => resolve(true)) + .catch(reject); + return; + } + + return false; + }); }); - }); - }) - .then((approved) => { - if (!approved) { - return; - } - - // Start Sail. - // onMessage forwards WebSocketMessages to the tab that - // launched Sail. - const onMessage = (message: WebSocketMessage) => { - chrome.tabs.sendMessage(sender.tab.id, message); - }; - connector.connect().then((sailUrl) => { - const socketUrl = sailUrl.replace("http:", "ws:") + "/api/v1/run"; - return doConnection(socketUrl, data.projectUrl, onMessage).then((conn) => { + }) + .then((approved) => { + if (!approved) { + return; + } + + // Start Sail. + // onMessage forwards WebSocketMessages to the tab that + // launched Sail. + const onMessage = (message: WebSocketMessage) => { + port.postMessage(message); + }; + connector.connect().then((sailUrl) => { + const socketUrl = sailUrl.replace("http:", "ws:") + "/api/v1/run"; + return doConnection(socketUrl, data.projectUrl, onMessage).then((conn) => { + sendResponse({ + type: "sail", + }); + }); + }).catch((ex) => { sendResponse({ type: "sail", + error: ex.toString(), }); }); - }).catch((ex) => { + }) + .catch((ex) => { sendResponse({ type: "sail", error: ex.toString(), }); + }); - }) - .catch((ex) => { + } else { + // Check if we can get a sail URL. + connector.connect().then(() => { + sendResponse({ + type: "sail", + }) + }).catch((ex) => { sendResponse({ type: "sail", error: ex.toString(), }); - }); - } else { - // Check if we can get a sail URL. - connector.connect().then(() => { - sendResponse({ - type: "sail", - }) - }).catch((ex) => { - sendResponse({ - type: "sail", - error: ex.toString(), - }); - }); + } } - - return true; - } + }); }); // Open the config page when the browser action is clicked. chrome.browserAction.onClicked.addListener(() => { - const url = chrome.runtime.getURL("/out/config.html"); + const url = chrome.runtime.getURL("/config.html"); chrome.tabs.create({ url }); }); diff --git a/extension/src/common.ts b/extension/src/common.ts index 727dc43..4af26eb 100644 --- a/extension/src/common.ts +++ b/extension/src/common.ts @@ -29,26 +29,32 @@ export interface WebSocketMessage { // launchSail starts an instance of sail and instructs it to launch the // specified project URL. Terminal output will be sent to the onMessage handler. export const launchSail = (projectUrl: string, onMessage: (WebSocketMessage) => void): Promise => { - const listener = (message: any) => { - if (message.type && message.v) { - onMessage(message); - } - }; - chrome.runtime.onMessage.addListener(listener); - return new Promise((resolve, reject) => { - chrome.runtime.sendMessage({ - type: "sail", - projectUrl: projectUrl, - }, (response: ExtensionMessage) => { + const port = chrome.runtime.connect(); + port.onMessage.addListener((message: WebSocketMessage): void => { + if (message.type && message.v) { + onMessage(message); + } + if (message.type === "error") { + port.disconnect(); + } + }); + + const responseListener = (response: ExtensionMessage): void => { if (response.type === "sail") { + port.onMessage.removeListener(responseListener); if (response.error) { - chrome.runtime.onMessage.removeListener(listener); return reject(response.error); } resolve(); } + }; + + port.onMessage.addListener(responseListener); + port.postMessage({ + type: "sail", + projectUrl: projectUrl, }); }); }; @@ -57,16 +63,23 @@ export const launchSail = (projectUrl: string, onMessage: (WebSocketMessage) => // the extension to connect to Sail. This does not attempt a connection to Sail. export const sailAvailable = (): Promise => { return new Promise((resolve, reject) => { - chrome.runtime.sendMessage({ - type: "sail", - }, (response: ExtensionMessage) => { + const port = chrome.runtime.connect(); + + const responseListener = (response: ExtensionMessage): void => { if (response.type === "sail") { + port.onMessage.removeListener(responseListener); + port.disconnect(); if (response.error) { return reject(response.error); } resolve(); } + }; + + port.onMessage.addListener(responseListener); + port.postMessage({ + type: "sail", }); }); }; diff --git a/extension/src/config.html b/extension/src/config.html index 78a6330..960ceef 100644 --- a/extension/src/config.html +++ b/extension/src/config.html @@ -90,6 +90,6 @@

Add an approved host:

- + diff --git a/extension/src/config.ts b/extension/src/config.ts index 9599326..6c5fbf3 100644 --- a/extension/src/config.ts +++ b/extension/src/config.ts @@ -24,7 +24,7 @@ sailAvailable().then(() => { sailStatus.classList.add("error"); let message = "Failed to connect to Sail."; if (has("not found") || has("forbidden")) { - message = "After installing Sail, run sail install-for-chrome-ext."; + message = "After installing Sail, run sail install-ext-host."; } sailAvailableStatus.innerHTML = message; diff --git a/extension/webpack.config.js b/extension/webpack.config.js index 7589980..f5c3d5a 100644 --- a/extension/webpack.config.js +++ b/extension/webpack.config.js @@ -2,6 +2,8 @@ const path = require("path"); const HappyPack = require("happypack"); const os = require("os"); const CopyPlugin = require("copy-webpack-plugin"); + +const srcDir = path.join(__dirname, "src"); const outDir = path.join(__dirname, "out"); const mainConfig = (plugins = []) => ({ @@ -55,17 +57,18 @@ module.exports = [ ...mainConfig([ new CopyPlugin( [ - { - from: path.resolve(__dirname, "src/config.html"), - to: path.resolve(process.cwd(), "out/config.html"), - } + { from: path.join(srcDir, "config.html"), }, + { from: path.join(__dirname, "logo128.png") }, + { from: path.join(__dirname, "logo.svg") }, + { from: path.join(__dirname, "manifest.json") }, + { from: path.join(__dirname, "logo128.png") }, ], { copyUnmodified: true, } ), ]), - entry: path.join(__dirname, "src", "background.ts"), + entry: path.join(srcDir, "background.ts"), output: { path: outDir, filename: "background.js", @@ -73,7 +76,7 @@ module.exports = [ }, { ...mainConfig(), - entry: path.join(__dirname, "src", "content.ts"), + entry: path.join(srcDir, "content.ts"), output: { path: outDir, filename: "content.js", @@ -81,7 +84,7 @@ module.exports = [ }, { ...mainConfig(), - entry: path.join(__dirname, "src", "config.ts"), + entry: path.join(srcDir, "config.ts"), output: { path: outDir, filename: "config.js", diff --git a/extension/yarn.lock b/extension/yarn.lock index 8ac2336..3b300ca 100644 --- a/extension/yarn.lock +++ b/extension/yarn.lock @@ -1841,6 +1841,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -2027,21 +2032,6 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash.assign@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= - -lodash.clonedeep@^4.3.2: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - -lodash.mergewith@^4.6.0: - version "4.6.1" - resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" - integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== - lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -2052,6 +2042,11 @@ lodash@^4.0.0, lodash@~4.17.10: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== +lodash@^4.17.11: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -2198,6 +2193,16 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mini-css-extract-plugin@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1" + integrity sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw== + dependencies: + loader-utils "^1.1.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -2301,11 +2306,16 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -nan@^2.10.0, nan@^2.12.1: +nan@^2.12.1: version "2.13.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== +nan@^2.13.2: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -2405,10 +2415,10 @@ node-pre-gyp@^0.12.0: semver "^5.3.0" tar "^4" -node-sass@^4.11.0: - version "4.11.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" - integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== +node-sass@^4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017" + integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -2417,12 +2427,10 @@ node-sass@^4.11.0: get-stdin "^4.0.1" glob "^7.0.3" in-publish "^2.0.0" - lodash.assign "^4.2.0" - lodash.clonedeep "^4.3.2" - lodash.mergewith "^4.6.0" + lodash "^4.17.11" meow "^3.7.0" mkdirp "^0.5.1" - nan "^2.10.0" + nan "^2.13.2" node-gyp "^3.8.0" npmlog "^4.0.0" request "^2.88.0" @@ -2467,6 +2475,16 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + npm-bundled@^1.0.1: version "1.0.6" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" @@ -2821,6 +2839,11 @@ postcss@^7.0.14, postcss@^7.0.5, postcss@^7.0.6: source-map "^0.6.1" supports-color "^6.1.0" +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" @@ -2908,6 +2931,14 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -3295,6 +3326,13 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -3443,6 +3481,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -3512,6 +3555,14 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +style-loader@^0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" + integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== + dependencies: + loader-utils "^1.1.0" + schema-utils "^1.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" diff --git a/go.mod b/go.mod index 3038137..051e97f 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,7 @@ require ( go.coder.com/cli v0.1.1-0.20190426214427-610063ae7153 go.coder.com/flog v0.0.0-20190129195112-eaed154a0db8 golang.org/x/sys v0.0.0-20190415145633-3fd5a3612ccd // indirect - golang.org/x/xerrors v0.0.0-20190315151331-d61658bd2e18 + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 google.golang.org/grpc v1.20.0 // indirect gotest.tools v2.2.0+incompatible // indirect nhooyr.io/websocket v0.2.0 diff --git a/go.sum b/go.sum index 43bb6c8..a41e6c6 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,8 @@ golang.org/x/tools v0.0.0-20190419195823-c39e7748f6eb h1:JbWwiXQ1L1jWKTGSwj6y63W golang.org/x/tools v0.0.0-20190419195823-c39e7748f6eb/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/xerrors v0.0.0-20190315151331-d61658bd2e18 h1:1AGvnywFL1aB5KLRxyLseWJI6aSYPo3oF7HSpXdWQdU= golang.org/x/xerrors v0.0.0-20190315151331-d61658bd2e18/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/main.go b/main.go index 0423173..269e21d 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( "go.coder.com/cli" ) -// A dedication to Nhooyr Software. +// Dedicated to nhooyr_software. var _ interface { cli.Command cli.FlaggedCommand @@ -58,6 +58,8 @@ func (r *rootCmd) RegisterFlags(fl *flag.FlagSet) { } func (r rootCmd) Subcommands() []cli.Command { + extHostCmd := &installExtHostCmd{} + return []cli.Command{ &runcmd{gf: &r.globalFlags}, &shellcmd{gf: &r.globalFlags}, @@ -65,7 +67,8 @@ func (r rootCmd) Subcommands() []cli.Command { &lscmd{}, &rmcmd{gf: &r.globalFlags}, &proxycmd{}, - &chromeExtInstall{}, + extHostCmd, + &chromeExtInstallCmd{cmd: extHostCmd}, &versioncmd{}, } } @@ -74,7 +77,8 @@ func main() { root := &rootCmd{} if (len(os.Args) >= 2 && strings.HasPrefix(os.Args[1], "chrome-extension://")) || - (len(os.Args) >= 3 && strings.HasPrefix(os.Args[2], "chrome-extension://")) { + (len(os.Args) >= 3 && strings.HasPrefix(os.Args[2], "chrome-extension://")) || + (len(os.Args) >= 2 && strings.HasSuffix(os.Args[1], "com.coder.sail.json")) { runNativeMsgHost() return } diff --git a/site/content/docs/browser-extension.md b/site/content/docs/browser-extension.md index 0ae6a6c..15a2234 100644 --- a/site/content/docs/browser-extension.md +++ b/site/content/docs/browser-extension.md @@ -15,10 +15,6 @@ The Sail browser extension allows you to open GitHub or GitLab projects with a s ## Install 1. [Install Sail if you haven't already](/docs/installation) -1. Run `sail install-for-chrome-ext` to install the chrome extension manifest.json +1. Run `sail install-ext-host` to install the extension manifest.json 1. [Install the extension from the Chrome Marketplace](https://chrome.google.com/webstore/detail/sail/deeepphleikpinikcbjplcgojfhkcmna) 1. Get Sailing! - - - -