FOUNDRY
C8 Platform
← 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