@wordpress/sync
Version:
305 lines (304 loc) • 9.12 kB
JavaScript
// 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