Skip to content

Commit 9b2e4fc

Browse files
committed
Handle CoderApi review comments
1 parent e5bdad3 commit 9b2e4fc

File tree

1 file changed

+76
-94
lines changed

1 file changed

+76
-94
lines changed

src/api/coderApi.ts

Lines changed: 76 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const coderSessionTokenHeader = "Coder-Session-Token";
5757
*/
5858
export class CoderApi extends Api implements vscode.Disposable {
5959
private readonly reconnectingSockets = new Set<
60-
ReconnectingWebSocket<unknown>
60+
ReconnectingWebSocket<never>
6161
>();
6262

6363
private constructor(private readonly output: Logger) {
@@ -80,6 +80,16 @@ export class CoderApi extends Api implements vscode.Disposable {
8080
return client;
8181
}
8282

83+
getHost(): string | undefined {
84+
return this.getAxiosInstance().defaults.baseURL;
85+
}
86+
87+
getSessionToken(): string | undefined {
88+
return this.getAxiosInstance().defaults.headers.common[
89+
coderSessionTokenHeader
90+
] as string | undefined;
91+
}
92+
8393
/**
8494
* Set both host and token together. Useful for login/logout/switch to
8595
* avoid triggering multiple reconnection events.
@@ -88,14 +98,15 @@ export class CoderApi extends Api implements vscode.Disposable {
8898
host: string | undefined,
8999
token: string | undefined,
90100
): void => {
91-
const defaults = this.getAxiosInstance().defaults;
92-
const currentHost = defaults.baseURL;
93-
const currentToken = defaults.headers.common[coderSessionTokenHeader];
101+
const currentHost = this.getHost();
102+
const currentToken = this.getSessionToken();
94103

104+
// We cannot use the super.setHost/setSessionToken methods because they are shadowed here
105+
const defaults = this.getAxiosInstance().defaults;
95106
defaults.baseURL = host;
96107
defaults.headers.common[coderSessionTokenHeader] = token;
97108

98-
const hostChanged = currentHost !== host;
109+
const hostChanged = (currentHost || "") !== (host || "");
99110
const tokenChanged = currentToken !== token;
100111

101112
if (hostChanged || tokenChanged) {
@@ -109,16 +120,12 @@ export class CoderApi extends Api implements vscode.Disposable {
109120
}
110121
};
111122

112-
setSessionToken = (token: string): void => {
113-
const currentHost = this.getAxiosInstance().defaults.baseURL;
114-
this.setCredentials(currentHost, token);
123+
override setSessionToken = (token: string): void => {
124+
this.setCredentials(this.getHost(), token);
115125
};
116126

117-
setHost = (host: string | undefined): void => {
118-
const currentToken = this.getAxiosInstance().defaults.headers.common[
119-
coderSessionTokenHeader
120-
] as string | undefined;
121-
this.setCredentials(host, currentToken);
127+
override setHost = (host: string | undefined): void => {
128+
this.setCredentials(host, this.getSessionToken());
122129
};
123130

124131
/**
@@ -137,37 +144,49 @@ export class CoderApi extends Api implements vscode.Disposable {
137144
watchTargets: string[],
138145
options?: ClientOptions,
139146
) => {
140-
return this.createWebSocket<GetInboxNotificationResponse>({
141-
apiRoute: "/api/v2/notifications/inbox/watch",
142-
searchParams: {
143-
format: "plaintext",
144-
templates: watchTemplates.join(","),
145-
targets: watchTargets.join(","),
146-
},
147-
options,
148-
enableRetry: true,
149-
});
147+
const apiRoute = "/api/v2/notifications/inbox/watch";
148+
return this.createReconnectingSocket(
149+
() =>
150+
this.createOneWayWebSocket<GetInboxNotificationResponse>({
151+
apiRoute,
152+
searchParams: {
153+
format: "plaintext",
154+
templates: watchTemplates.join(","),
155+
targets: watchTargets.join(","),
156+
},
157+
options,
158+
}),
159+
apiRoute,
160+
);
150161
};
151162

152163
watchWorkspace = async (workspace: Workspace, options?: ClientOptions) => {
153-
return this.createWebSocketWithFallback({
154-
apiRoute: `/api/v2/workspaces/${workspace.id}/watch-ws`,
155-
fallbackApiRoute: `/api/v2/workspaces/${workspace.id}/watch`,
156-
options,
157-
enableRetry: true,
158-
});
164+
const apiRoute = `/api/v2/workspaces/${workspace.id}/watch-ws`;
165+
return this.createReconnectingSocket(
166+
() =>
167+
this.createStreamWithSseFallback({
168+
apiRoute,
169+
fallbackApiRoute: `/api/v2/workspaces/${workspace.id}/watch`,
170+
options,
171+
}),
172+
apiRoute,
173+
);
159174
};
160175

161176
watchAgentMetadata = async (
162177
agentId: WorkspaceAgent["id"],
163178
options?: ClientOptions,
164179
) => {
165-
return this.createWebSocketWithFallback({
166-
apiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata-ws`,
167-
fallbackApiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata`,
168-
options,
169-
enableRetry: true,
170-
});
180+
const apiRoute = `/api/v2/workspaceagents/${agentId}/watch-metadata-ws`;
181+
return this.createReconnectingSocket(
182+
() =>
183+
this.createStreamWithSseFallback({
184+
apiRoute,
185+
fallbackApiRoute: `/api/v2/workspaceagents/${agentId}/watch-metadata`,
186+
options,
187+
}),
188+
apiRoute,
189+
);
171190
};
172191

173192
watchBuildLogsByBuildId = async (
@@ -205,33 +224,13 @@ export class CoderApi extends Api implements vscode.Disposable {
205224
searchParams.append("after", lastLog.id.toString());
206225
}
207226

208-
return this.createWebSocket<TData>({
227+
return this.createOneWayWebSocket<TData>({
209228
apiRoute,
210229
searchParams,
211230
options,
212231
});
213232
}
214233

215-
private async createWebSocket<TData = unknown>(
216-
configs: Omit<OneWayWebSocketInit, "location"> & { enableRetry?: boolean },
217-
): Promise<UnidirectionalStream<TData>> {
218-
const { enableRetry, ...socketConfigs } = configs;
219-
220-
const socketFactory: SocketFactory<TData> = async () => {
221-
const baseUrlRaw = this.getAxiosInstance().defaults.baseURL;
222-
if (!baseUrlRaw) {
223-
throw new Error("No base URL set on REST client");
224-
}
225-
226-
return this.createOneWayWebSocket<TData>(socketConfigs);
227-
};
228-
229-
if (enableRetry) {
230-
return this.createReconnectingSocket(socketFactory, configs.apiRoute);
231-
}
232-
return socketFactory();
233-
}
234-
235234
private async createOneWayWebSocket<TData>(
236235
configs: Omit<OneWayWebSocketInit, "location">,
237236
): Promise<OneWayWebSocket<TData>> {
@@ -307,46 +306,34 @@ export class CoderApi extends Api implements vscode.Disposable {
307306
/**
308307
* Create a WebSocket connection with SSE fallback on 404.
309308
*
310-
* The factory tries WS first, falls back to SSE on 404. Since the factory
311-
* is called on every reconnect.
309+
* Tries WS first, falls back to SSE on 404.
312310
*
313311
* Note: The fallback on SSE ignores all passed client options except the headers.
314312
*/
315-
private async createWebSocketWithFallback(
313+
private async createStreamWithSseFallback(
316314
configs: Omit<OneWayWebSocketInit, "location"> & {
317315
fallbackApiRoute: string;
318-
enableRetry?: boolean;
319316
},
320317
): Promise<UnidirectionalStream<ServerSentEvent>> {
321-
const { fallbackApiRoute, enableRetry, ...socketConfigs } = configs;
322-
const socketFactory: SocketFactory<ServerSentEvent> = async () => {
323-
try {
324-
const ws =
325-
await this.createOneWayWebSocket<ServerSentEvent>(socketConfigs);
326-
return await this.waitForOpen(ws);
327-
} catch (error) {
328-
if (this.is404Error(error)) {
329-
this.output.warn(
330-
`WebSocket failed, using SSE fallback: ${socketConfigs.apiRoute}`,
331-
);
332-
const sse = this.createSseConnection(
333-
fallbackApiRoute,
334-
socketConfigs.searchParams,
335-
socketConfigs.options?.headers,
336-
);
337-
return await this.waitForOpen(sse);
338-
}
339-
throw error;
318+
const { fallbackApiRoute, ...socketConfigs } = configs;
319+
try {
320+
const ws =
321+
await this.createOneWayWebSocket<ServerSentEvent>(socketConfigs);
322+
return await this.waitForOpen(ws);
323+
} catch (error) {
324+
if (this.is404Error(error)) {
325+
this.output.warn(
326+
`WebSocket failed, using SSE fallback: ${socketConfigs.apiRoute}`,
327+
);
328+
const sse = this.createSseConnection(
329+
fallbackApiRoute,
330+
socketConfigs.searchParams,
331+
socketConfigs.options?.headers,
332+
);
333+
return await this.waitForOpen(sse);
340334
}
341-
};
342-
343-
if (enableRetry) {
344-
return this.createReconnectingSocket(
345-
socketFactory,
346-
socketConfigs.apiRoute,
347-
);
335+
throw error;
348336
}
349-
return socketFactory();
350337
}
351338

352339
/**
@@ -423,15 +410,10 @@ export class CoderApi extends Api implements vscode.Disposable {
423410
this.output,
424411
apiRoute,
425412
undefined,
426-
() =>
427-
this.reconnectingSockets.delete(
428-
reconnectingSocket as ReconnectingWebSocket<unknown>,
429-
),
413+
() => this.reconnectingSockets.delete(reconnectingSocket),
430414
);
431415

432-
this.reconnectingSockets.add(
433-
reconnectingSocket as ReconnectingWebSocket<unknown>,
434-
);
416+
this.reconnectingSockets.add(reconnectingSocket);
435417

436418
return reconnectingSocket;
437419
}

0 commit comments

Comments
 (0)