Files
cherry-studio/asr-server/embedded.js
T
1600822305 53643e81f0 升级
2025-04-20 01:46:29 +08:00

469 lines
14 KiB
JavaScript

/**
* 内置的ASR服务器模块
* 这个文件可以直接在Electron中运行,不需要外部依赖
*/
// 使用Electron内置的Node.js模块
const http = require('http')
const path = require('path')
const fs = require('fs')
// const net = require('net')
const crypto = require('crypto')
// 输出环境信息
console.log('ASR Server (Embedded) starting...')
console.log('Node.js version:', process.version)
console.log('Current directory:', __dirname)
console.log('Current working directory:', process.cwd())
console.log('Command line arguments:', process.argv)
// 创建HTTP服务器
const server = http.createServer((req, res) => {
try {
if (req.url === '/' || req.url === '/index.html') {
// 尝试多个可能的路径
const possiblePaths = [
// 当前目录
path.join(__dirname, 'index.html'),
// 上级目录
path.join(__dirname, '..', 'index.html'),
// 应用根目录
path.join(process.cwd(), 'index.html')
]
console.log('Possible index.html paths:', possiblePaths)
// 查找第一个存在的文件
let indexPath = null
for (const p of possiblePaths) {
try {
if (fs.existsSync(p)) {
indexPath = p
console.log(`Found index.html at: ${p}`)
break
}
} catch (e) {
console.error(`Error checking existence of ${p}:`, e)
}
}
if (indexPath) {
// 读取文件内容并发送
fs.readFile(indexPath, (err, data) => {
if (err) {
console.error('Error reading index.html:', err)
res.writeHead(500)
res.end('Error reading index.html')
return
}
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(data)
})
} else {
// 如果找不到文件,返回一个简单的HTML页面
console.error('Could not find index.html, serving fallback page')
res.writeHead(200, { 'Content-Type': 'text/html' })
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>ASR Server</title>
<style>
body { font-family: sans-serif; padding: 2em; }
h1 { color: #333; }
</style>
</head>
<body>
<h1>ASR Server is running</h1>
<p>This is a fallback page because the index.html file could not be found.</p>
<p>Server is running at: http://localhost:34515</p>
<p>Current directory: ${__dirname}</p>
<p>Working directory: ${process.cwd()}</p>
</body>
</html>
`)
}
} else {
// 处理其他请求
res.writeHead(404)
res.end('Not found')
}
} catch (error) {
console.error('Error handling request:', error)
res.writeHead(500)
res.end('Server error')
}
})
// 添加进程错误处理
process.on('uncaughtException', (error) => {
console.error('[Server] Uncaught exception:', error)
// 不立即退出,给日志输出的时间
setTimeout(() => process.exit(1), 1000)
})
process.on('unhandledRejection', (reason, promise) => {
console.error('[Server] Unhandled rejection at:', promise, 'reason:', reason)
})
// WebSocket客户端管理
const clients = {
browser: null,
electron: null
}
// 处理WebSocket连接
server.on('upgrade', (request, socket) => {
try {
console.log('[WebSocket] Connection upgrade request received')
// 解析WebSocket密钥
const key = request.headers['sec-websocket-key']
const acceptKey = crypto
.createHash('sha1')
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary')
.digest('base64')
// 发送WebSocket握手响应
socket.write(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
`Sec-WebSocket-Accept: ${acceptKey}\r\n` +
'\r\n'
)
console.log('[WebSocket] Handshake successful')
// 处理WebSocket数据
handleWebSocketConnection(socket)
} catch (error) {
console.error('[WebSocket] Error handling upgrade:', error)
socket.destroy()
}
})
// 处理WebSocket连接
function handleWebSocketConnection(socket) {
let buffer = Buffer.alloc(0)
let role = null
socket.on('data', (data) => {
try {
buffer = Buffer.concat([buffer, data])
// 处理数据帧
while (buffer.length > 2) {
// 检查是否有完整的帧
const firstByte = buffer[0]
const secondByte = buffer[1]
// const isFinalFrame = Boolean((firstByte >>> 7) & 0x1)
const [opCode, maskFlag, payloadLength] = [firstByte & 0xf, (secondByte >>> 7) & 0x1, secondByte & 0x7f]
// 处理不同的负载长度
let payloadStartIndex = 2
let payloadLen = payloadLength
if (payloadLength === 126) {
payloadLen = buffer.readUInt16BE(2)
payloadStartIndex = 4
} else if (payloadLength === 127) {
// 处理大于16位的长度
payloadLen = Number(buffer.readBigUInt64BE(2))
payloadStartIndex = 10
}
// 处理掩码
let maskingKey
if (maskFlag) {
maskingKey = buffer.slice(payloadStartIndex, payloadStartIndex + 4)
payloadStartIndex += 4
}
// 检查是否有足够的数据
const frameEnd = payloadStartIndex + payloadLen
if (buffer.length < frameEnd) {
// 需要更多数据
break
}
// 提取负载
let payload = buffer.slice(payloadStartIndex, frameEnd)
// 如果有掩码,解码负载
if (maskFlag && maskingKey) {
for (let i = 0; i < payload.length; i++) {
payload[i] = payload[i] ^ maskingKey[i % 4]
}
}
// 处理不同的操作码
if (opCode === 0x8) {
// 关闭帧
console.log('[WebSocket] Received close frame')
socket.end()
return
} else if (opCode === 0x9) {
// Ping
sendPong(socket)
} else if (opCode === 0x1 || opCode === 0x2) {
// 文本或二进制数据
const message = opCode === 0x1 ? payload.toString('utf8') : payload
handleMessage(socket, message, role)
}
// 移除已处理的帧
buffer = buffer.slice(frameEnd)
}
} catch (error) {
console.error('[WebSocket] Error processing data:', error)
}
})
socket.on('close', () => {
console.log(`[WebSocket] Connection closed${role ? ` (${role})` : ''}`)
if (role === 'browser') {
clients.browser = null
} else if (role === 'electron') {
clients.electron = null
}
})
socket.on('error', (error) => {
console.error(`[WebSocket] Socket error${role ? ` (${role})` : ''}:`, error)
})
}
// 发送WebSocket数据
function sendWebSocketFrame(socket, data, opCode = 0x1) {
try {
const payload = Buffer.from(typeof data === 'string' ? data : JSON.stringify(data))
const payloadLength = payload.length
let header
if (payloadLength < 126) {
header = Buffer.from([0x80 | opCode, payloadLength])
} else if (payloadLength < 65536) {
header = Buffer.alloc(4)
header[0] = 0x80 | opCode
header[1] = 126
header.writeUInt16BE(payloadLength, 2)
} else {
header = Buffer.alloc(10)
header[0] = 0x80 | opCode
header[1] = 127
header.writeBigUInt64BE(BigInt(payloadLength), 2)
}
socket.write(Buffer.concat([header, payload]))
} catch (error) {
console.error('[WebSocket] Error sending data:', error)
}
}
// 发送Pong响应
function sendPong(socket) {
const pongFrame = Buffer.from([0x8a, 0x00])
socket.write(pongFrame)
}
// 处理消息
function handleMessage(socket, message, currentRole) {
try {
if (typeof message === 'string') {
const data = JSON.parse(message)
// 处理身份识别
if (data.type === 'identify') {
const role = data.role
if (role === 'browser' || role === 'electron') {
console.log(`[WebSocket] Client identified as: ${role}`)
// 存储客户端连接
clients[role] = socket
// 设置当前连接的角色
socket._role = role
return
}
}
// 获取当前连接的角色
const role = currentRole || socket._role
// 转发消息
if (role === 'browser') {
// 浏览器发送的消息转发给Electron
if (clients.electron) {
console.log(`[WebSocket] Browser -> Electron: ${JSON.stringify(data)}`)
sendWebSocketFrame(clients.electron, message)
} else {
console.log('[WebSocket] Cannot forward message: Electron client not connected')
}
} else if (role === 'electron') {
// Electron发送的消息转发给浏览器
if (clients.browser) {
console.log(`[WebSocket] Electron -> Browser: ${JSON.stringify(data)}`)
sendWebSocketFrame(clients.browser, message)
} else {
console.log('[WebSocket] Cannot forward message: Browser client not connected')
}
} else {
console.log(`[WebSocket] Received message from unknown role: ${message}`)
}
}
} catch (error) {
console.error('[WebSocket] Error handling message:', error, message)
}
}
// 检查端口是否被占用
function isPortAvailable(port) {
return new Promise((resolve) => {
const testServer = require('net').createServer()
testServer.once('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.log(`[Server] Port ${port} is in use, trying another port...`)
resolve(false)
} else {
console.error(`[Server] Error checking port ${port}:`, err)
resolve(false)
}
})
testServer.once('listening', () => {
testServer.close()
resolve(true)
})
testServer.listen(port)
})
}
// 找到可用的端口
async function findAvailablePort(startPort) {
let port = startPort
const maxPort = startPort + 10 // 尝试最多10个端口
while (port < maxPort) {
if (await isPortAvailable(port)) {
return port
}
port++
}
throw new Error(`Could not find an available port between ${startPort} and ${maxPort - 1}`)
}
// 尝试启动服务器
;(async () => {
try {
// 默认端口
const defaultPort = 34515
// 找到可用的端口
const port = await findAvailablePort(defaultPort)
// 将端口号写入文件,便于主进程读取
const portFilePath = path.join(__dirname, 'port.txt')
fs.writeFileSync(portFilePath, port.toString(), 'utf8')
console.log(`[Server] Port ${port} is available, saved to ${portFilePath}`)
// 启动服务器
server.listen(port, () => {
console.log(`[Server] Server running at http://localhost:${port}`)
// 写入成功标记
fs.writeFileSync(path.join(__dirname, 'server-ready.txt'), 'ready', 'utf8')
})
// 处理服务器错误
server.on('error', (error) => {
console.error(`[Server] Failed to start server:`, error)
process.exit(1) // Exit if server fails to start
})
// 保持进程运行
// 使用定时器保持进程运行
const keepAliveInterval = setInterval(() => {
console.log('[Server] Keep alive ping...')
}, 10000) // 每10秒发送一次日志,保持进程运行
// 添加信号处理程序
process.on('SIGINT', () => {
console.log('[Server] Received SIGINT signal, shutting down...')
clearInterval(keepAliveInterval)
server.close()
process.exit(0)
})
process.on('SIGTERM', () => {
console.log('[Server] Received SIGTERM signal, shutting down...')
clearInterval(keepAliveInterval)
server.close()
process.exit(0)
})
// 处理进程退出
process.on('exit', () => {
console.log('[Server] Process is exiting, cleaning up resources...')
try {
// 清除定时器
if (keepAliveInterval) {
clearInterval(keepAliveInterval)
}
// 关闭服务器
if (server) {
try {
server.close()
} catch (err) {
console.error('[Server] Error closing server:', err)
}
}
// 删除端口文件
if (fs.existsSync(portFilePath)) {
fs.unlinkSync(portFilePath)
console.log('[Server] Removed port file:', portFilePath)
}
// 删除就绪标记
const readyFilePath = path.join(__dirname, 'server-ready.txt')
if (fs.existsSync(readyFilePath)) {
fs.unlinkSync(readyFilePath)
console.log('[Server] Removed ready file:', readyFilePath)
}
console.log('[Server] Cleanup completed')
} catch (e) {
console.error('[Server] Error cleaning up files:', e)
}
})
// 添加未捕获异常处理
process.on('uncaughtException', (error) => {
console.error('[Server] Uncaught exception:', error)
// 尝试清理资源
try {
if (keepAliveInterval) {
clearInterval(keepAliveInterval)
}
if (fs.existsSync(portFilePath)) {
fs.unlinkSync(portFilePath)
}
const readyFilePath = path.join(__dirname, 'server-ready.txt')
if (fs.existsSync(readyFilePath)) {
fs.unlinkSync(readyFilePath)
}
} catch (e) {
console.error('[Server] Error cleaning up after uncaught exception:', e)
}
// 给日志输出的时间
setTimeout(() => process.exit(1), 1000)
})
} catch (error) {
console.error('[Server] Critical error starting server:', error)
process.exit(1)
}
})()