working video calls

This commit is contained in:
Jeff-Emmett 2025-02-13 20:38:01 +01:00
parent 6d0ef158a4
commit 4380a7bdd6
7 changed files with 95 additions and 106 deletions

View File

@ -1,20 +1,13 @@
# Google API Credentials # Frontend (VITE) Public Variables
VITE_GOOGLE_CLIENT_ID='your_google_client_id' VITE_GOOGLE_CLIENT_ID='your_google_client_id'
VITE_GOOGLE_API_KEY='your_google_api_key'
VITE_GOOGLE_MAPS_API_KEY='your_google_maps_api_key' VITE_GOOGLE_MAPS_API_KEY='your_google_maps_api_key'
VITE_DAILY_DOMAIN='your_daily_domain'
VITE_TLDRAW_WORKER_URL='your_worker_url'
# Cloudflare Worker # Worker-only Variables (Do not prefix with VITE_)
CLOUDFLARE_API_TOKEN='your_cloudflare_token' CLOUDFLARE_API_TOKEN='your_cloudflare_token'
CLOUDFLARE_ACCOUNT_ID='your_account_id' CLOUDFLARE_ACCOUNT_ID='your_account_id'
CLOUDFLARE_ZONE_ID='your_zone_id' CLOUDFLARE_ZONE_ID='your_zone_id'
# Worker URL
TLDRAW_WORKER_URL='your_worker_url'
# R2 Bucket Configuration
R2_BUCKET_NAME='your_bucket_name' R2_BUCKET_NAME='your_bucket_name'
R2_PREVIEW_BUCKET_NAME='your_preview_bucket_name' R2_PREVIEW_BUCKET_NAME='your_preview_bucket_name'
DAILY_API_KEY=your_daily_api_key_here
# Daily.co Configuration
VITE_DAILY_API_KEY=your_daily_api_key_here
VITE_DAILY_DOMAIN='your_daily_domain'

1
.gitignore vendored
View File

@ -174,3 +174,4 @@ dist
.env.local .env.local
.env.*.local .env.*.local
.dev.vars .dev.vars
.env.production

View File

@ -1,6 +1,10 @@
import { BaseBoxShapeUtil, TLBaseShape } from "tldraw" import { BaseBoxShapeUtil, TLBaseShape } from "tldraw"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
interface DailyApiResponse {
url: string;
}
export type IVideoChatShape = TLBaseShape< export type IVideoChatShape = TLBaseShape<
"VideoChat", "VideoChat",
{ {
@ -36,33 +40,36 @@ export class VideoChatShape extends BaseBoxShapeUtil<IVideoChatShape> {
} }
try { try {
const apiKey = import.meta.env["VITE_DAILY_API_KEY"] const workerUrl = import.meta.env.VITE_TLDRAW_WORKER_URL
console.log("API Key available:", !!apiKey) const apiKey = import.meta.env.VITE_DAILY_API_KEY
if (!apiKey) throw new Error("Daily API key is missing")
const response = await fetch("https://api.daily.co/v1/rooms", { if (!apiKey) {
method: "POST", throw new Error('Daily.co API key not configured')
}
const response = await fetch(`${workerUrl}/daily/rooms`, {
method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey.trim()}`, 'Authorization': `Bearer ${apiKey}`
}, },
body: JSON.stringify({ body: JSON.stringify({
properties: { properties: {
enable_chat: true, enable_chat: true,
start_audio_off: true, enable_screenshare: true,
start_video_off: true, start_video_off: true,
}, start_audio_off: true
}), }
})
}) })
console.log("Response status:", response.status) if (!response.ok) {
console.log("Response data:", await response.clone().json()) const error = await response.json()
throw new Error(`Failed to create room (${response.status}): ${JSON.stringify(error)}`)
}
if (!response.ok) const data = (await response.json()) as DailyApiResponse;
throw new Error(`Failed to create room (${response.status})`) const url = data.url;
const responseData = (await response.json()) as { url: string }
const url = responseData.url
if (!url) throw new Error("Room URL is missing") if (!url) throw new Error("Room URL is missing")

8
src/vite-env.d.ts vendored
View File

@ -3,12 +3,8 @@
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_TLDRAW_WORKER_URL: string readonly VITE_TLDRAW_WORKER_URL: string
readonly VITE_GOOGLE_MAPS_API_KEY: string readonly VITE_GOOGLE_MAPS_API_KEY: string
readonly VITE_DAILY_API_KEY: string readonly VITE_GOOGLE_CLIENT_ID: string
readonly VITE_CLOUDFLARE_API_TOKEN: string readonly VITE_DAILY_DOMAIN: string
readonly VITE_CLOUDFLARE_ACCOUNT_ID: string
readonly VITE_CLOUDFLARE_ZONE_ID: string
readonly VITE_R2_BUCKET_NAME: string
readonly VITE_R2_PREVIEW_BUCKET_NAME: string
} }
interface ImportMeta { interface ImportMeta {

View File

@ -1,26 +1,31 @@
import { defineConfig } from "vite" import { defineConfig, loadEnv } from "vite"
import react from "@vitejs/plugin-react" import react from "@vitejs/plugin-react"
export default defineConfig({ export default defineConfig(({ mode }) => {
envPrefix: ["VITE_"], // Load env file based on `mode` in the current working directory.
plugins: [react()], // Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
server: { const env = loadEnv(mode, process.cwd(), '')
host: "0.0.0.0",
port: 5173, return {
}, envPrefix: ["VITE_"],
build: { plugins: [react()],
sourcemap: true, server: {
}, host: "0.0.0.0",
base: "/", port: 5173,
publicDir: "src/public",
resolve: {
alias: {
"@": "/src",
}, },
}, build: {
define: { sourcemap: true,
"import.meta.env.VITE_WORKER_URL": JSON.stringify( },
process.env.VITE_WORKER_URL, base: "/",
), publicDir: "src/public",
}, resolve: {
alias: {
"@": "/src",
},
},
define: {
__WORKER_URL__: JSON.stringify(env.VITE_TLDRAW_WORKER_URL),
__DAILY_API_KEY__: JSON.stringify(env.VITE_DAILY_API_KEY)
}
}
}) })

View File

@ -124,59 +124,34 @@ const router = AutoRouter<IRequest, [env: Environment, ctx: ExecutionContext]>({
}) })
}) })
.post("/daily/rooms", async (request, env) => { .post("/daily/rooms", async (req) => {
try { const apiKey = req.headers.get('Authorization')?.split('Bearer ')[1]
const { name, properties } = (await request.json()) as {
name: string if (!apiKey) {
properties: Record<string, unknown> return new Response(JSON.stringify({ error: 'No API key provided' }), {
} status: 401,
headers: { 'Content-Type': 'application/json' }
})
}
// Create a room using Daily.co API try {
const dailyResponse = await fetch("https://api.daily.co/v1/rooms", { const response = await fetch('https://api.daily.co/v1/rooms', {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
Authorization: `Bearer ${env.DAILY_API_KEY}`, 'Authorization': `Bearer ${apiKey}`
}, }
body: JSON.stringify({
name,
properties,
}),
}) })
const dailyData = await dailyResponse.json() const data = await response.json()
return new Response(JSON.stringify(data), {
if (!dailyResponse.ok) { headers: { 'Content-Type': 'application/json' }
return new Response( })
JSON.stringify({
message:
(dailyData as any).info || "Failed to create Daily.co room",
}),
{
status: 400,
headers: { "Content-Type": "application/json" },
},
)
}
return new Response(
JSON.stringify({
url: `https://${env.DAILY_DOMAIN}/${(dailyData as any).name}`,
}),
{
headers: { "Content-Type": "application/json" },
},
)
} catch (error) { } catch (error) {
return new Response( return new Response(JSON.stringify({ error: (error as Error).message }), {
JSON.stringify({ status: 500,
message: error instanceof Error ? error.message : "Unknown error", headers: { 'Content-Type': 'application/json' }
}), })
{
status: 500,
headers: { "Content-Type": "application/json" },
},
)
} }
}) })

View File

@ -6,6 +6,7 @@ account_id = "0e7b3338d5278ed1b148e6456b940913"
[vars] [vars]
# Environment variables are managed in Cloudflare Dashboard # Environment variables are managed in Cloudflare Dashboard
# Workers & Pages → jeffemmett-canvas → Settings → Variables # Workers & Pages → jeffemmett-canvas → Settings → Variables
DAILY_DOMAIN = "mycopunks.daily.co"
[dev] [dev]
port = 5172 port = 5172
@ -32,10 +33,21 @@ binding = 'BOARD_BACKUPS_BUCKET'
bucket_name = 'board-backups' bucket_name = 'board-backups'
preview_bucket_name = 'board-backups-preview' preview_bucket_name = 'board-backups-preview'
[miniflare]
kv_persist = true
r2_persist = true
durable_objects_persist = true
[observability] [observability]
enabled = true enabled = true
head_sampling_rate = 1 head_sampling_rate = 1
[triggers] [triggers]
crons = ["0 0 * * *"] # Run at midnight UTC every day crons = ["0 0 * * *"] # Run at midnight UTC every day
# crons = ["*/10 * * * *"] # Run every 10 minutes # crons = ["*/10 * * * *"] # Run every 10 minutes
# Secrets should be set using `wrangler secret put` command
# DO NOT put these directly in wrangler.toml:
# - DAILY_API_KEY
# - CLOUDFLARE_API_TOKEN
# etc.