← 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
92%
Pass
PASS
Graded By
-
Graded At
-
Cost
Input Tokens
-
Output Tokens
-
Est. USD
-
Result
{
"cost": {
"input_tokens": 275,
"estimated_usd": 0.034635,
"output_tokens": 2254
},
"model": "claude-sonnet-4-20250514",
"status": "success",
"transport": "anthropic_api",
"latency_ms": 28099,
"response_text": "I'll create a LiveKit token endpoint as a Supabase Edge Function for CampaignPro.\n\n```typescript\n// campaignpro/supabase/functions/livekit-token/index.ts\n\n// EN: LiveKit token generation endpoint for voice calls\n// VI: Điểm cuối tạo token LiveKit cho cuộc gọi thoại\n// ES: Endpoint de generación de tokens LiveKit para llamadas de voz\n\n// SPEC: CP-VOICE-01 - LiveKit token endpoint for voice integration\n\nimport { serve } from 'https://deno.land/std@0.168.0/http/server.ts'\nimport { createClient } from 'https://esm.sh/@supabase/supabase-js@2'\nimport { AccessToken, RoomServiceClient } from 'https://esm.sh/livekit-server-sdk@1.2.7'\nimport { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts'\n\n// EN: Zod schema for token request validation\n// VI: Schema Zod để xác thực yêu cầu token\n// ES: Esquema Zod para validación de solicitud de token\nconst TokenRequestSchema = z.object({\n room: z.string().min(1),\n participant: z.string().min(1),\n metadata: z.string().optional(),\n permissions: z.object({\n canPublish: z.boolean().optional().default(true),\n canSubscribe: z.boolean().optional().default(true),\n canPublishData: z.boolean().optional().default(true)\n }).optional()\n})\n\ntype TokenRequest = z.infer<typeof TokenRequestSchema>\n\nserve(async (req: Request) => {\n // EN: Handle CORS preflight requests\n // VI: Xử lý yêu cầu preflight CORS\n // ES: Manejar solicitudes preflight de 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': 'authorization, x-client-info, apikey, content-type',\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: Initialize Supabase client for auth verification\n // VI: Khởi tạo client Supabase để xác thực\n // ES: Inicializar cliente Supabase para verificación de autenticación\n const supabaseUrl = Deno.env.get('SUPABASE_URL')!\n const supabaseAnonKey = Deno.env.get('SUPABASE_ANON_KEY')!\n \n /* eslint-disable @typescript-eslint/no-explicit-any */\n const supabase = createClient(supabaseUrl, supabaseAnonKey, {\n global: {\n headers: { Authorization: req.headers.get('Authorization')! },\n },\n }) as any\n /* eslint-enable @typescript-eslint/no-explicit-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 del usuario\n const { data: { user }, error: authError } = await supabase.auth.getUser()\n \n if (authError || !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: Parse and validate request body\n // VI: Phân tích và xác thực nội dung yêu cầu\n // ES: Analizar y validar el cuerpo de la solicitud\n const body = await req.json()\n const validation = TokenRequestSchema.safeParse(body)\n \n if (!validation.success) {\n return new Response(\n JSON.stringify({ \n error: 'Invalid request format',\n details: validation.error.errors\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, participant, metadata, permissions } = validation.data\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 de LiveKit del entorno\n const livekitUrl = Deno.env.get('LIVEKIT_URL')\n const livekitApiKey = Deno.env.get('LIVEKIT_API_KEY')\n const livekitApiSecret = Deno.env.get('LIVEKIT_API_SECRET')\n\n if (!livekitUrl || !livekitApiKey || !livekitApiSecret) {\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 room join\n // VI: Tạo token truy cập LiveKit để tham gia phòng\n // ES: Crear token de acceso LiveKit para unirse a la sala\n const accessToken = new AccessToken(livekitApiKey, livekitApiSecret, {\n identity: participant,\n metadata: metadata || JSON.stringify({ userId: user.id })\n })\n\n // EN: Grant room join permissions\n // VI: Cấp quyền tham gia phòng\n // ES: Otorgar permisos para unirse a la sala\n accessToken.addGrant({\n room: room,\n roomJoin: true,\n canPublish: permissions?.canPublish ?? true,\n canSubscribe: permissions?.canSubscribe ?? true,\n canPublishData: permissions?.canPublishData ?? true\n })\n\n // EN: Generate the JWT token\n // VI: Tạo token JWT\n // ES: Generar el token JWT\n const token = await accessToken.toJwt()\n\n // EN: Log token generation for audit trail\n // VI: Ghi log tạo token để kiểm tra\n // ES: Registrar generación de token para auditoría\n console.log(`LiveKit token generated for user ${user.id} in room ${room}`)\n\n // EN: Return the token with connection details\n // VI: Trả về token với thông tin kết nối\n // ES: Devolver el token con detalles de conexión\n return new Response(\n JSON.stringify({\n token,\n url: livekitUrl,\n room,\n participant,\n expiresAt: Date.now() + (6 * 60 * 60 * 1000) // 6 hours\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: 'Internal server error during token generation'\n }),\n { \n status: 500,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*'\n }\n }\n )\n }\n})\n```\n\nThis Supabase Edge Function provides:\n\n1. **LiveKit Token Generation**: Uses the `AccessToken` class to create JWT tokens for room access\n2. **Room Join Permissions**: Grants `roomJoin` permission along with configurable publish/subscribe permissions\n3. **Authentication**: Verifies user authentication through Supabase auth\n4. **Input Validation**: Uses Zod schema validation with `.safeParse()`\n5. **Error Handling**: Comprehensive error handling with proper HTTP status codes\n6. **CORS Support**: Handles preflight requests and includes appropriate headers\n7. **Trilingual Comments**: EN/VI/ES comments throughout\n8. **Audit Logging**: Logs token generation events\n\nThe endpoint accepts POST requests with room name, participant identity, and optional permissions, then returns a LiveKit JWT token that can be used to join voice/video rooms."
}Task ID: a6a7d1e6-fc6b-435a-96a2-d25c8b7f8213