relayx-webjs 1.0.2 → 1.0.3

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.
@@ -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.3",
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",
@@ -0,0 +1,20 @@
1
+ import dns from 'node:dns';
2
+
3
+ export function initDNSSpoof(){
4
+ const originalLookup = dns.lookup;
5
+
6
+ // Override for the whole process
7
+ dns.lookup = function patchedLookup(hostname, options, callback) {
8
+
9
+ // ── Our one special case ──────────────────────────────────
10
+ if (hostname === 'api2.relay-x.io') {
11
+ // Map to loop‑back; family 4 avoids ::1
12
+ return process.nextTick(() =>
13
+ callback(null, [{address: '127.0.0.1', family: 4}])
14
+ );
15
+ }
16
+
17
+ // Anything else → real DNS
18
+ return originalLookup.call(dns, hostname, options, callback);
19
+ };
20
+ }
@@ -2,6 +2,7 @@ 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 { initDNSSpoof } from "./dns_change.js";
5
6
 
6
7
  export class Realtime {
7
8
 
@@ -21,6 +22,8 @@ export class Realtime {
21
22
  #RECONNECTED = "RECONNECTED";
22
23
  #RECONN_FAIL = "RECONN_FAIL";
23
24
 
25
+ #reservedSystemTopics = [CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED, this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT];
26
+
24
27
  setRemoteUserAttempts = 0;
25
28
  setRemoteUserRetries = 5;
26
29
 
@@ -40,6 +43,8 @@ export class Realtime {
40
43
 
41
44
  #maxPublishRetries = 5;
42
45
 
46
+ #connectCalled = false;
47
+
43
48
  constructor(config){
44
49
  if(typeof config != "object"){
45
50
  throw new Error("Realtime($config). $config not object => {}")
@@ -95,10 +100,13 @@ export class Realtime {
95
100
  if(arguments[0] instanceof Object){
96
101
  opts = arguments[0];
97
102
  staging = false;
98
- }else{
103
+ }else if(typeof arguments[0] == "boolean"){
99
104
  opts = {};
100
105
  staging = arguments[0];
101
106
  this.#log(staging)
107
+ }else{
108
+ opts = {};
109
+ staging = false
102
110
  }
103
111
  }else{
104
112
  staging = false;
@@ -108,23 +116,28 @@ export class Realtime {
108
116
  this.staging = staging;
109
117
  this.opts = opts;
110
118
 
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
- [
119
+ if(process.env.PROXY){
120
+ this.#baseUrl = ["wss://api2.relay-x.io:8666"];
121
+ initDNSSpoof();
122
+ }else{
123
+ if (staging !== undefined || staging !== null){
124
+ this.#baseUrl = staging ? [
125
+ "nats://0.0.0.0:4421",
126
+ "nats://0.0.0.0:4422",
127
+ "nats://0.0.0.0:4423"
128
+ ] :
129
+ [
130
+ `wss://api.relay-x.io:4421`,
131
+ `wss://api.relay-x.io:4422`,
132
+ `wss://api.relay-x.io:4423`
133
+ ];
134
+ }else{
135
+ this.#baseUrl = [
118
136
  `wss://api.relay-x.io:4421`,
119
137
  `wss://api.relay-x.io:4422`,
120
138
  `wss://api.relay-x.io:4423`
121
139
  ];
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
- ];
140
+ }
128
141
  }
129
142
 
130
143
  this.#log(this.#baseUrl);
@@ -164,6 +177,10 @@ export class Realtime {
164
177
  * Connects to the relay network
165
178
  */
166
179
  async connect(){
180
+ if(this.#connectCalled){
181
+ return;
182
+ }
183
+
167
184
  this.SEVER_URL = this.#baseUrl;
168
185
 
169
186
  var credsFile = this.#getUserCreds(this.api_key, this.secret)
@@ -178,7 +195,7 @@ export class Realtime {
178
195
  maxReconnectAttempts: 1200,
179
196
  reconnectTimeWait: 1000,
180
197
  authenticator: credsAuth,
181
- token: this.api_key,
198
+ token: this.api_key
182
199
  });
183
200
 
184
201
  this.#jetstream = await jetstream(this.#natsClient);
@@ -186,6 +203,7 @@ export class Realtime {
186
203
  await this.#getNameSpace()
187
204
 
188
205
  this.connected = true;
206
+ this.#connectCalled = true;
189
207
  }catch(err){
190
208
  this.#log("ERR")
191
209
  this.#log(err);
@@ -196,17 +214,13 @@ export class Realtime {
196
214
  if (this.connected == true){
197
215
  this.#log("Connected to server!");
198
216
 
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
217
  this.#natsClient.closed().then(() => {
207
218
  this.#log("the connection closed!");
208
219
 
209
220
  this.#offlineMessageBuffer.length = 0;
221
+ this.connected = false;
222
+ this.reconnecting = false;
223
+ this.#connectCalled = false;
210
224
 
211
225
  if (DISCONNECTED in this.#event_func){
212
226
  if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
@@ -224,12 +238,6 @@ export class Realtime {
224
238
  this.#log(`client disconnected - ${s.data}`);
225
239
 
226
240
  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
241
  break;
234
242
  case Events.LDM:
235
243
  this.#log("client has been requested to reconnect");
@@ -259,6 +267,7 @@ export class Realtime {
259
267
  this.#log("client is attempting to reconnect");
260
268
 
261
269
  this.reconnecting = true;
270
+ this.connected = false;
262
271
 
263
272
  if(RECONNECT in this.#event_func && this.reconnecting){
264
273
  this.#event_func[RECONNECT](this.#RECONNECTING);
@@ -276,6 +285,13 @@ export class Realtime {
276
285
  // Subscribe to topics
277
286
  this.#subscribeToTopics();
278
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
+ }
279
295
  }
280
296
  }
281
297
 
@@ -286,10 +302,11 @@ export class Realtime {
286
302
  if(this.#natsClient !== null){
287
303
  this.reconnected = false;
288
304
  this.disconnected = true;
305
+ this.#connectCalled = false;
289
306
 
290
307
  this.#offlineMessageBuffer.length = 0;
291
308
 
292
- await this.#deleteConsumer();
309
+ await this.#deleteAllConsumers();
293
310
 
294
311
  this.#natsClient.close();
295
312
  }else{
@@ -301,8 +318,20 @@ export class Realtime {
301
318
  * Start consumers for topics initialized by user
302
319
  */
303
320
  async #subscribeToTopics(){
304
- if(this.#topicMap.length > 0){
305
- 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);
306
335
  }
307
336
  }
308
337
 
@@ -324,6 +353,8 @@ export class Realtime {
324
353
  this.#topicMap = this.#topicMap.filter(item => item !== topic);
325
354
 
326
355
  delete this.#event_func[topic];
356
+
357
+ return await this.#deleteConsumer(topic);
327
358
  }
328
359
 
329
360
  /**
@@ -349,31 +380,23 @@ export class Realtime {
349
380
  throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
350
381
  }
351
382
 
352
- if(!(topic in this.#event_func)){
353
- this.#event_func[topic] = func;
354
- }else{
383
+ if(topic in this.#event_func || this.#topicMap.includes(topic)){
355
384
  return false
356
385
  }
357
386
 
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
- }
387
+ this.#event_func[topic] = func;
365
388
 
366
- throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
367
- }
389
+ if (!this.#reservedSystemTopics.includes(topic)){
390
+ if(!this.isTopicValid(topic)){
391
+ throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
392
+ }
368
393
 
369
- if(!this.#topicMap.includes(topic)){
370
- this.#topicMap.push(topic);
371
- }
394
+ this.#topicMap.push(topic);
372
395
 
373
- if(this.connected){
374
- // Connected we need to create a topic in a stream
375
- await this.#startConsumer(topic);
376
- }
396
+ if(this.connected){
397
+ // Connected we need to create a topic in a stream
398
+ await this.#startConsumer(topic);
399
+ }
377
400
  }
378
401
 
379
402
  return true;
@@ -403,7 +426,7 @@ export class Realtime {
403
426
  throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
404
427
  }
405
428
 
406
- if(!this.#isMessageValid(data)){
429
+ if(!this.isMessageValid(data)){
407
430
  throw new Error("$message must be JSON, string or number")
408
431
  }
409
432
 
@@ -418,15 +441,9 @@ export class Realtime {
418
441
  "start": Date.now()
419
442
  }
420
443
 
421
- this.#log("Encoding message via msg pack...")
422
- var encodedMessage = encode(message);
423
-
424
444
  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
- }
445
+ this.#log("Encoding message via msg pack...")
446
+ var encodedMessage = encode(message);
430
447
 
431
448
  this.#log(`Publishing to topic => ${this.#getStreamTopic(topic)}`)
432
449
 
@@ -453,7 +470,6 @@ export class Realtime {
453
470
  * @param {string} topic
454
471
  */
455
472
  async history(topic, start, end){
456
- this.#log(start)
457
473
  if(topic == null || topic == undefined){
458
474
  throw new Error("$topic is null or undefined");
459
475
  }
@@ -490,11 +506,16 @@ export class Realtime {
490
506
  end = end.toISOString();
491
507
  }
492
508
 
509
+ if(!this.connected){
510
+ return [];
511
+ }
512
+
493
513
  var opts = {
494
- name: `${topic}_${uuidv4()}_history`,
514
+ name: `webjs_${topic}_${uuidv4()}_history_consumer`,
495
515
  filter_subjects: [this.#getStreamTopic(topic)],
496
516
  replay_policy: ReplayPolicy.Instant,
497
517
  opt_start_time: start,
518
+ delivery_policy: DeliverPolicy.StartTime,
498
519
  ack_policy: AckPolicy.Explicit,
499
520
  }
500
521
 
@@ -523,7 +544,12 @@ export class Realtime {
523
544
  var data = decode(msg.data);
524
545
  this.#log(data);
525
546
 
526
- history.push(data.message);
547
+ history.push({
548
+ "id": data.id,
549
+ "topic": data.room,
550
+ "message": data.message,
551
+ "timestamp": msg.timestamp
552
+ });
527
553
  }
528
554
 
529
555
  var del = await consumer.delete();
@@ -571,46 +597,42 @@ export class Realtime {
571
597
  * @param {string} topic
572
598
  */
573
599
  async #startConsumer(topic){
574
- if(this.#consumer != null){
575
- return;
576
- }
577
-
578
- this.#log(`Starting consumer for topic: ${topic}_${uuidv4()}`)
600
+ const consumerName = `webjs_${topic}_${uuidv4()}_consumer`;
579
601
 
580
602
  var opts = {
581
- name: `${uuidv4()}`,
582
- filter_subjects: [this.#getStreamTopic(">")],
603
+ name: consumerName,
604
+ filter_subjects: [this.#getStreamTopic(topic)],
583
605
  replay_policy: ReplayPolicy.Instant,
584
606
  opt_start_time: new Date(),
585
607
  ack_policy: AckPolicy.Explicit,
586
608
  delivery_policy: DeliverPolicy.New
587
609
  }
588
610
 
589
- this.#consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
611
+ const consumer = await this.#jetstream.consumers.get(this.#getStreamName(), opts);
590
612
  this.#log(this.#topicMap)
591
613
 
592
- await this.#consumer.consume({
614
+ this.#consumerMap[topic] = consumer;
615
+
616
+ await consumer.consume({
593
617
  callback: async (msg) => {
594
618
  try{
595
619
  const now = Date.now();
620
+ msg.working()
596
621
  this.#log("Decoding msgpack message...")
597
622
  var data = decode(msg.data);
598
623
 
599
- var room = this.#stripStreamHash(msg.subject);
624
+ var msgTopic = this.#stripStreamHash(msg.subject);
600
625
 
601
626
  this.#log(data);
602
627
 
603
628
  // Push topic message to main thread
604
629
  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];
630
+ var topicMatch = this.#topicPatternMatcher(topic, msgTopic)
610
631
 
611
- this.#event_func[top]({
632
+ if(topicMatch){
633
+ this.#event_func[topic]({
612
634
  "id": data.id,
613
- "topic": room,
635
+ "topic": msgTopic,
614
636
  "data": data.message
615
637
  });
616
638
  }
@@ -632,15 +654,20 @@ export class Realtime {
632
654
  * Deletes consumer
633
655
  * @param {string} topic
634
656
  */
635
- async #deleteConsumer(){
657
+ async #deleteConsumer(topic){
658
+ this.#log(topic)
659
+ const consumer = this.#consumerMap[topic]
660
+
636
661
  var del = false;
637
662
 
638
- if (this.#consumer != null && this.#consumer != undefined){
639
- del = await this.#consumer.delete();
663
+ if (consumer != null && consumer != undefined){
664
+ del = await consumer.delete();
640
665
  }else{
641
666
  del = false
642
667
  }
643
668
 
669
+ delete this.#consumerMap[topic];
670
+
644
671
  return del;
645
672
  }
646
673
 
@@ -650,11 +677,6 @@ export class Realtime {
650
677
  return;
651
678
  }
652
679
 
653
- if(this.#latency.length >= 100){
654
- this.#log("Latency array is full, skipping log");
655
- return;
656
- }
657
-
658
680
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
659
681
 
660
682
  this.#log(`Timezone: ${timeZone}`);
@@ -671,7 +693,7 @@ export class Realtime {
671
693
  this.#latencyPush = setTimeout(async () => {
672
694
  this.#log("setTimeout called");
673
695
 
674
- if(this.#latency.length > 0){
696
+ if(this.#latency.length > 0 && this.connected && !this.#isSendingLatency){
675
697
  this.#log("Push from setTimeout")
676
698
  await this.#pushLatencyData({
677
699
  timezone: timeZone,
@@ -684,7 +706,7 @@ export class Realtime {
684
706
  }, 30000);
685
707
  }
686
708
 
687
- if(this.#latency.length == 100 && !this.#isSendingLatency){
709
+ if(this.#latency.length >= 100 && !this.#isSendingLatency){
688
710
  this.#log("Push from Length Check: " + this.#latency.length);
689
711
  await this.#pushLatencyData({
690
712
  timezone: timeZone,
@@ -740,8 +762,7 @@ export class Realtime {
740
762
  */
741
763
  isTopicValid(topic){
742
764
  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);
765
+ var arrayCheck = !this.#reservedSystemTopics.includes(topic);
745
766
 
746
767
  const TOPIC_REGEX = /^(?!.*\$)(?:[A-Za-z0-9_*~-]+(?:\.[A-Za-z0-9_*~-]+)*(?:\.>)?|>)$/u;
747
768
 
@@ -753,7 +774,7 @@ export class Realtime {
753
774
  }
754
775
  }
755
776
 
756
- #isMessageValid(message){
777
+ isMessageValid(message){
757
778
  if(message == null || message == undefined){
758
779
  throw new Error("$message cannot be null / undefined")
759
780
  }