From 2030ae447d53733e9ebfdf45234e19542e0d3216 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Sat, 24 Jan 2026 13:21:20 +0100 Subject: [PATCH] refactor: remove Daily.co, fix IndexedDB sync for stale cache Daily.co Cleanup: - Remove @daily-co/daily-js and @daily-co/daily-react packages - Remove DailyProvider wrapper from App.tsx - Remove ~380 lines of Daily API endpoints from worker.ts - Remove DAILY_DOMAIN from wrangler configs - Remove Daily env vars from .env.example - Video chat now uses self-hosted Jitsi (meet.jeffemmett.com) Sync Logic Fix: - Fix stale IndexedDB cache preventing server data from loading - Changed threshold from "10x more shapes" to "more shapes" - Server data now properly updates local cache on initial load - Keeps local-only records for offline work Co-Authored-By: Claude Opus 4.5 --- .env.example | 4 +- package-lock.json | 177 ------------ package.json | 2 - src/App.tsx | 30 +- src/automerge/useAutomergeSyncRepo.ts | 63 ++--- worker/worker.ts | 382 -------------------------- wrangler.dev.toml | 4 +- wrangler.toml | 4 +- 8 files changed, 34 insertions(+), 632 deletions(-) diff --git a/.env.example b/.env.example index 1629262..370f44e 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # Frontend (VITE) Public Variables VITE_GOOGLE_CLIENT_ID='your_google_client_id' VITE_GOOGLE_MAPS_API_KEY='your_google_maps_api_key' -VITE_DAILY_DOMAIN='your_daily_domain' VITE_TLDRAW_WORKER_URL='your_worker_url' # AI Configuration @@ -25,5 +24,4 @@ CLOUDFLARE_API_TOKEN='your_cloudflare_token' CLOUDFLARE_ACCOUNT_ID='your_account_id' CLOUDFLARE_ZONE_ID='your_zone_id' R2_BUCKET_NAME='your_bucket_name' -R2_PREVIEW_BUCKET_NAME='your_preview_bucket_name' -DAILY_API_KEY=your_daily_api_key_here \ No newline at end of file +R2_PREVIEW_BUCKET_NAME='your_preview_bucket_name' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c88d651..0cb351d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,6 @@ "@automerge/automerge-repo-react-hooks": "^2.2.0", "@automerge/automerge-repo-storage-indexeddb": "^2.5.0", "@chengsokdara/use-whisper": "^0.2.0", - "@daily-co/daily-js": "^0.60.0", - "@daily-co/daily-react": "^0.20.0", "@fal-ai/client": "^1.7.2", "@mdxeditor/editor": "^3.51.0", "@noble/hashes": "^2.0.1", @@ -3411,40 +3409,6 @@ "node": ">=18" } }, - "node_modules/@daily-co/daily-js": { - "version": "0.60.0", - "resolved": "https://registry.npmjs.org/@daily-co/daily-js/-/daily-js-0.60.0.tgz", - "integrity": "sha512-4GkmOKbxZfen4DI6N1HVqj5CiWrg7r8xALgcbwb5V+Ij1h7LHODDDd78XqhzEBBQp4yNjg6U2wz+l/cVznqc4A==", - "license": "BSD-2-Clause", - "dependencies": { - "@babel/runtime": "^7.12.5", - "@sentry/browser": "^7.60.1", - "bowser": "^2.8.1", - "dequal": "^2.0.3", - "events": "^3.1.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@daily-co/daily-react": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@daily-co/daily-react/-/daily-react-0.20.0.tgz", - "integrity": "sha512-Agcp5+nvMtZfej2jzPyl8ExmXG1Kk4ULk6BHz24RvNHYVsri8eJ3NSWZgJBmqPtCuEjLjM3wdo7/e1Aew0sfoA==", - "license": "BSD-2-Clause", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash.throttle": "^4.1.1" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@daily-co/daily-js": ">=0.68.0 <1", - "react": ">=16.13.1", - "recoil": "^0.7.0" - } - }, "node_modules/@dimforge/rapier3d-compat": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", @@ -8025,132 +7989,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@sentry-internal/feedback": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.120.4.tgz", - "integrity": "sha512-eSwgvTdrh03zYYaI6UVOjI9p4VmKg6+c2+CBQfRZX++6wwnCVsNv7XF7WUIpVGBAkJ0N2oapjQmCzJKGKBRWQg==", - "license": "MIT", - "dependencies": { - "@sentry/core": "7.120.4", - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry-internal/replay-canvas": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.120.4.tgz", - "integrity": "sha512-2+W4CgUL1VzrPjArbTid4WhKh7HH21vREVilZdvffQPVwOEpgNTPAb69loQuTlhJVveh9hWTj2nE5UXLbLP+AA==", - "license": "MIT", - "dependencies": { - "@sentry/core": "7.120.4", - "@sentry/replay": "7.120.4", - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry-internal/tracing": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.120.4.tgz", - "integrity": "sha512-Fz5+4XCg3akeoFK+K7g+d7HqGMjmnLoY2eJlpONJmaeT9pXY7yfUyXKZMmMajdE2LxxKJgQ2YKvSCaGVamTjHw==", - "license": "MIT", - "dependencies": { - "@sentry/core": "7.120.4", - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/browser": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.120.4.tgz", - "integrity": "sha512-ymlNtIPG6HAKzM/JXpWVGCzCNufZNADfy+O/olZuVJW5Be1DtOFyRnBvz0LeKbmxJbXb2lX/XMhuen6PXPdoQw==", - "license": "MIT", - "dependencies": { - "@sentry-internal/feedback": "7.120.4", - "@sentry-internal/replay-canvas": "7.120.4", - "@sentry-internal/tracing": "7.120.4", - "@sentry/core": "7.120.4", - "@sentry/integrations": "7.120.4", - "@sentry/replay": "7.120.4", - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/core": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.120.4.tgz", - "integrity": "sha512-TXu3Q5kKiq8db9OXGkWyXUbIxMMuttB5vJ031yolOl5T/B69JRyAoKuojLBjRv1XX583gS1rSSoX8YXX7ATFGA==", - "license": "MIT", - "dependencies": { - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/integrations": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.120.4.tgz", - "integrity": "sha512-kkBTLk053XlhDCg7OkBQTIMF4puqFibeRO3E3YiVc4PGLnocXMaVpOSCkMqAc1k1kZ09UgGi8DxfQhnFEjUkpA==", - "license": "MIT", - "dependencies": { - "@sentry/core": "7.120.4", - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4", - "localforage": "^1.8.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/replay": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.120.4.tgz", - "integrity": "sha512-FW8sPenNFfnO/K7sncsSTX4rIVak9j7VUiLIagJrcqZIC7d1dInFNjy8CdVJUlyz3Y3TOgIl3L3+ZpjfyMnaZg==", - "license": "MIT", - "dependencies": { - "@sentry-internal/tracing": "7.120.4", - "@sentry/core": "7.120.4", - "@sentry/types": "7.120.4", - "@sentry/utils": "7.120.4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry/types": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.120.4.tgz", - "integrity": "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils": { - "version": "7.120.4", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.120.4.tgz", - "integrity": "sha512-zCKpyDIWKHwtervNK2ZlaK8mMV7gVUijAgFeJStH+CU/imcdquizV3pFLlSQYRswG+Lbyd6CT/LGRh3IbtkCFw==", - "license": "MIT", - "dependencies": { - "@sentry/types": "7.120.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@sindresorhus/is": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz", @@ -11917,12 +11755,6 @@ "base-x": "^3.0.2" } }, - "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -17241,15 +17073,6 @@ "@types/trusted-types": "^2.0.2" } }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "license": "Apache-2.0", - "dependencies": { - "lie": "3.1.1" - } - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", diff --git a/package.json b/package.json index 7854702..ac2ed7c 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,6 @@ "@automerge/automerge-repo-react-hooks": "^2.2.0", "@automerge/automerge-repo-storage-indexeddb": "^2.5.0", "@chengsokdara/use-whisper": "^0.2.0", - "@daily-co/daily-js": "^0.60.0", - "@daily-co/daily-react": "^0.20.0", "@fal-ai/client": "^1.7.2", "@mdxeditor/editor": "^3.51.0", "@noble/hashes": "^2.0.1", diff --git a/src/App.tsx b/src/App.tsx index 2622786..2a60a0e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -34,11 +34,6 @@ import { Web3Provider } from './providers/Web3Provider'; // Import Google Data test component import { GoogleDataTest } from './components/GoogleDataTest'; -// Lazy load Daily.co provider - only needed for video chat -const DailyProvider = lazy(() => - import('@daily-co/daily-react').then(m => ({ default: m.DailyProvider })) -); - // Loading skeleton for lazy-loaded routes const LoadingSpinner = () => (
(
); -// Daily.co call object - initialized lazily when needed -let dailyCallObject: any = null; -const getDailyCallObject = async () => { - if (dailyCallObject) return dailyCallObject; - - try { - // Only create call object if we're in a secure context and mediaDevices is available - if (typeof window !== 'undefined' && - window.location.protocol === 'https:' && - navigator.mediaDevices) { - const Daily = (await import('@daily-co/daily-js')).default; - dailyCallObject = Daily.createCallObject(); - } - } catch (error) { - console.warn('Daily.co call object initialization failed:', error); - } - return dailyCallObject; -}; - /** * Optional Auth Route component * Allows guests to browse, but provides login option @@ -151,8 +127,7 @@ const AppWithProviders = () => { }> - - + {/* Display notifications */} @@ -227,8 +202,7 @@ const AppWithProviders = () => { } /> - - + diff --git a/src/automerge/useAutomergeSyncRepo.ts b/src/automerge/useAutomergeSyncRepo.ts index c123aa3..e18c67a 100644 --- a/src/automerge/useAutomergeSyncRepo.ts +++ b/src/automerge/useAutomergeSyncRepo.ts @@ -481,11 +481,11 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus const localRecordCount = localDoc?.store ? Object.keys(localDoc.store).length : 0 // Merge server data with local data - // Strategy: - // 1. If local has NO SHAPES (only ephemeral records), use server data - // 2. If server has SIGNIFICANTLY MORE shapes (10x), prefer server (stale local cache) - // 3. Otherwise, only add server records that don't exist locally - // (preserve offline changes, let Automerge CRDT sync handle conflicts) + // Strategy (IMPROVED): + // 1. Server is the source of truth for initial page load + // 2. Always update local with server data for shape records + // 3. Keep local-only records (potential offline additions not yet synced) + // 4. This ensures stale IndexedDB cache doesn't override server data if (serverDoc.store && serverRecordCount > 0) { // Track if we merged any data (needed outside the change callback) let totalMerged = 0 @@ -500,46 +500,39 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus const localShapeCount = Object.values(doc.store).filter((r: any) => r?.typeName === 'shape').length const localIsEmpty = Object.keys(doc.store).length === 0 - // Server has significantly more shapes - local is likely stale cache - // Use 10x threshold or server has shapes but local has none - const serverHasSignificantlyMore = ( - localShapeCount === 0 && serverShapeCount > 0 - ) || ( - serverShapeCount > 0 && localShapeCount > 0 && serverShapeCount >= localShapeCount * 10 - ) - - // If local has no shapes but server does, or server has 10x more, - // replace local with server data (but keep local ephemeral records) - const shouldPreferServer = localIsEmpty || localShapeCount === 0 || serverHasSignificantlyMore + // IMPROVED: Server is source of truth on initial load + // Prefer server if: + // - Local is empty (first load or cleared cache) + // - Server has more shapes (local is likely stale/incomplete) + // - Local has shapes but server has different/more content + const serverHasMoreContent = serverShapeCount > localShapeCount + const shouldPreferServer = localIsEmpty || localShapeCount === 0 || serverHasMoreContent let addedFromServer = 0 - let skippedExisting = 0 - let replacedFromServer = 0 + let updatedFromServer = 0 + let keptLocal = 0 Object.entries(serverDoc.store).forEach(([id, record]) => { - if (shouldPreferServer) { - // Prefer server data - bootstrap or replace stale local - if (doc.store[id]) { - replacedFromServer++ - } else { - addedFromServer++ - } - doc.store[id] = record - } else if (!doc.store[id]) { - // Local has data but missing this record - add from server - // This handles: shapes created on another device and synced to R2 + const existsLocally = !!doc.store[id] + + if (!existsLocally) { + // Record doesn't exist locally - add from server doc.store[id] = record addedFromServer++ + } else if (shouldPreferServer) { + // Record exists locally but server has more content - update with server version + // This handles stale IndexedDB cache scenarios + doc.store[id] = record + updatedFromServer++ } else { - // Record exists locally - preserve local version - // The Automerge binary sync will handle merging conflicts via CRDT - // This preserves offline edits to existing shapes - skippedExisting++ + // Local has equal or more content - keep local version + // Local changes will sync to server via normal CRDT mechanism + keptLocal++ } }) - totalMerged = addedFromServer + replacedFromServer - console.log(`🔄 Server sync: added=${addedFromServer}, replaced=${replacedFromServer}, skipped=${skippedExisting}, shouldPreferServer=${shouldPreferServer}`) + totalMerged = addedFromServer + updatedFromServer + console.log(`🔄 Server sync: added=${addedFromServer}, updated=${updatedFromServer}, keptLocal=${keptLocal}, serverShapes=${serverShapeCount}, localShapes=${localShapeCount}, preferServer=${shouldPreferServer}`) }) const finalDoc = handle.doc() diff --git a/worker/worker.ts b/worker/worker.ts index 436b9e0..e5e801d 100644 --- a/worker/worker.ts +++ b/worker/worker.ts @@ -322,388 +322,6 @@ const router = AutoRouter({ }) }) - .post("/daily/rooms", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - // Get the request body from the client - const body = await req.json() - - const response = await fetch('https://api.daily.co/v1/rooms', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - }, - body: JSON.stringify(body) - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - // Get room info by name - .get("/daily/rooms/:roomName", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - const { roomName } = req.params - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const response = await fetch(`https://api.daily.co/v1/rooms/${roomName}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json' - } - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - .post("/daily/tokens", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const body = await req.json() as { room_name: string; properties: any }; - const response = await fetch('https://api.daily.co/v1/meeting-tokens', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - }, - body: JSON.stringify({ - room_name: body.room_name, - properties: body.properties - }) - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - // Add new transcription endpoints - .post("/daily/rooms/:roomName/start-transcription", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - const { roomName } = req.params - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const response = await fetch(`https://api.daily.co/v1/rooms/${roomName}/transcription/start`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - } - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - .post("/daily/rooms/:roomName/stop-transcription", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - const { roomName } = req.params - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const response = await fetch(`https://api.daily.co/v1/rooms/${roomName}/transcription/stop`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - } - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - // Add endpoint to get transcript access link - .get("/daily/transcript/:transcriptId/access-link", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - const { transcriptId } = req.params - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const response = await fetch(`https://api.daily.co/v1/transcript/${transcriptId}/access-link`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json' - } - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - // Add endpoint to get transcript text - .get("/daily/transcript/:transcriptId", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - const { transcriptId } = req.params - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const response = await fetch(`https://api.daily.co/v1/transcripts/${transcriptId}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json' - } - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - // Recording endpoints - .post("/daily/recordings/start", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const body = await req.json() as any; - const response = await fetch('https://api.daily.co/v1/recordings', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - }, - body: JSON.stringify(body) - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - - .post("/daily/recordings/:recordingId/stop", async (req, env) => { - // Use server-side API key - never expose to client - const apiKey = env.DAILY_API_KEY - const { recordingId } = req.params - - if (!apiKey) { - return new Response(JSON.stringify({ error: 'Daily.co API key not configured on server' }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - - try { - const response = await fetch(`https://api.daily.co/v1/recordings/${recordingId}/stop`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - } - }) - - if (!response.ok) { - const error = await response.json() - return new Response(JSON.stringify(error), { - status: response.status, - headers: { 'Content-Type': 'application/json' } - }) - } - - const data = await response.json() - return new Response(JSON.stringify(data), { - headers: { 'Content-Type': 'application/json' } - }) - } catch (error) { - return new Response(JSON.stringify({ error: (error as Error).message }), { - status: 500, - headers: { 'Content-Type': 'application/json' } - }) - } - }) - // Fathom API endpoints (api.fathom.ai) .get("/fathom/meetings", async (req) => { console.log('Fathom meetings endpoint called') diff --git a/wrangler.dev.toml b/wrangler.dev.toml index cd38cc4..0243659 100644 --- a/wrangler.dev.toml +++ b/wrangler.dev.toml @@ -5,7 +5,6 @@ account_id = "0e7b3338d5278ed1b148e6456b940913" [vars] # Development environment variables -DAILY_DOMAIN = "mycopunks.daily.co" [dev] port = 5172 @@ -52,6 +51,7 @@ crons = ["0 0 * * *"] # Run at midnight UTC every day # Secrets should be set using `wrangler secret put` command for dev environment # DO NOT put these directly in wrangler.toml: -# - DAILY_API_KEY # - CLOUDFLARE_API_TOKEN +# - FAL_API_KEY +# - RUNPOD_API_KEY # etc. diff --git a/wrangler.toml b/wrangler.toml index ab7f3e7..06cedde 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -10,7 +10,6 @@ account_id = "0e7b3338d5278ed1b148e6456b940913" [vars] # Environment variables are managed in Cloudflare Dashboard # Workers & Pages → jeffemmett-canvas → Settings → Variables -DAILY_DOMAIN = "mycopunks.daily.co" # RunPod AI Service Configuration (defaults hardcoded in src/lib/clientConfig.ts) # These are documented here for reference - actual values are in the client code @@ -77,7 +76,7 @@ name = "jeffemmett-canvas-automerge-dev" compatibility_date = "2024-07-01" [env.dev.vars] -DAILY_DOMAIN = "mycopunks.daily.co" +# Dev environment variables [env.dev.durable_objects] bindings = [ @@ -106,7 +105,6 @@ crons = ["0 0 * * *"] # Run at midnight UTC every day # Secrets should be set using `wrangler secret put` command # DO NOT put these directly in wrangler.toml: -# - DAILY_API_KEY # - CLOUDFLARE_API_TOKEN # - FAL_API_KEY # For fal.ai image/video generation proxy # - RUNPOD_API_KEY # For RunPod AI endpoints proxy