relayx-webjs 1.0.2 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ V1.0.4
2
+ - Removed DNS Spoofing for testing
3
+
1
4
  V1.0.2
2
5
  - Wildcard topics for pub / sub fixes
3
6
  - Unit tests added for wildcard pattern matching
@@ -5,7 +5,8 @@
5
5
  "scripts": {
6
6
  "dev": "vite",
7
7
  "build": "vite build",
8
- "preview": "vite preview"
8
+ "preview": "vite preview",
9
+ "preview-proxy": "PROXY=true vite preview"
9
10
  },
10
11
  "dependencies": {
11
12
  "react": "^18.2.0",
@@ -0,0 +1,111 @@
1
+ import { Realtime, CONNECTED, RECONNECT, DISCONNECTED, MESSAGE_RESEND } from "../../realtime/realtime.js"
2
+ import * as readline from 'readline';
3
+
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout
7
+ });
8
+
9
+ async function run(){
10
+ var realtime = new Realtime({
11
+ api_key: process.env.AUTH_JWT,
12
+ secret: process.env.AUTH_SECRET
13
+ });
14
+ await realtime.init(false, {
15
+ max_retries: 2,
16
+ debug: true
17
+ });
18
+
19
+ realtime.on(CONNECTED, async () => {
20
+ console.log("[IMPL] => CONNECTED!");
21
+ });
22
+
23
+ realtime.on(RECONNECT, (status) => {
24
+ console.log(`[IMPL] RECONNECT => ${status}`)
25
+ });
26
+
27
+ realtime.on(DISCONNECTED, () => {
28
+ console.log(`[IMPL] DISONNECT`)
29
+ });
30
+
31
+ await realtime.on("power-telemetry", (data) => {
32
+ console.log("power-telemetry", data);
33
+ });
34
+
35
+ await realtime.on("hello.>", async (data) => {
36
+ console.log("hello.>", data);
37
+ });
38
+
39
+ // await realtime.on("hello.hey.*", (data) => {
40
+ // console.log("hell.hey.*", data);
41
+ // });
42
+
43
+ // await realtime.on("hello.hey.>", (data) => {
44
+ // console.log("hello.hey.>", data);
45
+ // });
46
+
47
+ realtime.on(MESSAGE_RESEND, (data) => {
48
+ console.log(`[MSG RESEND] => ${data}`)
49
+ });
50
+
51
+ rl.on('line', async (input) => {
52
+ console.log(`You entered: ${input}`);
53
+
54
+ if(input == "exit"){
55
+ var output = await realtime.off("hello");
56
+ console.log(output);
57
+
58
+ realtime.close();
59
+
60
+ process.exit();
61
+ }else if(input == "history"){
62
+ rl.question("topic: ", async (topic) => {
63
+ var start = new Date();
64
+ var past = start.setDate(start.getDate() - 4)
65
+ var pastDate = new Date(past)
66
+
67
+ var end = new Date();
68
+ var past = end.setDate(end.getDate())
69
+ var endDate = new Date(past)
70
+
71
+ var history = await realtime.history(topic, pastDate)
72
+ console.log(history)
73
+ })
74
+ }else if(input == "off"){
75
+ rl.question("topic to off(): ", async (topic) => {
76
+ await realtime.off(topic);
77
+ console.log("off() executed")
78
+ })
79
+
80
+
81
+ }else if(input == "close"){
82
+ realtime.close();
83
+ console.log("Connection closed");
84
+ }else if(input == "init"){
85
+ await realtime.connect()
86
+ }else if(input == "on"){
87
+ rl.question("topic: ", async (topic) => {
88
+ await realtime.on(topic, (data) => {
89
+ console.log(topic, data);
90
+ });
91
+ })
92
+ }else{
93
+ rl.question("topic: ", async (topic) => {
94
+ var output = await realtime.publish(topic, input);
95
+ })
96
+ }
97
+ });
98
+
99
+ realtime.connect();
100
+
101
+ process.on('SIGINT', async () => {
102
+ console.log('Keyboard interrupt detected (Ctrl+C). Cleaning up...');
103
+ // Perform any necessary cleanup here
104
+
105
+ // Exit the process
106
+ process.exit();
107
+ });
108
+
109
+ }
110
+
111
+ await run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayx-webjs",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A powerful library for integrating real-time communication into your webapps, powered by the Relay Network.",
5
5
  "main": "realtime/realtime.js",
6
6
  "type": "module",
@@ -21,6 +21,8 @@ export class Realtime {
21
21
  #RECONNECTED = "RECONNECTED";
22
22
  #RECONN_FAIL = "RECONN_FAIL";
23
23
 
24
+ #reservedSystemTopics = [CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED, this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT];
25
+
24
26
  setRemoteUserAttempts = 0;
25
27
  setRemoteUserRetries = 5;
26
28
 
@@ -40,6 +42,8 @@ export class Realtime {
40
42
 
41
43
  #maxPublishRetries = 5;
42
44
 
45
+ #connectCalled = false;
46
+
43
47
  constructor(config){
44
48
  if(typeof config != "object"){
45
49
  throw new Error("Realtime($config). $config not object => {}")
@@ -95,10 +99,13 @@ export class Realtime {
95
99
  if(arguments[0] instanceof Object){
96
100
  opts = arguments[0];
97
101
  staging = false;
98
- }else{
102
+ }else if(typeof arguments[0] == "boolean"){
99
103
  opts = {};
100
104
  staging = arguments[0];
101
105
  this.#log(staging)
106
+ }else{
107
+ opts = {};
108
+ staging = false
102
109
  }
103
110
  }else{
104
111
  staging = false;
@@ -108,23 +115,27 @@ export class Realtime {
108
115
  this.staging = staging;
109
116
  this.opts = opts;
110
117
 
111
- if (staging !== undefined || staging !== null){
112
- this.#baseUrl = staging ? [
113
- "nats://0.0.0.0:4421",
114
- "nats://0.0.0.0:4422",
115
- "nats://0.0.0.0:4423"
116
- ] :
117
- [
118
+ if(process.env.PROXY){
119
+ this.#baseUrl = ["wss://api2.relay-x.io:8666"];
120
+ }else{
121
+ if (staging !== undefined || staging !== null){
122
+ this.#baseUrl = staging ? [
123
+ "nats://0.0.0.0:4421",
124
+ "nats://0.0.0.0:4422",
125
+ "nats://0.0.0.0:4423"
126
+ ] :
127
+ [
128
+ `wss://api.relay-x.io:4421`,
129
+ `wss://api.relay-x.io:4422`,
130
+ `wss://api.relay-x.io:4423`
131
+ ];
132
+ }else{
133
+ this.#baseUrl = [
118
134
  `wss://api.relay-x.io:4421`,
119
135
  `wss://api.relay-x.io:4422`,
120
136
  `wss://api.relay-x.io:4423`
121
137
  ];
122
- }else{
123
- this.#baseUrl = [
124
- `wss://api.relay-x.io:4421`,
125
- `wss://api.relay-x.io:4422`,
126
- `wss://api.relay-x.io:4423`
127
- ];
138
+ }
128
139
  }
129
140
 
130
141
  this.#log(this.#baseUrl);
@@ -164,6 +175,10 @@ export class Realtime {
164
175
  * Connects to the relay network
165
176
  */
166
177
  async connect(){
178
+ if(this.#connectCalled){
179
+ return;
180
+ }
181
+
167
182
  this.SEVER_URL = this.#baseUrl;
168
183
 
169
184
  var credsFile = this.#getUserCreds(this.api_key, this.secret)
@@ -178,7 +193,7 @@ export class Realtime {
178
193
  maxReconnectAttempts: 1200,
179
194
  reconnectTimeWait: 1000,
180
195
  authenticator: credsAuth,
181
- token: this.api_key,
196
+ token: this.api_key
182
197
  });
183
198
 
184
199
  this.#jetstream = await jetstream(this.#natsClient);
@@ -186,6 +201,7 @@ export class Realtime {
186
201
  await this.#getNameSpace()
187
202
 
188
203
  this.connected = true;
204
+ this.#connectCalled = true;
189
205
  }catch(err){
190
206
  this.#log("ERR")
191
207
  this.#log(err);
@@ -196,17 +212,13 @@ export class Realtime {
196
212
  if (this.connected == true){
197
213
  this.#log("Connected to server!");
198
214
 
199
- // Callback on client side
200
- if (CONNECTED in this.#event_func){
201
- if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
202
- this.#event_func[CONNECTED]()
203
- }
204
- }
205
-
206
215
  this.#natsClient.closed().then(() => {
207
216
  this.#log("the connection closed!");
208
217
 
209
218
  this.#offlineMessageBuffer.length = 0;
219
+ this.connected = false;
220
+ this.reconnecting = false;
221
+ this.#connectCalled = false;
210
222
 
211
223
  if (DISCONNECTED in this.#event_func){
212
224
  if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
@@ -224,12 +236,6 @@ export class Realtime {
224
236
  this.#log(`client disconnected - ${s.data}`);
225
237
 
226
238
  this.connected = false;
227
-
228
- if (DISCONNECTED in this.#event_func){
229
- if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
230
- this.#event_func[DISCONNECTED]()
231
- }
232
- }
233
239
  break;
234
240
  case Events.LDM:
235
241
  this.#log("client has been requested to reconnect");
@@ -259,6 +265,7 @@ export class Realtime {
259
265
  this.#log("client is attempting to reconnect");
260
266
 
261
267
  this.reconnecting = true;
268
+ this.connected = false;
262
269
 
263
270
  if(RECONNECT in this.#event_func && this.reconnecting){
264
271
  this.#event_func[RECONNECT](this.#RECONNECTING);
@@ -276,6 +283,13 @@ export class Realtime {
276
283
  // Subscribe to topics
277
284
  this.#subscribeToTopics();
278
285
  this.#log("Subscribed to topics");
286
+
287
+ // Callback on client side
288
+ if (CONNECTED in this.#event_func){
289
+ if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
290
+ this.#event_func[CONNECTED]()
291
+ }
292
+ }
279
293
  }
280
294
  }
281
295
 
@@ -286,10 +300,11 @@ export class Realtime {
286
300
  if(this.#natsClient !== null){
287
301
  this.reconnected = false;
288
302
  this.disconnected = true;
303
+ this.#connectCalled = false;
289
304
 
290
305
  this.#offlineMessageBuffer.length = 0;
291
306
 
292
- await this.#deleteConsumer();
307
+ await this.#deleteAllConsumers();
293
308
 
294
309
  this.#natsClient.close();
295
310
  }else{
@@ -301,8 +316,20 @@ export class Realtime {
301
316
  * Start consumers for topics initialized by user
302
317
  */
303
318
  async #subscribeToTopics(){
304
- if(this.#topicMap.length > 0){
305
- await this.#startConsumer();
319
+ this.#topicMap.forEach(async (topic) => {
320
+ // Subscribe to stream
321
+ await this.#startConsumer(topic);
322
+ });
323
+ }
324
+
325
+ /**
326
+ * Delete consumers for topics initialized by user
327
+ */
328
+ async #deleteAllConsumers(){
329
+ for(let i = 0; i < this.#topicMap.length; i++){
330
+ let topic = this.#topicMap[i];
331
+
332
+ await this.#deleteConsumer(topic);
306
333
  }
307
334
  }
308
335
 
@@ -324,6 +351,8 @@ export class Realtime {
324
351
  this.#topicMap = this.#topicMap.filter(item => item !== topic);
325
352
 
326
353
  delete this.#event_func[topic];
354
+
355
+ return await this.#deleteConsumer(topic);
327
356
  }
328
357
 
329
358
  /**
@@ -349,31 +378,23 @@ export class Realtime {
349
378
  throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
350
379
  }
351
380
 
352
- if(!(topic in this.#event_func)){
353
- this.#event_func[topic] = func;
354
- }else{
381
+ if(topic in this.#event_func || this.#topicMap.includes(topic)){
355
382
  return false
356
383
  }
357
384
 
358
- if (![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
359
- this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic)){
360
- if(!this.isTopicValid(topic)){
361
- // We have an invalid topic, lets remove it
362
- if(topic in this.#event_func){
363
- delete this.#event_func[topic];
364
- }
385
+ this.#event_func[topic] = func;
365
386
 
366
- throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
367
- }
387
+ if (!this.#reservedSystemTopics.includes(topic)){
388
+ if(!this.isTopicValid(topic)){
389
+ throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
390
+ }
368
391
 
369
- if(!this.#topicMap.includes(topic)){
370
- this.#topicMap.push(topic);
371
- }
392
+ this.#topicMap.push(topic);
372
393
 
373
- if(this.connected){
374
- // Connected we need to create a topic in a stream
375
- await this.#startConsumer(topic);
376
- }
394
+ if(this.connected){
395
+ // Connected we need to create a topic in a stream
396
+ await this.#startConsumer(topic);
397
+ }
377
398
  }
378
399
 
379
400
  return true;
@@ -403,7 +424,7 @@ export class Realtime {
403
424
  throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
404
425
  }
405
426
 
406
- if(!this.#isMessageValid(data)){
427
+ if(!this.isMessageValid(data)){
407
428
  throw new Error("$message must be JSON, string or number")
408
429
  }
409
430
 
@@ -418,15 +439,9 @@ export class Realtime {
418
439
  "start": Date.now()
419
440
  }
420
441
 
421
- this.#log("Encoding message via msg pack...")
422
- var encodedMessage = encode(message);
423
-
424
442
  if(this.connected){
425
- if(!this.#topicMap.includes(topic)){
426
- this.#topicMap.push(topic);
427
- }else{
428
- this.#log(`${topic} exists locally, moving on...`)
429
- }
443
+ this.#log("Encoding message via msg pack...")
444
+ var encodedMessage = encode(message);
430
445
 
431
446
  this.#log(`Publishing to topic => ${this.#getStreamTopic(topic)}`)
432
447
 
@@ -453,7 +468,6 @@ export class Realtime {
453
468
  * @param {string} topic
454
469
  */
455
470
  async history(topic, start, end){
456
- this.#log(start)
457
471
  if(topic == null || topic == undefined){
458
472
  throw new Error("$topic is null or undefined");
459
473
  }
@@ -490,11 +504,16 @@ export class Realtime {
490
504
  end = end.toISOString();
491
505
  }
492
506
 
507
+ if(!this.connected){
508
+ return [];
509
+ }
510
+
493
511
  var opts = {
494
- name: `${topic}_${uuidv4()}_history`,
512
+ name: `webjs_${topic}_${uuidv4()}_history_consumer`,
495
513
  filter_subjects: [this.#getStreamTopic(topic)],
496
514
  replay_policy: ReplayPolicy.Instant,
497
515
  opt_start_time: start,
516
+ delivery_policy: DeliverPolicy.StartTime,
498
517
  ack_policy: AckPolicy.Explicit,
499
518
  }
500
519
 
@@ -523,7 +542,12 @@ export class Realtime {
523
542
  var data = decode(msg.data);
524
543
  this.#log(data);
525
544
 
526
- history.push(data.message);
545
+ history.push({
546
+ "id": data.id,
547
+ "topic": data.room,
548
+ "message": data.message,
549
+ "timestamp": msg.timestamp
550
+ });
527
551
  }
528
552
 
529
553
  var del = await consumer.delete();
@@ -571,46 +595,42 @@ export class Realtime {
571
595
  * @param {string} topic
572
596
  */
573
597
  async #startConsumer(topic){
574
- if(this.#consumer != null){
575
- return;
576
- }
577
-
578
- this.#log(`Starting consumer for topic: ${topic}_${uuidv4()}`)
598
+ const consumerName = `webjs_${topic}_${uuidv4()}_consumer`;
579
599
 
580
600
  var opts = {
581
- name: `${uuidv4()}`,
582
- filter_subjects: [this.#getStreamTopic(">")],
601
+ name: consumerName,
602
+ filter_subjects: [this.#getStreamTopic(topic)],
583
603
  replay_policy: ReplayPolicy.Instant,
584
604
  opt_start_time: new Date(),
585
605
  ack_policy: AckPolicy.Explicit,
586
606
  delivery_policy: DeliverPolicy.New
587
607
  }
588
608
 
589
- this.#consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
609
+ const consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
590
610
  this.#log(this.#topicMap)
591
611
 
592
- await this.#consumer.consume({
612
+ this.#consumerMap[topic] = consumer;
613
+
614
+ await consumer.consume({
593
615
  callback: async (msg) => {
594
616
  try{
595
617
  const now = Date.now();
618
+ msg.working()
596
619
  this.#log("Decoding msgpack message...")
597
620
  var data = decode(msg.data);
598
621
 
599
- var room = this.#stripStreamHash(msg.subject);
622
+ var msgTopic = this.#stripStreamHash(msg.subject);
600
623
 
601
624
  this.#log(data);
602
625
 
603
626
  // Push topic message to main thread
604
627
  if (data.client_id != this.#getClientId()){
605
- var topics = this.#getCallbackTopics(room);
606
- this.#log(topics)
607
-
608
- for(let i = 0; i < topics.length; i++){
609
- var top = topics[i];
628
+ var topicMatch = this.#topicPatternMatcher(topic, msgTopic)
610
629
 
611
- this.#event_func[top]({
630
+ if(topicMatch){
631
+ this.#event_func[topic]({
612
632
  "id": data.id,
613
- "topic": room,
633
+ "topic": msgTopic,
614
634
  "data": data.message
615
635
  });
616
636
  }
@@ -632,15 +652,20 @@ export class Realtime {
632
652
  * Deletes consumer
633
653
  * @param {string} topic
634
654
  */
635
- async #deleteConsumer(){
655
+ async #deleteConsumer(topic){
656
+ this.#log(topic)
657
+ const consumer = this.#consumerMap[topic]
658
+
636
659
  var del = false;
637
660
 
638
- if (this.#consumer != null && this.#consumer != undefined){
639
- del = await this.#consumer.delete();
661
+ if (consumer != null && consumer != undefined){
662
+ del = await consumer.delete();
640
663
  }else{
641
664
  del = false
642
665
  }
643
666
 
667
+ delete this.#consumerMap[topic];
668
+
644
669
  return del;
645
670
  }
646
671
 
@@ -650,11 +675,6 @@ export class Realtime {
650
675
  return;
651
676
  }
652
677
 
653
- if(this.#latency.length >= 100){
654
- this.#log("Latency array is full, skipping log");
655
- return;
656
- }
657
-
658
678
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
659
679
 
660
680
  this.#log(`Timezone: ${timeZone}`);
@@ -671,7 +691,7 @@ export class Realtime {
671
691
  this.#latencyPush = setTimeout(async () => {
672
692
  this.#log("setTimeout called");
673
693
 
674
- if(this.#latency.length > 0){
694
+ if(this.#latency.length > 0 && this.connected && !this.#isSendingLatency){
675
695
  this.#log("Push from setTimeout")
676
696
  await this.#pushLatencyData({
677
697
  timezone: timeZone,
@@ -684,7 +704,7 @@ export class Realtime {
684
704
  }, 30000);
685
705
  }
686
706
 
687
- if(this.#latency.length == 100 && !this.#isSendingLatency){
707
+ if(this.#latency.length >= 100 && !this.#isSendingLatency){
688
708
  this.#log("Push from Length Check: " + this.#latency.length);
689
709
  await this.#pushLatencyData({
690
710
  timezone: timeZone,
@@ -740,8 +760,7 @@ export class Realtime {
740
760
  */
741
761
  isTopicValid(topic){
742
762
  if(topic !== null && topic !== undefined && (typeof topic) == "string"){
743
- var arrayCheck = ![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
744
- this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic);
763
+ var arrayCheck = !this.#reservedSystemTopics.includes(topic);
745
764
 
746
765
  const TOPIC_REGEX = /^(?!.*\$)(?:[A-Za-z0-9_*~-]+(?:\.[A-Za-z0-9_*~-]+)*(?:\.>)?|>)$/u;
747
766
 
@@ -753,7 +772,7 @@ export class Realtime {
753
772
  }
754
773
  }
755
774
 
756
- #isMessageValid(message){
775
+ isMessageValid(message){
757
776
  if(message == null || message == undefined){
758
777
  throw new Error("$message cannot be null / undefined")
759
778
  }