Просмотр исходного кода

Cloud: bugfixes: log refresh errors and allow logout from inactive-session (#4268)

* Cloud: log session refresh errors

* Cloud: bugfix: allow logouts during the inactive-session state
John Richmond 7 месяцев назад
Родитель
Сommit
2fda064ae9

+ 17 - 7
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<AuthServiceEvents> {
 
 	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")
@@ -281,14 +286,19 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
 			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
 		}
 	}
 

+ 1 - 0
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)

+ 13 - 0
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,

+ 1 - 0
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 = {

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -219,6 +219,7 @@ export type ExtensionState = Pick<
 	historyPreviewCollapsed?: boolean
 
 	cloudUserInfo: CloudUserInfo | null
+	cloudIsAuthenticated: boolean
 	organizationAllowList: OrganizationAllowList
 
 	autoCondenseContext: boolean

+ 8 - 1
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" && (
 				<SettingsView ref={settingsRef} onDone={() => setTab("chat")} targetSection={currentSection} />
 			)}
-			{tab === "account" && <AccountView userInfo={cloudUserInfo} onDone={() => switchTab("chat")} />}
+			{tab === "account" && (
+				<AccountView
+					userInfo={cloudUserInfo}
+					isAuthenticated={cloudIsAuthenticated}
+					onDone={() => switchTab("chat")}
+				/>
+			)}
 			<ChatView
 				ref={chatViewRef}
 				isHidden={tab !== "chat"}

+ 23 - 20
webview-ui/src/components/account/AccountView.tsx

@@ -7,10 +7,11 @@ import { vscode } from "@src/utils/vscode"
 
 type AccountViewProps = {
 	userInfo: CloudUserInfo | null
+	isAuthenticated: boolean
 	onDone: () => 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")}
 				</VSCodeButton>
 			</div>
-			{userInfo ? (
+			{isAuthenticated ? (
 				<>
-					<div className="flex flex-col items-center mb-6">
-						<div className="w-16 h-16 mb-3 rounded-full overflow-hidden">
-							{userInfo?.picture ? (
-								<img
-									src={userInfo.picture}
-									alt={t("account:profilePicture")}
-									className="w-full h-full object-cover"
-								/>
-							) : (
-								<div className="w-full h-full flex items-center justify-center bg-vscode-button-background text-vscode-button-foreground text-xl">
-									{userInfo?.name?.charAt(0) || userInfo?.email?.charAt(0) || "?"}
-								</div>
-							)}
+					{userInfo && (
+						<div className="flex flex-col items-center mb-6">
+							<div className="w-16 h-16 mb-3 rounded-full overflow-hidden">
+								{userInfo?.picture ? (
+									<img
+										src={userInfo.picture}
+										alt={t("account:profilePicture")}
+										className="w-full h-full object-cover"
+									/>
+								) : (
+									<div className="w-full h-full flex items-center justify-center bg-vscode-button-background text-vscode-button-foreground text-xl">
+										{userInfo?.name?.charAt(0) || userInfo?.email?.charAt(0) || "?"}
+									</div>
+								)}
+							</div>
+							<h2 className="text-lg font-medium text-vscode-foreground mb-1">
+								{userInfo?.name || t("account:unknownUser")}
+							</h2>
+							<p className="text-sm text-vscode-descriptionForeground">{userInfo?.email || ""}</p>
 						</div>
-						<h2 className="text-lg font-medium text-vscode-foreground mb-1">
-							{userInfo?.name || t("account:unknownUser")}
-						</h2>
-						<p className="text-sm text-vscode-descriptionForeground">{userInfo?.email || ""}</p>
-					</div>
+					)}
 					<div className="flex flex-col gap-2 mt-4">
 						<VSCodeButton
 							appearance="secondary"

+ 3 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -35,6 +35,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	filePaths: string[]
 	openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
 	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,

+ 1 - 0
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 = {