UNPKG

@wordpress/sync

Version:
317 lines (308 loc) 11.1 kB
/* wp:polyfill */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebrtcProviderWithHttpSignaling = exports.HttpSignalingConn = void 0; var _yWebrtc = require("./y-webrtc/y-webrtc"); var cryptoutils = _interopRequireWildcard(require("./y-webrtc/crypto")); var map = _interopRequireWildcard(require("lib0/map")); var _observable = require("lib0/observable"); var buffer = _interopRequireWildcard(require("lib0/buffer")); var _url = require("@wordpress/url"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * External dependencies */ /** * Internal dependencies */ /** * WordPress dependencies */ /** * Method copied as is from the SignalingConn constructor. * Setups the needed event handlers for an http signaling connection. * * @param {HttpSignalingConn} signalCon The signaling connection. * @param {string} url The url. */ function setupSignalEventHandlers(signalCon, url) { signalCon.on('connect', () => { (0, _yWebrtc.log)(`connected (${url})`); const topics = Array.from(_yWebrtc.rooms.keys()); signalCon.send({ type: 'subscribe', topics }); _yWebrtc.rooms.forEach(room => (0, _yWebrtc.publishSignalingMessage)(signalCon, room, { type: 'announce', from: room.peerId })); }); signalCon.on('message', (/** @type {{ type: any; topic: any; data: string; }} */m) => { switch (m.type) { case 'publish': { const roomName = m.topic; const room = _yWebrtc.rooms.get(roomName); if (room === null || typeof roomName !== 'string' || room === undefined) { return; } const execMessage = (/** @type {any} */data) => { const webrtcConns = room.webrtcConns; const peerId = room.peerId; if (data === null || data.from === peerId || data.to !== undefined && data.to !== peerId || room.bcConns.has(data.from)) { // ignore messages that are not addressed to this conn, or from clients that are connected via broadcastchannel 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 _yWebrtc.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) { (0, _yWebrtc.log)('offer rejected: ', data.from); return; } // if we don't reject the offer, we will be accepting it and answering it existingConn.glareToken = undefined; } } if (data.signal.type === 'answer') { (0, _yWebrtc.log)('offer answered by: ', data.from); const existingConn = webrtcConns.get(data.from); if (existingConn) { existingConn.glareToken = undefined; } } if (data.to === peerId) { map.setIfUndefined(webrtcConns, data.from, () => new _yWebrtc.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', () => (0, _yWebrtc.log)(`disconnect (${url})`)); } /** * Method that instantiates the http signaling connection. * Tries to implement the same methods a websocket provides using ajax requests * to send messages and EventSource to retrieve messages. * * @param {HttpSignalingConn} httpClient The signaling connection. */ function setupHttpSignal(httpClient) { if (httpClient.shouldConnect && httpClient.ws === null) { // eslint-disable-next-line no-restricted-syntax const subscriberId = Math.floor(100000 + Math.random() * 900000); const url = httpClient.url; const eventSource = new window.EventSource((0, _url.addQueryArgs)(url, { subscriber_id: subscriberId, action: 'gutenberg_signaling_server' })); /** * @type {any} */ 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); } } }; // @ts-ignore httpClient.ws = eventSource; httpClient.connecting = true; httpClient.connected = false; const onSingleMessage = (/** @type {any} */message) => { if (message && message.type === 'pong') { clearTimeout(pingTimeout); pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2); } httpClient.emit('message', [message, httpClient]); }; /** * @param {any} error */ 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(/** @type {string} */message) { window.fetch(url, { body: new URLSearchParams({ subscriber_id: subscriberId.toString(), action: 'gutenberg_signaling_server', message }), method: 'POST' }).catch(() => { (0, _yWebrtc.log)('Error sending to server with message: ' + message); }); }; } eventSource.onerror = () => { // Todo: add an error handler }; 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]); // set ping pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2); } }; } } const messageReconnectTimeout = 30000; /** * @augments Observable<string> */ class HttpSignalingConn extends _observable.Observable { /** * @param {string} url */ constructor(url) { super(); //WebsocketClient from lib0/websocket.js this.url = url; /** * @type {WebSocket?} */ this.ws = null; // @ts-ignore this.binaryType = null; // this.binaryType = binaryType this.connected = false; this.connecting = false; this.unsuccessfulReconnects = 0; this.lastMessageReceived = 0; /** * Whether to connect to other peers or not * * @type {boolean} */ this.shouldConnect = true; this._checkInterval = setInterval(() => { if (this.connected && messageReconnectTimeout < Date.now() - this.lastMessageReceived && this.ws) { // no message received in a long time - not even your own awareness // updates (which are updated every 15 seconds) this.ws.close(); } }, messageReconnectTimeout / 2); //setupWS( this ); setupHttpSignal(this); // From SignalingConn /** * @type {Set<WebrtcProvider>} */ this.providers = 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); } } } exports.HttpSignalingConn = HttpSignalingConn; class WebrtcProviderWithHttpSignaling extends _yWebrtc.WebrtcProvider { connect() { this.shouldConnect = true; this.signalingUrls.forEach((/** @type {string} */url) => { const signalingConn = map.setIfUndefined(_yWebrtc.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 _yWebrtc.SignalingConn(url) : () => new HttpSignalingConn(url)); this.signalingConns.push(signalingConn); signalingConn.providers.add(this); }); if (this.room) { this.room.connect(); } } } exports.WebrtcProviderWithHttpSignaling = WebrtcProviderWithHttpSignaling; //# sourceMappingURL=webrtc-http-stream-signaling.js.map