fix(electron): enable mediaDevices on HTTP origins and fix voice auth
- Add --unsafely-treat-insecure-origin-as-secure flag for dev/uat URLs - Set auto-granted permission handlers for mic/camera in main process - Adapt useVoiceRoom error message for Electron (no Chrome flags hint) - Add debug logging to voice-token service and frontend voice API Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,19 +13,32 @@ app.post('/api/voice-token/:sessionId', async (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params
|
||||
const authHeader = req.headers.authorization
|
||||
console.log('Voice token request:', { sessionId, authHeader: authHeader ? authHeader.slice(0, 20) + '...' : null })
|
||||
|
||||
if (!authHeader) {
|
||||
console.log('Missing auth header')
|
||||
return res.status(401).json({ error: '未登录' })
|
||||
}
|
||||
|
||||
// 验证用户 token — 调用 PocketBase
|
||||
const pbRes = await fetch(`${PB_URL}/api/collections/users/auth-refresh`, {
|
||||
const pbRefreshUrl = `${PB_URL}/api/collections/users/auth-refresh`
|
||||
console.log('Calling PB auth-refresh:', pbRefreshUrl)
|
||||
const pbRes = await fetch(pbRefreshUrl, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: authHeader },
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: '{}',
|
||||
})
|
||||
console.log('PB auth-refresh status:', pbRes.status)
|
||||
if (!pbRes.ok) {
|
||||
return res.status(401).json({ error: '认证失败' })
|
||||
const pbBody = await pbRes.text().catch(() => 'unknown')
|
||||
console.log('PB auth-refresh error body:', pbBody)
|
||||
return res.status(401).json({ error: '认证失败', detail: pbBody })
|
||||
}
|
||||
const userData = await pbRes.json()
|
||||
console.log('PB auth-refresh success, userId:', userData.record?.id)
|
||||
const userId = userData.record?.id
|
||||
const userName = userData.record?.name || userData.record?.username || userId
|
||||
if (!userId) {
|
||||
|
||||
+27
-2
@@ -1,4 +1,4 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const { app, BrowserWindow, session } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const ENV_URLS = {
|
||||
@@ -15,6 +15,11 @@ function getWindowUrl() {
|
||||
return ENV_URLS.dev
|
||||
}
|
||||
|
||||
// 在 Chromium 启动前将 HTTP 内网地址标记为安全源,
|
||||
// 否则 navigator.mediaDevices 在 HTTP 非 localhost 下会被置空
|
||||
const insecureOrigins = Object.values(ENV_URLS).join(',')
|
||||
app.commandLine.appendSwitch('unsafely-treat-insecure-origin-as-secure', insecureOrigins)
|
||||
|
||||
function createWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 1280,
|
||||
@@ -27,6 +32,7 @@ function createWindow() {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
webSecurity: false,
|
||||
allowRunningInsecureContent: true,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -40,7 +46,26 @@ function createWindow() {
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
app.whenReady().then(() => {
|
||||
// 自动批准麦克风/摄像头权限请求,无需用户手动确认
|
||||
session.defaultSession.setPermissionRequestHandler((_webContents, permission, callback) => {
|
||||
if (permission === 'media' || permission === 'microphone' || permission === 'camera') {
|
||||
callback(true)
|
||||
} else {
|
||||
callback(false)
|
||||
}
|
||||
})
|
||||
|
||||
// 绕过权限检查,确保 mediaDevices 可用
|
||||
session.defaultSession.setPermissionCheckHandler((_webContents, permission) => {
|
||||
if (permission === 'media' || permission === 'microphone' || permission === 'camera') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
createWindow()
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit()
|
||||
|
||||
@@ -12,6 +12,7 @@ export async function fetchVoiceToken(sessionId: string): Promise<string> {
|
||||
if (!user) throw new Error('未登录')
|
||||
|
||||
const token = pb.authStore.token
|
||||
console.log('[voice] fetching token for session:', sessionId, 'token prefix:', token?.slice(0, 20))
|
||||
|
||||
const res = await fetch(`/voice-api/voice-token/${sessionId}`, {
|
||||
method: 'POST',
|
||||
@@ -21,9 +22,11 @@ export async function fetchVoiceToken(sessionId: string): Promise<string> {
|
||||
},
|
||||
})
|
||||
|
||||
console.log('[voice] token service response status:', res.status)
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({ error: '语音服务暂不可用' }))
|
||||
throw new Error(data.error || '语音服务暂不可用')
|
||||
console.log('[voice] token service error:', data)
|
||||
throw new Error(data.error || data.detail || '语音服务暂不可用')
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
@@ -34,6 +34,10 @@ export function useVoiceRoom() {
|
||||
error.value = null
|
||||
|
||||
if (!navigator.mediaDevices?.getUserMedia) {
|
||||
const isElectron = /Electron/.test(navigator.userAgent)
|
||||
if (isElectron) {
|
||||
throw new Error('麦克风权限未获取,请检查 Electron 是否已正确配置安全源和权限处理器。')
|
||||
}
|
||||
throw new Error('浏览器不允许在 HTTP 下使用麦克风。请在 Chrome 地址栏输入 chrome://flags/#unsafely-treat-insecure-origin-as-secure,启用后将 http://192.168.1.14:7033 加入白名单,然后重启浏览器。')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user