diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 9eb4ed9e98..82c873e405 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -14,7 +14,7 @@ import { selectImages } from "../../integrations/misc/process-images" import { getTheme } from "../../integrations/theme/getTheme" import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker" import { McpHub } from "../../services/mcp/McpHub" -import { FirebaseAuthManager, UserInfo } from "../../services/auth/FirebaseAuthManager" +import { UserInfo } from "../../shared/UserInfo" import { ApiProvider, ModelInfo } from "../../shared/api" import { findLast } from "../../shared/array" import { ExtensionMessage, ExtensionState, Platform } from "../../shared/ExtensionMessage" @@ -105,7 +105,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { private cline?: Cline workspaceTracker?: WorkspaceTracker mcpHub?: McpHub - private authManager: FirebaseAuthManager private latestAnnouncementId = "jan-20-2025" // update to some unique identifier when we add a new announcement constructor( @@ -116,7 +115,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { ClineProvider.activeInstances.add(this) this.workspaceTracker = new WorkspaceTracker(this) this.mcpHub = new McpHub(this) - this.authManager = new FirebaseAuthManager(this) } /* @@ -142,7 +140,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { this.workspaceTracker = undefined this.mcpHub?.dispose() this.mcpHub = undefined - this.authManager.dispose() this.outputChannel.appendLine("Disposed all disposables") ClineProvider.activeInstances.delete(this) } @@ -150,7 +147,6 @@ export class ClineProvider implements vscode.WebviewViewProvider { // Auth methods async handleSignOut() { try { - await this.authManager.signOut() await this.storeSecret("authToken", undefined) await this.storeSecret("clineApiKey", undefined) await this.updateGlobalState("apiProvider", "openrouter") @@ -358,7 +354,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { - + Cline @@ -382,6 +378,10 @@ export class ClineProvider implements vscode.WebviewViewProvider { webview.onDidReceiveMessage( async (message: WebviewMessage) => { switch (message.type) { + case "authStateChanged": + await this.setUserInfo(message.user || undefined) + await this.postStateToWebview() + break case "webviewDidLaunch": this.postStateToWebview() this.workspaceTracker?.populateFilePaths() // don't await @@ -987,12 +987,14 @@ export class ClineProvider implements vscode.WebviewViewProvider { async handleAuthCallback(customToken: string, apiKey: string) { try { - // Store the custom token for future re-authentication - await this.storeSecret("authToken", customToken) + // Store API key for API calls await this.storeSecret("clineApiKey", apiKey) - // Sign in with Firebase using the custom token - await this.authManager.signInWithCustomToken(customToken) + // Send custom token to webview for Firebase auth + await this.postMessageToWebview({ + type: "authCallback", + customToken, + }) const clineProvider: ApiProvider = "cline" await this.updateGlobalState("apiProvider", clineProvider) diff --git a/src/services/auth/FirebaseAuthManager.ts b/src/services/auth/FirebaseAuthManager.ts deleted file mode 100644 index 6d7e85359b..0000000000 --- a/src/services/auth/FirebaseAuthManager.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { initializeApp } from "firebase/app" -import { - Auth, - User, - browserLocalPersistence, - getAuth, - onAuthStateChanged, - setPersistence, - signInWithCustomToken, - signOut, - AuthError as FirebaseAuthError, -} from "firebase/auth" -import * as vscode from "vscode" -import { ClineProvider } from "../../core/webview/ClineProvider" -import { firebaseConfig } from "./config" - -enum AuthErrorType { - Network = "network", - InvalidToken = "invalid_token", - ExpiredToken = "expired_token", - TokenMismatch = "token_mismatch", - Other = "other", -} - -interface AuthError { - type: AuthErrorType - message: string - originalError?: any -} - -interface RetryConfig { - maxAttempts: number - baseDelay: number // in ms - maxDelay: number // in ms -} - -const DEFAULT_RETRY_CONFIG: RetryConfig = { - maxAttempts: 3, - baseDelay: 1000, - maxDelay: 10000, -} - -export interface UserInfo { - displayName: string | null - email: string | null - photoURL: string | null -} - -export class FirebaseAuthManager { - private providerRef: WeakRef - private auth: Auth - private disposables: vscode.Disposable[] = [] - private isInitialAuthState = true - - constructor(provider: ClineProvider) { - console.log("Initializing FirebaseAuthManager", { provider }) - this.providerRef = new WeakRef(provider) - - try { - const app = initializeApp(firebaseConfig) - this.auth = getAuth(app) - console.log("Firebase app initialized", { appConfig: firebaseConfig }) - - // Set persistence to LOCAL to maintain auth state across sessions - this.setupPersistence() - - // Auth state listener - const unsubscribe = onAuthStateChanged(this.auth, this.handleAuthStateChange.bind(this)) - this.disposables.push({ dispose: () => unsubscribe() }) - console.log("Auth state change listener added") - } catch (error) { - console.error("Error initializing FirebaseAuthManager:", error) - throw error - } - } - - private async setupPersistence() { - try { - await this.retryWithBackoff(async () => { - await setPersistence(this.auth, browserLocalPersistence) - console.log("Firebase persistence set to LOCAL") - }) - } catch (error) { - const authError = this.classifyError(error) - console.error("Failed to set persistence after retries:", authError) - // Don't throw - persistence failure shouldn't prevent auth initialization - // But we should log it clearly for debugging - vscode.window.showErrorMessage( - "Warning: Failed to set authentication persistence. You may need to log in more frequently.", - ) - } - } - - private classifyError(error: any): AuthError { - console.log("Classifying auth error:", error) - - if (error?.code === "auth/network-request-failed") { - return { - type: AuthErrorType.Network, - message: "Network error during authentication", - originalError: error, - } - } - - // Only consider a token invalid if it's explicitly invalid or malformed - if (error?.code === "auth/invalid-custom-token" || error?.code === "auth/argument-error") { - return { - type: AuthErrorType.InvalidToken, - message: "Invalid authentication token format", - originalError: error, - } - } - - // Token mismatch indicates the token might be for a different project/environment - if (error?.code === "auth/custom-token-mismatch") { - return { - type: AuthErrorType.TokenMismatch, - message: "Token mismatch - may be for different environment", - originalError: error, - } - } - - // Handle expired tokens separately - if (error?.code === "auth/user-token-expired") { - return { - type: AuthErrorType.ExpiredToken, - message: "Authentication token has expired", - originalError: error, - } - } - - return { - type: AuthErrorType.Other, - message: error?.message || "Unknown authentication error", - originalError: error, - } - } - - private async delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)) - } - - private async retryWithBackoff(operation: () => Promise, config: RetryConfig = DEFAULT_RETRY_CONFIG): Promise { - let lastError: any - - for (let attempt = 1; attempt <= config.maxAttempts; attempt++) { - try { - console.log(`Attempting operation (attempt ${attempt}/${config.maxAttempts})`) - return await operation() - } catch (error) { - lastError = error - const authError = this.classifyError(error) - - // Don't retry for token-related errors - if ( - authError.type === AuthErrorType.InvalidToken || - authError.type === AuthErrorType.TokenMismatch || - authError.type === AuthErrorType.ExpiredToken - ) { - console.log("Token-related error - not retrying:", authError) - throw error - } - - if (attempt === config.maxAttempts) { - console.error(`All ${config.maxAttempts} attempts failed:`, authError) - throw error - } - - // Calculate delay with exponential backoff - const delay = Math.min(config.baseDelay * Math.pow(2, attempt - 1), config.maxDelay) - - console.log(`Attempt ${attempt} failed, retrying in ${delay}ms:`, authError) - await this.delay(delay) - } - } - - throw lastError - } - - private async cleanupFailedAuth(provider: ClineProvider, error: AuthError) { - console.log("Cleaning up failed authentication state", { errorType: error.type }) - try { - // Clear user info since it's no longer valid - await provider.setUserInfo(undefined) - console.log("User info cleared") - - // We no longer clear the auth token here - it will only be cleared on explicit user logout - // Instead, we just sign out of Firebase if needed - if ( - error.type === AuthErrorType.InvalidToken || - error.type === AuthErrorType.TokenMismatch || - error.type === AuthErrorType.ExpiredToken - ) { - console.log("Auth error detected - signing out but preserving token") - await this.signOut() - } - } catch (cleanupError) { - console.error("Error during auth state cleanup:", cleanupError) - } - } - - private async restoreSession() { - console.log("[restoreSession] Attempting to restore session") - const provider = this.providerRef.deref() - if (!provider) { - console.log("[restoreSession] Provider reference lost during session restore") - return - } - - // Get the auth state from the provider's state, which is loaded when the webview launches - const { apiConfiguration } = await provider.getState() - const storedToken = apiConfiguration?.authToken - console.log("[restoreSession] Auth state from provider:", { - hasToken: !!storedToken, - hasClineApiKey: !!apiConfiguration?.clineApiKey, - }) - - if (storedToken && apiConfiguration?.clineApiKey) { - console.log("Found stored custom token, attempting to restore session") - try { - await this.retryWithBackoff(async () => { - await this.signInWithCustomToken(storedToken) - console.log("Session restored successfully with custom token") - }) - } catch (error) { - const authError = this.classifyError(error) - console.error("Failed to restore session with custom token:", authError) - - // Clean up Firebase auth state but preserve the token - await this.cleanupFailedAuth(provider, authError) - - if (authError.type === AuthErrorType.Network) { - // For network errors, throw to allow retry - console.log("Network error during session restore - will retry later") - throw error - } - } - } else { - console.log("No stored custom token found") - } - } - - getCurrentUser(): User | null { - return this.auth.currentUser - } - - private async handleAuthStateChange(user: User | null) { - console.log("Auth state changed", { - user: user - ? { - uid: user.uid, - email: user.email, - emailVerified: user.emailVerified, - isAnonymous: user.isAnonymous, - metadata: user.metadata, - } - : null, - isInitialState: this.isInitialAuthState, - }) - - const provider = this.providerRef.deref() - if (!provider) { - console.error("Provider reference lost during auth state change") - return - } - - try { - if (user) { - console.log("User signed in", { - userId: user.uid, - lastLoginAt: user.metadata.lastSignInTime, - createdAt: user.metadata.creationTime, - }) - - // Store public user info in state - const userInfo = { - displayName: user.displayName, - email: user.email, - photoURL: user.photoURL, - } - await provider.setUserInfo(userInfo) - console.log("User info set in provider", { userInfo }) - } else if (!this.isInitialAuthState) { - // Only clear user info if this isn't the initial null state - console.log("User signed out (not initial state)") - await provider.setUserInfo(undefined) - } else { - console.log("Initial auth state is null, attempting session restore") - this.isInitialAuthState = false - try { - await this.restoreSession() - } catch (error) { - const authError = this.classifyError(error) - if (authError.type === AuthErrorType.Network) { - console.log("Session restore failed due to network error - will retry on next auth state change") - // Keep isInitialAuthState true so we retry on next change - this.isInitialAuthState = true - } else { - console.error("Session restore failed with non-network error:", authError) - } - } - } - - await provider.postStateToWebview() - console.log("Webview state updated after auth state change") - } catch (error) { - console.error("Error handling auth state change:", error) - // Attempt to clean up state if something went wrong - const authError = this.classifyError(error) - await this.cleanupFailedAuth(provider, authError) - } - } - - async signInWithCustomToken(token: string) { - console.log("Signing in with custom token") - try { - await this.retryWithBackoff(async () => { - await signInWithCustomToken(this.auth, token) - console.log("Successfully signed in with custom token") - }) - } catch (error) { - const authError = this.classifyError(error) - console.error("Failed to sign in with custom token:", authError) - throw error - } - } - - async signOut() { - console.log("Signing out") - this.isInitialAuthState = false // Ensure we treat the next null state as a real sign out - await signOut(this.auth) - } - - dispose() { - this.disposables.forEach((d) => d.dispose()) - console.log("Disposables disposed", { count: this.disposables.length }) - } -} diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index c08d611eda..4bf893f92d 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -26,6 +26,7 @@ export interface ExtensionMessage { | "vsCodeLmModels" | "requestVsCodeLmModels" | "emailSubscribed" + | "authCallback" text?: string action?: | "chatButtonClicked" @@ -46,6 +47,7 @@ export interface ExtensionMessage { openRouterModels?: Record openAiModels?: string[] mcpServers?: McpServer[] + customToken?: string } export type Platform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32" | "unknown" diff --git a/src/shared/UserInfo.ts b/src/shared/UserInfo.ts new file mode 100644 index 0000000000..a351b07794 --- /dev/null +++ b/src/shared/UserInfo.ts @@ -0,0 +1,5 @@ +export interface UserInfo { + displayName: string | null + email: string | null + photoURL: string | null +} diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index a18a3c405a..f9cad064c4 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -2,6 +2,7 @@ import { ApiConfiguration } from "./api" import { AutoApprovalSettings } from "./AutoApprovalSettings" import { BrowserSettings } from "./BrowserSettings" import { ChatSettings } from "./ChatSettings" +import { UserInfo } from "./UserInfo" export interface WebviewMessage { type: @@ -42,6 +43,8 @@ export interface WebviewMessage { | "accountLoginClicked" | "accountLogoutClicked" | "subscribeEmail" + | "authStateChanged" + | "authCallback" // | "relaunchChromeDebugMode" text?: string disabled?: boolean @@ -58,6 +61,10 @@ export interface WebviewMessage { serverName?: string toolName?: string autoApprove?: boolean + + // For auth + user?: UserInfo | null + customToken?: string } export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 5ac343c886..6590534f23 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -11,6 +11,7 @@ "@vscode/webview-ui-toolkit": "^1.4.0", "debounce": "^2.1.1", "fast-deep-equal": "^3.1.3", + "firebase": "^11.3.0", "fuse.js": "^7.0.0", "pretty-bytes": "^6.1.1", "react": "^18.3.1", @@ -3001,6 +3002,641 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@firebase/analytics": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.11.tgz", + "integrity": "sha512-zwuPiRE0+hgcS95JZbJ6DFQN4xYFO8IyGxpeePTV51YJMwCf3lkBa6FnZ/iXIqDKcBPMgMuuEZozI0BJWaLEYg==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/installations": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.17.tgz", + "integrity": "sha512-SJNVOeTvzdqZQvXFzj7yAirXnYcLDxh57wBFROfeowq/kRN1AqOw1tG6U4OiFOEhqi7s3xLze/LMkZatk2IEww==", + "dependencies": { + "@firebase/analytics": "0.10.11", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.6.12", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", + "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==" + }, + "node_modules/@firebase/app": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.11.0.tgz", + "integrity": "sha512-FaPl2RB2iClQK4IIAN4ruhzyGNRcvAwXk0Ltqdt55RiTmQ4aM2EAJicgI8QNQd2JIkeCT1K8JKsEba3T1/J7FA==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.11.tgz", + "integrity": "sha512-42zIfRI08/7bQqczAy7sY2JqZYEv3a1eNa4fLFdtJ54vNevbBIRSEA3fZgRqWFNHalh5ohsBXdrYgFqaRIuCcQ==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.18.tgz", + "integrity": "sha512-qjozwnwYmAIdrsVGrJk+hnF1WBois54IhZR6gO0wtZQoTvWL/GtiA2F31TIgAhF0ayUiZhztOv1RfC7YyrZGDQ==", + "dependencies": { + "@firebase/app-check": "0.8.11", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", + "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.49", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.49.tgz", + "integrity": "sha512-vf838b9WrHs2GH6NfsvA27a3ngDzCnR7oxmc5LJHaJ7mWSCuce1iDRJ2B6raJ6SH9592XXvtW+kzRcPYhC/LoA==", + "dependencies": { + "@firebase/app": "0.11.0", + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==" + }, + "node_modules/@firebase/auth": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.9.0.tgz", + "integrity": "sha512-Xz2mbEYauF689qXG/4HppS2+/yGo9R7B6eNUBh3H2+XpAZTGdx8d8TFsW/BMTAK9Q95NB0pb1Bbvfx0lwofq8Q==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.18.tgz", + "integrity": "sha512-dFBev8AMNb2AgIt9afwf/Ku4/0Wq9R9OFSeBB/xjyJt+RfQ9PnNWqU2oFphews23byLg6jle8twRA7iOYfRGRw==", + "dependencies": { + "@firebase/auth": "1.9.0", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.6.12", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==" + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", + "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.12.tgz", + "integrity": "sha512-YnxqjtohLbnb7raXt2YuA44cC1wA9GiehM/cmxrsoxKlFxBLy2V0OkRSj9gpngAE0UoJ421Wlav9ycO7lTPAUw==", + "dependencies": { + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.0.tgz", + "integrity": "sha512-inbLq0JyQD/d02Al3Lso0Hc8z1BVpB3dYSMFcQkeKhYyjn5bspLczLdasPbCOEUp8MOkLblLZhJuRs7Q/spFnw==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.12.tgz", + "integrity": "sha512-psFl5t6rSFHq3i3fnU1QQlc4BB9Hnhh8TgEqvQlPPm8kDLw8gYxvjqYw3c5CZW0+zKR837nwT6im/wtJUivMKw==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.3.tgz", + "integrity": "sha512-uHGQrSUeJvsDfA+IyHW5O4vdRPsCksEzv4T4Jins+bmQgYy20ZESU4x01xrQCn/nzqKHuQMEW99CoCO7D+5NiQ==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/database": "1.0.12", + "@firebase/database-types": "1.0.8", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.8.tgz", + "integrity": "sha512-6lPWIGeufhUq1heofZULyVvWFhD01TUrkkB9vyhmksjZ4XF7NaivQp9rICMk7QNhqwa+uDCaj4j+Q8qqcSVZ9g==", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.10.3" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.7.tgz", + "integrity": "sha512-DDYBjqSyd2vD3SjfRqI2Q9Ua1N0URP+1P0/SnNdVSp0/S5mkbaklIX/eU+199ze0XXnC61RYLqi6KYTYtGoz2A==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "@firebase/webchannel-wrapper": "1.0.3", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.42", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.42.tgz", + "integrity": "sha512-L/JqnVw7Bf+2jcCmW1nCiknkIVVM5RIR4rHE1UrtInAvP9vo8pUhFEZVzbwX71SuCoHOwjiaPDvVSeOFachokg==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/firestore": "4.7.7", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", + "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.2.tgz", + "integrity": "sha512-iKpFDoCYk/Qm+Qwv5ynRb9/yq64QOt0A0+t9NuekyAZnSoV56kSNq/PmsVmBauar5SlmEjhHk6QKdMBP9S0gXA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.12", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.19.tgz", + "integrity": "sha512-uw4tR8NcJCDu86UD63Za8A8SgFgmAVFb1XsGlkuBY7gpLyZWEFavWnwRkZ/8cUwpqUhp/SptXFZ1WFJSnOokLw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/functions": "0.12.2", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", + "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==" + }, + "node_modules/@firebase/installations": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.12.tgz", + "integrity": "sha512-ES/WpuAV2k2YtBTvdaknEo7IY8vaGjIjS3zhnHSAIvY9KwTR8XZFXOJoZ3nSkjN1A5R4MtEh+07drnzPDg9vaw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/util": "1.10.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.12.tgz", + "integrity": "sha512-RhcGknkxmFu92F6Jb3rXxv6a4sytPjJGifRZj8MSURPuv2Xu+/AispCXEfY1ZraobhEHTG5HLGsP6R4l9qB5aA==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/installations": "0.6.12", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", + "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.16", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.16.tgz", + "integrity": "sha512-VJ8sCEIeP3+XkfbJA7410WhYGHdloYFZXoHe/vt+vNVDGw8JQPTQSVTRvjrUprEf5I4Tbcnpr2H34lS6zhCHSA==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/installations": "0.6.12", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.10.3", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.16.tgz", + "integrity": "sha512-9HZZ88Ig3zQ0ok/Pwt4gQcNsOhoEy8hDHoGsV1am6ulgMuGuDVD2gl11Lere2ksL+msM12Lddi2x/7TCqmODZw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/messaging": "0.12.16", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", + "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==" + }, + "node_modules/@firebase/performance": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.0.tgz", + "integrity": "sha512-L91PwYuiJdKXKSRqsWNicvTppAJVzKjye03UlegeD6TkpKjb93T8AmJ9B0Mt0bcWHCNtnnRBCdSCvD2U9GZDjw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/installations": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.13.tgz", + "integrity": "sha512-pB0SMQj2TLQ6roDcX0YQDWvUnVgsVOl0VnUvyT/VBdCUuQYDHobZsPEuQsoEqmPA44KS/Gl0oyKqf+I8UPtRgw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/performance": "0.7.0", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", + "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==" + }, + "node_modules/@firebase/performance/node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.5.0.tgz", + "integrity": "sha512-weiEbpBp5PBJTHUWR4GwI7ZacaAg68BKha5QnZ8Go65W4oQjEWqCW/rfskABI/OkrGijlL3CUmCB/SA6mVo0qA==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/installations": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.12.tgz", + "integrity": "sha512-91jLWPtubIuPBngg9SzwvNCWzhMLcyBccmt7TNZP+y1cuYFNOWWHKUXQ3IrxCLB7WwLqQaEu7fTDAjHsTyBsSw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/remote-config": "0.5.0", + "@firebase/remote-config-types": "0.4.0", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz", + "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==" + }, + "node_modules/@firebase/storage": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.6.tgz", + "integrity": "sha512-BEJLYQzVgAoglRl5VRIRZ91RRBZgS/O37/PSGQJBYNuoLmFZUrtwrlLTOAwG776NlO9VQR+K2j15/36Lr2EqHA==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.16.tgz", + "integrity": "sha512-EeMuok/s0r938lEomia8XILEqSYULm7HcYZ/GTZLDWur0kMf2ktuPVZiTdRiwEV3Iki7FtQO5txrQ/0pLRVLAw==", + "dependencies": { + "@firebase/component": "0.6.12", + "@firebase/storage": "0.13.6", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", + "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.3.tgz", + "integrity": "sha512-wfoF5LTy0m2ufUapV0ZnpcGQvuavTbJ5Qr1Ze9OJGL70cSMvhDyjS4w2121XdA3lGZSTOsDOyGhpoDtYwck85A==", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/vertexai": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/vertexai/-/vertexai-1.0.4.tgz", + "integrity": "sha512-Nkf/r4u166b4Id6zrrW0Qtg1KyZpQvvYchtkebamnHtIfY+Qnt51I/sx4Saos/WrmO8SnrSU850LfmJ7pehYXg==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.6.12", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.3", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz", + "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@grpc/proto-loader/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -3780,6 +4416,60 @@ "node": ">= 8" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -9458,6 +10148,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.3.0.tgz", + "integrity": "sha512-wLuBsWqg/M3pay2qOOLLKjTQxPUO2yrJgZLt4TKUwA3c3lrFNM2zc40uzD9LQdUk6H9HEK6bXjGPFrpwmu7HzA==", + "dependencies": { + "@firebase/analytics": "0.10.11", + "@firebase/analytics-compat": "0.2.17", + "@firebase/app": "0.11.0", + "@firebase/app-check": "0.8.11", + "@firebase/app-check-compat": "0.3.18", + "@firebase/app-compat": "0.2.49", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.9.0", + "@firebase/auth-compat": "0.5.18", + "@firebase/data-connect": "0.3.0", + "@firebase/database": "1.0.12", + "@firebase/database-compat": "2.0.3", + "@firebase/firestore": "4.7.7", + "@firebase/firestore-compat": "0.3.42", + "@firebase/functions": "0.12.2", + "@firebase/functions-compat": "0.3.19", + "@firebase/installations": "0.6.12", + "@firebase/installations-compat": "0.2.12", + "@firebase/messaging": "0.12.16", + "@firebase/messaging-compat": "0.2.16", + "@firebase/performance": "0.7.0", + "@firebase/performance-compat": "0.2.13", + "@firebase/remote-config": "0.5.0", + "@firebase/remote-config-compat": "0.2.12", + "@firebase/storage": "0.13.6", + "@firebase/storage-compat": "0.3.16", + "@firebase/util": "1.10.3", + "@firebase/vertexai": "1.0.4" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -12888,6 +13613,11 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12918,6 +13648,11 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, + "node_modules/long": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", + "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -15644,6 +16379,29 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 8d89ea490f..9afe941038 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -6,6 +6,7 @@ "@vscode/webview-ui-toolkit": "^1.4.0", "debounce": "^2.1.1", "fast-deep-equal": "^3.1.3", + "firebase": "^11.3.0", "fuse.js": "^7.0.0", "pretty-bytes": "^6.1.1", "react": "^18.3.1", @@ -53,11 +54,11 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^15.0.6", "@testing-library/user-event": "^13.5.0", - "@types/vscode-webview": "^1.57.5", "@types/jest": "^27.5.2", "@types/node": "^20.x", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/vscode-webview": "^1.57.5", "jsdom": "^25.0.1", "vitest": "^2.1.9" } diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 0043ef330b..c432ac32d6 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -7,6 +7,7 @@ import SettingsView from "./components/settings/SettingsView" import WelcomeView from "./components/welcome/WelcomeView" import AccountView from "./components/account/AccountView" import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext" +import { FirebaseAuthProvider } from "./context/FirebaseAuthContext" import { vscode } from "./utils/vscode" import McpView from "./components/mcp/McpView" @@ -103,7 +104,9 @@ const AppContent = () => { const App = () => { return ( - + + + ) } diff --git a/webview-ui/src/components/account/AccountView.tsx b/webview-ui/src/components/account/AccountView.tsx index d96d3dcd4c..35b13bd65c 100644 --- a/webview-ui/src/components/account/AccountView.tsx +++ b/webview-ui/src/components/account/AccountView.tsx @@ -1,6 +1,6 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" import { memo } from "react" -import { useExtensionState } from "../../context/ExtensionStateContext" +import { useFirebaseAuth } from "../../context/FirebaseAuthContext" import { vscode } from "../../utils/vscode" type AccountViewProps = { @@ -8,14 +8,17 @@ type AccountViewProps = { } const AccountView = ({ onDone }: AccountViewProps) => { - const { isLoggedIn, userInfo } = useExtensionState() + const { user, handleSignOut } = useFirebaseAuth() const handleLogin = () => { vscode.postMessage({ type: "accountLoginClicked" }) } const handleLogout = () => { + // First notify extension to clear API keys and state vscode.postMessage({ type: "accountLogoutClicked" }) + // Then sign out of Firebase + handleSignOut() } return ( @@ -51,11 +54,11 @@ const AccountView = ({ onDone }: AccountViewProps) => { flexDirection: "column", }}>
- {isLoggedIn ? ( + {user ? ( <> - {userInfo?.photoURL && ( + {user.photoURL && ( Profile { /> )}
- {userInfo?.displayName &&
{userInfo.displayName}
} - {userInfo?.email &&
{userInfo.email}
} + {user.displayName &&
{user.displayName}
} + {user.email &&
{user.email}
}
Log out diff --git a/webview-ui/src/context/FirebaseAuthContext.tsx b/webview-ui/src/context/FirebaseAuthContext.tsx new file mode 100644 index 0000000000..c4c8d4aa26 --- /dev/null +++ b/webview-ui/src/context/FirebaseAuthContext.tsx @@ -0,0 +1,105 @@ +import { User, getAuth, signInWithCustomToken, signOut } from "firebase/auth" +import { initializeApp } from "firebase/app" +import React, { createContext, useCallback, useContext, useEffect, useState } from "react" +import { vscode } from "../utils/vscode" + +// Firebase configuration from extension +const firebaseConfig = { + apiKey: "AIzaSyDcXAaanNgR2_T0dq2oOl5XyKPksYHppVo", + authDomain: "cline-bot.firebaseapp.com", + projectId: "cline-bot", + storageBucket: "cline-bot.firebasestorage.app", + messagingSenderId: "364369702101", + appId: "1:364369702101:web:0013885dcf20b43799c65c", + measurementId: "G-MDPRELSCD1", +} + +interface FirebaseAuthContextType { + user: User | null + isInitialized: boolean + signInWithToken: (token: string) => Promise + handleSignOut: () => Promise +} + +const FirebaseAuthContext = createContext(undefined) + +export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null) + const [isInitialized, setIsInitialized] = useState(false) + + // Initialize Firebase + const app = initializeApp(firebaseConfig) + const auth = getAuth(app) + + // Handle auth state changes + useEffect(() => { + const unsubscribe = auth.onAuthStateChanged((user) => { + setUser(user) + setIsInitialized(true) + + // Sync auth state with extension + vscode.postMessage({ + type: "authStateChanged", + user: user + ? { + displayName: user.displayName, + email: user.email, + photoURL: user.photoURL, + } + : null, + }) + }) + + return () => unsubscribe() + }, [auth]) + + // Listen for auth callback from extension + useEffect(() => { + const handleMessage = (event: MessageEvent) => { + const message = event.data + if (message.type === "authCallback" && message.customToken) { + signInWithToken(message.customToken) + } + } + + window.addEventListener("message", handleMessage) + return () => window.removeEventListener("message", handleMessage) + }, []) + + const signInWithToken = useCallback( + async (token: string) => { + try { + await signInWithCustomToken(auth, token) + console.log("Successfully signed in with custom token") + } catch (error) { + console.error("Error signing in with custom token:", error) + throw error + } + }, + [auth], + ) + + const handleSignOut = useCallback(async () => { + try { + await signOut(auth) + console.log("Successfully signed out of Firebase") + } catch (error) { + console.error("Error signing out of Firebase:", error) + throw error + } + }, [auth]) + + return ( + + {children} + + ) +} + +export const useFirebaseAuth = () => { + const context = useContext(FirebaseAuthContext) + if (context === undefined) { + throw new Error("useFirebaseAuth must be used within a FirebaseAuthProvider") + } + return context +}