@ch1/rpc-web-socket
Version:
JavaScript Remote Procedure Call (RPC) - Web Socket Adapter
152 lines (147 loc) • 4.83 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var rpc = require('@ch1/rpc');
/**
* This module uses `JSON.stringify` and `JSON.parse` for a number of reasons:
*
* - I'm too lazy to figure out an efficient `ArrayBuffer` approach, that said
* I can imagine it easy to make a hybrid...
* - Historically the library used them out of paranoia and sloth (detecting
* browser object clone support)
* - Because apparently `JSON` is actually reasonable:
* https://nolanlawson.com/2016/02/29/high-performance-web-worker-messages/
*/
function configureNativeSocketOnMethod(config) {
config.on = (callback) => {
const handler = (data) => {
callback(JSON.parse(data.data));
};
config.socket.addEventListener('message', handler);
return () => config.socket.removeEventListener('message', handler);
};
}
function configureWsSocketOnMethod(config) {
config.on = (callback) => {
const handler = (data) => {
callback(JSON.parse(data));
};
config.socket.on('message', handler);
return () => config.socket.removeEventListener('message', handler);
};
}
function configureOnEmit(config) {
if (!config.socket) {
throw new TypeError('rpc-web-socket: socket interface required');
}
if (typeof config.socket.on !== 'function') {
if (typeof config.socket.addEventListener !== 'function') {
throw new TypeError('rpc-web-socket: socket must have an on method');
}
configureNativeSocketOnMethod(config);
}
else {
configureWsSocketOnMethod(config);
}
if (typeof config.socket.send !== 'function') {
throw new TypeError('rpc-web-socket: socket must have a send method');
}
config.emit = (data) => {
config.socket.send(JSON.stringify(data));
};
}
function configureNativeSocket(config, remote, remoteDesc) {
let resolve;
let reject;
let rpc$$1;
const earlyDestroys = [];
const expose = {
onDestroy: (...args) => {
let destroyed = false;
const destroy = (reason) => {
destroyed = true;
desc.onDestroy(reason);
};
const desc = {
isValid: () => {
if (destroyed) {
return false;
}
else {
return true;
}
},
args,
onDestroy: reason => { },
};
earlyDestroys.push(desc);
return destroy;
},
ready: new Promise((res, rej) => {
resolve = res;
reject = rej;
}),
};
// @todo awkard... this will need a refactor
config.socket.addEventListener('close', () => {
if (rpc$$1) {
rpc$$1.destroy('rpc: web-socket closed');
}
else {
console.warn('rpc: web-socket error: rpc still not defined (close)');
}
});
// @todo awkard... this will need a refactor
config.socket.addEventListener('error', error => {
if (rpc$$1) {
rpc$$1.destroy('rpc: web-socket error: native: ' + error.message);
}
else {
console.warn('rpc: web-socket error: rpc still not defined (error)');
}
});
config.socket.addEventListener('open', () => {
rpc$$1 = rpc.create(config, remote, remoteDesc);
earlyDestroys.forEach(desc => {
if (desc.isValid()) {
desc.onDestroy = rpc$$1.onDestroy(...desc.args);
}
});
rpc$$1.ready
.then(() => {
Object.keys(rpc$$1).forEach(key => {
expose[key] = rpc$$1[key];
});
resolve();
})
.catch(reject);
});
return expose;
}
function configureWsSocket(config, remote, remoteDesc) {
const rpc$$1 = rpc.create(config, remote, remoteDesc);
const interval = setInterval(() => {
if (config.socket.isAlive === false) {
config.socket.terminate();
}
config.socket.isAlive = false;
config.socket.ping();
}, config.pingDelay || 10000);
config.socket.isAlive = true;
config.socket.on('pong', () => (config.socket.isAlive = true));
const destroy = rpc$$1.destroy;
rpc$$1.destroy = () => {
clearInterval(interval);
return destroy();
};
return rpc$$1;
}
function create(config, remote, remoteDesc) {
configureOnEmit(config);
if (typeof WebSocket !== 'undefined') {
return configureNativeSocket(config, remote, remoteDesc);
}
else {
return configureWsSocket(config, remote, remoteDesc);
}
}
exports.create = create;