1 |
|
2 | 'use strict';
|
3 |
|
4 | var WebSocket = require('ws');
|
5 | var Emitter = require('events');
|
6 | var util = require('util');
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | function Backoff (options) {
|
17 | this.options = options || {};
|
18 |
|
19 |
|
20 | this.reset();
|
21 | }
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | Backoff.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 |
|
42 |
|
43 |
|
44 | Backoff.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 |
|
56 |
|
57 |
|
58 |
|
59 | function 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 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function Peer (url, options) {
|
77 | if (!(this instanceof Peer)) {
|
78 | return new Peer(url, options);
|
79 | }
|
80 |
|
81 |
|
82 | Emitter.call(this);
|
83 | this.setMaxListeners(Infinity);
|
84 |
|
85 | this.options = options || {};
|
86 |
|
87 |
|
88 | this.deferredMsgs = [];
|
89 |
|
90 | this.url = Peer.formatURL(url);
|
91 | this.backoff = new Backoff(this.options.backoff);
|
92 |
|
93 |
|
94 | this.connect();
|
95 |
|
96 | var peer = this;
|
97 | var reconnect = scheduleReconnect.bind(null, peer);
|
98 |
|
99 |
|
100 | this.on('close', reconnect);
|
101 | this.on('error', function (error) {
|
102 | if (error.code === 'ECONNREFUSED') {
|
103 | reconnect();
|
104 | }
|
105 | });
|
106 |
|
107 |
|
108 | this.on('open', function () {
|
109 | peer.drainQueue();
|
110 | peer.backoff.reset();
|
111 | });
|
112 |
|
113 | }
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | Peer.formatURL = function (url) {
|
121 |
|
122 |
|
123 | return url.replace(/^http/, 'ws');
|
124 | };
|
125 |
|
126 | util.inherits(Peer, Emitter);
|
127 | var API = Peer.prototype;
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | API.connect = function () {
|
134 | var url = this.url;
|
135 |
|
136 |
|
137 | var socket = new WebSocket(url, this.options.wsc.protocols, this.options.wsc);
|
138 |
|
139 |
|
140 | socket._events = this._events;
|
141 |
|
142 | this.socket = socket;
|
143 |
|
144 | return socket;
|
145 | };
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | API.drainQueue = function () {
|
152 | var peer = this;
|
153 |
|
154 | this.deferredMsgs.forEach(function (msg) {
|
155 | peer.send(msg);
|
156 | });
|
157 |
|
158 |
|
159 | this.deferredMsgs = [];
|
160 |
|
161 | return this;
|
162 | };
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 | API.send = function (msg) {
|
171 | var socket = this.socket;
|
172 | var state = socket.readyState;
|
173 | var ready = socket.OPEN;
|
174 |
|
175 |
|
176 | if (typeof msg !== 'string') {
|
177 | msg = JSON.stringify(msg);
|
178 | }
|
179 |
|
180 |
|
181 | if (state === ready) {
|
182 | socket.send(msg);
|
183 | } else {
|
184 | this.deferredMsgs.push(msg);
|
185 | }
|
186 |
|
187 | return this;
|
188 | };
|
189 |
|
190 | module.exports = Peer;
|