@persistica/flux-agent 0.0.24 → 0.0.26
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/index.js +88 -41
- package/package.json +1 -1
- package/src/lib/flux-agent.class.d.ts +3 -3
package/index.js
CHANGED
|
@@ -40,6 +40,7 @@ var ON_NETWORK_CHANNEL_PUBLISH = "nc-on-pub";
|
|
|
40
40
|
var SUBSCRIBE_NETWORK_CHANNEL_NAME = "subscribe-to-nc-name";
|
|
41
41
|
var SUBSCRIBED_NETWORK_CHANNEL_NAME = "subscribed-to-nc-name";
|
|
42
42
|
var UNSUBSCRIBE_NETWORK_CHANNEL_NAME = "unsubscribe-nc-name";
|
|
43
|
+
var UNSUBSCRIBED_NETWORK_CHANNEL_NAME = "unsubscribed-nc-name";
|
|
43
44
|
var AUTHORITY_CHANNEL_SUBSCRIBE = "acs";
|
|
44
45
|
var AUTHORITY_DISCONNECT_AGENT = "ada";
|
|
45
46
|
var ERROR = "e";
|
|
@@ -87,7 +88,7 @@ var validateNetworkIdOrThrow = (networkId) => {
|
|
|
87
88
|
// libs/flux/shared/types/src/lib/client-id.type.ts
|
|
88
89
|
var NANOID_LENGTH = 21;
|
|
89
90
|
var NANOID_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-";
|
|
90
|
-
var
|
|
91
|
+
var isClientId = (id) => {
|
|
91
92
|
if (typeof id !== "string") {
|
|
92
93
|
return false;
|
|
93
94
|
}
|
|
@@ -198,7 +199,7 @@ var WebSocketClient = class extends RPCServer {
|
|
|
198
199
|
);
|
|
199
200
|
setTimeout(() => {
|
|
200
201
|
this.reconnectAttempts++;
|
|
201
|
-
this.connect();
|
|
202
|
+
void this.connect();
|
|
202
203
|
}, delay);
|
|
203
204
|
} else if (this.options.autoReconnect) {
|
|
204
205
|
this.emit("error", new Error("Connection failed: retries exhausted"));
|
|
@@ -402,9 +403,7 @@ var FluxAgentNetworkConnection = class {
|
|
|
402
403
|
this._fluxWebSocketClientConnection,
|
|
403
404
|
this._webRTCStateChange
|
|
404
405
|
);
|
|
405
|
-
console.log(
|
|
406
|
-
this.iceConnection ? `RTCPeerConnection is available` : `RTCPeerConnection is not available`
|
|
407
|
-
);
|
|
406
|
+
console.log(`RTCPeerConnection is available`);
|
|
408
407
|
} else {
|
|
409
408
|
console.warn("\u2757WebRTC is not available in this environment.");
|
|
410
409
|
}
|
|
@@ -422,7 +421,7 @@ var FluxAgentNetworkConnection = class {
|
|
|
422
421
|
const fluxNetworkChannel = await this._fluxWebSocketConnection.joinChannel(
|
|
423
422
|
channelName
|
|
424
423
|
);
|
|
425
|
-
this.connectedChannelSet.add(
|
|
424
|
+
this.connectedChannelSet.add(channelName);
|
|
426
425
|
return fluxNetworkChannel;
|
|
427
426
|
}
|
|
428
427
|
/**
|
|
@@ -442,7 +441,7 @@ var FluxAgentNetworkConnection = class {
|
|
|
442
441
|
* Connects to a remote agent.
|
|
443
442
|
*/
|
|
444
443
|
connectToAgent(clientId) {
|
|
445
|
-
this._fluxWebSocketConnection.connectToAgent(clientId);
|
|
444
|
+
void this._fluxWebSocketConnection.connectToAgent(clientId);
|
|
446
445
|
return new FluxRemoteClient(this.iceConnection);
|
|
447
446
|
}
|
|
448
447
|
/**
|
|
@@ -458,25 +457,25 @@ var hasOverlap = (a, b) => a && b ? [...a].some((item) => (Array.isArray(b) ? b
|
|
|
458
457
|
var PicoLogger = /* @__PURE__ */ (() => {
|
|
459
458
|
let allowScopes;
|
|
460
459
|
const configure = (configuration) => {
|
|
461
|
-
allowScopes =
|
|
460
|
+
allowScopes = configuration.allowScopes === "*" ? "*" : new Set(configuration.allowScopes);
|
|
462
461
|
};
|
|
463
462
|
const log = (message, scopes) => {
|
|
464
463
|
if (allowScopes !== "*" && !hasOverlap(allowScopes, scopes)) {
|
|
465
464
|
return;
|
|
466
465
|
}
|
|
467
|
-
console.log(`[${scopes}] ${message}`);
|
|
466
|
+
console.log(`[${scopes?.toString()}] ${message}`);
|
|
468
467
|
};
|
|
469
468
|
const warn = (message, scopes) => {
|
|
470
469
|
if (allowScopes !== "*" && !hasOverlap(allowScopes, scopes)) {
|
|
471
470
|
return;
|
|
472
471
|
}
|
|
473
|
-
console.warn(`[${scopes}] ${message}`);
|
|
472
|
+
console.warn(`[${scopes?.toString()}] ${message}`);
|
|
474
473
|
};
|
|
475
474
|
const error = (message, scopes) => {
|
|
476
475
|
if (allowScopes !== "*" && !hasOverlap(allowScopes, scopes)) {
|
|
477
476
|
return;
|
|
478
477
|
}
|
|
479
|
-
console.error(`[${scopes}] ${message}`);
|
|
478
|
+
console.error(`[${scopes?.toString()}] ${message}`);
|
|
480
479
|
};
|
|
481
480
|
return {
|
|
482
481
|
configure,
|
|
@@ -488,8 +487,8 @@ var PicoLogger = /* @__PURE__ */ (() => {
|
|
|
488
487
|
|
|
489
488
|
// libs/flux/shared/connection/src/lib/flux-network-channel.class.ts
|
|
490
489
|
var FluxNetworkChannel = class {
|
|
491
|
-
constructor(
|
|
492
|
-
this.
|
|
490
|
+
constructor(_channelName, _fluxWebSocketConnection) {
|
|
491
|
+
this._channelName = _channelName;
|
|
493
492
|
this._fluxWebSocketConnection = _fluxWebSocketConnection;
|
|
494
493
|
}
|
|
495
494
|
/**
|
|
@@ -497,7 +496,7 @@ var FluxNetworkChannel = class {
|
|
|
497
496
|
*/
|
|
498
497
|
publish(message) {
|
|
499
498
|
this._fluxWebSocketConnection.publish(
|
|
500
|
-
this.
|
|
499
|
+
this._channelName,
|
|
501
500
|
message
|
|
502
501
|
);
|
|
503
502
|
}
|
|
@@ -506,7 +505,7 @@ var FluxNetworkChannel = class {
|
|
|
506
505
|
*/
|
|
507
506
|
onPublish(fn) {
|
|
508
507
|
this._fluxWebSocketConnection.onPublish(
|
|
509
|
-
this.
|
|
508
|
+
this._channelName,
|
|
510
509
|
fn
|
|
511
510
|
// TODO
|
|
512
511
|
);
|
|
@@ -517,11 +516,17 @@ var FluxNetworkChannel = class {
|
|
|
517
516
|
var CONNECTION_TIMEOUT_MS = 2e3;
|
|
518
517
|
var ChannelStateManager = class {
|
|
519
518
|
connectionTimeouts = /* @__PURE__ */ new Map();
|
|
519
|
+
joinedChannels = /* @__PURE__ */ new Map();
|
|
520
520
|
/**
|
|
521
521
|
* Join a channel.
|
|
522
522
|
*/
|
|
523
523
|
async joinChannel(channelName, fluxWebSocketConnection, webSocketClient) {
|
|
524
524
|
if (webSocketClient) {
|
|
525
|
+
const existingChannel = this.joinedChannels.get(channelName);
|
|
526
|
+
if (existingChannel) {
|
|
527
|
+
existingChannel.useCount++;
|
|
528
|
+
return Promise.resolve(existingChannel.channel);
|
|
529
|
+
}
|
|
525
530
|
return new Promise((resolve, reject) => {
|
|
526
531
|
const existingTimeout = this.connectionTimeouts.get(channelName);
|
|
527
532
|
if (existingTimeout) {
|
|
@@ -539,7 +544,9 @@ var ChannelStateManager = class {
|
|
|
539
544
|
this.connectionTimeouts.delete(channelName);
|
|
540
545
|
}
|
|
541
546
|
fluxWebSocketConnection.removePackageTypeInterceptor(SUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
|
|
542
|
-
|
|
547
|
+
const channel = new FluxNetworkChannel(channelName, fluxWebSocketConnection);
|
|
548
|
+
this.joinedChannels.set(channelName, { channel, useCount: 1 });
|
|
549
|
+
resolve(channel);
|
|
543
550
|
};
|
|
544
551
|
fluxWebSocketConnection.interceptPackageTypeMessages(
|
|
545
552
|
SUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
@@ -561,9 +568,46 @@ var ChannelStateManager = class {
|
|
|
561
568
|
/**
|
|
562
569
|
* Leave a channel.
|
|
563
570
|
*/
|
|
564
|
-
async leaveChannel(channelName, webSocketClient) {
|
|
571
|
+
async leaveChannel(channelName, fluxWebSocketConnection, webSocketClient) {
|
|
572
|
+
const existingChannel = this.joinedChannels.get(channelName);
|
|
573
|
+
if (!existingChannel) {
|
|
574
|
+
return Promise.reject(new Error("No channel instance found"));
|
|
575
|
+
}
|
|
576
|
+
existingChannel.useCount--;
|
|
577
|
+
if (existingChannel.useCount <= 0) {
|
|
578
|
+
this.joinedChannels.delete(channelName);
|
|
579
|
+
}
|
|
580
|
+
if (webSocketClient && existingChannel.useCount <= 0) {
|
|
581
|
+
return new Promise((resolve, reject) => {
|
|
582
|
+
const cb = (message) => {
|
|
583
|
+
const receivedChannelName = message.substring(message.indexOf(":") + 1);
|
|
584
|
+
if (channelName !== receivedChannelName) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const timeout = this.connectionTimeouts.get(channelName);
|
|
588
|
+
if (timeout) {
|
|
589
|
+
clearTimeout(timeout);
|
|
590
|
+
this.connectionTimeouts.delete(channelName);
|
|
591
|
+
}
|
|
592
|
+
fluxWebSocketConnection.removePackageTypeInterceptor(UNSUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
|
|
593
|
+
resolve(void 0);
|
|
594
|
+
};
|
|
595
|
+
fluxWebSocketConnection.interceptPackageTypeMessages(
|
|
596
|
+
UNSUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
597
|
+
cb
|
|
598
|
+
);
|
|
599
|
+
this.connectionTimeouts.set(
|
|
600
|
+
channelName,
|
|
601
|
+
setTimeout(() => {
|
|
602
|
+
this.connectionTimeouts.delete(channelName);
|
|
603
|
+
fluxWebSocketConnection.removePackageTypeInterceptor(UNSUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
|
|
604
|
+
reject(new Error(`Channel unsubscription timed out for channel "${channelName}"`));
|
|
605
|
+
}, CONNECTION_TIMEOUT_MS)
|
|
606
|
+
);
|
|
607
|
+
webSocketClient.send(`${UNSUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
565
610
|
if (webSocketClient) {
|
|
566
|
-
webSocketClient.send(`${UNSUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
|
|
567
611
|
return Promise.resolve(void 0);
|
|
568
612
|
}
|
|
569
613
|
return Promise.reject(new Error("Not connected"));
|
|
@@ -579,8 +623,8 @@ var createWSConnection = (id, ticket, stateManager, onReconnectCallback, options
|
|
|
579
623
|
ticket,
|
|
580
624
|
{
|
|
581
625
|
domain: options.domain,
|
|
582
|
-
secretKey: options
|
|
583
|
-
retries: options
|
|
626
|
+
secretKey: options.secretKey,
|
|
627
|
+
retries: options.retries
|
|
584
628
|
}
|
|
585
629
|
);
|
|
586
630
|
};
|
|
@@ -593,8 +637,8 @@ var FluxWebSocketConnection = class {
|
|
|
593
637
|
this.options = options;
|
|
594
638
|
this.options = {
|
|
595
639
|
domain: this.options.domain,
|
|
596
|
-
secretKey: this.options
|
|
597
|
-
retries: this.options
|
|
640
|
+
secretKey: this.options.secretKey,
|
|
641
|
+
retries: this.options.retries ?? 1e4
|
|
598
642
|
};
|
|
599
643
|
const url = new URL(this.options.domain);
|
|
600
644
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -677,7 +721,6 @@ var FluxWebSocketConnection = class {
|
|
|
677
721
|
});
|
|
678
722
|
this.socket.clearEventSubscribers();
|
|
679
723
|
this.socket.on("message", this.socketMessageHandler).on("close", this.socketCloseHandler).on("connecting", this.socketConnectingHandler).on("error", this.socketErrorHandler);
|
|
680
|
-
this.stateManager.emitNetworkState("connecting");
|
|
681
724
|
void this.socket.connect();
|
|
682
725
|
return this.connectPromise;
|
|
683
726
|
}
|
|
@@ -731,6 +774,7 @@ var FluxWebSocketConnection = class {
|
|
|
731
774
|
async leaveChannel(channelName) {
|
|
732
775
|
return this.channelStateManager.leaveChannel(
|
|
733
776
|
channelName,
|
|
777
|
+
this,
|
|
734
778
|
this.webSocketClient
|
|
735
779
|
);
|
|
736
780
|
}
|
|
@@ -788,7 +832,7 @@ var FluxWebSocketConnection = class {
|
|
|
788
832
|
* Disconnects an agent from the network.
|
|
789
833
|
*/
|
|
790
834
|
disconnectAgent(id) {
|
|
791
|
-
if (!
|
|
835
|
+
if (!isClientId(id)) {
|
|
792
836
|
throw new Error(`Invalid agent id: '${id}'`);
|
|
793
837
|
}
|
|
794
838
|
if (this.webSocketClient) {
|
|
@@ -817,9 +861,9 @@ var FluxWebSocketConnection = class {
|
|
|
817
861
|
this.stateManager.emitNetworkState(reason === "kicked" ? "kicked" : "disconnected");
|
|
818
862
|
}
|
|
819
863
|
handleSocketConnecting(retryAttempt) {
|
|
820
|
-
this.stateManager.emitNetworkState("
|
|
864
|
+
this.stateManager.emitNetworkState("connecting");
|
|
821
865
|
if (retryAttempt > 0) {
|
|
822
|
-
PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options
|
|
866
|
+
PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options.retries ?? "none"}`, this.fluxInstanceId);
|
|
823
867
|
} else {
|
|
824
868
|
PicoLogger.log(`\u{1F504} Connecting: ${this.fluxInstanceId}`);
|
|
825
869
|
}
|
|
@@ -865,26 +909,26 @@ var FluxWebSocketConnection = class {
|
|
|
865
909
|
}
|
|
866
910
|
}
|
|
867
911
|
}
|
|
868
|
-
|
|
912
|
+
return;
|
|
869
913
|
}
|
|
870
914
|
case RPC_REQUEST: {
|
|
871
915
|
const payload = JSON.parse(message_.substring(message_.indexOf(":") + 1));
|
|
872
|
-
this.socket.handleMessage(payload, (data) => {
|
|
916
|
+
void this.socket.handleMessage(payload, (data) => {
|
|
873
917
|
this.socket.send(`${RPC_RESPONSE}:${JSON.stringify(data)}`);
|
|
874
918
|
});
|
|
875
|
-
|
|
919
|
+
return;
|
|
876
920
|
}
|
|
877
921
|
case RPC_RESPONSE: {
|
|
878
922
|
const payload = message_.substring(message_.indexOf(":") + 1);
|
|
879
923
|
PicoLogger.log("\u{1F50C} Unhandled type rpc response", "ws-client");
|
|
880
924
|
PicoLogger.log(`payload: ${payload}`, "ws-client");
|
|
881
|
-
|
|
925
|
+
return;
|
|
882
926
|
}
|
|
883
927
|
default:
|
|
884
928
|
if (message_.startsWith(`${ERROR}:`)) {
|
|
885
929
|
}
|
|
886
930
|
PicoLogger.log(`\u{1F50C} Unhandled type: "${message_}"`, "ws-client");
|
|
887
|
-
|
|
931
|
+
return;
|
|
888
932
|
}
|
|
889
933
|
}
|
|
890
934
|
};
|
|
@@ -1021,9 +1065,8 @@ var StateManager = class {
|
|
|
1021
1065
|
|
|
1022
1066
|
// libs/flux/shared/utils/src/lib/promises.utils.ts
|
|
1023
1067
|
var retryOrThrow = async (fn, shouldRetry, options) => {
|
|
1024
|
-
let attempt = 0;
|
|
1025
1068
|
let overrideDelay;
|
|
1026
|
-
|
|
1069
|
+
for (let attempt = 0; attempt <= options.retries; attempt++) {
|
|
1027
1070
|
try {
|
|
1028
1071
|
if (attempt > 0) {
|
|
1029
1072
|
if (options.onRetry) {
|
|
@@ -1032,12 +1075,11 @@ var retryOrThrow = async (fn, shouldRetry, options) => {
|
|
|
1032
1075
|
}
|
|
1033
1076
|
return await fn();
|
|
1034
1077
|
} catch (err) {
|
|
1035
|
-
attempt
|
|
1036
|
-
if (attempt > options.retries || !shouldRetry(err)) {
|
|
1078
|
+
if (attempt >= options.retries || !shouldRetry(err)) {
|
|
1037
1079
|
throw err;
|
|
1038
1080
|
}
|
|
1039
1081
|
if (options.delayMs > 0 || overrideDelay && overrideDelay > 0) {
|
|
1040
|
-
|
|
1082
|
+
const delay = overrideDelay ?? options.delayMs;
|
|
1041
1083
|
await new Promise((res) => setTimeout(
|
|
1042
1084
|
res,
|
|
1043
1085
|
delay
|
|
@@ -1045,6 +1087,7 @@ var retryOrThrow = async (fn, shouldRetry, options) => {
|
|
|
1045
1087
|
}
|
|
1046
1088
|
}
|
|
1047
1089
|
}
|
|
1090
|
+
throw new Error("Retry loop exited unexpectedly.");
|
|
1048
1091
|
};
|
|
1049
1092
|
|
|
1050
1093
|
// libs/flux/shared/utils/src/lib/machine-id.util.ts
|
|
@@ -1151,15 +1194,19 @@ var FluxClientData = class {
|
|
|
1151
1194
|
// packages/flux/agent/src/lib/flux-agent.class.ts
|
|
1152
1195
|
var DEFAULT_FLUX_DOMAIN = "https://mesh.persistica.io";
|
|
1153
1196
|
var FluxAgent = class {
|
|
1154
|
-
constructor(networkId, options) {
|
|
1155
|
-
this.networkId = networkId;
|
|
1156
|
-
this.options = options;
|
|
1157
|
-
validateNetworkIdOrThrow(this.networkId);
|
|
1158
|
-
}
|
|
1159
1197
|
id = nanoid();
|
|
1160
1198
|
fluxWebSocketConnection;
|
|
1161
1199
|
fluxClientData = new FluxClientData();
|
|
1162
1200
|
stateManager = new StateManager();
|
|
1201
|
+
networkId;
|
|
1202
|
+
options;
|
|
1203
|
+
constructor(networkId, options) {
|
|
1204
|
+
if (!validateNetworkIdOrThrow(networkId)) {
|
|
1205
|
+
throw new Error("Will never be thrown");
|
|
1206
|
+
}
|
|
1207
|
+
this.networkId = networkId;
|
|
1208
|
+
this.options = options;
|
|
1209
|
+
}
|
|
1163
1210
|
/**
|
|
1164
1211
|
* Connect to the network using provided identification.
|
|
1165
1212
|
*
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { TMessageCallback } from '@flux/shared/ws';
|
|
2
2
|
import type { FluxAgentNetworkConnection } from '@flux/shared/connection';
|
|
3
3
|
export declare class FluxAgent {
|
|
4
|
-
private readonly networkId;
|
|
5
|
-
private readonly options?;
|
|
6
4
|
readonly id: string;
|
|
7
5
|
private fluxWebSocketConnection;
|
|
8
6
|
private readonly fluxClientData;
|
|
9
7
|
private readonly stateManager;
|
|
8
|
+
private readonly networkId;
|
|
9
|
+
private readonly options;
|
|
10
10
|
constructor(networkId: string, options?: {
|
|
11
11
|
domain?: string;
|
|
12
12
|
secretKey?: string;
|
|
13
13
|
retries?: number;
|
|
14
|
-
}
|
|
14
|
+
});
|
|
15
15
|
/**
|
|
16
16
|
* Connect to the network using provided identification.
|
|
17
17
|
*
|