gun/lib/wsp/Peer.js
Jesse Gibson 4cd2d434d1 Add websocket backoff/retry logic
New library handles websocket reconnection logic and queues messages
that were sent while offline.
2016-11-10 10:11:01 -07:00

177 lines
3.7 KiB
JavaScript

var WebSocket = require('ws');
/**
* 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.
* @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 () {
this.time *= this.factor;
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;
return this;
};
/**
* Create a websocket client and handle reconnect backoff logic.
* @param {String} url - A preformatted url (starts with ws://)
* @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);
}
this.options = options || {};
// Messages sent while offline.
this.offline = [];
this.url = Peer.formatURL(url);
this.backoff = new Backoff(this.options.backoff);
this.retry(url);
}
/**
* 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');
};
var API = Peer.prototype;
/**
* Attempts a websocket connection.
* @param {String} url - The websocket URL.
* @return {WebSocket} - The new websocket instance.
*/
API.retry = function () {
var url = this.url;
var socket = new WebSocket(url);
this.socket = socket;
this.retryOnDisconnect(socket);
this.sendOnConnection();
return socket;
};
/**
* Sends the messages that couldn't be sent before once
* the connection is open.
* @return {Peer} - The context.
*/
API.sendOnConnection = function () {
var peer = this;
var queue = this.offline;
var socket = this.socket;
// Wait for the socket to connect.
socket.once('open', function () {
queue.forEach(function (msg) {
socket.send(msg);
});
peer.offline = [];
});
return this;
};
/**
* Schedules the next retry, according to the backoff.
* @param {Peer} peer - A peer instance.
* @return {Timeout} - The timeout value from `setTimeout`.
*/
function schedule (peer) {
var backoff = peer.backoff;
var time = backoff.time;
backoff.next();
return setTimeout(function () {
var socket = peer.retry();
// Successfully reconnected? Reset the backoff.
socket.once('open', backoff.reset.bind(backoff));
}, time);
}
/**
* Attaches handlers to the socket, attempting reconnection
* when it's closed.
* @param {WebSocket} socket - The websocket instance to bind to.
* @return {WebSocket} - The same websocket.
*/
API.retryOnDisconnect = function (socket) {
var peer = this;
// Listen for socket close events.
socket.once('close', function () {
schedule(peer);
});
socket.on('error', function (error) {
if (error.code === 'ECONNREFUSED') {
schedule(peer);
}
});
return socket;
};
/**
* Send data through the socket, or add it to a queue
* of offline requests if it's not ready yet.
* @param {String} msg - The data to send.
* @return {Peer} - The context.
*/
API.send = function (msg) {
var socket = this.socket;
var state = socket.readyState;
var ready = socket.OPEN;
if (state === ready) {
socket.send(msg);
} else {
this.offline.push(msg);
}
return this;
};
module.exports = Peer;