relayx-js 1.0.9 → 1.0.11

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.
@@ -8,8 +8,8 @@ const rl = readline.createInterface({
8
8
 
9
9
  async function run(){
10
10
  var realtime = new Realtime({
11
- api_key: "eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJhdWQiOiJOQVRTIiwibmFtZSI6IjY3ZmZhMWVkMzk0NDRlODMwOGIxMDBiNCIsInN1YiI6IlVENUtRN0hNU01IMlNVWklCTTNCU1IyMjdQNUw3NFZKVTNUS0tPMk5XT1U2Qkg2S0pEV0JCNEFNIiwibmF0cyI6eyJkYXRhIjotMSwicGF5bG9hZCI6LTEsInN1YnMiOi0xLCJwdWIiOnsiZGVueSI6WyI-Il19LCJzdWIiOnsiZGVueSI6WyI-Il19LCJvcmdfZGF0YSI6eyJvcmdfaWQiOiI2N2ZmYTFkMzRmMDQ5MTAzNGQ5ZWJiNzIiLCJvcmdfbmFtZSI6IlNwYWNlWCIsInZhbGlkaXR5X2tleSI6IjU3NjUyM2UwLWM3ZGMtNGVlMS04OTFjLTAzNTFmMDgxOTkzYyIsInJvbGUiOiJ1c2VyIiwicHJvamVjdF9pZCI6IjY3ZmZhMWVkMzk0NDRlODMwOGIxMDBiNCJ9LCJpc3N1ZXJfYWNjb3VudCI6IkFCMzczR0JSWkVMNEhLNUdXWkJNNFNVTEFLVUhZSTVNUE9UWVNKQUpCVVVNU01KT09XREhFMjVYIiwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfSwiaXNzIjoiQUIzNzNHQlJaRUw0SEs1R1daQk00U1VMQUtVSFlJNU1QT1RZU0pBSkJVVU1TTUpPT1dESEUyNVgiLCJpYXQiOjE3NDQ4MDYzOTQsImp0aSI6Im5ZczM4MEZxa1dnNis3eGl4Wnl6VjJWS0ZLSVFNOEJrOG9ESFFzTjdBcW1YaUNmd05HTnNkQnVNK21YMW8xN0dFM2YxeEJXSFFGU1VUSmFrTmN1MGNnPT0ifQ.FVRHylWkCyIN0Axed0Z-H2Zgfh3HjEHGkjWQfUe70O3hl3OQYkQa1Ka-IYilGPB2fpd272t3VJarMvF1e5OSCw",
12
- secret: "SUAC7MP5AIOVJJTV2DCKRQRVAANBETTVYP26O4YE2BXELFOCURXLMEX5YE"
11
+ api_key: process.env.AUTH_JWT,
12
+ secret: process.env.AUTH_SECRET
13
13
  });
14
14
  await realtime.init(true, {
15
15
  max_retries: 2,
@@ -32,8 +32,8 @@ async function run(){
32
32
  console.log("power-telemetry", data);
33
33
  });
34
34
 
35
- await realtime.on("hello1", (data) => {
36
- console.log("hello1", data);
35
+ await realtime.on("test232", (data) => {
36
+ console.log("test232", data);
37
37
  });
38
38
 
39
39
  realtime.on(MESSAGE_RESEND, (data) => {
@@ -57,11 +57,11 @@ async function run(){
57
57
  var pastDate = new Date(past)
58
58
 
59
59
  var end = new Date();
60
- var past = end.setDate(end.getDate() - 2)
60
+ var past = end.setDate(end.getDate())
61
61
  var endDate = new Date(past)
62
62
 
63
- var history = await realtime.history(topic, pastDate, endDate)
64
- // console.log(history)
63
+ var history = await realtime.history(topic, pastDate)
64
+ console.log(history)
65
65
  })
66
66
  }else if(input == "off"){
67
67
  rl.question("topic to off(): ", async (topic) => {
@@ -83,9 +83,7 @@ async function run(){
83
83
  })
84
84
  }else{
85
85
  rl.question("topic: ", async (topic) => {
86
- var output = await realtime.publish(topic, {
87
- "data": input
88
- });
86
+ var output = await realtime.publish(topic, input);
89
87
  })
90
88
  }
91
89
  });
@@ -2,8 +2,8 @@ import { Realtime, CONNECTED, RECONNECT, DISCONNECTED, MESSAGE_RESEND } from "..
2
2
 
3
3
  async function run(){
4
4
  var realtime = new Realtime({
5
- api_key: "eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJhdWQiOiJOQVRTIiwibmFtZSI6IjY3ZmY5OGEwMTQ4NTgwYTcwY2U4ZThjMiIsInN1YiI6IlVBRlc1MkU0TEZDNERSSDI2M0M3SUlNS1ZTWU1WU1hYTjZSMkxMUDNBUVI0RExIQTI1WFc1RlpZIiwibmF0cyI6eyJkYXRhIjotMSwicGF5bG9hZCI6LTEsInN1YnMiOi0xLCJwdWIiOnsiZGVueSI6WyI-Il19LCJzdWIiOnsiZGVueSI6WyI-Il19LCJvcmdfZGF0YSI6eyJvcmdfaWQiOiI2N2ZmOTg3YzE1NDU5ZDc3MTAzMDVlY2IiLCJvcmdfbmFtZSI6IlNwYWNlWCIsInZhbGlkaXR5X2tleSI6IjY1NWU3N2ZhLTgxZTEtNDNhOS1hZTdhLWIyODA0M2MxNGFjMSIsInJvbGUiOiJ1c2VyIiwicHJvamVjdF9pZCI6IjY3ZmY5OGEwMTQ4NTgwYTcwY2U4ZThjMiJ9LCJpc3N1ZXJfYWNjb3VudCI6IkFCMzczR0JSWkVMNEhLNUdXWkJNNFNVTEFLVUhZSTVNUE9UWVNKQUpCVVVNU01KT09XREhFMjVYIiwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfSwiaXNzIjoiQUIzNzNHQlJaRUw0SEs1R1daQk00U1VMQUtVSFlJNU1QT1RZU0pBSkJVVU1TTUpPT1dESEUyNVgiLCJpYXQiOjE3NDQ4MDQwOTYsImp0aSI6Im5SdnRiUkhFWEtZVjhQRXoxeXhZQjFMRUVHNkU0ZlRndCs3cmZRWGdEZUNGM1ZSNXlsbGFiR3ZsaWxBSGR6dThkeXQvM3lxcVl3MDg1eUhSS1ZHT1R3PT0ifQ.tGj1iL8537ttVqQlTrW3qLrpYOb19I5VFZYurEltIA2aKgRrFLhz08qRtO1tm-GKVwgonM_10P_Aj1MnhzbeCw",
6
- secret: "SUALQKYAGDY6HHMW7NCHMKV6RAWSZPS2USQD6MIWIZWW6DATMSZ7MOCLUM"
5
+ api_key: process.env.AUTH_JWT,
6
+ secret: process.env.AUTH_SECRET
7
7
  });
8
8
  await realtime.init(true, {
9
9
  max_retries: 2,
@@ -16,19 +16,13 @@ async function run(){
16
16
  for (let angle = 0; angle <= 18000; angle++){
17
17
 
18
18
  var value = Math.floor(Math.random() * (100 + 1))
19
- // console.log(value)
20
-
21
- // await realtime.publish("test-power", {
22
- // "value": value,
23
- // "time": Date.now() + 2000
24
- // })
25
19
 
26
20
  var sent = await realtime.publish("power-telemetry", {
27
21
  "value": value,
28
22
  "time": Date.now()
29
23
  });
30
24
 
31
- // console.log(`Message sent => ${sent}`);
25
+ console.log(`Message sent => ${sent}`);
32
26
 
33
27
  await realtime.sleep(100)
34
28
  }
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
2
  "name": "relayx-js",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "main": "realtime/realtime.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "test": "NODE_ENV=test node tests/test.js"
8
8
  },
9
- "keywords": ["realtime", "realtime-communication", "relay", "relay-x", "relay-x-js"],
9
+ "keywords": [
10
+ "realtime",
11
+ "realtime-communication",
12
+ "relay",
13
+ "relay-x",
14
+ "relay-x-js"
15
+ ],
10
16
  "author": "Relay",
11
17
  "license": "Apache 2.0",
12
18
  "description": "A powerful library for integrating real-time communication into your software stack, powered by the Relay Network.",
13
19
  "dependencies": {
20
+ "@msgpack/msgpack": "^3.1.1",
14
21
  "@nats-io/jetstream": "^3.0.0-35",
15
- "axios": "1.7.7",
22
+ "axios": "^1.8.4",
16
23
  "jest": "^29.7.0",
17
- "nats": "^2.28.2"
24
+ "nats": "^2.28.2",
25
+ "uuid": "^11.1.0"
18
26
  },
19
27
  "devDependencies": {
20
28
  "@babel/core": "^7.26.0",
@@ -1,7 +1,8 @@
1
1
  import axios from 'axios';
2
2
  import { connect, JSONCodec, Events, DebugEvents, AckPolicy, ReplayPolicy, credsAuthenticator } from "nats";
3
- import { DeliverPolicy, jetstream, jetstreamManager } from "@nats-io/jetstream";
4
- import { readFileSync } from "fs"
3
+ import { DeliverPolicy, jetstream } from "@nats-io/jetstream";
4
+ import { encode, decode } from "@msgpack/msgpack";
5
+ import { v4 as uuidv4 } from 'uuid';
5
6
 
6
7
  export class Realtime {
7
8
 
@@ -10,8 +11,6 @@ export class Realtime {
10
11
  #natsClient = null;
11
12
  #codec = JSONCodec();
12
13
  #jetstream = null;
13
- #jsManager = null;
14
- #streamTracker = [];
15
14
  #consumerMap = {};
16
15
 
17
16
  #event_func = {};
@@ -36,8 +35,10 @@ export class Realtime {
36
35
  // Offline messages
37
36
  #offlineMessageBuffer = [];
38
37
 
39
- // Test Variables
40
- #timeout = 1000;
38
+ // Latency
39
+ #latency = [];
40
+ #latencyPush = null;
41
+ #isSendingLatency = false;
41
42
 
42
43
  #maxPublishRetries = 5;
43
44
 
@@ -62,6 +63,7 @@ export class Realtime {
62
63
  }
63
64
 
64
65
  this.namespace = null;
66
+ this.topicHash = null;
65
67
  }
66
68
 
67
69
  /*
@@ -161,8 +163,10 @@ export class Realtime {
161
163
 
162
164
  if(data["status"] == "NAMESPACE_RETRIEVE_SUCCESS"){
163
165
  this.namespace = data["data"]["namespace"]
166
+ this.topicHash = data["data"]["hash"]
164
167
  }else{
165
168
  this.namespace = null;
169
+ this.topicHash = null;
166
170
  return
167
171
  }
168
172
  }
@@ -189,7 +193,6 @@ export class Realtime {
189
193
  token: this.api_key,
190
194
  });
191
195
 
192
- this.#jsManager = await jetstreamManager(this.#natsClient);
193
196
  this.#jetstream = await jetstream(this.#natsClient);
194
197
 
195
198
  await this.#getNameSpace()
@@ -225,7 +228,6 @@ export class Realtime {
225
228
  this.#log(`client disconnected - ${s.data}`);
226
229
 
227
230
  this.connected = false;
228
- this.#streamTracker = [];
229
231
  this.#consumerMap = {};
230
232
 
231
233
  if (DISCONNECTED in this.#event_func){
@@ -248,8 +250,6 @@ export class Realtime {
248
250
  this.reconnecting = false;
249
251
  this.connected = true;
250
252
 
251
- // this.#subscribeToTopics();
252
-
253
253
  if(RECONNECT in this.#event_func){
254
254
  this.#event_func[RECONNECT](this.#RECONNECTED);
255
255
  }
@@ -374,10 +374,10 @@ export class Realtime {
374
374
  this.#topicMap.push(topic);
375
375
  }
376
376
 
377
- if(this.connected){
378
- // Connected we need to create a topic in a stream
379
- await this.#startConsumer(topic);
380
- }
377
+ if(this.connected){
378
+ // Connected we need to create a topic in a stream
379
+ await this.#startConsumer(topic);
380
+ }
381
381
  }
382
382
 
383
383
  return true;
@@ -418,13 +418,12 @@ export class Realtime {
418
418
  "start": Date.now()
419
419
  }
420
420
 
421
- var encodedMessage = this.#codec.encode(message)
421
+ this.#log("Encoding message via msg pack...")
422
+ var encodedMessage = encode(message);
422
423
 
423
424
  if(this.connected){
424
425
  if(!this.#topicMap.includes(topic)){
425
426
  this.#topicMap.push(topic);
426
-
427
- await this.#createOrGetStream();
428
427
  }else{
429
428
  this.#log(`${topic} exists locally, moving on...`)
430
429
  }
@@ -487,12 +486,8 @@ export class Realtime {
487
486
  end = end.toISOString();
488
487
  }
489
488
 
490
- console.log(`END => ${end}`)
491
-
492
- await this.#createOrGetStream();
493
-
494
489
  var opts = {
495
- name: topic,
490
+ name: `${topic}_${uuidv4()}_history`,
496
491
  filter_subjects: [this.#getStreamTopic(topic)],
497
492
  replay_policy: ReplayPolicy.Instant,
498
493
  opt_start_time: start,
@@ -503,30 +498,33 @@ export class Realtime {
503
498
  this.#log(this.#topicMap)
504
499
  this.#log("Consumer is consuming");
505
500
 
506
- this.#consumerMap[topic] = consumer;
507
-
508
- const msgs = await consumer.consume();
509
-
510
501
  var history = [];
511
502
 
512
- for await (const m of msgs){
513
- m.ack();
503
+ while(true){
504
+ var msg = await consumer.next({
505
+ expires: 1000
506
+ });
507
+
508
+ if(msg == null){
509
+ break;
510
+ }
514
511
 
515
512
  if(end != null || end != undefined){
516
- if(m.timestamp > end){
513
+ if(msg.timestamp > end){
517
514
  break
518
515
  }
519
516
  }
520
517
 
521
- console.log(m.timestamp)
522
-
523
- var data = m.json();
518
+ this.#log("Decoding msgpack message...")
519
+ var data = decode(msg.data);
520
+ this.#log(data);
521
+
524
522
  history.push(data.message);
525
523
  }
526
524
 
527
525
  var del = await consumer.delete();
528
526
 
529
- this.#log("History pull done", del)
527
+ this.#log("History pull done: " + del);
530
528
 
531
529
  return history;
532
530
  }
@@ -569,10 +567,10 @@ export class Realtime {
569
567
  * @param {string} topic
570
568
  */
571
569
  async #startConsumer(topic){
572
- await this.#createOrGetStream();
570
+ this.#log(`Starting consumer for topic: ${topic}_${uuidv4()}`)
573
571
 
574
572
  var opts = {
575
- name: topic,
573
+ name: `${topic}_${uuidv4()}`,
576
574
  filter_subjects: [this.#getStreamTopic(topic), this.#getStreamTopic(topic) + "_presence"],
577
575
  replay_policy: ReplayPolicy.Instant,
578
576
  opt_start_time: new Date(),
@@ -586,17 +584,15 @@ export class Realtime {
586
584
  this.#consumerMap[topic] = consumer;
587
585
 
588
586
  await consumer.consume({
589
- callback: (msg) => {
590
- console.log("TIMESTAMP", msg.info)
591
-
592
- msg.ack();
587
+ callback: async (msg) => {
593
588
  try{
594
- var data = this.#codec.decode(msg.data);
589
+ const now = Date.now();
590
+ this.#log("Decoding msgpack message...")
591
+ var data = decode(msg.data);
592
+
595
593
  var room = data.room;
596
594
 
597
595
  this.#log(data);
598
- const latency = Date.now() - data.start
599
- this.#log(`Latency => ${latency}`)
600
596
 
601
597
  // Push topic message to main thread
602
598
  if (room in this.#event_func && data.client_id != this.#getClientId()){
@@ -605,9 +601,13 @@ export class Realtime {
605
601
  "data": data.message
606
602
  });
607
603
  }
604
+
605
+ msg.ack();
606
+
607
+ await this.#logLatency(now, data);
608
608
  }catch(err){
609
609
  this.#log("Consumer err " + err);
610
- msg.nack();
610
+ msg.nack(5000);
611
611
  }
612
612
  }
613
613
  });
@@ -634,37 +634,52 @@ export class Realtime {
634
634
  return del;
635
635
  }
636
636
 
637
- /**
638
- * Gets stream if it exists or creates one
639
- * @param {string} streamName
640
- */
641
- async #createOrGetStream(){
642
- const streamName = this.#getStreamName();
643
- var stream = null;
644
-
645
- try{
646
- stream = await this.#jsManager.streams.info(streamName);
647
- }catch(err){
648
- stream = null;
637
+ async #logLatency(now, data){
638
+ if(data.client_id == this.#getClientId()){
639
+ this.#log("Skipping latency log for own message");
640
+ return;
649
641
  }
650
642
 
651
- this.#log(`STREAM => ${stream}`)
643
+ if(this.#latency.length >= 100){
644
+ this.#log("Latency array is full, skipping log");
645
+ return;
646
+ }
652
647
 
653
- if (stream == null){
654
- // Stream does not exist, create one
655
- await this.#jsManager.streams.add({
656
- name: streamName,
657
- subjects: [...this.#getStreamTopicList(), ...this.#getPresenceTopics()],
658
- num_replicas: 3
659
- });
648
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
660
649
 
661
- this.#log(`${streamName} created`);
662
- }else{
663
- var subs = [...stream.config.subjects, ...this.#getStreamTopicList(), ...this.#getPresenceTopics()];
664
- stream.config.subjects = [...new Set(subs)];
665
- await this.#jsManager.streams.update(streamName, stream.config);
650
+ this.#log(`Timezone: ${timeZone}`);
651
+
652
+ const latency = now - data.start
653
+ this.#log(`Latency => ${latency}`)
654
+
655
+ this.#latency.push({
656
+ latency: latency,
657
+ timestamp: now
658
+ });
666
659
 
667
- this.#log(`${streamName} exists, updating and moving on...`);
660
+ if(this.#latencyPush == null){
661
+ this.#latencyPush = setTimeout(async () => {
662
+ this.#log("setTimeout called");
663
+
664
+ if(this.#latency.length > 0){
665
+ this.#log("Push from setTimeout")
666
+ await this.#pushLatencyData({
667
+ timezone: timeZone,
668
+ history: this.#latency,
669
+ });
670
+ }else{
671
+ this.#log("No latency data to push");
672
+ }
673
+
674
+ }, 30000);
675
+ }
676
+
677
+ if(this.#latency.length == 100 && !this.#isSendingLatency){
678
+ this.#log("Push from Length Check: " + this.#latency.length);
679
+ await this.#pushLatencyData({
680
+ timezone: timeZone,
681
+ history: this.#latency,
682
+ });
668
683
  }
669
684
  }
670
685
 
@@ -673,6 +688,41 @@ export class Realtime {
673
688
  return this.#natsClient?.info?.client_id
674
689
  }
675
690
 
691
+ async #pushLatencyData(data){
692
+ this.#isSendingLatency = true;
693
+
694
+ try{
695
+ var res = await this.#natsClient.request("accounts.user.log_latency",
696
+ JSONCodec().encode({
697
+ api_key: this.api_key,
698
+ payload: data
699
+ }),
700
+ {
701
+ timeout: 5000
702
+ }
703
+ )
704
+
705
+ var data = res.json()
706
+
707
+ this.#log(data)
708
+ this.#resetLatencyTracker();
709
+ }catch(err){
710
+ this.#log("Error getting pushing latency data")
711
+ this.#log(err);
712
+ }
713
+
714
+ this.#isSendingLatency = false;
715
+ }
716
+
717
+ #resetLatencyTracker(){
718
+ this.#latency = [];
719
+
720
+ if(this.#latencyPush != null){
721
+ clearTimeout(this.#latencyPush);
722
+ this.#latencyPush = null;
723
+ }
724
+ }
725
+
676
726
  /**
677
727
  * Checks if a topic can be used to send messages to.
678
728
  * @param {string} topic - Name of event
@@ -701,34 +751,14 @@ export class Realtime {
701
751
  }
702
752
 
703
753
  #getStreamTopic(topic){
704
- if(this.namespace != null){
705
- return this.namespace + "_stream_" + topic;
754
+ if(this.topicHash != null){
755
+ return this.topicHash + "." + topic;
706
756
  }else{
707
757
  this.close();
708
- throw new Error("$namespace is null. Cannot initialize program with null $namespace")
758
+ throw new Error("$topicHash is null. Cannot initialize program with null $topicHash")
709
759
  }
710
760
  }
711
761
 
712
- #getStreamTopicList(){
713
- var topics = [];
714
-
715
- this.#topicMap.forEach((topic) => {
716
- topics.push(this.#getStreamTopic(topic))
717
- })
718
-
719
- return topics
720
- }
721
-
722
- #getPresenceTopics(){
723
- var presence = [];
724
-
725
- this.#topicMap.forEach((topic) => {
726
- presence.push(this.#getStreamTopic(topic) + "_presence")
727
- })
728
-
729
- return presence
730
- }
731
-
732
762
  sleep(ms) {
733
763
  return new Promise(resolve => setTimeout(resolve, ms));
734
764
  }
@@ -778,7 +808,6 @@ export class Realtime {
778
808
 
779
809
  output = await func(...args);
780
810
  success = output.success;
781
- // this.#log(output);
782
811
 
783
812
  methodDataOutput = output.output;
784
813
 
package/tests/test.js CHANGED
@@ -116,6 +116,7 @@ test('init() function test', async () => {
116
116
 
117
117
  test("Namespace check test", async () => {
118
118
  assert.strictEqual(realTimeEnabled.namespace.length > 0, true)
119
+ assert.strictEqual(realTimeEnabled.topicHash.length > 0, true)
119
120
  });
120
121
 
121
122
  test("Retry method test", async () => {
@@ -401,17 +402,19 @@ test("Get stream name test", () => {
401
402
  });
402
403
 
403
404
  realtime.namespace = "spacex-dragon-program"
405
+ realtime.topicHash = "topic_hash";
404
406
 
405
407
  var getStreamName = realtime.testGetStreamName();
406
408
  var getStreamTopic = realtime.testGetStreamTopic();
407
409
 
408
410
  var name = getStreamName();
409
- assert.strictEqual(name, "spacex-dragon-program_stream")
411
+ assert.strictEqual(name, `${realtime.namespace}_stream`);
410
412
 
411
413
  var topic = getStreamTopic("hello_world")
412
- assert.strictEqual(topic, "spacex-dragon-program_stream_hello_world")
414
+ assert.strictEqual(topic, `${realtime.topicHash}.hello_world`)
413
415
 
414
416
  realtime.namespace = null;
417
+ realtime.topicHash = null;
415
418
 
416
419
  assert.throws(() => {
417
420
  getStreamName();
@@ -422,7 +425,7 @@ test("Get stream name test", () => {
422
425
  assert.throws(() => {
423
426
  getStreamTopic("hello_world");
424
427
  },
425
- new Error("$namespace is null. Cannot initialize program with null $namespace"),
428
+ new Error("$topicHash is null. Cannot initialize program with null $topicHash"),
426
429
  "Expected error was not thrown")
427
430
  });
428
431
 
@@ -508,19 +511,4 @@ test("History test", async () => {
508
511
  },
509
512
  new Error("$start must be a Date object"),
510
513
  "Expected error was not thrown");
511
-
512
- await realTimeEnabled.history("hello", new Date());
513
-
514
- await realTimeEnabled.history("hello", new Date(), new Date());
515
-
516
- var start = new Date();
517
- var past = start.setDate(start.getDate() - 4)
518
- var pastDate = new Date(past)
519
-
520
- var end = new Date();
521
- var past = end.setDate(end.getDate() - 2)
522
- var endDate = new Date(past)
523
-
524
- var history = await realTimeEnabled.history("hello", pastDate, endDate)
525
- assert.strictEqual(history.length > 0, true)
526
514
  })