gun/lib/wsp/Peer.js

191 lines
4.0 KiB
JavaScript

/* eslint-disable no-underscore-dangle */
'use strict';
var WebSocket = require('ws');
var Emitter = require('events');
var util = require('util');
/**
* Calculates backoff instances.
* @param {Object} [options] - Override the default settings.
* @param {Object} options.time=50 - Initial backoff time.
* @param {Object} options.factor=2 - How much to multiply the time by.
* @param {Object} options.max=1min - Maximum backoff time.
* @class
*/
function Backoff (options) {
this.options = options || {};
// Sets the initial backoff settings.
this.reset();
}
/**
* Increments the time by the factor.
* @return {Number} - The next backoff time.
*/
Backoff.prototype.next = function () {
var next = this.time * this.factor;
if (next > this.max) {
this.time = this.max;
return this.max;
}
this.time = next;
return this.time;
};
/**
* Resets the backoff state to it's original condition.
* @return {Backoff} - The context.
*/
Backoff.prototype.reset = function () {
var options = this.options;
this.time = options.time || 50;
this.factor = options.factor || 2;
this.max = options.max || 1 * 60 * 1000;
return this;
};
/**
* Schedules the next connection, according to the backoff.
* @param {Peer} peer - A peer instance.
* @return {Timeout} - The timeout value from `setTimeout`.
*/
function scheduleReconnect (peer) {
var backoff = peer.backoff;
var time = backoff.time;
backoff.next();
var reconnect = peer.connect.bind(peer);
return setTimeout(reconnect, time);
}
/**
* Handles reconnections and defers messages until the socket is ready.
* @param {String} url - The address to connect to.
* @param {Object} [options] - Override how the socket is managed.
* @param {Object} options.backoff - Backoff options (see the constructor).
* @class
*/
function Peer (url, options) {
if (!(this instanceof Peer)) {
return new Peer(url, options);
}
// Extend EventEmitter.
Emitter.call(this);
this.setMaxListeners(Infinity);
this.options = options || {};
// Messages sent before the socket is ready.
this.deferredMsgs = [];
this.url = Peer.formatURL(url);
this.backoff = new Backoff(this.options.backoff);
// Set up the websocket.
this.connect();
var peer = this;
var reconnect = scheduleReconnect.bind(null, peer);
// Handle reconnection.
this.on('close', reconnect);
this.on('error', function (error) {
if (error.code === 'ECONNREFUSED') {
reconnect();
}
});
// Send deferred messages.
this.on('open', function () {
peer.drainQueue();
peer.backoff.reset();
});
}
/**
* Turns http URLs into WebSocket URLs.
* @param {String} url - The url to format.
* @return {String} - A correctly formatted WebSocket URL.
*/
Peer.formatURL = function (url) {
// Works for `https` and `wss` URLs, too.
return url.replace(/^http/, 'ws');
};
util.inherits(Peer, Emitter);
var API = Peer.prototype;
/**
* Attempts a websocket connection.
* @return {WebSocket} - The new websocket instance.
*/
API.connect = function () {
var url = this.url;
// Open a new websocket.
var socket = new WebSocket(url, this.options.wsc.protocols, this.options.wsc);
// Re-use the previous listeners.
socket._events = this._events;
this.socket = socket;
return socket;
};
/**
* Sends all the messages in the deferred queue.
* @return {Peer} - The context.
*/
API.drainQueue = function () {
var peer = this;
this.deferredMsgs.forEach(function (msg) {
peer.send(msg);
});
// Reset the queue.
this.deferredMsgs = [];
return this;
};
/**
* Send data through the socket, or add it to a queue
* of deferred messages if it's not ready yet.
* @param {Mixed} msg - String, or anything that JSON can handle.
* @return {Peer} - The context.
*/
API.send = function (msg) {
var socket = this.socket;
var state = socket.readyState;
var ready = socket.OPEN;
// Make sure it's a string.
if (typeof msg !== 'string') {
msg = JSON.stringify(msg);
}
// Make sure the socket is ready.
if (state === ready) {
socket.send(msg);
} else {
this.deferredMsgs.push(msg);
}
return this;
};
module.exports = Peer;