Skip to content

Commit ad201cc

Browse files
Cloud: support static cloud settings (#5435)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent 25be233 commit ad201cc

File tree

8 files changed

+469
-147
lines changed

8 files changed

+469
-147
lines changed

packages/cloud/src/CloudService.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { TelemetryService } from "@roo-code/telemetry"
1212
import { CloudServiceCallbacks } from "./types"
1313
import type { AuthService } from "./auth"
1414
import { WebAuthService, StaticTokenAuthService } from "./auth"
15-
import { SettingsService } from "./SettingsService"
15+
import type { SettingsService } from "./SettingsService"
16+
import { CloudSettingsService } from "./CloudSettingsService"
17+
import { StaticSettingsService } from "./StaticSettingsService"
1618
import { TelemetryClient } from "./TelemetryClient"
1719
import { ShareService, TaskNotFoundError } from "./ShareService"
1820

@@ -59,13 +61,20 @@ export class CloudService {
5961
this.authService.on("logged-out", this.authListener)
6062
this.authService.on("user-info", this.authListener)
6163

62-
this.settingsService = new SettingsService(
63-
this.context,
64-
this.authService,
65-
() => this.callbacks.stateChanged?.(),
66-
this.log,
67-
)
68-
this.settingsService.initialize()
64+
// Check for static settings environment variable
65+
const staticOrgSettings = process.env.ROO_CODE_CLOUD_ORG_SETTINGS
66+
if (staticOrgSettings && staticOrgSettings.length > 0) {
67+
this.settingsService = new StaticSettingsService(staticOrgSettings, this.log)
68+
} else {
69+
const cloudSettingsService = new CloudSettingsService(
70+
this.context,
71+
this.authService,
72+
() => this.callbacks.stateChanged?.(),
73+
this.log,
74+
)
75+
cloudSettingsService.initialize()
76+
this.settingsService = cloudSettingsService
77+
}
6978

7079
this.telemetryClient = new TelemetryClient(this.authService, this.settingsService)
7180

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import * as vscode from "vscode"
2+
3+
import {
4+
ORGANIZATION_ALLOW_ALL,
5+
OrganizationAllowList,
6+
OrganizationSettings,
7+
organizationSettingsSchema,
8+
} from "@roo-code/types"
9+
10+
import { getRooCodeApiUrl } from "./Config"
11+
import type { AuthService } from "./auth"
12+
import { RefreshTimer } from "./RefreshTimer"
13+
import type { SettingsService } from "./SettingsService"
14+
15+
const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"
16+
17+
export class CloudSettingsService implements SettingsService {
18+
private context: vscode.ExtensionContext
19+
private authService: AuthService
20+
private settings: OrganizationSettings | undefined = undefined
21+
private timer: RefreshTimer
22+
private log: (...args: unknown[]) => void
23+
24+
constructor(
25+
context: vscode.ExtensionContext,
26+
authService: AuthService,
27+
callback: () => void,
28+
log?: (...args: unknown[]) => void,
29+
) {
30+
this.context = context
31+
this.authService = authService
32+
this.log = log || console.log
33+
34+
this.timer = new RefreshTimer({
35+
callback: async () => {
36+
return await this.fetchSettings(callback)
37+
},
38+
successInterval: 30000,
39+
initialBackoffMs: 1000,
40+
maxBackoffMs: 30000,
41+
})
42+
}
43+
44+
public initialize(): void {
45+
this.loadCachedSettings()
46+
47+
// Clear cached settings if we have missed a log out.
48+
if (this.authService.getState() == "logged-out" && this.settings) {
49+
this.removeSettings()
50+
}
51+
52+
this.authService.on("active-session", () => {
53+
this.timer.start()
54+
})
55+
56+
this.authService.on("logged-out", () => {
57+
this.timer.stop()
58+
this.removeSettings()
59+
})
60+
61+
if (this.authService.hasActiveSession()) {
62+
this.timer.start()
63+
}
64+
}
65+
66+
private async fetchSettings(callback: () => void): Promise<boolean> {
67+
const token = this.authService.getSessionToken()
68+
69+
if (!token) {
70+
return false
71+
}
72+
73+
try {
74+
const response = await fetch(`${getRooCodeApiUrl()}/api/organization-settings`, {
75+
headers: {
76+
Authorization: `Bearer ${token}`,
77+
},
78+
})
79+
80+
if (!response.ok) {
81+
this.log(
82+
"[cloud-settings] Failed to fetch organization settings:",
83+
response.status,
84+
response.statusText,
85+
)
86+
return false
87+
}
88+
89+
const data = await response.json()
90+
const result = organizationSettingsSchema.safeParse(data)
91+
92+
if (!result.success) {
93+
this.log("[cloud-settings] Invalid organization settings format:", result.error)
94+
return false
95+
}
96+
97+
const newSettings = result.data
98+
99+
if (!this.settings || this.settings.version !== newSettings.version) {
100+
this.settings = newSettings
101+
await this.cacheSettings()
102+
callback()
103+
}
104+
105+
return true
106+
} catch (error) {
107+
this.log("[cloud-settings] Error fetching organization settings:", error)
108+
return false
109+
}
110+
}
111+
112+
private async cacheSettings(): Promise<void> {
113+
await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, this.settings)
114+
}
115+
116+
private loadCachedSettings(): void {
117+
this.settings = this.context.globalState.get<OrganizationSettings>(ORGANIZATION_SETTINGS_CACHE_KEY)
118+
}
119+
120+
public getAllowList(): OrganizationAllowList {
121+
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
122+
}
123+
124+
public getSettings(): OrganizationSettings | undefined {
125+
return this.settings
126+
}
127+
128+
private async removeSettings(): Promise<void> {
129+
this.settings = undefined
130+
await this.cacheSettings()
131+
}
132+
133+
public dispose(): void {
134+
this.timer.stop()
135+
}
136+
}
Lines changed: 22 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,23 @@
1-
import * as vscode from "vscode"
2-
3-
import {
4-
ORGANIZATION_ALLOW_ALL,
5-
OrganizationAllowList,
6-
OrganizationSettings,
7-
organizationSettingsSchema,
8-
} from "@roo-code/types"
9-
10-
import { getRooCodeApiUrl } from "./Config"
11-
import type { AuthService } from "./auth"
12-
import { RefreshTimer } from "./RefreshTimer"
13-
14-
const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"
15-
16-
export class SettingsService {
17-
private context: vscode.ExtensionContext
18-
private authService: AuthService
19-
private settings: OrganizationSettings | undefined = undefined
20-
private timer: RefreshTimer
21-
private log: (...args: unknown[]) => void
22-
23-
constructor(
24-
context: vscode.ExtensionContext,
25-
authService: AuthService,
26-
callback: () => void,
27-
log?: (...args: unknown[]) => void,
28-
) {
29-
this.context = context
30-
this.authService = authService
31-
this.log = log || console.log
32-
33-
this.timer = new RefreshTimer({
34-
callback: async () => {
35-
return await this.fetchSettings(callback)
36-
},
37-
successInterval: 30000,
38-
initialBackoffMs: 1000,
39-
maxBackoffMs: 30000,
40-
})
41-
}
42-
43-
public initialize(): void {
44-
this.loadCachedSettings()
45-
46-
// Clear cached settings if we have missed a log out.
47-
if (this.authService.getState() == "logged-out" && this.settings) {
48-
this.removeSettings()
49-
}
50-
51-
this.authService.on("active-session", () => {
52-
this.timer.start()
53-
})
54-
55-
this.authService.on("logged-out", () => {
56-
this.timer.stop()
57-
this.removeSettings()
58-
})
59-
60-
if (this.authService.hasActiveSession()) {
61-
this.timer.start()
62-
}
63-
}
64-
65-
private async fetchSettings(callback: () => void): Promise<boolean> {
66-
const token = this.authService.getSessionToken()
67-
68-
if (!token) {
69-
return false
70-
}
71-
72-
try {
73-
const response = await fetch(`${getRooCodeApiUrl()}/api/organization-settings`, {
74-
headers: {
75-
Authorization: `Bearer ${token}`,
76-
},
77-
})
78-
79-
if (!response.ok) {
80-
this.log(
81-
"[cloud-settings] Failed to fetch organization settings:",
82-
response.status,
83-
response.statusText,
84-
)
85-
return false
86-
}
87-
88-
const data = await response.json()
89-
const result = organizationSettingsSchema.safeParse(data)
90-
91-
if (!result.success) {
92-
this.log("[cloud-settings] Invalid organization settings format:", result.error)
93-
return false
94-
}
95-
96-
const newSettings = result.data
97-
98-
if (!this.settings || this.settings.version !== newSettings.version) {
99-
this.settings = newSettings
100-
await this.cacheSettings()
101-
callback()
102-
}
103-
104-
return true
105-
} catch (error) {
106-
this.log("[cloud-settings] Error fetching organization settings:", error)
107-
return false
108-
}
109-
}
110-
111-
private async cacheSettings(): Promise<void> {
112-
await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, this.settings)
113-
}
114-
115-
private loadCachedSettings(): void {
116-
this.settings = this.context.globalState.get<OrganizationSettings>(ORGANIZATION_SETTINGS_CACHE_KEY)
117-
}
118-
119-
public getAllowList(): OrganizationAllowList {
120-
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
121-
}
122-
123-
public getSettings(): OrganizationSettings | undefined {
124-
return this.settings
125-
}
126-
127-
public async removeSettings(): Promise<void> {
128-
this.settings = undefined
129-
await this.cacheSettings()
130-
}
131-
132-
public dispose(): void {
133-
this.timer.stop()
134-
}
1+
import type { OrganizationAllowList, OrganizationSettings } from "@roo-code/types"
2+
3+
/**
4+
* Interface for settings services that provide organization settings
5+
*/
6+
export interface SettingsService {
7+
/**
8+
* Get the organization allow list
9+
* @returns The organization allow list or default if none available
10+
*/
11+
getAllowList(): OrganizationAllowList
12+
13+
/**
14+
* Get the current organization settings
15+
* @returns The organization settings or undefined if none available
16+
*/
17+
getSettings(): OrganizationSettings | undefined
18+
19+
/**
20+
* Dispose of the settings service and clean up resources
21+
*/
22+
dispose(): void
13523
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
ORGANIZATION_ALLOW_ALL,
3+
OrganizationAllowList,
4+
OrganizationSettings,
5+
organizationSettingsSchema,
6+
} from "@roo-code/types"
7+
8+
import type { SettingsService } from "./SettingsService"
9+
10+
export class StaticSettingsService implements SettingsService {
11+
private settings: OrganizationSettings
12+
private log: (...args: unknown[]) => void
13+
14+
constructor(envValue: string, log?: (...args: unknown[]) => void) {
15+
this.log = log || console.log
16+
this.settings = this.parseEnvironmentSettings(envValue)
17+
}
18+
19+
private parseEnvironmentSettings(envValue: string): OrganizationSettings {
20+
try {
21+
const decodedValue = Buffer.from(envValue, "base64").toString("utf-8")
22+
const parsedJson = JSON.parse(decodedValue)
23+
return organizationSettingsSchema.parse(parsedJson)
24+
} catch (error) {
25+
this.log(`[StaticSettingsService] failed to parse static settings: ${error.message}`, error)
26+
throw new Error("Failed to parse static settings", { cause: error })
27+
}
28+
}
29+
30+
public getAllowList(): OrganizationAllowList {
31+
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
32+
}
33+
34+
public getSettings(): OrganizationSettings | undefined {
35+
return this.settings
36+
}
37+
38+
public dispose(): void {
39+
// No resources to clean up for static settings
40+
}
41+
}

packages/cloud/src/TelemetryClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { BaseTelemetryClient } from "@roo-code/telemetry"
88

99
import { getRooCodeApiUrl } from "./Config"
1010
import type { AuthService } from "./auth"
11-
import { SettingsService } from "./SettingsService"
11+
import type { SettingsService } from "./SettingsService"
1212

1313
export class TelemetryClient extends BaseTelemetryClient {
1414
constructor(

0 commit comments

Comments
 (0)