@persistica/flux-agent 0.0.19 → 0.0.20
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 +162 -96
- package/package.json +1 -1
- package/src/lib/flux-agent.class.d.ts +0 -3
package/index.js
CHANGED
|
@@ -453,33 +453,6 @@ var FluxAgentNetworkConnection = class {
|
|
|
453
453
|
}
|
|
454
454
|
};
|
|
455
455
|
|
|
456
|
-
// libs/flux/shared/connection/src/lib/flux-network-channel.class.ts
|
|
457
|
-
var FluxNetworkChannel = class {
|
|
458
|
-
constructor(channelName, _fluxWebSocketConnection) {
|
|
459
|
-
this.channelName = channelName;
|
|
460
|
-
this._fluxWebSocketConnection = _fluxWebSocketConnection;
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Broadcasts a message to the channel.
|
|
464
|
-
*/
|
|
465
|
-
publish(message) {
|
|
466
|
-
this._fluxWebSocketConnection.publish(
|
|
467
|
-
this.channelName,
|
|
468
|
-
message
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Listen to messages on this channel.
|
|
473
|
-
*/
|
|
474
|
-
onPublish(fn) {
|
|
475
|
-
this._fluxWebSocketConnection.onPublish(
|
|
476
|
-
this.channelName,
|
|
477
|
-
fn
|
|
478
|
-
// TODO
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
|
|
483
456
|
// libs/utils/logger/src/lib/utils-logger.ts
|
|
484
457
|
var hasOverlap = (a, b) => a && b ? [...a].some((item) => (Array.isArray(b) ? b : [b]).includes(item)) : false;
|
|
485
458
|
var PicoLogger = /* @__PURE__ */ (() => {
|
|
@@ -513,6 +486,91 @@ var PicoLogger = /* @__PURE__ */ (() => {
|
|
|
513
486
|
};
|
|
514
487
|
})();
|
|
515
488
|
|
|
489
|
+
// libs/flux/shared/connection/src/lib/flux-network-channel.class.ts
|
|
490
|
+
var FluxNetworkChannel = class {
|
|
491
|
+
constructor(channelName, _fluxWebSocketConnection) {
|
|
492
|
+
this.channelName = channelName;
|
|
493
|
+
this._fluxWebSocketConnection = _fluxWebSocketConnection;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Broadcasts a message to the channel.
|
|
497
|
+
*/
|
|
498
|
+
publish(message) {
|
|
499
|
+
this._fluxWebSocketConnection.publish(
|
|
500
|
+
this.channelName,
|
|
501
|
+
message
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Listen to messages on this channel.
|
|
506
|
+
*/
|
|
507
|
+
onPublish(fn) {
|
|
508
|
+
this._fluxWebSocketConnection.onPublish(
|
|
509
|
+
this.channelName,
|
|
510
|
+
fn
|
|
511
|
+
// TODO
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// libs/flux/shared/connection/src/lib/channel-state-manager.ts
|
|
517
|
+
var CONNECTION_TIMEOUT_MS = 2e3;
|
|
518
|
+
var ChannelStateManager = class {
|
|
519
|
+
connectionTimeouts = /* @__PURE__ */ new Map();
|
|
520
|
+
/**
|
|
521
|
+
* Join a channel.
|
|
522
|
+
*/
|
|
523
|
+
async joinChannel(channelName, fluxWebSocketConnection, webSocketClient) {
|
|
524
|
+
if (webSocketClient) {
|
|
525
|
+
return new Promise((resolve, reject) => {
|
|
526
|
+
webSocketClient.send(`${SUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
|
|
527
|
+
const existingTimeout = this.connectionTimeouts.get(channelName);
|
|
528
|
+
if (existingTimeout) {
|
|
529
|
+
clearTimeout(existingTimeout);
|
|
530
|
+
this.connectionTimeouts.delete(channelName);
|
|
531
|
+
}
|
|
532
|
+
const cb = (message) => {
|
|
533
|
+
const timeout = this.connectionTimeouts.get(channelName);
|
|
534
|
+
if (timeout) {
|
|
535
|
+
clearTimeout(timeout);
|
|
536
|
+
this.connectionTimeouts.delete(channelName);
|
|
537
|
+
}
|
|
538
|
+
fluxWebSocketConnection.removePackageTypeInterceptor(SUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
|
|
539
|
+
const receivedChannelName = message.substring(message.indexOf(":") + 1);
|
|
540
|
+
if (channelName !== receivedChannelName) {
|
|
541
|
+
reject(new Error(`Channel name mismatch: "${channelName}" !== "${receivedChannelName}"`));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
resolve(new FluxNetworkChannel(channelName, fluxWebSocketConnection));
|
|
545
|
+
};
|
|
546
|
+
this.connectionTimeouts.set(
|
|
547
|
+
channelName,
|
|
548
|
+
setTimeout(() => {
|
|
549
|
+
this.connectionTimeouts.delete(channelName);
|
|
550
|
+
fluxWebSocketConnection.removePackageTypeInterceptor(SUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
|
|
551
|
+
reject(new Error(`Channel subscription timed out for channel "${channelName}"`));
|
|
552
|
+
}, CONNECTION_TIMEOUT_MS)
|
|
553
|
+
);
|
|
554
|
+
fluxWebSocketConnection.interceptPackageTypeMessages(
|
|
555
|
+
SUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
556
|
+
cb
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
return Promise.reject(new Error("Not connected"));
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Leave a channel.
|
|
564
|
+
*/
|
|
565
|
+
async leaveChannel(channelName, webSocketClient) {
|
|
566
|
+
if (webSocketClient) {
|
|
567
|
+
webSocketClient.send(`${UNSUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
|
|
568
|
+
return Promise.resolve(void 0);
|
|
569
|
+
}
|
|
570
|
+
return Promise.reject(new Error("Not connected"));
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
516
574
|
// libs/flux/shared/connection/src/lib/flux-ws-connection.ts
|
|
517
575
|
var createWSConnection = (id, ticket, stateManager, onReconnectCallback, options) => {
|
|
518
576
|
return new FluxWebSocketConnection(
|
|
@@ -539,7 +597,7 @@ var FluxWebSocketConnection = class {
|
|
|
539
597
|
secretKey: this.options?.secretKey,
|
|
540
598
|
retries: this.options?.retries ?? 1e4
|
|
541
599
|
};
|
|
542
|
-
const url = new URL(this.options
|
|
600
|
+
const url = new URL(this.options.domain);
|
|
543
601
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
544
602
|
url.searchParams.set("token", this.token);
|
|
545
603
|
this.socket = new FluxWebSocketClientConnection(
|
|
@@ -550,14 +608,36 @@ var FluxWebSocketConnection = class {
|
|
|
550
608
|
retries: this.options.retries
|
|
551
609
|
}
|
|
552
610
|
);
|
|
611
|
+
this.interceptPackageTypeMessages("isReady", this.readyInterceptor);
|
|
553
612
|
}
|
|
613
|
+
// ****************************************************************************
|
|
614
|
+
// *** State
|
|
615
|
+
// ****************************************************************************
|
|
554
616
|
socket;
|
|
555
617
|
callbacks = /* @__PURE__ */ new Set();
|
|
556
618
|
channelCallbacks = /* @__PURE__ */ new Map();
|
|
557
|
-
// Handles messages before the rest of the logic. Will not continue if there are interceptors
|
|
619
|
+
// Handles messages before the rest of the logic. Will not continue if there are interceptors.
|
|
558
620
|
packageTypeInterceptorCallbacks = /* @__PURE__ */ new Map();
|
|
559
621
|
webSocketClient;
|
|
622
|
+
connectPromise;
|
|
623
|
+
connectPromiseResolver;
|
|
560
624
|
first = true;
|
|
625
|
+
channelStateManager = new ChannelStateManager();
|
|
626
|
+
// ****************************************************************************
|
|
627
|
+
// *** Reused socket callbacks
|
|
628
|
+
// ****************************************************************************
|
|
629
|
+
readyInterceptor = () => {
|
|
630
|
+
this.resolveConnectedSocket();
|
|
631
|
+
if (this.first) {
|
|
632
|
+
this.first = false;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
this.onReconnectCallback();
|
|
636
|
+
};
|
|
637
|
+
socketMessageHandler = this.handleMessage.bind(this);
|
|
638
|
+
socketCloseHandler = (reason) => this.handleSocketClose(reason);
|
|
639
|
+
socketConnectingHandler = (retryAttempt) => this.handleSocketConnecting(retryAttempt);
|
|
640
|
+
socketErrorHandler = (error) => this.handleSocketError(error);
|
|
561
641
|
/**
|
|
562
642
|
* Intercepts messages of a given package type.
|
|
563
643
|
*/
|
|
@@ -590,46 +670,24 @@ var FluxWebSocketConnection = class {
|
|
|
590
670
|
if (this.webSocketClient) {
|
|
591
671
|
return Promise.resolve(this.webSocketClient);
|
|
592
672
|
}
|
|
593
|
-
|
|
594
|
-
this.
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
this.webSocketClient = this.socket;
|
|
599
|
-
resolve(this.socket);
|
|
600
|
-
if (this.first) {
|
|
601
|
-
this.first = false;
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
this.onReconnectCallback();
|
|
605
|
-
}
|
|
606
|
-
);
|
|
607
|
-
this.socket.clearEventSubscribers();
|
|
608
|
-
this.socket.on("message", this.handleMessage.bind(this)).on("close", (reason) => {
|
|
609
|
-
this.webSocketClient = void 0;
|
|
610
|
-
PicoLogger.log(
|
|
611
|
-
reason === "kicked" ? `\u{1F50C}\u{1F534} Kicked, delaying reconnect ${RECONNECTION_DELAY_ON_KICK_MS / 6e4} minutes` : "\u{1F50C}\u{1F534} Disconnected",
|
|
612
|
-
this.fluxInstanceId
|
|
613
|
-
);
|
|
614
|
-
this.stateManager.emitNetworkState(reason === "kicked" ? "kicked" : "disconnected");
|
|
615
|
-
}).on("connecting", (retryAttempt) => {
|
|
616
|
-
this.stateManager.emitNetworkState("disconnected");
|
|
617
|
-
if (retryAttempt > 0) {
|
|
618
|
-
PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options?.retries ?? "none"}`, this.fluxInstanceId);
|
|
619
|
-
} else {
|
|
620
|
-
PicoLogger.log(`\u{1F504} Connecting: ${this.fluxInstanceId}`);
|
|
621
|
-
}
|
|
622
|
-
}).on("error", (error) => {
|
|
623
|
-
PicoLogger.log(`\u274C Error: "${error.message}".`, this.fluxInstanceId);
|
|
624
|
-
});
|
|
625
|
-
this.stateManager.emitNetworkState("connecting");
|
|
626
|
-
this.socket.connect();
|
|
673
|
+
if (this.connectPromise) {
|
|
674
|
+
return this.connectPromise;
|
|
675
|
+
}
|
|
676
|
+
this.connectPromise = new Promise((resolve) => {
|
|
677
|
+
this.connectPromiseResolver = resolve;
|
|
627
678
|
});
|
|
679
|
+
this.socket.clearEventSubscribers();
|
|
680
|
+
this.socket.on("message", this.socketMessageHandler).on("close", this.socketCloseHandler).on("connecting", this.socketConnectingHandler).on("error", this.socketErrorHandler);
|
|
681
|
+
this.stateManager.emitNetworkState("connecting");
|
|
682
|
+
void this.socket.connect();
|
|
683
|
+
return this.connectPromise;
|
|
628
684
|
}
|
|
629
685
|
/**
|
|
630
686
|
* Disconnects from the Flux platform.
|
|
631
687
|
*/
|
|
632
688
|
disconnect() {
|
|
689
|
+
this.connectPromiseResolver = void 0;
|
|
690
|
+
this.connectPromise = void 0;
|
|
633
691
|
this.socket.close();
|
|
634
692
|
this.webSocketClient = void 0;
|
|
635
693
|
this.stateManager.emitNetworkState("disconnected");
|
|
@@ -662,35 +720,20 @@ var FluxWebSocketConnection = class {
|
|
|
662
720
|
* Join a channel.
|
|
663
721
|
*/
|
|
664
722
|
async joinChannel(channelName) {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const receivedChannelName = message.substring(message.indexOf(":") + 1);
|
|
671
|
-
if (channelName !== receivedChannelName) {
|
|
672
|
-
reject(new Error(`Channel name mismatch: "${channelName}" !== "${receivedChannelName}"`));
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
resolve(new FluxNetworkChannel(channelName, this));
|
|
676
|
-
};
|
|
677
|
-
this.interceptPackageTypeMessages(
|
|
678
|
-
SUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
679
|
-
cb
|
|
680
|
-
);
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
return Promise.reject(new Error("Not connected"));
|
|
723
|
+
return this.channelStateManager.joinChannel(
|
|
724
|
+
channelName,
|
|
725
|
+
this,
|
|
726
|
+
this.webSocketClient
|
|
727
|
+
);
|
|
684
728
|
}
|
|
685
729
|
/**
|
|
686
|
-
*
|
|
730
|
+
* Leave a channel.
|
|
687
731
|
*/
|
|
688
732
|
async leaveChannel(channelName) {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
return Promise.reject(new Error("Not connected"));
|
|
733
|
+
return this.channelStateManager.leaveChannel(
|
|
734
|
+
channelName,
|
|
735
|
+
this.webSocketClient
|
|
736
|
+
);
|
|
694
737
|
}
|
|
695
738
|
/**
|
|
696
739
|
* Publishes a message to a channel.
|
|
@@ -758,6 +801,33 @@ var FluxWebSocketConnection = class {
|
|
|
758
801
|
// ****************************************************************************
|
|
759
802
|
// *** Internal Helpers
|
|
760
803
|
// ****************************************************************************
|
|
804
|
+
resolveConnectedSocket() {
|
|
805
|
+
this.stateManager.emitNetworkState("connected");
|
|
806
|
+
this.webSocketClient = this.socket;
|
|
807
|
+
const connectPromiseResolver = this.connectPromiseResolver;
|
|
808
|
+
this.connectPromiseResolver = void 0;
|
|
809
|
+
this.connectPromise = void 0;
|
|
810
|
+
connectPromiseResolver?.(this.socket);
|
|
811
|
+
}
|
|
812
|
+
handleSocketClose(reason) {
|
|
813
|
+
this.webSocketClient = void 0;
|
|
814
|
+
PicoLogger.log(
|
|
815
|
+
reason === "kicked" ? `\u{1F50C}\u{1F534} Kicked, delaying reconnect ${RECONNECTION_DELAY_ON_KICK_MS / 6e4} minutes` : "\u{1F50C}\u{1F534} Disconnected",
|
|
816
|
+
this.fluxInstanceId
|
|
817
|
+
);
|
|
818
|
+
this.stateManager.emitNetworkState(reason === "kicked" ? "kicked" : "disconnected");
|
|
819
|
+
}
|
|
820
|
+
handleSocketConnecting(retryAttempt) {
|
|
821
|
+
this.stateManager.emitNetworkState("disconnected");
|
|
822
|
+
if (retryAttempt > 0) {
|
|
823
|
+
PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options?.retries ?? "none"}`, this.fluxInstanceId);
|
|
824
|
+
} else {
|
|
825
|
+
PicoLogger.log(`\u{1F504} Connecting: ${this.fluxInstanceId}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
handleSocketError(error) {
|
|
829
|
+
PicoLogger.log(`\u274C Error: "${error.message}".`, this.fluxInstanceId);
|
|
830
|
+
}
|
|
761
831
|
/**
|
|
762
832
|
* Handles incoming WebSocket messages.
|
|
763
833
|
*/
|
|
@@ -1080,9 +1150,6 @@ var FluxClientData = class {
|
|
|
1080
1150
|
};
|
|
1081
1151
|
|
|
1082
1152
|
// packages/flux/agent/src/lib/flux-agent.class.ts
|
|
1083
|
-
globalThis.agentLoadCount ??= 0;
|
|
1084
|
-
globalThis.agentLoadCount++;
|
|
1085
|
-
console.log(`[flux-agent] Reloaded ${globalThis.agentLoadCount} time(s)`);
|
|
1086
1153
|
var DEFAULT_FLUX_DOMAIN = "https://mesh.persistica.io";
|
|
1087
1154
|
var FluxAgent = class {
|
|
1088
1155
|
constructor(networkId, options) {
|
|
@@ -1105,10 +1172,10 @@ var FluxAgent = class {
|
|
|
1105
1172
|
async connect(identification, clientUId) {
|
|
1106
1173
|
const domain = this.options?.domain ?? DEFAULT_FLUX_DOMAIN;
|
|
1107
1174
|
this.stateManager.emitNetworkState("authorizing");
|
|
1108
|
-
if (clientUId && !validateAgentUIDOrThrow(clientUId)) {
|
|
1109
|
-
throw new Error("Will never be thrown");
|
|
1110
|
-
}
|
|
1111
1175
|
try {
|
|
1176
|
+
if (clientUId && !validateAgentUIDOrThrow(clientUId)) {
|
|
1177
|
+
throw new Error("Will never be thrown");
|
|
1178
|
+
}
|
|
1112
1179
|
const ticket = await retryOrThrow(
|
|
1113
1180
|
async () => {
|
|
1114
1181
|
return authenticateAgentOrThrow(
|
|
@@ -1146,8 +1213,7 @@ var FluxAgent = class {
|
|
|
1146
1213
|
}
|
|
1147
1214
|
);
|
|
1148
1215
|
this.fluxClientData.updateWsConnection(this.fluxWebSocketConnection);
|
|
1149
|
-
|
|
1150
|
-
return fluxNetworkConnection;
|
|
1216
|
+
return await this.fluxWebSocketConnection.connectToNetwork();
|
|
1151
1217
|
} catch (error) {
|
|
1152
1218
|
this.stateManager.emitNetworkState("auth-error");
|
|
1153
1219
|
return Promise.reject(error);
|
package/package.json
CHANGED