relayx-js 1.0.14 → 1.0.16

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,10 @@
1
+ V1.0.16
2
+ - URL Fix
3
+
4
+ V1.0.15
5
+ - Wildcard topic pub / sub
6
+ - Message replay on reconnect
7
+
1
8
  V1.0.8
2
9
  - History API fetch() to consume()
3
10
 
@@ -11,7 +11,7 @@ async function run(){
11
11
  api_key: process.env.AUTH_JWT,
12
12
  secret: process.env.AUTH_SECRET
13
13
  });
14
- await realtime.init(true, {
14
+ await realtime.init(false, {
15
15
  max_retries: 2,
16
16
  debug: true
17
17
  });
@@ -32,8 +32,8 @@ async function run(){
32
32
  console.log("power-telemetry", data);
33
33
  });
34
34
 
35
- await realtime.on("test232", (data) => {
36
- console.log("test232", data);
35
+ await realtime.on("hello.*", (data) => {
36
+ console.log("hello.*", data);
37
37
  });
38
38
 
39
39
  realtime.on(MESSAGE_RESEND, (data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayx-js",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "main": "realtime/realtime.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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
 
@@ -109,23 +110,28 @@ export class Realtime {
109
110
  this.staging = staging;
110
111
  this.opts = opts;
111
112
 
112
- if (staging !== undefined || staging !== null){
113
- this.#baseUrl = staging ? [
114
- "nats://0.0.0.0:4221",
115
- "nats://0.0.0.0:4222",
116
- "nats://0.0.0.0:4223"
117
- ] :
118
- [
113
+ if(process.env.PROXY){
114
+ this.#baseUrl = ["tls://api2.relay-x.io:8666"];
115
+ initDNSSpoof();
116
+ }else{
117
+ if (staging !== undefined || staging !== null){
118
+ this.#baseUrl = staging ? [
119
+ "nats://0.0.0.0:4221",
120
+ "nats://0.0.0.0:4222",
121
+ "nats://0.0.0.0:4223",
122
+ ] :
123
+ [
124
+ `tls://api.relay-x.io:4221`,
125
+ `tls://api.relay-x.io:4222`,
126
+ `tls://api.relay-x.io:4223`
127
+ ];
128
+ }else{
129
+ this.#baseUrl = [
119
130
  `tls://api.relay-x.io:4221`,
120
131
  `tls://api.relay-x.io:4222`,
121
132
  `tls://api.relay-x.io:4223`
122
133
  ];
123
- }else{
124
- this.#baseUrl = [
125
- `tls://api.relay-x.io:4221`,
126
- `tls://api.relay-x.io:4222`,
127
- `tls://api.relay-x.io:4223`
128
- ];
134
+ }
129
135
  }
130
136
 
131
137
  this.#log(this.#baseUrl);
@@ -180,6 +186,7 @@ export class Realtime {
180
186
  reconnectTimeWait: 1000,
181
187
  authenticator: credsAuth,
182
188
  token: this.api_key,
189
+ resolve: process.env.PROXY ? false : true
183
190
  });
184
191
 
185
192
  this.#jetstream = await jetstream(this.#natsClient);
@@ -574,7 +581,7 @@ export class Realtime {
574
581
 
575
582
  var opts = {
576
583
  name: `${topic}_${uuidv4()}`,
577
- filter_subjects: [this.#getStreamTopic(topic), this.#getStreamTopic(topic) + "_presence"],
584
+ filter_subjects: [this.#getStreamTopic(topic)],
578
585
  replay_policy: ReplayPolicy.Instant,
579
586
  opt_start_time: new Date(),
580
587
  ack_policy: AckPolicy.Explicit,
@@ -664,7 +671,7 @@ export class Realtime {
664
671
  this.#latencyPush = setTimeout(async () => {
665
672
  this.#log("setTimeout called");
666
673
 
667
- if(this.#latency.length > 0){
674
+ if(this.#latency.length > 0 && this.connected){
668
675
  this.#log("Push from setTimeout")
669
676
  await this.#pushLatencyData({
670
677
  timezone: timeZone,
@@ -736,7 +743,9 @@ export class Realtime {
736
743
  var arrayCheck = ![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
737
744
  this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic);
738
745
 
739
- var spaceStarCheck = !topic.includes(" ") && !topic.includes("*") && !topic.includes(".");
746
+ const TOPIC_REGEX = /^(?!\$)[A-Za-z0-9_,.*>\$-]+$/;
747
+
748
+ var spaceStarCheck = !topic.includes(" ") && TOPIC_REGEX.test(topic);
740
749
 
741
750
  return arrayCheck && spaceStarCheck;
742
751
  }else{
package/tests/test.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Realtime, CONNECTED, RECONNECT, DISCONNECTED, MESSAGE_RESEND } from "../realtime/realtime.js";
2
- import axios from "axios";
3
2
  import { test, before, after } from 'node:test';
4
3
  import assert from 'node:assert';
5
4
 
@@ -8,10 +7,10 @@ let realTimeEnabled;
8
7
  before(async () => {
9
8
  // Start server for testing. Run local server!!
10
9
  realTimeEnabled = new Realtime({
11
- api_key: process.env.user_key,
12
- secret: process.env.secret
10
+ api_key: process.env.AUTH_JWT,
11
+ secret: process.env.AUTH_SECRET
13
12
  });
14
- await realTimeEnabled.init(true, {
13
+ await realTimeEnabled.init(false, {
15
14
  debug: true
16
15
  });
17
16
  await realTimeEnabled.connect();
@@ -55,8 +54,8 @@ test("No creds in constructor", async () => {
55
54
 
56
55
  test('init() function test', async () => {
57
56
  var realtime = new Realtime({
58
- api_key: process.env.user_key,
59
- secret: process.env.secret
57
+ api_key: process.env.AUTH_JWT,
58
+ secret: process.env.AUTH_SECRET
60
59
  });
61
60
  await realtime.init(true);
62
61
 
@@ -119,37 +118,10 @@ test("Namespace check test", async () => {
119
118
  assert.strictEqual(realTimeEnabled.topicHash.length > 0, true)
120
119
  });
121
120
 
122
- test("Retry method test", async () => {
123
- var retryMethod = realTimeEnabled.testRetryTillSuccess();
124
-
125
- assert.notStrictEqual(retryMethod, null, "Obj != null")
126
-
127
- function testMethod1(arg){
128
- return {
129
- success: true,
130
- output: arg
131
- }
132
- }
133
-
134
- var output = await retryMethod(testMethod1, 5, 1, "test_output")
135
-
136
- assert.strictEqual(output, "test_output");
137
-
138
- function testMethod2(){
139
- return {
140
- success: false,
141
- output: null
142
- }
143
- }
144
-
145
- output = await retryMethod(testMethod2, 5, 1);
146
- assert.strictEqual(output, null);
147
- });
148
-
149
121
  test("get publish retry count test based in init()", async () => {
150
122
  var realtime = new Realtime({
151
- api_key: process.env.user_key,
152
- secret: process.env.secret
123
+ api_key: process.env.AUTH_JWT,
124
+ secret: process.env.AUTH_SECRET
153
125
  });
154
126
 
155
127
  await realtime.init({
@@ -199,6 +171,60 @@ test("Testing publish(topic, data) method", async () => {
199
171
  });
200
172
 
201
173
  assert.strictEqual(response, true);
174
+
175
+ response = await realTimeEnabled.publish("hello.hey", {
176
+ message: "Hello World!"
177
+ });
178
+
179
+ assert.strictEqual(response, true);
180
+
181
+ response = await realTimeEnabled.publish("hello.*", {
182
+ message: "Hello World!"
183
+ });
184
+
185
+ assert.strictEqual(response, true);
186
+
187
+ response = await realTimeEnabled.publish("hello.>", {
188
+ message: "Hello World!"
189
+ });
190
+
191
+ assert.strictEqual(response, true);
192
+
193
+ response = await realTimeEnabled.publish("test-room", {
194
+ message: "Hello World!"
195
+ });
196
+
197
+ assert.strictEqual(response, true);
198
+
199
+ response = await realTimeEnabled.publish("test-room.*", {
200
+ message: "Hello World!"
201
+ });
202
+
203
+ assert.strictEqual(response, true);
204
+
205
+ response = await realTimeEnabled.publish("test-room.>", {
206
+ message: "Hello World!"
207
+ });
208
+
209
+ assert.strictEqual(response, true);
210
+
211
+ response = await realTimeEnabled.publish("test_room", {
212
+ message: "Hello World!"
213
+ });
214
+
215
+ assert.strictEqual(response, true);
216
+
217
+ response = await realTimeEnabled.publish("test_room.*", {
218
+ message: "Hello World!"
219
+ });
220
+
221
+ assert.strictEqual(response, true);
222
+
223
+ response = await realTimeEnabled.publish("test_room.>", {
224
+ message: "Hello World!"
225
+ });
226
+
227
+ assert.strictEqual(response, true);
202
228
  });
203
229
 
204
230
  test("Testing publish(topic, data) with invalid inputs", async () => {
@@ -271,8 +297,8 @@ test("Testing publish(topic, data) with invalid inputs", async () => {
271
297
 
272
298
  test("on() test", async () => {
273
299
  var realtime = new Realtime({
274
- api_key: process.env.user_key,
275
- secret: process.env.secret
300
+ api_key: process.env.AUTH_JWT,
301
+ secret: process.env.AUTH_SECRET
276
302
  });
277
303
 
278
304
  await assert.rejects(async () => {
@@ -349,8 +375,8 @@ test("on() test", async () => {
349
375
 
350
376
  test("off() test", async () => {
351
377
  var realtime = new Realtime({
352
- api_key: process.env.user_key,
353
- secret: process.env.secret
378
+ api_key: process.env.AUTH_JWT,
379
+ secret: process.env.AUTH_SECRET
354
380
  });
355
381
 
356
382
  await assert.rejects(async () => {
@@ -397,8 +423,8 @@ test("off() test", async () => {
397
423
 
398
424
  test("Get stream name test", () => {
399
425
  var realtime = new Realtime({
400
- api_key: process.env.user_key,
401
- secret: process.env.secret
426
+ api_key: process.env.AUTH_JWT,
427
+ secret: process.env.AUTH_SECRET
402
428
  });
403
429
 
404
430
  realtime.namespace = "spacex-dragon-program"
@@ -449,7 +475,76 @@ test("Test isTopicValidMethod()", () => {
449
475
  assert.strictEqual(valid, false);
450
476
  });
451
477
 
452
- var unreservedValidTopics = ["hello", "test-room", "heyyyyy", "room-connect"];
478
+ unreservedInvalidTopics = [
479
+ '$internal', // starts with $
480
+ 'hello world', // space
481
+ 'topic/', // slash
482
+ 'name?', // ?
483
+ 'foo#bar', // #
484
+ 'bar.baz!', // !
485
+ ' space', // leading space
486
+ 'tab\tchar', // tab
487
+ 'line\nbreak', // newline
488
+ 'comma ,', // space + comma
489
+ '', // empty string
490
+ 'bad|pipe', // |
491
+ 'semi;colon', // ;
492
+ 'colon:here', // :
493
+ "quote's", // '
494
+ '"doublequote"', // "
495
+ 'brackets[]', // []
496
+ 'brace{}', // {}
497
+ 'paren()', // ()
498
+ 'plus+sign', // +
499
+ 'eq=val', // =
500
+ 'gt>lt<', // < mixed with >
501
+ 'percent%', // %
502
+ 'caret^', // ^
503
+ 'ampersand&', // &
504
+ 'back\\slash', // backslash
505
+ '中文字符', // non‑ASCII
506
+ '👍emoji', // emoji
507
+ 'foo\rbar', // carriage return
508
+ 'end ' // trailing space
509
+ ];
510
+
511
+ unreservedInvalidTopics.forEach(topic => {
512
+ var valid = realTimeEnabled.isTopicValid(topic);
513
+ assert.strictEqual(valid, false);
514
+ });
515
+
516
+ var unreservedValidTopics = [
517
+ 'Orders',
518
+ 'customer_123',
519
+ 'foo-bar',
520
+ 'a,b,c',
521
+ '*',
522
+ 'foo>*',
523
+ 'hello$world',
524
+ 'topic.123',
525
+ 'ABC_def-ghi',
526
+ 'data_stream_2025',
527
+ 'NODE*',
528
+ 'pubsub>events',
529
+ 'log,metric,error',
530
+ 'X123_Y456',
531
+ 'multi.step.topic',
532
+ 'batch-process',
533
+ 'sensor1_data',
534
+ 'finance$Q2',
535
+ 'alpha,beta,gamma',
536
+ 'Z9_Y8-X7',
537
+ 'config>*',
538
+ 'route-map',
539
+ 'STATS_2025-07',
540
+ 'msg_queue*',
541
+ 'update>patch',
542
+ 'pipeline_v2',
543
+ 'FOO$BAR$BAZ',
544
+ 'user.profile',
545
+ 'id_001-xyz',
546
+ 'event_queue>'
547
+ ];
453
548
 
454
549
  unreservedValidTopics.forEach(topic => {
455
550
  var valid = realTimeEnabled.isTopicValid(topic);
@@ -1,160 +0,0 @@
1
- import axios from 'axios';
2
-
3
- /**
4
- * Class responsible for getting messages stored in the DB
5
- */
6
- export class History{
7
-
8
- #api_key = null;
9
- #staging = null;
10
- #baseUrl = null;
11
- #debug = null;
12
-
13
- constructor(api_key){
14
- this.#api_key = api_key;
15
- }
16
-
17
- init(staging, debug){
18
- this.#staging = staging;
19
- this.#debug = debug;
20
-
21
- this.#setBaseUrl();
22
- }
23
-
24
- /**
25
- * Get message from DB since a $timestamp
26
- * @param {number} timestamp - unix timestamp
27
- * @param {number} page - page number of pagination
28
- * @param {number} limit - limit per page
29
- * @returns - Message array
30
- */
31
- async getMessagesSince(topic, timestamp, page, limit){
32
- if(topic == null || topic == undefined){
33
- return new Error("$topic variable missing in getMessagesSince()");
34
- }else{
35
- if(typeof topic !== "string"){
36
- return new Error("$topic is not a string");
37
- }
38
- }
39
-
40
- if(timestamp == null || timestamp == undefined){
41
- return new Error("$timestamp variable missing in getMessagesSince()");
42
- }else{
43
- if(!Number.isInteger(timestamp) && !Number.isNaN(timestamp)){
44
- return new Error("$timestamp is either NaN or not an invalid integer");
45
- }
46
- }
47
-
48
- if(page == null || page == undefined){
49
- return new Error("$page variable missing in getMessagesSince()");
50
- }else{
51
- if(!Number.isInteger(page) && !Number.isNaN(page)){
52
- return new Error("$page is either NaN or not an invalid integer");
53
- }
54
- }
55
-
56
- if(limit == null || limit == undefined){
57
- return new Error("$limit variable missing in getMessagesSince()");
58
- }else{
59
- if(!Number.isInteger(limit) && !Number.isNaN(limit)){
60
- console.log("$limit is either NaN or not an invalid integer")
61
- return new Error("$limit is either NaN or not an invalid integer");
62
- }
63
- }
64
-
65
- try{
66
- var startTime = Date.now();
67
- var urlPart = `/history/since?topic=${topic}&timestamp=${timestamp}&page=${page}&limit=${limit}`
68
-
69
- var response = await axios.get(this.#baseUrl + urlPart,{
70
- headers: {
71
- "Authorization": `Bearer ${this.#api_key}`
72
- }
73
- });
74
-
75
- var data = response.data
76
- this.#log(data);
77
-
78
- this.#logResponseTime(startTime, urlPart);
79
-
80
- if (data?.status === "SUCCESS"){
81
- return data.data;
82
- }else{
83
- return null;
84
- }
85
- }catch(err){
86
- throw Error(err.message);
87
- }
88
- }
89
-
90
- /**
91
- * Get message from DB by ID
92
- * @param {string} id - ID of the message
93
- * @returns - Message object
94
- */
95
- async getMessageById(id){
96
- var startTime = Date.now();
97
- var urlPart = `/history/message-by-id?id=${id}`;
98
-
99
- if(id !== null && id !== undefined){
100
- try{
101
- var response = await axios.get(this.#baseUrl + urlPart,{
102
- headers: {
103
- "Authorization": `Bearer ${this.#api_key}`
104
- }
105
- });
106
-
107
- var data = response.data
108
- this.#log(data);
109
-
110
- this.#logResponseTime(startTime, urlPart);
111
-
112
- if (data?.status === "SUCCESS"){
113
- return data.data;
114
- }else{
115
- return null;
116
- }
117
- }catch(err){
118
- throw new Error(err.message);
119
- }
120
- }else{
121
- return null;
122
- }
123
- }
124
-
125
- // Utility Functions
126
- /**
127
- * Constructs base url based on staging flag
128
- */
129
- #setBaseUrl(){
130
- if (this.#staging !== undefined || this.#staging !== null){
131
- this.#baseUrl = this.#staging ? "http://127.0.0.1:3000" : "http://128.199.176.185:3000";
132
- }else{
133
- this.#baseUrl = "http://128.199.176.185:3000";
134
- }
135
- }
136
-
137
- #log(msg){
138
- if(this.#debug !== null && this.#debug !== undefined && (typeof this.#debug == "boolean")){
139
- if(this.#debug){
140
- console.log(msg);
141
- }
142
- }
143
- }
144
-
145
- async #logResponseTime(startTime, url){
146
- var responseTime = Date.now() - startTime;
147
-
148
- var data = {
149
- "url": url,
150
- "response_time": responseTime
151
- }
152
-
153
- await axios.post(this.#baseUrl + "/metrics/log", data, {
154
- headers: {
155
- "Authorization": `Bearer ${this.#api_key}`
156
- }
157
- });
158
- }
159
-
160
- }
package/realtime/http.js DELETED
File without changes