Skip to content

Commit d4c00e5

Browse files
authored
Simplify CloudService callbacks (#4097)
- Remove AuthService callbacks, add user-info event - Store userInfo on AuthService and fetch it when sessions go active - Simplify CloudService callbacks to just stateChanged - Add cloudUserInfo to state and remove authenticatedUser message
1 parent 7820b75 commit d4c00e5

File tree

12 files changed

+72
-56
lines changed

12 files changed

+72
-56
lines changed

packages/cloud/src/AuthService.ts

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import * as vscode from "vscode"
66

77
import type { CloudUserInfo } from "@roo-code/types"
88

9-
import { CloudServiceCallbacks } from "./types"
109
import { getClerkBaseUrl, getRooCodeApiUrl } from "./Config"
1110
import { RefreshTimer } from "./RefreshTimer"
1211

1312
export interface AuthServiceEvents {
1413
"active-session": [data: { previousState: AuthState }]
1514
"logged-out": [data: { previousState: AuthState }]
15+
"user-info": [data: { userInfo: CloudUserInfo }]
1616
}
1717

1818
const CLIENT_TOKEN_KEY = "clerk-client-token"
@@ -23,19 +23,18 @@ type AuthState = "initializing" | "logged-out" | "active-session" | "inactive-se
2323

2424
export class AuthService extends EventEmitter<AuthServiceEvents> {
2525
private context: vscode.ExtensionContext
26-
private userChanged: CloudServiceCallbacks["userChanged"]
2726
private timer: RefreshTimer
2827
private state: AuthState = "initializing"
2928

3029
private clientToken: string | null = null
3130
private sessionToken: string | null = null
3231
private sessionId: string | null = null
32+
private userInfo: CloudUserInfo | null = null
3333

34-
constructor(context: vscode.ExtensionContext, userChanged: CloudServiceCallbacks["userChanged"]) {
34+
constructor(context: vscode.ExtensionContext) {
3535
super()
3636

3737
this.context = context
38-
this.userChanged = userChanged
3938

4039
this.timer = new RefreshTimer({
4140
callback: async () => {
@@ -140,9 +139,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
140139
this.emit("active-session", { previousState })
141140
this.timer.start()
142141

143-
if (this.userChanged) {
144-
this.getUserInfo().then(this.userChanged)
145-
}
142+
this.fetchUserInfo()
146143

147144
vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud")
148145
console.log("[auth] Successfully authenticated with Roo Code Cloud")
@@ -174,6 +171,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
174171
this.clientToken = null
175172
this.sessionToken = null
176173
this.sessionId = null
174+
this.userInfo = null
177175
const previousState = this.state
178176
this.state = "logged-out"
179177
this.emit("logged-out", { previousState })
@@ -182,9 +180,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
182180
await this.clerkLogout(oldClientToken, oldSessionId)
183181
}
184182

185-
if (this.userChanged) {
186-
this.getUserInfo().then(this.userChanged)
187-
}
183+
this.fetchUserInfo()
188184

189185
vscode.window.showInformationMessage("Logged out from Roo Code Cloud")
190186
console.log("[auth] Logged out from Roo Code Cloud")
@@ -224,7 +220,7 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
224220
*
225221
* This method refreshes the session token using the client token.
226222
*/
227-
private async refreshSession() {
223+
private async refreshSession(): Promise<void> {
228224
if (!this.sessionId || !this.clientToken) {
229225
console.log("[auth] Cannot refresh session: missing session ID or token")
230226
this.state = "inactive-session"
@@ -237,24 +233,26 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
237233

238234
if (previousState !== "active-session") {
239235
this.emit("active-session", { previousState })
236+
this.fetchUserInfo()
237+
}
238+
}
240239

241-
if (this.userChanged) {
242-
this.getUserInfo().then(this.userChanged)
243-
}
240+
private async fetchUserInfo(): Promise<void> {
241+
if (!this.clientToken) {
242+
return
244243
}
244+
245+
this.userInfo = await this.clerkMe()
246+
this.emit("user-info", { userInfo: this.userInfo })
245247
}
246248

247249
/**
248250
* Extract user information from the ID token
249251
*
250252
* @returns User information from ID token claims or null if no ID token available
251253
*/
252-
public async getUserInfo(): Promise<CloudUserInfo | undefined> {
253-
if (!this.clientToken) {
254-
return undefined
255-
}
256-
257-
return await this.clerkMe()
254+
public getUserInfo(): CloudUserInfo | null {
255+
return this.userInfo
258256
}
259257

260258
private async clerkSignIn(
@@ -383,12 +381,12 @@ export class AuthService extends EventEmitter<AuthServiceEvents> {
383381
return this._instance
384382
}
385383

386-
static async createInstance(context: vscode.ExtensionContext, userChanged: CloudServiceCallbacks["userChanged"]) {
384+
static async createInstance(context: vscode.ExtensionContext) {
387385
if (this._instance) {
388386
throw new Error("AuthService instance already created")
389387
}
390388

391-
this._instance = new AuthService(context, userChanged)
389+
this._instance = new AuthService(context)
392390
await this._instance.initialize()
393391
return this._instance
394392
}

packages/cloud/src/CloudService.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export class CloudService {
1313

1414
private context: vscode.ExtensionContext
1515
private callbacks: CloudServiceCallbacks
16+
private authListener: () => void
1617
private authService: AuthService | null = null
1718
private settingsService: SettingsService | null = null
1819
private telemetryClient: TelemetryClient | null = null
@@ -21,6 +22,9 @@ export class CloudService {
2122
private constructor(context: vscode.ExtensionContext, callbacks: CloudServiceCallbacks) {
2223
this.context = context
2324
this.callbacks = callbacks
25+
this.authListener = () => {
26+
this.callbacks.stateChanged?.()
27+
}
2428
}
2529

2630
public async initialize(): Promise<void> {
@@ -29,12 +33,14 @@ export class CloudService {
2933
}
3034

3135
try {
32-
this.authService = await AuthService.createInstance(this.context, (userInfo) => {
33-
this.callbacks.userChanged?.(userInfo)
34-
})
36+
this.authService = await AuthService.createInstance(this.context)
37+
38+
this.authService.on("active-session", this.authListener)
39+
this.authService.on("logged-out", this.authListener)
40+
this.authService.on("user-info", this.authListener)
3541

3642
this.settingsService = await SettingsService.createInstance(this.context, () =>
37-
this.callbacks.settingsChanged?.(),
43+
this.callbacks.stateChanged?.(),
3844
)
3945

4046
this.telemetryClient = new TelemetryClient(this.authService)
@@ -74,7 +80,7 @@ export class CloudService {
7480
return this.authService!.hasActiveSession()
7581
}
7682

77-
public async getUserInfo(): Promise<CloudUserInfo | undefined> {
83+
public getUserInfo(): CloudUserInfo | null {
7884
this.ensureInitialized()
7985
return this.authService!.getUserInfo()
8086
}
@@ -106,6 +112,11 @@ export class CloudService {
106112
// Lifecycle
107113

108114
public dispose(): void {
115+
if (this.authService) {
116+
this.authService.off("active-session", this.authListener)
117+
this.authService.off("logged-out", this.authListener)
118+
this.authService.off("user-info", this.authListener)
119+
}
109120
if (this.settingsService) {
110121
this.settingsService.dispose()
111122
}

packages/cloud/src/__tests__/CloudService.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,14 @@ describe("CloudService", () => {
128128

129129
describe("createInstance", () => {
130130
it("should create and initialize CloudService instance", async () => {
131-
const callbacks = { userChanged: vi.fn(), settingsChanged: vi.fn() }
131+
const callbacks = {
132+
stateChanged: vi.fn(),
133+
}
134+
132135
const cloudService = await CloudService.createInstance(mockContext, callbacks)
133136

134137
expect(cloudService).toBeInstanceOf(CloudService)
135-
expect(AuthService.createInstance).toHaveBeenCalledWith(mockContext, expect.any(Function))
138+
expect(AuthService.createInstance).toHaveBeenCalledWith(mockContext)
136139
expect(SettingsService.createInstance).toHaveBeenCalledWith(mockContext, expect.any(Function))
137140
})
138141

@@ -150,7 +153,7 @@ describe("CloudService", () => {
150153
let callbacks: CloudServiceCallbacks
151154

152155
beforeEach(async () => {
153-
callbacks = { userChanged: vi.fn(), settingsChanged: vi.fn() }
156+
callbacks = { stateChanged: vi.fn() }
154157
cloudService = await CloudService.createInstance(mockContext, callbacks)
155158
})
156159

packages/cloud/src/types.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import { CloudUserInfo } from "@roo-code/types"
2-
31
export interface CloudServiceCallbacks {
4-
userChanged?: (userInfo: CloudUserInfo | undefined) => void
5-
settingsChanged?: () => void
2+
stateChanged?: () => void
63
}

src/core/webview/ClineProvider.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
type TerminalActionPromptType,
2424
type HistoryItem,
2525
ORGANIZATION_ALLOW_ALL,
26+
CloudUserInfo,
2627
} from "@roo-code/types"
2728
import { TelemetryService } from "@roo-code/telemetry"
2829
import { CloudService } from "@roo-code/cloud"
@@ -1296,6 +1297,7 @@ export class ClineProvider
12961297
maxReadFileLine,
12971298
terminalCompressProgressBar,
12981299
historyPreviewCollapsed,
1300+
cloudUserInfo,
12991301
organizationAllowList,
13001302
condensingApiConfigId,
13011303
customCondensingPrompt,
@@ -1391,6 +1393,7 @@ export class ClineProvider
13911393
terminalCompressProgressBar: terminalCompressProgressBar ?? true,
13921394
hasSystemPromptOverride,
13931395
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
1396+
cloudUserInfo,
13941397
organizationAllowList,
13951398
condensingApiConfigId,
13961399
customCondensingPrompt,
@@ -1436,6 +1439,16 @@ export class ClineProvider
14361439
)
14371440
}
14381441

1442+
let cloudUserInfo: CloudUserInfo | null = null
1443+
1444+
try {
1445+
cloudUserInfo = CloudService.instance.getUserInfo()
1446+
} catch (error) {
1447+
console.error(
1448+
`[getState] failed to get cloud user info: ${error instanceof Error ? error.message : String(error)}`,
1449+
)
1450+
}
1451+
14391452
// Return the same structure as before
14401453
return {
14411454
apiConfiguration: providerSettings,
@@ -1504,6 +1517,7 @@ export class ClineProvider
15041517
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
15051518
maxReadFileLine: stateValues.maxReadFileLine ?? -1,
15061519
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
1520+
cloudUserInfo,
15071521
organizationAllowList,
15081522
// Explicitly add condensing settings
15091523
condensingApiConfigId: stateValues.condensingApiConfigId,

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ describe("ClineProvider", () => {
423423
showRooIgnoredFiles: true,
424424
renderContext: "sidebar",
425425
maxReadFileLine: 500,
426+
cloudUserInfo: null,
426427
organizationAllowList: ORGANIZATION_ALLOW_ALL,
427428
autoCondenseContext: true,
428429
autoCondenseContextPercent: 100,

src/core/webview/webviewMessageHandler.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,6 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
106106
await Promise.all([
107107
await updateGlobalState("listApiConfigMeta", listApiConfig),
108108
await provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
109-
async () => {
110-
try {
111-
if (CloudService.instance.hasActiveSession()) {
112-
const userInfo = await CloudService.instance.getUserInfo()
113-
provider.postMessageToWebview({ type: "authenticatedUser", userInfo })
114-
}
115-
} catch (error) {
116-
provider.log(`AuthService#getUserInfo failed: ${error}`)
117-
}
118-
},
119109
])
120110
})
121111
.catch((error) =>

src/extension.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,7 @@ export async function activate(context: vscode.ExtensionContext) {
7070

7171
// Initialize Roo Code Cloud service.
7272
await CloudService.createInstance(context, {
73-
userChanged: (userInfo) =>
74-
ClineProvider.getVisibleInstance()?.postMessageToWebview({ type: "authenticatedUser", userInfo }),
75-
settingsChanged: () => ClineProvider.getVisibleInstance()?.postStateToWebview(),
73+
stateChanged: () => ClineProvider.getVisibleInstance()?.postStateToWebview(),
7674
})
7775

7876
// Initialize i18n for internationalization support

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export type ExtensionState = Pick<
217217
settingsImportedAt?: number
218218
historyPreviewCollapsed?: boolean
219219

220+
cloudUserInfo: CloudUserInfo | null
220221
organizationAllowList: OrganizationAllowList
221222

222223
autoCondenseContext: boolean

webview-ui/src/App.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef, useState } from "react"
22
import { useEvent } from "react-use"
33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
44

5-
import type { CloudUserInfo } from "@roo-code/types"
65
import { ExtensionMessage } from "@roo/ExtensionMessage"
76

87
import TranslationProvider from "./i18n/TranslationContext"
@@ -30,12 +29,18 @@ const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]
3029
}
3130

3231
const App = () => {
33-
const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } =
34-
useExtensionState()
32+
const {
33+
didHydrateState,
34+
showWelcome,
35+
shouldShowAnnouncement,
36+
telemetrySetting,
37+
telemetryKey,
38+
machineId,
39+
cloudUserInfo,
40+
} = useExtensionState()
3541

3642
const [showAnnouncement, setShowAnnouncement] = useState(false)
3743
const [tab, setTab] = useState<Tab>("chat")
38-
const [userInfo, setUserInfo] = useState<CloudUserInfo | null>(null)
3944

4045
const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
4146
isOpen: boolean
@@ -84,10 +89,6 @@ const App = () => {
8489
if (message.type === "acceptInput") {
8590
chatViewRef.current?.acceptInput()
8691
}
87-
88-
if (message.type === "authenticatedUser") {
89-
setUserInfo(message.userInfo || null)
90-
}
9192
},
9293
[switchTab],
9394
)
@@ -126,7 +127,7 @@ const App = () => {
126127
{tab === "settings" && (
127128
<SettingsView ref={settingsRef} onDone={() => setTab("chat")} targetSection={currentSection} />
128129
)}
129-
{tab === "account" && <AccountView userInfo={userInfo} onDone={() => switchTab("chat")} />}
130+
{tab === "account" && <AccountView userInfo={cloudUserInfo} onDone={() => switchTab("chat")} />}
130131
<ChatView
131132
ref={chatViewRef}
132133
isHidden={tab !== "chat"}

0 commit comments

Comments
 (0)