Skip to content
Closed
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
81 changes: 79 additions & 2 deletions packages/cloud/src/auth/WebAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,24 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
* This method initiates the authentication flow by generating a state parameter
* and opening the browser to the authorization URL.
*/
/**
* Detects if running in Firebase Studio IDE (formerly IDX Google) environment
*/
private isFirebaseStudioIDE(): boolean {
const appName = vscode.env.appName?.toLowerCase() || ""
const remoteName = vscode.env.remoteName?.toLowerCase() || ""

return (
appName.includes("idx") ||
appName.includes("firebase") ||
appName.includes("studio") ||
remoteName.includes("idx") ||
remoteName.includes("firebase") ||
process.env.IDX_WORKSPACE_ID !== undefined ||
process.env.FIREBASE_PROJECT_ID !== undefined
)
}

public async login(): Promise<void> {
try {
// Generate a cryptographically random state parameter.
Expand All @@ -250,11 +268,34 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
const packageJSON = this.context.extension?.packageJSON
const publisher = packageJSON?.publisher ?? "RooVeterinaryInc"
const name = packageJSON?.name ?? "roo-cline"

const isCloudIDE = this.isFirebaseStudioIDE()
this.log(`[auth] Initiating login - Firebase Studio IDE detected: ${isCloudIDE}`)
this.log(`[auth] App name: ${vscode.env.appName}`)
this.log(`[auth] Remote name: ${vscode.env.remoteName}`)
this.log(`[auth] URI scheme: ${vscode.env.uriScheme}`)

const params = new URLSearchParams({
state,
auth_redirect: `${vscode.env.uriScheme}://${publisher}.${name}`,
})

// Add cloud IDE indicator for server-side handling
if (isCloudIDE) {
params.append("cloud_ide", "firebase_studio")
}

const url = `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}`
this.log(`[auth] Opening authentication URL: ${url}`)

if (isCloudIDE) {
// Show additional guidance for Firebase Studio IDE users
vscode.window.showInformationMessage(
"Opening authentication in Firebase Studio IDE. After signing in, the callback should be automatically handled.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User‐facing messages (e.g. the authentication guidance) are hardcoded. Use the translation (i18n) function instead to support localization.

Suggested change
"Opening authentication in Firebase Studio IDE. After signing in, the callback should be automatically handled.",
t("Opening authentication in Firebase Studio IDE. After signing in, the callback should be automatically handled."),

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

{ modal: false },
)
}

await vscode.env.openExternal(vscode.Uri.parse(url))
} catch (error) {
this.log(`[auth] Error initiating Roo Code Cloud auth: ${error}`)
Expand All @@ -277,8 +318,24 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
state: string | null,
organizationId?: string | null,
): Promise<void> {
const isCloudIDE = this.isFirebaseStudioIDE()

this.log(`[auth] Handling callback - Firebase Studio IDE: ${isCloudIDE}`)
this.log(`[auth] Code present: ${!!code}, State present: ${!!state}`)

if (!code || !state) {
vscode.window.showInformationMessage("Invalid Roo Code Cloud sign in url")
const message = "Invalid Roo Code Cloud sign in url"
this.log(`[auth] ${message}`)

if (isCloudIDE) {
// Provide more specific guidance for Firebase Studio IDE
vscode.window.showErrorMessage(
"Authentication callback failed in Firebase Studio IDE. Please try signing in again. " +
"If the issue persists, check that popup blockers are disabled and the extension has proper permissions.",
)
} else {
vscode.window.showInformationMessage(message)
}
return
}

Expand All @@ -288,20 +345,40 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A

if (state !== storedState) {
this.log("[auth] State mismatch in callback")
this.log(`[auth] Expected state: ${storedState}, Received state: ${state}`)
throw new Error("Invalid state parameter. Authentication request may have been tampered with.")
}

this.log("[auth] State validation successful, proceeding with sign-in")
const credentials = await this.clerkSignIn(code)

// Set organizationId (null for personal accounts)
credentials.organizationId = organizationId || null

await this.storeCredentials(credentials)

vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud")
const successMessage = "Successfully authenticated with Roo Code Cloud"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with naming guidelines, consider using 'Roo Code' instead of 'Roo Code Cloud' when referring to the product in user-facing messages.

This comment was generated because it violated a code review rule: irule_VrRKWqywZ2YV2SOE.

if (isCloudIDE) {
vscode.window.showInformationMessage(
`${successMessage} in Firebase Studio IDE. You can now use Roo Code Cloud features.`,
)
} else {
vscode.window.showInformationMessage(successMessage)
}

this.log("[auth] Successfully authenticated with Roo Code Cloud")
} catch (error) {
this.log(`[auth] Error handling Roo Code Cloud callback: ${error}`)

if (isCloudIDE) {
// Provide more detailed error information for Firebase Studio IDE
const errorMessage = error instanceof Error ? error.message : String(error)
vscode.window.showErrorMessage(
`Authentication failed in Firebase Studio IDE: ${errorMessage}. ` +
"Please try again or contact support if the issue persists.",
)
}

const previousState = this.state
this.state = "logged-out"
this.emit("logged-out", { previousState })
Expand Down
40 changes: 38 additions & 2 deletions packages/cloud/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,46 @@
import * as vscode from "vscode"

/**
* Detects if running in Firebase Studio IDE (formerly IDX Google) environment
*/
function isFirebaseStudioIDE(): boolean {
const appName = vscode.env.appName?.toLowerCase() || ""
const remoteName = vscode.env.remoteName?.toLowerCase() || ""

return (
appName.includes("idx") ||
appName.includes("firebase") ||
appName.includes("studio") ||
remoteName.includes("idx") ||
remoteName.includes("firebase") ||
process.env.IDX_WORKSPACE_ID !== undefined ||
process.env.FIREBASE_PROJECT_ID !== undefined
)
}

/**
* Get the User-Agent string for API requests
* @param context Optional extension context for more accurate version detection
* @returns User-Agent string in format "Roo-Code {version}"
* @returns User-Agent string in format "Roo-Code {version} ({environment})"
*/
export function getUserAgent(context?: vscode.ExtensionContext): string {
return `Roo-Code ${context?.extension?.packageJSON?.version || "unknown"}`
const version = context?.extension?.packageJSON?.version || "unknown"
const baseUserAgent = `Roo-Code ${version}`

// Add environment information for better debugging
const environment = []

if (isFirebaseStudioIDE()) {
environment.push("Firebase-Studio-IDE")
}

if (vscode.env.remoteName) {
environment.push(`Remote-${vscode.env.remoteName}`)
}

if (environment.length > 0) {
return `${baseUserAgent} (${environment.join("; ")})`
}

return baseUserAgent
}
76 changes: 71 additions & 5 deletions src/activate/handleUri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,67 @@ import { CloudService } from "@roo-code/cloud"

import { ClineProvider } from "../core/webview/ClineProvider"

/**
* Detects if running in Firebase Studio IDE (formerly IDX Google) environment
*/
function isFirebaseStudioIDE(): boolean {
// Check for Firebase Studio IDE specific environment indicators
const appName = vscode.env.appName?.toLowerCase() || ""
const remoteName = vscode.env.remoteName?.toLowerCase() || ""

// Firebase Studio IDE typically has these characteristics
return (
appName.includes("idx") ||
appName.includes("firebase") ||
appName.includes("studio") ||
remoteName.includes("idx") ||
remoteName.includes("firebase") ||
process.env.IDX_WORKSPACE_ID !== undefined ||
process.env.FIREBASE_PROJECT_ID !== undefined
)
}

export const handleUri = async (uri: vscode.Uri) => {
const path = uri.path
const query = new URLSearchParams(uri.query.replace(/\+/g, "%2B"))
const visibleProvider = ClineProvider.getVisibleInstance()

// Enhanced logging for debugging authentication issues in cloud IDEs
const isCloudIDE = isFirebaseStudioIDE()
console.log(`[handleUri] Processing URI: ${uri.toString()}`)
console.log(`[handleUri] Path: ${path}`)
console.log(`[handleUri] Query params: ${query.toString()}`)
console.log(`[handleUri] Firebase Studio IDE detected: ${isCloudIDE}`)
console.log(`[handleUri] App name: ${vscode.env.appName}`)
console.log(`[handleUri] Remote name: ${vscode.env.remoteName}`)
console.log(`[handleUri] URI scheme: ${vscode.env.uriScheme}`)

if (!visibleProvider) {
console.warn(`[handleUri] No visible provider found for URI: ${uri.toString()}`)
return
}

switch (path) {
case "/glama": {
const code = query.get("code")
if (code) {
console.log(`[handleUri] Processing Glama callback with code: ${code.substring(0, 10)}...`)
await visibleProvider.handleGlamaCallback(code)
}
break
}
case "/openrouter": {
const code = query.get("code")
if (code) {
console.log(`[handleUri] Processing OpenRouter callback with code: ${code.substring(0, 10)}...`)
await visibleProvider.handleOpenRouterCallback(code)
}
break
}
case "/requesty": {
const code = query.get("code")
if (code) {
console.log(`[handleUri] Processing Requesty callback with code: ${code.substring(0, 10)}...`)
await visibleProvider.handleRequestyCallback(code)
}
break
Expand All @@ -40,14 +74,46 @@ export const handleUri = async (uri: vscode.Uri) => {
const state = query.get("state")
const organizationId = query.get("organizationId")

await CloudService.instance.handleAuthCallback(
code,
state,
organizationId === "null" ? null : organizationId,
)
console.log(`[handleUri] Processing Clerk auth callback`)
console.log(`[handleUri] Code present: ${!!code}`)
console.log(`[handleUri] State present: ${!!state}`)
console.log(`[handleUri] Organization ID: ${organizationId}`)

if (isCloudIDE) {
console.log(`[handleUri] Firebase Studio IDE environment detected - using enhanced callback handling`)

// Show user feedback for cloud IDE environments
if (code && state) {
vscode.window.showInformationMessage(
"Authentication callback received in Firebase Studio IDE. Processing login...",
)
}
}

try {
await CloudService.instance.handleAuthCallback(
code,
state,
organizationId === "null" ? null : organizationId,
)

if (isCloudIDE) {
console.log(`[handleUri] Successfully processed auth callback in Firebase Studio IDE`)
}
} catch (error) {
console.error(`[handleUri] Error processing auth callback:`, error)

if (isCloudIDE) {
vscode.window.showErrorMessage(
`Authentication failed in Firebase Studio IDE: ${error instanceof Error ? error.message : String(error)}`,
)
}
throw error
}
break
}
default:
console.log(`[handleUri] Unhandled URI path: ${path}`)
break
}
}
38 changes: 38 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,51 @@ import { initializeI18n } from "./i18n"
let outputChannel: vscode.OutputChannel
let extensionContext: vscode.ExtensionContext

/**
* Detects if running in Firebase Studio IDE (formerly IDX Google) environment
*/
function isFirebaseStudioIDE(): boolean {
const appName = vscode.env.appName?.toLowerCase() || ""
const remoteName = vscode.env.remoteName?.toLowerCase() || ""

return (
appName.includes("idx") ||
appName.includes("firebase") ||
appName.includes("studio") ||
remoteName.includes("idx") ||
remoteName.includes("firebase") ||
process.env.IDX_WORKSPACE_ID !== undefined ||
process.env.FIREBASE_PROJECT_ID !== undefined
)
}

// This method is called when your extension is activated.
// Your extension is activated the very first time the command is executed.
export async function activate(context: vscode.ExtensionContext) {
extensionContext = context
outputChannel = vscode.window.createOutputChannel(Package.outputChannel)
context.subscriptions.push(outputChannel)

// Enhanced logging for Firebase Studio IDE environments
const isCloudIDE = isFirebaseStudioIDE()
outputChannel.appendLine(`${Package.name} extension activated - ${JSON.stringify(Package)}`)
outputChannel.appendLine(`Environment detection:`)
outputChannel.appendLine(` - Firebase Studio IDE: ${isCloudIDE}`)
outputChannel.appendLine(` - App name: ${vscode.env.appName}`)
outputChannel.appendLine(` - Remote name: ${vscode.env.remoteName}`)
outputChannel.appendLine(` - URI scheme: ${vscode.env.uriScheme}`)
outputChannel.appendLine(` - Machine ID: ${vscode.env.machineId}`)
outputChannel.appendLine(` - Session ID: ${vscode.env.sessionId}`)

if (isCloudIDE) {
outputChannel.appendLine(`Firebase Studio IDE environment detected - enabling enhanced authentication handling`)

// Show user notification about Firebase Studio IDE support
vscode.window.showInformationMessage(
"Roo Code detected Firebase Studio IDE environment. Enhanced authentication support is enabled.",
{ modal: false },
)
}

// Migrate old settings to new
await migrateSettings(context, outputChannel)
Expand Down