@graffy/client
Version:
Graffy client library for the browser.
192 lines (150 loc) • 5.01 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = Socket;
var _keys = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/keys"));
var _setTimeout2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/set-timeout"));
var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice"));
var _now = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/date/now"));
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat"));
var _bind = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/bind"));
var _common = require("@graffy/common");
var _debug = _interopRequireDefault(require("debug"));
var _context;
var log = (0, _debug.default)('graffy:client:socket'); // eslint-disable-next-line no-console
log.log = (0, _bind.default)(_context = console.log).call(_context, console);
var MIN_DELAY = 1000;
var MAX_DELAY = 300000;
var DELAY_GROWTH = 1.5;
var INTERVAL = 2000;
var PING_TIMEOUT = 40000; // Make this greater than server interval.
var RESET_TIMEOUT = 10000;
function Socket(url, _temp) {
var _ref = _temp === void 0 ? {} : _temp,
onUnhandled = _ref.onUnhandled,
onStatusChange = _ref.onStatusChange;
var handlers = {};
var buffer = [];
var isOpen = false;
var isConnecting = false;
var socket;
var lastAlive;
var lastAttempt;
var attempts = 0;
var connectTimer;
var aliveTimer;
function start(params, callback) {
var _context2;
var id = (0, _common.makeId)();
var request = (0, _concat.default)(_context2 = [id]).call(_context2, params);
handlers[id] = {
request: request,
callback: callback
};
if (isAlive()) send(request);
return id;
}
function stop(id, params) {
var _context3;
delete handlers[id];
if (params) send((0, _concat.default)(_context3 = [id]).call(_context3, params));
}
function connect() {
log('Trying to connect to', url);
isOpen = false;
isConnecting = true;
lastAttempt = (0, _now.default)();
attempts++;
socket = new WebSocket(url);
socket.onmessage = received;
socket.onerror = closed;
socket.onclose = closed;
socket.onopen = opened;
}
function received(event) {
var _deserialize = (0, _common.deserialize)(event.data),
id = _deserialize[0],
data = (0, _slice.default)(_deserialize).call(_deserialize, 1);
setAlive();
if (id === ':ping') {
send([':pong']);
} else if (handlers[id]) {
var _handlers$id;
(_handlers$id = handlers[id]).callback.apply(_handlers$id, data);
} else {
var _context4;
// We received an unexpected push.
onUnhandled && onUnhandled.apply(void 0, (0, _concat.default)(_context4 = [id]).call(_context4, data));
}
}
function closed(_event) {
log('Closed');
if (isOpen && onStatusChange) onStatusChange(false);
var wasOpen = isOpen;
isOpen = false;
isConnecting = false;
lastAttempt = (0, _now.default)();
if (wasOpen && !attempts) {
// Quick reconnect path if we previously had a stable connection.
connect();
return;
}
maybeConnect();
}
function maybeConnect() {
var connDelay = lastAttempt + Math.min(MAX_DELAY, MIN_DELAY * Math.pow(DELAY_GROWTH, attempts)) - (0, _now.default)();
log('Will reconnect in', connDelay, 'ms');
if (connDelay <= 0) {
connect();
return;
}
clearTimeout(connectTimer);
connectTimer = (0, _setTimeout2.default)(connect, connDelay);
}
function opened() {
log('Connected', buffer.length, (0, _keys.default)(handlers).length);
isOpen = true;
isConnecting = false;
lastAttempt = (0, _now.default)();
setAlive();
if (onStatusChange) onStatusChange(true);
for (var id in handlers) {
send(handlers[id].request);
}
while (buffer.length) {
send(buffer.shift());
}
}
function setAlive() {
lastAlive = (0, _now.default)();
log('Set alive', lastAlive - lastAttempt);
if (lastAlive - lastAttempt > RESET_TIMEOUT) attempts = 0;
}
function isAlive() {
log('Liveness check', isOpen ? 'open' : 'closed', (0, _now.default)() - lastAlive);
clearTimeout(aliveTimer);
aliveTimer = (0, _setTimeout2.default)(isAlive, INTERVAL);
if (!isOpen) {
if (!isConnecting) maybeConnect();
return false;
}
if ((0, _now.default)() - lastAlive < PING_TIMEOUT) return true;
socket.close();
return false;
}
function send(req) {
if (isAlive()) {
socket.send((0, _common.serialize)(req));
} else {
buffer.push(req);
}
}
connect();
aliveTimer = (0, _setTimeout2.default)(isAlive, INTERVAL);
return {
start: start,
stop: stop,
isAlive: isAlive
};
}
module.exports = exports.default;