110 lines
3.1 KiB
JavaScript
110 lines
3.1 KiB
JavaScript
// 轻量信令服务器,协议与 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}`);
|