1- import globalAxios , {
1+ import {
22 AxiosError ,
3- type AxiosInstance ,
43 type InternalAxiosRequestConfig as RequestConfig ,
54} from 'axios' ;
65import { type IdentityApi , createApiRef } from '@backstage/core-plugin-api' ;
7- import {
8- type Workspace ,
9- CODER_API_REF_ID_PREFIX ,
10- WorkspacesRequest ,
11- WorkspacesResponse ,
12- User ,
13- } from '../typesConstants' ;
6+ import { CODER_API_REF_ID_PREFIX } from '../typesConstants' ;
147import type { UrlSync } from './UrlSync' ;
158import type { CoderWorkspacesConfig } from '../hooks/useCoderWorkspacesConfig' ;
16- import { CoderSdk } from './MockCoderSdk' ;
9+ import {
10+ type CoderSdk ,
11+ type User ,
12+ type Workspace ,
13+ type WorkspacesRequest ,
14+ type WorkspacesResponse ,
15+ makeCoderSdk ,
16+ } from './vendoredSdk' ;
1717
1818export const CODER_AUTH_HEADER_KEY = 'Coder-Session-Token' ;
1919const DEFAULT_REQUEST_TIMEOUT_MS = 20_000 ;
@@ -39,11 +39,6 @@ type CoderClientApi = Readonly<{
3939 * Return value indicates whether the token is valid.
4040 */
4141 syncToken : ( newToken : string ) => Promise < boolean > ;
42-
43- /**
44- * Cleans up a client instance, removing its links to all external systems.
45- */
46- cleanupClient : ( ) => void ;
4742} > ;
4843
4944const sharedCleanupAbortReason = new DOMException (
@@ -59,19 +54,30 @@ export const disabledClientError = new Error(
5954) ;
6055
6156type ConstructorInputs = Readonly < {
57+ /**
58+ * initialToken is strictly for testing, and is basically limited to making it
59+ * easier to test API logic.
60+ *
61+ * If trying to test UI logic that depends on CoderClient, it's probably
62+ * better to interact with CoderClient indirectly through the auth components,
63+ * so that React state is aware of everything.
64+ */
6265 initialToken ?: string ;
63- requestTimeoutMs ?: number ;
6466
67+ requestTimeoutMs ?: number ;
6568 apis : Readonly < {
6669 urlSync : UrlSync ;
6770 identityApi : IdentityApi ;
6871 } > ;
6972} > ;
7073
74+ type RequestInterceptor = (
75+ config : RequestConfig ,
76+ ) => RequestConfig | Promise < RequestConfig > ;
77+
7178export class CoderClient implements CoderClientApi {
7279 private readonly urlSync : UrlSync ;
7380 private readonly identityApi : IdentityApi ;
74- private readonly axios : AxiosInstance ;
7581
7682 private readonly requestTimeoutMs : number ;
7783 private readonly cleanupController : AbortController ;
@@ -82,33 +88,28 @@ export class CoderClient implements CoderClientApi {
8288
8389 constructor ( inputs : ConstructorInputs ) {
8490 const {
85- apis,
8691 initialToken,
92+ apis : { urlSync, identityApi } ,
8793 requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS ,
8894 } = inputs ;
89- const { urlSync, identityApi } = apis ;
9095
9196 this . urlSync = urlSync ;
9297 this . identityApi = identityApi ;
93- this . axios = globalAxios . create ( ) ;
94-
9598 this . loadedSessionToken = initialToken ;
9699 this . requestTimeoutMs = requestTimeoutMs ;
97-
98100 this . cleanupController = new AbortController ( ) ;
99101 this . trackedEjectionIds = new Set ( ) ;
100102
101- this . sdk = this . getBackstageCoderSdk ( this . axios ) ;
103+ this . sdk = this . createBackstageCoderSdk ( ) ;
102104 this . addBaseRequestInterceptors ( ) ;
103105 }
104106
105107 private addRequestInterceptor (
106- requestInterceptor : (
107- config : RequestConfig ,
108- ) => RequestConfig | Promise < RequestConfig > ,
108+ requestInterceptor : RequestInterceptor ,
109109 errorInterceptor ?: ( error : unknown ) => unknown ,
110110 ) : number {
111- const ejectionId = this . axios . interceptors . request . use (
111+ const axios = this . sdk . getAxiosInstance ( ) ;
112+ const ejectionId = axios . interceptors . request . use (
112113 requestInterceptor ,
113114 errorInterceptor ,
114115 ) ;
@@ -120,7 +121,8 @@ export class CoderClient implements CoderClientApi {
120121 private removeRequestInterceptorById ( ejectionId : number ) : boolean {
121122 // Even if we somehow pass in an ID that hasn't been associated with the
122123 // Axios instance, that's a noop. No harm in calling method no matter what
123- this . axios . interceptors . request . eject ( ejectionId ) ;
124+ const axios = this . sdk . getAxiosInstance ( ) ;
125+ axios . interceptors . request . eject ( ejectionId ) ;
124126
125127 if ( ! this . trackedEjectionIds . has ( ejectionId ) ) {
126128 return false ;
@@ -179,10 +181,8 @@ export class CoderClient implements CoderClientApi {
179181 this . addRequestInterceptor ( baseRequestInterceptor , baseErrorInterceptor ) ;
180182 }
181183
182- private getBackstageCoderSdk (
183- axiosInstance : AxiosInstance ,
184- ) : BackstageCoderSdk {
185- const baseSdk = new CoderSdk ( axiosInstance ) ;
184+ private createBackstageCoderSdk ( ) : BackstageCoderSdk {
185+ const baseSdk = makeCoderSdk ( ) ;
186186
187187 const getWorkspaces : ( typeof baseSdk ) [ 'getWorkspaces' ] = async request => {
188188 const workspacesRes = await baseSdk . getWorkspaces ( request ) ;
@@ -335,23 +335,6 @@ export class CoderClient implements CoderClientApi {
335335 this . removeRequestInterceptorById ( validationId ) ;
336336 }
337337 } ;
338-
339- cleanupClient = ( ) : void => {
340- this . trackedEjectionIds . forEach ( id => {
341- this . axios . interceptors . request . eject ( id ) ;
342- } ) ;
343-
344- this . trackedEjectionIds . clear ( ) ;
345- this . cleanupController . abort ( sharedCleanupAbortReason ) ;
346- this . loadedSessionToken = undefined ;
347-
348- // Not using this.addRequestInterceptor, because we don't want to track this
349- // interceptor at all. It should never be ejected once the client has been
350- // disabled
351- this . axios . interceptors . request . use ( ( ) => {
352- throw disabledClientError ;
353- } ) ;
354- } ;
355338}
356339
357340function appendParamToQuery (
0 commit comments