mudb
Version:
Real-time database for multiplayer games
287 lines • 10.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const system_1 = require("../../scheduler/system");
const logger_1 = require("../../logger");
const socket_1 = require("../socket");
const rtc_1 = require("./rtc");
const error_1 = require("../../util/error");
const error = error_1.makeError('socket/webrtc/server');
const isBrowser = typeof self !== undefined && !!self && self['Object'] === Object;
function noop() { }
class MuRTCSocketClient {
constructor(sessionId, pc, answerOpts, signal, connection, serverClose, logger) {
this._state = socket_1.MuSocketState.INIT;
this._signal = noop;
this._onMessage = noop;
this._onClose = noop;
this._serverClose = noop;
this._reliableChannel = null;
this._unreliableChannel = null;
this.sessionId = sessionId;
this._pc = pc;
this._answerOpts = answerOpts;
this._signal = (data) => {
signal(data, sessionId);
};
this._serverClose = serverClose;
this._logger = logger;
pc.onicecandidate = ({ candidate }) => {
if (this._state === socket_1.MuSocketState.INIT) {
if (candidate) {
this._signal(candidate.toJSON());
}
}
};
pc.oniceconnectionstatechange = () => {
if (this._state === socket_1.MuSocketState.INIT) {
if (pc.iceConnectionState === 'failed' || pc.iceConnectionState === 'closed') {
logger.error(`ICE connection ${pc.iceConnectionState}`);
this.close();
}
else {
logger.log(`${sessionId} ICE connection state: ${pc.iceConnectionState}`);
}
}
};
pc.onconnectionstatechange = () => {
if (this._state !== socket_1.MuSocketState.CLOSED) {
if (pc.connectionState === 'failed') {
logger.error(`connection failed`);
this.close();
}
}
};
pc.ondatachannel = ({ channel }) => {
if (this._state !== socket_1.MuSocketState.INIT) {
return;
}
if (!channel) {
this._logger.error('`channel` property is missing`');
this.close();
return;
}
channel.onopen = () => {
if (this._state === socket_1.MuSocketState.CLOSED) {
return;
}
this._logger.log(`${channel.label} channel is open`);
if (this._reliableChannel && this._unreliableChannel &&
this._reliableChannel.readyState === 'open' &&
this._unreliableChannel.readyState === 'open') {
connection(this);
}
};
channel.onerror = (e) => {
if (this._state !== socket_1.MuSocketState.CLOSED) {
this.close(e);
}
};
channel.onclose = () => {
if (this._state !== socket_1.MuSocketState.CLOSED) {
this.close(`${channel.label} channel closed unexpectedly`);
}
};
if (/unreliable/.test(channel.label)) {
this._unreliableChannel = channel;
this._unreliableChannel.binaryType = 'arraybuffer';
this._unreliableChannel.onmessage = ({ data }) => {
if (typeof data !== 'string') {
this._onMessage(new Uint8Array(data).subarray(0), true);
}
else {
this._onMessage(data, true);
}
};
}
else if (/reliable/.test(channel.label)) {
this._reliableChannel = channel;
this._reliableChannel.binaryType = 'arraybuffer';
this._reliableChannel.onmessage = ({ data }) => {
if (typeof data !== 'string') {
this._onMessage(new Uint8Array(data).subarray(0), false);
}
else {
this._onMessage(data, false);
}
};
}
};
}
state() { return this._state; }
handleSignal(data) {
if (this._state !== socket_1.MuSocketState.INIT) {
return;
}
if ('sdp' in data) {
this._pc.setRemoteDescription(data)
.then(() => {
this._pc.createAnswer(this._answerOpts)
.then((answer) => {
this._pc.setLocalDescription(answer)
.then(() => {
this._signal(answer);
}).catch((e) => this.close(e));
}).catch((e) => this.close(e));
}).catch((e) => this.close(e));
}
else if ('candidate' in data) {
this._pc.addIceCandidate(data).catch((e) => this.close(e));
}
else {
this._logger.error(`invalid negotiation message: ${data}`);
}
}
open(spec) {
if (this._state !== socket_1.MuSocketState.INIT) {
throw error(`socket had been opened`);
}
this._state = socket_1.MuSocketState.OPEN;
this._onMessage = spec.message;
this._onClose = spec.close;
spec.ready();
}
send(data, unreliable) {
if (this._state !== socket_1.MuSocketState.OPEN) {
return;
}
if (unreliable && this._unreliableChannel) {
this._unreliableChannel.send(data);
}
else if (this._reliableChannel) {
this._reliableChannel.send(data);
}
}
close(e) {
if (this._state === socket_1.MuSocketState.CLOSED) {
return;
}
if (e) {
this._logger.exception(e);
}
this._state = socket_1.MuSocketState.CLOSED;
this._pc.close();
this._pc.onicecandidate = null;
this._pc.oniceconnectionstatechange = null;
this._pc.onconnectionstatechange = null;
this._pc.ondatachannel = null;
if (this._reliableChannel) {
this._reliableChannel.onopen = null;
this._reliableChannel.onmessage = null;
this._reliableChannel.onerror = null;
this._reliableChannel.onclose = null;
this._reliableChannel = null;
}
if (this._unreliableChannel) {
this._unreliableChannel.onopen = null;
this._unreliableChannel.onmessage = null;
this._unreliableChannel.onerror = null;
this._unreliableChannel.onclose = null;
this._unreliableChannel = null;
}
this._pc = null;
this._onClose();
this._serverClose();
}
reliableBufferedAmount() {
return 0;
}
unreliableBufferedAmount() {
return 0;
}
}
exports.MuRTCSocketClient = MuRTCSocketClient;
class MuRTCSocketServer {
constructor(spec) {
this._state = socket_1.MuSocketServerState.INIT;
this.clients = [];
this._onConnection = noop;
this._onClose = noop;
this._pendingClients = [];
if (isBrowser && !rtc_1.browserRTC()) {
throw error(`browser doesn't support WebRTC`);
}
if (!isBrowser && !spec.wrtc) {
throw error(`specify WebRTC binding via spec.wrtc`);
}
this.wrtc = rtc_1.browserRTC() || spec.wrtc;
this._signal = spec.signal;
this._pcConfig = spec.pcConfig || {
iceServers: [
{ urls: 'stun:global.stun.twilio.com:3478' },
],
};
this._pcConfig.sdpSemantics = 'unified-plan';
this._answerOpts = spec.answerOpts || {};
this._scheduler = spec.scheduler || system_1.MuSystemScheduler;
this._logger = spec.logger || logger_1.MuDefaultLogger;
}
state() { return this._state; }
start(spec) {
if (this._state !== socket_1.MuSocketServerState.INIT) {
throw error(`attempt to start when server is ${this._state === socket_1.MuSocketServerState.RUNNING ? 'running' : 'shut down'}`);
}
this._scheduler.setTimeout(() => {
if (this._state !== socket_1.MuSocketServerState.INIT) {
return;
}
this._state = socket_1.MuSocketServerState.RUNNING;
this._onConnection = spec.connection;
this._onClose = spec.close;
spec.ready();
}, 0);
}
handleSignal(packet) {
if (this._state !== socket_1.MuSocketServerState.RUNNING) {
return;
}
function findClient(sessionId, clients) {
for (let i = clients.length - 1; i >= 0; --i) {
if (clients[i].sessionId === sessionId) {
return clients[i];
}
}
return null;
}
try {
const data = JSON.parse(packet);
if (!data.sid) {
this._logger.error(`no session id in negotiation message`);
return;
}
const sessionId = data.sid;
delete data.sid;
let client = findClient(sessionId, this._pendingClients);
if (!client) {
client = new MuRTCSocketClient(sessionId, new this.wrtc.RTCPeerConnection(this._pcConfig), this._answerOpts, this._signal, () => {
if (client) {
this._onConnection(client);
this.clients.push(client);
this._pendingClients.splice(this._pendingClients.indexOf(client), 1);
}
}, () => {
if (client) {
this.clients.splice(this.clients.indexOf(client), 1);
}
}, this._logger);
this._pendingClients.push(client);
}
client.handleSignal(data);
}
catch (e) {
this._logger.exception(e);
}
}
close() {
if (this._state === socket_1.MuSocketServerState.SHUTDOWN) {
return;
}
this._state = socket_1.MuSocketServerState.SHUTDOWN;
for (let i = 0; i < this.clients.length; ++i) {
this.clients[i].close();
}
this.clients.length = 0;
this._onClose();
}
}
exports.MuRTCSocketServer = MuRTCSocketServer;
//# sourceMappingURL=server.js.map