oorja 1.5.1 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +5 -3
  2. package/lib/commands/signout.js +2 -2
  3. package/lib/commands/teletype/index.js +3 -3
  4. package/lib/lib/config.d.ts +5 -6
  5. package/lib/lib/config.js +12 -7
  6. package/lib/lib/encryption.d.ts +1 -1
  7. package/lib/lib/encryption.js +12 -7
  8. package/lib/lib/index.js +1 -0
  9. package/lib/lib/oorja/index.js +14 -13
  10. package/lib/lib/oorja/preflight.d.ts +1 -1
  11. package/lib/lib/oorja/preflight.js +17 -11
  12. package/lib/lib/surya/index.d.ts +2 -2
  13. package/lib/lib/surya/index.js +15 -13
  14. package/lib/lib/surya/types.d.ts +10 -10
  15. package/lib/lib/surya/vendor/phoenix/ajax.d.ts +8 -0
  16. package/lib/lib/surya/vendor/phoenix/ajax.js +85 -0
  17. package/lib/lib/surya/vendor/phoenix/channel.d.ts +154 -0
  18. package/lib/lib/surya/vendor/phoenix/channel.js +311 -0
  19. package/lib/lib/surya/vendor/phoenix/constants.d.ts +33 -0
  20. package/lib/lib/surya/vendor/phoenix/constants.js +32 -0
  21. package/lib/lib/surya/vendor/phoenix/index.d.ts +199 -0
  22. package/lib/lib/surya/vendor/phoenix/index.js +207 -0
  23. package/lib/lib/surya/vendor/phoenix/longpoll.d.ts +12 -0
  24. package/lib/lib/surya/vendor/phoenix/longpoll.js +129 -0
  25. package/lib/lib/surya/vendor/phoenix/presence.d.ts +44 -0
  26. package/lib/lib/surya/vendor/phoenix/presence.js +155 -0
  27. package/lib/lib/surya/vendor/phoenix/push.d.ts +57 -0
  28. package/lib/lib/surya/vendor/phoenix/push.js +125 -0
  29. package/lib/lib/surya/vendor/phoenix/serializer.d.ts +53 -0
  30. package/lib/lib/surya/vendor/phoenix/serializer.js +102 -0
  31. package/lib/lib/surya/vendor/phoenix/socket.d.ts +222 -0
  32. package/lib/lib/surya/vendor/phoenix/socket.js +544 -0
  33. package/lib/lib/surya/vendor/phoenix/timer.d.ts +25 -0
  34. package/lib/lib/surya/vendor/phoenix/timer.js +43 -0
  35. package/lib/lib/surya/vendor/phoenix/utils.d.ts +1 -0
  36. package/lib/lib/surya/vendor/phoenix/utils.js +15 -0
  37. package/lib/lib/teletype/auxiliary.d.ts +1 -1
  38. package/lib/lib/teletype/auxiliary.js +8 -4
  39. package/lib/lib/teletype/index.d.ts +1 -1
  40. package/lib/lib/teletype/index.js +13 -12
  41. package/lib/lib/utils.js +2 -1
  42. package/oclif.manifest.json +1 -1
  43. package/package.json +10 -9
  44. package/lib/lib/surya/vendor/phoenix.d.ts +0 -486
  45. package/lib/lib/surya/vendor/phoenix.js +0 -1299
@@ -1,1299 +0,0 @@
1
- "use strict";
2
- // @ts-nocheck
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.Presence = exports.Ajax = exports.LongPoll = exports.Socket = exports.Serializer = exports.Channel = void 0;
5
- // MIT License
6
- // Copyright (c) 2014 Chris McCord
7
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
- // minor tweaks to work within node for oorja
11
- const DEFAULT_VSN = "2.0.0";
12
- const SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
13
- const DEFAULT_TIMEOUT = 10000;
14
- const WS_CLOSE_NORMAL = 1000;
15
- const CHANNEL_STATES = {
16
- closed: "closed",
17
- errored: "errored",
18
- joined: "joined",
19
- joining: "joining",
20
- leaving: "leaving",
21
- };
22
- const CHANNEL_EVENTS = {
23
- close: "phx_close",
24
- error: "phx_error",
25
- join: "phx_join",
26
- reply: "phx_reply",
27
- leave: "phx_leave"
28
- };
29
- const CHANNEL_LIFECYCLE_EVENTS = [
30
- CHANNEL_EVENTS.close,
31
- CHANNEL_EVENTS.error,
32
- CHANNEL_EVENTS.join,
33
- CHANNEL_EVENTS.reply,
34
- CHANNEL_EVENTS.leave
35
- ];
36
- const TRANSPORTS = {
37
- longpoll: "longpoll",
38
- websocket: "websocket"
39
- };
40
- // wraps value in closure or returns closure
41
- let closure = (value) => {
42
- if (typeof value === "function") {
43
- return value;
44
- }
45
- else {
46
- let closure = function () { return value; };
47
- return closure;
48
- }
49
- };
50
- /**
51
- * Initializes the Push
52
- * @param {Channel} channel - The Channel
53
- * @param {string} event - The event, for example `"phx_join"`
54
- * @param {Object} payload - The payload, for example `{user_id: 123}`
55
- * @param {number} timeout - The push timeout in milliseconds
56
- */
57
- class Push {
58
- constructor(channel, event, payload, timeout) {
59
- this.channel = channel;
60
- this.event = event;
61
- this.payload = payload || function () { return {}; };
62
- this.receivedResp = null;
63
- this.timeout = timeout;
64
- this.timeoutTimer = null;
65
- this.recHooks = [];
66
- this.sent = false;
67
- }
68
- /**
69
- *
70
- * @param {number} timeout
71
- */
72
- resend(timeout) {
73
- this.timeout = timeout;
74
- this.reset();
75
- this.send();
76
- }
77
- /**
78
- *
79
- */
80
- send() {
81
- if (this.hasReceived("timeout")) {
82
- return;
83
- }
84
- this.startTimeout();
85
- this.sent = true;
86
- this.channel.socket.push({
87
- topic: this.channel.topic,
88
- event: this.event,
89
- payload: this.payload(),
90
- ref: this.ref,
91
- join_ref: this.channel.joinRef()
92
- });
93
- }
94
- /**
95
- *
96
- * @param {*} status
97
- * @param {*} callback
98
- */
99
- receive(status, callback) {
100
- if (this.hasReceived(status)) {
101
- callback(this.receivedResp.response);
102
- }
103
- this.recHooks.push({ status, callback });
104
- return this;
105
- }
106
- /**
107
- * @private
108
- */
109
- reset() {
110
- this.cancelRefEvent();
111
- this.ref = null;
112
- this.refEvent = null;
113
- this.receivedResp = null;
114
- this.sent = false;
115
- }
116
- /**
117
- * @private
118
- */
119
- matchReceive({ status, response, ref }) {
120
- this.recHooks.filter(h => h.status === status)
121
- .forEach(h => h.callback(response));
122
- }
123
- /**
124
- * @private
125
- */
126
- cancelRefEvent() {
127
- if (!this.refEvent) {
128
- return;
129
- }
130
- this.channel.off(this.refEvent);
131
- }
132
- /**
133
- * @private
134
- */
135
- cancelTimeout() {
136
- clearTimeout(this.timeoutTimer);
137
- this.timeoutTimer = null;
138
- }
139
- /**
140
- * @private
141
- */
142
- startTimeout() {
143
- if (this.timeoutTimer) {
144
- this.cancelTimeout();
145
- }
146
- this.ref = this.channel.socket.makeRef();
147
- this.refEvent = this.channel.replyEventName(this.ref);
148
- this.channel.on(this.refEvent, payload => {
149
- this.cancelRefEvent();
150
- this.cancelTimeout();
151
- this.receivedResp = payload;
152
- this.matchReceive(payload);
153
- });
154
- this.timeoutTimer = setTimeout(() => {
155
- this.trigger("timeout", {});
156
- }, this.timeout);
157
- }
158
- /**
159
- * @private
160
- */
161
- hasReceived(status) {
162
- return this.receivedResp && this.receivedResp.status === status;
163
- }
164
- /**
165
- * @private
166
- */
167
- trigger(status, response) {
168
- this.channel.trigger(this.refEvent, { status, response });
169
- }
170
- }
171
- /**
172
- *
173
- * @param {string} topic
174
- * @param {(Object|function)} params
175
- * @param {Socket} socket
176
- */
177
- class Channel {
178
- constructor(topic, params, socket) {
179
- this.state = CHANNEL_STATES.closed;
180
- this.topic = topic;
181
- this.params = closure(params || {});
182
- this.socket = socket;
183
- this.bindings = [];
184
- this.bindingRef = 0;
185
- this.timeout = this.socket.timeout;
186
- this.joinedOnce = false;
187
- this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
188
- this.pushBuffer = [];
189
- this.stateChangeRefs = [];
190
- this.rejoinTimer = new Timer(() => {
191
- if (this.socket.isConnected()) {
192
- this.rejoin();
193
- }
194
- }, this.socket.rejoinAfterMs);
195
- this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));
196
- this.stateChangeRefs.push(this.socket.onOpen(() => {
197
- this.rejoinTimer.reset();
198
- if (this.isErrored()) {
199
- this.rejoin();
200
- }
201
- }));
202
- this.joinPush.receive("ok", () => {
203
- this.state = CHANNEL_STATES.joined;
204
- this.rejoinTimer.reset();
205
- this.pushBuffer.forEach(pushEvent => pushEvent.send());
206
- this.pushBuffer = [];
207
- });
208
- this.joinPush.receive("error", () => {
209
- this.state = CHANNEL_STATES.errored;
210
- if (this.socket.isConnected()) {
211
- this.rejoinTimer.scheduleTimeout();
212
- }
213
- });
214
- this.onClose(() => {
215
- this.rejoinTimer.reset();
216
- if (this.socket.hasLogger())
217
- this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`);
218
- this.state = CHANNEL_STATES.closed;
219
- this.socket.remove(this);
220
- });
221
- this.onError(reason => {
222
- if (this.socket.hasLogger())
223
- this.socket.log("channel", `error ${this.topic}`, reason);
224
- if (this.isJoining()) {
225
- this.joinPush.reset();
226
- }
227
- this.state = CHANNEL_STATES.errored;
228
- if (this.socket.isConnected()) {
229
- this.rejoinTimer.scheduleTimeout();
230
- }
231
- });
232
- this.joinPush.receive("timeout", () => {
233
- if (this.socket.hasLogger())
234
- this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout);
235
- let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout);
236
- leavePush.send();
237
- this.state = CHANNEL_STATES.errored;
238
- this.joinPush.reset();
239
- if (this.socket.isConnected()) {
240
- this.rejoinTimer.scheduleTimeout();
241
- }
242
- });
243
- this.on(CHANNEL_EVENTS.reply, (payload, ref) => {
244
- this.trigger(this.replyEventName(ref), payload);
245
- });
246
- }
247
- /**
248
- * Join the channel
249
- * @param {integer} timeout
250
- * @returns {Push}
251
- */
252
- join(timeout = this.timeout) {
253
- if (this.joinedOnce) {
254
- throw new Error(`tried to join multiple times. 'join' can only be called a single time per channel instance`);
255
- }
256
- else {
257
- this.timeout = timeout;
258
- this.joinedOnce = true;
259
- this.rejoin();
260
- return this.joinPush;
261
- }
262
- }
263
- /**
264
- * Hook into channel close
265
- * @param {Function} callback
266
- */
267
- onClose(callback) {
268
- this.on(CHANNEL_EVENTS.close, callback);
269
- }
270
- /**
271
- * Hook into channel errors
272
- * @param {Function} callback
273
- */
274
- onError(callback) {
275
- return this.on(CHANNEL_EVENTS.error, reason => callback(reason));
276
- }
277
- /**
278
- * Subscribes on channel events
279
- *
280
- * Subscription returns a ref counter, which can be used later to
281
- * unsubscribe the exact event listener
282
- *
283
- * @example
284
- * const ref1 = channel.on("event", do_stuff)
285
- * const ref2 = channel.on("event", do_other_stuff)
286
- * channel.off("event", ref1)
287
- * // Since unsubscription, do_stuff won't fire,
288
- * // while do_other_stuff will keep firing on the "event"
289
- *
290
- * @param {string} event
291
- * @param {Function} callback
292
- * @returns {integer} ref
293
- */
294
- on(event, callback) {
295
- let ref = this.bindingRef++;
296
- this.bindings.push({ event, ref, callback });
297
- return ref;
298
- }
299
- /**
300
- * Unsubscribes off of channel events
301
- *
302
- * Use the ref returned from a channel.on() to unsubscribe one
303
- * handler, or pass nothing for the ref to unsubscribe all
304
- * handlers for the given event.
305
- *
306
- * @example
307
- * // Unsubscribe the do_stuff handler
308
- * const ref1 = channel.on("event", do_stuff)
309
- * channel.off("event", ref1)
310
- *
311
- * // Unsubscribe all handlers from event
312
- * channel.off("event")
313
- *
314
- * @param {string} event
315
- * @param {integer} ref
316
- */
317
- off(event, ref) {
318
- this.bindings = this.bindings.filter((bind) => {
319
- return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref));
320
- });
321
- }
322
- /**
323
- * @private
324
- */
325
- canPush() { return this.socket.isConnected() && this.isJoined(); }
326
- /**
327
- * Sends a message `event` to phoenix with the payload `payload`.
328
- * Phoenix receives this in the `handle_in(event, payload, socket)`
329
- * function. if phoenix replies or it times out (default 10000ms),
330
- * then optionally the reply can be received.
331
- *
332
- * @example
333
- * channel.push("event")
334
- * .receive("ok", payload => console.log("phoenix replied:", payload))
335
- * .receive("error", err => console.log("phoenix errored", err))
336
- * .receive("timeout", () => console.log("timed out pushing"))
337
- * @param {string} event
338
- * @param {Object} payload
339
- * @param {number} [timeout]
340
- * @returns {Push}
341
- */
342
- push(event, payload, timeout = this.timeout) {
343
- if (!this.joinedOnce) {
344
- throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`);
345
- }
346
- let pushEvent = new Push(this, event, function () { return payload; }, timeout);
347
- if (this.canPush()) {
348
- pushEvent.send();
349
- }
350
- else {
351
- pushEvent.startTimeout();
352
- this.pushBuffer.push(pushEvent);
353
- }
354
- return pushEvent;
355
- }
356
- /** Leaves the channel
357
- *
358
- * Unsubscribes from server events, and
359
- * instructs channel to terminate on server
360
- *
361
- * Triggers onClose() hooks
362
- *
363
- * To receive leave acknowledgements, use the `receive`
364
- * hook to bind to the server ack, ie:
365
- *
366
- * @example
367
- * channel.leave().receive("ok", () => alert("left!") )
368
- *
369
- * @param {integer} timeout
370
- * @returns {Push}
371
- */
372
- leave(timeout = this.timeout) {
373
- this.rejoinTimer.reset();
374
- this.joinPush.cancelTimeout();
375
- this.state = CHANNEL_STATES.leaving;
376
- let onClose = () => {
377
- if (this.socket.hasLogger())
378
- this.socket.log("channel", `leave ${this.topic}`);
379
- this.trigger(CHANNEL_EVENTS.close, "leave");
380
- };
381
- let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout);
382
- leavePush.receive("ok", () => onClose())
383
- .receive("timeout", () => onClose());
384
- leavePush.send();
385
- if (!this.canPush()) {
386
- leavePush.trigger("ok", {});
387
- }
388
- return leavePush;
389
- }
390
- /**
391
- * Overridable message hook
392
- *
393
- * Receives all events for specialized message handling
394
- * before dispatching to the channel callbacks.
395
- *
396
- * Must return the payload, modified or unmodified
397
- * @param {string} event
398
- * @param {Object} payload
399
- * @param {integer} ref
400
- * @returns {Object}
401
- */
402
- onMessage(event, payload, ref) { return payload; }
403
- /**
404
- * @private
405
- */
406
- isLifecycleEvent(event) { return CHANNEL_LIFECYCLE_EVENTS.indexOf(event) >= 0; }
407
- /**
408
- * @private
409
- */
410
- isMember(topic, event, payload, joinRef) {
411
- if (this.topic !== topic) {
412
- return false;
413
- }
414
- if (joinRef && joinRef !== this.joinRef() && this.isLifecycleEvent(event)) {
415
- if (this.socket.hasLogger())
416
- this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef });
417
- return false;
418
- }
419
- else {
420
- return true;
421
- }
422
- }
423
- /**
424
- * @private
425
- */
426
- joinRef() { return this.joinPush.ref; }
427
- /**
428
- * @private
429
- */
430
- rejoin(timeout = this.timeout) {
431
- if (this.isLeaving()) {
432
- return;
433
- }
434
- this.socket.leaveOpenTopic(this.topic);
435
- this.state = CHANNEL_STATES.joining;
436
- this.joinPush.resend(timeout);
437
- }
438
- /**
439
- * @private
440
- */
441
- trigger(event, payload, ref, joinRef) {
442
- let handledPayload = this.onMessage(event, payload, ref, joinRef);
443
- if (payload && !handledPayload) {
444
- throw new Error("channel onMessage callbacks must return the payload, modified or unmodified");
445
- }
446
- let eventBindings = this.bindings.filter(bind => bind.event === event);
447
- for (let i = 0; i < eventBindings.length; i++) {
448
- let bind = eventBindings[i];
449
- bind.callback(handledPayload, ref, joinRef || this.joinRef());
450
- }
451
- }
452
- /**
453
- * @private
454
- */
455
- replyEventName(ref) { return `chan_reply_${ref}`; }
456
- /**
457
- * @private
458
- */
459
- isClosed() { return this.state === CHANNEL_STATES.closed; }
460
- /**
461
- * @private
462
- */
463
- isErrored() { return this.state === CHANNEL_STATES.errored; }
464
- /**
465
- * @private
466
- */
467
- isJoined() { return this.state === CHANNEL_STATES.joined; }
468
- /**
469
- * @private
470
- */
471
- isJoining() { return this.state === CHANNEL_STATES.joining; }
472
- /**
473
- * @private
474
- */
475
- isLeaving() { return this.state === CHANNEL_STATES.leaving; }
476
- }
477
- exports.Channel = Channel;
478
- /* The default serializer for encoding and decoding messages */
479
- exports.Serializer = {
480
- encode(msg, callback) {
481
- let payload = [
482
- msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload
483
- ];
484
- return callback(JSON.stringify(payload));
485
- },
486
- decode(rawPayload, callback) {
487
- let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload);
488
- return callback({ join_ref, ref, topic, event, payload });
489
- }
490
- };
491
- /** Initializes the Socket
492
- *
493
- *
494
- * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
495
- *
496
- * @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`,
497
- * `"wss://example.com"`
498
- * `"/socket"` (inherited host & protocol)
499
- * @param {Object} [opts] - Optional configuration
500
- * @param {string} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
501
- *
502
- * Defaults to WebSocket with automatic LongPoll fallback.
503
- * @param {Function} [opts.encode] - The function to encode outgoing messages.
504
- *
505
- * Defaults to JSON encoder.
506
- *
507
- * @param {Function} [opts.decode] - The function to decode incoming messages.
508
- *
509
- * Defaults to JSON:
510
- *
511
- * ```javascript
512
- * (payload, callback) => callback(JSON.parse(payload))
513
- * ```
514
- *
515
- * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.
516
- *
517
- * Defaults `DEFAULT_TIMEOUT`
518
- * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message
519
- * @param {number} [opts.reconnectAfterMs] - The optional function that returns the millsec
520
- * socket reconnect interval.
521
- *
522
- * Defaults to stepped backoff of:
523
- *
524
- * ```javascript
525
- * function(tries){
526
- * return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000
527
- * }
528
- * ````
529
- *
530
- * @param {number} [opts.rejoinAfterMs] - The optional function that returns the millsec
531
- * rejoin interval for individual channels.
532
- *
533
- * ```javascript
534
- * function(tries){
535
- * return [1000, 2000, 5000][tries - 1] || 10000
536
- * }
537
- * ````
538
- *
539
- * @param {Function} [opts.logger] - The optional function for specialized logging, ie:
540
- *
541
- * ```javascript
542
- * function(kind, msg, data) {
543
- * console.log(`${kind}: ${msg}`, data)
544
- * }
545
- * ```
546
- *
547
- * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.
548
- *
549
- * Defaults to 20s (double the server long poll timer).
550
- *
551
- * @param {{Object|function)} [opts.params] - The optional params to pass when connecting
552
- * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.
553
- *
554
- * Defaults to "arraybuffer"
555
- *
556
- * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.
557
- *
558
- * Defaults to DEFAULT_VSN.
559
- */
560
- class Socket {
561
- constructor(endPoint, opts = {}) {
562
- this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
563
- this.channels = [];
564
- this.sendBuffer = [];
565
- this.ref = 0;
566
- this.timeout = opts.timeout || DEFAULT_TIMEOUT;
567
- this.transport = require("ws");
568
- this.defaultEncoder = exports.Serializer.encode;
569
- this.defaultDecoder = exports.Serializer.decode;
570
- this.closeWasClean = false;
571
- this.unloaded = false;
572
- this.binaryType = opts.binaryType || "arraybuffer";
573
- if (this.transport !== LongPoll) {
574
- this.encode = opts.encode || this.defaultEncoder;
575
- this.decode = opts.decode || this.defaultDecoder;
576
- }
577
- else {
578
- this.encode = this.defaultEncoder;
579
- this.decode = this.defaultDecoder;
580
- }
581
- this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
582
- this.rejoinAfterMs = (tries) => {
583
- if (opts.rejoinAfterMs) {
584
- return opts.rejoinAfterMs(tries);
585
- }
586
- else {
587
- return [1000, 2000, 5000][tries - 1] || 10000;
588
- }
589
- };
590
- this.reconnectAfterMs = (tries) => {
591
- if (this.unloaded) {
592
- return 100;
593
- }
594
- if (opts.reconnectAfterMs) {
595
- return opts.reconnectAfterMs(tries);
596
- }
597
- else {
598
- return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000;
599
- }
600
- };
601
- this.logger = opts.logger || null;
602
- this.longpollerTimeout = opts.longpollerTimeout || 20000;
603
- this.params = closure(opts.params || {});
604
- this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;
605
- this.vsn = opts.vsn || DEFAULT_VSN;
606
- this.heartbeatTimer = null;
607
- this.pendingHeartbeatRef = null;
608
- this.reconnectTimer = new Timer(() => {
609
- this.teardown(() => this.connect());
610
- }, this.reconnectAfterMs);
611
- }
612
- /**
613
- * Returns the socket protocol
614
- *
615
- * @returns {string}
616
- */
617
- protocol() { return location.protocol.match(/^https/) ? "wss" : "ws"; }
618
- /**
619
- * The fully qualifed socket url
620
- *
621
- * @returns {string}
622
- */
623
- endPointURL() {
624
- let uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn });
625
- return uri;
626
- // if(uri.charAt(0) !== "/"){ return uri }
627
- // if(uri.charAt(1) === "/"){ return `${this.protocol()}:${uri}` }
628
- // return `${this.protocol()}://${location.host}${uri}`
629
- }
630
- /**
631
- * Disconnects the socket
632
- *
633
- * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.
634
- *
635
- * @param {Function} callback - Optional callback which is called after socket is disconnected.
636
- * @param {integer} code - A status code for disconnection (Optional).
637
- * @param {string} reason - A textual description of the reason to disconnect. (Optional)
638
- */
639
- disconnect(callback, code, reason) {
640
- this.closeWasClean = true;
641
- this.reconnectTimer.reset();
642
- this.teardown(callback, code, reason);
643
- }
644
- /**
645
- *
646
- * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
647
- *
648
- * Passing params to connect is deprecated; pass them in the Socket constructor instead:
649
- * `new Socket("/socket", {params: {user_id: userToken}})`.
650
- */
651
- connect(params) {
652
- if (params) {
653
- console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
654
- this.params = closure(params);
655
- }
656
- if (this.conn) {
657
- return;
658
- }
659
- this.closeWasClean = false;
660
- this.conn = new this.transport(this.endPointURL());
661
- this.conn.binaryType = this.binaryType;
662
- this.conn.timeout = this.longpollerTimeout;
663
- this.conn.onopen = () => this.onConnOpen();
664
- this.conn.onerror = error => this.onConnError(error);
665
- this.conn.onmessage = event => this.onConnMessage(event);
666
- this.conn.onclose = event => this.onConnClose(event);
667
- }
668
- /**
669
- * Logs the message. Override `this.logger` for specialized logging. noops by default
670
- * @param {string} kind
671
- * @param {string} msg
672
- * @param {Object} data
673
- */
674
- log(kind, msg, data) { this.logger(kind, msg, data); }
675
- /**
676
- * Returns true if a logger has been set on this socket.
677
- */
678
- hasLogger() { return this.logger !== null; }
679
- /**
680
- * Registers callbacks for connection open events
681
- *
682
- * @example socket.onOpen(function(){ console.info("the socket was opened") })
683
- *
684
- * @param {Function} callback
685
- */
686
- onOpen(callback) {
687
- let ref = this.makeRef();
688
- this.stateChangeCallbacks.open.push([ref, callback]);
689
- return ref;
690
- }
691
- /**
692
- * Registers callbacks for connection close events
693
- * @param {Function} callback
694
- */
695
- onClose(callback) {
696
- let ref = this.makeRef();
697
- this.stateChangeCallbacks.close.push([ref, callback]);
698
- return ref;
699
- }
700
- /**
701
- * Registers callbacks for connection error events
702
- *
703
- * @example socket.onError(function(error){ alert("An error occurred") })
704
- *
705
- * @param {Function} callback
706
- */
707
- onError(callback) {
708
- let ref = this.makeRef();
709
- this.stateChangeCallbacks.error.push([ref, callback]);
710
- return ref;
711
- }
712
- /**
713
- * Registers callbacks for connection message events
714
- * @param {Function} callback
715
- */
716
- onMessage(callback) {
717
- let ref = this.makeRef();
718
- this.stateChangeCallbacks.message.push([ref, callback]);
719
- return ref;
720
- }
721
- /**
722
- * @private
723
- */
724
- onConnOpen() {
725
- if (this.hasLogger())
726
- this.log("transport", `connected to ${this.endPointURL()}`);
727
- this.unloaded = false;
728
- this.closeWasClean = false;
729
- this.flushSendBuffer();
730
- this.reconnectTimer.reset();
731
- this.resetHeartbeat();
732
- this.stateChangeCallbacks.open.forEach(([, callback]) => callback());
733
- }
734
- /**
735
- * @private
736
- */
737
- resetHeartbeat() {
738
- if (this.conn && this.conn.skipHeartbeat) {
739
- return;
740
- }
741
- this.pendingHeartbeatRef = null;
742
- clearInterval(this.heartbeatTimer);
743
- this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs);
744
- }
745
- teardown(callback, code, reason) {
746
- if (!this.conn) {
747
- return callback && callback();
748
- }
749
- this.waitForBufferDone(() => {
750
- if (this.conn) {
751
- if (code) {
752
- this.conn.close(code, reason || "");
753
- }
754
- else {
755
- this.conn.close();
756
- }
757
- }
758
- this.waitForSocketClosed(() => {
759
- if (this.conn) {
760
- this.conn.onclose = function () { }; // noop
761
- this.conn = null;
762
- }
763
- callback && callback();
764
- });
765
- });
766
- }
767
- waitForBufferDone(callback, tries = 1) {
768
- if (tries === 5 || !this.conn || !this.conn.bufferedAmount) {
769
- callback();
770
- return;
771
- }
772
- setTimeout(() => {
773
- this.waitForBufferDone(callback, tries + 1);
774
- }, 150 * tries);
775
- }
776
- waitForSocketClosed(callback, tries = 1) {
777
- if (tries === 5 || !this.conn || this.conn.readyState === SOCKET_STATES.closed) {
778
- callback();
779
- return;
780
- }
781
- setTimeout(() => {
782
- this.waitForSocketClosed(callback, tries + 1);
783
- }, 150 * tries);
784
- }
785
- onConnClose(event) {
786
- if (this.hasLogger())
787
- this.log("transport", "close", event);
788
- this.triggerChanError();
789
- clearInterval(this.heartbeatTimer);
790
- if (!this.closeWasClean) {
791
- this.reconnectTimer.scheduleTimeout();
792
- }
793
- this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));
794
- }
795
- /**
796
- * @private
797
- */
798
- onConnError(error) {
799
- if (this.hasLogger())
800
- this.log("transport", error);
801
- this.triggerChanError();
802
- this.stateChangeCallbacks.error.forEach(([, callback]) => callback(error));
803
- }
804
- /**
805
- * @private
806
- */
807
- triggerChanError() {
808
- this.channels.forEach(channel => {
809
- if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {
810
- channel.trigger(CHANNEL_EVENTS.error);
811
- }
812
- });
813
- }
814
- /**
815
- * @returns {string}
816
- */
817
- connectionState() {
818
- switch (this.conn && this.conn.readyState) {
819
- case SOCKET_STATES.connecting: return "connecting";
820
- case SOCKET_STATES.open: return "open";
821
- case SOCKET_STATES.closing: return "closing";
822
- default: return "closed";
823
- }
824
- }
825
- /**
826
- * @returns {boolean}
827
- */
828
- isConnected() { return this.connectionState() === "open"; }
829
- /**
830
- * @private
831
- *
832
- * @param {Channel}
833
- */
834
- remove(channel) {
835
- this.off(channel.stateChangeRefs);
836
- this.channels = this.channels.filter(c => c.joinRef() !== channel.joinRef());
837
- }
838
- /**
839
- * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.
840
- *
841
- * @param {refs} - list of refs returned by calls to
842
- * `onOpen`, `onClose`, `onError,` and `onMessage`
843
- */
844
- off(refs) {
845
- for (let key in this.stateChangeCallbacks) {
846
- this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {
847
- return refs.indexOf(ref) === -1;
848
- });
849
- }
850
- }
851
- /**
852
- * Initiates a new channel for the given topic
853
- *
854
- * @param {string} topic
855
- * @param {Object} chanParams - Parameters for the channel
856
- * @returns {Channel}
857
- */
858
- channel(topic, chanParams = {}) {
859
- let chan = new Channel(topic, chanParams, this);
860
- this.channels.push(chan);
861
- return chan;
862
- }
863
- /**
864
- * @param {Object} data
865
- */
866
- push(data) {
867
- if (this.hasLogger()) {
868
- let { topic, event, payload, ref, join_ref } = data;
869
- this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload);
870
- }
871
- if (this.isConnected()) {
872
- this.encode(data, result => this.conn.send(result));
873
- }
874
- else {
875
- this.sendBuffer.push(() => this.encode(data, result => this.conn.send(result)));
876
- }
877
- }
878
- /**
879
- * Return the next message ref, accounting for overflows
880
- * @returns {string}
881
- */
882
- makeRef() {
883
- let newRef = this.ref + 1;
884
- if (newRef === this.ref) {
885
- this.ref = 0;
886
- }
887
- else {
888
- this.ref = newRef;
889
- }
890
- return this.ref.toString();
891
- }
892
- sendHeartbeat() {
893
- if (!this.isConnected()) {
894
- return;
895
- }
896
- if (this.pendingHeartbeatRef) {
897
- this.pendingHeartbeatRef = null;
898
- if (this.hasLogger())
899
- this.log("transport", "heartbeat timeout. Attempting to re-establish connection");
900
- this.abnormalClose("heartbeat timeout");
901
- return;
902
- }
903
- this.pendingHeartbeatRef = this.makeRef();
904
- this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef });
905
- }
906
- abnormalClose(reason) {
907
- this.closeWasClean = false;
908
- this.conn.close(WS_CLOSE_NORMAL, reason);
909
- }
910
- flushSendBuffer() {
911
- if (this.isConnected() && this.sendBuffer.length > 0) {
912
- this.sendBuffer.forEach(callback => callback());
913
- this.sendBuffer = [];
914
- }
915
- }
916
- onConnMessage(rawMessage) {
917
- this.decode(rawMessage.data, msg => {
918
- let { topic, event, payload, ref, join_ref } = msg;
919
- if (ref && ref === this.pendingHeartbeatRef) {
920
- this.pendingHeartbeatRef = null;
921
- }
922
- if (this.hasLogger())
923
- this.log("receive", `${payload.status || ""} ${topic} ${event} ${ref && "(" + ref + ")" || ""}`, payload);
924
- for (let i = 0; i < this.channels.length; i++) {
925
- const channel = this.channels[i];
926
- if (!channel.isMember(topic, event, payload, join_ref)) {
927
- continue;
928
- }
929
- channel.trigger(event, payload, ref, join_ref);
930
- }
931
- for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {
932
- let [, callback] = this.stateChangeCallbacks.message[i];
933
- callback(msg);
934
- }
935
- });
936
- }
937
- leaveOpenTopic(topic) {
938
- let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining()));
939
- if (dupChannel) {
940
- if (this.hasLogger())
941
- this.log("transport", `leaving duplicate topic "${topic}"`);
942
- dupChannel.leave();
943
- }
944
- }
945
- }
946
- exports.Socket = Socket;
947
- class LongPoll {
948
- constructor(endPoint) {
949
- this.endPoint = null;
950
- this.token = null;
951
- this.skipHeartbeat = true;
952
- this.onopen = function () { }; // noop
953
- this.onerror = function () { }; // noop
954
- this.onmessage = function () { }; // noop
955
- this.onclose = function () { }; // noop
956
- this.pollEndpoint = this.normalizeEndpoint(endPoint);
957
- this.readyState = SOCKET_STATES.connecting;
958
- this.poll();
959
- }
960
- normalizeEndpoint(endPoint) {
961
- return (endPoint
962
- .replace("ws://", "http://")
963
- .replace("wss://", "https://")
964
- .replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll));
965
- }
966
- endpointURL() {
967
- return Ajax.appendParams(this.pollEndpoint, { token: this.token });
968
- }
969
- closeAndRetry() {
970
- this.close();
971
- this.readyState = SOCKET_STATES.connecting;
972
- }
973
- ontimeout() {
974
- this.onerror("timeout");
975
- this.closeAndRetry();
976
- }
977
- poll() {
978
- if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
979
- return;
980
- }
981
- Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), (resp) => {
982
- if (resp) {
983
- var { status, token, messages } = resp;
984
- this.token = token;
985
- }
986
- else {
987
- var status = 0;
988
- }
989
- switch (status) {
990
- case 200:
991
- messages.forEach(msg => this.onmessage({ data: msg }));
992
- this.poll();
993
- break;
994
- case 204:
995
- this.poll();
996
- break;
997
- case 410:
998
- this.readyState = SOCKET_STATES.open;
999
- this.onopen();
1000
- this.poll();
1001
- break;
1002
- case 403:
1003
- this.onerror();
1004
- this.close();
1005
- break;
1006
- case 0:
1007
- case 500:
1008
- this.onerror();
1009
- this.closeAndRetry();
1010
- break;
1011
- default: throw new Error(`unhandled poll status ${status}`);
1012
- }
1013
- });
1014
- }
1015
- send(body) {
1016
- Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), (resp) => {
1017
- if (!resp || resp.status !== 200) {
1018
- this.onerror(resp && resp.status);
1019
- this.closeAndRetry();
1020
- }
1021
- });
1022
- }
1023
- close(code, reason) {
1024
- this.readyState = SOCKET_STATES.closed;
1025
- this.onclose();
1026
- }
1027
- }
1028
- exports.LongPoll = LongPoll;
1029
- class Ajax {
1030
- static request(method, endPoint, accept, body, timeout, ontimeout, callback) {
1031
- if (global.XDomainRequest) {
1032
- let req = new XDomainRequest(); // IE8, IE9
1033
- this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
1034
- }
1035
- else {
1036
- let req = new global.XMLHttpRequest(); // IE7+, Firefox, Chrome, Opera, Safari
1037
- this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
1038
- }
1039
- }
1040
- static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
1041
- req.timeout = timeout;
1042
- req.open(method, endPoint);
1043
- req.onload = () => {
1044
- let response = this.parseJSON(req.responseText);
1045
- callback && callback(response);
1046
- };
1047
- if (ontimeout) {
1048
- req.ontimeout = ontimeout;
1049
- }
1050
- // Work around bug in IE9 that requires an attached onprogress handler
1051
- req.onprogress = () => { };
1052
- req.send(body);
1053
- }
1054
- static xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
1055
- req.open(method, endPoint, true);
1056
- req.timeout = timeout;
1057
- req.setRequestHeader("Content-Type", accept);
1058
- req.onerror = () => { callback && callback(null); };
1059
- req.onreadystatechange = () => {
1060
- if (req.readyState === this.states.complete && callback) {
1061
- let response = this.parseJSON(req.responseText);
1062
- callback(response);
1063
- }
1064
- };
1065
- if (ontimeout) {
1066
- req.ontimeout = ontimeout;
1067
- }
1068
- req.send(body);
1069
- }
1070
- static parseJSON(resp) {
1071
- if (!resp || resp === "") {
1072
- return null;
1073
- }
1074
- try {
1075
- return JSON.parse(resp);
1076
- }
1077
- catch (e) {
1078
- console && console.log("failed to parse JSON response", resp);
1079
- return null;
1080
- }
1081
- }
1082
- static serialize(obj, parentKey) {
1083
- let queryStr = [];
1084
- for (var key in obj) {
1085
- if (!obj.hasOwnProperty(key)) {
1086
- continue;
1087
- }
1088
- let paramKey = parentKey ? `${parentKey}[${key}]` : key;
1089
- let paramVal = obj[key];
1090
- if (typeof paramVal === "object") {
1091
- queryStr.push(this.serialize(paramVal, paramKey));
1092
- }
1093
- else {
1094
- queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
1095
- }
1096
- }
1097
- return queryStr.join("&");
1098
- }
1099
- static appendParams(url, params) {
1100
- if (Object.keys(params).length === 0) {
1101
- return url;
1102
- }
1103
- let prefix = url.match(/\?/) ? "&" : "?";
1104
- return `${url}${prefix}${this.serialize(params)}`;
1105
- }
1106
- }
1107
- exports.Ajax = Ajax;
1108
- Ajax.states = { complete: 4 };
1109
- /**
1110
- * Initializes the Presence
1111
- * @param {Channel} channel - The Channel
1112
- * @param {Object} opts - The options,
1113
- * for example `{events: {state: "state", diff: "diff"}}`
1114
- */
1115
- class Presence {
1116
- constructor(channel, opts = {}) {
1117
- let events = opts.events || { state: "presence_state", diff: "presence_diff" };
1118
- this.state = {};
1119
- this.pendingDiffs = [];
1120
- this.channel = channel;
1121
- this.joinRef = null;
1122
- this.caller = {
1123
- onJoin: function () { },
1124
- onLeave: function () { },
1125
- onSync: function () { }
1126
- };
1127
- this.channel.on(events.state, newState => {
1128
- let { onJoin, onLeave, onSync } = this.caller;
1129
- this.joinRef = this.channel.joinRef();
1130
- this.state = Presence.syncState(this.state, newState, onJoin, onLeave);
1131
- this.pendingDiffs.forEach(diff => {
1132
- this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);
1133
- });
1134
- this.pendingDiffs = [];
1135
- onSync();
1136
- });
1137
- this.channel.on(events.diff, diff => {
1138
- let { onJoin, onLeave, onSync } = this.caller;
1139
- if (this.inPendingSyncState()) {
1140
- this.pendingDiffs.push(diff);
1141
- }
1142
- else {
1143
- this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave);
1144
- onSync();
1145
- }
1146
- });
1147
- }
1148
- onJoin(callback) { this.caller.onJoin = callback; }
1149
- onLeave(callback) { this.caller.onLeave = callback; }
1150
- onSync(callback) { this.caller.onSync = callback; }
1151
- list(by) { return Presence.list(this.state, by); }
1152
- inPendingSyncState() {
1153
- return !this.joinRef || (this.joinRef !== this.channel.joinRef());
1154
- }
1155
- // lower-level public static API
1156
- /**
1157
- * Used to sync the list of presences on the server
1158
- * with the client's state. An optional `onJoin` and `onLeave` callback can
1159
- * be provided to react to changes in the client's local presences across
1160
- * disconnects and reconnects with the server.
1161
- *
1162
- * @returns {Presence}
1163
- */
1164
- static syncState(currentState, newState, onJoin, onLeave) {
1165
- let state = this.clone(currentState);
1166
- let joins = {};
1167
- let leaves = {};
1168
- this.map(state, (key, presence) => {
1169
- if (!newState[key]) {
1170
- leaves[key] = presence;
1171
- }
1172
- });
1173
- this.map(newState, (key, newPresence) => {
1174
- let currentPresence = state[key];
1175
- if (currentPresence) {
1176
- let newRefs = newPresence.metas.map(m => m.phx_ref);
1177
- let curRefs = currentPresence.metas.map(m => m.phx_ref);
1178
- let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0);
1179
- let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0);
1180
- if (joinedMetas.length > 0) {
1181
- joins[key] = newPresence;
1182
- joins[key].metas = joinedMetas;
1183
- }
1184
- if (leftMetas.length > 0) {
1185
- leaves[key] = this.clone(currentPresence);
1186
- leaves[key].metas = leftMetas;
1187
- }
1188
- }
1189
- else {
1190
- joins[key] = newPresence;
1191
- }
1192
- });
1193
- return this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
1194
- }
1195
- /**
1196
- *
1197
- * Used to sync a diff of presence join and leave
1198
- * events from the server, as they happen. Like `syncState`, `syncDiff`
1199
- * accepts optional `onJoin` and `onLeave` callbacks to react to a user
1200
- * joining or leaving from a device.
1201
- *
1202
- * @returns {Presence}
1203
- */
1204
- static syncDiff(currentState, { joins, leaves }, onJoin, onLeave) {
1205
- let state = this.clone(currentState);
1206
- if (!onJoin) {
1207
- onJoin = function () { };
1208
- }
1209
- if (!onLeave) {
1210
- onLeave = function () { };
1211
- }
1212
- this.map(joins, (key, newPresence) => {
1213
- let currentPresence = state[key];
1214
- state[key] = newPresence;
1215
- if (currentPresence) {
1216
- let joinedRefs = state[key].metas.map(m => m.phx_ref);
1217
- let curMetas = currentPresence.metas.filter(m => joinedRefs.indexOf(m.phx_ref) < 0);
1218
- state[key].metas.unshift(...curMetas);
1219
- }
1220
- onJoin(key, currentPresence, newPresence);
1221
- });
1222
- this.map(leaves, (key, leftPresence) => {
1223
- let currentPresence = state[key];
1224
- if (!currentPresence) {
1225
- return;
1226
- }
1227
- let refsToRemove = leftPresence.metas.map(m => m.phx_ref);
1228
- currentPresence.metas = currentPresence.metas.filter(p => {
1229
- return refsToRemove.indexOf(p.phx_ref) < 0;
1230
- });
1231
- onLeave(key, currentPresence, leftPresence);
1232
- if (currentPresence.metas.length === 0) {
1233
- delete state[key];
1234
- }
1235
- });
1236
- return state;
1237
- }
1238
- /**
1239
- * Returns the array of presences, with selected metadata.
1240
- *
1241
- * @param {Object} presences
1242
- * @param {Function} chooser
1243
- *
1244
- * @returns {Presence}
1245
- */
1246
- static list(presences, chooser) {
1247
- if (!chooser) {
1248
- chooser = function (key, pres) { return pres; };
1249
- }
1250
- return this.map(presences, (key, presence) => {
1251
- return chooser(key, presence);
1252
- });
1253
- }
1254
- // private
1255
- static map(obj, func) {
1256
- return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key]));
1257
- }
1258
- static clone(obj) { return JSON.parse(JSON.stringify(obj)); }
1259
- }
1260
- exports.Presence = Presence;
1261
- /**
1262
- *
1263
- * Creates a timer that accepts a `timerCalc` function to perform
1264
- * calculated timeout retries, such as exponential backoff.
1265
- *
1266
- * @example
1267
- * let reconnectTimer = new Timer(() => this.connect(), function(tries){
1268
- * return [1000, 5000, 10000][tries - 1] || 10000
1269
- * })
1270
- * reconnectTimer.scheduleTimeout() // fires after 1000
1271
- * reconnectTimer.scheduleTimeout() // fires after 5000
1272
- * reconnectTimer.reset()
1273
- * reconnectTimer.scheduleTimeout() // fires after 1000
1274
- *
1275
- * @param {Function} callback
1276
- * @param {Function} timerCalc
1277
- */
1278
- class Timer {
1279
- constructor(callback, timerCalc) {
1280
- this.callback = callback;
1281
- this.timerCalc = timerCalc;
1282
- this.timer = null;
1283
- this.tries = 0;
1284
- }
1285
- reset() {
1286
- this.tries = 0;
1287
- clearTimeout(this.timer);
1288
- }
1289
- /**
1290
- * Cancels any previous scheduleTimeout and schedules callback
1291
- */
1292
- scheduleTimeout() {
1293
- clearTimeout(this.timer);
1294
- this.timer = setTimeout(() => {
1295
- this.tries = this.tries + 1;
1296
- this.callback();
1297
- }, this.timerCalc(this.tries + 1));
1298
- }
1299
- }