1
- import globalAxios , {
1
+ import {
2
2
AxiosError ,
3
- type AxiosInstance ,
4
3
type InternalAxiosRequestConfig as RequestConfig ,
5
4
} from 'axios' ;
6
5
import { 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' ;
14
7
import type { UrlSync } from './UrlSync' ;
15
8
import 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' ;
17
17
18
18
export const CODER_AUTH_HEADER_KEY = 'Coder-Session-Token' ;
19
19
const DEFAULT_REQUEST_TIMEOUT_MS = 20_000 ;
@@ -39,11 +39,6 @@ type CoderClientApi = Readonly<{
39
39
* Return value indicates whether the token is valid.
40
40
*/
41
41
syncToken : ( newToken : string ) => Promise < boolean > ;
42
-
43
- /**
44
- * Cleans up a client instance, removing its links to all external systems.
45
- */
46
- cleanupClient : ( ) => void ;
47
42
} > ;
48
43
49
44
const sharedCleanupAbortReason = new DOMException (
@@ -59,19 +54,30 @@ export const disabledClientError = new Error(
59
54
) ;
60
55
61
56
type 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
+ */
62
65
initialToken ?: string ;
63
- requestTimeoutMs ?: number ;
64
66
67
+ requestTimeoutMs ?: number ;
65
68
apis : Readonly < {
66
69
urlSync : UrlSync ;
67
70
identityApi : IdentityApi ;
68
71
} > ;
69
72
} > ;
70
73
74
+ type RequestInterceptor = (
75
+ config : RequestConfig ,
76
+ ) => RequestConfig | Promise < RequestConfig > ;
77
+
71
78
export class CoderClient implements CoderClientApi {
72
79
private readonly urlSync : UrlSync ;
73
80
private readonly identityApi : IdentityApi ;
74
- private readonly axios : AxiosInstance ;
75
81
76
82
private readonly requestTimeoutMs : number ;
77
83
private readonly cleanupController : AbortController ;
@@ -82,33 +88,28 @@ export class CoderClient implements CoderClientApi {
82
88
83
89
constructor ( inputs : ConstructorInputs ) {
84
90
const {
85
- apis,
86
91
initialToken,
92
+
2851
apis : { urlSync, identityApi } ,
87
93
requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS ,
88
94
} = inputs ;
89
- const { urlSync, identityApi } = apis ;
90
95
91
96
this . urlSync = urlSync ;
92
97
this . identityApi = identityApi ;
93
- this . axios = globalAxios . create ( ) ;
94
-
95
98
this . loadedSessionToken = initialToken ;
96
99
this . requestTimeoutMs = requestTimeoutMs ;
97
-
98
100
this . cleanupController = new AbortController ( ) ;
99
101
this . trackedEjectionIds = new Set ( ) ;
100
102
101
- this . sdk = this . getBackstageCoderSdk ( this . axios ) ;
103
+ this . sdk = this . createBackstageCoderSdk ( ) ;
102
104
this . addBaseRequestInterceptors ( ) ;
103
105
}
104
106
105
107
private addRequestInterceptor (
106
- requestInterceptor : (
107
- config : RequestConfig ,
108
- ) => RequestConfig | Promise < RequestConfig > ,
108
+ requestInterceptor : RequestInterceptor ,
109
109
errorInterceptor ?: ( error : unknown ) => unknown ,
110
110
) : number {
111
- const ejectionId = this . axios . interceptors . request . use (
111
+ const axios = this . sdk . getAxiosInstance ( ) ;
112
+ const ejectionId = axios . interceptors . request . use (
112
113
requestInterceptor ,
113
114
errorInterceptor ,
114
115
) ;
@@ -120,7 +121,8 @@ export class CoderClient implements CoderClientApi {
120
121
private removeRequestInterceptorById ( ejectionId : number ) : boolean {
121
122
// Even if we somehow pass in an ID that hasn't been associated with the
122
123
// 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 ) ;
124
126
125
127
if ( ! this . trackedEjectionIds . has ( ejectionId ) ) {
126
128
return false ;
@@ -179,10 +181,8 @@ export class CoderClient implements CoderClientApi {
179
181
this . addRequestInterceptor ( baseRequestInterceptor , baseErrorInterceptor ) ;
180
182
}
181
183
182
- private getBackstageCoderSdk (
183
- axiosInstance : AxiosInstance ,
184
- ) : BackstageCoderSdk {
185
- const baseSdk = new CoderSdk ( axiosInstance ) ;
184
+ private createBackstageCoderSdk ( ) : BackstageCoderSdk {
185
+ const baseSdk = makeCoderSdk ( ) ;
186
186
187
187
const getWorkspaces : ( typeof baseSdk ) [ 'getWorkspaces' ] = async request => {
188
188
const workspacesRes = await baseSdk . getWorkspaces ( request ) ;
@@ -335,23 +335,6 @@ export class CoderClient implements CoderClientApi {
335
335
this . removeRequestInterceptorById ( validationId ) ;
336
336
}
337
337
} ;
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
- } ;
355
338
}
356
339
357
340
function appendParamToQuery (
0 commit comments