Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions packages/cloud/src/CloudService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { TelemetryService } from "@roo-code/telemetry"
import { CloudServiceCallbacks } from "./types"
import type { AuthService } from "./auth"
import { WebAuthService, StaticTokenAuthService } from "./auth"
import { SettingsService } from "./SettingsService"
import type { SettingsService } from "./SettingsService"
import { CloudSettingsService } from "./CloudSettingsService"
import { StaticSettingsService } from "./StaticSettingsService"
import { TelemetryClient } from "./TelemetryClient"
import { ShareService, TaskNotFoundError } from "./ShareService"

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

this.settingsService = new SettingsService(
this.context,
this.authService,
() => this.callbacks.stateChanged?.(),
this.log,
)
this.settingsService.initialize()
// Check for static settings environment variable
const staticOrgSettings = process.env.ROO_CODE_CLOUD_ORG_SETTINGS
if (staticOrgSettings && staticOrgSettings.length > 0) {
this.settingsService = new StaticSettingsService(staticOrgSettings, this.log)
} else {
const cloudSettingsService = new CloudSettingsService(
this.context,
this.authService,
() => this.callbacks.stateChanged?.(),
this.log,
)
cloudSettingsService.initialize()
this.settingsService = cloudSettingsService
}

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

Expand Down
136 changes: 136 additions & 0 deletions packages/cloud/src/CloudSettingsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import * as vscode from "vscode"

import {
ORGANIZATION_ALLOW_ALL,
OrganizationAllowList,
OrganizationSettings,
organizationSettingsSchema,
} from "@roo-code/types"

import { getRooCodeApiUrl } from "./Config"
import type { AuthService } from "./auth"
import { RefreshTimer } from "./RefreshTimer"
import type { SettingsService } from "./SettingsService"

const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"

export class CloudSettingsService implements SettingsService {
private context: vscode.ExtensionContext
private authService: AuthService
private settings: OrganizationSettings | undefined = undefined
private timer: RefreshTimer
private log: (...args: unknown[]) => void

constructor(
context: vscode.ExtensionContext,
authService: AuthService,
callback: () => void,
log?: (...args: unknown[]) => void,
) {
this.context = context
this.authService = authService
this.log = log || console.log

this.timer = new RefreshTimer({
callback: async () => {
return await this.fetchSettings(callback)
},
successInterval: 30000,
initialBackoffMs: 1000,
maxBackoffMs: 30000,
})
}

public initialize(): void {
this.loadCachedSettings()

// Clear cached settings if we have missed a log out.
if (this.authService.getState() == "logged-out" && this.settings) {
this.removeSettings()
}

this.authService.on("active-session", () => {
this.timer.start()
})

this.authService.on("logged-out", () => {
this.timer.stop()
this.removeSettings()
})

if (this.authService.hasActiveSession()) {
this.timer.start()
}
}

private async fetchSettings(callback: () => void): Promise<boolean> {
const token = this.authService.getSessionToken()

if (!token) {
return false
}

try {
const response = await fetch(`${getRooCodeApiUrl()}/api/organization-settings`, {
headers: {
Authorization: `Bearer ${token}`,
},
})

if (!response.ok) {
this.log(
"[cloud-settings] Failed to fetch organization settings:",
response.status,
response.statusText,
)
return false
}

const data = await response.json()
const result = organizationSettingsSchema.safeParse(data)

if (!result.success) {
this.log("[cloud-settings] Invalid organization settings format:", result.error)
return false
}

const newSettings = result.data

if (!this.settings || this.settings.version !== newSettings.version) {
this.settings = newSettings
await this.cacheSettings()
callback()
}

return true
} catch (error) {
this.log("[cloud-settings] Error fetching organization settings:", error)
return false
}
}

private async cacheSettings(): Promise<void> {
await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, this.settings)
}

private loadCachedSettings(): void {
this.settings = this.context.globalState.get<OrganizationSettings>(ORGANIZATION_SETTINGS_CACHE_KEY)
}

public getAllowList(): OrganizationAllowList {
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
}

public getSettings(): OrganizationSettings | undefined {
return this.settings
}

private async removeSettings(): Promise<void> {
this.settings = undefined
await this.cacheSettings()
}

public dispose(): void {
this.timer.stop()
}
}
156 changes: 22 additions & 134 deletions packages/cloud/src/SettingsService.ts
Original file line number Diff line number Diff line change
@@ -1,135 +1,23 @@
import * as vscode from "vscode"

import {
ORGANIZATION_ALLOW_ALL,
OrganizationAllowList,
OrganizationSettings,
organizationSettingsSchema,
} from "@roo-code/types"

import { getRooCodeApiUrl } from "./Config"
import type { AuthService } from "./auth"
import { RefreshTimer } from "./RefreshTimer"

const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"

export class SettingsService {
private context: vscode.ExtensionContext
private authService: AuthService
private settings: OrganizationSettings | undefined = undefined
private timer: RefreshTimer
private log: (...args: unknown[]) => void

constructor(
context: vscode.ExtensionContext,
authService: AuthService,
callback: () => void,
log?: (...args: unknown[]) => void,
) {
this.context = context
this.authService = authService
this.log = log || console.log

this.timer = new RefreshTimer({
callback: async () => {
return await this.fetchSettings(callback)
},
successInterval: 30000,
initialBackoffMs: 1000,
maxBackoffMs: 30000,
})
}

public initialize(): void {
this.loadCachedSettings()

// Clear cached settings if we have missed a log out.
if (this.authService.getState() == "logged-out" && this.settings) {
this.removeSettings()
}

this.authService.on("active-session", () => {
this.timer.start()
})

this.authService.on("logged-out", () => {
this.timer.stop()
this.removeSettings()
})

if (this.authService.hasActiveSession()) {
this.timer.start()
}
}

private async fetchSettings(callback: () => void): Promise<boolean> {
const token = this.authService.getSessionToken()

if (!token) {
return false
}

try {
const response = await fetch(`${getRooCodeApiUrl()}/api/organization-settings`, {
headers: {
Authorization: `Bearer ${token}`,
},
})

if (!response.ok) {
this.log(
"[cloud-settings] Failed to fetch organization settings:",
response.status,
response.statusText,
)
return false
}

const data = await response.json()
const result = organizationSettingsSchema.safeParse(data)

if (!result.success) {
this.log("[cloud-settings] Invalid organization settings format:", result.error)
return false
}

const newSettings = result.data

if (!this.settings || this.settings.version !== newSettings.version) {
this.settings = newSettings
await this.cacheSettings()
callback()
}

return true
} catch (error) {
this.log("[cloud-settings] Error fetching organization settings:", error)
return false
}
}

private async cacheSettings(): Promise<void> {
await this.context.globalState.update(ORGANIZATION_SETTINGS_CACHE_KEY, this.settings)
}

private loadCachedSettings(): void {
this.settings = this.context.globalState.get<OrganizationSettings>(ORGANIZATION_SETTINGS_CACHE_KEY)
}

public getAllowList(): OrganizationAllowList {
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
}

public getSettings(): OrganizationSettings | undefined {
return this.settings
}

public async removeSettings(): Promise<void> {
this.settings = undefined
await this.cacheSettings()
}

public dispose(): void {
this.timer.stop()
}
import type { OrganizationAllowList, OrganizationSettings } from "@roo-code/types"

/**
* Interface for settings services that provide organization settings
*/
export interface SettingsService {
/**
* Get the organization allow list
* @returns The organization allow list or default if none available
*/
getAllowList(): OrganizationAllowList

/**
* Get the current organization settings
* @returns The organization settings or undefined if none available
*/
getSettings(): OrganizationSettings | undefined

/**
* Dispose of the settings service and clean up resources
*/
dispose(): void
}
41 changes: 41 additions & 0 deletions packages/cloud/src/StaticSettingsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
ORGANIZATION_ALLOW_ALL,
OrganizationAllowList,
OrganizationSettings,
organizationSettingsSchema,
} from "@roo-code/types"

import type { SettingsService } from "./SettingsService"

export class StaticSettingsService implements SettingsService {
private settings: OrganizationSettings
private log: (...args: unknown[]) => void

constructor(envValue: string, log?: (...args: unknown[]) => void) {
this.log = log || console.log
this.settings = this.parseEnvironmentSettings(envValue)
}

private parseEnvironmentSettings(envValue: string): OrganizationSettings {
try {
const decodedValue = Buffer.from(envValue, "base64").toString("utf-8")
const parsedJson = JSON.parse(decodedValue)
return organizationSettingsSchema.parse(parsedJson)
} catch (error) {
this.log(`[StaticSettingsService] failed to parse static settings: ${error.message}`, error)
throw new Error("Failed to parse static settings", { cause: error })
}
}

public getAllowList(): OrganizationAllowList {
return this.settings?.allowList || ORGANIZATION_ALLOW_ALL
}

public getSettings(): OrganizationSettings | undefined {
return this.settings
}

public dispose(): void {
// No resources to clean up for static settings
}
}
2 changes: 1 addition & 1 deletion packages/cloud/src/TelemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { BaseTelemetryClient } from "@roo-code/telemetry"

import { getRooCodeApiUrl } from "./Config"
import type { AuthService } from "./auth"
import { SettingsService } from "./SettingsService"
import type { SettingsService } from "./SettingsService"

export class TelemetryClient extends BaseTelemetryClient {
constructor(
Expand Down
Loading
Loading