@monterosa/sdk-enmasse-kit 2.0.0-rc.2 → 2.0.0-rc.4

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2180 @@
1
+ 'use strict';
2
+
3
+ var sdkUtil = require('@monterosa/sdk-util');
4
+ var sdkStorageKit = require('@monterosa/sdk-storage-kit');
5
+ var sdkLauncherKit = require('@monterosa/sdk-launcher-kit');
6
+
7
+ const ARG_OVERRIDE_METHODS = 'enmasse_override_methods';
8
+ const ARG_DEBUG = 'enmasse_debug';
9
+ const ARG_CONFIG = 'enmasse_config';
10
+
11
+ const SSCP_KLASS_AUTH = 'auth';
12
+ const SSCP_KLASS_AUTHR = 'authr';
13
+ const SSCP_KLASS_AUTHOK = 'authok';
14
+ const SSCP_KLASS_PING = 'ping';
15
+ const SSCP_KLASS_SUB = 'sub';
16
+ const SSCP_KLASS_UNSUB = 'unsub';
17
+ const SSCP_KLASS_EOC = 'eoc';
18
+ const SSCP_KLASS_LOGIN = 'login';
19
+ const SSCP_KLASS_LOGIN_OK = 'login_ok';
20
+ const SSCP_KLASS_LOGIN_FAIL = 'login_fail';
21
+ const SSCP_KLASS_LOGOUT = 'logout';
22
+ const SSCP_KLASS_COUNTER = 'counter';
23
+
24
+ const getSettingFor = (name) => {
25
+ if (sdkUtil.checkAvailability()) {
26
+ const value = sdkUtil.getItem(name);
27
+
28
+ if (value !== null) {
29
+ return value;
30
+ }
31
+ }
32
+
33
+ // Guarding the use of the window object in environments where it might not be
34
+ // available, such as during server - side rendering in a framework like Next.js
35
+ if (typeof window === 'undefined') {
36
+ return null;
37
+ }
38
+
39
+ const { searchParams } = new URL(window.location.href);
40
+
41
+ if (searchParams.has(name)) {
42
+ return searchParams.get(name);
43
+ }
44
+
45
+ return false;
46
+ };
47
+
48
+ const getSettingForMethods = () => {
49
+ const value = getSettingFor(ARG_OVERRIDE_METHODS);
50
+
51
+ if (typeof value === 'string') {
52
+ return value.split(',');
53
+ }
54
+
55
+ return undefined;
56
+ };
57
+
58
+ const ENMASSE_OVERRIDE_METHODS = getSettingForMethods();
59
+ const ENMASSE_DEBUG = getSettingFor(ARG_DEBUG);
60
+ const ENMASSE_CONFIG = getSettingFor(ARG_CONFIG);
61
+
62
+ var version = "2.0.0-rc.4";
63
+
64
+ /* eslint no-bitwise: "off" */
65
+
66
+ const TYPE_PROTOCOL = 1; // 0000 0000 0001
67
+ const TYPE_MANAGER = 2; // 0000 0000 0010
68
+ const TYPE_TRANSPORT = 4; // 0000 0000 0100
69
+ const TYPE_DRIVER = 8; // 0000 0000 1000
70
+ const TYPE_QUEUE = 16; // 0000 0001 0000
71
+ const TYPE_TIME = 32; // 0000 0010 0000
72
+ const TYPE_MANAGER_EXTRA = 64; // 0000 0100 0000
73
+ const TYPE_DRIVER_EXTRA = 128; // 0000 1000 0000
74
+ const TYPE_SERVICE = 256; // 0001 0000 0000
75
+
76
+ const LEVELS = {
77
+ brief: TYPE_PROTOCOL,
78
+ full: TYPE_PROTOCOL | TYPE_MANAGER | TYPE_TRANSPORT | TYPE_DRIVER,
79
+ exhaustive:
80
+ TYPE_PROTOCOL |
81
+ TYPE_MANAGER |
82
+ TYPE_MANAGER_EXTRA |
83
+ TYPE_TRANSPORT |
84
+ TYPE_DRIVER |
85
+ TYPE_DRIVER_EXTRA |
86
+ TYPE_QUEUE |
87
+ TYPE_TIME |
88
+ TYPE_SERVICE,
89
+ };
90
+
91
+ // eslint-disable-next-line
92
+ let log = () => {};
93
+
94
+ if (ENMASSE_DEBUG) {
95
+ log = (...args) => {
96
+ if (typeof console === 'undefined' || !console.log) {
97
+ return;
98
+ }
99
+
100
+ // parse arguments
101
+ // var args = Array.prototype.slice.call(arguments);
102
+
103
+ // the first argument is the debug type
104
+ const [type] = args;
105
+
106
+ // the rest of arguments are arbitrary data
107
+ const rest = args.slice(1);
108
+
109
+ // const time = ['getMinutes', 'getSeconds']
110
+ // .map((f) => new Date()[f]())
111
+ // .map((v) => (v < 10 ? '0' : '') + v)
112
+ // .join(':');
113
+
114
+ // add current time
115
+ // rest.unshift(time);
116
+
117
+ // do not log if mask doesn't contain current type
118
+ if ((LEVELS[ENMASSE_DEBUG] & type) === 0) {
119
+ return;
120
+ }
121
+
122
+ console.log(...rest);
123
+ };
124
+ }
125
+
126
+ class Queue {
127
+ constructor(config = {}) {
128
+ // the main storage for messages
129
+ this.queue = [];
130
+
131
+ // stash of the queue
132
+ this.stashed = [];
133
+
134
+ this.running = false;
135
+
136
+ this.runner = typeof config.runner === 'function' ? config.runner : null;
137
+
138
+ // list of channels we subscribed to
139
+ this.subscriptions = [];
140
+ }
141
+
142
+ push(message, force = false) {
143
+ log(TYPE_QUEUE, 'Queue::push', message, force);
144
+
145
+ const { klass, channel } = message;
146
+
147
+ if (!force) {
148
+ switch (klass) {
149
+ case SSCP_KLASS_LOGIN:
150
+ case SSCP_KLASS_LOGOUT:
151
+ case SSCP_KLASS_AUTHR:
152
+ break;
153
+ case SSCP_KLASS_SUB:
154
+ if (this.subscriptions.indexOf(channel) === -1) {
155
+ this.subscriptions.push(channel);
156
+ } else {
157
+ return;
158
+ }
159
+ break;
160
+ case SSCP_KLASS_UNSUB: {
161
+ const index = this.subscriptions.indexOf(channel);
162
+
163
+ if (index !== -1) {
164
+ this.subscriptions.splice(index, 1);
165
+ } else {
166
+ return;
167
+ }
168
+ break;
169
+ }
170
+ default:
171
+ if (this.subscriptions.indexOf(channel) === -1) {
172
+ return;
173
+ }
174
+ break;
175
+ }
176
+ }
177
+
178
+ this.queue.push(message);
179
+ }
180
+
181
+ getSubscriptions() {
182
+ return this.subscriptions;
183
+ }
184
+
185
+ run() {
186
+ log(
187
+ TYPE_QUEUE,
188
+ 'Queue::run',
189
+ `running=${this.running}, head=${this.queue[0]}`,
190
+ );
191
+
192
+ // abort if whether
193
+ // 1. runner function is not set
194
+ // 2. OR queue is already running
195
+ // 3. OR there is no messages in the queue
196
+ if (this.runner === null || this.running || !this.queue[0]) {
197
+ return;
198
+ }
199
+
200
+ this.running = true;
201
+
202
+ this.runner(this.queue.shift());
203
+ }
204
+
205
+ success() {
206
+ log(TYPE_QUEUE, 'Queue::success');
207
+
208
+ this.running = false;
209
+
210
+ this.run();
211
+ }
212
+
213
+ failure() {
214
+ log(TYPE_QUEUE, 'Queue::failure');
215
+
216
+ this.running = false;
217
+ }
218
+
219
+ stash() {
220
+ log(TYPE_QUEUE, 'Queue::stash');
221
+
222
+ let item;
223
+
224
+ // eslint-disable-next-line
225
+ while (undefined !== (item = this.queue.shift())) {
226
+ this.stashed.push(item);
227
+ }
228
+ }
229
+
230
+ unstash() {
231
+ log(TYPE_QUEUE, 'Queue::unstash');
232
+
233
+ let item;
234
+
235
+ // eslint-disable-next-line
236
+ while (undefined !== (item = this.stashed.shift())) {
237
+ this.queue.push(item);
238
+ }
239
+ }
240
+ }
241
+
242
+ // Returns current local timestamp
243
+ const local = () => new Date() / 1000;
244
+
245
+ // Server time default to local time
246
+ let serverTime = local();
247
+
248
+ // Time when the server time was received on the client
249
+ let clientTimeAtSync = local();
250
+
251
+ const setTime = (newTime) => {
252
+ log(TYPE_TIME, 'Time::setTime', newTime);
253
+
254
+ serverTime = newTime;
255
+ // offset = local() - time;
256
+ clientTimeAtSync = local();
257
+ };
258
+
259
+ const now = () => {
260
+ const elapsedTime = local() - clientTimeAtSync;
261
+
262
+ return Math.round(serverTime + elapsedTime);
263
+ };
264
+
265
+ class Transport extends sdkUtil.Emitter {
266
+ // generic events
267
+ static ON_READY = 'ready';
268
+ static ON_MESSAGE = 'message';
269
+
270
+ // connection state events
271
+ static ON_CONNECTED = 'connected';
272
+ static ON_CONNECT_FAILED = 'connect_failed';
273
+ static ON_CONNECT_LOST = 'connect_lost';
274
+ static ON_DISCONNECTED = 'disconnected';
275
+
276
+ // states
277
+ static STATE_CONNECTED = 'connected';
278
+ static STATE_CONNECTING = 'connecting';
279
+ static STATE_DISCONNECTED = 'disconnected';
280
+ static STATE_DISCONNECTING = 'disconnecting';
281
+
282
+ constructor() {
283
+ super();
284
+
285
+ this.state = this.STATE_DISCONNECTED;
286
+
287
+ this.trigger = this.emit;
288
+ this.bind = this.on;
289
+ this.unbind = this.off;
290
+ }
291
+ }
292
+
293
+ /* eslint class-methods-use-this: "off" */
294
+
295
+ class Driver extends sdkUtil.Emitter {
296
+ static ON_READY = 'ready';
297
+ static ON_MESSAGE = 'message';
298
+ static ON_CONNECTED = 'connected';
299
+ static ON_DISCONNECTED = 'disconnected';
300
+
301
+ constructor() {
302
+ super();
303
+
304
+ this.connected = false;
305
+
306
+ this.trigger = this.emit;
307
+ this.bind = this.on;
308
+ this.unbind = this.off;
309
+ }
310
+
311
+ connect() {}
312
+
313
+ disconnect() {}
314
+
315
+ // Arguments:
316
+ //
317
+ // data = null
318
+ // success = () => {}
319
+ // failure = () => {}
320
+ send() {}
321
+
322
+ init() {
323
+ this.trigger(Driver.ON_READY);
324
+ }
325
+ }
326
+
327
+ const globals$1 = sdkUtil.getGlobal();
328
+
329
+ class XHRDriver extends Driver {
330
+ constructor(config = {}) {
331
+ super();
332
+
333
+ this.config = config;
334
+ this.requests = {};
335
+ this.requestIdx = 0;
336
+ }
337
+
338
+ // CORS request wrapper
339
+ _request(url, success = () => {}, failure = () => {}, abort = () => {}) {
340
+ this.requestIdx += 1;
341
+
342
+ log(
343
+ TYPE_DRIVER_EXTRA,
344
+ `XHRDriver::request url=${url}, idx=${this.requestIdx}`,
345
+ );
346
+
347
+ const self = this;
348
+ let xhr = new XMLHttpRequest();
349
+ const method = 'POST';
350
+
351
+ if (typeof XDomainRequest !== 'undefined') {
352
+ xhr = new XDomainRequest();
353
+ xhr.open(method, url);
354
+ } else if ('withCredentials' in xhr) {
355
+ xhr.open(method, url, true);
356
+ xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
357
+ } else {
358
+ return null;
359
+ }
360
+
361
+ xhr._idx = this.requestIdx;
362
+
363
+ xhr.onload = function onLoad() {
364
+ log(
365
+ TYPE_DRIVER_EXTRA,
366
+ `XHRDriver::request::onload idx=${xhr._idx}, response=${this.responseText}`,
367
+ );
368
+
369
+ if (this.responseText.length > 0) {
370
+ success(self._parseResponse(this.responseText));
371
+ } else {
372
+ failure(this.responseText);
373
+ }
374
+ };
375
+
376
+ xhr.onerror = function onError() {
377
+ log(
378
+ TYPE_DRIVER_EXTRA,
379
+ `XHRDriver::request::onerror idx=${xhr._idx}, response=${this.responseText}`,
380
+ );
381
+
382
+ failure(this.responseText);
383
+ };
384
+
385
+ // XHR doesn't trigger onabort event in IE < 10
386
+ // attaching custom abort handler and execute
387
+ // it manually on each abort() call
388
+ xhr._onabort = () => {
389
+ log(
390
+ TYPE_DRIVER_EXTRA,
391
+ `XHRDriver::request::onabort idx=${xhr._idx}`,
392
+ );
393
+
394
+ abort();
395
+ };
396
+
397
+ return xhr;
398
+ }
399
+
400
+ _parseResponse(response) {
401
+ const data = {};
402
+ const chunks = response.split('&');
403
+
404
+ for (let i = 0, l = chunks.length; i < l; i++) {
405
+ const [key, value] = chunks[i].split('=');
406
+
407
+ const normalisedValue = decodeURIComponent(value.replace('\0', ''));
408
+
409
+ if (key in data) {
410
+ if (data[key].constructor !== Array) {
411
+ data[key] = [data[key]];
412
+ }
413
+ data[key].push(normalisedValue);
414
+ } else {
415
+ data[key] = normalisedValue;
416
+ }
417
+ }
418
+
419
+ return data;
420
+ }
421
+
422
+ _removeRequest(idx) {
423
+ log(TYPE_DRIVER_EXTRA, 'XHRDriver::removeRequest', idx);
424
+
425
+ if (Object.prototype.hasOwnProperty.call(this.requests, idx)) {
426
+ delete this.requests[idx];
427
+ }
428
+ }
429
+
430
+ connect() {
431
+ log(TYPE_DRIVER, 'XHRDriver::connect');
432
+
433
+ if (this.connected) {
434
+ log(
435
+ TYPE_DRIVER,
436
+ 'XHRDriver::connect already been connected!',
437
+ );
438
+
439
+ return;
440
+ }
441
+
442
+ this.connected = true;
443
+
444
+ this.trigger(XHRDriver.ON_CONNECTED);
445
+ }
446
+
447
+ disconnect() {
448
+ log(TYPE_DRIVER, 'XHRDriver::disconnect');
449
+
450
+ if (this.connected) {
451
+ // abort all requests
452
+ for (const idx in this.requests) {
453
+ if (
454
+ Object.prototype.hasOwnProperty.call(this.requests, idx) &&
455
+ this.requests[idx].abort
456
+ ) {
457
+ this.requests[idx].abort();
458
+
459
+ if (this.requests[idx]._onabort) {
460
+ this.requests[idx]._onabort();
461
+ }
462
+
463
+ this._removeRequest(idx);
464
+ }
465
+ }
466
+
467
+ this.connected = false;
468
+ }
469
+
470
+ this.trigger(XHRDriver.ON_DISCONNECTED);
471
+ }
472
+
473
+ send(url, data, success = () => {}, failure = () => {}) {
474
+ log(TYPE_DRIVER, 'XHRDriver::send', data);
475
+
476
+ let xhr;
477
+ let retry = 1;
478
+
479
+ const send = () => {
480
+ // eslint-disable-next-line
481
+ xhr = this._request(url, onSuccess, onFailure, onAbort);
482
+
483
+ this.requests[xhr._idx] = xhr;
484
+
485
+ // fixes aborting requests in IE
486
+ setTimeout(() => {
487
+ xhr.send(data);
488
+ }, 0);
489
+ };
490
+
491
+ const onSuccess = (response) => {
492
+ log(TYPE_DRIVER, 'XHRDriver::success', data);
493
+ this._removeRequest(xhr._idx);
494
+ success(response);
495
+ };
496
+
497
+ const onFailure = (response) => {
498
+ log(TYPE_DRIVER, 'XHRDriver::failure, retry: ', retry);
499
+ this._removeRequest(xhr._idx);
500
+ if (retry <= this.config.request_retries) {
501
+ setTimeout(() => {
502
+ send();
503
+ retry++;
504
+ }, this.config.request_retry_delay * 1000);
505
+ } else {
506
+ failure(response);
507
+ }
508
+ };
509
+
510
+ const onAbort = () => {
511
+ log(TYPE_DRIVER, 'XHRDriver::abort', data);
512
+ this._removeRequest(xhr._idx);
513
+ failure();
514
+ };
515
+
516
+ send();
517
+ }
518
+
519
+ static hasSupport() {
520
+ return (
521
+ 'XDomainRequest' in globals$1 || 'withCredentials' in new XMLHttpRequest()
522
+ );
523
+ }
524
+ }
525
+
526
+ /* eslint class-methods-use-this: "off" */
527
+
528
+ const globals = sdkUtil.getGlobal();
529
+
530
+ class WebSocketDriver extends Driver {
531
+ constructor() {
532
+ super();
533
+
534
+ this.websocket = null;
535
+ this.closeTimeout = null;
536
+ }
537
+
538
+ _onMessage(e) {
539
+ log(TYPE_DRIVER, 'WebSocketDriver::onMessage', e.data);
540
+ this.trigger(WebSocketDriver.ON_MESSAGE, e.data);
541
+ }
542
+
543
+ _onOpen() {
544
+ log(TYPE_DRIVER, 'WebSocketDriver::onOpen');
545
+ this.connected = true;
546
+ this.trigger(WebSocketDriver.ON_CONNECTED);
547
+ }
548
+
549
+ _onClose() {
550
+ log(TYPE_DRIVER, 'WebSocketDriver::onClose');
551
+ this._disconnect();
552
+ }
553
+
554
+ _onError() {
555
+ log(TYPE_DRIVER, 'WebSocketDriver::onError');
556
+ }
557
+
558
+ _disconnect() {
559
+ log(TYPE_DRIVER, 'WebSocketDriver::disconnect');
560
+
561
+ this.connected = false;
562
+
563
+ // reset handlers if websocket was created
564
+ if (this.websocket !== null) {
565
+ this.websocket.onmessage = () => {};
566
+ this.websocket.onopen = () => {};
567
+ this.websocket.onclose = () => {};
568
+ this.websocket.onerror = () => {};
569
+ this.websocket = null;
570
+ }
571
+
572
+ clearTimeout(this.closeTimeout);
573
+
574
+ this.trigger(WebSocketDriver.ON_DISCONNECTED);
575
+ }
576
+
577
+ connect(host, port, secure) {
578
+ if (this.connected) {
579
+ return;
580
+ }
581
+
582
+ log(TYPE_DRIVER, 'WebSocketDriver::connect', host, port);
583
+
584
+ const url = `${secure ? 'wss' : 'ws'}://${host}:${port}/ws`;
585
+
586
+ this.websocket = new WebSocket(url);
587
+ this.websocket.onmessage = this._onMessage.bind(this);
588
+ this.websocket.onopen = this._onOpen.bind(this);
589
+ this.websocket.onclose = this._onClose.bind(this);
590
+ this.websocket.onerror = this._onError.bind(this);
591
+ }
592
+
593
+ disconnect() {
594
+ log(TYPE_DRIVER, 'WebSocketDriver::disconnect');
595
+
596
+ if (this.connected) {
597
+ // ON_DISCONNECTED will be triggered at onClose event
598
+ this.websocket.close();
599
+
600
+ // websocket does not fire onclose if network connection is lost
601
+ // set timeout to force fire ON_DISCONNECTED event
602
+ this.closeTimeout = setTimeout(this._disconnect.bind(this), 750);
603
+ } else {
604
+ // disconnect instantly if websocket wasn't connected or still connecting
605
+ this._disconnect();
606
+ }
607
+ }
608
+
609
+ send(data, success = () => {}) {
610
+ log(TYPE_DRIVER, 'WebSocketDriver::send', data);
611
+ this.websocket.send(data);
612
+ success();
613
+ }
614
+
615
+ static hasSupport() {
616
+ return 'WebSocket' in globals;
617
+ }
618
+ }
619
+
620
+ const COMMAND_CONNECT = 'connect';
621
+ const COMMAND_PASS = 'pass';
622
+ const COMMAND_POLL = 'poll';
623
+
624
+ class ProxyTransport extends Transport {
625
+ constructor(config = {}) {
626
+ log(TYPE_TRANSPORT, 'ProxyTransport::constructor', config);
627
+
628
+ super(config);
629
+
630
+ this.driver = config.driver || null;
631
+ this.host = config.host || null;
632
+ this.port = config.port || null;
633
+ this.secure = config.secure || false;
634
+
635
+ /*
636
+ * Proxy properties
637
+ * host and port should be always preserved on connect and reconnect
638
+ * drop proxyIp, childId and clientId on reconnect and disconnect
639
+ */
640
+
641
+ this.proxyIp = null;
642
+ this.childId = null;
643
+ this.clientId = null;
644
+ this.sessionId = null;
645
+
646
+ this.driver.bind(Driver.ON_READY, this._handleDriverReady.bind(this));
647
+ this.driver.bind(
648
+ Driver.ON_CONNECTED,
649
+ this._handleDriverConnected.bind(this),
650
+ );
651
+ this.driver.bind(
652
+ Driver.ON_DISCONNECTED,
653
+ this._handleDriverDisconnected.bind(this),
654
+ );
655
+ }
656
+
657
+ _url() {
658
+ return (
659
+ `${this.secure ? 'https' : 'http'}://` +
660
+ `${this.proxyIp ? this.proxyIp : this.host}:` +
661
+ `${this.port}/` +
662
+ `${this.childId !== null ? `${this.childId}/` : ''}` +
663
+ `?_r=${new Date().getTime()}`
664
+ );
665
+ }
666
+
667
+ _request(params = '', success = () => {}, failure = () => {}) {
668
+ const self = this;
669
+
670
+ const onSuccess = (response) => {
671
+ // fire ON_MESSAGE event unless sscp message is empty
672
+ if (response.sscp.constructor === String) {
673
+ response.sscp = [response.sscp];
674
+ }
675
+
676
+ for (let i = 0, l = response.sscp.length; i < l; i++) {
677
+ if (response.sscp[i] !== '') {
678
+ self.trigger(ProxyTransport.ON_MESSAGE, response.sscp[i]);
679
+ }
680
+ }
681
+
682
+ success(response);
683
+ };
684
+
685
+ const onFailure = () => {
686
+ switch (self.state) {
687
+ case ProxyTransport.STATE_CONNECTED:
688
+ self.state = ProxyTransport.STATE_DISCONNECTING;
689
+ self.trigger(ProxyTransport.ON_CONNECT_FAILED);
690
+ break;
691
+ case ProxyTransport.STATE_CONNECTING:
692
+ self.state = ProxyTransport.STATE_DISCONNECTING;
693
+ self.trigger(ProxyTransport.ON_CONNECT_FAILED);
694
+ break;
695
+ case ProxyTransport.STATE_DISCONNECTING:
696
+ self.state = ProxyTransport.STATE_DISCONNECTED;
697
+ self.trigger(ProxyTransport.ON_DISCONNECTED);
698
+ break;
699
+ }
700
+
701
+ failure();
702
+ };
703
+
704
+ this.driver.send(this._url(), params, onSuccess, onFailure);
705
+ }
706
+
707
+ _poll() {
708
+ const params =
709
+ `command=${COMMAND_POLL}` +
710
+ `&clientid=${this.clientId}` +
711
+ `&sid=${this.sessionId}`;
712
+
713
+ this._request(params, this._poll.bind(this));
714
+ }
715
+
716
+ _connect() {
717
+ const self = this;
718
+
719
+ const onSuccess = (response) => {
720
+ self.proxyIp = response.proxy;
721
+ self.clientId = parseInt(response.clientid, 10);
722
+ self.childId = parseInt(response.childid, 10);
723
+ self.sessionId = response.sid;
724
+
725
+ self.state = ProxyTransport.STATE_CONNECTED;
726
+
727
+ self.trigger(ProxyTransport.ON_CONNECTED);
728
+
729
+ self._poll();
730
+ };
731
+
732
+ const onFailure = () => {
733
+ self.state = ProxyTransport.STATE_DISCONNECTED;
734
+ self.trigger(ProxyTransport.ON_CONNECT_FAILED);
735
+ };
736
+
737
+ const params = `command=${COMMAND_CONNECT}`;
738
+
739
+ this._request(params, onSuccess, onFailure);
740
+ }
741
+
742
+ _handleDriverReady() {
743
+ this.trigger(ProxyTransport.ON_READY);
744
+ }
745
+
746
+ _handleDriverConnected() {
747
+ this._connect();
748
+ }
749
+
750
+ _handleDriverDisconnected() {
751
+ log(
752
+ TYPE_TRANSPORT,
753
+ 'ProxyTransport::handleDriverDisconnected',
754
+ );
755
+
756
+ const { state } = this;
757
+
758
+ this.state = ProxyTransport.STATE_DISCONNECTED;
759
+
760
+ switch (state) {
761
+ case ProxyTransport.STATE_CONNECTED:
762
+ this.trigger(ProxyTransport.ON_CONNECT_LOST);
763
+ break;
764
+ case ProxyTransport.STATE_CONNECTING:
765
+ this.trigger(ProxyTransport.ON_CONNECT_FAILED);
766
+ break;
767
+ case ProxyTransport.STATE_DISCONNECTING:
768
+ this.trigger(ProxyTransport.ON_DISCONNECTED);
769
+ break;
770
+ }
771
+ }
772
+
773
+ send(sscp, success = () => {}, failure = () => {}) {
774
+ log(TYPE_TRANSPORT, 'ProxyTransport::send', sscp);
775
+
776
+ const params =
777
+ `command=${COMMAND_PASS}` +
778
+ `&clientid=${this.clientId}` +
779
+ `&sscp=${sscp}` +
780
+ `&sid=${this.sessionId}`;
781
+
782
+ this._request(params, success, failure);
783
+ }
784
+
785
+ connect() {
786
+ log(
787
+ TYPE_TRANSPORT,
788
+ 'ProxyTransport::connect',
789
+ this.host,
790
+ this.port,
791
+ );
792
+
793
+ this.state = ProxyTransport.STATE_CONNECTING;
794
+ this.driver.connect();
795
+ }
796
+
797
+ disconnect() {
798
+ log(TYPE_TRANSPORT, 'ProxyTransport::disconnect');
799
+
800
+ // drop proxy settings
801
+ this.proxyIp = null;
802
+ this.childId = null;
803
+ this.sessionId = null;
804
+ this.state = ProxyTransport.STATE_DISCONNECTING;
805
+
806
+ this.driver.disconnect();
807
+ }
808
+
809
+ init() {
810
+ log(TYPE_TRANSPORT, 'ProxyTransport::init');
811
+
812
+ this.driver.init();
813
+ }
814
+ }
815
+
816
+ class SocketTransport extends Transport {
817
+ constructor(config = {}) {
818
+ log(TYPE_TRANSPORT, 'SocketTransport::constructor', config);
819
+
820
+ super();
821
+
822
+ this.driver = config.driver || null;
823
+ this.host = config.host || null;
824
+ this.port = config.port || null;
825
+ this.secure = config.secure || false;
826
+
827
+ this.driver.bind(Driver.ON_READY, this._handleDriverReady.bind(this));
828
+ this.driver.bind(Driver.ON_MESSAGE, this._handleDriverMessage.bind(this));
829
+ this.driver.bind(
830
+ Driver.ON_CONNECTED,
831
+ this._handleDriverConnected.bind(this),
832
+ );
833
+ this.driver.bind(
834
+ Driver.ON_DISCONNECTED,
835
+ this._handleDriverDisconnected.bind(this),
836
+ );
837
+ }
838
+
839
+ _handleDriverReady() {
840
+ log(TYPE_TRANSPORT, 'SocketTransport::_handleDriverReady');
841
+
842
+ this.trigger(SocketTransport.ON_READY);
843
+ }
844
+
845
+ _handleDriverMessage(message) {
846
+ log(TYPE_TRANSPORT, 'SocketTransport::handleDriverMessage');
847
+
848
+ this.trigger(SocketTransport.ON_MESSAGE, message);
849
+ }
850
+
851
+ _handleDriverConnected() {
852
+ log(TYPE_TRANSPORT, 'SocketTransport::handleDriverConnected');
853
+
854
+ this.state = SocketTransport.STATE_CONNECTED;
855
+ this.trigger(SocketTransport.ON_CONNECTED);
856
+ }
857
+
858
+ _handleDriverDisconnected() {
859
+ log(
860
+ TYPE_TRANSPORT,
861
+ 'SocketTransport::handleDriverDisconnected',
862
+ );
863
+
864
+ const { state } = this;
865
+
866
+ this.state = SocketTransport.STATE_DISCONNECTED;
867
+
868
+ switch (state) {
869
+ case SocketTransport.STATE_CONNECTED:
870
+ this.trigger(SocketTransport.ON_CONNECT_LOST);
871
+ break;
872
+ case SocketTransport.STATE_CONNECTING:
873
+ this.trigger(SocketTransport.ON_CONNECT_FAILED);
874
+ break;
875
+ case SocketTransport.STATE_DISCONNECTING:
876
+ this.trigger(SocketTransport.ON_DISCONNECTED);
877
+ break;
878
+ }
879
+ }
880
+
881
+ send(sscp, success = () => {}, failure = () => {}) {
882
+ log(TYPE_TRANSPORT, 'SocketTransport::send', sscp);
883
+ this.driver.send(sscp, success, failure);
884
+ }
885
+
886
+ connect() {
887
+ log(
888
+ TYPE_TRANSPORT,
889
+ 'SocketTransport::connect',
890
+ this.host,
891
+ this.port,
892
+ );
893
+ this.state = SocketTransport.STATE_CONNECTING;
894
+ this.driver.connect(this.host, this.port, this.secure);
895
+ }
896
+
897
+ disconnect() {
898
+ log(TYPE_TRANSPORT, 'SocketTransport::disconnect');
899
+ this.state = SocketTransport.STATE_DISCONNECTING;
900
+ this.driver.disconnect();
901
+ }
902
+
903
+ init() {
904
+ log(TYPE_TRANSPORT, 'SocketTransport::init');
905
+ this.driver.init();
906
+ }
907
+ }
908
+
909
+ const METHOD_PROXY = 'proxy';
910
+ const METHOD_WEBSOCKET = 'websocket';
911
+
912
+ class TransportManager extends sdkUtil.Emitter {
913
+ constructor() {
914
+ super();
915
+
916
+ this.trigger = this.emit;
917
+ this.bind = this.on;
918
+ this.unbind = this.off;
919
+
920
+ this.ON_READY = 'ready';
921
+ this.config = null;
922
+ this.idx = 0;
923
+
924
+ // list of connection types pairs: transport - driver
925
+ this.methods = {};
926
+ this.transports = {};
927
+ this.loaded = 0;
928
+ this.sequence = [];
929
+ this.allowed = [];
930
+ this.secure = true;
931
+
932
+ // Guarding the use of the window object in environments where it might
933
+ // not be available, such as during server - side rendering in a framework
934
+ // like Next.js
935
+ if (typeof window !== 'undefined') {
936
+ this.secure = window.location.protocol.indexOf('https') === 0;
937
+ }
938
+
939
+ // list of connection methods which where unsuccessful
940
+ this.unsuccessful = [];
941
+ }
942
+
943
+ _instantiate() {
944
+ log(TYPE_MANAGER, 'TransportManager::instantiate');
945
+
946
+ // instantiate all transports
947
+ this.allowed.forEach((method) => {
948
+ const config = this._getMethodConfig(method);
949
+
950
+ // pick the random host
951
+ const host =
952
+ config.hosts[Math.floor(Math.random() * config.hosts.length)];
953
+
954
+ const TransportKlass = this.methods[method].transport;
955
+ const DriverKlass = this.methods[method].driver;
956
+
957
+ const transport = new TransportKlass({
958
+ driver: new DriverKlass(config.driver || {}),
959
+ host,
960
+ port: this.secure ? config.secure_port : config.port,
961
+ secure: this.secure,
962
+ });
963
+
964
+ this.transports[method] = transport;
965
+
966
+ transport.bind(Transport.ON_READY, this._handleTransportReady.bind(this));
967
+ }, this);
968
+
969
+ // initialise all transports
970
+ for (const method in this.transports) {
971
+ if (Object.prototype.hasOwnProperty.call(this.transports, method)) {
972
+ this.transports[method].init();
973
+ }
974
+ }
975
+ }
976
+
977
+ _getMethodConfig(method) {
978
+ return this.config.methods.find((item) => item.type === method);
979
+ }
980
+
981
+ _handleTransportReady() {
982
+ this.loaded += 1;
983
+
984
+ if (this.loaded === this.allowed.length) {
985
+ this.trigger(this.ON_READY);
986
+ }
987
+ }
988
+
989
+ current() {
990
+ return this.transports[this.allowed[this.idx]];
991
+ }
992
+
993
+ next() {
994
+ this.idx += 1;
995
+
996
+ if (this.idx >= this.allowed.length) {
997
+ return false;
998
+ }
999
+
1000
+ return this.current();
1001
+ }
1002
+
1003
+ reset() {
1004
+ this.allowed = [];
1005
+ this.unsuccessful = [];
1006
+
1007
+ // gather all methods in one array in correct connection sequence
1008
+ for (let i = 0, l = this.sequence.length; i < l; i += 1) {
1009
+ // shift method's idx by the value of the current method idx
1010
+ const method = this.sequence[(i + this.idx) % l];
1011
+
1012
+ if (this.methods[method].driver.hasSupport()) {
1013
+ this.allowed.push(method);
1014
+ }
1015
+ }
1016
+
1017
+ this.idx = 0;
1018
+ }
1019
+
1020
+ addUnsuccessful(transport) {
1021
+ for (const method in this.transports) {
1022
+ if (
1023
+ Object.prototype.hasOwnProperty.call(this.transports, method) &&
1024
+ this.transports[method] === transport
1025
+ ) {
1026
+ this.unsuccessful.push(method);
1027
+ }
1028
+ }
1029
+ }
1030
+
1031
+ init(config = {}) {
1032
+ this.config = config;
1033
+ this.idx = 0;
1034
+ this.loaded = 0;
1035
+ this.sequence = [];
1036
+ this.secure = config.forceSecure === true ? true : this.secure;
1037
+
1038
+ this.methods[METHOD_PROXY] = {
1039
+ driver: XHRDriver,
1040
+ transport: ProxyTransport,
1041
+ };
1042
+ this.methods[METHOD_WEBSOCKET] = {
1043
+ driver: WebSocketDriver,
1044
+ transport: SocketTransport,
1045
+ };
1046
+
1047
+ if (ENMASSE_OVERRIDE_METHODS) {
1048
+ this.sequence = ENMASSE_OVERRIDE_METHODS;
1049
+ } else {
1050
+ this.config.methods.forEach(
1051
+ (method) => this.sequence.push(method.type),
1052
+ this,
1053
+ );
1054
+ }
1055
+
1056
+ // remove unsupported methods
1057
+ /* eslint-disable */
1058
+ this.sequence = this.sequence.filter((item) =>
1059
+ Object.prototype.hasOwnProperty.call(this.methods, item),
1060
+ );
1061
+ /* eslint-enable */
1062
+
1063
+ this.reset();
1064
+
1065
+ this._instantiate();
1066
+ }
1067
+ }
1068
+
1069
+ var TransportManager$1 = new TransportManager();
1070
+
1071
+ /* eslint @typescript-eslint/no-use-before-define: ["error", { "variables": false }] */
1072
+
1073
+ const ConnectionManager = (() => {
1074
+ const self = new sdkUtil.Emitter();
1075
+
1076
+ self.trigger = self.emit;
1077
+ self.bind = self.on;
1078
+ self.unbind = self.off;
1079
+
1080
+ /*
1081
+ * Constants
1082
+ */
1083
+ const CONNECT_DELAYS = [0, 3, 5, 10, 20, 30, 45, 60];
1084
+
1085
+ /*
1086
+ * States
1087
+ */
1088
+ self.STATE_DISCONNECTED = 'state_disconnected';
1089
+ self.STATE_DISCONNECTING = 'state_disconnecting';
1090
+ self.STATE_CONNECTING = 'state_connecting';
1091
+ self.STATE_CONNECTED = 'state_connected';
1092
+
1093
+ /*
1094
+ * Events
1095
+ */
1096
+ self.ON_MESSAGE = 'message';
1097
+ self.ON_STATE = 'state';
1098
+
1099
+ /*
1100
+ * Private variables
1101
+ */
1102
+ let config = null;
1103
+ let state = self.STATE_DISCONNECTED;
1104
+ let transport = null;
1105
+ let attempt = 1;
1106
+
1107
+ // Timestamp to track when the app was backgrounded
1108
+ let backgroundedAt = null;
1109
+
1110
+ const inactivityTimeout = {
1111
+ start() {
1112
+ log(
1113
+ TYPE_MANAGER_EXTRA,
1114
+ 'ConnectionManager::inactivityTimeout::start',
1115
+ );
1116
+
1117
+ const stamp = now();
1118
+
1119
+ this.interval = setInterval(() => {
1120
+ if (now() - stamp >= config.inactivity_timeout) {
1121
+ this.resolve();
1122
+ }
1123
+ }, 1000);
1124
+ },
1125
+
1126
+ stop() {
1127
+ log(
1128
+ TYPE_MANAGER_EXTRA,
1129
+ 'ConnectionManager::inactivityTimeout::stop',
1130
+ );
1131
+
1132
+ clearInterval(this.interval);
1133
+ },
1134
+
1135
+ reset() {
1136
+ log(
1137
+ TYPE_MANAGER_EXTRA,
1138
+ 'ConnectionManager::inactivityTimeout::reset',
1139
+ );
1140
+
1141
+ this.stop();
1142
+ this.start();
1143
+ },
1144
+
1145
+ resolve() {
1146
+ log(
1147
+ TYPE_MANAGER_EXTRA,
1148
+ 'ConnectionManager::inactivityTimeout::resolve',
1149
+ );
1150
+
1151
+ disconnectingOnConnectLost();
1152
+ },
1153
+ };
1154
+
1155
+ const connectTimeout = {
1156
+ start() {
1157
+ log(
1158
+ TYPE_MANAGER_EXTRA,
1159
+ 'ConnectionManager::connectTimeout::start',
1160
+ );
1161
+
1162
+ this.timeout = setTimeout(() => {
1163
+ this.resolve();
1164
+ }, config.connect_timeout * 1000);
1165
+ },
1166
+
1167
+ stop() {
1168
+ log(
1169
+ TYPE_MANAGER_EXTRA,
1170
+ 'ConnectionManager::connectTimeout::stop',
1171
+ );
1172
+
1173
+ clearTimeout(this.timeout);
1174
+ },
1175
+
1176
+ resolve() {
1177
+ log(
1178
+ TYPE_MANAGER_EXTRA,
1179
+ 'ConnectionManager::connectTimeout::resolve',
1180
+ );
1181
+
1182
+ disconnectingOnConnectFailed();
1183
+ },
1184
+ };
1185
+
1186
+ const connectDelay = {
1187
+ active: false,
1188
+
1189
+ start(delay) {
1190
+ log(
1191
+ TYPE_MANAGER_EXTRA,
1192
+ 'ConnectionManager::connectDelay::start',
1193
+ );
1194
+
1195
+ this.active = true;
1196
+ this.timeout = setTimeout(() => {
1197
+ this.resolve();
1198
+ }, delay * 1000);
1199
+ },
1200
+
1201
+ stop() {
1202
+ log(
1203
+ TYPE_MANAGER_EXTRA,
1204
+ 'ConnectionManager::connectDelay::stop',
1205
+ );
1206
+
1207
+ this.active = false;
1208
+ clearTimeout(this.timeout);
1209
+ },
1210
+
1211
+ resolve() {
1212
+ log(
1213
+ TYPE_MANAGER_EXTRA,
1214
+ 'ConnectionManager::connectDelay::resolve',
1215
+ );
1216
+
1217
+ this.stop();
1218
+ connect();
1219
+ },
1220
+ };
1221
+
1222
+ const initTransport = (newTransport) => {
1223
+ log(TYPE_MANAGER, 'ConnectionManager::initTransport');
1224
+
1225
+ transport = newTransport;
1226
+
1227
+ transport.bind(Transport.ON_MESSAGE, handleTransportMessage);
1228
+ transport.bind(Transport.ON_CONNECTED, handleTransportConnected);
1229
+ transport.bind(Transport.ON_CONNECT_FAILED, handleTransportConnectFailed);
1230
+ transport.bind(Transport.ON_CONNECT_LOST, handleTransportConnectLost);
1231
+ transport.bind(Transport.ON_DISCONNECTED, handleTransportDisconnected);
1232
+ };
1233
+
1234
+ const teardownTransport = () => {
1235
+ log(TYPE_MANAGER, 'ConnectionManager::teardownTransport');
1236
+
1237
+ if (transport === null) {
1238
+ return;
1239
+ }
1240
+
1241
+ transport.unbind(Transport.ON_MESSAGE, handleTransportMessage);
1242
+ transport.unbind(Transport.ON_CONNECTED, handleTransportConnected);
1243
+ transport.unbind(Transport.ON_CONNECT_FAILED, handleTransportConnectFailed);
1244
+ transport.unbind(Transport.ON_CONNECT_LOST, handleTransportConnectLost);
1245
+ transport.unbind(Transport.ON_DISCONNECTED, handleTransportDisconnected);
1246
+
1247
+ transport = null;
1248
+ };
1249
+
1250
+ const handleTransportMessage = (message) => {
1251
+ log(
1252
+ TYPE_MANAGER,
1253
+ 'ConnectionManager::handleTransportMessage',
1254
+ message,
1255
+ );
1256
+
1257
+ const messages = message.split('\0');
1258
+
1259
+ for (let i = 0; i < messages.length; i += 1) {
1260
+ if (messages[i].length > 0) {
1261
+ inactivityTimeout.reset();
1262
+ log(
1263
+ TYPE_MANAGER,
1264
+ 'ConnectionManager::handleTransportMessage',
1265
+ 'send upstream m:',
1266
+ messages[i],
1267
+ );
1268
+ self.trigger(self.ON_MESSAGE, messages[i]);
1269
+ }
1270
+ }
1271
+ };
1272
+
1273
+ const handleTransportConnected = () => {
1274
+ log(
1275
+ TYPE_MANAGER,
1276
+ 'ConnectionManager::handleTransportConnected',
1277
+ );
1278
+ setStateToConnected();
1279
+ };
1280
+
1281
+ const handleTransportConnectFailed = () => {
1282
+ log(
1283
+ TYPE_MANAGER,
1284
+ 'ConnectionManager::handleTransportConnectFailed',
1285
+ );
1286
+ disconnectingOnConnectFailed();
1287
+ };
1288
+
1289
+ const handleTransportConnectLost = () => {
1290
+ log(
1291
+ TYPE_MANAGER,
1292
+ 'ConnectionManager::handleTransportConnectLost',
1293
+ );
1294
+ disconnectingOnConnectLost();
1295
+ };
1296
+
1297
+ const handleTransportDisconnected = () => {
1298
+ log(
1299
+ TYPE_MANAGER,
1300
+ 'ConnectionManager::handleTransportDisconnected',
1301
+ );
1302
+
1303
+ switch (state) {
1304
+ case self.STATE_CONNECTED:
1305
+ disconnectedOnConnectLost();
1306
+ break;
1307
+ case self.STATE_CONNECTING:
1308
+ disconnectedOnConnectFailed();
1309
+ break;
1310
+ case self.STATE_DISCONNECTING:
1311
+ setStateToDisconnected();
1312
+ break;
1313
+ }
1314
+ };
1315
+
1316
+ const disconnectingOnConnectFailed = () => {
1317
+ log(
1318
+ TYPE_MANAGER,
1319
+ 'ConnectionManager::disconnectingOnConnectFailed()',
1320
+ );
1321
+
1322
+ disconnect();
1323
+ };
1324
+
1325
+ const disconnectingOnConnectLost = () => {
1326
+ log(
1327
+ TYPE_MANAGER,
1328
+ 'ConnectionManager::disconnectingOnConnectLost()',
1329
+ );
1330
+
1331
+ disconnect();
1332
+ };
1333
+
1334
+ const disconnectedOnConnectLost = () => {
1335
+ log(
1336
+ TYPE_MANAGER,
1337
+ 'ConnectionManager::disconnectedOnConnectLost()',
1338
+ );
1339
+
1340
+ teardownTransport();
1341
+
1342
+ // automatically reconnect
1343
+ setStateToConnecting();
1344
+ };
1345
+
1346
+ const disconnectedOnConnectFailed = () => {
1347
+ log(
1348
+ TYPE_MANAGER,
1349
+ 'ConnectionManager::disconnectedOnConnectFailed()',
1350
+ );
1351
+
1352
+ TransportManager$1.addUnsuccessful(transport);
1353
+
1354
+ teardownTransport();
1355
+
1356
+ if (TransportManager$1.next() === false) {
1357
+ setStateToConnecting();
1358
+ } else {
1359
+ connect();
1360
+ }
1361
+ };
1362
+
1363
+ const setStateToConnecting = () => {
1364
+ log(
1365
+ TYPE_MANAGER,
1366
+ 'ConnectionManager::setStateToConnecting()',
1367
+ );
1368
+
1369
+ let delay;
1370
+
1371
+ if (attempt >= CONNECT_DELAYS.length) {
1372
+ delay = CONNECT_DELAYS[CONNECT_DELAYS.length - 1];
1373
+ } else {
1374
+ delay = CONNECT_DELAYS[attempt - 1];
1375
+ }
1376
+
1377
+ state = self.STATE_CONNECTING;
1378
+ self.trigger(self.ON_STATE, state, attempt, delay);
1379
+
1380
+ TransportManager$1.reset();
1381
+
1382
+ attempt += 1;
1383
+ connectDelay.start(delay);
1384
+ };
1385
+
1386
+ const setStateToConnected = () => {
1387
+ log(TYPE_MANAGER, 'ConnectionManager::setStateToConnected()');
1388
+
1389
+ attempt = 1;
1390
+ TransportManager$1.reset();
1391
+ inactivityTimeout.reset();
1392
+ connectTimeout.stop();
1393
+ connectDelay.stop();
1394
+
1395
+ state = self.STATE_CONNECTED;
1396
+ self.trigger(self.ON_STATE, state);
1397
+ };
1398
+
1399
+ const setStateToDisconnecting = () => {
1400
+ log(
1401
+ TYPE_MANAGER,
1402
+ 'ConnectionManager::setStateToDisconnecting()',
1403
+ );
1404
+
1405
+ state = self.STATE_DISCONNECTING;
1406
+ self.trigger(self.ON_STATE, state);
1407
+
1408
+ disconnect();
1409
+ };
1410
+
1411
+ const setStateToDisconnected = () => {
1412
+ log(
1413
+ TYPE_MANAGER,
1414
+ 'ConnectionManager::setStateToDisconnected()',
1415
+ );
1416
+
1417
+ attempt = 1;
1418
+ teardownTransport();
1419
+ TransportManager$1.reset();
1420
+
1421
+ state = self.STATE_DISCONNECTED;
1422
+ self.trigger(self.ON_STATE, state);
1423
+ };
1424
+
1425
+ const connect = () => {
1426
+ if (state === self.STATE_CONNECTING) {
1427
+ connectTimeout.start();
1428
+ initTransport(TransportManager$1.current());
1429
+ transport.connect();
1430
+ }
1431
+ };
1432
+
1433
+ const disconnect = () => {
1434
+ inactivityTimeout.stop();
1435
+ connectTimeout.stop();
1436
+ connectDelay.stop();
1437
+
1438
+ if (transport) {
1439
+ transport.disconnect();
1440
+ } else {
1441
+ setStateToDisconnected();
1442
+ }
1443
+ };
1444
+
1445
+ /**
1446
+ * Handles changes in the document's visibility state (visible/hidden).
1447
+ *
1448
+ * - Tracks when the document is backgrounded by recording the timestamp.
1449
+ * - When the document becomes visible again, checks if the time spent in the background
1450
+ * exceeds a configured timeout. If it does, the connection is disconnected.
1451
+ * - Ensures actions are only taken if necessary, based on the current connection state
1452
+ * and whether the page was previously backgrounded.
1453
+ *
1454
+ * Note on Safari (ios and desktop) and Firefox quirk: visibilitychange event is fired
1455
+ * immidiately when the page is loaded, even if the page is visible.
1456
+ */
1457
+ const handleVisibilityChange = () => {
1458
+ log(
1459
+ TYPE_MANAGER,
1460
+ 'ConnectionManager::handleVisibilityChange()',
1461
+ document.visibilityState,
1462
+ );
1463
+
1464
+ switch (document.visibilityState) {
1465
+ case 'visible':
1466
+ if (backgroundedAt === null) {
1467
+ return;
1468
+ }
1469
+
1470
+ if (state !== self.STATE_CONNECTED) {
1471
+ return;
1472
+ }
1473
+
1474
+ if (Date.now() - backgroundedAt > config.background_timeout * 1000) {
1475
+ log(
1476
+ TYPE_MANAGER,
1477
+ 'ConnectionManager::handleVisibilityChange()',
1478
+ 'disconnecting due to background timeout',
1479
+ );
1480
+
1481
+ disconnect();
1482
+ }
1483
+
1484
+ backgroundedAt = null;
1485
+ break;
1486
+ case 'hidden':
1487
+ backgroundedAt = Date.now();
1488
+ break;
1489
+ }
1490
+ };
1491
+
1492
+ const handleNetworkStatus = () => {
1493
+ if (!navigator.onLine && state === self.STATE_CONNECTED) {
1494
+ log(
1495
+ TYPE_MANAGER,
1496
+ 'ConnectionManager::handleNetworkStatus()',
1497
+ 'disconnecting due to network loss',
1498
+ );
1499
+ disconnect();
1500
+ }
1501
+ };
1502
+
1503
+ self.connect = () => {
1504
+ log(TYPE_MANAGER, 'ConnectionManager::connect()');
1505
+
1506
+ // resolve connect instantly if the process has been already postponed
1507
+ if (connectDelay.active) {
1508
+ connectDelay.resolve();
1509
+ } else if (state === self.STATE_DISCONNECTED) {
1510
+ setStateToConnecting();
1511
+ }
1512
+
1513
+ window.addEventListener('online', handleNetworkStatus);
1514
+ window.addEventListener('offline', handleNetworkStatus);
1515
+ window.addEventListener('visibilitychange', handleVisibilityChange);
1516
+ };
1517
+
1518
+ self.disconnect = () => {
1519
+ log(TYPE_MANAGER, 'ConnectionManager::disconnect()');
1520
+
1521
+ setStateToDisconnecting();
1522
+
1523
+ window.removeEventListener('online', handleNetworkStatus);
1524
+ window.removeEventListener('offline', handleNetworkStatus);
1525
+ window.removeEventListener('visibilitychange', handleVisibilityChange);
1526
+ };
1527
+
1528
+ self.send = (sscp, success, failure) => {
1529
+ log(TYPE_MANAGER, 'ConnectionManager::send', sscp);
1530
+ success = success || (() => {});
1531
+ failure = failure || (() => {});
1532
+ transport.send(sscp, success, failure);
1533
+ };
1534
+
1535
+ self.getConnectAttempt = () => attempt;
1536
+
1537
+ self.init = (cfg) => {
1538
+ log(TYPE_MANAGER, 'ConnectionManager::init()', cfg);
1539
+ config = cfg;
1540
+ };
1541
+
1542
+ return self;
1543
+ })();
1544
+
1545
+ /* eslint-disable no-console */
1546
+
1547
+ class Demographics extends sdkUtil.Emitter {
1548
+ constructor(...args) {
1549
+ super(...args);
1550
+
1551
+ this.ON_ADD_COUNTER = 'add-counter';
1552
+
1553
+ this.trigger = this.emit;
1554
+ this.bind = this.on;
1555
+ this.unbind = this.off;
1556
+ }
1557
+
1558
+ addCounter(
1559
+ channel,
1560
+ eventId,
1561
+ segment,
1562
+ counter,
1563
+ round = null,
1564
+ min = null,
1565
+ max = null,
1566
+ ) {
1567
+ log(
1568
+ TYPE_SERVICE,
1569
+ 'Demographics::addCounter',
1570
+ channel,
1571
+ eventId,
1572
+ segment,
1573
+ counter,
1574
+ round,
1575
+ min,
1576
+ max,
1577
+ );
1578
+
1579
+ if (!channel) {
1580
+ return;
1581
+ }
1582
+
1583
+ if (!eventId) {
1584
+ return;
1585
+ }
1586
+
1587
+ if (
1588
+ !segment ||
1589
+ segment.constructor !== Array ||
1590
+ !segment.length ||
1591
+ segment.length % 2 !== 0
1592
+ ) {
1593
+ return;
1594
+ }
1595
+
1596
+ if (counter !== parseInt(counter, 10)) {
1597
+ return;
1598
+ }
1599
+
1600
+ const keys = segment.filter((v, i) => (i + 1) % 2).join(':');
1601
+ const values = segment.filter((v, i) => i % 2).join(':');
1602
+
1603
+ this.trigger(this.ON_ADD_COUNTER, channel, [
1604
+ eventId,
1605
+ min,
1606
+ max,
1607
+ round,
1608
+ keys,
1609
+ values,
1610
+ counter,
1611
+ ]);
1612
+ }
1613
+ }
1614
+
1615
+ var Demographics$1 = new Demographics();
1616
+
1617
+ /* eslint-disable no-console */
1618
+
1619
+ const Enmasse = (() => {
1620
+ /*
1621
+ * Link to the scope
1622
+ */
1623
+
1624
+ const self = new sdkUtil.Emitter();
1625
+
1626
+ self.trigger = self.emit;
1627
+ self.bind = self.on;
1628
+ self.unbind = self.off;
1629
+
1630
+ /*
1631
+ * Constants
1632
+ */
1633
+
1634
+ self.VERSION = version;
1635
+ self.PROTOCOL_VERSION = '8';
1636
+
1637
+ self.SSCP_KLASS_AUTH = SSCP_KLASS_AUTH;
1638
+ self.SSCP_KLASS_AUTHR = SSCP_KLASS_AUTHR;
1639
+ self.SSCP_KLASS_AUTHOK = SSCP_KLASS_AUTHOK;
1640
+ self.SSCP_KLASS_PING = SSCP_KLASS_PING;
1641
+ self.SSCP_KLASS_SUB = SSCP_KLASS_SUB;
1642
+ self.SSCP_KLASS_UNSUB = SSCP_KLASS_UNSUB;
1643
+ self.SSCP_KLASS_EOC = SSCP_KLASS_EOC;
1644
+ self.SSCP_KLASS_LOGIN = SSCP_KLASS_LOGIN;
1645
+ self.SSCP_KLASS_LOGOUT = SSCP_KLASS_LOGOUT;
1646
+ self.SSCP_KLASS_COUNTER = SSCP_KLASS_COUNTER;
1647
+
1648
+ self.ON_STATE = 'on_state';
1649
+ self.ON_MESSAGE = 'on_message';
1650
+ self.ON_EOC = 'on_eoc';
1651
+ self.ON_READY = 'on_ready';
1652
+ self.ON_ERROR = 'on_error';
1653
+
1654
+ self.STATE_DISCONNECTED = 'disconnected';
1655
+ self.STATE_CONNECTED = 'connected';
1656
+ self.STATE_CONNECTING = 'connecting';
1657
+
1658
+ self.Demographics = Demographics$1;
1659
+
1660
+ // var CALLBACK_NAME = "__enmasse_callback";
1661
+ // var CONFIG_TIMEOUT = 5000;
1662
+
1663
+ const PARAM_SESSION_ID = 'enmasse_session_id';
1664
+
1665
+ /*
1666
+ * Enmasse properties
1667
+ */
1668
+
1669
+ // Link to the config file.
1670
+ let configFile = null;
1671
+
1672
+ // list of arbitray data passed into handshake
1673
+ // message along with the protocol version
1674
+ let info = [];
1675
+
1676
+ // session id is generated by server and passed
1677
+ // on client on handshake
1678
+ let sessionId = null;
1679
+
1680
+ // out messages queue
1681
+ let queue = null;
1682
+
1683
+ // current state of the state machine
1684
+ let state = self.STATE_DISCONNECTED;
1685
+
1686
+ // system channel
1687
+ let sysChannel = null;
1688
+
1689
+ // force secure connection
1690
+ let forceSecure = false;
1691
+
1692
+ // sign in promise object
1693
+ let loginContext = {
1694
+ inProgress: false,
1695
+ success: () => {},
1696
+ failure: () => {},
1697
+ };
1698
+
1699
+ /*
1700
+ * Private methods
1701
+ */
1702
+
1703
+ const storeSession = async (id) => {
1704
+ try {
1705
+ await sdkStorageKit.storageWrite(PARAM_SESSION_ID, id);
1706
+ } catch (err) {
1707
+ console.error(
1708
+ `Failed to persist session to shared storage: ${sdkUtil.getErrorMessage(err)}`,
1709
+ );
1710
+ }
1711
+ };
1712
+
1713
+ const restoreSession = async () => {
1714
+ let restoredId = null;
1715
+
1716
+ // Read session from shared storage
1717
+ try {
1718
+ restoredId = await sdkStorageKit.storageRead(PARAM_SESSION_ID);
1719
+ } catch (err) {
1720
+ console.error(
1721
+ `Failed to read session from shared storage: ${sdkUtil.getErrorMessage(err)}`,
1722
+ );
1723
+ }
1724
+
1725
+ if (restoredId !== null) {
1726
+ return restoredId;
1727
+ }
1728
+
1729
+ /**
1730
+ * Further down is a special case when we try to restore session id from the
1731
+ * local storage and save it to the shared storage. This could happen in
1732
+ * previous versions of the SDK when we didn't have a shared storage.
1733
+ */
1734
+
1735
+ if (sdkLauncherKit.getParentApplication() === null) {
1736
+ return null;
1737
+ }
1738
+
1739
+ try {
1740
+ restoredId = sdkUtil.getItem(PARAM_SESSION_ID);
1741
+ } catch (err) {
1742
+ console.error(
1743
+ `Failed to read local session during migration: ${sdkUtil.getErrorMessage(
1744
+ err,
1745
+ )}`,
1746
+ );
1747
+ }
1748
+
1749
+ if (restoredId === null) {
1750
+ return null;
1751
+ }
1752
+
1753
+ try {
1754
+ await sdkStorageKit.storageWrite(PARAM_SESSION_ID, restoredId);
1755
+ } catch (err) {
1756
+ console.error(
1757
+ `Failed to persist session to shared storage during migration: ${sdkUtil.getErrorMessage(
1758
+ err,
1759
+ )}`,
1760
+ );
1761
+ }
1762
+
1763
+ return restoredId;
1764
+ };
1765
+
1766
+ const resetSession = async () => {
1767
+ sessionId = null;
1768
+
1769
+ try {
1770
+ await sdkStorageKit.storageRemove(PARAM_SESSION_ID);
1771
+ } catch (err) {
1772
+ console.error(
1773
+ `Failed to remove session from shared storage: ${sdkUtil.getErrorMessage(err)}`,
1774
+ );
1775
+ }
1776
+ };
1777
+
1778
+ const validateSession = (id) =>
1779
+ // eslint-disable-next-line
1780
+ /^[a-f\d]{8}\-[a-f\d]{4}\-[a-f\d]{4}-[a-f\d]{4}\-[a-f\d]{12}$/i.test(id);
1781
+
1782
+ const finaliseLogin = (callback) => {
1783
+ if (loginContext.inProgress === false) {
1784
+ return;
1785
+ }
1786
+
1787
+ loginContext = {
1788
+ inProgress: false,
1789
+ success: () => {},
1790
+ failure: () => {},
1791
+ };
1792
+
1793
+ callback();
1794
+ };
1795
+
1796
+ const resolveLogin = () => {
1797
+ finaliseLogin(loginContext.success);
1798
+ };
1799
+
1800
+ const rejectLogin = () => {
1801
+ finaliseLogin(loginContext.failure);
1802
+ };
1803
+
1804
+ const setStateToDisconnected = () => {
1805
+ log(TYPE_PROTOCOL, 'Enmasse::setStateToDisconnected()');
1806
+
1807
+ state = self.STATE_DISCONNECTED;
1808
+ sysChannel = null;
1809
+
1810
+ rejectLogin();
1811
+
1812
+ self.trigger(self.ON_STATE, state);
1813
+ };
1814
+
1815
+ const setStateToConnected = () => {
1816
+ log(TYPE_PROTOCOL, 'Enmasse::setStateToConnected()');
1817
+
1818
+ const time = now();
1819
+ state = self.STATE_CONNECTED;
1820
+ self.trigger(self.ON_STATE, state, time);
1821
+ };
1822
+
1823
+ const setStateToConnecting = (attempt, delay) => {
1824
+ log(TYPE_PROTOCOL, 'Enmasse::setStateToConnecting()');
1825
+
1826
+ state = self.STATE_CONNECTING;
1827
+ rejectLogin();
1828
+
1829
+ self.trigger(self.ON_STATE, state, attempt, delay);
1830
+ };
1831
+
1832
+ const parseSSCP = (sscp) => {
1833
+ const data = {
1834
+ channel: 0,
1835
+ klass: '',
1836
+ sent_at: 0,
1837
+ body: [],
1838
+ };
1839
+
1840
+ const separator1 = sscp.indexOf('/');
1841
+
1842
+ if (separator1 !== -1) {
1843
+ const separator2 = sscp.indexOf('/', separator1 + 1);
1844
+
1845
+ if (separator2 !== -1) {
1846
+ data.channel = sscp.substr(0, separator1);
1847
+ data.klass = sscp.substr(separator1 + 1, separator2 - separator1 - 1);
1848
+
1849
+ const separator3 = sscp.indexOf('/', separator2 + 1);
1850
+
1851
+ if (separator3 !== -1) {
1852
+ data.sent_at = parseInt(
1853
+ sscp.substr(separator2 + 1, separator3 - separator2 - 1),
1854
+ 10,
1855
+ );
1856
+
1857
+ const body = sscp.substr(separator3 + 1);
1858
+
1859
+ if (body !== '') {
1860
+ const values = body.split('|');
1861
+
1862
+ for (let i = 0, l = values.length; i < l; i += 1) {
1863
+ data.body[i] = decodeURIComponent(values[i]);
1864
+ }
1865
+ }
1866
+ }
1867
+ }
1868
+ }
1869
+
1870
+ return data;
1871
+ };
1872
+
1873
+ const stringifySSCP = (message) =>
1874
+ [
1875
+ message.channel,
1876
+ message.klass,
1877
+ Math.floor(new Date() / 1000),
1878
+ message.body.join('|'),
1879
+ ].join('/');
1880
+
1881
+ const runner = (message) => {
1882
+ const success = () => {
1883
+ queue.success();
1884
+ };
1885
+
1886
+ const failure = () => {
1887
+ queue.failure();
1888
+ };
1889
+
1890
+ ConnectionManager.send(stringifySSCP(message), success, failure);
1891
+ };
1892
+
1893
+ const handleTransportManagerReady = () => {
1894
+ log(TYPE_PROTOCOL, 'Enmasse::handleTransportManagerReady');
1895
+ self.trigger(self.ON_READY);
1896
+ };
1897
+
1898
+ const handleConnectionManagerState = (...args) => {
1899
+ log(
1900
+ TYPE_PROTOCOL,
1901
+ 'Enmasse::handleConnectionManagerState',
1902
+ args,
1903
+ );
1904
+
1905
+ switch (args[0]) {
1906
+ // case ConnectionManager.STATE_DISCONNECTING:
1907
+ // break;
1908
+ case ConnectionManager.STATE_DISCONNECTED:
1909
+ setStateToDisconnected();
1910
+ break;
1911
+ case ConnectionManager.STATE_CONNECTING:
1912
+ setStateToConnecting(args[1], args[2]);
1913
+ break;
1914
+ case ConnectionManager.STATE_CONNECTED:
1915
+ break;
1916
+ }
1917
+ };
1918
+
1919
+ const handleConnectionManagerMessage = async (message) => {
1920
+ log(
1921
+ TYPE_PROTOCOL,
1922
+ 'Enmasse::handleConnectionManagerMessage',
1923
+ message,
1924
+ );
1925
+
1926
+ const data = parseSSCP(message);
1927
+
1928
+ switch (data.klass) {
1929
+ case SSCP_KLASS_AUTH: {
1930
+ const time = data.sent_at;
1931
+
1932
+ const clientInfo = [
1933
+ `enmassejs-${self.VERSION}`,
1934
+ navigator.userAgent,
1935
+ ].concat(info);
1936
+
1937
+ // store system channel
1938
+ sysChannel = data.channel;
1939
+
1940
+ // url encode each client info item
1941
+ clientInfo.forEach((item, idx) => {
1942
+ clientInfo[idx] = encodeURIComponent(item);
1943
+ });
1944
+
1945
+ if (sessionId === null) {
1946
+ [sessionId] = data.body;
1947
+ await storeSession(sessionId);
1948
+ }
1949
+
1950
+ setTime(time);
1951
+
1952
+ queue.stash();
1953
+
1954
+ queue.push(
1955
+ {
1956
+ channel: sysChannel,
1957
+ klass: SSCP_KLASS_AUTHR,
1958
+ body: [sessionId, self.PROTOCOL_VERSION, clientInfo.join(',')],
1959
+ },
1960
+ true,
1961
+ );
1962
+
1963
+ queue.run();
1964
+
1965
+ break;
1966
+ }
1967
+ case SSCP_KLASS_AUTHOK: {
1968
+ // We have to resubscribe to the previously
1969
+ // subscribed channels after the reconnect.
1970
+ const subscriptions = queue.getSubscriptions();
1971
+
1972
+ subscriptions.forEach((channel) => {
1973
+ queue.push(
1974
+ {
1975
+ channel,
1976
+ klass: SSCP_KLASS_SUB,
1977
+ body: [],
1978
+ },
1979
+ true,
1980
+ );
1981
+ });
1982
+
1983
+ queue.unstash();
1984
+ queue.run();
1985
+
1986
+ setStateToConnected();
1987
+ break;
1988
+ }
1989
+ case SSCP_KLASS_LOGIN_OK:
1990
+ resolveLogin();
1991
+ break;
1992
+ case SSCP_KLASS_LOGIN_FAIL:
1993
+ rejectLogin();
1994
+ break;
1995
+ case SSCP_KLASS_PING:
1996
+ // ignoring ping message handling
1997
+ break;
1998
+ case SSCP_KLASS_EOC:
1999
+ self.trigger(self.ON_EOC, data.channel);
2000
+ break;
2001
+ default:
2002
+ self.trigger(self.ON_MESSAGE, data);
2003
+ break;
2004
+ }
2005
+ };
2006
+
2007
+ const handleDemographicsCounter = (channel, data) => {
2008
+ self.send(channel, 'avgcounter', data);
2009
+ };
2010
+
2011
+ /**
2012
+ * @internal
2013
+ */
2014
+ self.PARAM_SESSION_ID = PARAM_SESSION_ID;
2015
+
2016
+ /**
2017
+ * @internal
2018
+ */
2019
+ self.resetSession = resetSession;
2020
+
2021
+ /*
2022
+ * Public methods
2023
+ */
2024
+
2025
+ self.connect = () => {
2026
+ log(TYPE_PROTOCOL, 'Enmasse::connect');
2027
+
2028
+ ConnectionManager.connect();
2029
+ };
2030
+
2031
+ self.disconnect = () => {
2032
+ log(TYPE_PROTOCOL, 'Enmasse:disconnect');
2033
+
2034
+ ConnectionManager.disconnect();
2035
+ };
2036
+
2037
+ self.send = (channel, klass, body) => {
2038
+ log(TYPE_PROTOCOL, 'Enmasse::send', channel, klass, body);
2039
+
2040
+ body = body || [];
2041
+
2042
+ queue.push({
2043
+ channel,
2044
+ klass,
2045
+ body,
2046
+ });
2047
+
2048
+ if (state === self.STATE_CONNECTED) {
2049
+ queue.run();
2050
+ }
2051
+ };
2052
+
2053
+ self.getSessionId = () => sessionId;
2054
+
2055
+ self.getState = () => state;
2056
+
2057
+ self.subscribe = (channel) => {
2058
+ self.send(channel, SSCP_KLASS_SUB);
2059
+ };
2060
+
2061
+ self.unsubscribe = (channel) => {
2062
+ self.send(channel, SSCP_KLASS_UNSUB);
2063
+ };
2064
+
2065
+ self.login = (
2066
+ userId,
2067
+ timestamp,
2068
+ signature,
2069
+ success = () => {},
2070
+ failure = () => {},
2071
+ ) => {
2072
+ if (sysChannel === null) {
2073
+ return;
2074
+ }
2075
+
2076
+ if (loginContext.inProgress) {
2077
+ console.warn('Login is already in progress');
2078
+ return;
2079
+ }
2080
+
2081
+ loginContext = { inProgress: true, success, failure };
2082
+
2083
+ self.send(sysChannel, SSCP_KLASS_LOGIN, [userId, timestamp, signature]);
2084
+ };
2085
+
2086
+ self.logout = () => {
2087
+ if (sysChannel === null) {
2088
+ return;
2089
+ }
2090
+
2091
+ if (loginContext.inProgress) {
2092
+ rejectLogin();
2093
+ }
2094
+
2095
+ self.send(sysChannel, SSCP_KLASS_LOGOUT);
2096
+ };
2097
+
2098
+ self.init = async function init(config = () => {}) {
2099
+ // config file is passed either as GET parameter or via constructor
2100
+ configFile = !ENMASSE_CONFIG ? config.config || null : ENMASSE_CONFIG;
2101
+ info = config.info || [];
2102
+ sessionId = await restoreSession();
2103
+
2104
+ if (typeof config.forceSecure === 'boolean') {
2105
+ forceSecure = config.forceSecure;
2106
+ }
2107
+
2108
+ if (configFile === null) {
2109
+ // eslint-disable-next-line
2110
+ throw 'EnMasse config file is not set';
2111
+ }
2112
+
2113
+ // If session id has invalid format
2114
+ if (sessionId !== null && !validateSession(sessionId)) {
2115
+ await resetSession();
2116
+ }
2117
+
2118
+ queue = new Queue({
2119
+ runner,
2120
+ });
2121
+
2122
+ TransportManager$1.bind(
2123
+ TransportManager$1.ON_READY,
2124
+ handleTransportManagerReady,
2125
+ );
2126
+ ConnectionManager.bind(
2127
+ ConnectionManager.ON_STATE,
2128
+ handleConnectionManagerState,
2129
+ );
2130
+ ConnectionManager.bind(
2131
+ ConnectionManager.ON_MESSAGE,
2132
+ handleConnectionManagerMessage,
2133
+ );
2134
+ Demographics$1.bind(Demographics$1.ON_ADD_COUNTER, handleDemographicsCounter);
2135
+
2136
+ try {
2137
+ const response = await fetch(configFile);
2138
+
2139
+ const { status, statusText: text } = response;
2140
+
2141
+ let data;
2142
+
2143
+ try {
2144
+ data = await response.json();
2145
+ } catch (jsonErr) {
2146
+ self.trigger(self.ON_ERROR, `Invalid JSON from config ${configFile}`);
2147
+ return;
2148
+ }
2149
+
2150
+ const isSuccess =
2151
+ (status >= 200 && status <= 300) ||
2152
+ // If you conclude with an XMLHttpRequest receiving status=0 and
2153
+ // statusText=null, this means the request was not allowed to be
2154
+ // performed.
2155
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#xmlhttprequests_being_stopped
2156
+ (status === 0 && !!text) ||
2157
+ status === '';
2158
+
2159
+ data.forceSecure = forceSecure;
2160
+
2161
+ if (data.background_timeout === undefined) {
2162
+ data.background_timeout = 0;
2163
+ }
2164
+
2165
+ if (isSuccess) {
2166
+ ConnectionManager.init(data);
2167
+ TransportManager$1.init(data);
2168
+ } else {
2169
+ self.trigger(self.ON_ERROR, `Failed to load config ${configFile}`);
2170
+ }
2171
+ } catch (err) {
2172
+ self.trigger(self.ON_ERROR, `Error loading config: ${err.message}`);
2173
+ }
2174
+ };
2175
+
2176
+ return self;
2177
+ })();
2178
+
2179
+ module.exports = Enmasse;
2180
+ //# sourceMappingURL=index.cjs.map