relayx-js 1.0.17 → 1.0.19

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.
@@ -28,18 +28,17 @@ async function run(){
28
28
  console.log(`[IMPL] DISONNECT`)
29
29
  });
30
30
 
31
- // await realtime.on("power-telemetry", (data) => {
32
- // console.log("power-telemetry", data);
33
- // });
31
+ await realtime.on("power-telemetry", (data) => {
32
+ console.log("power-telemetry", data);
33
+ });
34
34
 
35
35
  // await realtime.on("hello.*", (data) => {
36
36
  // console.log("hello.*", data);
37
37
  // });
38
38
 
39
- // await realtime.on("hello.>", async (data) => {
40
- // await realtime.sleep(10000)
41
- // console.log("hello.>", data);
42
- // });
39
+ await realtime.on("hello.>", async (data) => {
40
+ console.log("hello.>", data);
41
+ });
43
42
 
44
43
  // await realtime.on("hello.hey.*", (data) => {
45
44
  // console.log("hell.hey.*", data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayx-js",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "main": "realtime/realtime.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -12,18 +12,17 @@ export class Realtime {
12
12
  #codec = JSONCodec();
13
13
  #jetstream = null;
14
14
  #consumerMap = {};
15
- #consumer = null;
16
15
 
17
16
  #event_func = {};
18
17
  #topicMap = [];
19
18
 
20
- #config = "CiAgICAgICAgLS0tLS1CRUdJTiBOQVRTIFVTRVIgSldULS0tLS0KICAgICAgICBKV1RfS0VZCiAgICAgICAgLS0tLS0tRU5EIE5BVFMgVVNFUiBKV1QtLS0tLS0KCiAgICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKiBJTVBPUlRBTlQgKioqKioqKioqKioqKioqKioqKioqKioqKgogICAgICAgIE5LRVkgU2VlZCBwcmludGVkIGJlbG93IGNhbiBiZSB1c2VkIHRvIHNpZ24gYW5kIHByb3ZlIGlkZW50aXR5LgogICAgICAgIE5LRVlzIGFyZSBzZW5zaXRpdmUgYW5kIHNob3VsZCBiZSB0cmVhdGVkIGFzIHNlY3JldHMuCgogICAgICAgIC0tLS0tQkVHSU4gVVNFUiBOS0VZIFNFRUQtLS0tLQogICAgICAgIFNFQ1JFVF9LRVkKICAgICAgICAtLS0tLS1FTkQgVVNFUiBOS0VZIFNFRUQtLS0tLS0KCiAgICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgogICAgICAgIA=="
21
-
22
19
  // Status Codes
23
20
  #RECONNECTING = "RECONNECTING";
24
21
  #RECONNECTED = "RECONNECTED";
25
22
  #RECONN_FAIL = "RECONN_FAIL";
26
23
 
24
+ #reservedSystemTopics = [CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED, this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT];
25
+
27
26
  setRemoteUserAttempts = 0;
28
27
  setRemoteUserRetries = 5;
29
28
 
@@ -43,6 +42,8 @@ export class Realtime {
43
42
 
44
43
  #maxPublishRetries = 5;
45
44
 
45
+ #connectCalled = false;
46
+
46
47
  constructor(config){
47
48
  if(typeof config != "object"){
48
49
  throw new Error("Realtime($config). $config not object => {}")
@@ -98,10 +99,13 @@ export class Realtime {
98
99
  if(arguments[0] instanceof Object){
99
100
  opts = arguments[0];
100
101
  staging = false;
101
- }else{
102
+ }else if(typeof arguments[0] == "boolean"){
102
103
  opts = {};
103
104
  staging = arguments[0];
104
105
  this.#log(staging)
106
+ }else{
107
+ opts = {};
108
+ staging = false
105
109
  }
106
110
  }else{
107
111
  staging = false;
@@ -172,6 +176,10 @@ export class Realtime {
172
176
  * Connects to the relay network
173
177
  */
174
178
  async connect(){
179
+ if(this.#connectCalled){
180
+ return;
181
+ }
182
+
175
183
  this.SEVER_URL = this.#baseUrl;
176
184
 
177
185
  var credsFile = this.#getUserCreds(this.api_key, this.secret)
@@ -195,6 +203,7 @@ export class Realtime {
195
203
  await this.#getNameSpace()
196
204
 
197
205
  this.connected = true;
206
+ this.#connectCalled = true;
198
207
  }catch(err){
199
208
  this.#log("ERR")
200
209
  this.#log(err);
@@ -205,17 +214,13 @@ export class Realtime {
205
214
  if (this.connected == true){
206
215
  this.#log("Connected to server!");
207
216
 
208
- // Callback on client side
209
- if (CONNECTED in this.#event_func){
210
- if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
211
- this.#event_func[CONNECTED]()
212
- }
213
- }
214
-
215
217
  this.#natsClient.closed().then(() => {
216
218
  this.#log("the connection closed!");
217
219
 
218
220
  this.#offlineMessageBuffer.length = 0;
221
+ this.connected = false;
222
+ this.reconnecting = false;
223
+ this.#connectCalled = false;
219
224
 
220
225
  if (DISCONNECTED in this.#event_func){
221
226
  if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
@@ -233,12 +238,6 @@ export class Realtime {
233
238
  this.#log(`client disconnected - ${s.data}`);
234
239
 
235
240
  this.connected = false;
236
-
237
- if (DISCONNECTED in this.#event_func){
238
- if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
239
- this.#event_func[DISCONNECTED]()
240
- }
241
- }
242
241
  break;
243
242
  case Events.LDM:
244
243
  this.#log("client has been requested to reconnect");
@@ -268,6 +267,7 @@ export class Realtime {
268
267
  this.#log("client is attempting to reconnect");
269
268
 
270
269
  this.reconnecting = true;
270
+ this.connected = false;
271
271
 
272
272
  if(RECONNECT in this.#event_func && this.reconnecting){
273
273
  this.#event_func[RECONNECT](this.#RECONNECTING);
@@ -285,6 +285,13 @@ export class Realtime {
285
285
  // Subscribe to topics
286
286
  this.#subscribeToTopics();
287
287
  this.#log("Subscribed to topics");
288
+
289
+ // Callback on client side
290
+ if (CONNECTED in this.#event_func){
291
+ if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
292
+ this.#event_func[CONNECTED]()
293
+ }
294
+ }
288
295
  }
289
296
  }
290
297
 
@@ -295,12 +302,11 @@ export class Realtime {
295
302
  if(this.#natsClient !== null){
296
303
  this.reconnected = false;
297
304
  this.disconnected = true;
298
-
299
- this.#consumer?.delete()
305
+ this.#connectCalled = false;
300
306
 
301
307
  this.#offlineMessageBuffer.length = 0;
302
308
 
303
- await this.#deleteConsumer();
309
+ await this.#deleteAllConsumers();
304
310
 
305
311
  this.#natsClient.close();
306
312
  }else{
@@ -312,8 +318,20 @@ export class Realtime {
312
318
  * Start consumers for topics initialized by user
313
319
  */
314
320
  async #subscribeToTopics(){
315
- if(this.#topicMap.length > 0){
316
- await this.#startConsumer();
321
+ this.#topicMap.forEach(async (topic) => {
322
+ // Subscribe to stream
323
+ await this.#startConsumer(topic);
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Delete consumers for topics initialized by user
329
+ */
330
+ async #deleteAllConsumers(){
331
+ for(let i = 0; i < this.#topicMap.length; i++){
332
+ let topic = this.#topicMap[i];
333
+
334
+ await this.#deleteConsumer(topic);
317
335
  }
318
336
  }
319
337
 
@@ -335,6 +353,8 @@ export class Realtime {
335
353
  this.#topicMap = this.#topicMap.filter(item => item !== topic);
336
354
 
337
355
  delete this.#event_func[topic];
356
+
357
+ return await this.#deleteConsumer(topic);
338
358
  }
339
359
 
340
360
  /**
@@ -360,31 +380,23 @@ export class Realtime {
360
380
  throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
361
381
  }
362
382
 
363
- if(!(topic in this.#event_func)){
364
- this.#event_func[topic] = func;
365
- }else{
383
+ if(topic in this.#event_func || this.#topicMap.includes(topic)){
366
384
  return false
367
385
  }
368
386
 
369
- if (![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
370
- this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic)){
371
- if(!this.isTopicValid(topic)){
372
- // We have an invalid topic, lets remove it
373
- if(topic in this.#event_func){
374
- delete this.#event_func[topic];
375
- }
387
+ this.#event_func[topic] = func;
376
388
 
377
- throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
378
- }
389
+ if (!this.#reservedSystemTopics.includes(topic)){
390
+ if(!this.isTopicValid(topic)){
391
+ throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
392
+ }
379
393
 
380
- if(!this.#topicMap.includes(topic)){
381
- this.#topicMap.push(topic);
382
- }
394
+ this.#topicMap.push(topic);
383
395
 
384
- if(this.connected){
385
- // Connected we need to create a topic in a stream
386
- await this.#startConsumer(topic);
387
- }
396
+ if(this.connected){
397
+ // Connected we need to create a topic in a stream
398
+ await this.#startConsumer(topic);
399
+ }
388
400
  }
389
401
 
390
402
  return true;
@@ -414,7 +426,7 @@ export class Realtime {
414
426
  throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
415
427
  }
416
428
 
417
- if(!this.#isMessageValid(data)){
429
+ if(!this.isMessageValid(data)){
418
430
  throw new Error("$message must be JSON, string or number")
419
431
  }
420
432
 
@@ -429,15 +441,9 @@ export class Realtime {
429
441
  "start": Date.now()
430
442
  }
431
443
 
432
- this.#log("Encoding message via msg pack...")
433
- var encodedMessage = encode(message);
434
-
435
444
  if(this.connected){
436
- if(!this.#topicMap.includes(topic)){
437
- this.#topicMap.push(topic);
438
- }else{
439
- this.#log(`${topic} exists locally, moving on...`)
440
- }
445
+ this.#log("Encoding message via msg pack...")
446
+ var encodedMessage = encode(message);
441
447
 
442
448
  this.#log(`Publishing to topic => ${this.#getStreamTopic(topic)}`)
443
449
 
@@ -464,7 +470,6 @@ export class Realtime {
464
470
  * @param {string} topic
465
471
  */
466
472
  async history(topic, start, end){
467
- this.#log(start)
468
473
  if(topic == null || topic == undefined){
469
474
  throw new Error("$topic is null or undefined");
470
475
  }
@@ -501,11 +506,16 @@ export class Realtime {
501
506
  end = end.toISOString();
502
507
  }
503
508
 
509
+ if(!this.connected){
510
+ return [];
511
+ }
512
+
504
513
  var opts = {
505
- name: `${topic}_${uuidv4()}_history`,
514
+ name: `nodejs_${topic}_${uuidv4()}_history_consumer`,
506
515
  filter_subjects: [this.#getStreamTopic(topic)],
507
516
  replay_policy: ReplayPolicy.Instant,
508
517
  opt_start_time: start,
518
+ delivery_policy: DeliverPolicy.StartTime,
509
519
  ack_policy: AckPolicy.Explicit,
510
520
  }
511
521
 
@@ -534,7 +544,12 @@ export class Realtime {
534
544
  var data = decode(msg.data);
535
545
  this.#log(data);
536
546
 
537
- history.push(data.message);
547
+ history.push({
548
+ "id": data.id,
549
+ "topic": data.room,
550
+ "message": data.message,
551
+ "timestamp": msg.timestamp
552
+ });
538
553
  }
539
554
 
540
555
  var del = await consumer.delete();
@@ -581,45 +596,43 @@ export class Realtime {
581
596
  * Starts consumer for particular topic if stream exists
582
597
  * @param {string} topic
583
598
  */
584
- async #startConsumer(){
585
- if(this.#consumer != null){
586
- return;
587
- }
599
+ async #startConsumer(topic){
600
+ const consumerName = `nodejs_${topic}_${uuidv4()}_consumer`;
588
601
 
589
602
  var opts = {
590
- name: `${uuidv4()}`,
591
- filter_subjects: [this.#getStreamTopic(">")],
603
+ name: consumerName,
604
+ filter_subjects: [this.#getStreamTopic(topic)],
592
605
  replay_policy: ReplayPolicy.Instant,
593
606
  opt_start_time: new Date(),
594
607
  ack_policy: AckPolicy.Explicit,
595
608
  delivery_policy: DeliverPolicy.New
596
609
  }
597
610
 
598
- this.#consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
611
+ const consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
599
612
  this.#log(this.#topicMap)
600
613
 
601
- await this.#consumer.consume({
614
+ this.#consumerMap[topic] = consumer;
615
+
616
+ await consumer.consume({
602
617
  callback: async (msg) => {
603
618
  try{
604
619
  const now = Date.now();
620
+ msg.working()
605
621
  this.#log("Decoding msgpack message...")
606
622
  var data = decode(msg.data);
607
623
 
608
- var room = this.#stripStreamHash(msg.subject);
624
+ var msgTopic = this.#stripStreamHash(msg.subject);
609
625
 
610
626
  this.#log(data);
611
627
 
612
628
  // Push topic message to main thread
613
629
  if (data.client_id != this.#getClientId()){
614
- var topics = this.#getCallbackTopics(room);
615
- this.#log(topics)
630
+ var topicMatch = this.#topicPatternMatcher(topic, msgTopic)
616
631
 
617
- for(let i = 0; i < topics.length; i++){
618
- var top = topics[i];
619
-
620
- this.#event_func[top]({
632
+ if(topicMatch){
633
+ this.#event_func[topic]({
621
634
  "id": data.id,
622
- "topic": room,
635
+ "topic": msgTopic,
623
636
  "data": data.message
624
637
  });
625
638
  }
@@ -637,15 +650,20 @@ export class Realtime {
637
650
  this.#log("Consumer is consuming");
638
651
  }
639
652
 
640
- async #deleteConsumer(){
653
+ async #deleteConsumer(topic){
654
+ this.#log(topic)
655
+ const consumer = this.#consumerMap[topic]
656
+
641
657
  var del = false;
642
658
 
643
- if (this.#consumer != null && this.#consumer != undefined){
644
- del = await this.#consumer.delete();
659
+ if (consumer != null && consumer != undefined){
660
+ del = await consumer.delete();
645
661
  }else{
646
662
  del = false
647
663
  }
648
664
 
665
+ delete this.#consumerMap[topic];
666
+
649
667
  return del;
650
668
  }
651
669
 
@@ -655,11 +673,6 @@ export class Realtime {
655
673
  return;
656
674
  }
657
675
 
658
- if(this.#latency.length >= 100){
659
- this.#log("Latency array is full, skipping log");
660
- return;
661
- }
662
-
663
676
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
664
677
 
665
678
  this.#log(`Timezone: ${timeZone}`);
@@ -676,7 +689,7 @@ export class Realtime {
676
689
  this.#latencyPush = setTimeout(async () => {
677
690
  this.#log("setTimeout called");
678
691
 
679
- if(this.#latency.length > 0 && this.connected){
692
+ if(this.#latency.length > 0 && this.connected && !this.#isSendingLatency){
680
693
  this.#log("Push from setTimeout")
681
694
  await this.#pushLatencyData({
682
695
  timezone: timeZone,
@@ -689,7 +702,7 @@ export class Realtime {
689
702
  }, 30000);
690
703
  }
691
704
 
692
- if(this.#latency.length == 100 && !this.#isSendingLatency){
705
+ if(this.#latency.length >= 100 && !this.#isSendingLatency){
693
706
  this.#log("Push from Length Check: " + this.#latency.length);
694
707
  await this.#pushLatencyData({
695
708
  timezone: timeZone,
@@ -745,8 +758,7 @@ export class Realtime {
745
758
  */
746
759
  isTopicValid(topic){
747
760
  if(topic !== null && topic !== undefined && (typeof topic) == "string"){
748
- var arrayCheck = ![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
749
- this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic);
761
+ var arrayCheck = !this.#reservedSystemTopics.includes(topic);
750
762
 
751
763
  const TOPIC_REGEX = /^(?!.*\$)(?:[A-Za-z0-9_*~-]+(?:\.[A-Za-z0-9_*~-]+)*(?:\.>)?|>)$/u;
752
764
 
@@ -758,7 +770,7 @@ export class Realtime {
758
770
  }
759
771
  }
760
772
 
761
- #isMessageValid(message){
773
+ isMessageValid(message){
762
774
  if(message == null || message == undefined){
763
775
  throw new Error("$message cannot be null / undefined")
764
776
  }
@@ -955,13 +967,21 @@ export class Realtime {
955
967
  return methodDataOutput;
956
968
  }
957
969
 
958
- #getUserCreds(jwt, secret){
959
- var template = Buffer.from(this.#config, "base64").toString("utf8")
970
+ #getUserCreds(jwt, secret){
971
+ return `
972
+ -----BEGIN NATS USER JWT-----
973
+ ${jwt}
974
+ ------END NATS USER JWT------
975
+
976
+ ************************* IMPORTANT *************************
977
+ NKEY Seed printed below can be used to sign and prove identity.
978
+ NKEYs are sensitive and should be treated as secrets.
960
979
 
961
- var creds = template.replace("JWT_KEY", jwt);
962
- creds = creds.replace("SECRET_KEY", secret)
980
+ -----BEGIN USER NKEY SEED-----
981
+ ${secret}
982
+ ------END USER NKEY SEED------
963
983
 
964
- return creds
984
+ *************************************************************`
965
985
  }
966
986
 
967
987
  // Exposure for tests