@@ -13,10 +13,16 @@ const OAUTH_CLIENT_PREFIX = "coder.oauth.client.";
1313const CURRENT_DEPLOYMENT_KEY = "coder.currentDeployment" ;
1414const OAUTH_CALLBACK_KEY = "coder.oauthCallback" ;
1515
16- const KNOWN_LABELS_KEY = "coder.knownLabels" ;
16+ const DEPLOYMENT_USAGE_KEY = "coder.deploymentUsage" ;
17+ const DEFAULT_MAX_DEPLOYMENTS = 10 ;
1718
1819const LEGACY_SESSION_TOKEN_KEY = "sessionToken" ;
1920
21+ export interface DeploymentUsage {
22+ label : string ;
23+ lastAccessedAt : string ;
24+ }
25+
2026export type StoredOAuthTokens = Omit < TokenResponse , "expires_in" > & {
2127 expiry_timestamp : number ;
2228 deployment_url : string ;
@@ -179,7 +185,7 @@ export class SecretsManager {
179185 `${ SESSION_KEY_PREFIX } ${ label } ` ,
180186 JSON . stringify ( auth ) ,
181187 ) ;
182- await this . addKnownLabel ( label ) ;
188+ await this . recordDeploymentAccess ( label ) ;
183189 }
184190
185191 public async clearSessionAuth ( label : string ) : Promise < void > {
@@ -208,7 +214,7 @@ export class SecretsManager {
208214 `${ OAUTH_TOKENS_PREFIX } ${ label } ` ,
209215 JSON . stringify ( tokens ) ,
210216 ) ;
211- await this . addKnownLabel ( label ) ;
217+ await this . recordDeploymentAccess ( label ) ;
212218 }
213219
214220 public async clearOAuthTokens ( label : string ) : Promise < void > {
@@ -237,7 +243,7 @@ export class SecretsManager {
237243 `${ OAUTH_CLIENT_PREFIX } ${ label } ` ,
238244 JSON . stringify ( registration ) ,
239245 ) ;
240- await this . addKnownLabel ( label ) ;
246+ await this . recordDeploymentAccess ( label ) ;
241247 }
242248
243249 public async clearOAuthClientRegistration ( label : string ) : Promise < void > {
@@ -252,42 +258,48 @@ export class SecretsManager {
252258 }
253259
254260 /**
255- * TODO currently it might be used wrong because we can be connected to a remote deployment
256- * and we log out from the sidebar causing the session to be removed and the auto-refresh disabled.
257- *
258- * Potential solutions:
259- * 1. Keep the last 10 auths and possibly remove entries not used in a while instead.
260- * We do not remove entries on logout!
261- * 2. Show the user a warning that their remote deployment might be disconnected.
262- *
263- * Update all usages of this after arriving at a decision!
261+ * Record that a deployment was accessed, moving it to the front of the LRU list.
262+ * Prunes deployments beyond maxCount, clearing their auth data.
263+ */
264+ public async recordDeploymentAccess (
265+ label : string ,
266+ maxCount = DEFAULT_MAX_DEPLOYMENTS ,
267+ ) : Promise < void > {
268+ const usage = this . getDeploymentUsage ( ) ;
269+ const filtered = usage . filter ( ( u ) => u . label !== label ) ;
270+ filtered . unshift ( { label, lastAccessedAt : new Date ( ) . toISOString ( ) } ) ;
271+
272+ const toKeep = filtered . slice ( 0 , maxCount ) ;
273+ const toRemove = filtered . slice ( maxCount ) ;
274+
275+ await Promise . all ( toRemove . map ( ( u ) => this . clearAllAuthData ( u . label ) ) ) ;
276+ await this . memento . update ( DEPLOYMENT_USAGE_KEY , toKeep ) ;
277+ }
278+
279+ /**
280+ * Clear all auth data for a deployment and remove it from the usage list.
264281 */
265282 public async clearAllAuthData ( label : string ) : Promise < void > {
266283 await Promise . all ( [
267284 this . clearSessionAuth ( label ) ,
268285 this . clearOAuthData ( label ) ,
269286 ] ) ;
270- await this . removeKnownLabel ( label ) ;
287+ const usage = this . getDeploymentUsage ( ) . filter ( ( u ) => u . label !== label ) ;
288+ await this . memento . update ( DEPLOYMENT_USAGE_KEY , usage ) ;
271289 }
272290
291+ /**
292+ * Get all known deployment labels, ordered by most recently accessed.
293+ */
273294 public getKnownLabels ( ) : string [ ] {
274- return this . memento . get < string [ ] > ( KNOWN_LABELS_KEY ) ?? [ ] ;
295+ return this . getDeploymentUsage ( ) . map ( ( u ) => u . label ) ;
275296 }
276297
277- private async addKnownLabel ( label : string ) : Promise < void > {
278- const labels = new Set ( this . getKnownLabels ( ) ) ;
279- if ( ! labels . has ( label ) ) {
280- labels . add ( label ) ;
281- await this . memento . update ( KNOWN_LABELS_KEY , Array . from ( labels ) ) ;
282- }
283- }
284-
285- private async removeKnownLabel ( label : string ) : Promise < void > {
286- const labels = new Set ( this . getKnownLabels ( ) ) ;
287- if ( labels . has ( label ) ) {
288- labels . delete ( label ) ;
289- await this . memento . update ( KNOWN_LABELS_KEY , Array . from ( labels ) ) ;
290- }
298+ /**
299+ * Get the full deployment usage list with access timestamps.
300+ */
301+ private getDeploymentUsage ( ) : DeploymentUsage [ ] {
302+ return this . memento . get < DeploymentUsage [ ] > ( DEPLOYMENT_USAGE_KEY ) ?? [ ] ;
291303 }
292304
293305 /**
0 commit comments