relayx-js 1.0.13 → 1.0.15

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.
@@ -0,0 +1,51 @@
1
+ # Sample workflow for building and deploying a Jekyll site to GitHub Pages
2
+ name: Deploy Jekyll with GitHub Pages dependencies preinstalled
3
+
4
+ on:
5
+ # Runs on pushes targeting the default branch
6
+ push:
7
+ branches: ["main"]
8
+
9
+ # Allows you to run this workflow manually from the Actions tab
10
+ workflow_dispatch:
11
+
12
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+
18
+ # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19
+ # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20
+ concurrency:
21
+ group: "pages"
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ # Build job
26
+ build:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v4
31
+ - name: Setup Pages
32
+ uses: actions/configure-pages@v5
33
+ - name: Build with Jekyll
34
+ uses: actions/jekyll-build-pages@v1
35
+ with:
36
+ source: ./
37
+ destination: ./_site
38
+ - name: Upload artifact
39
+ uses: actions/upload-pages-artifact@v3
40
+
41
+ # Deployment job
42
+ deploy:
43
+ environment:
44
+ name: github-pages
45
+ url: ${{ steps.deployment.outputs.page_url }}
46
+ runs-on: ubuntu-latest
47
+ needs: build
48
+ steps:
49
+ - name: Deploy to GitHub Pages
50
+ id: deployment
51
+ uses: actions/deploy-pages@v4
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ V1.0.15
2
+ - Wildcard topic pub / sub
3
+ - Message replay on reconnect
4
+
1
5
  V1.0.8
2
6
  - History API fetch() to consume()
3
7
 
package/README.md CHANGED
@@ -22,9 +22,7 @@ Install the relay library by running the command below in your terminal<br>
22
22
  api_key: process.env.api_key,
23
23
  secret: process.env.secret,
24
24
  });
25
- realtime.init({
26
- browser_mode: true // Enable when using in webapps
27
- });
25
+ realtime.init();
28
26
 
29
27
  // Initialization of topic listeners go here... (look at examples/example_chat.js for full implementation)
30
28
 
@@ -160,7 +158,6 @@ This event is fired when the library resends the messages upon reconnection to t
160
158
  1. init()<br>
161
159
  Initializes library with configuration options.
162
160
  * debug (boolean): enables library level logging
163
- * browser_mode (boolean): Allows websocket connections to be made from a browser. Default is TCP
164
161
  2. connect()<br>
165
162
  Connects the library to the Relay Network. This is an async function.
166
163
  3. close()<br>
@@ -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.13",
3
+ "version": "1.0.15",
4
4
  "main": "realtime/realtime.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -17,12 +17,11 @@
17
17
  "license": "Apache 2.0",
18
18
  "description": "A powerful library for integrating real-time communication into your software stack, powered by the Relay Network.",
19
19
  "dependencies": {
20
- "@msgpack/msgpack": "^3.1.1",
21
- "@nats-io/jetstream": "^3.0.0-35",
22
- "axios": "^1.8.4",
23
- "jest": "^29.7.0",
24
- "nats": "^2.28.2",
25
- "uuid": "^11.1.0"
20
+ "@msgpack/msgpack": "3.1.1",
21
+ "@nats-io/jetstream": "3.0.0-35",
22
+ "jest": "29.7.0",
23
+ "nats": "2.28.2",
24
+ "uuid": "11.1.0"
26
25
  },
27
26
  "devDependencies": {
28
27
  "@babel/core": "^7.26.0",
@@ -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
+ }
@@ -1,8 +1,8 @@
1
- import axios from 'axios';
2
1
  import { connect, JSONCodec, Events, DebugEvents, AckPolicy, ReplayPolicy, credsAuthenticator } from "nats";
3
2
  import { DeliverPolicy, jetstream } from "@nats-io/jetstream";
4
3
  import { encode, decode } from "@msgpack/msgpack";
5
4
  import { v4 as uuidv4 } from 'uuid';
5
+ import { initDNSSpoof } from "./dns_change.js";
6
6
 
7
7
  export class Realtime {
8
8
 
@@ -110,30 +110,28 @@ export class Realtime {
110
110
  this.staging = staging;
111
111
  this.opts = opts;
112
112
 
113
- var browserMode = this.opts["browser_mode"];
114
- var protocol = "tls"
115
-
116
- if(browserMode != null && browserMode != undefined && (typeof browserMode == "boolean")){
117
- protocol = browserMode ? "wss" : "tls"
118
- }
119
-
120
- if (staging !== undefined || staging !== null){
121
- this.#baseUrl = staging ? [
122
- "nats://0.0.0.0:4221",
123
- "nats://0.0.0.0:4222",
124
- "nats://0.0.0.0:4223"
125
- ] :
126
- [
127
- `${protocol}://api.relay-x.io:4221`,
128
- `${protocol}://api.relay-x.io:4222`,
129
- `${protocol}://api.relay-x.io:4223`
130
- ];
113
+ if(process.env.PROXY){
114
+ this.#baseUrl = ["tls://api2.relay-x.io:8666"];
115
+ initDNSSpoof();
131
116
  }else{
132
- this.#baseUrl = [
133
- `${protocol}://api.relay-x.io:4221`,
134
- `${protocol}://api.relay-x.io:4222`,
135
- `${protocol}://api.relay-x.io:4223`
136
- ];
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://api2.relay-x.io:4221`,
125
+ `tls://api2.relay-x.io:4222`,
126
+ `tls://api2.relay-x.io:4223`
127
+ ];
128
+ }else{
129
+ this.#baseUrl = [
130
+ `tls://api.relay-x.io:4221`,
131
+ `tls://api.relay-x.io:4222`,
132
+ `tls://api.relay-x.io:4223`
133
+ ];
134
+ }
137
135
  }
138
136
 
139
137
  this.#log(this.#baseUrl);
@@ -188,6 +186,7 @@ export class Realtime {
188
186
  reconnectTimeWait: 1000,
189
187
  authenticator: credsAuth,
190
188
  token: this.api_key,
189
+ resolve: process.env.PROXY ? false : true
191
190
  });
192
191
 
193
192
  this.#jetstream = await jetstream(this.#natsClient);
@@ -216,6 +215,12 @@ export class Realtime {
216
215
  this.#log("the connection closed!");
217
216
 
218
217
  this.#offlineMessageBuffer.length = 0;
218
+
219
+ if (DISCONNECTED in this.#event_func){
220
+ if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
221
+ this.#event_func[DISCONNECTED]()
222
+ }
223
+ }
219
224
  });
220
225
 
221
226
  (async () => {
@@ -576,7 +581,7 @@ export class Realtime {
576
581
 
577
582
  var opts = {
578
583
  name: `${topic}_${uuidv4()}`,
579
- filter_subjects: [this.#getStreamTopic(topic), this.#getStreamTopic(topic) + "_presence"],
584
+ filter_subjects: [this.#getStreamTopic(topic)],
580
585
  replay_policy: ReplayPolicy.Instant,
581
586
  opt_start_time: new Date(),
582
587
  ack_policy: AckPolicy.Explicit,
@@ -666,7 +671,7 @@ export class Realtime {
666
671
  this.#latencyPush = setTimeout(async () => {
667
672
  this.#log("setTimeout called");
668
673
 
669
- if(this.#latency.length > 0){
674
+ if(this.#latency.length > 0 && this.connected){
670
675
  this.#log("Push from setTimeout")
671
676
  await this.#pushLatencyData({
672
677
  timezone: timeZone,
@@ -738,7 +743,9 @@ export class Realtime {
738
743
  var arrayCheck = ![CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED,
739
744
  this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic);
740
745
 
741
- 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);
742
749
 
743
750
  return arrayCheck && spaceStarCheck;
744
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,14 @@ test("Test isTopicValidMethod()", () => {
449
475
  assert.strictEqual(valid, false);
450
476
  });
451
477
 
452
- var unreservedValidTopics = ["hello", "test-room", "heyyyyy", "room-connect"];
478
+ unreservedInvalidTopics = ["$hey.hey", "orders created", ""];
479
+
480
+ unreservedInvalidTopics.forEach(topic => {
481
+ var valid = realTimeEnabled.isTopicValid(topic);
482
+ assert.strictEqual(valid, false);
483
+ });
484
+
485
+ var unreservedValidTopics = ["hello", "test-room", "heyyyyy", "room-connect", "hey$"];
453
486
 
454
487
  unreservedValidTopics.forEach(topic => {
455
488
  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