From 6cfe8dc748e6664247d5bffae3c339c18f761130 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 15:13:45 +0000 Subject: [PATCH 1/3] Replace Firebase Analytics with native GA4 Migrate analytics from the Firebase SDK to native Google Analytics (GA4) via @next/third-parties/google. The app only ever used Firebase for GA4, so this removes the Firebase Installations dependency that required five extra env vars. Analytics now needs only NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID. The unused NEXT_PUBLIC_FIREBASE_{API_KEY,APP_ID,AUTH_DOMAIN,PROJECT_ID,STORAGE_BUCKET, MESSAGING_SENDER_ID} vars are removed from config, CI, deploy files, and docs. - Mount in the root layout (loads gtag.js) - Rewrite useAnalytics().track() to use sendGAEvent (same signature) - Rewrite AnalyticsTracker to send page_view on route change via sendGAEvent, skipping the initial render to avoid double-counting - Delete src/lib/firebase.ts and drop the firebase npm dependency (@genkit-ai/firebase is unaffected) - Simplify the in-app analytics-not-configured warning Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017hiMXdXjDgUWiCzuueiN3d --- .env.example | 8 +- .github/workflows/README.md | 8 +- .github/workflows/copilot-test.yml | 22 +- AGENTS.md | 12 +- CHANGELOG.md | 12 + CLAUDE.md | 17 +- README.md | 14 +- SECURITY.md | 4 +- apphosting.dev.yaml | 18 - apphosting.prd.yaml | 18 - package-lock.json | 734 +-------------------------- package.json | 2 +- src/app/(app)/layout.tsx | 4 +- src/app/layout.tsx | 3 + src/components/analytics-tracker.tsx | 55 +- src/hooks/use-analytics.ts | 25 +- src/lib/firebase.ts | 79 --- 17 files changed, 94 insertions(+), 941 deletions(-) delete mode 100644 src/lib/firebase.ts diff --git a/.env.example b/.env.example index 8b4fa3af..e895149f 100644 --- a/.env.example +++ b/.env.example @@ -5,13 +5,7 @@ # ── Site ────────────────────────────────────────── NEXT_PUBLIC_SITE_URL=http://localhost:3000 -# ── Firebase (analytics — public client config) ── -NEXT_PUBLIC_FIREBASE_API_KEY= -NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= -NEXT_PUBLIC_FIREBASE_PROJECT_ID= -NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= -NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= -NEXT_PUBLIC_FIREBASE_APP_ID= +# ── Google Analytics (GA4 measurement ID, e.g. G-XXXXXXXXXX) ── NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= # ── AI (required for chat and insights) ────────── diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 44d9ab1b..d6e1efec 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -10,13 +10,7 @@ The `copilot-test.yml` workflow provides a standardized testing environment for Go to **Settings > Secrets and variables > Actions > Repository secrets** and add: -**Firebase (Client-side):** -- `NEXT_PUBLIC_FIREBASE_API_KEY` -- `NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN` -- `NEXT_PUBLIC_FIREBASE_PROJECT_ID` -- `NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET` -- `NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID` -- `NEXT_PUBLIC_FIREBASE_APP_ID` +**Google Analytics (Client-side):** - `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` **Google Sheets (Server-side):** diff --git a/.github/workflows/copilot-test.yml b/.github/workflows/copilot-test.yml index d67e28be..e660c64a 100644 --- a/.github/workflows/copilot-test.yml +++ b/.github/workflows/copilot-test.yml @@ -31,13 +31,7 @@ jobs: # Test XPUB for wallet testing TEST_XPUB: ${{ vars.TEST_XPUB }} - # Firebase (Client-side) - Public configuration - NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} - NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} - NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} - NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} - NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} - NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + # Google Analytics (Client-side) - Public GA4 measurement ID NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} # Google Sheets (Server-side) - Private @@ -77,13 +71,7 @@ jobs: - name: Create .env.local file run: | cat > .env.local << EOF - # Firebase (Client-side) - NEXT_PUBLIC_FIREBASE_API_KEY="${NEXT_PUBLIC_FIREBASE_API_KEY}" - NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN="${NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN}" - NEXT_PUBLIC_FIREBASE_PROJECT_ID="${NEXT_PUBLIC_FIREBASE_PROJECT_ID}" - NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET="${NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET}" - NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID="${NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID}" - NEXT_PUBLIC_FIREBASE_APP_ID="${NEXT_PUBLIC_FIREBASE_APP_ID}" + # Google Analytics (Client-side) NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID="${NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID}" # Google Sheets (Server-side) @@ -153,10 +141,10 @@ jobs: echo "- ❌ GOOGLE_GENAI_API_KEY: Not configured" >> $GITHUB_STEP_SUMMARY fi - if [ -n "$NEXT_PUBLIC_FIREBASE_API_KEY" ]; then - echo "- ✅ Firebase: Configured" >> $GITHUB_STEP_SUMMARY + if [ -n "$NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID" ]; then + echo "- ✅ Google Analytics: Configured" >> $GITHUB_STEP_SUMMARY else - echo "- ❌ Firebase: Not configured" >> $GITHUB_STEP_SUMMARY + echo "- ❌ Google Analytics: Not configured" >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY diff --git a/AGENTS.md b/AGENTS.md index 03266bd1..b41f63bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -165,13 +165,7 @@ Create a `.env` file in the project root with the following variables: # Site Configuration NEXT_PUBLIC_SITE_URL=http://localhost:3000 -# Firebase (required for analytics) -NEXT_PUBLIC_FIREBASE_API_KEY=your_firebase_api_key -NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com -NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id -NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com -NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id -NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id +# Google Analytics (GA4 measurement ID; required for analytics) NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-XXXXXXXXXX # OpenAI / Genkit (required for AI features) @@ -470,7 +464,7 @@ docker build -t bitsleuth-analyzer . # Run container docker run -p 3000:3000 \ -e OPENAI_API_KEY=your_key \ - -e NEXT_PUBLIC_FIREBASE_API_KEY=your_key \ + -e NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-XXXXXXXXXX \ bitsleuth-analyzer ``` @@ -534,7 +528,7 @@ For detailed contribution guidelines, including documentation standards and file ### Common Issues **Analytics Warning in Browser** -- Ensure `NEXT_PUBLIC_FIREBASE_API_KEY` and `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` are set +- Ensure `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` is set - Restart dev server after adding environment variables **AI Features Not Working** diff --git a/CHANGELOG.md b/CHANGELOG.md index f17bd605..28322e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to BitSleuth - AI Bitcoin Wallet Analyzer will be documented The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +#### Analytics +- Migrated analytics from the Firebase SDK to native Google Analytics (GA4) via + `@next/third-parties/google`. Analytics now requires only + `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID`; the other `NEXT_PUBLIC_FIREBASE_*` + variables (API key, app ID, auth domain, project ID, storage bucket, messaging + sender ID) are no longer needed and have been removed from config and docs. +- Removed the `firebase` npm dependency (`@genkit-ai/firebase` is unaffected). + ## [1.2.1] - 2026-06-12 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index f17138b4..7ab6544a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ npm run dev:clean # Clean .next cache and start dev | **Styling** | Tailwind CSS 4, shadcn/ui, Radix UI, Lucide Icons | | **AI** | Genkit 1.24+, OpenAI GPT-4.1 Mini | | **Bitcoin** | bitcoinjs-lib, bip32, secp256k1 | -| **Data APIs** | Blockstream, mempool.space, CoinGecko, Firebase (analytics only) | +| **Data APIs** | Blockstream, mempool.space, CoinGecko, Google Analytics (GA4) | | **Testing** | Vitest (unit), Playwright (E2E) | | **Build** | Turbopack | @@ -114,7 +114,7 @@ src/ │ └── wallet-context.tsx # Global wallet state │ ├── hooks/ -│ ├── use-analytics.ts # Firebase analytics +│ ├── use-analytics.ts # Google Analytics (GA4) event tracking │ ├── use-chunk-retry.ts # Chunked data fetching │ ├── use-mobile.tsx # Mobile detection │ └── use-toast.ts # Toast notifications @@ -125,7 +125,6 @@ src/ │ ├── market.ts # Market data service (CoinGecko) │ ├── mempool.ts # Mempool data service │ ├── newsService.ts # News API (CryptoCompare) -│ ├── firebase.ts # Firebase initialization │ ├── bitcoin-init.ts # Bitcoin library setup │ ├── cache-utils.ts # Client-side caching │ ├── chunk-retry-service.ts # Chunked retry logic @@ -288,13 +287,7 @@ Required variables in `.env`: # Site NEXT_PUBLIC_SITE_URL=http://localhost:3000 -# Firebase (required for analytics) -NEXT_PUBLIC_FIREBASE_API_KEY=... -NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=... -NEXT_PUBLIC_FIREBASE_PROJECT_ID=... -NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=... -NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=... -NEXT_PUBLIC_FIREBASE_APP_ID=... +# Google Analytics (GA4 measurement ID, required for analytics) NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=... # OpenAI (required for AI features) @@ -309,7 +302,7 @@ GOOGLE_SHEETS_PRIVATE_KEY=... ``` **Notes:** -- Firebase keys are public client config (safe for browser) +- The GA4 measurement ID is public client config (safe for browser) - Missing optional keys show non-blocking warnings - Copy `.env.example` as starting point @@ -410,7 +403,7 @@ The codebase implements several performance optimizations: | Issue | Solution | |-------|----------| | AI features not working | Ensure `npm run genkit:dev` is running and `OPENAI_API_KEY` is set | -| Analytics warning | Set Firebase environment variables | +| Analytics warning | Set `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` | | Build errors | Run `npm run dev:clean` or delete `node_modules` and reinstall | | Type errors | Run `npm run typecheck` for details | | News not loading | Set `CRYPTOCOMPARE_API_KEY` | diff --git a/README.md b/README.md index 0e94b750..a2dcfe24 100644 --- a/README.md +++ b/README.md @@ -79,19 +79,13 @@ For VS Code users, install the recommended "Tailwind CSS IntelliSense" extension ## Environment variables -Create a `.env` file in the project root. Only the public Firebase keys are required for analytics; other keys enable additional features. +Create a `.env` file in the project root. Only the GA4 measurement ID is required for analytics; other keys enable additional features. ```env # Site NEXT_PUBLIC_SITE_URL=http://localhost:3000 -# Firebase (analytics; all are public client config) -NEXT_PUBLIC_FIREBASE_API_KEY= -NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= -NEXT_PUBLIC_FIREBASE_PROJECT_ID= -NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= -NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= -NEXT_PUBLIC_FIREBASE_APP_ID= +# Google Analytics (GA4 measurement ID, e.g. G-XXXXXXXXXX) NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= # OpenAI / Genkit (required for AI features) @@ -111,7 +105,7 @@ GOOGLE_SHEETS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KE ``` Notes: -- Without `NEXT_PUBLIC_FIREBASE_API_KEY` and `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID`, analytics will be disabled and a non-blocking warning will appear in-app. +- Without `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID`, analytics will be disabled and a non-blocking warning will appear in-app. - `COINGECKO_API_KEY` is optional and, when present, is added to CoinGecko requests. - `CRYPTOCOMPARE_API_KEY` enables the News panel with live data; otherwise a helpful message is shown. - Google Sheets export is optional; if unset, feedback stays local and the app continues to work. @@ -157,7 +151,7 @@ Notes: ## Troubleshooting -- Missing analytics warning: set `NEXT_PUBLIC_FIREBASE_API_KEY` and `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` and restart dev server. +- Missing analytics warning: set `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` and restart dev server. - News panel shows configuration message: set `CRYPTOCOMPARE_API_KEY`. - Google Sheets export fails: verify sheet ID, service account email access, and private key formatting (preserve newlines). diff --git a/SECURITY.md b/SECURITY.md index 09efb8d8..cbb296b2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -53,7 +53,7 @@ Send an email to **security@bitsleuth.ai** with the following information: ### Server-Side API Key Isolation - Sensitive API keys (`OPENAI_API_KEY`, `GOOGLE_SHEETS_PRIVATE_KEY`, `COINGECKO_API_KEY`, `CRYPTOCOMPARE_API_KEY`) are server-side only, accessed via `process.env` (never prefixed with `NEXT_PUBLIC_`) -- Firebase configuration keys (`NEXT_PUBLIC_FIREBASE_*`) are public client config designed by Google to be safe for browser exposure — they only enable anonymous analytics collection +- The Google Analytics measurement ID (`NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID`) is public client config designed to be safe for browser exposure — it only enables anonymous analytics collection ### Security Headers @@ -119,7 +119,7 @@ All blockchain data is fetched from public, read-only APIs: ## Environment Variables **Public client configuration (safe for browser):** -- `NEXT_PUBLIC_FIREBASE_*` — Firebase Analytics client config +- `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID` — Google Analytics (GA4) measurement ID - `NEXT_PUBLIC_SITE_URL` — Application URL **Server-side secrets (must not be exposed to client):** diff --git a/apphosting.dev.yaml b/apphosting.dev.yaml index d746d892..67865cdb 100644 --- a/apphosting.dev.yaml +++ b/apphosting.dev.yaml @@ -8,24 +8,6 @@ runConfig: # Environment variables and secrets env: - - variable: NEXT_PUBLIC_FIREBASE_API_KEY - secret: NEXT_PUBLIC_FIREBASE_API_KEY - - - variable: NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN - secret: NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN - - - variable: NEXT_PUBLIC_FIREBASE_PROJECT_ID - secret: NEXT_PUBLIC_FIREBASE_PROJECT_ID - - - variable: NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET - secret: NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET - - - variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID - secret: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID - - - variable: NEXT_PUBLIC_FIREBASE_APP_ID - secret: NEXT_PUBLIC_FIREBASE_APP_ID_DEV - - variable: NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID secret: NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID_DEV diff --git a/apphosting.prd.yaml b/apphosting.prd.yaml index 7d0ebd58..c12a3fa9 100644 --- a/apphosting.prd.yaml +++ b/apphosting.prd.yaml @@ -8,24 +8,6 @@ runConfig: # Environment variables and secrets env: - - variable: NEXT_PUBLIC_FIREBASE_API_KEY - secret: NEXT_PUBLIC_FIREBASE_API_KEY - - - variable: NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN - secret: NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN - - - variable: NEXT_PUBLIC_FIREBASE_PROJECT_ID - secret: NEXT_PUBLIC_FIREBASE_PROJECT_ID - - - variable: NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET - secret: NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET - - - variable: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID - secret: NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID - - - variable: NEXT_PUBLIC_FIREBASE_APP_ID - secret: NEXT_PUBLIC_FIREBASE_APP_ID - - variable: NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID secret: NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID diff --git a/package-lock.json b/package-lock.json index 4c887761..67a3d07d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@genkit-ai/firebase": "^1.36.0", "@genkit-ai/next": "^1.36.0", "@hookform/resolvers": "^5.4.0", + "@next/third-parties": "^16.2.6", "@opentelemetry/winston-transport": "^0.28.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", @@ -46,7 +47,6 @@ "dotenv": "^17.4.2", "ecpair": "^3.0.1", "embla-carousel-react": "^8.6.0", - "firebase": "^12.13.0", "genkit": "^1.36.0", "google-auth-library": "^10.2.0", "googleapis": "^171.0.0", @@ -1030,630 +1030,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@firebase/ai": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.13.0.tgz", - "integrity": "sha512-nJJDQKqjAcbkZdZGT/5WTVLrGZ+pYhWbwKC90nNzmvtoRTtnOJaNS34fhKSHQeB9SALgD2kxuWT5I4AkytdZ/Q==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.4", - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/analytics": { - "version": "0.10.22", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.22.tgz", - "integrity": "sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/installations": "0.6.22", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/analytics-compat": { - "version": "0.2.28", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.28.tgz", - "integrity": "sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/analytics": "0.10.22", - "@firebase/analytics-types": "0.8.4", - "@firebase/component": "0.7.3", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/analytics-types": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.4.tgz", - "integrity": "sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app": { - "version": "0.14.13", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.13.tgz", - "integrity": "sha512-H89Jeyp31+EZk9GPu6vaeL9mEmoXgM3nASB7UPBYYS/lqAks21mO1BU1dF8NbsVTL6tgGZkGUtiGJgxtDiwHkw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/app-check": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.4.tgz", - "integrity": "sha512-G8EsbVJV9gSfoibx0dNoNOUrvr+PkL7J//+W/BST/oUassimkZeq9bjj3bKkB0pn4og5GMQ9qs7FefwP00kkgg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/app-check-compat": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.4.tgz", - "integrity": "sha512-9iP0MvmaVagulNXmrca96U3tqNAI3j98wsC1z7rj62nnOTajlrHM//jjB9VoHqRw6/islMskp6RsKnM7vhLDqA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check": "0.11.4", - "@firebase/app-check-types": "0.5.4", - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.4.tgz", - "integrity": "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-check-types": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.4.tgz", - "integrity": "sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-compat": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.13.tgz", - "integrity": "sha512-pn3FvXwUR34kWPccDQfCKsNZcM2wD1OS+J1jeEgzM1ZNXoxR2NaF6e5DjDuRrnTwR6LN2XQQt0IqE6yKmgpCQg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app": "0.14.13", - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.5.tgz", - "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/logger": "0.5.1" - } - }, - "node_modules/@firebase/auth": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.2.tgz", - "integrity": "sha512-B4w0iS7MxRg28oIh2fJFTE6cM0lYdBrW19eHpc42jqEcloUjlYyVrpPqZvqA4+v9KFEVSKEs2SfWyta7hbzkJQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, - "node_modules/@firebase/auth-compat": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.7.tgz", - "integrity": "sha512-XgKnOgY1Siq7gylAmLkYtHAlRxNeWEAspH+nO3gJZJnfHqoTHbr9UjJ3nHNFALYXV5CfpQlyPROyB2ztySBHBQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth": "1.13.2", - "@firebase/auth-types": "0.13.1", - "@firebase/component": "0.7.3", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.5.tgz", - "integrity": "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-types": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.1.tgz", - "integrity": "sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/component": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.3.tgz", - "integrity": "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/data-connect": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.7.1.tgz", - "integrity": "sha512-2LbUU8mmSA63HknxQMmWHjpzuNLBKflvVwQc2tpoVKg0biWleNEJX031ELks0vzFs+dDjOUkCJR72RP6mQHFOg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth-interop-types": "0.2.5", - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/database": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.3.tgz", - "integrity": "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.4", - "@firebase/auth-interop-types": "0.2.5", - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.4.tgz", - "integrity": "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/database": "1.1.3", - "@firebase/database-types": "1.0.20", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.20.tgz", - "integrity": "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-types": "0.9.5", - "@firebase/util": "1.15.1" - } - }, - "node_modules/@firebase/firestore": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.15.0.tgz", - "integrity": "sha512-Fj9osqYkz2Rqr7kW3/A8BRd8CyJ7yA5K8YjhihRdyJWbL+FsELVcR6DpoCplrp1IyU+xeGgTubo1UOySXpY+EA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "@firebase/webchannel-wrapper": "1.0.6", - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.8", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/firestore-compat": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.10.tgz", - "integrity": "sha512-yMP3FADDjikdrQv4YmvL4EkIny6Hw+N+a2O5T40rlHiniyMpRPxgYkKiFOvMZnsqKLqBVnKqCAElC0pa/IZtdw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/firestore": "4.15.0", - "@firebase/firestore-types": "3.0.4", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/firestore-types": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.4.tgz", - "integrity": "sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/firestore/node_modules/@grpc/grpc-js": { - "version": "1.9.16", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz", - "integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@firebase/functions": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.5.tgz", - "integrity": "sha512-bWCx713f4kE/uFV7gdFOLBS7lDoiZj48MRkbAqe35gkXcCeWF4QjRNO07Jhmve7EJIoQOBczL29y2r8VRuN1kw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.4", - "@firebase/auth-interop-types": "0.2.5", - "@firebase/component": "0.7.3", - "@firebase/messaging-interop-types": "0.2.5", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/functions-compat": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.5.tgz", - "integrity": "sha512-10qlUXGY25G5/1g9UihqksPp2po+ZqSE7LEizsrdUP7vrTmkysXxGSZCDyojSEp6mQe/ecRDdDDI+z4XRdb4wQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/functions": "0.13.5", - "@firebase/functions-types": "0.6.4", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/functions-types": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.4.tgz", - "integrity": "sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/installations": { - "version": "0.6.22", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.22.tgz", - "integrity": "sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/util": "1.15.1", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/installations-compat": { - "version": "0.2.22", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.22.tgz", - "integrity": "sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/installations": "0.6.22", - "@firebase/installations-types": "0.5.4", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/installations-types": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.4.tgz", - "integrity": "sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/logger": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.1.tgz", - "integrity": "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/messaging": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.13.0.tgz", - "integrity": "sha512-GZoo0uGRvEbszo83xcgbjJp4FpkmBEr4l8Z4hi8gl+P1Spn/MTK3HapanMzSX4yUHuTEiF5hasWRxOaz+o5sxQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/installations": "0.6.22", - "@firebase/messaging-interop-types": "0.2.5", - "@firebase/util": "1.15.1", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/messaging-compat": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.27.tgz", - "integrity": "sha512-JNOiu1PPgdHzEPEtoFiNxQuu0x9bm4bfETSQCpGfcTlgWkhlSK7uh7nlsjC10TQLUNgYetLmuutaYTh8aeYLVA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/messaging": "0.13.0", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/messaging-interop-types": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.5.tgz", - "integrity": "sha512-tUEKnaAP2Y/MNIqgnriPpV6e5l13Vs/+p2yrd6NGlncPJT9O3a8muYZtdnWe+IJ4fgKLHJVC79n/asxk/N5Msw==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/performance": { - "version": "0.7.12", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.12.tgz", - "integrity": "sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/installations": "0.6.22", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0", - "web-vitals": "^4.2.4" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/performance-compat": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.25.tgz", - "integrity": "sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/performance": "0.7.12", - "@firebase/performance-types": "0.2.4", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/performance-types": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.4.tgz", - "integrity": "sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/remote-config": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.4.tgz", - "integrity": "sha512-lslywR5lGvHWTu4z/MPoYs3UwS3CKdeY+ELXY87087VsOpBpkD+9Orra23tA9GW683arPTDOM3CM6eKmtiOO3g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/installations": "0.6.22", - "@firebase/logger": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/remote-config-compat": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.25.tgz", - "integrity": "sha512-FnA5S4IxFJAAFrCnYzWlO0FCaizlYdqhe42ygFMA+wE/mUP+w36iXzHyKj1OO1A+2gyMFjeRHyg8HhkJ6c5vRA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/logger": "0.5.1", - "@firebase/remote-config": "0.8.4", - "@firebase/remote-config-types": "0.5.1", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/remote-config-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.1.tgz", - "integrity": "sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/storage": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.3.tgz", - "integrity": "sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/storage-compat": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.3.tgz", - "integrity": "sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.7.3", - "@firebase/storage": "0.14.3", - "@firebase/storage-types": "0.8.4", - "@firebase/util": "1.15.1", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/storage-types": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.4.tgz", - "integrity": "sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/util": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.1.tgz", - "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@firebase/webchannel-wrapper": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.6.tgz", - "integrity": "sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q==", - "license": "Apache-2.0" - }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -3879,6 +3255,19 @@ "node": ">= 10" } }, + "node_modules/@next/third-parties": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/third-parties/-/third-parties-16.2.9.tgz", + "integrity": "sha512-JZPGQRN8eQs2WMfBXOf6Zjrma+JIN0KyA24MvmIa3bUI4BwTaJ1UlxmYVc5ri6l30LQ8fPv6Kmx20447hCltrg==", + "license": "MIT", + "dependencies": { + "third-party-capital": "1.0.20" + }, + "peerDependencies": { + "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0" + } + }, "node_modules/@noble/ciphers": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.1.1.tgz", @@ -11940,18 +11329,6 @@ "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -12091,42 +11468,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/firebase": { - "version": "12.14.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.14.0.tgz", - "integrity": "sha512-aEZ/lniDR1hOCYpx/x/V8Nrrqq9pepKDNkqP/4WGZFC69gTv6F59Z4/54W/SUP4L/hFlrRNmWj35aweQq+IHow==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/ai": "2.13.0", - "@firebase/analytics": "0.10.22", - "@firebase/analytics-compat": "0.2.28", - "@firebase/app": "0.14.13", - "@firebase/app-check": "0.11.4", - "@firebase/app-check-compat": "0.4.4", - "@firebase/app-compat": "0.5.13", - "@firebase/app-types": "0.9.5", - "@firebase/auth": "1.13.2", - "@firebase/auth-compat": "0.6.7", - "@firebase/data-connect": "0.7.1", - "@firebase/database": "1.1.3", - "@firebase/database-compat": "2.1.4", - "@firebase/firestore": "4.15.0", - "@firebase/firestore-compat": "0.4.10", - "@firebase/functions": "0.13.5", - "@firebase/functions-compat": "0.4.5", - "@firebase/installations": "0.6.22", - "@firebase/installations-compat": "0.2.22", - "@firebase/messaging": "0.13.0", - "@firebase/messaging-compat": "0.2.27", - "@firebase/performance": "0.7.12", - "@firebase/performance-compat": "0.2.25", - "@firebase/remote-config": "0.8.4", - "@firebase/remote-config-compat": "0.2.25", - "@firebase/storage": "0.14.3", - "@firebase/storage-compat": "0.4.3", - "@firebase/util": "1.15.1" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -13111,12 +12452,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", @@ -13177,12 +12512,6 @@ "node": ">=0.10.0" } }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -18223,6 +17552,12 @@ "utrie": "^1.0.2" } }, + "node_modules/third-party-capital": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/third-party-capital/-/third-party-capital-1.0.20.tgz", + "integrity": "sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==", + "license": "ISC" + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -19204,41 +18539,12 @@ "node": ">= 8" } }, - "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==", - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index d56def81..11ffc15b 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@genkit-ai/firebase": "^1.36.0", "@genkit-ai/next": "^1.36.0", "@hookform/resolvers": "^5.4.0", + "@next/third-parties": "^16.2.6", "@opentelemetry/winston-transport": "^0.28.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", @@ -70,7 +71,6 @@ "dotenv": "^17.4.2", "ecpair": "^3.0.1", "embla-carousel-react": "^8.6.0", - "firebase": "^12.13.0", "genkit": "^1.36.0", "google-auth-library": "^10.2.0", "googleapis": "^171.0.0", diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 6f10d356..f28e268c 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -62,7 +62,7 @@ function AnalyticsWarning() { // We check on mount to avoid hydration errors React.useEffect(() => { - setIsConfigured(!!process.env.NEXT_PUBLIC_FIREBASE_API_KEY && !!process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID); + setIsConfigured(!!process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID); }, []); if (isConfigured) { @@ -74,7 +74,7 @@ function AnalyticsWarning() { Analytics Not Configured - Your Firebase environment variables are missing. To enable analytics, add NEXT_PUBLIC_FIREBASE_API_KEY and NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID to the .env file, then restart the development server. + Your Google Analytics measurement ID is missing. To enable analytics, add NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID to the .env file, then restart the development server. ) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9e7ca1b4..37435377 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Suspense } from 'react'; import './globals.css'; import { Toaster } from '@/components/ui/toaster'; import { AnalyticsTracker } from '@/components/analytics-tracker'; +import { GoogleAnalytics } from '@next/third-parties/google'; import { WalletProviderWrapper } from '@/components/wallet-provider-wrapper'; import { ThemeProvider } from '@/components/theme-provider'; import { ErrorBoundary } from '@/components/error-boundary'; @@ -93,6 +94,7 @@ export default function RootLayout({ }>) { const isTestMode = process.env.NODE_ENV === 'test'; const testXpub = isTestMode ? process.env.TEST_XPUB : undefined; + const gaId = process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID; return ( @@ -168,6 +170,7 @@ export default function RootLayout({ + {gaId && } ); diff --git a/src/components/analytics-tracker.tsx b/src/components/analytics-tracker.tsx index 14dca554..1c19e62e 100644 --- a/src/components/analytics-tracker.tsx +++ b/src/components/analytics-tracker.tsx @@ -1,45 +1,40 @@ 'use client'; -import { useEffect } from 'react'; +import { useEffect, useRef } from 'react'; import { usePathname, useSearchParams } from 'next/navigation'; -import { getFirebaseAnalytics } from '@/lib/firebase'; -import { logEvent } from 'firebase/analytics'; +import { sendGAEvent } from '@next/third-parties/google'; /** - * A component that tracks page views for Firebase Analytics. - * It uses Next.js navigation events to log a 'page_view' whenever the route changes. + * A component that tracks page views for Google Analytics (GA4) on client-side + * route changes. The initial page view is emitted automatically by the + * component on load, so we skip the first render here to + * avoid double-counting. */ export function AnalyticsTracker() { const pathname = usePathname(); const searchParams = useSearchParams(); + const isInitialRender = useRef(true); - // Track Page Views for Analytics useEffect(() => { - const logPageView = async () => { - const analytics = await getFirebaseAnalytics(); - - if (analytics) { - try { - const search = searchParams.toString(); - const url = `${pathname}${search ? `?${search}` : ''}`; - - const eventParams = { - page_location: window.location.href, - page_path: url, - page_title: document.title, - }; - - logEvent(analytics, 'page_view', eventParams); - - } catch (error) { - console.error("ANALYTICS_TRACKER: Error logging page_view event:", error); - } - } - }; - - logPageView(); - + // The component already sends the initial page_view. + if (isInitialRender.current) { + isInitialRender.current = false; + return; + } + + try { + const search = searchParams.toString(); + const url = `${pathname}${search ? `?${search}` : ''}`; + + sendGAEvent('event', 'page_view', { + page_location: window.location.href, + page_path: url, + page_title: document.title, + }); + } catch (error) { + console.error("ANALYTICS_TRACKER: Error logging page_view event:", error); + } }, [pathname, searchParams]); // This component does not render anything to the DOM. diff --git a/src/hooks/use-analytics.ts b/src/hooks/use-analytics.ts index 27ba5955..e18b4667 100644 --- a/src/hooks/use-analytics.ts +++ b/src/hooks/use-analytics.ts @@ -2,29 +2,24 @@ 'use client'; import { useCallback } from 'react'; -import { getFirebaseAnalytics } from '@/lib/firebase'; -import { logEvent } from 'firebase/analytics'; +import { sendGAEvent } from '@next/third-parties/google'; /** * A custom hook to provide a function for tracking custom analytics events. - * It gracefully handles cases where Firebase Analytics is not initialized or available. - * + * It sends events to Google Analytics (GA4) via gtag. If GA is not configured + * (no measurement ID), gtag is not loaded and these calls are no-ops. + * * @example * const { track } = useAnalytics(); * track('button_click', { button_name: 'primary_cta' }); */ export function useAnalytics() { - const track = useCallback(async (eventName: string, eventParams?: { [key: string]: any }) => { - // getFirebaseAnalytics is a singleton, so it's safe to call it multiple times. - const analytics = await getFirebaseAnalytics(); - - if (analytics) { - try { - logEvent(analytics, eventName, eventParams); - } catch (error) { - // Log an error to the console for developers, but don't crash the app. - console.error(`ANALYTICS: Error logging event '${eventName}':`, error); - } + const track = useCallback((eventName: string, eventParams?: { [key: string]: any }) => { + try { + sendGAEvent('event', eventName, eventParams ?? {}); + } catch (error) { + // Log an error to the console for developers, but don't crash the app. + console.error(`ANALYTICS: Error logging event '${eventName}':`, error); } }, []); diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts deleted file mode 100644 index 1b52a844..00000000 --- a/src/lib/firebase.ts +++ /dev/null @@ -1,79 +0,0 @@ - - -'use client'; - -import { initializeApp, getApp, getApps, type FirebaseApp, type FirebaseOptions } from "firebase/app"; -import type { Analytics } from "firebase/analytics"; - -const firebaseConfig: FirebaseOptions = { - apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, - authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, - projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, - storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, - measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, -}; - -// Caching initialization promises to ensure they only run once. -let appPromise: Promise | null = null; -let analyticsPromise: Promise | null = null; - -const checkFirebaseConfig = (): boolean => { - if (!firebaseConfig.measurementId || !firebaseConfig.apiKey) { - console.warn("FIREBASE: Analytics/Performance configuration is missing. Ensure NEXT_PUBLIC_FIREBASE_API_KEY and NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID are in .env and the server is RESTARTED."); - return false; - } - return true; -} - -/** - * Initializes and gets the Firebase App instance. This is a prerequisite for other services. - * @returns A promise that resolves to the FirebaseApp instance or null if not configured. - */ -const getFirebaseApp = (): Promise => { - if (appPromise) { - return appPromise; - } - appPromise = (async () => { - if (typeof window === 'undefined' || !checkFirebaseConfig()) { - return null; - } - return getApps().length > 0 ? getApp() : initializeApp(firebaseConfig); - })(); - return appPromise; -} - - -/** - * Gets the Firebase Analytics instance, but only if it's supported and configured. - * This function is safe to call on both server and client. It will only initialize on the client. - * @returns A promise that resolves to the Analytics instance or null. - */ -export const getFirebaseAnalytics = (): Promise => { - if (analyticsPromise) { - return analyticsPromise; - } - - analyticsPromise = (async () => { - const app = await getFirebaseApp(); - if (!app) { - return null; - } - - try { - const { getAnalytics, isSupported } = await import("firebase/analytics"); - const supported = await isSupported(); - if (!supported) { - console.warn("FIREBASE: Analytics is not supported in this browser."); - return null; - } - return getAnalytics(app); - } catch (e) { - console.error("FIREBASE: Failed to initialize Firebase Analytics:", e); - return null; - } - })(); - - return analyticsPromise; -} From 3fdea1cb0c1073e2bbeaa56bcf31ac0b4f8c5804 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 16:08:55 +0000 Subject: [PATCH 2/3] Standardize on OPENAI_API_KEY, remove OPENAI_CHATGPT_API_KEY alias The OpenAI credential was referenced via two env vars: OPENAI_API_KEY and the OPENAI_CHATGPT_API_KEY backward-compat fallback. The latter only existed as an alias and, in both apphosting configs, mapped to the same OPENAI_API_KEY secret. Consolidate on OPENAI_API_KEY (the documented standard and SDK convention) so only one variable is referenced. - src/ai/genkit.ts: drop the fallback, update warning and comment - src/ai/flows/wallet-insights-chat.ts: drop the alias from error detection and the user-facing config message - apphosting.dev.yaml / apphosting.prd.yaml: remove the alias env mapping Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014fbd2ZstdjJNhn3UQGjU5i --- apphosting.dev.yaml | 4 ---- apphosting.prd.yaml | 4 ---- src/ai/flows/wallet-insights-chat.ts | 4 ++-- src/ai/genkit.ts | 6 +++--- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/apphosting.dev.yaml b/apphosting.dev.yaml index 67865cdb..9a294522 100644 --- a/apphosting.dev.yaml +++ b/apphosting.dev.yaml @@ -28,7 +28,3 @@ env: - variable: OPENAI_API_KEY secret: OPENAI_API_KEY - - # Backward compatibility for deployments using the ChatGPT-specific key name - - variable: OPENAI_CHATGPT_API_KEY - secret: OPENAI_API_KEY diff --git a/apphosting.prd.yaml b/apphosting.prd.yaml index c12a3fa9..d412ffa5 100644 --- a/apphosting.prd.yaml +++ b/apphosting.prd.yaml @@ -28,7 +28,3 @@ env: - variable: OPENAI_API_KEY secret: OPENAI_API_KEY - - # Backward compatibility for deployments using the ChatGPT-specific key name - - variable: OPENAI_CHATGPT_API_KEY - secret: OPENAI_API_KEY diff --git a/src/ai/flows/wallet-insights-chat.ts b/src/ai/flows/wallet-insights-chat.ts index efce1270..33307c59 100644 --- a/src/ai/flows/wallet-insights-chat.ts +++ b/src/ai/flows/wallet-insights-chat.ts @@ -2311,7 +2311,7 @@ Return only a JSON object with an "answer" string and optional "followUpSuggesti } catch (e: any) { console.error("Error in walletInsightsChatFlow:", e); const errorMessage = extractErrorMessage(e) || 'Unknown error'; - const isApiKeyError = errorMessage.includes('API key') || errorMessage.includes('OPENAI_API_KEY') || errorMessage.includes('OPENAI_CHATGPT_API_KEY') || errorMessage.includes('401') || errorMessage.includes('403'); + const isApiKeyError = errorMessage.includes('API key') || errorMessage.includes('OPENAI_API_KEY') || errorMessage.includes('401') || errorMessage.includes('403'); const schemaError = isSchemaValidationError(e); const formattingError = isFormattingError(e); @@ -2330,7 +2330,7 @@ Return only a JSON object with an "answer" string and optional "followUpSuggesti return { answer: isApiKeyError - ? "⚠️ **AI Service Configuration Issue**\n\nThe AI chat feature requires a valid OpenAI API key to be configured. Please contact the administrator to set up the OPENAI_API_KEY environment variable (or OPENAI_CHATGPT_API_KEY for backward compatibility).\n\nYou can still use other features of BitSleuth, such as transaction viewing, analysis charts, and security recommendations." + ? "⚠️ **AI Service Configuration Issue**\n\nThe AI chat feature requires a valid OpenAI API key to be configured. Please contact the administrator to set up the OPENAI_API_KEY environment variable.\n\nYou can still use other features of BitSleuth, such as transaction viewing, analysis charts, and security recommendations." : schemaError || formattingError ? "I had trouble formatting that last response, but nothing is wrong with your question. Please try again and I'll keep the answer concise." : `I'm sorry, I encountered an error while processing your request: ${errorMessage}\n\nPlease try again or rephrase your question.`, diff --git a/src/ai/genkit.ts b/src/ai/genkit.ts index 89570e44..c6dae2d5 100644 --- a/src/ai/genkit.ts +++ b/src/ai/genkit.ts @@ -14,14 +14,14 @@ if (process.env.NODE_ENV === 'production') { // Check for API key and fail fast if missing to avoid placeholder configuration // Check for API key and warn if missing (don't throw to avoid build/import failures) -const apiKey = process.env.OPENAI_API_KEY || process.env.OPENAI_CHATGPT_API_KEY; +const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { console.warn( - 'OPENAI_API_KEY (or OPENAI_CHATGPT_API_KEY) is missing. AI features will not work.' + 'OPENAI_API_KEY is missing. AI features will not work.' ); } -// OpenAI configuration - uses OPENAI_API_KEY (with OPENAI_CHATGPT_API_KEY as a fallback) from environment +// OpenAI configuration - uses OPENAI_API_KEY from environment export const ai = genkit({ plugins: [ openAI({ From 5ce4b15e0e7a8973ac250721f694f675780a3cc1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 16:13:23 +0000 Subject: [PATCH 3/3] Remove stale "fail fast" comment in genkit API key check The comment claimed the code fails fast on a missing key, but it warns and continues. Drop the misleading line and keep the accurate one. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_014fbd2ZstdjJNhn3UQGjU5i --- src/ai/genkit.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ai/genkit.ts b/src/ai/genkit.ts index c6dae2d5..35d713eb 100644 --- a/src/ai/genkit.ts +++ b/src/ai/genkit.ts @@ -12,7 +12,6 @@ if (process.env.NODE_ENV === 'production') { } } -// Check for API key and fail fast if missing to avoid placeholder configuration // Check for API key and warn if missing (don't throw to avoid build/import failures) const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) {