UNPKG

@heyvr/sdk-arena

Version:

The SDK for heyVR's arena system.

365 lines (364 loc) 15.5 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import Peer, { PeerError } from "peerjs"; import { Arena } from "./Arena"; import { BaseLobby } from "./BaseLobby"; export class LobbyP2P extends BaseLobby { constructor(lobby) { super(); this.lobby = lobby; this.peer = new Peer(Math.random().toString(36).substring(2, 7).split('').sort(() => Math.random() - 0.5).join('').toUpperCase(), { host: `p2p.${Arena.tld()}`, port: 443, secure: true, config: { iceServers: [ { urls: 'stun:ice.heyvr.io:5002' }, { urls: 'turn:ice.heyvr.io:5002', username: 'heyvr', credential: 'heyvr' } ] } }); this.peer.on('open', id => { Arena.log(`Connected to PeerJS server, your peer ID is: "${id}"`); this.host = id; this.dispatch('serverConnected', id); }); this.peer.on('error', e => { Arena.log(`Server error: ${e.message}`); this.dispatch('serverError', e); this.destroy(); }); this.peer.on('connection', con => { Arena.log(`Connected to remote peer: ${con.peer}, Host: ${this.host}`); if ((this.target || this.con) && con.peer !== this.target) { Arena.log(`Dropped incoming connection from ${con.peer}, already connected to ${this.target}`); const attempts = setInterval(() => { var _a; if ('open' === ((_a = con.dataChannel) === null || _a === void 0 ? void 0 : _a.readyState)) { con.send({ type: 'event', payload: 'refused' }); clearInterval(attempts); } }, 100); return; } undefined === this.con && (this.con = con); undefined === this.target && (this.target = con.peer); con.on('close', () => this.dispatch('peerDisconnected', false)); con.on('error', e => this.dispatch('peerError', e)); con.on('data', this.registerDataCallbacks.bind(this)); this.dispatch('peerConnected', con); this.disconnectSafely(); }); this.peer.on('disconnected', () => this.dispatch('serverDisconnected', null)); LobbyP2P.instance = this; } get actions() { return { listPlayers: () => { return new Promise(resolve => resolve([this.username, this.opponent].filter(p => undefined !== p))); }, disconnect: () => { var _a; (_a = this.con) === null || _a === void 0 ? void 0 : _a.close(); this.con = undefined; Arena.log("Disconnected from the remote peer.", "warn"); }, reconnect: () => { return new Promise((resolve, reject) => { if (!this.target) { return reject(new Error("You haven't connected to any peer yet.")); } if (this.con) { return reject(new Error("Can't reconnect, you're already connected.")); } this.peer.reconnect(); this.peer.on('open', () => { const con = this.peer.connect(this.target); con.on('open', () => { this.con = con; resolve(this); this.dispatch('peerConnected', this.con); Arena.log(`Reconnected to remote peer.`); }); con.on('error', e => reject(new Error(e.message))); }); }); }, abandon: () => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; try { (_a = this.lobby) === null || _a === void 0 ? void 0 : _a.actions.destroy(); if ('open' === ((_c = (_b = this.con) === null || _b === void 0 ? void 0 : _b.dataChannel) === null || _c === void 0 ? void 0 : _c.readyState)) { yield ((_d = this.con) === null || _d === void 0 ? void 0 : _d.send({ type: 'event', payload: 'goodbye' })); (_e = this.con) === null || _e === void 0 ? void 0 : _e.close(); } } catch (e) { Arena.log(`Error during abandon: ${e.message}`, 'error'); } this.destroy(); Arena.log(`Abandoned the P2P lobby.`, "warn"); }) }; } get communications() { return { chat: (message) => { return new Promise(resolve => { Arena.log(`Sending message: "${message}"`); this.resolveConnection() .then(con => con.send({ type: 'message', payload: message })) .then(() => resolve(message)); }); }, call: () => { return new Promise((resolve, reject) => { Arena.log(`Staring a call...`); new MediaDevices() .getUserMedia() .then(stream => { if (!this.target) { return reject(new Error("Can't start a call before connecting to a peer.")); } this.media = this.peer.call(this.target, stream); this.media.on('error', reject); Arena.log(`Call made to the peer: "${this.target}"`); resolve(this.media); }) .catch(reject); }); }, onCall: (callback) => { this.peer.on('call', media => { Arena.log(`Received a call from: "${media.peer}"`); callback(stream => { this.media = media; media.answer(stream); Arena.log(`Call accepted from: "${media.peer}"`); }, () => { media.close(); Arena.log(`Call declined from: "${media.peer}"`, "warn"); }); }); }, hangUp: () => { return new Promise((resolve, reject) => { if (this.media) { this.media.close(); Arena.log(`Hung up a call from: "${this.media.peer}"`); this.media = undefined; resolve(); } else { Arena.log(`Can't hang up, not on a call.`, "error"); reject(); } }); }, onMessage: (callback) => { this.resolveConnection().then(con => con.on('data', (data) => { if ('message' === data.type) { callback(data.payload); Arena.log(`Received message: "${data.payload}"`); } })); } }; } get data() { return { send: (data) => { var _a; data && ((_a = this.con) === null || _a === void 0 ? void 0 : _a.send({ type: 'data', payload: data })); }, onReceive: (callback) => { this.on('dataReceived', callback); }, }; } get events() { return { onConnect: (callback) => { this.on('peerConnected', () => callback()); }, onMatchFound: (callback) => { var _a; (_a = this.lobby) === null || _a === void 0 ? void 0 : _a.data.onReceiveAll((data) => __awaiter(this, void 0, void 0, function* () { if (!data.key) { return; } if ('con_req' === data.key) { this.username = this.lobby.session.username; this.opponent = data.guest; Arena.log(`Received a request to connect to remote peer ${data.value}`); this.lobby.data.sendToAll({ key: 'match_found', value: data.value, host: this.username, guest: data.guest }); yield this.connect(data.value); this.lobby.actions.destroy(); callback(this.opponent); } if ('match_found' === data.key) { Arena.log(`Match Found, Target ID: ${data.value}`); !this.opponent && (this.opponent = data.host); !this.username && (this.username = data.guest); callback(this.opponent); } })); }, onDisconnect: (callback) => { this.on('peerDisconnected', callback); }, onAbandon: (callback) => { this.on('peerAbandoned', callback); }, onError: (callback) => { this.on('peerRefused', () => callback(new PeerError('peer-unavailable', "Can't establish connection, the match is no longer available."), true)); this.on('peerError', e => callback(e, false)); this.on('serverError', e => callback(e, true)); }, }; } get session() { var _a, _b, _c; return { username: (_a = this.username) !== null && _a !== void 0 ? _a : 'Unknown', opponent: (_b = this.opponent) !== null && _b !== void 0 ? _b : "Unknown", isConnected: undefined !== this.con && "open" === ((_c = this.con.dataChannel) === null || _c === void 0 ? void 0 : _c.readyState), }; } connect(target) { return new Promise((resolve, reject) => { if (this.con) { return reject(new Error(`You're already connected to a target with the ID of "${this.con.peer}".`)); } this.on('peerConnecting', con => { this.con = con; this.con.on('data', this.registerDataCallbacks.bind(this)); this.con.on('open', () => { Arena.log(`Host is now connected to : "${target}"`); this.dispatch('peerConnected', this.con); this.target = target; this.disconnectSafely(); resolve(); }); this.con.on('close', () => { this.dispatch('peerDisconnected', false); Arena.log(`Remote peer has been disconnected.`, "warn"); this.con = undefined; }); this.con.on('error', e => { Arena.log(`Connection error: ${e.message}`, "error"); this.dispatch('peerError', e); reject(); }); }); Arena.log(`Connecting to peer: "${target}"`); if (this.host) { Arena.log(`Already connected to PeerJS server, connecting to: "${target}"`); this.dispatch('peerConnecting', this.peer.connect(target)); } else { Arena.log(`Not connected to PeerJS server, will connect to peer "${target}" after connecting`); this.peer.on('open', () => { Arena.log(`Completed connection to PeerJS server, connecting to "${target}"`); this.dispatch('peerConnecting', this.peer.connect(target)); }); } }); } destroy() { this.peer.destroy(); this.con = undefined; this.host = undefined; this.media = undefined; this.target = undefined; this.opponent = undefined; this.username = undefined; this.lobby = undefined; LobbyP2P.instance = undefined; } disconnectSafely() { const closures = setInterval(() => { var _a; if (!this.con) { return clearInterval(closures); } if ('open' === ((_a = this.con.dataChannel) === null || _a === void 0 ? void 0 : _a.readyState)) { Arena.log("Closed the connection to P2P server."); this.peer.disconnect(); clearInterval(closures); } }, 100); } getID() { return new Promise(resolve => { if (this.host) { Arena.log(`Host's peer ID is already resolved: "${this.host}"`); resolve(this.host); } else { this.peer.on('open', id => { Arena.log(`Finished resolving host's peer ID: "${this.host}"`); resolve(id); }); } }); } static getInstance() { if (!LobbyP2P.instance) { throw new Error("You're not connected to any lobbies at the moment."); } return LobbyP2P.instance; } registerDataCallbacks(data) { var _a; if ('data' === data.type) { this.dispatch('dataReceived', data.payload); } if ('event' !== data.type) { return; } if ('refused' === data.payload) { Arena.log(`Connection has been refused by the target, abandoning match.`, "error"); this.dispatch('peerRefused', true); (_a = this.con) === null || _a === void 0 ? void 0 : _a.close(); this.con = this.target = this.opponent = undefined; } if ('goodbye' === data.payload) { this.dispatch('peerAbandoned', true); } } resolveConnection() { return new Promise(resolve => { if (this.con) { return resolve(this.con); } this.peer.on('connection', resolve); this.on('peerConnected', resolve); }); } } LobbyP2P.events = [ 'peerConnecting', 'peerConnected', 'peerDisconnected', 'peerAbandoned', 'peerRefused', 'peerError', 'dataReceived', 'serverConnected', 'serverDisconnected', 'serverError' ]; LobbyP2P.instance = undefined;