@@ -12,11 +12,6 @@ import { type Deployment, type DeploymentWithAuth } from "./types";
1212import type { User } from "coder/site/src/api/typesGenerated" ;
1313import type * as vscode from "vscode" ;
1414
15- /**
16- * Internal state type that allows mutation of user property.
17- */
18- type DeploymentWithUser = Deployment & { user : User } ;
19-
2015/**
2116 * Manages deployment state for the extension.
2217 *
@@ -35,7 +30,7 @@ export class DeploymentManager implements vscode.Disposable {
3530 private readonly contextManager : ContextManager ;
3631 private readonly logger : Logger ;
3732
38- #deployment: DeploymentWithUser | null = null ;
33+ #deployment: Deployment | null = null ;
3934 #authListenerDisposable: vscode . Disposable | undefined ;
4035 #crossWindowSyncDisposable: vscode . Disposable | undefined ;
4136
@@ -78,7 +73,7 @@ export class DeploymentManager implements vscode.Disposable {
7873 * Check if we have an authenticated deployment (does not guarantee that the current auth data is valid).
7974 */
8075 public isAuthenticated ( ) : boolean {
81- return this . #deployment !== null ;
76+ return this . contextManager . get ( "coder.authenticated" ) ;
8277 }
8378
8479 /**
@@ -89,10 +84,10 @@ export class DeploymentManager implements vscode.Disposable {
8984 public async setDeploymentIfValid (
9085 deployment : Deployment & { token ?: string } ,
9186 ) : Promise < boolean > {
92- const auth = await this . secretsManager . getSessionAuth (
93- deployment . safeHostname ,
94- ) ;
95- const token = deployment . token ?? auth ?. token ;
87+ const token =
88+ deployment . token ??
89+ ( await this . secretsManager . getSessionAuth ( deployment . safeHostname ) )
90+ ?. token ;
9691 const tempClient = CoderApi . create ( deployment . url , token , this . logger ) ;
9792
9893 try {
@@ -132,7 +127,7 @@ export class DeploymentManager implements vscode.Disposable {
132127 // Register auth listener before setDeployment so background token refresh
133128 // can update client credentials via the listener
134129 this . registerAuthListener ( ) ;
135- this . updateAuthContexts ( ) ;
130+ this . updateAuthContexts ( deployment . user ) ;
136131 this . refreshWorkspaces ( ) ;
137132
138133 await this . oauthSessionManager . setDeployment ( deployment ) ;
@@ -143,16 +138,32 @@ export class DeploymentManager implements vscode.Disposable {
143138 * Clears the current deployment.
144139 */
145140 public async clearDeployment ( ) : Promise < void > {
141+ this . suspendSession ( ) ;
146142 this . #authListenerDisposable?. dispose ( ) ;
147143 this . #authListenerDisposable = undefined ;
148144 this . #deployment = null ;
149145
150- this . client . setCredentials ( undefined , undefined ) ;
146+ await this . secretsManager . setCurrentDeployment ( undefined ) ;
147+ }
148+
149+ /**
150+ * Suspend session: shows logged-out state but keeps deployment for easy re-login.
151+ * Auth listener remains active so recovery can happen automatically if tokens update.
152+ */
153+ public suspendSession ( ) : void {
151154 this . oauthSessionManager . clearDeployment ( ) ;
152- this . updateAuthContexts ( ) ;
153- this . refreshWorkspaces ( ) ;
155+ this . client . setCredentials ( undefined , undefined ) ;
156+ this . updateAuthContexts ( undefined ) ;
157+ this . clearWorkspaces ( ) ;
158+ }
154159
155- await this . secretsManager . setCurrentDeployment ( undefined ) ;
160+ /**
161+ * Clear all workspace providers without fetching.
162+ */
163+ private clearWorkspaces ( ) : void {
164+ for ( const provider of this . workspaceProviders ) {
165+ provider . clear ( ) ;
166+ }
156167 }
157168
158169 public dispose ( ) : void {
@@ -163,6 +174,7 @@ export class DeploymentManager implements vscode.Disposable {
163174 /**
164175 * Register auth listener for the current deployment.
165176 * Updates credentials when they change (token refresh, cross-window sync).
177+ * Also handles recovery from suspended session state.
166178 */
167179 private registerAuthListener ( ) : void {
168180 if ( ! this . #deployment) {
@@ -182,7 +194,18 @@ export class DeploymentManager implements vscode.Disposable {
182194 }
183195
184196 if ( auth ) {
185- this . client . setCredentials ( auth . url , auth . token ) ;
197+ if ( this . contextManager . get ( "coder.authenticated" ) ) {
198+ this . client . setCredentials ( auth . url , auth . token ) ;
199+ } else {
200+ this . logger . debug (
201+ "Token updated after session suspended, recovering" ,
202+ ) ;
203+ await this . setDeploymentIfValid ( {
204+ url : auth . url ,
205+ safeHostname,
206+ token : auth . token ,
207+ } ) ;
208+ }
186209 } else {
187210 await this . clearDeployment ( ) ;
188211 }
@@ -210,8 +233,7 @@ export class DeploymentManager implements vscode.Disposable {
210233 /**
211234 * Update authentication-related contexts.
212235 */
213- private updateAuthContexts ( ) : void {
214- const user = this . #deployment?. user ;
236+ private updateAuthContexts ( user : User | undefined ) : void {
215237 this . contextManager . set ( "coder.authenticated" , Boolean ( user ) ) ;
216238 const isOwner = user ?. roles . some ( ( r ) => r . name === "owner" ) ?? false ;
217239 this . contextManager . set ( "coder.isOwner" , isOwner ) ;
0 commit comments