relayx-webjs 1.0.5 → 1.1.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.
@@ -2,6 +2,9 @@ import { connect, JSONCodec, Events, DebugEvents, AckPolicy, ReplayPolicy, creds
2
2
  import { DeliverPolicy, jetstream } from "@nats-io/jetstream";
3
3
  import { encode, decode } from "@msgpack/msgpack";
4
4
  import { v4 as uuidv4 } from 'uuid';
5
+ import { Queue } from "./queue.js";
6
+ import { ErrorLogging } from "./utils.js";
7
+ import { KVStore } from "./kv_storage.js";
5
8
 
6
9
  export class Realtime {
7
10
 
@@ -13,6 +16,10 @@ export class Realtime {
13
16
  #consumerMap = {};
14
17
  #consumer = null;
15
18
 
19
+ #kvStore = null;
20
+
21
+ #errorLogging = null;
22
+
16
23
  #event_func = {};
17
24
  #topicMap = [];
18
25
 
@@ -71,71 +78,25 @@ export class Realtime {
71
78
  /*
72
79
  Initializes library with configuration options.
73
80
  */
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 if(typeof arguments[0] == "boolean"){
103
- opts = {};
104
- staging = arguments[0];
105
- this.#log(staging)
106
- }else{
107
- opts = {};
108
- staging = false
109
- }
110
- }else{
111
- staging = false;
112
- opts = {};
113
- }
114
-
115
- this.staging = staging;
116
- this.opts = opts;
117
-
118
- if (staging !== undefined || staging !== null){
119
- this.#baseUrl = staging ? [
120
- "nats://0.0.0.0:4421",
121
- "nats://0.0.0.0:4422",
122
- "nats://0.0.0.0:4423"
123
- ] :
124
- [
125
- `wss://api.relay-x.io:4421`,
126
- `wss://api.relay-x.io:4422`,
127
- `wss://api.relay-x.io:4423`
128
- ];
129
- }else{
130
- this.#baseUrl = [
131
- `wss://api.relay-x.io:4421`,
132
- `wss://api.relay-x.io:4422`,
133
- `wss://api.relay-x.io:4423`
134
- ];
135
- }
81
+ async init(data){
82
+ this.#errorLogging = new ErrorLogging();
83
+
84
+ this.staging = this.#checkVarOk(data.staging) && typeof data.staging == "boolean" ? data.staging : false;
85
+ this.opts = data.opts;
86
+
87
+ this.#baseUrl = this.staging ? [
88
+ "nats://0.0.0.0:4421",
89
+ "nats://0.0.0.0:4422",
90
+ "nats://0.0.0.0:4423"
91
+ ] :
92
+ [
93
+ `wss://api.relay-x.io:4421`,
94
+ `wss://api.relay-x.io:4422`,
95
+ `wss://api.relay-x.io:4423`
96
+ ];
136
97
 
137
98
  this.#log(this.#baseUrl);
138
- this.#log(opts);
99
+ this.#log(this.opts);
139
100
  }
140
101
 
141
102
  /**
@@ -199,8 +160,16 @@ export class Realtime {
199
160
  this.connected = true;
200
161
  this.#connectCalled = true;
201
162
  }catch(err){
202
- this.#log("ERR")
203
- this.#log(err);
163
+ this.#errorLogging.logError({
164
+ err: err
165
+ })
166
+
167
+ // Callback on client side
168
+ if (CONNECTED in this.#event_func){
169
+ if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
170
+ this.#event_func[CONNECTED](false)
171
+ }
172
+ }
204
173
 
205
174
  this.connected = false;
206
175
  }
@@ -255,7 +224,11 @@ export class Realtime {
255
224
  this.#publishMessagesOnReconnect();
256
225
  break;
257
226
  case Events.Error:
258
- this.#log("client got a permissions error");
227
+ if(s.data == "NATS_PROTOCOL_ERR"){
228
+ console.log("User kicked off network by account admin!")
229
+
230
+ await this.#natsClient.close();
231
+ }
259
232
  break;
260
233
  case DebugEvents.Reconnecting:
261
234
  this.#log("client is attempting to reconnect");
@@ -270,8 +243,6 @@ export class Realtime {
270
243
  case DebugEvents.StaleConnection:
271
244
  this.#log("client has a stale connection");
272
245
  break;
273
- default:
274
- this.#log(`got an unknown status ${s.type}`);
275
246
  }
276
247
  }
277
248
  })().then();
@@ -283,7 +254,7 @@ export class Realtime {
283
254
  // Callback on client side
284
255
  if (CONNECTED in this.#event_func){
285
256
  if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
286
- this.#event_func[CONNECTED]()
257
+ this.#event_func[CONNECTED](true)
287
258
  }
288
259
  }
289
260
  }
@@ -314,7 +285,15 @@ export class Realtime {
314
285
  async #subscribeToTopics(){
315
286
  this.#topicMap.forEach(async (topic) => {
316
287
  // Subscribe to stream
317
- await this.#startConsumer(topic);
288
+ try{
289
+ await this.#startConsumer(topic);
290
+ }catch(err){
291
+ this.#errorLogging.logError({
292
+ err: err,
293
+ topic: topic,
294
+ op: "subscribe"
295
+ })
296
+ }
318
297
  });
319
298
  }
320
299
 
@@ -389,7 +368,15 @@ export class Realtime {
389
368
 
390
369
  if(this.connected){
391
370
  // Connected we need to create a topic in a stream
392
- await this.#startConsumer(topic);
371
+ try{
372
+ await this.#startConsumer(topic);
373
+ }catch(err){
374
+ this.#errorLogging.logError({
375
+ err: err,
376
+ topic: topic,
377
+ op: "subscribe"
378
+ })
379
+ }
393
380
  }
394
381
  }
395
382
 
@@ -441,12 +428,22 @@ export class Realtime {
441
428
 
442
429
  this.#log(`Publishing to topic => ${this.#getStreamTopic(topic)}`)
443
430
 
444
- const ack = await this.#jetstream.publish(this.#getStreamTopic(topic), encodedMessage);
445
- this.#log(`Publish Ack =>`)
446
- this.#log(ack)
447
-
448
- var latency = Date.now() - start;
449
- this.#log(`Latency => ${latency} ms`);
431
+ var ack = null;
432
+
433
+ try{
434
+ ack = await this.#jetstream.publish(this.#getStreamTopic(topic), encodedMessage);
435
+ this.#log(`Publish Ack =>`)
436
+ this.#log(ack)
437
+
438
+ var latency = Date.now() - start;
439
+ this.#log(`Latency => ${latency} ms`);
440
+ }catch(err){
441
+ this.#errorLogging.logError({
442
+ err: err,
443
+ topic: topic,
444
+ op: "publish"
445
+ })
446
+ }
450
447
 
451
448
  return ack !== null && ack !== undefined;
452
449
  }else{
@@ -553,6 +550,51 @@ export class Realtime {
553
550
  return history;
554
551
  }
555
552
 
553
+ // Queue Functions
554
+ async initQueue(queueID){
555
+ if(!this.connected){
556
+ this.#log("Not connected to relayX network. Skipping queue init")
557
+
558
+ return null;
559
+ }
560
+
561
+ this.#log("Validating queue ID...")
562
+ if(queueID == undefined || queueID == null || queueID == ""){
563
+ throw new Error("$queueID cannot be null / undefined / empty!")
564
+ }
565
+
566
+ var queue = new Queue({
567
+ jetstream: this.#jetstream,
568
+ nats_client: this.#natsClient,
569
+ api_key: this.api_key,
570
+ debug: this.opts?.debug ? this.opts?.debug : false
571
+ });
572
+
573
+ var initResult = await queue.init(queueID);
574
+
575
+ return initResult ? queue : null;
576
+ }
577
+
578
+ // KV Functions
579
+ async initKVStore(){
580
+
581
+ if(this.#kvStore == null){
582
+ var debugCheck = this.opts.debug !== null && this.opts.debug !== undefined && typeof this.opts.debug == "boolean"
583
+
584
+ this.#kvStore = new KVStore({
585
+ namespace: this.namespace,
586
+ jetstream: this.#jetstream,
587
+ debug: debugCheck ? this.opts.debug : false
588
+ })
589
+
590
+ var init = await this.#kvStore.init()
591
+
592
+ return init ? this.#kvStore : null;
593
+ }else{
594
+ return this.#kvStore
595
+ }
596
+ }
597
+
556
598
  /**
557
599
  * Method resends messages when the client successfully connects to the
558
600
  * server again
@@ -595,7 +637,7 @@ export class Realtime {
595
637
 
596
638
  var opts = {
597
639
  name: consumerName,
598
- filter_subjects: [this.#getStreamTopic(topic)],
640
+ filter_subjects: this.#getStreamTopic(topic),
599
641
  replay_policy: ReplayPolicy.Instant,
600
642
  opt_start_time: new Date(),
601
643
  ack_policy: AckPolicy.Explicit,
@@ -603,7 +645,6 @@ export class Realtime {
603
645
  }
604
646
 
605
647
  const consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
606
- this.#log(this.#topicMap)
607
648
 
608
649
  this.#consumerMap[topic] = consumer;
609
650
 
@@ -641,7 +682,7 @@ export class Realtime {
641
682
  }
642
683
  }
643
684
  });
644
- this.#log("Consumer is consuming");
685
+ this.#log(`Consumer is consuming for => ${topic}`);
645
686
  }
646
687
 
647
688
  /**
@@ -714,6 +755,10 @@ export class Realtime {
714
755
  return this.#natsClient?.info?.client_id
715
756
  }
716
757
 
758
+ #checkVarOk(variable){
759
+ return variable !== null && variable !== undefined
760
+ }
761
+
717
762
  async #pushLatencyData(data){
718
763
  this.#isSendingLatency = true;
719
764
 
@@ -853,20 +898,7 @@ export class Realtime {
853
898
  while (i < a.length || j < b.length) {
854
899
  const tokA = a[i];
855
900
  const tokB = b[j];
856
-
857
- /*──────────── literal match or single‑token wildcard on either side ────────────*/
858
- const singleWildcard =
859
- (tokA === "*" && j < b.length) ||
860
- (tokB === "*" && i < a.length);
861
-
862
- if (
863
- (tokA !== undefined && tokA === tokB) ||
864
- singleWildcard
865
- ) {
866
- i++; j++;
867
- continue;
868
- }
869
-
901
+
870
902
  /*────────────────── multi‑token wildcard ">" — must be **final** ───────────────*/
871
903
  if (tokA === ">") {
872
904
  if (i !== a.length - 1) return false; // '>' not in last position → invalid
@@ -883,6 +915,19 @@ export class Realtime {
883
915
  continue;
884
916
  }
885
917
 
918
+ /*──────────── literal match or single‑token wildcard on either side ────────────*/
919
+ const singleWildcard =
920
+ (tokA === "*" && j < b.length) ||
921
+ (tokB === "*" && i < a.length);
922
+
923
+ if (
924
+ (tokA !== undefined && tokA === tokB) ||
925
+ singleWildcard
926
+ ) {
927
+ i++; j++;
928
+ continue;
929
+ }
930
+
886
931
  /*───────────────────────────── back‑track using last '>' ───────────────────────*/
887
932
  if (starAi !== -1) { // let patternA's '>' absorb one more token of B
888
933
  j = ++starAj;
@@ -1046,6 +1091,14 @@ ${secret}
1046
1091
  return null;
1047
1092
  }
1048
1093
  }
1094
+
1095
+ testGetJetstream(){
1096
+ if(process.env.NODE_ENV == "test"){
1097
+ return this.#jetstream;
1098
+ }else{
1099
+ return null;
1100
+ }
1101
+ }
1049
1102
  }
1050
1103
 
1051
1104
  export const CONNECTED = "CONNECTED";
@@ -0,0 +1,113 @@
1
+ import { JetStreamApiError, JetStreamError } from "@nats-io/jetstream";
2
+
3
+ export class ErrorLogging {
4
+
5
+ logError(data){
6
+ var err = data.err;
7
+
8
+ if(err instanceof JetStreamApiError){
9
+ var code = err.code;
10
+
11
+ if(code == 10077){
12
+ // Code 10077 is for message limit exceeded
13
+ console.table({
14
+ Event: "Message Limit Exceeded",
15
+ Description: "Current message count for account exceeds plan defined limits. Upgrade plan to remove limits",
16
+ Link: "https://console.relay-x.io/billing"
17
+ })
18
+
19
+ throw new Error("Message limit exceeded!")
20
+ }
21
+ }
22
+
23
+ if(err instanceof JetStreamError){
24
+ var code = err.code;
25
+
26
+ if(code == 409){
27
+ // Consumer deleted
28
+
29
+ console.table({
30
+ Event: "Consumer Manually Deleted!",
31
+ Description: "Consumer was manually deleted by user using deleteConsumer() or the library equivalent",
32
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/detailed_doc/NodeJS/queue_consume#deleting-a-consumer"
33
+ })
34
+ }
35
+ }
36
+
37
+ if(err.name == "NatsError"){
38
+ var code = err.code;
39
+ var chainedError = err.chainedError;
40
+ var permissionContext = err.permissionContext;
41
+ var userOp = data.op;
42
+
43
+ if(code == "PERMISSIONS_VIOLATION"){
44
+ if(userOp == "publish"){
45
+ console.table({
46
+ Event: "Publish Permissions Violation",
47
+ Description: `User is not permitted to publish on '${data.topic}'`,
48
+ Topic: data.topic,
49
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/setup/api_key_permissions#messaging--publish-permissions"
50
+ })
51
+
52
+ throw new Error(`User is not permitted to publish on '${data.topic}'`)
53
+ }else if(userOp == "subscribe"){
54
+ console.table({
55
+ Event: "Subscribe Permissions Violation",
56
+ Description: `User is not permitted to subscribe to '${data.topic}'`,
57
+ Topic: data.topic,
58
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/setup/api_key_permissions#messaging--subscribe-permissions"
59
+ })
60
+
61
+ throw new Error(`User is not permitted to subscribe to '${data.topic}'`)
62
+ }else if(userOp == "kv_write"){
63
+ console.table({
64
+ Event: "KV Write Failure",
65
+ Description: `User is not permitted to write to KV Store`,
66
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/setup/api_key_permissions#write-permission"
67
+ })
68
+
69
+ throw new Error(`User is not permitted to write to KV Store`)
70
+ }else if(userOp == "kv_read"){
71
+ console.table({
72
+ Event: "KV Read Failure",
73
+ Description: `User is not permitted to read from KV Store`,
74
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/setup/api_key_permissions#read-permission"
75
+ })
76
+
77
+ throw new Error(`User is not permitted to read from KV Store`)
78
+ }else if(userOp == "kv_delete"){
79
+ console.table({
80
+ Event: "KV Key Delete Failure",
81
+ Description: `User is not permitted to delete key from KV Store`,
82
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/setup/api_key_permissions#write-permission"
83
+ })
84
+
85
+ throw new Error(`User is not permitted to delete key from KV Store`)
86
+ }
87
+ }else if(code == "AUTHORIZATION_VIOLATION"){
88
+ console.table({
89
+ Event: "Authentication Failure",
90
+ Description: `User failed to authenticate. Check if API key exists & if it is enabled`,
91
+ "Docs to Solve Issue": "https://docs.relay-x.io/docs/setup/api_key_permissions#enabling-and-disabling-keys"
92
+ })
93
+ }
94
+ }
95
+ }
96
+
97
+ }
98
+
99
+ export class Logging {
100
+
101
+ #debug = false;
102
+
103
+ constructor(debug){
104
+ this.#debug = debug !== null && debug !== undefined && typeof debug == "boolean" ? debug : false;
105
+ }
106
+
107
+ log(...msg){
108
+ if(this.#debug){
109
+ console.log(...msg)
110
+ }
111
+ }
112
+
113
+ }