relayx-js 1.0.16 → 1.0.18
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 +4 -0
- package/examples/example_chat.js +14 -2
- package/package.json +1 -1
- package/realtime/realtime.js +195 -77
- package/tests/test.js +137 -61
package/CHANGELOG.md
CHANGED
package/examples/example_chat.js
CHANGED
|
@@ -32,10 +32,22 @@ async function run(){
|
|
|
32
32
|
console.log("power-telemetry", data);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
await realtime.on("hello.*", (data) => {
|
|
36
|
-
|
|
35
|
+
// await realtime.on("hello.*", (data) => {
|
|
36
|
+
// console.log("hello.*", data);
|
|
37
|
+
// });
|
|
38
|
+
|
|
39
|
+
await realtime.on("hello.>", async (data) => {
|
|
40
|
+
console.log("hello.>", data);
|
|
37
41
|
});
|
|
38
42
|
|
|
43
|
+
// await realtime.on("hello.hey.*", (data) => {
|
|
44
|
+
// console.log("hell.hey.*", data);
|
|
45
|
+
// });
|
|
46
|
+
|
|
47
|
+
// await realtime.on("hello.hey.>", (data) => {
|
|
48
|
+
// console.log("hello.hey.>", data);
|
|
49
|
+
// });
|
|
50
|
+
|
|
39
51
|
realtime.on(MESSAGE_RESEND, (data) => {
|
|
40
52
|
console.log(`[MSG RESEND] => ${data}`)
|
|
41
53
|
});
|
package/package.json
CHANGED
package/realtime/realtime.js
CHANGED
|
@@ -16,13 +16,13 @@ export class Realtime {
|
|
|
16
16
|
#event_func = {};
|
|
17
17
|
#topicMap = [];
|
|
18
18
|
|
|
19
|
-
#config = "CiAgICAgICAgLS0tLS1CRUdJTiBOQVRTIFVTRVIgSldULS0tLS0KICAgICAgICBKV1RfS0VZCiAgICAgICAgLS0tLS0tRU5EIE5BVFMgVVNFUiBKV1QtLS0tLS0KCiAgICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKiBJTVBPUlRBTlQgKioqKioqKioqKioqKioqKioqKioqKioqKgogICAgICAgIE5LRVkgU2VlZCBwcmludGVkIGJlbG93IGNhbiBiZSB1c2VkIHRvIHNpZ24gYW5kIHByb3ZlIGlkZW50aXR5LgogICAgICAgIE5LRVlzIGFyZSBzZW5zaXRpdmUgYW5kIHNob3VsZCBiZSB0cmVhdGVkIGFzIHNlY3JldHMuCgogICAgICAgIC0tLS0tQkVHSU4gVVNFUiBOS0VZIFNFRUQtLS0tLQogICAgICAgIFNFQ1JFVF9LRVkKICAgICAgICAtLS0tLS1FTkQgVVNFUiBOS0VZIFNFRUQtLS0tLS0KCiAgICAgICAgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgogICAgICAgIA=="
|
|
20
|
-
|
|
21
19
|
// Status Codes
|
|
22
20
|
#RECONNECTING = "RECONNECTING";
|
|
23
21
|
#RECONNECTED = "RECONNECTED";
|
|
24
22
|
#RECONN_FAIL = "RECONN_FAIL";
|
|
25
23
|
|
|
24
|
+
#reservedSystemTopics = [CONNECTED, DISCONNECTED, RECONNECT, this.#RECONNECTED, this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT];
|
|
25
|
+
|
|
26
26
|
setRemoteUserAttempts = 0;
|
|
27
27
|
setRemoteUserRetries = 5;
|
|
28
28
|
|
|
@@ -42,6 +42,8 @@ export class Realtime {
|
|
|
42
42
|
|
|
43
43
|
#maxPublishRetries = 5;
|
|
44
44
|
|
|
45
|
+
#connectCalled = false;
|
|
46
|
+
|
|
45
47
|
constructor(config){
|
|
46
48
|
if(typeof config != "object"){
|
|
47
49
|
throw new Error("Realtime($config). $config not object => {}")
|
|
@@ -97,10 +99,13 @@ export class Realtime {
|
|
|
97
99
|
if(arguments[0] instanceof Object){
|
|
98
100
|
opts = arguments[0];
|
|
99
101
|
staging = false;
|
|
100
|
-
}else{
|
|
102
|
+
}else if(typeof arguments[0] == "boolean"){
|
|
101
103
|
opts = {};
|
|
102
104
|
staging = arguments[0];
|
|
103
105
|
this.#log(staging)
|
|
106
|
+
}else{
|
|
107
|
+
opts = {};
|
|
108
|
+
staging = false
|
|
104
109
|
}
|
|
105
110
|
}else{
|
|
106
111
|
staging = false;
|
|
@@ -171,6 +176,10 @@ export class Realtime {
|
|
|
171
176
|
* Connects to the relay network
|
|
172
177
|
*/
|
|
173
178
|
async connect(){
|
|
179
|
+
if(this.#connectCalled){
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
174
183
|
this.SEVER_URL = this.#baseUrl;
|
|
175
184
|
|
|
176
185
|
var credsFile = this.#getUserCreds(this.api_key, this.secret)
|
|
@@ -194,6 +203,7 @@ export class Realtime {
|
|
|
194
203
|
await this.#getNameSpace()
|
|
195
204
|
|
|
196
205
|
this.connected = true;
|
|
206
|
+
this.#connectCalled = true;
|
|
197
207
|
}catch(err){
|
|
198
208
|
this.#log("ERR")
|
|
199
209
|
this.#log(err);
|
|
@@ -204,17 +214,13 @@ export class Realtime {
|
|
|
204
214
|
if (this.connected == true){
|
|
205
215
|
this.#log("Connected to server!");
|
|
206
216
|
|
|
207
|
-
// Callback on client side
|
|
208
|
-
if (CONNECTED in this.#event_func){
|
|
209
|
-
if (this.#event_func[CONNECTED] !== null || this.#event_func[CONNECTED] !== undefined){
|
|
210
|
-
this.#event_func[CONNECTED]()
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
217
|
this.#natsClient.closed().then(() => {
|
|
215
218
|
this.#log("the connection closed!");
|
|
216
219
|
|
|
217
220
|
this.#offlineMessageBuffer.length = 0;
|
|
221
|
+
this.connected = false;
|
|
222
|
+
this.reconnecting = false;
|
|
223
|
+
this.#connectCalled = false;
|
|
218
224
|
|
|
219
225
|
if (DISCONNECTED in this.#event_func){
|
|
220
226
|
if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
|
|
@@ -232,13 +238,6 @@ export class Realtime {
|
|
|
232
238
|
this.#log(`client disconnected - ${s.data}`);
|
|
233
239
|
|
|
234
240
|
this.connected = false;
|
|
235
|
-
this.#consumerMap = {};
|
|
236
|
-
|
|
237
|
-
if (DISCONNECTED in this.#event_func){
|
|
238
|
-
if (this.#event_func[DISCONNECTED] !== null || this.#event_func[DISCONNECTED] !== undefined){
|
|
239
|
-
this.#event_func[DISCONNECTED]()
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
241
|
break;
|
|
243
242
|
case Events.LDM:
|
|
244
243
|
this.#log("client has been requested to reconnect");
|
|
@@ -268,6 +267,7 @@ export class Realtime {
|
|
|
268
267
|
this.#log("client is attempting to reconnect");
|
|
269
268
|
|
|
270
269
|
this.reconnecting = true;
|
|
270
|
+
this.connected = false;
|
|
271
271
|
|
|
272
272
|
if(RECONNECT in this.#event_func && this.reconnecting){
|
|
273
273
|
this.#event_func[RECONNECT](this.#RECONNECTING);
|
|
@@ -285,19 +285,29 @@ export class Realtime {
|
|
|
285
285
|
// Subscribe to topics
|
|
286
286
|
this.#subscribeToTopics();
|
|
287
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
|
+
}
|
|
288
295
|
}
|
|
289
296
|
}
|
|
290
297
|
|
|
291
298
|
/**
|
|
292
299
|
* Closes connection
|
|
293
300
|
*/
|
|
294
|
-
close(){
|
|
301
|
+
async close(){
|
|
295
302
|
if(this.#natsClient !== null){
|
|
296
303
|
this.reconnected = false;
|
|
297
304
|
this.disconnected = true;
|
|
305
|
+
this.#connectCalled = false;
|
|
298
306
|
|
|
299
307
|
this.#offlineMessageBuffer.length = 0;
|
|
300
308
|
|
|
309
|
+
await this.#deleteAllConsumers();
|
|
310
|
+
|
|
301
311
|
this.#natsClient.close();
|
|
302
312
|
}else{
|
|
303
313
|
this.#log("Null / undefined socket, cannot close connection");
|
|
@@ -314,6 +324,17 @@ export class Realtime {
|
|
|
314
324
|
});
|
|
315
325
|
}
|
|
316
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);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
317
338
|
/**
|
|
318
339
|
* Deletes reference to user defined event callback.
|
|
319
340
|
* This will stop listening to a topic
|
|
@@ -359,31 +380,23 @@ export class Realtime {
|
|
|
359
380
|
throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
|
|
360
381
|
}
|
|
361
382
|
|
|
362
|
-
if(
|
|
363
|
-
this.#event_func[topic] = func;
|
|
364
|
-
}else{
|
|
383
|
+
if(topic in this.#event_func || this.#topicMap.includes(topic)){
|
|
365
384
|
return false
|
|
366
385
|
}
|
|
367
386
|
|
|
368
|
-
|
|
369
|
-
this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic)){
|
|
370
|
-
if(!this.isTopicValid(topic)){
|
|
371
|
-
// We have an invalid topic, lets remove it
|
|
372
|
-
if(topic in this.#event_func){
|
|
373
|
-
delete this.#event_func[topic];
|
|
374
|
-
}
|
|
387
|
+
this.#event_func[topic] = func;
|
|
375
388
|
|
|
376
|
-
|
|
377
|
-
|
|
389
|
+
if (!this.#reservedSystemTopics.includes(topic)){
|
|
390
|
+
if(!this.isTopicValid(topic)){
|
|
391
|
+
throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
|
|
392
|
+
}
|
|
378
393
|
|
|
379
|
-
|
|
380
|
-
this.#topicMap.push(topic);
|
|
381
|
-
}
|
|
394
|
+
this.#topicMap.push(topic);
|
|
382
395
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
396
|
+
if(this.connected){
|
|
397
|
+
// Connected we need to create a topic in a stream
|
|
398
|
+
await this.#startConsumer(topic);
|
|
399
|
+
}
|
|
387
400
|
}
|
|
388
401
|
|
|
389
402
|
return true;
|
|
@@ -413,7 +426,7 @@ export class Realtime {
|
|
|
413
426
|
throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
|
|
414
427
|
}
|
|
415
428
|
|
|
416
|
-
if(!this
|
|
429
|
+
if(!this.isMessageValid(data)){
|
|
417
430
|
throw new Error("$message must be JSON, string or number")
|
|
418
431
|
}
|
|
419
432
|
|
|
@@ -428,15 +441,9 @@ export class Realtime {
|
|
|
428
441
|
"start": Date.now()
|
|
429
442
|
}
|
|
430
443
|
|
|
431
|
-
this.#log("Encoding message via msg pack...")
|
|
432
|
-
var encodedMessage = encode(message);
|
|
433
|
-
|
|
434
444
|
if(this.connected){
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}else{
|
|
438
|
-
this.#log(`${topic} exists locally, moving on...`)
|
|
439
|
-
}
|
|
445
|
+
this.#log("Encoding message via msg pack...")
|
|
446
|
+
var encodedMessage = encode(message);
|
|
440
447
|
|
|
441
448
|
this.#log(`Publishing to topic => ${this.#getStreamTopic(topic)}`)
|
|
442
449
|
|
|
@@ -463,7 +470,6 @@ export class Realtime {
|
|
|
463
470
|
* @param {string} topic
|
|
464
471
|
*/
|
|
465
472
|
async history(topic, start, end){
|
|
466
|
-
this.#log(start)
|
|
467
473
|
if(topic == null || topic == undefined){
|
|
468
474
|
throw new Error("$topic is null or undefined");
|
|
469
475
|
}
|
|
@@ -476,6 +482,10 @@ export class Realtime {
|
|
|
476
482
|
throw new Error(`Expected $topic type -> string. Instead receieved -> ${typeof topic}`);
|
|
477
483
|
}
|
|
478
484
|
|
|
485
|
+
if(!this.isTopicValid(topic)){
|
|
486
|
+
throw new Error("Invalid topic, use isTopicValid($topic) to validate topic")
|
|
487
|
+
}
|
|
488
|
+
|
|
479
489
|
if(start == undefined || start == null){
|
|
480
490
|
throw new Error(`$start must be provided. $start is => ${start}`)
|
|
481
491
|
}
|
|
@@ -496,11 +506,16 @@ export class Realtime {
|
|
|
496
506
|
end = end.toISOString();
|
|
497
507
|
}
|
|
498
508
|
|
|
509
|
+
if(!this.connected){
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
|
|
499
513
|
var opts = {
|
|
500
514
|
name: `${topic}_${uuidv4()}_history`,
|
|
501
515
|
filter_subjects: [this.#getStreamTopic(topic)],
|
|
502
516
|
replay_policy: ReplayPolicy.Instant,
|
|
503
517
|
opt_start_time: start,
|
|
518
|
+
delivery_policy: DeliverPolicy.StartTime,
|
|
504
519
|
ack_policy: AckPolicy.Explicit,
|
|
505
520
|
}
|
|
506
521
|
|
|
@@ -529,7 +544,12 @@ export class Realtime {
|
|
|
529
544
|
var data = decode(msg.data);
|
|
530
545
|
this.#log(data);
|
|
531
546
|
|
|
532
|
-
history.push(
|
|
547
|
+
history.push({
|
|
548
|
+
"id": data.id,
|
|
549
|
+
"topic": data.room,
|
|
550
|
+
"message": data.message,
|
|
551
|
+
"timestamp": msg.timestamp
|
|
552
|
+
});
|
|
533
553
|
}
|
|
534
554
|
|
|
535
555
|
var del = await consumer.delete();
|
|
@@ -577,10 +597,10 @@ export class Realtime {
|
|
|
577
597
|
* @param {string} topic
|
|
578
598
|
*/
|
|
579
599
|
async #startConsumer(topic){
|
|
580
|
-
|
|
600
|
+
const consumerName = `${topic}_${uuidv4()}_consumer`;
|
|
581
601
|
|
|
582
602
|
var opts = {
|
|
583
|
-
name:
|
|
603
|
+
name: consumerName,
|
|
584
604
|
filter_subjects: [this.#getStreamTopic(topic)],
|
|
585
605
|
replay_policy: ReplayPolicy.Instant,
|
|
586
606
|
opt_start_time: new Date(),
|
|
@@ -597,19 +617,25 @@ export class Realtime {
|
|
|
597
617
|
callback: async (msg) => {
|
|
598
618
|
try{
|
|
599
619
|
const now = Date.now();
|
|
620
|
+
msg.working()
|
|
600
621
|
this.#log("Decoding msgpack message...")
|
|
601
622
|
var data = decode(msg.data);
|
|
602
623
|
|
|
603
|
-
var
|
|
624
|
+
var msgTopic = this.#stripStreamHash(msg.subject);
|
|
604
625
|
|
|
605
626
|
this.#log(data);
|
|
606
627
|
|
|
607
628
|
// Push topic message to main thread
|
|
608
|
-
if (
|
|
609
|
-
this.#
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
629
|
+
if (data.client_id != this.#getClientId()){
|
|
630
|
+
var topicMatch = this.#topicPatternMatcher(topic, msgTopic)
|
|
631
|
+
|
|
632
|
+
if(topicMatch){
|
|
633
|
+
this.#event_func[topic]({
|
|
634
|
+
"id": data.id,
|
|
635
|
+
"topic": msgTopic,
|
|
636
|
+
"data": data.message
|
|
637
|
+
});
|
|
638
|
+
}
|
|
613
639
|
}
|
|
614
640
|
|
|
615
641
|
msg.ack();
|
|
@@ -617,18 +643,15 @@ export class Realtime {
|
|
|
617
643
|
await this.#logLatency(now, data);
|
|
618
644
|
}catch(err){
|
|
619
645
|
this.#log("Consumer err " + err);
|
|
620
|
-
msg.
|
|
646
|
+
msg.nak(5000);
|
|
621
647
|
}
|
|
622
648
|
}
|
|
623
649
|
});
|
|
624
650
|
this.#log("Consumer is consuming");
|
|
625
651
|
}
|
|
626
652
|
|
|
627
|
-
/**
|
|
628
|
-
* Deletes consumer
|
|
629
|
-
* @param {string} topic
|
|
630
|
-
*/
|
|
631
653
|
async #deleteConsumer(topic){
|
|
654
|
+
this.#log(topic)
|
|
632
655
|
const consumer = this.#consumerMap[topic]
|
|
633
656
|
|
|
634
657
|
var del = false;
|
|
@@ -650,11 +673,6 @@ export class Realtime {
|
|
|
650
673
|
return;
|
|
651
674
|
}
|
|
652
675
|
|
|
653
|
-
if(this.#latency.length >= 100){
|
|
654
|
-
this.#log("Latency array is full, skipping log");
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
676
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
659
677
|
|
|
660
678
|
this.#log(`Timezone: ${timeZone}`);
|
|
@@ -671,7 +689,7 @@ export class Realtime {
|
|
|
671
689
|
this.#latencyPush = setTimeout(async () => {
|
|
672
690
|
this.#log("setTimeout called");
|
|
673
691
|
|
|
674
|
-
if(this.#latency.length > 0 && this.connected){
|
|
692
|
+
if(this.#latency.length > 0 && this.connected && !this.#isSendingLatency){
|
|
675
693
|
this.#log("Push from setTimeout")
|
|
676
694
|
await this.#pushLatencyData({
|
|
677
695
|
timezone: timeZone,
|
|
@@ -684,7 +702,7 @@ export class Realtime {
|
|
|
684
702
|
}, 30000);
|
|
685
703
|
}
|
|
686
704
|
|
|
687
|
-
if(this.#latency.length
|
|
705
|
+
if(this.#latency.length >= 100 && !this.#isSendingLatency){
|
|
688
706
|
this.#log("Push from Length Check: " + this.#latency.length);
|
|
689
707
|
await this.#pushLatencyData({
|
|
690
708
|
timezone: timeZone,
|
|
@@ -740,10 +758,9 @@ export class Realtime {
|
|
|
740
758
|
*/
|
|
741
759
|
isTopicValid(topic){
|
|
742
760
|
if(topic !== null && topic !== undefined && (typeof topic) == "string"){
|
|
743
|
-
var arrayCheck = !
|
|
744
|
-
this.#RECONNECTING, this.#RECONN_FAIL, MESSAGE_RESEND, SERVER_DISCONNECT].includes(topic);
|
|
761
|
+
var arrayCheck = !this.#reservedSystemTopics.includes(topic);
|
|
745
762
|
|
|
746
|
-
const TOPIC_REGEX = /^(
|
|
763
|
+
const TOPIC_REGEX = /^(?!.*\$)(?:[A-Za-z0-9_*~-]+(?:\.[A-Za-z0-9_*~-]+)*(?:\.>)?|>)$/u;
|
|
747
764
|
|
|
748
765
|
var spaceStarCheck = !topic.includes(" ") && TOPIC_REGEX.test(topic);
|
|
749
766
|
|
|
@@ -753,7 +770,7 @@ export class Realtime {
|
|
|
753
770
|
}
|
|
754
771
|
}
|
|
755
772
|
|
|
756
|
-
|
|
773
|
+
isMessageValid(message){
|
|
757
774
|
if(message == null || message == undefined){
|
|
758
775
|
throw new Error("$message cannot be null / undefined")
|
|
759
776
|
}
|
|
@@ -800,6 +817,91 @@ export class Realtime {
|
|
|
800
817
|
}
|
|
801
818
|
}
|
|
802
819
|
|
|
820
|
+
#stripStreamHash(topic){
|
|
821
|
+
return topic.replace(`${this.topicHash}.`, "")
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
#getCallbackTopics(topic){
|
|
825
|
+
var validTopics = [];
|
|
826
|
+
|
|
827
|
+
var topicPatterns = Object.keys(this.#event_func);
|
|
828
|
+
|
|
829
|
+
for(let i = 0; i < topicPatterns.length; i++){
|
|
830
|
+
var pattern = topicPatterns[i];
|
|
831
|
+
|
|
832
|
+
if([CONNECTED, RECONNECT, MESSAGE_RESEND, DISCONNECTED, SERVER_DISCONNECT].includes(pattern)){
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
var match = this.#topicPatternMatcher(pattern, topic);
|
|
837
|
+
|
|
838
|
+
if(match){
|
|
839
|
+
validTopics.push(pattern)
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return validTopics;
|
|
844
|
+
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
#topicPatternMatcher(patternA, patternB) {
|
|
848
|
+
const a = patternA.split(".");
|
|
849
|
+
const b = patternB.split(".");
|
|
850
|
+
|
|
851
|
+
let i = 0, j = 0; // cursors in a & b
|
|
852
|
+
let starAi = -1, starAj = -1; // last '>' position in A and the token count it has consumed
|
|
853
|
+
let starBi = -1, starBj = -1; // same for pattern B
|
|
854
|
+
|
|
855
|
+
while (i < a.length || j < b.length) {
|
|
856
|
+
const tokA = a[i];
|
|
857
|
+
const tokB = b[j];
|
|
858
|
+
|
|
859
|
+
/*──────────── literal match or single‑token wildcard on either side ────────────*/
|
|
860
|
+
const singleWildcard =
|
|
861
|
+
(tokA === "*" && j < b.length) ||
|
|
862
|
+
(tokB === "*" && i < a.length);
|
|
863
|
+
|
|
864
|
+
if (
|
|
865
|
+
(tokA !== undefined && tokA === tokB) ||
|
|
866
|
+
singleWildcard
|
|
867
|
+
) {
|
|
868
|
+
i++; j++;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/*────────────────── multi‑token wildcard ">" — must be **final** ───────────────*/
|
|
873
|
+
if (tokA === ">") {
|
|
874
|
+
if (i !== a.length - 1) return false; // '>' not in last position → invalid
|
|
875
|
+
if (j >= b.length) return false; // must consume at least one token
|
|
876
|
+
starAi = i++; // remember where '>' is
|
|
877
|
+
starAj = ++j; // gobble one token from B
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (tokB === ">") {
|
|
881
|
+
if (j !== b.length - 1) return false; // same rule for patternB
|
|
882
|
+
if (i >= a.length) return false;
|
|
883
|
+
starBi = j++;
|
|
884
|
+
starBj = ++i;
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/*───────────────────────────── back‑track using last '>' ───────────────────────*/
|
|
889
|
+
if (starAi !== -1) { // let patternA's '>' absorb one more token of B
|
|
890
|
+
j = ++starAj;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
if (starBi !== -1) { // let patternB's '>' absorb one more token of A
|
|
894
|
+
i = ++starBj;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/*────────────────────────────────── dead‑end ───────────────────────────────────*/
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
|
|
803
905
|
sleep(ms) {
|
|
804
906
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
805
907
|
}
|
|
@@ -865,13 +967,21 @@ export class Realtime {
|
|
|
865
967
|
return methodDataOutput;
|
|
866
968
|
}
|
|
867
969
|
|
|
868
|
-
|
|
869
|
-
|
|
970
|
+
#getUserCreds(jwt, secret){
|
|
971
|
+
return `
|
|
972
|
+
-----BEGIN NATS USER JWT-----
|
|
973
|
+
${jwt}
|
|
974
|
+
------END NATS USER JWT------
|
|
975
|
+
|
|
976
|
+
************************* IMPORTANT *************************
|
|
977
|
+
NKEY Seed printed below can be used to sign and prove identity.
|
|
978
|
+
NKEYs are sensitive and should be treated as secrets.
|
|
870
979
|
|
|
871
|
-
|
|
872
|
-
|
|
980
|
+
-----BEGIN USER NKEY SEED-----
|
|
981
|
+
${secret}
|
|
982
|
+
------END USER NKEY SEED------
|
|
873
983
|
|
|
874
|
-
|
|
984
|
+
*************************************************************`
|
|
875
985
|
}
|
|
876
986
|
|
|
877
987
|
// Exposure for tests
|
|
@@ -930,6 +1040,14 @@ export class Realtime {
|
|
|
930
1040
|
return null;
|
|
931
1041
|
}
|
|
932
1042
|
}
|
|
1043
|
+
|
|
1044
|
+
testPatternMatcher(){
|
|
1045
|
+
if(process.env.NODE_ENV == "test"){
|
|
1046
|
+
return this.#topicPatternMatcher.bind(this)
|
|
1047
|
+
}else{
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
933
1051
|
}
|
|
934
1052
|
|
|
935
1053
|
export const CONNECTED = "CONNECTED";
|
package/tests/test.js
CHANGED
|
@@ -476,36 +476,38 @@ test("Test isTopicValidMethod()", () => {
|
|
|
476
476
|
});
|
|
477
477
|
|
|
478
478
|
unreservedInvalidTopics = [
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
"
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
479
|
+
"$foo",
|
|
480
|
+
"foo$",
|
|
481
|
+
"foo.$.bar",
|
|
482
|
+
"foo..bar",
|
|
483
|
+
".foo",
|
|
484
|
+
"foo.",
|
|
485
|
+
"foo.>.bar",
|
|
486
|
+
">foo",
|
|
487
|
+
"foo>bar",
|
|
488
|
+
"foo.>bar",
|
|
489
|
+
"foo.bar.>.",
|
|
490
|
+
"foo bar",
|
|
491
|
+
"foo/bar",
|
|
492
|
+
"foo#bar",
|
|
493
|
+
"",
|
|
494
|
+
" ",
|
|
495
|
+
"..",
|
|
496
|
+
".>",
|
|
497
|
+
"foo..",
|
|
498
|
+
".",
|
|
499
|
+
">.",
|
|
500
|
+
"foo,baz",
|
|
501
|
+
"αbeta",
|
|
502
|
+
"foo|bar",
|
|
503
|
+
"foo;bar",
|
|
504
|
+
"foo:bar",
|
|
505
|
+
"foo%bar",
|
|
506
|
+
"foo.*.>.bar",
|
|
507
|
+
"foo.*.>.",
|
|
508
|
+
"foo.*..bar",
|
|
509
|
+
"foo.>.bar",
|
|
510
|
+
"foo>"
|
|
509
511
|
];
|
|
510
512
|
|
|
511
513
|
unreservedInvalidTopics.forEach(topic => {
|
|
@@ -514,40 +516,41 @@ test("Test isTopicValidMethod()", () => {
|
|
|
514
516
|
});
|
|
515
517
|
|
|
516
518
|
var unreservedValidTopics = [
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
519
|
+
"foo",
|
|
520
|
+
"foo.bar",
|
|
521
|
+
"foo.bar.baz",
|
|
522
|
+
"*",
|
|
523
|
+
"foo.*",
|
|
524
|
+
"*.bar",
|
|
525
|
+
"foo.*.baz",
|
|
526
|
+
">",
|
|
527
|
+
"foo.>",
|
|
528
|
+
"foo.bar.>",
|
|
529
|
+
"*.*.>",
|
|
530
|
+
"alpha_beta",
|
|
531
|
+
"alpha-beta",
|
|
532
|
+
"alpha~beta",
|
|
533
|
+
"abc123",
|
|
534
|
+
"123abc",
|
|
535
|
+
"~",
|
|
536
|
+
"alpha.*.>",
|
|
537
|
+
"alpha.*",
|
|
538
|
+
"alpha.*.*",
|
|
539
|
+
"-foo",
|
|
540
|
+
"foo_bar-baz~qux",
|
|
541
|
+
"A.B.C",
|
|
542
|
+
"sensor.temperature",
|
|
543
|
+
"metric.cpu.load",
|
|
544
|
+
"foo.*.*",
|
|
545
|
+
"foo.*.>",
|
|
546
|
+
"foo_bar.*",
|
|
547
|
+
"*.*",
|
|
548
|
+
"metrics.>"
|
|
547
549
|
];
|
|
548
550
|
|
|
549
551
|
unreservedValidTopics.forEach(topic => {
|
|
550
552
|
var valid = realTimeEnabled.isTopicValid(topic);
|
|
553
|
+
console.log(topic)
|
|
551
554
|
assert.strictEqual(valid, true);
|
|
552
555
|
});
|
|
553
556
|
});
|
|
@@ -606,4 +609,77 @@ test("History test", async () => {
|
|
|
606
609
|
},
|
|
607
610
|
new Error("$start must be a Date object"),
|
|
608
611
|
"Expected error was not thrown");
|
|
609
|
-
})
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
test("Pattern matcher test", async () => {
|
|
615
|
+
var cases = [
|
|
616
|
+
["foo", "foo", true], // 1
|
|
617
|
+
["foo", "bar", false], // 2
|
|
618
|
+
["foo.*", "foo.bar", true], // 3
|
|
619
|
+
["foo.bar", "foo.*", true], // 4
|
|
620
|
+
["*", "token", true], // 5
|
|
621
|
+
["*", "*", true], // 6
|
|
622
|
+
["foo.*", "foo.bar.baz", false], // 7
|
|
623
|
+
["foo.>", "foo.bar.baz", true], // 8
|
|
624
|
+
["foo.>", "foo", false], // 9 (zero‑token '>' now invalid)
|
|
625
|
+
["foo.bar.baz", "foo.>", true], // 10
|
|
626
|
+
["foo.bar.>", "foo.bar", false], // 11
|
|
627
|
+
["foo", "foo.>", false], // 12
|
|
628
|
+
["foo.*.>", "foo.bar.baz.qux", true], // 13
|
|
629
|
+
["foo.*.baz", "foo.bar.>", true], // 14
|
|
630
|
+
["alpha.*", "beta.gamma", false], // 15
|
|
631
|
+
["alpha.beta", "alpha.*.*", false], // 16
|
|
632
|
+
["foo.>.bar", "foo.any.bar", false], // 17 ('>' mid‑pattern)
|
|
633
|
+
[">", "foo.bar", true], // 18
|
|
634
|
+
[">", ">", true], // 19
|
|
635
|
+
["*", ">", true], // 20
|
|
636
|
+
["*.>", "foo.bar", true], // 21
|
|
637
|
+
["*.*.*", "a.b.c", true], // 22
|
|
638
|
+
["*.*.*", "a.b", false], // 23
|
|
639
|
+
["a.b.c.d.e", "a.b.c.d.e", true], // 24
|
|
640
|
+
["a.b.c.d.e", "a.b.c.d.f", false], // 25
|
|
641
|
+
["a.b.*.d", "a.b.c.d", true], // 26
|
|
642
|
+
["a.b.*.d", "a.b.c.e", false], // 27
|
|
643
|
+
["a.b.>", "a.b", false], // 28
|
|
644
|
+
["a.b", "a.b.c.d.>", false], // 29
|
|
645
|
+
["a.b.>.c", "a.b.x.c", false], // 30
|
|
646
|
+
["a.*.*", "a.b", false], // 31
|
|
647
|
+
["a.*", "a.b.c", false], // 32
|
|
648
|
+
["metrics.cpu.load", "metrics.*.load", true], // 33
|
|
649
|
+
["metrics.cpu.load", "metrics.cpu.*", true], // 34
|
|
650
|
+
["metrics.cpu.load", "metrics.>.load", false], // 35
|
|
651
|
+
["metrics.>", "metrics", false], // 36
|
|
652
|
+
["metrics.>", "othermetrics.cpu", false], // 37
|
|
653
|
+
["*.*.>", "a.b", false], // 38
|
|
654
|
+
["*.*.>", "a.b.c.d", true], // 39
|
|
655
|
+
["a.b.c", "*.*.*", true], // 40
|
|
656
|
+
["a.b.c", "*.*", false], // 41
|
|
657
|
+
["alpha.*.>", "alpha", false], // 42
|
|
658
|
+
["alpha.*.>", "alpha.beta", false], // 43
|
|
659
|
+
["alpha.*.>", "alpha.beta.gamma", true], // 44
|
|
660
|
+
["alpha.*.>", "beta.alpha.gamma", false], // 45
|
|
661
|
+
["foo-bar_baz", "foo-bar_baz", true], // 46
|
|
662
|
+
["foo-bar_*", "foo-bar_123", false], // 47 ( '*' here is literal )
|
|
663
|
+
["foo-bar_*", "foo-bar_*", true], // 48
|
|
664
|
+
["order-*", "order-123", false], // 49
|
|
665
|
+
["hello.hey.*", "hello.hey.>", true] // 50
|
|
666
|
+
];
|
|
667
|
+
|
|
668
|
+
var realtime = new Realtime({
|
|
669
|
+
api_key: process.env.AUTH_JWT,
|
|
670
|
+
secret: process.env.AUTH_SECRET
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
var patternMatcher = realtime.testPatternMatcher();
|
|
674
|
+
|
|
675
|
+
cases.forEach(testCase => {
|
|
676
|
+
var tokenA = testCase[0];
|
|
677
|
+
var tokenB = testCase[1];
|
|
678
|
+
var expectedResult = testCase[2];
|
|
679
|
+
|
|
680
|
+
console.log(`${tokenA} ⇆ ${tokenB} → ${expectedResult}`)
|
|
681
|
+
|
|
682
|
+
var result = patternMatcher(tokenA, tokenB)
|
|
683
|
+
assert.strictEqual(expectedResult, result)
|
|
684
|
+
});
|
|
685
|
+
})
|