2026-05-25 20:37:36 +08:00
|
|
|
import * as Logger from "../utils/logger.js";
|
2026-04-29 15:18:30 +08:00
|
|
|
|
|
|
|
|
export class Signaling extends EventTarget {
|
|
|
|
|
|
2026-05-25 22:58:11 +08:00
|
|
|
constructor(interval = 1000, baseUrl = null) {
|
2026-04-29 15:18:30 +08:00
|
|
|
super();
|
|
|
|
|
this.running = false;
|
|
|
|
|
this.interval = interval;
|
2026-05-25 22:58:11 +08:00
|
|
|
this.baseUrl = baseUrl;
|
2026-04-29 15:18:30 +08:00
|
|
|
this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
headers() {
|
|
|
|
|
if (this.sessionId !== undefined) {
|
|
|
|
|
return { 'Content-Type': 'application/json', 'Session-Id': this.sessionId };
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return { 'Content-Type': 'application/json' };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url(method, parameter = '') {
|
2026-05-25 22:58:11 +08:00
|
|
|
let ret = (this.baseUrl || location.origin) + '/signaling';
|
2026-04-29 15:18:30 +08:00
|
|
|
if (method)
|
|
|
|
|
ret += '/' + method;
|
|
|
|
|
if (parameter)
|
|
|
|
|
ret += '?' + parameter;
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async start() {
|
|
|
|
|
if (this.running) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.running = true;
|
|
|
|
|
while (!this.sessionId) {
|
|
|
|
|
const createResponse = await fetch(this.url(''), { method: 'PUT', headers: this.headers() });
|
|
|
|
|
const session = await createResponse.json();
|
|
|
|
|
this.sessionId = session.sessionId;
|
|
|
|
|
|
|
|
|
|
if (!this.sessionId) {
|
|
|
|
|
await this.sleep(this.interval);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.loopGetAll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async loopGetAll() {
|
|
|
|
|
let lastTimeRequest = Date.now() - 30000;
|
|
|
|
|
while (this.running) {
|
|
|
|
|
const res = await this.getAll(lastTimeRequest);
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
lastTimeRequest = data.datetime ? data.datetime : Date.now();
|
|
|
|
|
|
|
|
|
|
const messages = data.messages;
|
|
|
|
|
|
|
|
|
|
for (const msg of messages) {
|
|
|
|
|
switch (msg.type) {
|
|
|
|
|
case "connect":
|
|
|
|
|
break;
|
|
|
|
|
case "disconnect":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('disconnect', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "offer":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('offer', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "answer":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('answer', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "candidate":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('candidate', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "on-message":
|
2026-05-12 22:12:19 +08:00
|
|
|
{
|
|
|
|
|
let parsed = msg.data;
|
|
|
|
|
if (typeof msg.data === 'string') {
|
|
|
|
|
try { parsed = JSON.parse(msg.data); } catch(e) {
|
|
|
|
|
Logger.error(`Signaling: on-message, error: ${e}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.dispatchEvent(new CustomEvent('on-message', { detail: parsed }));
|
|
|
|
|
}
|
2026-04-29 15:18:30 +08:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await this.sleep(this.interval);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async stop() {
|
|
|
|
|
this.running = false;
|
|
|
|
|
await fetch(this.url(''), { method: 'DELETE', headers: this.headers() });
|
|
|
|
|
this.sessionId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async createConnection(connectionId) {
|
|
|
|
|
const data = { 'connectionId': connectionId };
|
|
|
|
|
const res = await fetch(this.url('connection'), { method: 'PUT', headers: this.headers(), body: JSON.stringify(data) });
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
Logger.log(`Signaling: HTTP create connection, connectionId: ${json.connectionId}, polite:${json.polite}`);
|
|
|
|
|
|
|
|
|
|
this.dispatchEvent(new CustomEvent('connect', { detail: json }));
|
|
|
|
|
return json;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async deleteConnection(connectionId) {
|
|
|
|
|
const data = { 'connectionId': connectionId };
|
|
|
|
|
const res = await fetch(this.url('connection'), { method: 'DELETE', headers: this.headers(), body: JSON.stringify(data) });
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
this.dispatchEvent(new CustomEvent('disconnect', { detail: json }));
|
|
|
|
|
return json;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async sendOffer(connectionId, sdp) {
|
|
|
|
|
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
|
|
|
|
Logger.log('sendOffer:' + data);
|
|
|
|
|
await fetch(this.url('offer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async sendAnswer(connectionId, sdp) {
|
|
|
|
|
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
|
|
|
|
Logger.log('sendAnswer:' + data);
|
|
|
|
|
await fetch(this.url('answer'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) {
|
|
|
|
|
const data = {
|
|
|
|
|
'candidate': candidate,
|
|
|
|
|
'sdpMLineIndex': sdpMLineIndex,
|
|
|
|
|
'sdpMid': sdpMid,
|
|
|
|
|
'connectionId': connectionId
|
|
|
|
|
};
|
|
|
|
|
Logger.log('sendCandidate:' + data);
|
|
|
|
|
await fetch(this.url('candidate'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
|
|
|
|
|
}
|
|
|
|
|
// 在 Signaling 类中添加
|
|
|
|
|
async sendMessage(connectionId, message) {
|
|
|
|
|
const data = {
|
|
|
|
|
'message': message,
|
|
|
|
|
'connectionId': connectionId
|
|
|
|
|
};
|
|
|
|
|
await fetch(this.url('on-message'), { method: 'POST', headers: this.headers(), body: JSON.stringify(data) });
|
|
|
|
|
}
|
|
|
|
|
async getAll(fromTime = 0) {
|
|
|
|
|
return await fetch(this.url(``, `fromtime=${fromTime}`), { method: 'GET', headers: this.headers() });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class WebSocketSignaling extends EventTarget {
|
|
|
|
|
|
2026-05-25 22:58:11 +08:00
|
|
|
constructor(interval = 1000, websocketUrl = null) {
|
2026-04-29 15:18:30 +08:00
|
|
|
super();
|
|
|
|
|
this.interval = interval;
|
|
|
|
|
this.sleep = msec => new Promise(resolve => setTimeout(resolve, msec));
|
|
|
|
|
|
2026-05-25 22:58:11 +08:00
|
|
|
if (!websocketUrl) {
|
|
|
|
|
if (location.protocol === "https:") {
|
|
|
|
|
websocketUrl = "wss://" + location.host;
|
|
|
|
|
} else {
|
|
|
|
|
websocketUrl = "ws://" + location.host;
|
|
|
|
|
}
|
2026-04-29 15:18:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.websocket = new WebSocket(websocketUrl);
|
|
|
|
|
this.connectionId = null;
|
|
|
|
|
|
|
|
|
|
this.websocket.onopen = () => {
|
|
|
|
|
this.isWsOpen = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.websocket.onclose = () => {
|
|
|
|
|
this.isWsOpen = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.websocket.onmessage = (event) => {
|
|
|
|
|
const msg = JSON.parse(event.data);
|
|
|
|
|
if (!msg || !this) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.log(msg);
|
|
|
|
|
|
|
|
|
|
switch (msg.type) {
|
|
|
|
|
case "connect":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('connect', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "disconnect":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('disconnect', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "offer":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('offer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, polite: msg.data.polite, participantId: msg.participantId } }));
|
|
|
|
|
break;
|
|
|
|
|
case "answer":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('answer', { detail: { connectionId: msg.from, sdp: msg.data.sdp, participantId: msg.participantId } }));
|
|
|
|
|
break;
|
|
|
|
|
case "candidate":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('candidate', { detail: { connectionId: msg.from, candidate: msg.data.candidate, sdpMLineIndex: msg.data.sdpMLineIndex, sdpMid: msg.data.sdpMid, participantId: msg.participantId } }));
|
|
|
|
|
break;
|
|
|
|
|
case "on-message":
|
2026-05-12 22:12:19 +08:00
|
|
|
{
|
|
|
|
|
let parsed = msg.data;
|
|
|
|
|
if (typeof msg.data === 'string') {
|
2026-05-12 22:15:32 +08:00
|
|
|
try { parsed = JSON.parse(msg.data); } catch(e) {
|
|
|
|
|
Logger.error(`Signaling: on-message, error: ${e}`);
|
|
|
|
|
}
|
2026-05-12 22:12:19 +08:00
|
|
|
}
|
|
|
|
|
if (msg.participantId) {
|
|
|
|
|
parsed.participantId = msg.participantId;
|
|
|
|
|
}
|
|
|
|
|
this.dispatchEvent(new CustomEvent('on-message', { detail: parsed }));
|
2026-04-29 15:18:30 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case "participant-left":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('participant-left', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "participant-joined":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('participant-joined', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "broadcast":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('on-message', { detail: msg.message }));
|
|
|
|
|
break;
|
2026-05-18 23:03:28 +08:00
|
|
|
case "invite-call":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('invite-call', { detail: msg.data }));
|
|
|
|
|
break;
|
|
|
|
|
case "invite-accepted":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('invite-accepted', { detail: msg.data }));
|
|
|
|
|
break;
|
|
|
|
|
case "invite-rejected":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('invite-rejected', { detail: msg.data }));
|
|
|
|
|
break;
|
|
|
|
|
case "invite-failed":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('invite-failed', { detail: msg.data }));
|
|
|
|
|
break;
|
2026-06-02 02:34:40 +08:00
|
|
|
case "recording-started":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('recording-started', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "recording-peer-request":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('recording-peer-request', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "recording-stopped":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('recording-stopped', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "recording-status":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('recording-status', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "recording-answer":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('recording-answer', { detail: msg }));
|
|
|
|
|
break;
|
|
|
|
|
case "recording-candidate":
|
|
|
|
|
this.dispatchEvent(new CustomEvent('recording-candidate', { detail: msg }));
|
|
|
|
|
break;
|
2026-04-29 15:18:30 +08:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async start() {
|
|
|
|
|
while (!this.isWsOpen) {
|
|
|
|
|
await this.sleep(100);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async stop() {
|
|
|
|
|
this.websocket.close();
|
|
|
|
|
while (this.isWsOpen) {
|
|
|
|
|
await this.sleep(100);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createConnection(connectionId) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: "connect", connectionId: connectionId });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleteConnection(connectionId) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: "disconnect", connectionId: connectionId });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendOffer(connectionId, sdp, participantId) {
|
|
|
|
|
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
|
|
|
|
const sendJson = JSON.stringify({ type: "offer", from: connectionId, data: data, participantId: participantId || '' });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendAnswer(connectionId, sdp, participantId) {
|
|
|
|
|
const data = { 'sdp': sdp, 'connectionId': connectionId };
|
|
|
|
|
const sendJson = JSON.stringify({ type: "answer", from: connectionId, data: data, participantId: participantId || '' });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendCandidate(connectionId, candidate, sdpMLineIndex, sdpMid, participantId) {
|
|
|
|
|
const data = {
|
|
|
|
|
'candidate': candidate,
|
|
|
|
|
'sdpMLineIndex': sdpMLineIndex,
|
|
|
|
|
'sdpMid': sdpMid,
|
|
|
|
|
'connectionId': connectionId
|
|
|
|
|
};
|
|
|
|
|
const sendJson = JSON.stringify({ type: "candidate", from: connectionId, data: data, participantId: participantId || '' });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
// 在 WebSocketSignaling 类中添加
|
|
|
|
|
sendMessage(connectionId, message) {
|
|
|
|
|
const data = {
|
|
|
|
|
'message': message,
|
|
|
|
|
'senderId': message.senderId,
|
|
|
|
|
'connectionId': connectionId
|
|
|
|
|
};
|
|
|
|
|
const sendJson = JSON.stringify({ type: "on-message", data: data });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
2026-05-18 23:03:28 +08:00
|
|
|
|
2026-05-25 22:21:50 +08:00
|
|
|
sendUserInfo(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'host-userInfo', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 23:03:28 +08:00
|
|
|
sendInviteCall(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'invite-call', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendInviteAccepted(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'invite-accepted', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendInviteRejected(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'invite-rejected', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
2026-06-02 02:34:40 +08:00
|
|
|
|
|
|
|
|
sendRecordingOffer(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'recording-offer', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendRecordingCandidate(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'recording-candidate', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendRecordingStatus(payload) {
|
|
|
|
|
const sendJson = JSON.stringify({ type: 'recording-status', data: payload });
|
|
|
|
|
Logger.log(sendJson);
|
|
|
|
|
this.websocket.send(sendJson);
|
|
|
|
|
}
|
2026-04-29 15:18:30 +08:00
|
|
|
}
|