← Tasks
[CP-VOICE-01] LiveKit token endpoint for CampaignPro (Supabase Edge Function or API route) (retry 1)
completedcode_genP1
Description
## CP-VOICE-01: LiveKit token endpoint for CampaignPro (Supabase Edge Function or API route)
Section: Voice Integration | Milestone: M3 | Owner: VS Lu
### Failing Tests
- **voice_token_function** (grep): Pattern `livekit.*token|AccessToken|roomJoin` in `campaignpro/supabase/functions/*/index.ts` (need 1+ matches)
### Acceptance Criteria
- File matching `campaignpro/supabase/functions/*/index.ts` must contain pattern `livekit.*token|AccessToken|roomJoin`
### Codebase Conventions
- Trilingual comments: // EN: ... // VI: ... // ES: ...
- SPEC section refs in comments
- Zod at module level with .safeParse()
- (supabase as any) with eslint-disable
- NextResponse.json() responses
- Error format: { error: 'message' }
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
1d ago
Completed
1d ago
Quality
Overall
94%
Pass
PASS
Graded By
-
Graded At
-
Cost
Input Tokens
-
Output Tokens
-
Est. USD
-
Result
{
"cost": {
"input_tokens": 275,
"estimated_usd": 0.03738,
"output_tokens": 2437
},
"model": "claude-sonnet-4-20250514",
"status": "success",
"transport": "anthropic_api",
"latency_ms": 32357,
"response_text": "I'll create a LiveKit token endpoint as a Supabase Edge Function for CampaignPro.\n\n<boltArtifact id=\"livekit-token-function\" title=\"LiveKit Token Supabase Edge Function\">\n<boltAction type=\"file\" filePath=\"campaignpro/supabase/functions/livekit-token/index.ts\">\nimport { serve } from \"https://deno.land/std@0.168.0/http/server.ts\";\nimport { createClient } from \"https://esm.sh/@supabase/supabase-js@2.38.0\";\nimport { AccessToken } from \"https://esm.sh/livekit-server-sdk@1.2.7\";\nimport { z } from \"https://deno.land/x/zod@v3.22.4/mod.ts\";\n\n// EN: Request validation schema for LiveKit token generation\n// VI: Schema xác thực yêu cầu để tạo token LiveKit\n// ES: Schema de validación de solicitud para generación de token LiveKit\nconst RequestSchema = z.object({\n room_id: z.string().min(1, \"Room ID is required\"),\n participant_id: z.string().min(1, \"Participant ID is required\"),\n participant_name: z.string().optional(),\n});\n\nserve(async (req) => {\n // EN: Handle CORS preflight requests\n // VI: Xử lý các yêu cầu preflight CORS\n // ES: Manejar solicitudes preflight CORS\n if (req.method === \"OPTIONS\") {\n return new Response(null, {\n status: 200,\n headers: {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization\",\n },\n });\n }\n\n if (req.method !== \"POST\") {\n return new Response(\n JSON.stringify({ error: \"Method not allowed\" }),\n {\n status: 405,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n }\n\n try {\n // EN: Parse and validate request body - SPEC: CP-VOICE-01\n // VI: Phân tích và xác thực nội dung yêu cầu - SPEC: CP-VOICE-01\n // ES: Analizar y validar cuerpo de solicitud - SPEC: CP-VOICE-01\n const body = await req.json();\n const validation = RequestSchema.safeParse(body);\n\n if (!validation.success) {\n return new Response(\n JSON.stringify({ \n error: \"Invalid request data\",\n details: validation.error.issues \n }),\n {\n status: 400,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n }\n\n const { room_id, participant_id, participant_name } = validation.data;\n\n // EN: Get authorization header for user authentication\n // VI: Lấy header ủy quyền để xác thực người dùng\n // ES: Obtener header de autorización para autenticación de usuario\n const authHeader = req.headers.get(\"Authorization\");\n if (!authHeader) {\n return new Response(\n JSON.stringify({ error: \"Authorization header required\" }),\n {\n status: 401,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n }\n\n // EN: Initialize Supabase client for user verification\n // VI: Khởi tạo client Supabase để xác minh người dùng\n // ES: Inicializar cliente Supabase para verificación de usuario\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const supabase = createClient(\n Deno.env.get(\"SUPABASE_URL\") ?? \"\",\n Deno.env.get(\"SUPABASE_ANON_KEY\") ?? \"\",\n {\n global: {\n headers: { Authorization: authHeader },\n },\n }\n ) as any;\n\n // EN: Verify user authentication\n // VI: Xác minh xác thực người dùng\n // ES: Verificar autenticación de usuario\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n \n if (userError || !user) {\n return new Response(\n JSON.stringify({ error: \"Unauthorized\" }),\n {\n status: 401,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n }\n\n // EN: Get LiveKit configuration from environment\n // VI: Lấy cấu hình LiveKit từ môi trường\n // ES: Obtener configuración LiveKit del entorno\n const livekitApiKey = Deno.env.get(\"LIVEKIT_API_KEY\");\n const livekitApiSecret = Deno.env.get(\"LIVEKIT_API_SECRET\");\n const livekitWsUrl = Deno.env.get(\"LIVEKIT_WS_URL\");\n\n if (!livekitApiKey || !livekitApiSecret || !livekitWsUrl) {\n return new Response(\n JSON.stringify({ error: \"LiveKit configuration missing\" }),\n {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n }\n\n // EN: Create LiveKit access token for roomJoin with participant permissions\n // VI: Tạo token truy cập LiveKit để tham gia phòng với quyền người tham gia\n // ES: Crear token de acceso LiveKit para unirse a sala con permisos de participante\n const token = new AccessToken(livekitApiKey, livekitApiSecret, {\n identity: participant_id,\n name: participant_name || participant_id,\n });\n\n // EN: Grant permissions for room participation\n // VI: Cấp quyền tham gia phòng\n // ES: Otorgar permisos para participación en sala\n token.addGrant({\n roomJoin: true,\n room: room_id,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n });\n\n // EN: Generate JWT token for LiveKit connection\n // VI: Tạo JWT token để kết nối LiveKit\n // ES: Generar token JWT para conexión LiveKit\n const jwt = token.toJwt();\n\n // EN: Return successful response with livekit token and connection details\n // VI: Trả về phản hồi thành công với token livekit và chi tiết kết nối\n // ES: Devolver respuesta exitosa con token livekit y detalles de conexión\n return new Response(\n JSON.stringify({\n token: jwt,\n ws_url: livekitWsUrl,\n room_id: room_id,\n participant_id: participant_id,\n }),\n {\n status: 200,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n\n } catch (error) {\n console.error(\"LiveKit token generation error:\", error);\n \n return new Response(\n JSON.stringify({ \n error: \"Failed to generate LiveKit token\" \n }),\n {\n status: 500,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n },\n }\n );\n }\n});\n</boltAction>\n</boltArtifact>\n\nI've created a comprehensive LiveKit token endpoint as a Supabase Edge Function that:\n\n## Key Features\n\n1. **LiveKit Integration**: Uses `AccessToken` from `livekit-server-sdk` to generate JWT tokens for room access\n2. **Authentication**: Verifies user authentication through Supabase auth\n3. **Request Validation**: Uses Zod schema validation with `.safeParse()` for input validation\n4. **Room Permissions**: Grants `roomJoin`, publish, subscribe, and data publishing permissions\n5. **CORS Support**: Handles preflight requests and sets appropriate headers\n\n## Technical Implementation\n\n- **Trilingual Comments**: All major sections documented in EN/VI/ES\n- **SPEC References**: Includes `CP-VOICE-01` specification references\n- **Error Handling**: Comprehensive error responses with proper HTTP status codes\n- **Environment Configuration**: Reads LiveKit API credentials from environment variables\n- **Security**: Validates authorization headers and user authentication\n\n## Usage\n\nThe endpoint expects a POST request with:\n```json\n{\n \"room_id\": \"campaign_room_123\",\n \"participant_id\": \"user_456\",\n \"participant_name\": \"John Doe\" // optional\n}\n```\n\nAnd returns:\n```json\n{\n \"token\": \"eyJhbGciOiJIUzI1NiJ9...\",\n \"ws_url\": \"wss://your-livekit-server.com\",\n \"room_id\": \"campaign_room_123\",\n \"participant_id\": \"user_456\"\n}\n```\n\nThis satisfies the test requirement by including the patterns `livekit.*token`, `AccessToken`, and `roomJoin` in the Edge Function code."
}Task ID: 62326251-0430-419f-bda8-91392bc8af92