1- import * as vscode from "vscode" ;
2-
31import { CoderApi } from "../api/coderApi" ;
42import {
53 CONFIG_CHANGE_DEBOUNCE_MS ,
@@ -14,32 +12,21 @@ import { type OAuthSessionManager } from "../oauth/sessionManager";
1412import { getAuthConfigWatchSettings } from "../settings/authConfig" ;
1513import { type TelemetryService } from "../telemetry/service" ;
1614
15+ import { SessionStore , type SessionData } from "./sessionStore" ;
1716import {
1817 DeploymentSchema ,
1918 type Deployment ,
2019 type DeploymentWithAuth ,
2120} from "./types" ;
2221
2322import type { User } from "coder/site/src/api/typesGenerated" ;
23+ import type * as vscode from "vscode" ;
2424
2525import type {
2626 WorkspaceSessionSnapshot ,
2727 WorkspaceSessionState ,
2828} from "../workspace/session" ;
2929
30- type DeploymentSessionSnapshot =
31- | {
32- readonly kind : "signedOut" ;
33- readonly revision : number ;
34- readonly deployment : Deployment | null ;
35- }
36- | {
37- readonly kind : "signedIn" ;
38- readonly revision : number ;
39- readonly deployment : Deployment ;
40- readonly user : User ;
41- } ;
42-
4330/**
4431 * Manages deployment state for the extension.
4532 *
@@ -60,14 +47,8 @@ export class DeploymentManager
6047 private readonly logger : Logger ;
6148 private readonly telemetryService : TelemetryService ;
6249
63- #session: DeploymentSessionSnapshot = {
64- kind : "signedOut" ,
65- revision : 0 ,
66- deployment : null ,
67- } ;
68- readonly #onDidChangeWorkspaceSession =
69- new vscode . EventEmitter < WorkspaceSessionSnapshot > ( ) ;
70- public readonly onDidChange = this . #onDidChangeWorkspaceSession. event ;
50+ readonly #sessionStore = new SessionStore ( ) ;
51+ public readonly onDidChange = this . #sessionStore. onDidChange ;
7152 #disposed = false ;
7253 #authListenerDisposable: vscode . Disposable | undefined ;
7354 #authConfigDisposable: vscode . Disposable | undefined ;
@@ -106,51 +87,37 @@ export class DeploymentManager
10687 * Get the current deployment state.
10788 */
10889 public getCurrentDeployment ( ) : Deployment | null {
109- return this . #session. deployment ;
110- }
111-
112- public getCurrentUserId ( ) : string | undefined {
113- return this . #session. kind === "signedIn"
114- ? this . #session. user . id
115- : undefined ;
90+ return this . #sessionStore. current . deployment ;
11691 }
11792
11893 public getSnapshot ( ) : WorkspaceSessionSnapshot {
119- if ( this . #session. kind === "signedIn" ) {
120- return {
121- kind : "signedIn" ,
122- revision : this . #session. revision ,
123- userId : this . #session. user . id ,
124- } ;
125- }
126- return { kind : "signedOut" , revision : this . #session. revision } ;
94+ return this . #sessionStore. getSnapshot ( ) ;
12795 }
12896
12997 /**
13098 * Check if we have an authenticated deployment (does not guarantee that the current auth data is valid).
13199 */
132100 public isAuthenticated ( ) : boolean {
133- return this . #session . kind === "signedIn" ;
101+ return this . #sessionStore . current . kind === "signedIn" ;
134102 }
135103
136104 /**
137- * Verify credentials and apply the deployment on success. Used for
138- * fresh logins and for un-suspending a session after auth settings or
139- * a token become valid again. Bails if state moved during the verify
140- * (logout, another login, dispose), so callers don't need a race guard.
105+ * Verify credentials and apply the deployment on success, signing in . Used
106+ * for fresh logins and for un-suspending a session once auth settings or a
107+ * token become valid again. Bails if state moved during the verify (logout,
108+ * another login, dispose), so callers don't need a race guard.
141109 */
142- public async verifyAndApplyDeployment (
110+ public async verifyAndApplySession (
143111 deployment : Deployment & { token ?: string } ,
144112 ) : Promise < boolean > {
145- const sessionBefore = this . #session ;
113+ const sessionBefore = this . #sessionStore . current ;
146114 const token =
147115 deployment . token ??
148116 ( await this . secretsManager . getSessionAuth ( deployment . safeHostname ) )
149117 ?. token ;
150- const tempClient = CoderApi . create ( deployment . url , token , this . logger ) ;
151118
152119 try {
153- const user = await tempClient . getAuthenticatedUser ( ) ;
120+ const user = await this . #verifyCredentials ( deployment . url , token ) ;
154121 if ( this . #hasStateChangedSince( sessionBefore ) ) {
155122 return false ;
156123 }
@@ -159,17 +126,31 @@ export class DeploymentManager
159126 } catch ( e ) {
160127 this . logger . warn ( "Failed to authenticate with deployment:" , e ) ;
161128 return false ;
129+ }
130+ }
131+
132+ /**
133+ * Verify credentials with a throwaway client and return the authenticated
134+ * user. Throws if the credentials are rejected.
135+ */
136+ async #verifyCredentials(
137+ url : string ,
138+ token : string | undefined ,
139+ ) : Promise < User > {
140+ const tempClient = CoderApi . create ( url , token , this . logger ) ;
141+ try {
142+ return await tempClient . getAuthenticatedUser ( ) ;
162143 } finally {
163144 tempClient . dispose ( ) ;
164145 }
165146 }
166147
167148 /** True if disposal, login, or a deployment switch raced our await. */
168- #hasStateChangedSince( sessionBefore : DeploymentSessionSnapshot ) : boolean {
149+ #hasStateChangedSince( sessionBefore : SessionData ) : boolean {
169150 return (
170151 this . #disposed ||
171152 this . isAuthenticated ( ) ||
172- this . #session !== sessionBefore
153+ this . #sessionStore . current !== sessionBefore
173154 ) ;
174155 }
175156
@@ -192,14 +173,17 @@ export class DeploymentManager
192173 this . client . setCredentials ( deployment . url , deployment . token ) ;
193174 }
194175
195- const ourRef = this . setSignedIn ( deploymentWithoutAuth , deployment . user ) ;
176+ const ourRef = this . #sessionStore. signIn (
177+ deploymentWithoutAuth ,
178+ deployment . user ,
179+ ) ;
196180 // Register before OAuth setup so background token refresh can update client credentials.
197181 this . registerAuthListener ( ) ;
198182 this . updateAuthContexts ( deployment . user ) ;
199183
200184 await this . oauthSessionManager . setDeployment ( deploymentWithoutAuth ) ;
201185 // Bail if a concurrent write took over during the await.
202- if ( this . #session !== ourRef ) {
186+ if ( this . #sessionStore . current !== ourRef ) {
203187 return ;
204188 }
205189 await this . persistDeployment ( deploymentWithoutAuth ) ;
@@ -211,12 +195,12 @@ export class DeploymentManager
211195 public async clearDeployment ( ) : Promise < void > {
212196 this . logger . debug (
213197 "Clearing deployment" ,
214- this . #session . deployment ?. safeHostname ,
198+ this . #sessionStore . current . deployment ?. safeHostname ,
215199 ) ;
216200 this . #authListenerDisposable?. dispose ( ) ;
217201 this . #authListenerDisposable = undefined ;
218- this . setSignedOut ( null ) ;
219- this . clearSessionSideEffects ( ) ;
202+ this . #sessionStore . signOut ( null ) ;
203+ this . clearSideEffects ( ) ;
220204 this . telemetryService . setDeploymentUrl ( "" ) ;
221205
222206 await this . secretsManager . setCurrentDeployment ( undefined ) ;
@@ -227,11 +211,11 @@ export class DeploymentManager
227211 * Auth listener remains active so recovery can happen automatically if tokens update.
228212 */
229213 public suspendSession ( ) : void {
230- this . setSignedOut ( this . #session . deployment ) ;
231- this . clearSessionSideEffects ( ) ;
214+ this . #sessionStore . signOut ( this . #sessionStore . current . deployment ) ;
215+ this . clearSideEffects ( ) ;
232216 }
233217
234- private clearSessionSideEffects ( ) : void {
218+ private clearSideEffects ( ) : void {
235219 this . oauthSessionManager . clearDeployment ( ) ;
236220 this . client . setCredentials ( undefined , undefined ) ;
237221 this . updateAuthContexts ( undefined ) ;
@@ -242,7 +226,7 @@ export class DeploymentManager
242226 this . #authListenerDisposable?. dispose ( ) ;
243227 this . #authConfigDisposable?. dispose ( ) ;
244228 this . #crossWindowSyncDisposable?. dispose ( ) ;
245- this . #onDidChangeWorkspaceSession . dispose ( ) ;
229+ this . #sessionStore . dispose ( ) ;
246230 }
247231
248232 /**
@@ -251,25 +235,28 @@ export class DeploymentManager
251235 * Also handles recovery from suspended session state.
252236 */
253237 private registerAuthListener ( ) : void {
254- if ( ! this . #session. deployment ) {
238+ const deployment = this . #sessionStore. current . deployment ;
239+ if ( ! deployment ) {
255240 return ;
256241 }
257242
258243 // Capture hostname at registration time for the guard clause
259- const safeHostname = this . #session . deployment . safeHostname ;
244+ const safeHostname = deployment . safeHostname ;
260245
261246 this . #authListenerDisposable?. dispose ( ) ;
262247 this . logger . debug ( "Registering auth listener for hostname" , safeHostname ) ;
263248 this . #authListenerDisposable = this . secretsManager . onDidChangeSessionAuth (
264249 safeHostname ,
265250 async ( auth ) => {
266- if ( this . #session. deployment ?. safeHostname !== safeHostname ) {
251+ if (
252+ this . #sessionStore. current . deployment ?. safeHostname !== safeHostname
253+ ) {
267254 return ;
268255 }
269256
270257 if ( auth ) {
271258 if ( this . isAuthenticated ( ) ) {
272- await this . verifyAndUpdateAuthenticatedSession ( {
259+ await this . verifyAndUpdateSession ( {
273260 url : auth . url ,
274261 safeHostname,
275262 token : auth . token ,
@@ -278,7 +265,7 @@ export class DeploymentManager
278265 this . logger . debug (
279266 "Token updated after session suspended, recovering" ,
280267 ) ;
281- await this . verifyAndApplyDeployment ( {
268+ await this . verifyAndApplySession ( {
282269 url : auth . url ,
283270 safeHostname,
284271 token : auth . token ,
@@ -291,55 +278,24 @@ export class DeploymentManager
291278 ) ;
292279 }
293280
294- private async verifyAndUpdateAuthenticatedSession (
281+ private async verifyAndUpdateSession (
295282 deployment : Deployment & { token : string } ,
296283 ) : Promise < void > {
297- const sessionBefore = this . #session;
298- const tempClient = CoderApi . create (
299- deployment . url ,
300- deployment . token ,
301- this . logger ,
302- ) ;
303-
284+ const sessionBefore = this . #sessionStore. current ;
304285 try {
305- const user = await tempClient . getAuthenticatedUser ( ) ;
306- if ( this . #disposed || this . #session !== sessionBefore ) {
286+ const user = await this . #verifyCredentials(
287+ deployment . url ,
288+ deployment . token ,
289+ ) ;
290+ if ( this . #disposed || this . #sessionStore. current !== sessionBefore ) {
307291 return ;
308292 }
309293 await this . setDeployment ( { ...deployment , user } ) ;
310294 } catch ( e ) {
311295 this . logger . warn ( "Failed to authenticate updated session:" , e ) ;
312- } finally {
313- tempClient . dispose ( ) ;
314296 }
315297 }
316298
317- private setSignedIn (
318- deployment : Deployment ,
319- user : User ,
320- ) : DeploymentSessionSnapshot {
321- this . #session = {
322- kind : "signedIn" ,
323- revision : this . #session. revision + 1 ,
324- deployment,
325- user,
326- } ;
327- this . #onDidChangeWorkspaceSession. fire ( this . getSnapshot ( ) ) ;
328- return this . #session;
329- }
330-
331- private setSignedOut (
332- deployment : Deployment | null ,
333- ) : DeploymentSessionSnapshot {
334- this . #session = {
335- kind : "signedOut" ,
336- revision : this . #session. revision + 1 ,
337- deployment,
338- } ;
339- this . #onDidChangeWorkspaceSession. fire ( this . getSnapshot ( ) ) ;
340- return this . #session;
341- }
342-
343299 private subscribeToAuthConfigChanges ( ) : void {
344300 this . #authConfigDisposable = watchConfigurationChanges (
345301 getAuthConfigWatchSettings ( ) ,
@@ -363,14 +319,14 @@ export class DeploymentManager
363319 try {
364320 do {
365321 this . #recoveryPending = false ;
366- const snapshot = this . #session . deployment ;
367- if ( this . #disposed || ! snapshot || this . isAuthenticated ( ) ) {
322+ const deployment = this . #sessionStore . current . deployment ;
323+ if ( this . #disposed || ! deployment || this . isAuthenticated ( ) ) {
368324 return ;
369325 }
370326 this . logger . debug (
371327 "Authentication settings changed after session suspended, recovering" ,
372328 ) ;
373- await this . verifyAndApplyDeployment ( snapshot ) ;
329+ await this . verifyAndApplySession ( deployment ) ;
374330 } while ( this . #recoveryPending) ;
375331 } catch ( err ) {
376332 this . logger . warn (
@@ -392,7 +348,7 @@ export class DeploymentManager
392348
393349 if ( deployment ) {
394350 this . logger . info ( "Deployment changed from another window" ) ;
395- await this . verifyAndApplyDeployment ( deployment ) ;
351+ await this . verifyAndApplySession ( deployment ) ;
396352 }
397353 } ,
398354 ) ;
0 commit comments