mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00

since options have to be fetched higher anyway... Or rather - this options is outside of knowing gun options
191 lines
4.0 KiB
JavaScript
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;
|