@persistica/flux-agent 0.0.18 → 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 +174 -96
- package/package.json +1 -1
- package/src/lib/flux-agent.class.d.ts +0 -3
package/index.js
CHANGED
|
@@ -132,6 +132,7 @@ var RPCServer = class {
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
// libs/flux/shared/ws/src/lib/ws-client.ts
|
|
135
|
+
var RECONNECTION_DELAY_ON_KICK_MS = 18e5;
|
|
135
136
|
var WebSocketClient = class extends RPCServer {
|
|
136
137
|
options;
|
|
137
138
|
eventListeners = {
|
|
@@ -180,17 +181,25 @@ var WebSocketClient = class extends RPCServer {
|
|
|
180
181
|
this.ws.onmessage = (event) => {
|
|
181
182
|
this.emit("message", event.data);
|
|
182
183
|
};
|
|
183
|
-
this.ws.onclose = () => {
|
|
184
|
+
this.ws.onclose = (closeEvent) => {
|
|
185
|
+
const wasKicked = closeEvent.reason === "Kicked by process";
|
|
184
186
|
clearTimeout(timeout);
|
|
185
187
|
if (this.isOpen) {
|
|
186
|
-
this.emit(
|
|
188
|
+
this.emit(
|
|
189
|
+
"close",
|
|
190
|
+
wasKicked ? "kicked" : void 0
|
|
191
|
+
);
|
|
187
192
|
this.isOpen = false;
|
|
188
193
|
}
|
|
189
194
|
if (this.options.autoReconnect && (this.options.retries === void 0 || this.reconnectAttempts < this.options.retries)) {
|
|
195
|
+
const delay = wasKicked ? RECONNECTION_DELAY_ON_KICK_MS : Math.min(
|
|
196
|
+
this.options.reconnectDelay ?? Number.POSITIVE_INFINITY,
|
|
197
|
+
(this.options.reconnectDelay ?? 0) * (this.reconnectAttempts || 1)
|
|
198
|
+
);
|
|
190
199
|
setTimeout(() => {
|
|
191
200
|
this.reconnectAttempts++;
|
|
192
201
|
this.connect();
|
|
193
|
-
},
|
|
202
|
+
}, delay);
|
|
194
203
|
} else if (this.options.autoReconnect) {
|
|
195
204
|
this.emit("error", new Error("Connection failed: retries exhausted"));
|
|
196
205
|
}
|
|
@@ -444,33 +453,6 @@ var FluxAgentNetworkConnection = class {
|
|
|
444
453
|
}
|
|
445
454
|
};
|
|
446
455
|
|
|
447
|
-
// libs/flux/shared/connection/src/lib/flux-network-channel.class.ts
|
|
448
|
-
var FluxNetworkChannel = class {
|
|
449
|
-
constructor(channelName, _fluxWebSocketConnection) {
|
|
450
|
-
this.channelName = channelName;
|
|
451
|
-
this._fluxWebSocketConnection = _fluxWebSocketConnection;
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Broadcasts a message to the channel.
|
|
455
|
-
*/
|
|
456
|
-
publish(message) {
|
|
457
|
-
this._fluxWebSocketConnection.publish(
|
|
458
|
-
this.channelName,
|
|
459
|
-
message
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Listen to messages on this channel.
|
|
464
|
-
*/
|
|
465
|
-
onPublish(fn) {
|
|
466
|
-
this._fluxWebSocketConnection.onPublish(
|
|
467
|
-
this.channelName,
|
|
468
|
-
fn
|
|
469
|
-
// TODO
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
|
|
474
456
|
// libs/utils/logger/src/lib/utils-logger.ts
|
|
475
457
|
var hasOverlap = (a, b) => a && b ? [...a].some((item) => (Array.isArray(b) ? b : [b]).includes(item)) : false;
|
|
476
458
|
var PicoLogger = /* @__PURE__ */ (() => {
|
|
@@ -504,6 +486,91 @@ var PicoLogger = /* @__PURE__ */ (() => {
|
|
|
504
486
|
};
|
|
505
487
|
})();
|
|
506
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
|
+
|
|
507
574
|
// libs/flux/shared/connection/src/lib/flux-ws-connection.ts
|
|
508
575
|
var createWSConnection = (id, ticket, stateManager, onReconnectCallback, options) => {
|
|
509
576
|
return new FluxWebSocketConnection(
|
|
@@ -530,7 +597,7 @@ var FluxWebSocketConnection = class {
|
|
|
530
597
|
secretKey: this.options?.secretKey,
|
|
531
598
|
retries: this.options?.retries ?? 1e4
|
|
532
599
|
};
|
|
533
|
-
const url = new URL(this.options
|
|
600
|
+
const url = new URL(this.options.domain);
|
|
534
601
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
535
602
|
url.searchParams.set("token", this.token);
|
|
536
603
|
this.socket = new FluxWebSocketClientConnection(
|
|
@@ -541,14 +608,36 @@ var FluxWebSocketConnection = class {
|
|
|
541
608
|
retries: this.options.retries
|
|
542
609
|
}
|
|
543
610
|
);
|
|
611
|
+
this.interceptPackageTypeMessages("isReady", this.readyInterceptor);
|
|
544
612
|
}
|
|
613
|
+
// ****************************************************************************
|
|
614
|
+
// *** State
|
|
615
|
+
// ****************************************************************************
|
|
545
616
|
socket;
|
|
546
617
|
callbacks = /* @__PURE__ */ new Set();
|
|
547
618
|
channelCallbacks = /* @__PURE__ */ new Map();
|
|
548
|
-
// 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.
|
|
549
620
|
packageTypeInterceptorCallbacks = /* @__PURE__ */ new Map();
|
|
550
621
|
webSocketClient;
|
|
622
|
+
connectPromise;
|
|
623
|
+
connectPromiseResolver;
|
|
551
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);
|
|
552
641
|
/**
|
|
553
642
|
* Intercepts messages of a given package type.
|
|
554
643
|
*/
|
|
@@ -581,43 +670,24 @@ var FluxWebSocketConnection = class {
|
|
|
581
670
|
if (this.webSocketClient) {
|
|
582
671
|
return Promise.resolve(this.webSocketClient);
|
|
583
672
|
}
|
|
584
|
-
|
|
585
|
-
this.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
this.webSocketClient = this.socket;
|
|
590
|
-
resolve(this.socket);
|
|
591
|
-
if (this.first) {
|
|
592
|
-
this.first = false;
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
this.onReconnectCallback();
|
|
596
|
-
}
|
|
597
|
-
);
|
|
598
|
-
this.socket.clearEventSubscribers();
|
|
599
|
-
this.socket.on("message", this.handleMessage.bind(this)).on("close", () => {
|
|
600
|
-
this.webSocketClient = void 0;
|
|
601
|
-
this.stateManager.emitNetworkState("disconnected");
|
|
602
|
-
PicoLogger.log("\u{1F50C}\u{1F534} Disconnected", this.fluxInstanceId);
|
|
603
|
-
}).on("connecting", (retryAttempt) => {
|
|
604
|
-
this.stateManager.emitNetworkState("disconnected");
|
|
605
|
-
if (retryAttempt > 0) {
|
|
606
|
-
PicoLogger.log(`\u{1F504} Connecting attempt: #${retryAttempt} of ${this.options?.retries ?? "none"}`, this.fluxInstanceId);
|
|
607
|
-
} else {
|
|
608
|
-
PicoLogger.log(`\u{1F504} Connecting: ${this.fluxInstanceId}`);
|
|
609
|
-
}
|
|
610
|
-
}).on("error", (error) => {
|
|
611
|
-
PicoLogger.log(`\u274C Error: "${error.message}".`, this.fluxInstanceId);
|
|
612
|
-
});
|
|
613
|
-
this.stateManager.emitNetworkState("connecting");
|
|
614
|
-
this.socket.connect();
|
|
673
|
+
if (this.connectPromise) {
|
|
674
|
+
return this.connectPromise;
|
|
675
|
+
}
|
|
676
|
+
this.connectPromise = new Promise((resolve) => {
|
|
677
|
+
this.connectPromiseResolver = resolve;
|
|
615
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;
|
|
616
684
|
}
|
|
617
685
|
/**
|
|
618
686
|
* Disconnects from the Flux platform.
|
|
619
687
|
*/
|
|
620
688
|
disconnect() {
|
|
689
|
+
this.connectPromiseResolver = void 0;
|
|
690
|
+
this.connectPromise = void 0;
|
|
621
691
|
this.socket.close();
|
|
622
692
|
this.webSocketClient = void 0;
|
|
623
693
|
this.stateManager.emitNetworkState("disconnected");
|
|
@@ -650,35 +720,20 @@ var FluxWebSocketConnection = class {
|
|
|
650
720
|
* Join a channel.
|
|
651
721
|
*/
|
|
652
722
|
async joinChannel(channelName) {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
const receivedChannelName = message.substring(message.indexOf(":") + 1);
|
|
659
|
-
if (channelName !== receivedChannelName) {
|
|
660
|
-
reject(new Error(`Channel name mismatch: "${channelName}" !== "${receivedChannelName}"`));
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
resolve(new FluxNetworkChannel(channelName, this));
|
|
664
|
-
};
|
|
665
|
-
this.interceptPackageTypeMessages(
|
|
666
|
-
SUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
667
|
-
cb
|
|
668
|
-
);
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
return Promise.reject(new Error("Not connected"));
|
|
723
|
+
return this.channelStateManager.joinChannel(
|
|
724
|
+
channelName,
|
|
725
|
+
this,
|
|
726
|
+
this.webSocketClient
|
|
727
|
+
);
|
|
672
728
|
}
|
|
673
729
|
/**
|
|
674
|
-
*
|
|
730
|
+
* Leave a channel.
|
|
675
731
|
*/
|
|
676
732
|
async leaveChannel(channelName) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
return Promise.reject(new Error("Not connected"));
|
|
733
|
+
return this.channelStateManager.leaveChannel(
|
|
734
|
+
channelName,
|
|
735
|
+
this.webSocketClient
|
|
736
|
+
);
|
|
682
737
|
}
|
|
683
738
|
/**
|
|
684
739
|
* Publishes a message to a channel.
|
|
@@ -746,6 +801,33 @@ var FluxWebSocketConnection = class {
|
|
|
746
801
|
// ****************************************************************************
|
|
747
802
|
// *** Internal Helpers
|
|
748
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
|
+
}
|
|
749
831
|
/**
|
|
750
832
|
* Handles incoming WebSocket messages.
|
|
751
833
|
*/
|
|
@@ -1068,9 +1150,6 @@ var FluxClientData = class {
|
|
|
1068
1150
|
};
|
|
1069
1151
|
|
|
1070
1152
|
// packages/flux/agent/src/lib/flux-agent.class.ts
|
|
1071
|
-
globalThis.agentLoadCount ??= 0;
|
|
1072
|
-
globalThis.agentLoadCount++;
|
|
1073
|
-
console.log(`[flux-agent] Reloaded ${globalThis.agentLoadCount} time(s)`);
|
|
1074
1153
|
var DEFAULT_FLUX_DOMAIN = "https://mesh.persistica.io";
|
|
1075
1154
|
var FluxAgent = class {
|
|
1076
1155
|
constructor(networkId, options) {
|
|
@@ -1093,10 +1172,10 @@ var FluxAgent = class {
|
|
|
1093
1172
|
async connect(identification, clientUId) {
|
|
1094
1173
|
const domain = this.options?.domain ?? DEFAULT_FLUX_DOMAIN;
|
|
1095
1174
|
this.stateManager.emitNetworkState("authorizing");
|
|
1096
|
-
if (clientUId && !validateAgentUIDOrThrow(clientUId)) {
|
|
1097
|
-
throw new Error("Will never be thrown");
|
|
1098
|
-
}
|
|
1099
1175
|
try {
|
|
1176
|
+
if (clientUId && !validateAgentUIDOrThrow(clientUId)) {
|
|
1177
|
+
throw new Error("Will never be thrown");
|
|
1178
|
+
}
|
|
1100
1179
|
const ticket = await retryOrThrow(
|
|
1101
1180
|
async () => {
|
|
1102
1181
|
return authenticateAgentOrThrow(
|
|
@@ -1134,8 +1213,7 @@ var FluxAgent = class {
|
|
|
1134
1213
|
}
|
|
1135
1214
|
);
|
|
1136
1215
|
this.fluxClientData.updateWsConnection(this.fluxWebSocketConnection);
|
|
1137
|
-
|
|
1138
|
-
return fluxNetworkConnection;
|
|
1216
|
+
return await this.fluxWebSocketConnection.connectToNetwork();
|
|
1139
1217
|
} catch (error) {
|
|
1140
1218
|
this.stateManager.emitNetworkState("auth-error");
|
|
1141
1219
|
return Promise.reject(error);
|
package/package.json
CHANGED