63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
// server/supabase/functions/realtime-token/index.ts
|
|
// OpenAI Realtime API ephemeral token 발급.
|
|
// 렌더러가 이 토큰으로 OpenAI와 직접 WebRTC 연결한다 (서버 키 비노출).
|
|
// 요청: application/json { model?, voice?, instructions? }
|
|
// 응답: OpenAI client_secrets 응답 그대로 + { model, tier }
|
|
//
|
|
// 이 파일은 조립 루트(composition root)다. 유스케이스는 handler.ts,
|
|
// 정책은 policy.ts, OpenAI 호출은 openai-provider.ts에 있다.
|
|
|
|
import { corsHeaders } from '../_shared/cors.ts'
|
|
import { authErrorResponse, requireUser, type AuthError } from '../_shared/auth.ts'
|
|
import {
|
|
checkQuota,
|
|
consumeQuota,
|
|
createServiceRoleClient,
|
|
getQuotaPolicy,
|
|
} from '../_shared/quota.ts'
|
|
import { readProviderKey } from '../_shared/provider-key.ts'
|
|
import {
|
|
type AuthenticateResult,
|
|
createRealtimeTokenHandler,
|
|
type RealtimeQuotaPort,
|
|
} from './handler.ts'
|
|
import { createOpenAiRealtimeProvider } from './openai-provider.ts'
|
|
|
|
function isAuthError(err: unknown): err is AuthError {
|
|
return !!err && typeof err === 'object' && 'status' in err && 'message' in err
|
|
}
|
|
|
|
async function authenticate(req: Request): Promise<AuthenticateResult> {
|
|
try {
|
|
const user = await requireUser(req)
|
|
return { ok: true, user: { id: user.id } }
|
|
} catch (err) {
|
|
if (isAuthError(err)) return { ok: false, response: authErrorResponse(err, corsHeaders) }
|
|
throw err
|
|
}
|
|
}
|
|
|
|
/** 서비스 롤 클라이언트는 요청 처리 중에 만든다 — 환경 누락이 부팅 실패가 아닌 500이 되도록. */
|
|
function createSupabaseRealtimeQuota(): RealtimeQuotaPort {
|
|
return {
|
|
check: (userId) => checkQuota(userId, 'realtime_session', createServiceRoleClient()),
|
|
consume: (userId, tier) =>
|
|
consumeQuota(
|
|
userId,
|
|
'realtime_session',
|
|
createServiceRoleClient(),
|
|
getQuotaPolicy(tier, 'realtime_session').limit,
|
|
),
|
|
}
|
|
}
|
|
|
|
const handler = createRealtimeTokenHandler({
|
|
authenticate,
|
|
quota: createSupabaseRealtimeQuota(),
|
|
provider: createOpenAiRealtimeProvider({ readApiKey: () => readProviderKey('OPENAI_API_KEY') }),
|
|
logger: {
|
|
error: (message, meta) => console.error(message, meta),
|
|
},
|
|
})
|
|
|
|
Deno.serve(handler)
|