@@ -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,40 @@ 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+ return this . createReconnectingSocket ( ( ) =>
148+ this . createOneWayWebSocket < GetInboxNotificationResponse > ( {
149+ apiRoute : "/api/v2/notifications/inbox/watch" ,
150+ searchParams : {
151+ format : "plaintext" ,
152+ templates : watchTemplates . join ( "," ) ,
153+ targets : watchTargets . join ( "," ) ,
154+ } ,
155+ options,
156+ } ) ,
157+ ) ;
150158 } ;
151159
152160 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- } ) ;
161+ return this . createReconnectingSocket ( ( ) =>
162+ this . createStreamWithSseFallback ( {
163+ apiRoute : `/api/v2/workspaces/${ workspace . id } /watch-ws` ,
164+ fallbackApiRoute : `/api/v2/workspaces/${ workspace . id } /watch` ,
165+ options,
166+ } ) ,
167+ ) ;
159168 } ;
160169
161170 watchAgentMetadata = async (
162171 agentId : WorkspaceAgent [ "id" ] ,
163172 options ?: ClientOptions ,
164173 ) => {
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- } ) ;
174+ return this . createReconnectingSocket ( ( ) =>
175+ this . createStreamWithSseFallback ( {
176+ apiRoute : `/api/v2/workspaceagents/${ agentId } /watch-metadata-ws` ,
177+ fallbackApiRoute : `/api/v2/workspaceagents/${ agentId } /watch-metadata` ,
178+ options,
179+ } ) ,
180+ ) ;
171181 } ;
172182
173183 watchBuildLogsByBuildId = async (
@@ -205,33 +215,13 @@ export class CoderApi extends Api implements vscode.Disposable {
205215 searchParams . append ( "after" , lastLog . id . toString ( ) ) ;
206216 }
207217
208- return this . createWebSocket < TData > ( {
218+ return this . createOneWayWebSocket < TData > ( {
209219 apiRoute,
210220 searchParams,
211221 options,
212222 } ) ;
213223 }
214224
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-
235225 private async createOneWayWebSocket < TData > (
236226 configs : Omit < OneWayWebSocketInit , "location" > ,
237227 ) : Promise < OneWayWebSocket < TData > > {
@@ -307,46 +297,34 @@ export class CoderApi extends Api implements vscode.Disposable {
307297 /**
308298 * Create a WebSocket connection with SSE fallback on 404.
309299 *
310- * The factory tries WS first, falls back to SSE on 404. Since the factory
311- * is called on every reconnect.
300+ * Tries WS first, falls back to SSE on 404.
312301 *
313302 * Note: The fallback on SSE ignores all passed client options except the headers.
314303 */
315- private async createWebSocketWithFallback (
304+ private async createStreamWithSseFallback (
316305 configs : Omit < OneWayWebSocketInit , "location" > & {
317306 fallbackApiRoute : string ;
318- enableRetry ?: boolean ;
319307 } ,
320308 ) : 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 ;
309+ const { fallbackApiRoute, ...socketConfigs } = configs ;
310+ try {
311+ const ws =
312+ await this . createOneWayWebSocket < ServerSentEvent > ( socketConfigs ) ;
313+ return await this . waitForOpen ( ws ) ;
314+ } catch ( error ) {
315+ if ( this . is404Error ( error ) ) {
316+ this . output . warn (
317+ `WebSocket failed, using SSE fallback: ${ socketConfigs . apiRoute } ` ,
318+ ) ;
319+ const sse = this . createSseConnection (
320+ fallbackApiRoute ,
321+ socketConfigs . searchParams ,
322+ socketConfigs . options ?. headers ,
323+ ) ;
324+ return await this . waitForOpen ( sse ) ;
340325 }
341- } ;
342-
343- if ( enableRetry ) {
344- return this . createReconnectingSocket (
345- socketFactory ,
346- socketConfigs . apiRoute ,
347- ) ;
326+ throw error ;
348327 }
349- return socketFactory ( ) ;
350328 }
351329
352330 /**
@@ -416,22 +394,15 @@ export class CoderApi extends Api implements vscode.Disposable {
416394 */
417395 private async createReconnectingSocket < TData > (
418396 socketFactory : SocketFactory < TData > ,
419- apiRoute : string ,
420397 ) : Promise < ReconnectingWebSocket < TData > > {
421398 const reconnectingSocket = await ReconnectingWebSocket . create < TData > (
422399 socketFactory ,
423400 this . output ,
424- apiRoute ,
425401 undefined ,
426- ( ) =>
427- this . reconnectingSockets . delete (
428- reconnectingSocket as ReconnectingWebSocket < unknown > ,
429- ) ,
402+ ( ) => this . reconnectingSockets . delete ( reconnectingSocket ) ,
430403 ) ;
431404
432- this . reconnectingSockets . add (
433- reconnectingSocket as ReconnectingWebSocket < unknown > ,
434- ) ;
405+ this . reconnectingSockets . add ( reconnectingSocket ) ;
435406
436407 return reconnectingSocket ;
437408 }
0 commit comments