relayx-js 1.0.0

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.
@@ -0,0 +1,769 @@
1
+ import { History } from "./history.js";
2
+ import axios from 'axios';
3
+ import { connect, JSONCodec, Events, DebugEvents, AckPolicy, ReplayPolicy, credsAuthenticator } from "nats";
4
+ import { DeliverPolicy, jetstream, jetstreamManager } from "@nats-io/jetstream";
5
+ import { readFileSync } from "fs"
6
+
7
+ export class Realtime {
8
+
9
+ #baseUrl = "";
10
+
11
+ #natsClient = null;
12
+ #codec = JSONCodec();
13
+ #jetstream = null;
14
+ #jsManager = null;
15
+ #streamTracker = [];
16
+ #consumerMap = {};
17
+
18
+ #event_func = {};
19
+ #topicMap = [];
20
+
21
+ #config = "CiAgICAgICAgLS0tLS1CRUdJTiBOQVRTIFVTRVIgSldULS0tLS0KICAgICAgICBKV1RfS0VZCiAgICAgICAgLS0tLS0tRU5EIE5BVFMgVVNFUiBKV1QtLS0tLS0KCiAgICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKiBJTVBPUlRBTlQgKioqKioqKioqKioqKioqKioqKioqKioqKgogICAgICAgIE5LRVkgU2VlZCBwcmludGVkIGJlbG93IGNhbiBiZSB1c2VkIHRvIHNpZ24gYW5kIHByb3ZlIGlkZW50aXR5LgogICAgICAgIE5LRVlzIGFyZSBzZW5zaXRpdmUgYW5kIHNob3VsZCBiZSB0cmVhdGVkIGFzIHNlY3JldHMuCgogICAgICAgIC0tLS0tQkVHSU4gVVNFUiBOS0VZIFNFRUQtLS0tLQogICAgICAgIFNFQ1JFVF9LRVkKICAgICAgICAtLS0tLS1FTkQgVVNFUiBOS0VZIFNFRUQtLS0tLS0KCiAgICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgogICAgICAgIA=="
22
+
23
+ // Status Codes
24
+ #RECONNECTING = "RECONNECTING";
25
+ #RECONNECTED = "RECONNECTED";
26
+ #RECONN_FAIL = "RECONN_FAIL";
27
+
28
+ setRemoteUserAttempts = 0;
29
+ setRemoteUserRetries = 5;
30
+
31
+ // Retry attempts end
32
+ reconnected = false;
33
+ disconnected = true;
34
+ reconnecting = false;
35
+ connected = false;
36
+
37
+ // Offline messages
38
+ #offlineMessageBuffer = [];
39
+
40
+ // History API
41
+ history = null;
42
+
43
+ // Test Variables
44
+ #timeout = 1000;
45
+
46
+ #maxPublishRetries = 5;
47
+
48
+ constructor(config){
49
+ if(typeof config != "object"){
50
+ throw new Error("Realtime($config). $config not object => {}")
51
+ }
52
+
53
+ if(config != null && config != undefined){
54
+ this.api_key = config.api_key != undefined ? config.api_key : null;
55
+ this.secret = config.secret != undefined ? config.secret : null;
56
+
57
+ if(this.api_key == null){
58
+ throw new Error("api_key value null")
59
+ }
60
+
61
+ if(this.secret == null){
62
+ throw new Error("secret value null")
63
+ }
64
+ }else{
65
+ throw new Error("{api_key: <value>, secret: <value>} not passed in constructor")
66
+ }
67
+
68
+ this.namespace = null;
69
+ }
70
+
71
+ /*
72
+ Initialized library with configuration options.
73
+ */
74
+ async init(staging, opts){
75
+ /**
76
+ * Method can take in 2 variables
77
+ * @param{boolean} staging - Sets URL to staging or production URL
78
+ * @param{Object} opts - Library configuration options
79
+ */
80
+ var len = arguments.length;
81
+
82
+ if (len > 2){
83
+ new Error("Method takes only 2 variables, " + len + " given");
84
+ }
85
+
86
+ if (len == 2){
87
+ if(typeof arguments[0] == "boolean"){
88
+ staging = arguments[0];
89
+ }else{
90
+ staging = false;
91
+ }
92
+
93
+ if(arguments[1] instanceof Object){
94
+ opts = arguments[1];
95
+ }else{
96
+ opts = {};
97
+ }
98
+ }else if(len == 1){
99
+ if(arguments[0] instanceof Object){
100
+ opts = arguments[0];
101
+ staging = false;
102
+ }else{
103
+ opts = {};
104
+ staging = arguments[0];
105
+ this.#log(staging)
106
+ }
107
+ }else{
108
+ staging = false;
109
+ opts = {};
110
+ }
111
+
112
+ this.staging = staging;
113
+
114
+ if (staging !== undefined || staging !== null){
115
+ this.#baseUrl = staging ? [
116
+ "nats://0.0.0.0:4221",
117
+ "nats://0.0.0.0:4222",
118
+ "nats://0.0.0.0:4223",
119
+ "nats://0.0.0.0:4224",
120
+ "nats://0.0.0.0:4225",
121
+ "nats://0.0.0.0:4226"] :
122
+ [
123
+ "nats://api.relay-x.io:4221",
124
+ "nats://api.relay-x.io:4222",
125
+ "nats://api.relay-x.io:4223",
126
+ "nats://api.relay-x.io:4224",
127
+ "nats://api.relay-x.io:4225",
128
+ "nats://api.relay-x.io:4226",
129
+ ];
130
+ }else{
131
+ this.#baseUrl = [
132
+ "nats://api.relay-x.io:4221",
133
+ "nats://api.relay-x.io:4222",
134
+ "nats://api.relay-x.io:4223",
135
+ "nats://api.relay-x.io:4224",
136
+ "nats://api.relay-x.io:4225",
137
+ "nats://api.relay-x.io:4226",
138
+ ];
139
+ }
140
+
141
+ this.#log(this.#baseUrl);
142
+ this.#log(opts);
143
+
144
+ this.opts = opts;
145
+ }
146
+
147
+ /**
148
+ * Gets the namespace of the user using a REST API
149
+ * @returns {string} namespace value. Null if failed to retreive
150
+ */
151
+ async #getNameSpace() {
152
+ var res = await this.#natsClient.request("accounts.user.get_namespace",
153
+ this.#codec.encode({
154
+ "api_key": this.api_key
155
+ }),
156
+ {
157
+ timeout: 5000
158
+ }
159
+ )
160
+
161
+ var data = res.json()
162
+
163
+ this.#log(data)
164
+
165
+ if(data["status"] == "NAMESPACE_RETRIEVE_SUCCESS"){
166
+ this.namespace = data["data"]["namespace"]
167
+ }else{
168
+ this.namespace = null;
169
+ return
170
+ }
171
+ }
172
+
173
+
174
+ /**
175
+ * Connects to the relay network
176
+ */
177
+ async connect(){
178
+ this.SEVER_URL = this.#baseUrl;
179
+
180
+ var credsFile = this.#getUserCreds(this.api_key, this.secret)
181
+ credsFile = new TextEncoder().encode(credsFile);
182
+ var credsAuth = credsAuthenticator(credsFile);
183
+
184
+ try{
185
+ this.#natsClient = await connect({
186
+ servers: this.SEVER_URL,
187
+ noEcho: true,
188
+ maxReconnectAttempts: 1200,
189
+ reconnect: true,
190
+ reconnectTimeWait: 1000,
191
+ authenticator: credsAuth,
192
+ token: this.api_key
193
+ });
194
+
195
+ this.#jsManager = await jetstreamManager(this.#natsClient);
196
+ this.#jetstream = await jetstream(this.#natsClient);
197
+
198
+ await this.#getNameSpace()
199
+
200
+ this.connected = true;
201
+ }catch(err){
202
+ this.#log("ERR")
203
+ this.#log(err);
204
+
205
+ this.connected = false;
206
+ }
207
+
208
+ if (this.connected == true){
209
+ this.#log("Connected to server!");
210
+
211
+ // Callback on client side
212
+ if (CONNECTED in this.#event_func){
213
+ if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
214
+ this.#event_func[CONNECTED]()
215
+ }
216
+ }
217
+
218
+ this.#natsClient.closed().then(() => {
219
+ this.#log("the connection closed!");
220
+ });
221
+
222
+ (async () => {
223
+ for await (const s of this.#natsClient.status()) {
224
+ this.#log(s.type)
225
+
226
+ switch (s.type) {
227
+ case Events.Disconnect:
228
+ this.#log(`client disconnected - ${s.data}`);
229
+
230
+ this.connected = false;
231
+ this.#streamTracker = [];
232
+ this.#consumerMap = {};
233
+
234
+ if (DISCONNECTED in this.#event_func){
235
+ if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
236
+ this.#event_func[DISCONNECTED]()
237
+ }
238
+ }
239
+ break;
240
+ case Events.LDM:
241
+ this.#log("client has been requested to reconnect");
242
+ break;
243
+ case Events.Update:
244
+ this.#log(`client received a cluster update - `);
245
+ this.#log(s.data)
246
+ break;
247
+ case Events.Reconnect:
248
+ this.#log(`client reconnected -`);
249
+ this.#log(s.data)
250
+
251
+ this.reconnecting = false;
252
+ this.connected = true;
253
+
254
+ this.#subscribeToTopics();
255
+
256
+ if(RECONNECT in this.#event_func){
257
+ this.#event_func[RECONNECT](this.#RECONNECTED);
258
+ }
259
+
260
+ // Resend any messages sent while client was offline
261
+ this.#publishMessagesOnReconnect();
262
+ break;
263
+ case Events.Error:
264
+ this.#log("client got a permissions error");
265
+ break;
266
+ case DebugEvents.Reconnecting:
267
+ this.#log("client is attempting to reconnect");
268
+
269
+ this.reconnecting = true;
270
+
271
+ if(RECONNECT in this.#event_func && this.reconnecting){
272
+ this.#event_func[RECONNECT](this.#RECONNECTING);
273
+ }
274
+ break;
275
+ case DebugEvents.StaleConnection:
276
+ this.#log("client has a stale connection");
277
+ break;
278
+ default:
279
+ this.#log(`got an unknown status ${s.type}`);
280
+ }
281
+ }
282
+ })().then();
283
+
284
+ // Subscribe to topics
285
+ this.#subscribeToTopics();
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Closes connection
291
+ */
292
+ close(){
293
+ if(this.#natsClient !== null){
294
+ this.reconnected = false;
295
+ this.disconnected = true;
296
+
297
+ this.#natsClient.close();
298
+ }else{
299
+ this.#log("Null / undefined socket, cannot close connection");
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Start consumers for topics initialized by user
305
+ */
306
+ async #subscribeToTopics(){
307
+ this.#topicMap.forEach(async (topic) => {
308
+ // Subscribe to stream
309
+ await this.#startConsumer(topic);
310
+ });
311
+ }
312
+
313
+ /**
314
+ * Deletes reference to user defined event callback.
315
+ * This will stop listening to a topic
316
+ * @param {string} topic
317
+ * @returns {boolean} - To check if topic unsubscribe was successful
318
+ */
319
+ async off(topic){
320
+ if(topic == null || topic == undefined){
321
+ throw new Error("$topic is null / undefined")
322
+ }
323
+
324
+ if(typeof topic !== "string"){
325
+ throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
326
+ }
327
+
328
+ this.#topicMap = this.#topicMap.filter(item => item !== topic);
329
+
330
+ delete this.#event_func[topic];
331
+
332
+ return await this.#deleteConsumer(topic);
333
+ }
334
+
335
+ /**
336
+ * Subscribes to a topic
337
+ * @param {string} topic - Name of the event
338
+ * @param {function} func - Callback function to call on user thread
339
+ * @returns {boolean} - To check if topic subscription was successful
340
+ */
341
+ async on(topic, func){
342
+ if(topic == null || topic == undefined){
343
+ throw new Error("$topic is null / undefined")
344
+ }
345
+
346
+ if(func == null || func == undefined){
347
+ throw new Error("$func is null / undefined")
348
+ }
349
+
350
+ if ((typeof func !== "function")){
351
+ throw new Error(`Expected $listener type -> function. Instead receieved -> ${typeof func}`);
352
+ }
353
+
354
+ if(typeof topic !== "string"){
355
+ throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
356
+ }
357
+
358
+ if(!(topic in this.#event_func)){
359
+ this.#event_func[topic] = func;
360
+ }
361
+
362
+ if (![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
363
+ this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND].includes(topic)){
364
+ if(!this.#topicMap.includes(topic)){
365
+ this.#topicMap.push(topic);
366
+ }
367
+
368
+ if(this.connected){
369
+ // Connected we need to create a topic in a stream
370
+ await this.#startConsumer(topic);
371
+ }
372
+ }
373
+ }
374
+
375
+ /**
376
+ * A method to send a message to a topic.
377
+ * Retry methods included. Stores messages in an array if offline.
378
+ * @param {string} topic - Name of the event
379
+ * @param {object} data - Data to send
380
+ * @returns
381
+ */
382
+ async publish(topic, data){
383
+ if(topic == null || topic == undefined){
384
+ throw new Error("$topic is null or undefined");
385
+ }
386
+
387
+ if(topic == ""){
388
+ throw new Error("$topic cannot be an empty string")
389
+ }
390
+
391
+ if(typeof topic !== "string"){
392
+ throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
393
+ }
394
+
395
+ if(!this.isTopicValid(topic)){
396
+ throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
397
+ }
398
+
399
+ var start = Date.now()
400
+ var messageId = crypto.randomUUID();
401
+
402
+ var message = {
403
+ "client_id": this.#getClientId(),
404
+ "id": messageId,
405
+ "room": topic,
406
+ "message": data,
407
+ "start": Date.now()
408
+ }
409
+
410
+ var encodedMessage = this.#codec.encode(message)
411
+
412
+ if(this.connected){
413
+ if(!this.#topicMap.includes(topic)){
414
+ this.#topicMap.push(topic);
415
+
416
+ await this.#createOrGetStream();
417
+ }else{
418
+ this.#log(`${topic} exists locally, moving on...`)
419
+ }
420
+
421
+ this.#log(`Publishing to topic => ${this.#getStreamTopic(topic)}`)
422
+
423
+ const ack = await this.#jetstream.publish(this.#getStreamTopic(topic), encodedMessage);
424
+ this.#log(`Publish Ack =>`)
425
+ this.#log(ack)
426
+
427
+ var latency = Date.now() - start;
428
+ this.#log(`Latency => ${latency} ms`);
429
+
430
+ return ack !== null && ack !== undefined;
431
+ }else{
432
+ this.#offlineMessageBuffer.push({
433
+ topic: topic,
434
+ message: data
435
+ });
436
+
437
+ return false;
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Method resends messages when the client successfully connects to the
443
+ * server again
444
+ * @returns - Array of success and failure messages
445
+ */
446
+ async #publishMessagesOnReconnect(){
447
+ var messageSentStatus = [];
448
+
449
+ for(let i = 0; i < this.#offlineMessageBuffer.length; i++){
450
+ let data = this.#offlineMessageBuffer[i];
451
+
452
+ const topic = data.topic;
453
+ const message = data.message;
454
+
455
+ const output = await this.publish(topic, message);
456
+
457
+ messageSentStatus.push({
458
+ topic: topic,
459
+ message: message,
460
+ resent: output
461
+ });
462
+ }
463
+
464
+ // Clearing out offline messages
465
+ this.#offlineMessageBuffer.length = 0;
466
+
467
+ // Send to client
468
+ if(MESSAGE_RESEND in this.#event_func && messageSentStatus.length > 0){
469
+ this.#event_func[MESSAGE_RESEND](messageSentStatus);
470
+ }
471
+ }
472
+
473
+ // Room functions
474
+ /**
475
+ * Starts consumer for particular topic if stream exists
476
+ * @param {string} topic
477
+ */
478
+ async #startConsumer(topic){
479
+ await this.#createOrGetStream();
480
+
481
+ var opts = {
482
+ name: topic,
483
+ filter_subjects: [this.#getStreamTopic(topic), this.#getStreamTopic(topic) + "_presence"],
484
+ replay_policy: ReplayPolicy.Instant,
485
+ opt_start_time: new Date(),
486
+ }
487
+
488
+ const consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
489
+ this.#log(this.#topicMap)
490
+ this.#log("Consumer is consuming");
491
+
492
+ this.#consumerMap[topic] = consumer;
493
+
494
+ await consumer.consume({
495
+ callback: (msg) => {
496
+
497
+ msg.ack();
498
+
499
+ try{
500
+ var data = this.#codec.decode(msg.data);
501
+ var room = data.room;
502
+
503
+ this.#log(data);
504
+ const latency = Date.now() - data.start
505
+ this.#log(`Latency => ${latency}`)
506
+
507
+ // Push topic message to main thread
508
+ if (room in this.#event_func && data.client_id != this.#getClientId()){
509
+ this.#event_func[room]({
510
+ "id": data.id,
511
+ "data": data.message
512
+ });
513
+ }
514
+ }catch(err){
515
+ this.#log("Consumer err " + err);
516
+ msg.nack();
517
+ }
518
+ }
519
+ });
520
+ }
521
+
522
+ /**
523
+ * Deletes consumer
524
+ * @param {string} topic
525
+ */
526
+ async #deleteConsumer(topic){
527
+ const consumer = this.#consumerMap[topic]
528
+
529
+ var del = false;
530
+
531
+ if (consumer != null && consumer != undefined){
532
+ del = await consumer.delete();
533
+ }else{
534
+ del = true
535
+ }
536
+
537
+ delete this.#consumerMap[topic];
538
+
539
+ return del;
540
+ }
541
+
542
+ /**
543
+ * Gets stream if it exists or creates one
544
+ * @param {string} streamName
545
+ */
546
+ async #createOrGetStream(){
547
+ const streamName = this.#getStreamName();
548
+ var stream = null;
549
+
550
+ try{
551
+ stream = await this.#jsManager.streams.info(streamName);
552
+ }catch(err){
553
+ stream = null;
554
+ }
555
+
556
+ this.#log(`STREAM => ${stream}`)
557
+
558
+ if (stream == null){
559
+ // Stream does not exist, create one
560
+ await this.#jsManager.streams.add({
561
+ name: streamName,
562
+ subjects: [...this.#getStreamTopicList(), ...this.#getPresenceTopics()],
563
+ ack_policy: AckPolicy.Explicit,
564
+ delivery_policy: DeliverPolicy.New
565
+ });
566
+
567
+ this.#log(`${streamName} created`);
568
+ }else{
569
+ stream.config.subjects = [...this.#getStreamTopicList(), ...this.#getPresenceTopics()];
570
+ await this.#jsManager.streams.update(streamName, stream.config);
571
+
572
+ this.#log(`${streamName} exists, updating and moving on...`);
573
+ }
574
+ }
575
+
576
+ // Utility functions
577
+ #getClientId(){
578
+ return this.#natsClient?.info?.client_id
579
+ }
580
+
581
+ /**
582
+ * Checks if a topic can be used to send messages to.
583
+ * @param {string} topic - Name of event
584
+ * @returns {boolean} - If topic is valid or not
585
+ */
586
+ isTopicValid(topic){
587
+ if(topic !== null && topic !== undefined && (typeof topic) == "string"){
588
+ return ![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
589
+ this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND].includes(topic);
590
+ }else{
591
+ return false;
592
+ }
593
+ }
594
+
595
+ #getStreamName(){
596
+ if(this.namespace != null){
597
+ return this.namespace + "_stream"
598
+ }else{
599
+ this.close();
600
+ throw new Error("$namespace is null. Cannot initialize program with null $namespace")
601
+ }
602
+ }
603
+
604
+ #getStreamTopic(topic){
605
+ if(this.namespace != null){
606
+ return this.namespace + "_stream_" + topic;
607
+ }else{
608
+ this.close();
609
+ throw new Error("$namespace is null. Cannot initialize program with null $namespace")
610
+ }
611
+ }
612
+
613
+ #getStreamTopicList(){
614
+ var topics = [];
615
+
616
+ this.#topicMap.forEach((topic) => {
617
+ topics.push(this.#getStreamTopic(topic))
618
+ })
619
+
620
+ return topics
621
+ }
622
+
623
+ #getPresenceTopics(){
624
+ var presence = [];
625
+
626
+ this.#topicMap.forEach((topic) => {
627
+ presence.push(this.#getStreamTopic(topic) + "_presence")
628
+ })
629
+
630
+ return presence
631
+ }
632
+
633
+ sleep(ms) {
634
+ return new Promise(resolve => setTimeout(resolve, ms));
635
+ }
636
+
637
+ #log(msg){
638
+ if(this.opts?.debug){
639
+ console.log(msg);
640
+ }
641
+ }
642
+
643
+ #getPublishRetry(){
644
+ this.#log(this.opts)
645
+ if(this.opts !== null && this.opts !== undefined){
646
+ if(this.opts.max_retries !== null && this.opts.max_retries !== undefined){
647
+ if (this.opts.max_retries <= 0){
648
+ return this.#maxPublishRetries;
649
+ }else{
650
+ return this.opts.max_retries;
651
+ }
652
+ }else{
653
+ return this.#maxPublishRetries;
654
+ }
655
+ }else{
656
+ return this.#maxPublishRetries;
657
+ }
658
+ }
659
+
660
+ /**
661
+ *
662
+ * @param {function} func - Function to execute under retry
663
+ * @param {int} count - Number of times to retry
664
+ * @param {int} delay - Delay between each retry
665
+ * @param {...any} args - Args to pass to func
666
+ * @returns {any} - Output of the func method
667
+ */
668
+ async #retryTillSuccess(func, count, delay, ...args){
669
+ func = func.bind(this);
670
+
671
+ var output = null;
672
+ var success = false;
673
+ var methodDataOutput = null;
674
+
675
+ for(let i = 1; i <= count; i++){
676
+ this.#log(`Attempt ${i} at executing ${func.name}()`)
677
+
678
+ await this.sleep(delay)
679
+
680
+ output = await func(...args);
681
+ success = output.success;
682
+ // this.#log(output);
683
+
684
+ methodDataOutput = output.output;
685
+
686
+ if (success){
687
+ this.#log(`Successfully called ${func.name}`)
688
+ break;
689
+ }
690
+ }
691
+
692
+ if(!success){
693
+ this.#log(`${func.name} executed ${count} times BUT not a success`);
694
+ }
695
+
696
+ return methodDataOutput;
697
+ }
698
+
699
+ #getUserCreds(jwt, secret){
700
+ var template = Buffer.from(this.#config, "base64").toString("utf8")
701
+
702
+ var creds = template.replace("JWT_KEY", jwt);
703
+ creds = creds.replace("SECRET_KEY", secret)
704
+
705
+ return creds
706
+ }
707
+
708
+ // Exposure for tests
709
+ testRetryTillSuccess(){
710
+ if(process.env.NODE_ENV == "test"){
711
+ return this.#retryTillSuccess.bind(this);
712
+ }else{
713
+ return null;
714
+ }
715
+ }
716
+
717
+ testGetPublishRetry(){
718
+ if(process.env.NODE_ENV == "test"){
719
+ return this.#getPublishRetry.bind(this);
720
+ }else{
721
+ return null;
722
+ }
723
+ }
724
+
725
+ testGetStreamName(){
726
+ if(process.env.NODE_ENV == "test"){
727
+ return this.#getStreamName.bind(this);
728
+ }else{
729
+ return null;
730
+ }
731
+ }
732
+
733
+ testGetStreamTopic(){
734
+ if(process.env.NODE_ENV == "test"){
735
+ return this.#getStreamTopic.bind(this);
736
+ }else{
737
+ return null;
738
+ }
739
+ }
740
+
741
+ testGetTopicMap(){
742
+ if(process.env.NODE_ENV == "test"){
743
+ return this.#topicMap
744
+ }else{
745
+ return null;
746
+ }
747
+ }
748
+
749
+ testGetEventMap(){
750
+ if(process.env.NODE_ENV == "test"){
751
+ return this.#event_func
752
+ }else{
753
+ return null;
754
+ }
755
+ }
756
+
757
+ testGetConsumerMap(){
758
+ if(process.env.NODE_ENV == "test"){
759
+ return this.#consumerMap
760
+ }else{
761
+ return null;
762
+ }
763
+ }
764
+ }
765
+
766
+ export const CONNECTED = "CONNECTED";
767
+ export const RECONNECT = "RECONNECT";
768
+ export const MESSAGE_RESEND = "MESSAGE_RESEND";
769
+ export const DISCONNECTED = "DISCONNECTED";