@heyvr/sdk-arena
Version:
The SDK for heyVR's arena system.
365 lines (364 loc) • 15.5 kB
JavaScript
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;