Files
VibeCoding/WebRTCControllerWeb/server/index.js

110 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 轻量信令服务器,协议与 WebRTCSignalServer (Spring) 完全兼容。
// 端点: ws://host:PORT/ws/signal
// 支持消息: REGISTER / DEVICE_LIST / OFFER / ANSWER / ICE_CANDIDATE / CONTROL_COMMAND / CONNECTION_REJECTED
// 支持通知: REGISTER_SUCCESS / DEVICE_LIST / TARGET_OFFLINE / REQUEST_ERROR / REQUEST_TIMEOUT
import { WebSocketServer } from 'ws';
const PORT = Number(process.env.PORT || 8088);
const PATH = process.env.WS_PATH || '/ws/signal';
const wss = new WebSocketServer({ port: PORT, path: PATH });
// deviceId -> { ws, type }
const sessions = new Map();
// "from->to" -> true等待被控端确认
const pending = new Map();
const keyOf = (a, b) => `${a}->${b}`;
function send(ws, obj) {
if (ws && ws.readyState === ws.OPEN) ws.send(JSON.stringify(obj));
}
wss.on('connection', (ws) => {
ws.on('message', (data) => {
let msg;
try {
msg = JSON.parse(data.toString());
} catch {
return;
}
const type = (msg.type || '').toUpperCase();
switch (type) {
case 'REGISTER': handleRegister(ws, msg); break;
case 'DEVICE_LIST': handleDeviceList(ws); break;
case 'OFFER': handleOffer(msg); break;
case 'ANSWER':
case 'CONNECTION_REJECTED':
pending.delete(keyOf(msg.toDeviceId, msg.fromDeviceId));
forward(msg);
break;
case 'ICE_CANDIDATE':
case 'CONTROL_COMMAND':
forward(msg);
break;
default:
forward(msg);
}
});
ws.on('close', () => {
for (const [id, s] of sessions) {
if (s.ws === ws) { sessions.delete(id); break; }
}
});
});
function handleRegister(ws, msg) {
const id = msg.fromDeviceId;
const type = (msg.deviceType || '').toUpperCase();
if (!id || !type) return;
sessions.set(id, { ws, type });
send(ws, { type: 'REGISTER_SUCCESS', deviceId: id });
console.log(`设备注册: ${id} (${type})`);
}
function handleDeviceList(ws) {
const controllers = [];
const controlled = [];
for (const [id, s] of sessions) {
if (s.type === 'CONTROLLER') controllers.push(id);
else if (s.type === 'CONTROLLED') controlled.push(id);
}
send(ws, { type: 'DEVICE_LIST', controllers, controlled });
}
function handleOffer(msg) {
const from = msg.fromDeviceId;
const to = msg.toDeviceId;
const fromType = sessions.get(from)?.type;
const toType = sessions.get(to)?.type;
if (fromType !== 'CONTROLLER' || toType !== 'CONTROLLED') {
send(sessions.get(from)?.ws, {
type: 'REQUEST_ERROR',
toDeviceId: to,
payload: '连接请求只能由 CONTROLLER 发往 CONTROLLED',
});
return;
}
const target = sessions.get(to)?.ws;
if (!target) {
send(sessions.get(from)?.ws, {
type: 'TARGET_OFFLINE',
toDeviceId: to,
payload: '目标被控端不在线,请确认设备已开启并连接服务器',
});
return;
}
pending.set(keyOf(from, to), true);
forward(msg);
}
function forward(msg) {
const target = sessions.get(msg.toDeviceId)?.ws;
if (!target) return;
send(target, msg);
}
console.log(`信令服务器已启动: ws://localhost:${PORT}${PATH}`);