Skip to content

Commit d6d3060

Browse files
committed
refactor: detect session changes by identity and queue re-fetches
Address round 3 review feedback on the session-state architecture: - Drop the revision counter and the lean WorkspaceSessionSnapshot projection in favor of comparing SessionData by identity; every sign-in/sign-out already creates a new object, so the revision tracked the same information twice. - Move the session-state contract into the deployment layer (SessionState in sessionStore.ts) and expose it through deploymentManager.sessionState, so the workspace tree depends on deployment state rather than the other way around. - Replace the bounded in-fetch retry loop with a queued re-fetch: fetchAndRefresh() calls that land mid-flight (session changes, manual refreshes) mark a pending re-fetch that runs once the current fetch settles. Stale results are dropped instead of retried inline, requests never run in parallel, and a fetch that fails after a session change is now retried instead of leaving the tree empty.
1 parent 4f3a3db commit d6d3060

8 files changed

Lines changed: 166 additions & 208 deletions

File tree

src/deployment/deploymentManager.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import { type OAuthSessionManager } from "../oauth/sessionManager";
1717
import { getAuthConfigWatchSettings } from "../settings/authConfig";
1818
import { type TelemetryService } from "../telemetry/service";
1919

20-
import { SessionStore, type SessionData } from "./sessionStore";
20+
import {
21+
SessionStore,
22+
type SessionData,
23+
type SessionState,
24+
} from "./sessionStore";
2125
import {
2226
DeploymentSchema,
2327
type Deployment,
@@ -27,11 +31,6 @@ import {
2731
import type { User } from "coder/site/src/api/typesGenerated";
2832
import type * as vscode from "vscode";
2933

30-
import type {
31-
WorkspaceSessionSnapshot,
32-
WorkspaceSessionState,
33-
} from "../workspace/session";
34-
3534
/**
3635
* Manages deployment state for the extension.
3736
*
@@ -43,9 +42,7 @@ import type {
4342
* - Context updates (coder.authenticated, coder.isOwner)
4443
* - Cross-window sync handling
4544
*/
46-
export class DeploymentManager
47-
implements vscode.Disposable, WorkspaceSessionState
48-
{
45+
export class DeploymentManager implements vscode.Disposable {
4946
private readonly secretsManager: SecretsManager;
5047
private readonly mementoManager: MementoManager;
5148
private readonly contextManager: ContextManager;
@@ -54,7 +51,8 @@ export class DeploymentManager
5451
private readonly deploymentTelemetry: DeploymentTelemetry;
5552

5653
readonly #sessionStore = new SessionStore();
57-
public readonly onDidChange = this.#sessionStore.onDidChange;
54+
/** Read-only session view for consumers that track auth changes. */
55+
public readonly sessionState: SessionState = this.#sessionStore;
5856
#disposed = false;
5957
#authListenerDisposable: vscode.Disposable | undefined;
6058
#authConfigDisposable: vscode.Disposable | undefined;
@@ -97,10 +95,6 @@ export class DeploymentManager
9795
return this.#sessionStore.current.deployment;
9896
}
9997

100-
public getSnapshot(): WorkspaceSessionSnapshot {
101-
return this.#sessionStore.getSnapshot();
102-
}
103-
10498
/**
10599
* Check if we have an authenticated deployment (does not guarantee that the current auth data is valid).
106100
*/

src/deployment/sessionStore.ts

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ import * as vscode from "vscode";
22

33
import type { User } from "coder/site/src/api/typesGenerated";
44

5-
import type {
6-
WorkspaceSessionSnapshot,
7-
WorkspaceSessionState,
8-
} from "../workspace/session";
9-
105
import type { Deployment } from "./types";
116

127
/**
@@ -25,36 +20,31 @@ export type SessionData =
2520
};
2621

2722
/**
28-
* Owns the deployment session. State changes only through signIn()/signOut(),
29-
* each of which bumps the revision and notifies listeners.
23+
* Read-only session access: the current data plus change notifications.
3024
*
31-
* Consumers that only need auth status (like the workspace tree) take the lean
32-
* WorkspaceSessionState projection instead of the full session.
25+
* To detect a change across an await, keep the SessionData read before the
26+
* await and compare it by identity afterward; sign-in and sign-out always
27+
* replace the object.
3328
*/
34-
export class SessionStore implements WorkspaceSessionState {
29+
export interface SessionState {
30+
readonly current: SessionData;
31+
readonly onDidChange: vscode.Event<SessionData>;
32+
}
33+
34+
/**
35+
* Owns the deployment session. State changes only through signIn()/signOut(),
36+
* each of which replaces the session object and notifies listeners.
37+
*/
38+
export class SessionStore implements SessionState {
3539
#data: SessionData = { kind: "signedOut", deployment: null };
36-
#revision = 0;
37-
readonly #onDidChange = new vscode.EventEmitter<WorkspaceSessionSnapshot>();
40+
readonly #onDidChange = new vscode.EventEmitter<SessionData>();
3841

3942
public readonly onDidChange = this.#onDidChange.event;
4043

41-
/** Full session state, including deployment and user. */
4244
public get current(): SessionData {
4345
return this.#data;
4446
}
4547

46-
/** Lean projection for consumers that only track auth status and revision. */
47-
public getSnapshot(): WorkspaceSessionSnapshot {
48-
if (this.#data.kind === "signedIn") {
49-
return {
50-
kind: "signedIn",
51-
revision: this.#revision,
52-
userId: this.#data.user.id,
53-
};
54-
}
55-
return { kind: "signedOut", revision: this.#revision };
56-
}
57-
5848
public signIn(deployment: Deployment, user: User): SessionData {
5949
return this.transition({ kind: "signedIn", deployment, user });
6050
}
@@ -65,8 +55,7 @@ export class SessionStore implements WorkspaceSessionState {
6555

6656
private transition(data: SessionData): SessionData {
6757
this.#data = data;
68-
this.#revision++;
69-
this.#onDidChange.fire(this.getSnapshot());
58+
this.#onDidChange.fire(data);
7059
return data;
7160
}
7261

src/extension.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ async function doActivate(
166166
WorkspaceQuery.Mine,
167167
client,
168168
output,
169-
deploymentManager,
169+
deploymentManager.sessionState,
170170
{ refreshIntervalMs: 5_000 },
171171
);
172172
ctx.subscriptions.push(myWorkspacesProvider);
@@ -175,15 +175,15 @@ async function doActivate(
175175
WorkspaceQuery.All,
176176
client,
177177
output,
178-
deploymentManager,
178+
deploymentManager.sessionState,
179179
);
180180
ctx.subscriptions.push(allWorkspacesProvider);
181181

182182
const sharedWorkspacesProvider = new WorkspaceProvider(
183183
WorkspaceQuery.Shared,
184184
client,
185185
output,
186-
deploymentManager,
186+
deploymentManager.sessionState,
187187
);
188188
ctx.subscriptions.push(sharedWorkspacesProvider);
189189

src/workspace/session.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)