2020-07-16 17:27:39 +10:00
|
|
|
import io from "socket.io-client";
|
|
|
|
|
import { EventEmitter } from "events";
|
|
|
|
|
|
|
|
|
|
import Connection from "./Connection";
|
|
|
|
|
|
2020-10-23 10:27:22 +11:00
|
|
|
import { omit } from "../helpers/shared";
|
2020-10-27 10:26:55 +11:00
|
|
|
import { logError } from "../helpers/logging";
|
2020-07-20 20:54:21 +10:00
|
|
|
|
2020-07-16 17:27:39 +10:00
|
|
|
/**
|
|
|
|
|
* @typedef {object} SessionPeer
|
|
|
|
|
* @property {string} id - The socket id of the peer
|
|
|
|
|
* @property {Connection} connection - The actual peer connection
|
|
|
|
|
* @property {boolean} initiator - Is this peer the initiator of the connection
|
|
|
|
|
* @property {boolean} sync - Should this connection sync other connections
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* Handles connections to multiple peers
|
|
|
|
|
*
|
|
|
|
|
* Events:
|
|
|
|
|
* - connect: A party member has connected
|
|
|
|
|
* - data
|
|
|
|
|
* - trackAdded
|
|
|
|
|
* - trackRemoved
|
|
|
|
|
* - disconnect: A party member has disconnected
|
|
|
|
|
* - error
|
|
|
|
|
* - authenticationSuccess
|
|
|
|
|
* - authenticationError
|
2020-11-26 11:09:48 +11:00
|
|
|
* - connected: You have connected to the party
|
|
|
|
|
* - disconnected: You have disconnected from the party
|
2020-07-16 17:27:39 +10:00
|
|
|
*/
|
|
|
|
|
class Session extends EventEmitter {
|
|
|
|
|
/**
|
|
|
|
|
* The socket io connection
|
|
|
|
|
*
|
|
|
|
|
* @type {SocketIOClient.Socket}
|
|
|
|
|
*/
|
|
|
|
|
socket;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A mapping of socket ids to session peers
|
|
|
|
|
*
|
|
|
|
|
* @type {Object.<string, SessionPeer>}
|
|
|
|
|
*/
|
|
|
|
|
peers;
|
|
|
|
|
|
2020-11-26 11:09:48 +11:00
|
|
|
/**
|
|
|
|
|
* The state of the session
|
|
|
|
|
*
|
|
|
|
|
* @type {('unknown'|'online'|'offline')}
|
|
|
|
|
*/
|
|
|
|
|
state;
|
|
|
|
|
|
2020-07-16 17:27:39 +10:00
|
|
|
get id() {
|
2020-11-26 11:09:48 +11:00
|
|
|
return this.socket && this.socket.id;
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_iceServers;
|
|
|
|
|
|
|
|
|
|
// Store party id and password for reconnect
|
|
|
|
|
_partyId;
|
|
|
|
|
_password;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
super();
|
|
|
|
|
this.peers = {};
|
2020-11-26 11:09:48 +11:00
|
|
|
this.state = "unknown";
|
2020-07-16 17:27:39 +10:00
|
|
|
// Signal connected peers of a closure on refresh
|
|
|
|
|
window.addEventListener("beforeunload", this._handleUnload.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-26 11:09:48 +11:00
|
|
|
async connect() {
|
|
|
|
|
try {
|
|
|
|
|
const response = await fetch(process.env.REACT_APP_ICE_SERVERS_URL);
|
2020-11-27 18:31:21 +11:00
|
|
|
response.headers.forEach(console.log);
|
2020-11-26 18:54:32 +11:00
|
|
|
if (!response.ok) {
|
2020-11-26 18:55:27 +11:00
|
|
|
throw Error("Unable to fetch ICE servers");
|
2020-11-26 18:54:32 +11:00
|
|
|
}
|
2020-11-26 11:09:48 +11:00
|
|
|
const data = await response.json();
|
|
|
|
|
this._iceServers = data.iceServers;
|
|
|
|
|
|
|
|
|
|
this.socket = io(process.env.REACT_APP_BROKER_URL, {
|
|
|
|
|
transports: ["websocket"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.socket.on(
|
|
|
|
|
"party member joined",
|
|
|
|
|
this._handlePartyMemberJoined.bind(this)
|
|
|
|
|
);
|
|
|
|
|
this.socket.on(
|
|
|
|
|
"party member left",
|
|
|
|
|
this._handlePartyMemberLeft.bind(this)
|
|
|
|
|
);
|
|
|
|
|
this.socket.on("joined party", this._handleJoinedParty.bind(this));
|
|
|
|
|
this.socket.on("signal", this._handleSignal.bind(this));
|
|
|
|
|
this.socket.on("auth error", this._handleAuthError.bind(this));
|
|
|
|
|
this.socket.on("disconnect", this._handleSocketDisconnect.bind(this));
|
|
|
|
|
this.socket.io.on("reconnect", this._handleSocketReconnect.bind(this));
|
|
|
|
|
|
|
|
|
|
this.state = "online";
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error);
|
|
|
|
|
this.state = "offline";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-16 17:27:39 +10:00
|
|
|
/**
|
|
|
|
|
* Send data to all connected peers
|
|
|
|
|
*
|
|
|
|
|
* @param {string} id - the id of the event to send
|
|
|
|
|
* @param {object} data
|
|
|
|
|
* @param {string} channel
|
|
|
|
|
*/
|
|
|
|
|
send(id, data, channel) {
|
|
|
|
|
for (let peer of Object.values(this.peers)) {
|
|
|
|
|
peer.connection.send({ id, data }, channel);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Join a party
|
|
|
|
|
*
|
|
|
|
|
* @param {string} partyId - the id of the party to join
|
|
|
|
|
* @param {string} password - the password of the party
|
|
|
|
|
*/
|
|
|
|
|
async joinParty(partyId, password) {
|
2020-07-20 20:54:21 +10:00
|
|
|
if (typeof partyId !== "string" || typeof password !== "string") {
|
|
|
|
|
console.error(
|
|
|
|
|
"Unable to join party: invalid party ID or password",
|
|
|
|
|
partyId,
|
|
|
|
|
password
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-16 17:27:39 +10:00
|
|
|
this._partyId = partyId;
|
|
|
|
|
this._password = password;
|
2020-11-26 11:09:48 +11:00
|
|
|
this.socket.emit("join party", partyId, password);
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_addPeer(id, initiator, sync) {
|
|
|
|
|
try {
|
|
|
|
|
const connection = new Connection({
|
|
|
|
|
initiator,
|
|
|
|
|
trickle: true,
|
|
|
|
|
config: { iceServers: this._iceServers },
|
|
|
|
|
});
|
|
|
|
|
if (initiator) {
|
|
|
|
|
connection.createDataChannel("map", { iceServers: this._iceServers });
|
|
|
|
|
connection.createDataChannel("token", { iceServers: this._iceServers });
|
|
|
|
|
}
|
|
|
|
|
const peer = { id, connection, initiator, sync };
|
|
|
|
|
|
2020-07-17 15:39:45 +10:00
|
|
|
function sendPeer(id, data, channel) {
|
|
|
|
|
peer.connection.send({ id, data }, channel);
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSignal(signal) {
|
|
|
|
|
this.socket.emit("signal", JSON.stringify({ to: peer.id, signal }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleConnect() {
|
|
|
|
|
this.emit("connect", { peer, reply: sendPeer });
|
|
|
|
|
if (peer.sync) {
|
|
|
|
|
peer.connection.send({ id: "sync" });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDataComplete(data) {
|
|
|
|
|
if (data.id === "close") {
|
|
|
|
|
// Close connection when signaled to close
|
|
|
|
|
peer.connection.destroy();
|
|
|
|
|
}
|
|
|
|
|
this.emit("data", {
|
|
|
|
|
peer,
|
|
|
|
|
id: data.id,
|
|
|
|
|
data: data.data,
|
|
|
|
|
reply: sendPeer,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDataProgress({ id, count, total }) {
|
|
|
|
|
this.emit("dataProgress", { peer, id, count, total, reply: sendPeer });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleTrack(track, stream) {
|
|
|
|
|
this.emit("trackAdded", { peer, track, stream });
|
|
|
|
|
track.addEventListener("mute", () => {
|
|
|
|
|
this.emit("trackRemoved", { peer, track, stream });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleClose() {
|
|
|
|
|
this.emit("disconnect", { peer });
|
2020-10-27 16:02:20 +11:00
|
|
|
if (peer.id in this.peers) {
|
|
|
|
|
peer.connection.destroy();
|
|
|
|
|
this.peers = omit(this.peers, [peer.id]);
|
|
|
|
|
}
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleError(error) {
|
2020-10-28 10:10:27 +11:00
|
|
|
console.error(error);
|
2020-07-16 17:27:39 +10:00
|
|
|
this.emit("error", { peer, error });
|
2020-10-27 16:02:20 +11:00
|
|
|
if (peer.id in this.peers) {
|
|
|
|
|
peer.connection.destroy();
|
|
|
|
|
this.peers = omit(this.peers, [peer.id]);
|
2020-10-27 13:42:23 +11:00
|
|
|
}
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peer.connection.on("signal", handleSignal.bind(this));
|
|
|
|
|
peer.connection.on("connect", handleConnect.bind(this));
|
|
|
|
|
peer.connection.on("dataComplete", handleDataComplete.bind(this));
|
|
|
|
|
peer.connection.on("dataProgress", handleDataProgress.bind(this));
|
|
|
|
|
peer.connection.on("track", handleTrack.bind(this));
|
|
|
|
|
peer.connection.on("close", handleClose.bind(this));
|
|
|
|
|
peer.connection.on("error", handleError.bind(this));
|
|
|
|
|
|
|
|
|
|
this.peers[id] = peer;
|
|
|
|
|
} catch (error) {
|
2020-10-27 10:26:55 +11:00
|
|
|
logError(error);
|
2020-07-16 17:27:39 +10:00
|
|
|
this.emit("error", { error });
|
2020-10-27 13:42:23 +11:00
|
|
|
this.emit("disconnected");
|
2020-10-27 15:03:50 +11:00
|
|
|
for (let peer of Object.values(this.peers)) {
|
|
|
|
|
peer.connection && peer.connection.destroy();
|
|
|
|
|
}
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handlePartyMemberJoined(id) {
|
|
|
|
|
this._addPeer(id, false, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handlePartyMemberLeft(id) {
|
|
|
|
|
if (id in this.peers) {
|
|
|
|
|
this.peers[id].connection.destroy();
|
|
|
|
|
delete this.peers[id];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleJoinedParty(otherIds) {
|
2020-11-21 16:01:37 +11:00
|
|
|
for (let i = 0; i < otherIds.length; i++) {
|
|
|
|
|
const id = otherIds[i];
|
2020-07-16 17:27:39 +10:00
|
|
|
// Send a sync request to the first member of the party
|
2020-11-21 16:01:37 +11:00
|
|
|
const sync = i === 0;
|
2020-07-16 17:27:39 +10:00
|
|
|
this._addPeer(id, true, sync);
|
|
|
|
|
}
|
|
|
|
|
this.emit("authenticationSuccess");
|
|
|
|
|
this.emit("connected");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleSignal(data) {
|
2020-11-21 16:15:04 +11:00
|
|
|
const { from, signal } = data;
|
2020-07-16 17:27:39 +10:00
|
|
|
if (from in this.peers) {
|
|
|
|
|
this.peers[from].connection.signal(signal);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleAuthError() {
|
|
|
|
|
this.emit("authenticationError");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleUnload() {
|
|
|
|
|
for (let peer of Object.values(this.peers)) {
|
|
|
|
|
peer.connection.send({ id: "close" });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleSocketDisconnect() {
|
|
|
|
|
this.emit("disconnected");
|
2020-10-27 15:03:50 +11:00
|
|
|
for (let peer of Object.values(this.peers)) {
|
|
|
|
|
peer.connection && peer.connection.destroy();
|
|
|
|
|
}
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleSocketReconnect() {
|
2020-08-28 20:21:03 +10:00
|
|
|
if (this._partyId) {
|
|
|
|
|
this.joinParty(this._partyId, this._password);
|
|
|
|
|
}
|
2020-07-16 17:27:39 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Session;
|