@@ -57,7 +57,7 @@ const coderSessionTokenHeader = "Coder-Session-Token";
5757 */
5858export 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