@mothepro/fancy-p2p
Version:
A quick and efficient way to form p2p groups in the browser
127 lines • 6.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fancy_emitter_1 = require("fancy-emitter");
const Client_js_1 = __importDefault(require("./Client.js"));
const Peer_js_1 = __importStar(require("./Peer.js"));
const Signaling_js_1 = __importDefault(require("./Signaling.js"));
const random_js_1 = __importDefault(require("../util/random.js"));
class default_1 {
constructor({ name, stuns, lobby, server: { address, version }, fallback = false, retries = 0, timeout = -1 }) {
this.state = 0 /* OFFLINE */;
/** Activated when the state changes, Cancels when finalized, Deactivates when error is throw. */
this.stateChange = new fancy_emitter_1.Emitter(newState => this.state = newState);
/** The peers who's connections are still open */
this.peers = [];
/**
* Generates a random number in [0,1), same as Math.random()
* If `isInt` is true, then an integer in range [-2 ** 31, 2 ** 31) is generated instead.
*
* `state` must be `State.READY`.
*/
this.random = (isInt = false) => this.assert(3 /* READY */) &&
isInt
? this.rng.next().value
: 0.5 + this.rng.next().value / 4294967295 /* INT */;
/**
* Propose a group with other clients connected to this lobby.
*
* `state` must be `State.LOBBY`.
*/
this.proposeGroup = (...members) => this.assert(1 /* LOBBY */) &&
this.server.proposeGroup(...members);
/**
* Whether a group with the following memebers has been proposed or answered.
*
* `state` must be `State.LOBBY`.
*/
this.groupExists = (...members) => this.assert(1 /* LOBBY */) &&
this.server.groupExists(...members);
/**
* Send data to all connected peers.
*
* `state` must be `State.READY`.
*/
this.broadcast = (data, includeSelf = true) => {
this.assert(3 /* READY */);
for (const peer of this.peers)
if (peer.message.isAlive && (includeSelf || !peer.isYou))
peer.send(data);
};
this.server = new Signaling_js_1.default(address, lobby, name, version);
// Bind Emitters
this.lobbyConnection = this.server.connection;
this.bindServerState(stuns, retries, timeout, fallback);
this.stateChange.on(() => { })
.finally(this.server.stateChange.cancel) // close server when state is done
.catch(() => { }); // handle elsewhere
}
// TODO allow READY state even tho the state doesn't change until the next tick
assert(valid, message = `Expected state to be ${valid} but was ${this.state}`) {
if (this.state != valid)
throw Error(message);
return true;
}
async bindServerState(stuns, retries, timeout, fallback) {
try {
for await (const state of this.server.stateChange)
switch (state) {
case 1 /* READY */:
this.stateChange.activate(1 /* LOBBY */);
break;
case 2 /* FINALIZED */:
this.stateChange.activate(2 /* LOADING */);
this.rng = random_js_1.default(this.server.code);
const members = [
...this.server.members,
{ id: this.server.myId }
];
// sort the IDs then use a consistent fisher yates shuffle on them
members.sort(({ id: firstID }, { id: secondID }) => firstID - secondID);
for (let i = members.length - 1; i > 0; i--) {
const j = Math.abs(this.rng.next().value) % i;
[members[j], members[i]] = [members[i], members[j]];
}
for (const client of members)
this.peers.push(client instanceof Client_js_1.default
? new Peer_js_1.default(stuns, client, retries, timeout, fallback ? this.server : undefined)
: new Peer_js_1.MockPeer(this.server.self.name));
this.server.peersForFallbackLogging = this.peers;
// Every connection is connected successfully, ready up & close connection with server
// TODO, does every promise need to resolve? Or can broken peers just be kicked?
const peerConnectionStatuses = await Promise.all(this.peers.map(peer => peer.ready));
this.server.stateChange.activate(fallback && !peerConnectionStatuses.every(isUsingDirectConnection => isUsingDirectConnection)
? 3 /* FALLBACK */
: 0 /* CLOSED */);
this.stateChange.activate(3 /* READY */);
}
this.assert(3 /* READY */, 'Connection with server closed prematurely');
}
catch (err) {
this.stateChange.deactivate(err);
}
}
}
exports.default = default_1;
//# sourceMappingURL=P2P.js.map