Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,6 @@
],
"menus": {
"view/title": [
{
"command": "tailscale.refreshServe",
"group": "overflow",
"when": "view == tailscale-serve-view"
},
{
"command": "tailscale.resetServe",
"group": "overflow",
Expand Down Expand Up @@ -145,11 +140,6 @@
"title": "Reset",
"category": "Tailscale"
},
{
"command": "tailscale.refreshServe",
"title": "Refresh",
"category": "Tailscale"
},
{
"command": "tailscale.openFunnelPanel",
"title": "Open Funnel Panel",
Expand Down
9 changes: 0 additions & 9 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,10 @@ export async function activate(context: vscode.ExtensionContext) {
tailscaleInstance
);

context.subscriptions.push(
vscode.commands.registerCommand('tailscale.refreshServe', () => {
Logger.info('called tailscale.refreshServe', 'command');
servePanelProvider.refreshState();
})
);

context.subscriptions.push(
vscode.commands.registerCommand('tailscale.resetServe', async () => {
Logger.info('called tailscale.resetServe', 'command');
await tailscaleInstance.serveDelete();
servePanelProvider.refreshState();

vscode.window.showInformationMessage('Serve configuration reset');
})
);
Expand Down
75 changes: 20 additions & 55 deletions src/serve-panel-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ export class ServePanelProvider implements vscode.WebviewViewProvider {
this._view.webview.postMessage(message);
}

public async refreshState() {
this.postMessage({
type: 'refreshState',
});
}

resolveWebviewView(webviewView: vscode.WebviewView) {
this._view = webviewView;
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
Expand All @@ -33,69 +27,40 @@ export class ServePanelProvider implements vscode.WebviewViewProvider {
};

webviewView.webview.onDidReceiveMessage(async (m: Message) => {
switch (m.type) {
case 'refreshState': {
Logger.info('Called refreshState', 'serve-panel');
await this.refreshState();
break;
}

case 'deleteServe': {
Logger.info('Called deleteServe', 'serve-panel');
try {
await this.ts.serveDelete(m.params);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
vscode.window.showErrorMessage('Unable to delete serve', e.message);
}

await this.refreshState();
break;
}

case 'addServe': {
Logger.info('Called addServe', 'serve-panel');
await this.ts.serveAdd(m.params);
await this.refreshState();
break;
}

case 'setFunnel': {
Logger.info('Called setFunnel', 'serve-panel');
try {
await this.ts.setFunnel(parseInt(m.params.port), m.params.allow);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
vscode.window.showErrorMessage('Unable to toggle funnel', e.message);
}
const { type } = m;
Logger.info(`called ${type}`, 'serve-panel');

await this.refreshState();
break;
}
let response;

case 'resetServe': {
Logger.info('Called resetServe', 'serve-panel');
switch (type) {
case 'relayRequest': {
const { id, endpoint, method } = m;
Logger.info(`${id}, ${endpoint}, ${method}`, 'serve-panel');
try {
await this.ts.serveDelete();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
vscode.window.showErrorMessage('Unable to delete serve', e.message);
response = await this.ts.performFetch(endpoint, method, m.data);
Logger.info(`response: ${JSON.stringify(response)}`, 'serve-panel');
this.postMessage({
id,
endpoint,
method,
type: 'relayResponse',
data: response,
});
} catch (e) {
vscode.window.showErrorMessage(`${e}`);
}

await this.refreshState();
break;
}

case 'writeToClipboard': {
Logger.info('Called writeToClipboard', 'serve-panel');
vscode.env.clipboard.writeText(m.params.text);
vscode.env.clipboard.writeText(m.data.text);
vscode.window.showInformationMessage('Copied to clipboard');
break;
}

case 'openLink': {
Logger.info(`Called openLink: ${m.params.url}`, 'serve-panel');
vscode.env.openExternal(vscode.Uri.parse(m.params.url));
vscode.env.openExternal(vscode.Uri.parse(m.data.url));
break;
}

Expand Down
83 changes: 26 additions & 57 deletions src/tailscale/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,83 +221,52 @@ export class Tailscale {
}

async serveStatus(): Promise<ServeStatus> {
if (!this.url) {
throw new Error('uninitialized client');
}
try {
const resp = await fetch(`${this.url}/serve`, {
headers: {
Authorization: 'Basic ' + this.authkey,
},
});

const status = (await resp.json()) as ServeStatus;
return status;
} catch (e) {
Logger.error(`error calling status: ${JSON.stringify(e, null, 2)}`);
throw e;
}
return (await this.performFetch('/serve')) as ServeStatus;
}

async serveAdd(p: ServeParams) {
if (!this.url) {
throw new Error('uninitialized client');
}
try {
const resp = await fetch(`${this.url}/serve`, {
method: 'POST',
headers: {
Authorization: 'Basic ' + this.authkey,
},
body: JSON.stringify(p),
});
if (!resp.ok) {
throw new Error('/serve failed');
}
} catch (e) {
Logger.info(`error adding serve: ${e}`);
throw e;
}
await this.performFetch('/serve', 'POST', p);
}

async serveDelete(p?: ServeParams) {
if (!this.url) {
throw new Error('uninitialized client');
}
try {
const resp = await fetch(`${this.url}/serve`, {
method: 'DELETE',
headers: {
Authorization: 'Basic ' + this.authkey,
},
body: JSON.stringify(p),
});
if (!resp.ok) {
throw new Error('/serve failed');
}
} catch (e) {
Logger.info(`error deleting serve: ${e}`);
throw e;
}
await this.performFetch('/serve', 'DELETE', p);
}

async setFunnel(port: number, on: boolean) {
await this.performFetch('/funnel', 'POST', { port, on });
}

async performFetch(endpoint: string, method = 'GET', body?: unknown) {
if (!this.url) {
throw new Error('uninitialized client');
}

try {
const resp = await fetch(`${this.url}/funnel`, {
method: 'POST',
const resp = await fetch(`${this.url}${endpoint}`, {
method,
headers: {
Authorization: 'Basic ' + this.authkey,
},
body: JSON.stringify({ port, on }),
body: body !== undefined && typeof body === 'object' ? JSON.stringify(body) : undefined,
});

if (!resp.ok) {
throw new Error('/serve failed');
Logger.error(`${endpoint} failed: ${JSON.stringify(resp)}`);
throw new Error(`${endpoint} failed`);
}

const text = await resp.text();

try {
return JSON.parse(text);
// eslint-disable-next-line no-empty
} catch {
Logger.error(`failed to parse json: ${text}`);
}

return text;
} catch (e) {
Logger.info(`error deleting serve: ${e}`);
Logger.info(`error in ${method} ${endpoint}: ${e}`);
throw e;
}
}
Expand Down
77 changes: 37 additions & 40 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,73 +80,69 @@ export interface Version {
* Messages sent from the webview to the extension.
*/

interface RefreshState {
type: 'refreshState';
interface RequestBase {
id?: number;
type: string;
data?: unknown;
}

interface DeleteServe {
type: 'deleteServe';
params: ServeParams;
interface RelayRequestBase extends RequestBase {
type: 'relayRequest';
endpoint: string;
method: string;
}

interface AddServe {
type: 'addServe';
params: ServeParams;
interface RelayServeRequest extends RelayRequestBase {
endpoint: '/serve';
method: 'GET' | 'POST' | 'DELETE';
}

interface ResetServe {
type: 'resetServe';
}

interface SetFunnel {
type: 'setFunnel';
params: {
port: string;
allow: boolean;
};
}

interface WriteToClipboard {
interface WriteToClipboard extends RequestBase {
type: 'writeToClipboard';
params: {
data: {
text: string;
};
}

interface OpenLink {
interface OpenLink extends RequestBase {
type: 'openLink';
params: {
data: {
url: string;
};
}

export type Message =
| RefreshState
| DeleteServe
| AddServe
| ResetServe
| SetFunnel
| WriteToClipboard
| OpenLink
| SudoPrompt;

interface SudoPrompt {
id?: number;
type: 'sudoPrompt';
operation: 'add' | 'delete';
params?: ServeParams;
}

export type Message = RelayServeRequest | WriteToClipboard | OpenLink | SudoPrompt;
export type MessageWithId = Omit<Message, 'id'> & { id: number };

/**
* Messages sent from the extension to the webview.
*/

interface UpdateState {
type: 'updateState';
state: ServeConfig;
interface ResponseBase {
id?: number;
type: string;
data?: unknown;
error?: string;
}

interface RelayResponseBase extends Omit<RelayRequestBase, 'type'>, Omit<ResponseBase, 'type'> {
id?: number;
endpoint: string;
method: string;
body?: unknown;
error?: string;
}

interface RefreshState {
type: 'refreshState';
export interface RelayServeResponse extends RelayResponseBase {
type: 'relayResponse';
body?: ServeStatus;
}

interface WebpackOk {
Expand All @@ -161,7 +157,8 @@ interface WebpackStillOk {
type: 'webpackStillOk';
}

export type WebviewData = UpdateState | RefreshState | WebpackOk | WebpackInvalid | WebpackStillOk;
export type Responses = RelayServeResponse;
export type WebviewData = Responses | WebpackOk | WebpackInvalid | WebpackStillOk;
export type WebviewEvent = Event & { data: WebviewData };

export interface NewPortNotification {
Expand Down
4 changes: 2 additions & 2 deletions src/vscode-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class VSCodeWrapper {
public writeToClipboard(text: string): void {
this.postMessage({
type: 'writeToClipboard',
params: {
data: {
text,
},
});
Expand All @@ -20,7 +20,7 @@ class VSCodeWrapper {
public openLink(url: string): void {
this.postMessage({
type: 'openLink',
params: {
data: {
url,
},
});
Expand Down
Loading