5d434ead6f
- 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>
75 lines
2.2 KiB
JavaScript
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}`)
|
|
})
|