Skip to content
This repository was archived by the owner on Apr 28, 2020. It is now read-only.

Commit 61d8d38

Browse files
authored
Merge pull request #238 from cdr/approved-hosts
Approved hosts
2 parents 3df26d9 + d635a94 commit 61d8d38

File tree

13 files changed

+790
-121
lines changed

13 files changed

+790
-121
lines changed

extension/logo.svg

Lines changed: 31 additions & 0 deletions
Loading

extension/manifest.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": 2,
33

44
"name": "Sail",
5-
"version": "1.0.9",
5+
"version": "1.1.0",
66
"author": "Coder",
77
"description": "Work in immutable, pre-configured development environments.",
88

@@ -15,22 +15,24 @@
1515
"content_scripts": [
1616
{
1717
"matches": [
18-
"https://github.com/*",
19-
"https://gitlab.com/*"
18+
"https://*/*"
2019
],
2120
"js": [
2221
"out/content.js"
2322
]
2423
}
2524
],
2625
"permissions": [
27-
"nativeMessaging"
26+
"<all_urls>",
27+
"nativeMessaging",
28+
"storage",
29+
"tabs"
2830
],
31+
"options_page": "out/config.html",
2932
"icons": {
3033
"128": "logo128.png"
3134
},
3235
"browser_action": {
33-
"default_title": "Sail",
34-
"default_popup": "out/popup.html"
36+
"default_title": "Sail"
3537
}
3638
}

extension/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
"copy-webpack-plugin": "^5.0.2",
1111
"css-loader": "^2.1.1",
1212
"happypack": "^5.0.1",
13-
"node-sass": "^4.11.0",
13+
"mini-css-extract-plugin": "^0.8.0",
14+
"node-sass": "^4.12.0",
1415
"sass-loader": "^7.1.0",
16+
"style-loader": "^0.23.1",
1517
"ts-loader": "^5.3.3",
1618
"typescript": "^3.4.4",
1719
"webpack": "^4.30.0",

extension/src/background.ts

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ExtensionMessage } from "./common";
1+
import {
2+
ExtensionMessage,
3+
WebSocketMessage,
4+
getApprovedHosts,
5+
addApprovedHost
6+
} from "./common";
27

38
export class SailConnector {
49
private port: chrome.runtime.Port;
@@ -13,7 +18,7 @@ export class SailConnector {
1318
this.port = chrome.runtime.connectNative("com.coder.sail");
1419
this.port.onMessage.addListener((message) => {
1520
if (!message.url) {
16-
return reject("Invalid handshaking message");
21+
return reject("Invalid handshake message");
1722
}
1823

1924
resolve(message.url);
@@ -37,26 +42,145 @@ export class SailConnector {
3742
}
3843
}
3944

45+
// Get the sail URL.
4046
const connector = new SailConnector();
4147
let connectError: string | undefined = "Not connected yet";
4248
connector.connect().then(() => connectError = undefined).catch((ex) => {
4349
connectError = `Failed to connect: ${ex.toString()}`;
4450
});
4551

52+
// doConnection attempts to connect to Sail over WebSocket.
53+
const doConnection = (socketUrl: string, projectUrl: string, onMessage: (data: WebSocketMessage) => void): Promise<WebSocket> => {
54+
return new Promise<WebSocket>((resolve, reject) => {
55+
const socket = new WebSocket(socketUrl);
56+
socket.addEventListener("open", () => {
57+
socket.send(JSON.stringify({
58+
project: projectUrl,
59+
}));
60+
61+
resolve(socket);
62+
});
63+
socket.addEventListener("close", (event) => {
64+
const v = `sail socket was closed: ${event.code}`;
65+
onMessage({ type: "error", v });
66+
reject(v);
67+
});
68+
69+
socket.addEventListener("message", (event) => {
70+
const data = JSON.parse(event.data);
71+
if (!data) {
72+
return;
73+
}
74+
const type = data.type;
75+
const content = type === "data" ? atob(data.v) : data.v;
76+
77+
switch (type) {
78+
case "data":
79+
case "error":
80+
onMessage({ type, v: content });
81+
break;
82+
default:
83+
throw new Error("unknown message type: " + type);
84+
}
85+
});
86+
});
87+
};
88+
4689
chrome.runtime.onMessage.addListener((data: ExtensionMessage, sender, sendResponse: (msg: ExtensionMessage) => void) => {
4790
if (data.type === "sail") {
48-
connector.connect().then((url) => {
49-
sendResponse({
50-
type: "sail",
51-
url,
52-
})
53-
}).catch((ex) => {
54-
sendResponse({
55-
type: "sail",
56-
error: ex.toString(),
91+
if (data.projectUrl) {
92+
// Launch a sail connection.
93+
if (!sender.tab) {
94+
// Only allow from content scripts.
95+
return;
96+
}
97+
98+
// Check that the tab is an approved host, otherwise ask
99+
// the user for permission before launching Sail.
100+
const url = new URL(sender.tab.url);
101+
const host = url.hostname;
102+
getApprovedHosts()
103+
.then((hosts) => {
104+
for (let h of hosts) {
105+
if (h === host || (h.startsWith(".") && (host === h.substr(1) || host.endsWith(h)))) {
106+
// Approved host.
107+
return true;
108+
}
109+
}
110+
111+
// If not approved, ask for approval.
112+
return new Promise((resolve, reject) => {
113+
chrome.tabs.executeScript(sender.tab.id, {
114+
code: `confirm("Launch Sail? This will add this host to your approved hosts list.")`,
115+
}, (result) => {
116+
if (chrome.runtime.lastError) {
117+
return reject(chrome.runtime.lastError.message);
118+
}
119+
120+
if (result) {
121+
// The user approved the confirm dialog.
122+
addApprovedHost(host)
123+
.then(() => resolve(true))
124+
.catch(reject);
125+
return;
126+
}
127+
128+
return false;
129+
});
130+
});
131+
})
132+
.then((approved) => {
133+
if (!approved) {
134+
return;
135+
}
136+
137+
// Start Sail.
138+
// onMessage forwards WebSocketMessages to the tab that
139+
// launched Sail.
140+
const onMessage = (message: WebSocketMessage) => {
141+
chrome.tabs.sendMessage(sender.tab.id, message);
142+
};
143+
connector.connect().then((sailUrl) => {
144+
const socketUrl = sailUrl.replace("http:", "ws:") + "/api/v1/run";
145+
return doConnection(socketUrl, data.projectUrl, onMessage).then((conn) => {
146+
sendResponse({
147+
type: "sail",
148+
});
149+
});
150+
}).catch((ex) => {
151+
sendResponse({
152+
type: "sail",
153+
error: ex.toString(),
154+
});
155+
});
156+
})
157+
.catch((ex) => {
158+
sendResponse({
159+
type: "sail",
160+
error: ex.toString(),
161+
});
162+
163+
});
164+
} else {
165+
// Check if we can get a sail URL.
166+
connector.connect().then(() => {
167+
sendResponse({
168+
type: "sail",
169+
})
170+
}).catch((ex) => {
171+
sendResponse({
172+
type: "sail",
173+
error: ex.toString(),
174+
});
57175
});
58-
});
176+
}
59177

60178
return true;
61179
}
62180
});
181+
182+
// Open the config page when the browser action is clicked.
183+
chrome.browserAction.onClicked.addListener(() => {
184+
const url = chrome.runtime.getURL("/out/config.html");
185+
chrome.tabs.create({ url });
186+
});

extension/src/common.scss

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
$bg-color: #fff;
2+
$bg-color-header: #f4f7fc;
3+
$bg-color-status: #c4d5ff;
4+
$bg-color-status-error: #ef9a9a;
5+
$bg-color-status-darker: #b1c0e6;
6+
$bg-color-input: #f4f7fc;
7+
$text-color: #677693;
8+
$text-color-darker: #000a44;
9+
$text-color-brand: #4569fc;
10+
$text-color-status: #486cff;
11+
$text-color-status-error: #8b1515;
12+
$text-color-link: #4d72f0;
13+
14+
$font-family: "aktiv grotesk", -apple-system, roboto, serif;
15+
16+
* {
17+
box-sizing: border-box;
18+
}
19+
20+
h1, h2, h3 {
21+
color: $text-color-darker;
22+
font-weight: bold;
23+
}
24+
25+
.error {
26+
color: $text-color-status-error;
27+
}
28+
.small {
29+
margin-top: 6px;
30+
margin-bottom: 6px;
31+
font-size: 0.8em;
32+
}
33+
34+
input[type=text] {
35+
padding: 6px 9px;
36+
border: solid $text-color-darker 1px;
37+
border-radius: 3px;
38+
background-color: $bg-color-input;
39+
outline: 0;
40+
}
41+
42+
button {
43+
padding: 7px 10px;
44+
border: none;
45+
border-radius: 3px;
46+
background-color: $bg-color-status;
47+
color: $text-color-status;
48+
font-weight: 600;
49+
outline: 0;
50+
cursor: pointer;
51+
52+
&:hover {
53+
background-color: $bg-color-status-darker;
54+
}
55+
}
56+
57+
a {
58+
color: $text-color-link;
59+
text-decoration: none;
60+
61+
&:hover {
62+
text-decoration: underline;
63+
}
64+
}

0 commit comments

Comments
 (0)