moving firebase auth logic to webview because nodejs firebase token refresh is not supported

This commit is contained in:
pashpashpash
2025-02-09 02:18:23 -08:00
parent 82d9385b8b
commit 2fde7642c2
10 changed files with 905 additions and 357 deletions
+12 -10
View File
@@ -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 {
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}';">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; connect-src https://identitytoolkit.googleapis.com https://*.firebaseauth.com https://*.firebaseio.com https://*.googleapis.com https://*.firebase.com; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}' 'unsafe-eval';">
<link rel="stylesheet" type="text/css" href="${stylesUri}">
<link href="${codiconsUri}" rel="stylesheet" />
<title>Cline</title>
@@ -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)
-338
View File
@@ -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<ClineProvider>
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<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
private async retryWithBackoff<T>(operation: () => Promise<T>, config: RetryConfig = DEFAULT_RETRY_CONFIG): Promise<T> {
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 })
}
}
+2
View File
@@ -26,6 +26,7 @@ export interface ExtensionMessage {
| "vsCodeLmModels"
| "requestVsCodeLmModels"
| "emailSubscribed"
| "authCallback"
text?: string
action?:
| "chatButtonClicked"
@@ -46,6 +47,7 @@ export interface ExtensionMessage {
openRouterModels?: Record<string, ModelInfo>
openAiModels?: string[]
mcpServers?: McpServer[]
customToken?: string
}
export type Platform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32" | "unknown"
+5
View File
@@ -0,0 +1,5 @@
export interface UserInfo {
displayName: string | null
email: string | null
photoURL: string | null
}
+7
View File
@@ -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"
+758
View File
@@ -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",
+2 -1
View File
@@ -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"
}
+4 -1
View File
@@ -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 (
<ExtensionStateContextProvider>
<AppContent />
<FirebaseAuthProvider>
<AppContent />
</FirebaseAuthProvider>
</ExtensionStateContextProvider>
)
}
@@ -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",
}}>
<div style={{ marginBottom: 5 }}>
{isLoggedIn ? (
{user ? (
<>
{userInfo?.photoURL && (
{user.photoURL && (
<img
src={userInfo.photoURL}
src={user.photoURL}
alt="Profile"
style={{
width: 48,
@@ -66,8 +69,8 @@ const AccountView = ({ onDone }: AccountViewProps) => {
/>
)}
<div style={{ fontSize: "14px", marginBottom: 10 }}>
{userInfo?.displayName && <div>{userInfo.displayName}</div>}
{userInfo?.email && <div>{userInfo.email}</div>}
{user.displayName && <div>{user.displayName}</div>}
{user.email && <div>{user.email}</div>}
</div>
<VSCodeButton onClick={handleLogout}>Log out</VSCodeButton>
</>
@@ -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<void>
handleSignOut: () => Promise<void>
}
const FirebaseAuthContext = createContext<FirebaseAuthContextType | undefined>(undefined)
export const FirebaseAuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(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 (
<FirebaseAuthContext.Provider value={{ user, isInitialized, signInWithToken, handleSignOut }}>
{children}
</FirebaseAuthContext.Provider>
)
}
export const useFirebaseAuth = () => {
const context = useContext(FirebaseAuthContext)
if (context === undefined) {
throw new Error("useFirebaseAuth must be used within a FirebaseAuthProvider")
}
return context
}