@wordpress/sync
Version:
317 lines (308 loc) • 11.1 kB
JavaScript
/* wp:polyfill */
;
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