From d2572b5cd1cc8e126d14b435a1d2e5943fa900e2 Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Mon, 2 Jun 2025 14:53:41 -0700 Subject: [PATCH 1/2] Cloud: log session refresh errors --- packages/cloud/src/AuthService.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/cloud/src/AuthService.ts b/packages/cloud/src/AuthService.ts index 8198c4e8f4..5d38bcfa47 100644 --- a/packages/cloud/src/AuthService.ts +++ b/packages/cloud/src/AuthService.ts @@ -281,14 +281,19 @@ export class AuthService extends EventEmitter { return } - const previousState = this.state - this.sessionToken = await this.clerkCreateSessionToken() - this.state = "active-session" + try { + const previousState = this.state + this.sessionToken = await this.clerkCreateSessionToken() + this.state = "active-session" - if (previousState !== "active-session") { - console.log("[auth] Transitioned to active-session state") - this.emit("active-session", { previousState }) - this.fetchUserInfo() + if (previousState !== "active-session") { + console.log("[auth] Transitioned to active-session state") + this.emit("active-session", { previousState }) + this.fetchUserInfo() + } + } catch (error) { + console.error("[auth] Failed to refresh session", error) + throw error } } From 86ebeaa6a90cf8b8e9b18ff4de3c87bfe180086c Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:09:47 -0700 Subject: [PATCH 2/2] Cloud: bugfix: allow logouts during the inactive-session state --- packages/cloud/src/AuthService.ts | 5 +++ packages/cloud/src/CloudService.ts | 1 + src/core/webview/ClineProvider.ts | 13 ++++++ .../webview/__tests__/ClineProvider.test.ts | 1 + src/shared/ExtensionMessage.ts | 1 + webview-ui/src/App.tsx | 9 +++- .../src/components/account/AccountView.tsx | 43 ++++++++++--------- .../src/context/ExtensionStateContext.tsx | 3 ++ .../__tests__/ExtensionStateContext.test.tsx | 1 + 9 files changed, 56 insertions(+), 21 deletions(-) diff --git a/packages/cloud/src/AuthService.ts b/packages/cloud/src/AuthService.ts index 5d38bcfa47..da8cdc1293 100644 --- a/packages/cloud/src/AuthService.ts +++ b/packages/cloud/src/AuthService.ts @@ -11,6 +11,7 @@ import { getClerkBaseUrl, getRooCodeApiUrl } from "./Config" import { RefreshTimer } from "./RefreshTimer" export interface AuthServiceEvents { + "inactive-session": [data: { previousState: AuthState }] "active-session": [data: { previousState: AuthState }] "logged-out": [data: { previousState: AuthState }] "user-info": [data: { userInfo: CloudUserInfo }] @@ -92,11 +93,15 @@ export class AuthService extends EventEmitter { private transitionToInactiveSession(credentials: AuthCredentials): void { this.credentials = credentials + + const previousState = this.state this.state = "inactive-session" this.sessionToken = null this.userInfo = null + this.emit("inactive-session", { previousState }) + this.timer.start() console.log("[auth] Transitioned to inactive-session state") diff --git a/packages/cloud/src/CloudService.ts b/packages/cloud/src/CloudService.ts index 945d19a72c..dea09b961d 100644 --- a/packages/cloud/src/CloudService.ts +++ b/packages/cloud/src/CloudService.ts @@ -35,6 +35,7 @@ export class CloudService { try { this.authService = await AuthService.createInstance(this.context) + this.authService.on("inactive-session", this.authListener) this.authService.on("active-session", this.authListener) this.authService.on("logged-out", this.authListener) this.authService.on("user-info", this.authListener) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 172685affd..aeac4835ad 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1300,6 +1300,7 @@ export class ClineProvider terminalCompressProgressBar, historyPreviewCollapsed, cloudUserInfo, + cloudIsAuthenticated, organizationAllowList, maxConcurrentFileReads, condensingApiConfigId, @@ -1398,6 +1399,7 @@ export class ClineProvider hasSystemPromptOverride, historyPreviewCollapsed: historyPreviewCollapsed ?? false, cloudUserInfo, + cloudIsAuthenticated: cloudIsAuthenticated ?? false, organizationAllowList, condensingApiConfigId, customCondensingPrompt, @@ -1453,6 +1455,16 @@ export class ClineProvider ) } + let cloudIsAuthenticated: boolean = false + + try { + cloudIsAuthenticated = CloudService.instance.isAuthenticated() + } catch (error) { + console.error( + `[getState] failed to get cloud authentication state: ${error instanceof Error ? error.message : String(error)}`, + ) + } + // Return the same structure as before return { apiConfiguration: providerSettings, @@ -1528,6 +1540,7 @@ export class ClineProvider : 1, historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false, cloudUserInfo, + cloudIsAuthenticated, organizationAllowList, // Explicitly add condensing settings condensingApiConfigId: stateValues.condensingApiConfigId, diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index bca291a48e..77ae9c8eb7 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -427,6 +427,7 @@ describe("ClineProvider", () => { organizationAllowList: ORGANIZATION_ALLOW_ALL, autoCondenseContext: true, autoCondenseContextPercent: 100, + cloudIsAuthenticated: false, } const message: ExtensionMessage = { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 96466360bb..a488807d81 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -219,6 +219,7 @@ export type ExtensionState = Pick< historyPreviewCollapsed?: boolean cloudUserInfo: CloudUserInfo | null + cloudIsAuthenticated: boolean organizationAllowList: OrganizationAllowList autoCondenseContext: boolean diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index a04d0dd40e..30d9337914 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -37,6 +37,7 @@ const App = () => { telemetryKey, machineId, cloudUserInfo, + cloudIsAuthenticated, } = useExtensionState() const [showAnnouncement, setShowAnnouncement] = useState(false) @@ -127,7 +128,13 @@ const App = () => { {tab === "settings" && ( setTab("chat")} targetSection={currentSection} /> )} - {tab === "account" && switchTab("chat")} />} + {tab === "account" && ( + switchTab("chat")} + /> + )} void } -export const AccountView = ({ userInfo, onDone }: AccountViewProps) => { +export const AccountView = ({ userInfo, isAuthenticated, onDone }: AccountViewProps) => { const { t } = useAppTranslation() const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg" @@ -23,27 +24,29 @@ export const AccountView = ({ userInfo, onDone }: AccountViewProps) => { {t("settings:common.done")} - {userInfo ? ( + {isAuthenticated ? ( <> -
-
- {userInfo?.picture ? ( - {t("account:profilePicture")} - ) : ( -
- {userInfo?.name?.charAt(0) || userInfo?.email?.charAt(0) || "?"} -
- )} + {userInfo && ( +
+
+ {userInfo?.picture ? ( + {t("account:profilePicture")} + ) : ( +
+ {userInfo?.name?.charAt(0) || userInfo?.email?.charAt(0) || "?"} +
+ )} +
+

+ {userInfo?.name || t("account:unknownUser")} +

+

{userInfo?.email || ""}

-

- {userInfo?.name || t("account:unknownUser")} -

-

{userInfo?.email || ""}

-
+ )}
organizationAllowList: OrganizationAllowList + cloudIsAuthenticated: boolean maxConcurrentFileReads?: number condensingApiConfigId?: string setCondensingApiConfigId: (value: string) => void @@ -199,6 +200,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode terminalCompressProgressBar: true, // Default to compress progress bar output historyPreviewCollapsed: false, // Initialize the new state (default to expanded) cloudUserInfo: null, + cloudIsAuthenticated: false, organizationAllowList: ORGANIZATION_ALLOW_ALL, autoCondenseContext: true, autoCondenseContextPercent: 100, @@ -317,6 +319,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode writeDelayMs: state.writeDelayMs, screenshotQuality: state.screenshotQuality, routerModels: extensionRouterModels, + cloudIsAuthenticated: state.cloudIsAuthenticated ?? false, setExperimentEnabled: (id, enabled) => setState((prevState) => ({ ...prevState, experiments: { ...prevState.experiments, [id]: enabled } })), setApiConfiguration, diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx index ff29b9669e..30f5fc7111 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.test.tsx @@ -207,6 +207,7 @@ describe("mergeExtensionState", () => { organizationAllowList: { allowAll: true, providers: {} }, autoCondenseContext: true, autoCondenseContextPercent: 100, + cloudIsAuthenticated: false, } const prevState: ExtensionState = {