UNPKG

@wordpress/sync

Version:
305 lines (304 loc) 9.12 kB
// packages/sync/src/providers/webrtc-http-stream-signaling.js import { WebrtcProvider, SignalingConn, WebrtcConn, signalingConns, rooms, publishSignalingMessage, log } from "./y-webrtc/y-webrtc"; import * as cryptoutils from "./y-webrtc/crypto"; import * as map from "lib0/map"; import { Observable } from "lib0/observable"; import * as buffer from "lib0/buffer"; import { addQueryArgs } from "@wordpress/url"; function setupSignalEventHandlers(signalCon, url) { signalCon.on("connect", () => { log(`connected (${url})`); const topics = Array.from(rooms.keys()); signalCon.send({ type: "subscribe", topics }); rooms.forEach( (room) => publishSignalingMessage(signalCon, room, { type: "announce", from: room.peerId }) ); }); signalCon.on( "message", (m) => { switch (m.type) { case "publish": { const roomName = m.topic; const room = rooms.get(roomName); if (room === null || typeof roomName !== "string" || room === void 0) { return; } const execMessage = (data) => { const webrtcConns = room.webrtcConns; const peerId = room.peerId; if (data === null || data.from === peerId || data.to !== void 0 && data.to !== peerId || room.bcConns.has(data.from)) { return; } const emitPeerChange = webrtcConns.has(data.from) ? () => { } : () => room.provider.emit("peers", [ { removed: [], added: [data.from], webrtcPeers: Array.from( room.webrtcConns.keys() ), bcPeers: Array.from(room.bcConns) } ]); switch (data.type) { case "announce": if (webrtcConns.size < room.provider.maxConns) { map.setIfUndefined( webrtcConns, data.from, () => new WebrtcConn( signalCon, true, data.from, room ) ); emitPeerChange(); } break; case "signal": if (data.signal.type === "offer") { const existingConn = webrtcConns.get( data.from ); if (existingConn) { const remoteToken = data.token; const localToken = existingConn.glareToken; if (localToken && localToken > remoteToken) { log( "offer rejected: ", data.from ); return; } existingConn.glareToken = void 0; } } if (data.signal.type === "answer") { log("offer answered by: ", data.from); const existingConn = webrtcConns.get( data.from ); if (existingConn) { existingConn.glareToken = void 0; } } if (data.to === peerId) { map.setIfUndefined( webrtcConns, data.from, () => new WebrtcConn( signalCon, false, data.from, room ) ).peer.signal(data.signal); emitPeerChange(); } break; } }; if (room.key) { if (typeof m.data === "string") { cryptoutils.decryptJson( buffer.fromBase64(m.data), room.key ).then(execMessage); } } else { execMessage(m.data); } } } } ); signalCon.on("disconnect", () => log(`disconnect (${url})`)); } function setupHttpSignal(httpClient) { if (httpClient.shouldConnect && httpClient.ws === null) { const subscriberId = Math.floor(1e5 + Math.random() * 9e5); const url = httpClient.url; const eventSource = new window.EventSource( addQueryArgs(url, { subscriber_id: subscriberId, action: "gutenberg_signaling_server" }) ); let pingTimeout = null; eventSource.onmessage = (event) => { httpClient.lastMessageReceived = Date.now(); const data = event.data; if (data) { const messages = JSON.parse(data); if (Array.isArray(messages)) { messages.forEach(onSingleMessage); } } }; httpClient.ws = eventSource; httpClient.connecting = true; httpClient.connected = false; const onSingleMessage = (message) => { if (message && message.type === "pong") { clearTimeout(pingTimeout); pingTimeout = setTimeout( sendPing, messageReconnectTimeout / 2 ); } httpClient.emit("message", [message, httpClient]); }; const onclose = (error) => { if (httpClient.ws !== null) { httpClient.ws.close(); httpClient.ws = null; httpClient.connecting = false; if (httpClient.connected) { httpClient.connected = false; httpClient.emit("disconnect", [ { type: "disconnect", error }, httpClient ]); } else { httpClient.unsuccessfulReconnects++; } } clearTimeout(pingTimeout); }; const sendPing = () => { if (httpClient.ws && httpClient.ws.readyState === window.EventSource.OPEN) { httpClient.send({ type: "ping" }); } }; if (httpClient.ws) { httpClient.ws.onclose = () => { onclose(null); }; httpClient.ws.send = function send(message) { window.fetch(url, { body: new URLSearchParams({ subscriber_id: subscriberId.toString(), action: "gutenberg_signaling_server", message }), method: "POST" }).catch(() => { log( "Error sending to server with message: " + message ); }); }; } eventSource.onerror = () => { }; eventSource.onopen = () => { if (httpClient.connected) { return; } if (eventSource.readyState === window.EventSource.OPEN) { httpClient.lastMessageReceived = Date.now(); httpClient.connecting = false; httpClient.connected = true; httpClient.unsuccessfulReconnects = 0; httpClient.emit("connect", [ { type: "connect" }, httpClient ]); pingTimeout = setTimeout( sendPing, messageReconnectTimeout / 2 ); } }; } } var messageReconnectTimeout = 3e4; var HttpSignalingConn = class extends Observable { /** * @param {string} url */ constructor(url) { super(); this.url = url; this.ws = null; this.binaryType = null; this.connected = false; this.connecting = false; this.unsuccessfulReconnects = 0; this.lastMessageReceived = 0; this.shouldConnect = true; this._checkInterval = setInterval(() => { if (this.connected && messageReconnectTimeout < Date.now() - this.lastMessageReceived && this.ws) { this.ws.close(); } }, messageReconnectTimeout / 2); setupHttpSignal(this); this.providers = /* @__PURE__ */ new Set(); setupSignalEventHandlers(this, url); } /** * @param {any} message */ send(message) { if (this.ws) { this.ws.send(JSON.stringify(message)); } } destroy() { clearInterval(this._checkInterval); this.disconnect(); super.destroy(); } disconnect() { this.shouldConnect = false; if (this.ws !== null) { this.ws.close(); } } connect() { this.shouldConnect = true; if (!this.connected && this.ws === null) { setupHttpSignal(this); } } }; var WebrtcProviderWithHttpSignaling = class extends WebrtcProvider { connect() { this.shouldConnect = true; this.signalingUrls.forEach((url) => { const signalingConn = map.setIfUndefined( signalingConns, url, // Only this conditional logic to create a normal websocket connection or // an http signaling connection was added to the constructor when compared // with the base class. url.startsWith("ws://") || url.startsWith("wss://") ? () => new SignalingConn(url) : () => new HttpSignalingConn(url) ); this.signalingConns.push(signalingConn); signalingConn.providers.add(this); }); if (this.room) { this.room.connect(); } } }; export { HttpSignalingConn, WebrtcProviderWithHttpSignaling }; //# sourceMappingURL=webrtc-http-stream-signaling.js.map