UNPKG

4.1 kBJavaScriptView Raw
1/* eslint-disable no-underscore-dangle */
2'use strict';
3
4var WebSocket = require('ws');
5var Emitter = require('events');
6var util = require('util');
7
8/**
9 * Calculates backoff instances.
10 * @param {Object} [options] - Override the default settings.
11 * @param {Object} options.time=50 - Initial backoff time.
12 * @param {Object} options.factor=2 - How much to multiply the time by.
13 * @param {Object} options.max=1min - Maximum backoff time.
14 * @class
15 */
16function Backoff (options) {
17 this.options = options || {};
18
19 // Sets the initial backoff settings.
20 this.reset();
21}
22
23/**
24 * Increments the time by the factor.
25 * @return {Number} - The next backoff time.
26 */
27Backoff.prototype.next = function () {
28 var next = this.time * this.factor;
29
30 if (next > this.max) {
31 this.time = this.max;
32 return this.max;
33 }
34
35 this.time = next;
36
37 return this.time;
38};
39
40/**
41 * Resets the backoff state to it's original condition.
42 * @return {Backoff} - The context.
43 */
44Backoff.prototype.reset = function () {
45 var options = this.options;
46
47 this.time = options.time || 50;
48 this.factor = options.factor || 2;
49 this.max = options.max || 1 * 60 * 1000;
50
51 return this;
52};
53
54/**
55 * Schedules the next connection, according to the backoff.
56 * @param {Peer} peer - A peer instance.
57 * @return {Timeout} - The timeout value from `setTimeout`.
58 */
59function scheduleReconnect (peer) {
60 var backoff = peer.backoff;
61 var time = backoff.time;
62 backoff.next();
63
64 var reconnect = peer.connect.bind(peer);
65
66 return setTimeout(reconnect, time);
67}
68
69/**
70 * Handles reconnections and defers messages until the socket is ready.
71 * @param {String} url - The address to connect to.
72 * @param {Object} [options] - Override how the socket is managed.
73 * @param {Object} options.backoff - Backoff options (see the constructor).
74 * @class
75 */
76function Peer (url, options) {
77 if (!(this instanceof Peer)) {
78 return new Peer(url, options);
79 }
80
81 // Extend EventEmitter.
82 Emitter.call(this);
83 this.setMaxListeners(Infinity);
84
85 this.options = options || {};
86
87 // Messages sent before the socket is ready.
88 this.deferredMsgs = [];
89
90 this.url = Peer.formatURL(url);
91 this.backoff = new Backoff(this.options.backoff);
92
93 // Set up the websocket.
94 this.connect();
95
96 var peer = this;
97 var reconnect = scheduleReconnect.bind(null, peer);
98
99 // Handle reconnection.
100 this.on('close', reconnect);
101 this.on('error', function (error) {
102 if (error.code === 'ECONNREFUSED') {
103 reconnect();
104 }
105 });
106
107 // Send deferred messages.
108 this.on('open', function () {
109 peer.drainQueue();
110 peer.backoff.reset();
111 });
112
113}
114
115/**
116 * Turns http URLs into WebSocket URLs.
117 * @param {String} url - The url to format.
118 * @return {String} - A correctly formatted WebSocket URL.
119 */
120Peer.formatURL = function (url) {
121
122 // Works for `https` and `wss` URLs, too.
123 return url.replace(/^http/, 'ws');
124};
125
126util.inherits(Peer, Emitter);
127var API = Peer.prototype;
128
129/**
130 * Attempts a websocket connection.
131 * @return {WebSocket} - The new websocket instance.
132 */
133API.connect = function () {
134 var url = this.url;
135
136 // Open a new websocket.
137 var socket = new WebSocket(url, this.options.wsc.protocols, this.options.wsc);
138
139 // Re-use the previous listeners.
140 socket._events = this._events;
141
142 this.socket = socket;
143
144 return socket;
145};
146
147/**
148 * Sends all the messages in the deferred queue.
149 * @return {Peer} - The context.
150 */
151API.drainQueue = function () {
152 var peer = this;
153
154 this.deferredMsgs.forEach(function (msg) {
155 peer.send(msg);
156 });
157
158 // Reset the queue.
159 this.deferredMsgs = [];
160
161 return this;
162};
163
164/**
165 * Send data through the socket, or add it to a queue
166 * of deferred messages if it's not ready yet.
167 * @param {Mixed} msg - String, or anything that JSON can handle.
168 * @return {Peer} - The context.
169 */
170API.send = function (msg) {
171 var socket = this.socket;
172 var state = socket.readyState;
173 var ready = socket.OPEN;
174
175 // Make sure it's a string.
176 if (typeof msg !== 'string') {
177 msg = JSON.stringify(msg);
178 }
179
180 // Make sure the socket is ready.
181 if (state === ready) {
182 socket.send(msg);
183 } else {
184 this.deferredMsgs.push(msg);
185 }
186
187 return this;
188};
189
190module.exports = Peer;