working video calls
This commit is contained in:
parent
87854883c6
commit
12256c5b9c
17
.env.example
17
.env.example
|
|
@ -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'
|
|
||||||
|
|
@ -174,3 +174,4 @@ dist
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
.dev.vars
|
.dev.vars
|
||||||
|
.env.production
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
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 }) => {
|
||||||
|
// Load env file based on `mode` in the current working directory.
|
||||||
|
// Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
|
||||||
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
|
|
||||||
|
return {
|
||||||
envPrefix: ["VITE_"],
|
envPrefix: ["VITE_"],
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
|
|
@ -19,8 +24,8 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
"import.meta.env.VITE_WORKER_URL": JSON.stringify(
|
__WORKER_URL__: JSON.stringify(env.VITE_TLDRAW_WORKER_URL),
|
||||||
process.env.VITE_WORKER_URL,
|
__DAILY_API_KEY__: JSON.stringify(env.VITE_DAILY_API_KEY)
|
||||||
),
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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({
|
|
||||||
message: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { 'Content-Type': 'application/json' }
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,6 +33,11 @@ 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
|
||||||
|
|
@ -39,3 +45,9 @@ 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.
|
||||||
Loading…
Reference in New Issue