Skip to content

Commit c1fe4a5

Browse files
committed
serve/simple: Use message passing instead of HTTP
Implements a frameworks to support asynchronous requests using messages. Signed-off-by: Tyler Smalley <tyler@tailscale.com>
1 parent d81d37e commit c1fe4a5

File tree

12 files changed

+184
-229
lines changed

12 files changed

+184
-229
lines changed

package.json

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,6 @@
112112
],
113113
"menus": {
114114
"view/title": [
115-
{
116-
"command": "tailscale.refreshServe",
117-
"group": "overflow",
118-
"when": "view == tailscale-serve-view"
119-
},
120115
{
121116
"command": "tailscale.resetServe",
122117
"group": "overflow",
@@ -145,11 +140,6 @@
145140
"title": "Reset",
146141
"category": "Tailscale"
147142
},
148-
{
149-
"command": "tailscale.refreshServe",
150-
"title": "Refresh",
151-
"category": "Tailscale"
152-
},
153143
{
154144
"command": "tailscale.openFunnelPanel",
155145
"title": "Open Funnel Panel",

src/extension.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,10 @@ export async function activate(context: vscode.ExtensionContext) {
4545
tailscaleInstance
4646
);
4747

48-
context.subscriptions.push(
49-
vscode.commands.registerCommand('tailscale.refreshServe', () => {
50-
Logger.info('called tailscale.refreshServe', 'command');
51-
servePanelProvider.refreshState();
52-
})
53-
);
54-
5548
context.subscriptions.push(
5649
vscode.commands.registerCommand('tailscale.resetServe', async () => {
5750
Logger.info('called tailscale.resetServe', 'command');
5851
await tailscaleInstance.serveDelete();
59-
servePanelProvider.refreshState();
60-
6152
vscode.window.showInformationMessage('Serve configuration reset');
6253
})
6354
);

src/serve-panel-provider.ts

Lines changed: 20 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@ export class ServePanelProvider implements vscode.WebviewViewProvider {
1818
this._view.webview.postMessage(message);
1919
}
2020

21-
public async refreshState() {
22-
this.postMessage({
23-
type: 'refreshState',
24-
});
25-
}
26-
2721
resolveWebviewView(webviewView: vscode.WebviewView) {
2822
this._view = webviewView;
2923
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
@@ -33,69 +27,35 @@ export class ServePanelProvider implements vscode.WebviewViewProvider {
3327
};
3428

3529
webviewView.webview.onDidReceiveMessage(async (m: Message) => {
36-
switch (m.type) {
37-
case 'refreshState': {
38-
Logger.info('Called refreshState', 'serve-panel');
39-
await this.refreshState();
40-
break;
41-
}
42-
43-
case 'deleteServe': {
44-
Logger.info('Called deleteServe', 'serve-panel');
45-
try {
46-
await this.ts.serveDelete(m.params);
47-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48-
} catch (e: any) {
49-
vscode.window.showErrorMessage('Unable to delete serve', e.message);
50-
}
51-
52-
await this.refreshState();
53-
break;
54-
}
55-
56-
case 'addServe': {
57-
Logger.info('Called addServe', 'serve-panel');
58-
await this.ts.serveAdd(m.params);
59-
await this.refreshState();
60-
break;
61-
}
62-
63-
case 'setFunnel': {
64-
Logger.info('Called setFunnel', 'serve-panel');
65-
try {
66-
await this.ts.setFunnel(parseInt(m.params.port), m.params.allow);
67-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68-
} catch (e: any) {
69-
vscode.window.showErrorMessage('Unable to toggle funnel', e.message);
70-
}
71-
72-
await this.refreshState();
73-
break;
74-
}
75-
76-
case 'resetServe': {
77-
Logger.info('Called resetServe', 'serve-panel');
78-
try {
79-
await this.ts.serveDelete();
80-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
81-
} catch (e: any) {
82-
vscode.window.showErrorMessage('Unable to delete serve', e.message);
83-
}
84-
85-
await this.refreshState();
30+
const { type } = m;
31+
Logger.info(`called ${type}`, 'serve-panel');
32+
33+
let response;
34+
35+
switch (type) {
36+
case 'relayRequest': {
37+
const { id, endpoint, method } = m;
38+
Logger.info(`${id}, ${endpoint}, ${method}`, 'serve-panel');
39+
response = await this.ts.performFetch(endpoint, method, m.data);
40+
Logger.info(`response: ${JSON.stringify(response)}`, 'serve-panel');
41+
this.postMessage({
42+
id,
43+
endpoint,
44+
method,
45+
type: 'relayResponse',
46+
data: response,
47+
});
8648
break;
8749
}
8850

8951
case 'writeToClipboard': {
90-
Logger.info('Called writeToClipboard', 'serve-panel');
91-
vscode.env.clipboard.writeText(m.params.text);
52+
vscode.env.clipboard.writeText(m.data.text);
9253
vscode.window.showInformationMessage('Copied to clipboard');
9354
break;
9455
}
9556

9657
case 'openLink': {
97-
Logger.info(`Called openLink: ${m.params.url}`, 'serve-panel');
98-
vscode.env.openExternal(vscode.Uri.parse(m.params.url));
58+
vscode.env.openExternal(vscode.Uri.parse(m.data.url));
9959
break;
10060
}
10161

src/tailscale/cli.ts

Lines changed: 23 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -221,83 +221,49 @@ export class Tailscale {
221221
}
222222

223223
async serveStatus(): Promise<ServeStatus> {
224-
if (!this.url) {
225-
throw new Error('uninitialized client');
226-
}
227-
try {
228-
const resp = await fetch(`${this.url}/serve`, {
229-
headers: {
230-
Authorization: 'Basic ' + this.authkey,
231-
},
232-
});
233-
234-
const status = (await resp.json()) as ServeStatus;
235-
return status;
236-
} catch (e) {
237-
Logger.error(`error calling status: ${JSON.stringify(e, null, 2)}`);
238-
throw e;
239-
}
224+
return (await this.performFetch('/serve')) as ServeStatus;
240225
}
241226

242227
async serveAdd(p: ServeParams) {
243-
if (!this.url) {
244-
throw new Error('uninitialized client');
245-
}
246-
try {
247-
const resp = await fetch(`${this.url}/serve`, {
248-
method: 'POST',
249-
headers: {
250-
Authorization: 'Basic ' + this.authkey,
251-
},
252-
body: JSON.stringify(p),
253-
});
254-
if (!resp.ok) {
255-
throw new Error('/serve failed');
256-
}
257-
} catch (e) {
258-
Logger.info(`error adding serve: ${e}`);
259-
throw e;
260-
}
228+
await this.performFetch('/serve', 'POST', p);
261229
}
262230

263231
async serveDelete(p?: ServeParams) {
264-
if (!this.url) {
265-
throw new Error('uninitialized client');
266-
}
267-
try {
268-
const resp = await fetch(`${this.url}/serve`, {
269-
method: 'DELETE',
270-
headers: {
271-
Authorization: 'Basic ' + this.authkey,
272-
},
273-
body: JSON.stringify(p),
274-
});
275-
if (!resp.ok) {
276-
throw new Error('/serve failed');
277-
}
278-
} catch (e) {
279-
Logger.info(`error deleting serve: ${e}`);
280-
throw e;
281-
}
232+
await this.performFetch('/serve', 'DELETE', p);
282233
}
283234

284235
async setFunnel(port: number, on: boolean) {
236+
await this.performFetch('/funnel', 'POST', { port, on });
237+
}
238+
239+
async performFetch(endpoint: string, method = 'GET', body?: unknown) {
285240
if (!this.url) {
286241
throw new Error('uninitialized client');
287242
}
243+
288244
try {
289-
const resp = await fetch(`${this.url}/funnel`, {
290-
method: 'POST',
245+
const resp = await fetch(`${this.url}${endpoint}`, {
246+
method,
291247
headers: {
292248
Authorization: 'Basic ' + this.authkey,
293249
},
294-
body: JSON.stringify({ port, on }),
250+
body: body !== undefined && typeof body === 'object' ? JSON.stringify(body) : undefined,
295251
});
252+
296253
if (!resp.ok) {
297-
throw new Error('/serve failed');
254+
throw new Error(`${endpoint} failed`);
298255
}
256+
257+
const text = await resp.text();
258+
259+
try {
260+
return JSON.parse(text);
261+
// eslint-disable-next-line no-empty
262+
} catch {}
263+
264+
return text;
299265
} catch (e) {
300-
Logger.info(`error deleting serve: ${e}`);
266+
Logger.info(`error in ${method} ${endpoint}: ${e}`);
301267
throw e;
302268
}
303269
}

src/types.ts

Lines changed: 36 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -80,73 +80,68 @@ export interface Version {
8080
* Messages sent from the webview to the extension.
8181
*/
8282

83-
interface RefreshState {
84-
type: 'refreshState';
83+
interface RequestBase {
84+
id?: number;
85+
type: string;
86+
data?: unknown;
8587
}
8688

87-
interface DeleteServe {
88-
type: 'deleteServe';
89-
params: ServeParams;
89+
interface RelayRequestBase extends RequestBase {
90+
type: 'relayRequest';
91+
endpoint: string;
92+
method: string;
9093
}
9194

92-
interface AddServe {
93-
type: 'addServe';
94-
params: ServeParams;
95+
interface RelayServeRequest extends RelayRequestBase {
96+
endpoint: '/serve';
97+
method: 'GET' | 'POST' | 'DELETE';
9598
}
9699

97-
interface ResetServe {
98-
type: 'resetServe';
99-
}
100-
101-
interface SetFunnel {
102-
type: 'setFunnel';
103-
params: {
104-
port: string;
105-
allow: boolean;
106-
};
107-
}
108-
109-
interface WriteToClipboard {
100+
interface WriteToClipboard extends RequestBase {
110101
type: 'writeToClipboard';
111-
params: {
102+
data: {
112103
text: string;
113104
};
114105
}
115106

116-
interface OpenLink {
107+
interface OpenLink extends RequestBase {
117108
type: 'openLink';
118-
params: {
109+
data: {
119110
url: string;
120111
};
121112
}
122113

123-
export type Message =
124-
| RefreshState
125-
| DeleteServe
126-
| AddServe
127-
| ResetServe
128-
| SetFunnel
129-
| WriteToClipboard
130-
| OpenLink
131-
| SudoPrompt;
132-
133114
interface SudoPrompt {
134115
type: 'sudoPrompt';
135116
operation: 'add' | 'delete';
136117
params?: ServeParams;
137118
}
138119

120+
export type Message = RelayServeRequest | WriteToClipboard | OpenLink | SudoPrompt;
121+
export type MessageWithId = Omit<Message, 'id'> & { id: number };
122+
139123
/**
140124
* Messages sent from the extension to the webview.
141125
*/
142126

143-
interface UpdateState {
144-
type: 'updateState';
145-
state: ServeConfig;
127+
interface ResponseBase {
128+
id?: number;
129+
type: string;
130+
data?: unknown;
131+
error?: string;
132+
}
133+
134+
interface RelayResponseBase extends Omit<RelayRequestBase, 'type'>, Omit<ResponseBase, 'type'> {
135+
id?: number;
136+
endpoint: string;
137+
method: string;
138+
body?: unknown;
139+
error?: string;
146140
}
147141

148-
interface RefreshState {
149-
type: 'refreshState';
142+
export interface RelayServeResponse extends RelayResponseBase {
143+
type: 'relayResponse';
144+
body?: ServeStatus;
150145
}
151146

152147
interface WebpackOk {
@@ -161,7 +156,8 @@ interface WebpackStillOk {
161156
type: 'webpackStillOk';
162157
}
163158

164-
export type WebviewData = UpdateState | RefreshState | WebpackOk | WebpackInvalid | WebpackStillOk;
159+
export type Responses = RelayServeResponse;
160+
export type WebviewData = Responses | WebpackOk | WebpackInvalid | WebpackStillOk;
165161
export type WebviewEvent = Event & { data: WebviewData };
166162

167163
export interface NewPortNotification {

src/vscode-api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class VSCodeWrapper {
1111
public writeToClipboard(text: string): void {
1212
this.postMessage({
1313
type: 'writeToClipboard',
14-
params: {
14+
data: {
1515
text,
1616
},
1717
});
@@ -20,7 +20,7 @@ class VSCodeWrapper {
2020
public openLink(url: string): void {
2121
this.postMessage({
2222
type: 'openLink',
23-
params: {
23+
data: {
2424
url,
2525
},
2626
});

0 commit comments

Comments
 (0)