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

Commit b7e4c08

Browse files
committed
move WebSocket connection to background script
1 parent bcf9063 commit b7e4c08

File tree

4 files changed

+133
-85
lines changed

4 files changed

+133
-85
lines changed

extension/src/background.ts

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExtensionMessage } from "./common";
1+
import { ExtensionMessage, WebSocketMessage } from "./common";
22

33
export class SailConnector {
44
private port: chrome.runtime.Port;
@@ -13,7 +13,7 @@ export class SailConnector {
1313
this.port = chrome.runtime.connectNative("com.coder.sail");
1414
this.port.onMessage.addListener((message) => {
1515
if (!message.url) {
16-
return reject("Invalid handshaking message");
16+
return reject("Invalid handshake message");
1717
}
1818

1919
resolve(message.url);
@@ -37,25 +37,90 @@ export class SailConnector {
3737
}
3838
}
3939

40+
// Get the sail URL.
4041
const connector = new SailConnector();
4142
let connectError: string | undefined = "Not connected yet";
4243
connector.connect().then(() => connectError = undefined).catch((ex) => {
4344
connectError = `Failed to connect: ${ex.toString()}`;
4445
});
4546

47+
// doConnection attempts to connect to Sail over WebSocket.
48+
const doConnection = (socketUrl: string, projectUrl: string, onMessage: (data: WebSocketMessage) => void): Promise<WebSocket> => {
49+
return new Promise<WebSocket>((resolve, reject) => {
50+
const socket = new WebSocket(socketUrl);
51+
socket.addEventListener("open", () => {
52+
socket.send(JSON.stringify({
53+
project: projectUrl,
54+
}));
55+
56+
resolve(socket);
57+
});
58+
socket.addEventListener("close", (event) => {
59+
const v = `sail socket was closed: ${event.code}`;
60+
onMessage({ type: "error", v });
61+
reject(v);
62+
});
63+
64+
socket.addEventListener("message", (event) => {
65+
const data = JSON.parse(event.data);
66+
if (!data) {
67+
return;
68+
}
69+
const type = data.type;
70+
const content = type === "data" ? atob(data.v) : data.v;
71+
72+
switch (type) {
73+
case "data":
74+
case "error":
75+
onMessage({ type, v: content });
76+
break;
77+
default:
78+
throw new Error("unknown message type: " + type);
79+
}
80+
});
81+
});
82+
};
83+
4684
chrome.runtime.onMessage.addListener((data: ExtensionMessage, sender, sendResponse: (msg: ExtensionMessage) => void) => {
4785
if (data.type === "sail") {
48-
connector.connect().then((url) => {
49-
sendResponse({
50-
type: "sail",
51-
url,
86+
if (data.projectUrl) {
87+
// Launch a sail connection.
88+
if (!sender.tab) {
89+
// Only allow from content scripts.
90+
return;
91+
}
92+
93+
// onMessage forwards WebSocketMessages to the tab that
94+
// launched Sail.
95+
const onMessage = (message: WebSocketMessage) => {
96+
chrome.tabs.sendMessage(sender.tab.id, message);
97+
};
98+
connector.connect().then((sailUrl) => {
99+
const socketUrl = sailUrl.replace("http:", "ws:") + "/api/v1/run";
100+
return doConnection(socketUrl, data.projectUrl, onMessage).then((conn) => {
101+
sendResponse({
102+
type: "sail",
103+
});
104+
});
105+
}).catch((ex) => {
106+
sendResponse({
107+
type: "sail",
108+
error: ex.toString(),
109+
});
52110
})
53-
}).catch((ex) => {
54-
sendResponse({
55-
type: "sail",
56-
error: ex.toString(),
111+
} else {
112+
// Check if we can get a sail URL.
113+
connector.connect().then(() => {
114+
sendResponse({
115+
type: "sail",
116+
})
117+
}).catch((ex) => {
118+
sendResponse({
119+
type: "sail",
120+
error: ex.toString(),
121+
});
57122
});
58-
});
123+
}
59124

60125
return true;
61126
}

extension/src/common.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,51 @@
11
export interface ExtensionMessage {
22
readonly type: "sail";
33
readonly error?: string;
4-
readonly url?: string;
4+
readonly projectUrl?: string;
55
}
66

7-
export const requestSail = (): Promise<string> => {
8-
return new Promise<string>((resolve, reject) => {
7+
export interface WebSocketMessage {
8+
readonly type: string;
9+
readonly v: any;
10+
}
11+
12+
export const launchSail = (projectUrl: string, onMessage: (WebSocketMessage) => void): Promise<void> => {
13+
const listener = (message: any) => {
14+
if (message.type && message.v) {
15+
onMessage(message);
16+
}
17+
};
18+
chrome.runtime.onMessage.addListener(listener);
19+
20+
return new Promise<void>((resolve, reject) => {
921
chrome.runtime.sendMessage({
1022
type: "sail",
11-
}, (response) => {
23+
projectUrl: projectUrl,
24+
}, (response: ExtensionMessage) => {
1225
if (response.type === "sail") {
1326
if (response.error) {
27+
chrome.runtime.onMessage.removeListener(listener);
1428
return reject(response.error);
1529
}
16-
17-
resolve(response.url);
30+
31+
resolve();
32+
}
33+
});
34+
});
35+
};
36+
37+
export const sailAvailable = (): Promise<void> => {
38+
return new Promise<void>((resolve, reject) => {
39+
chrome.runtime.sendMessage({
40+
type: "sail",
41+
}, (response: ExtensionMessage) => {
42+
if (response.type === "sail") {
43+
if (response.error) {
44+
return reject(response.error);
45+
}
46+
47+
resolve();
1848
}
1949
});
2050
});
21-
};
51+
};

extension/src/content.ts

Lines changed: 16 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,4 @@
1-
import { requestSail } from "./common";
2-
3-
const doConnection = (socketUrl: string, projectUrl: string, onMessage: (data: {
4-
readonly type: "data" | "error";
5-
readonly v: string;
6-
}) => void): Promise<WebSocket> => {
7-
return new Promise<WebSocket>((resolve, reject) => {
8-
const socket = new WebSocket(socketUrl);
9-
socket.addEventListener("open", () => {
10-
socket.send(JSON.stringify({
11-
project: projectUrl,
12-
}));
13-
14-
resolve(socket);
15-
});
16-
socket.addEventListener("close", (event) => {
17-
reject(`socket closed: ${event.code}`);
18-
});
19-
20-
socket.addEventListener("message", (event) => {
21-
const data = JSON.parse(event.data);
22-
if (!data) {
23-
return;
24-
}
25-
const type = data.type;
26-
const content = type === "data" ? atob(data.v) : data.v;
27-
28-
switch (type) {
29-
case "data":
30-
case "error":
31-
onMessage({ type, v: content });
32-
break;
33-
default:
34-
throw new Error("unknown message type: " + type);
35-
}
36-
});
37-
});
38-
};
1+
import { WebSocketMessage, launchSail, sailAvailable } from "./common";
392

403
const ensureButton = (): void | HTMLElement => {
414
const buttonId = "openinsail";
@@ -47,15 +10,13 @@ const ensureButton = (): void | HTMLElement => {
4710
const githubMenu = document.querySelector(".get-repo-select-menu");
4811
let button: HTMLElement | void;
4912
if (githubMenu) {
50-
// GitHub
5113
button = createGitHubButton();
5214

5315
githubMenu.parentElement.appendChild(button);
5416

5517
}
5618
const gitlabMenu = document.querySelector(".project-repo-buttons") as HTMLElement;
5719
if (gitlabMenu) {
58-
// GitLab
5920
button = createGitLabButton(gitlabMenu);
6021
}
6122

@@ -88,6 +49,7 @@ const ensureButton = (): void | HTMLElement => {
8849
bottom: 0;
8950
right: 0;
9051
width: 35vw;
52+
min-width: 500px;
9153
height: 40vh;
9254
background: black;
9355
padding: 10px;
@@ -116,27 +78,19 @@ const ensureButton = (): void | HTMLElement => {
11678
x.title = "Close";
11779
term.appendChild(x);
11880

119-
requestSail().then((socketUrl) => {
120-
return doConnection(socketUrl.replace("http:", "ws:") + "/api/v1/run", cloneUrl, (data) => {
121-
if (data.type === "data") {
122-
text.innerText += data.v;
123-
term.scrollTop = term.scrollHeight;
124-
} else if (data.type === "error") {
125-
text.innerText += data.v;
126-
term.scrollTop = term.scrollHeight;
127-
setTimeout(() => {
128-
btn.innerText = "Open in Sail";
129-
btn.classList.remove("disabled");
130-
term.remove();
131-
}, 5000);
132-
}
133-
});
134-
}).then((socket) => {
135-
socket.addEventListener("close", () => {
136-
btn.innerText = "Open in Sail";
137-
btn.classList.remove("disabled");
138-
term.remove();
139-
});
81+
launchSail(cloneUrl, (data: WebSocketMessage) => {
82+
if (data.type === "data") {
83+
text.innerText += data.v;
84+
term.scrollTop = term.scrollHeight;
85+
} else if (data.type === "error") {
86+
text.innerText += data.v;
87+
term.scrollTop = term.scrollHeight;
88+
setTimeout(() => {
89+
btn.innerText = "Open in Sail";
90+
btn.classList.remove("disabled");
91+
term.remove();
92+
}, 5000);
93+
}
14094
}).catch((ex) => {
14195
btn.innerText = ex.toString();
14296
setTimeout(() => {
@@ -147,7 +101,7 @@ const ensureButton = (): void | HTMLElement => {
147101
});
148102
});
149103

150-
requestSail().then(() => (button as HTMLElement).classList.remove("disabled"))
104+
sailAvailable().then(() => (button as HTMLElement).classList.remove("disabled"))
151105
.catch((ex) => {
152106
(button as HTMLElement).style.opacity = "0.5";
153107
(button as HTMLElement).title = "Setup Sail using the extension icon in the top-right!";

extension/src/popup.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import { requestSail } from "./common";
1+
import { sailAvailable } from "./common";
22

33
const root = document.getElementById("root") as HTMLElement;
4-
// const projects = document.getElementById("projects") as HTMLUListElement;
5-
document.body.style.width = "150px";
4+
document.body.style.width = "250px";
65

7-
requestSail().then((url) => {
6+
sailAvailable().then(() => {
87
document.body.innerText = "Sail is setup and working properly!";
98
}).catch((ex) => {
109
const has = (str: string) => ex.toString().indexOf(str) !== -1;
1110

1211
if (has("not found") || has("forbidden")) {
13-
document.body.innerText = "After installing sail, run `sail install-for-chrome-ext`.";
12+
document.body.innerText = "After installing Sail, run `sail install-for-chrome-ext`.\n\n" + ex.toString();
1413
} else {
1514
document.body.innerText = ex.toString();
1615
}

0 commit comments

Comments
 (0)