Files
gamegroup2/backend/voice-token-service/server.js
T
congsh 5d434ead6f feat(voice): add real-time voice room with LiveKit
- LiveKit WebRTC SFU container in docker-compose
- Voice token microservice (Node.js + Express)
- VoiceRoom page with member grid and controls
- useVoiceRoom composable for LiveKit connection
- Voice entry button in TeamSessionPanel
- Nginx proxy for voice-token service API

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-19 22:24:28 +08:00

75 lines
2.2 KiB
JavaScript

import express from 'express'
import { AccessToken } from 'livekit-server-sdk'
const app = express()
app.use(express.json())
const API_KEY = process.env.LIVEKIT_API_KEY || 'APIyxZGQjM2'
const API_SECRET = process.env.LIVEKIT_API_SECRET || 'secretNmU4ZDU3YjA0OWIxNDM4YjhlNWY3YTFjZGUzOWRi'
const PB_URL = process.env.PB_URL || 'http://gamegroup-pb:8090'
const PORT = process.env.PORT || 7882
app.post('/api/voice-token/:sessionId', async (req, res) => {
try {
const { sessionId } = req.params
const authHeader = req.headers.authorization
if (!authHeader) {
return res.status(401).json({ error: '未登录' })
}
// 验证用户 token — 调用 PocketBase
const pbRes = await fetch(`${PB_URL}/api/collections/users/auth-refresh`, {
method: 'POST',
headers: { Authorization: authHeader },
})
if (!pbRes.ok) {
return res.status(401).json({ error: '认证失败' })
}
const userData = await pbRes.json()
const userId = userData.record?.id
const userName = userData.record?.name || userData.record?.username || userId
if (!userId) {
return res.status(401).json({ error: '无效用户' })
}
// 获取 session 并验证成员
const sessionRes = await fetch(`${PB_URL}/api/collections/team_sessions/records/${sessionId}`, {
headers: { Authorization: authHeader },
})
if (!sessionRes.ok) {
return res.status(404).json({ error: '未找到临时小组' })
}
const session = await sessionRes.json()
const members = session.members || []
if (!members.includes(userId)) {
return res.status(403).json({ error: '你不是该小队的成员' })
}
// 签发 LiveKit token
const at = new AccessToken(API_KEY, API_SECRET, {
identity: userId,
name: userName,
})
at.addGrant({
roomJoin: true,
room: `team-${sessionId}`,
canPublish: true,
canSubscribe: true,
})
const token = await at.toJwt()
res.json({ token })
} catch (err) {
console.error('Voice token error:', err)
res.status(500).json({ error: '服务器错误' })
}
})
app.get('/health', (_req, res) => {
res.json({ status: 'ok' })
})
app.listen(PORT, () => {
console.log(`Voice token service listening on :${PORT}`)
})