@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 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?.domain ?? "http://localhost:5100");
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
- return new Promise((resolve) => {
594
- this.interceptPackageTypeMessages(
595
- "isReady",
596
- () => {
597
- this.stateManager.emitNetworkState("connected");
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
- if (this.webSocketClient) {
666
- this.webSocketClient.send(`${SUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
667
- return new Promise((resolve, reject) => {
668
- const cb = (message) => {
669
- this.removePackageTypeInterceptor(SUBSCRIBED_NETWORK_CHANNEL_NAME, cb);
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
- * Leaves a channel.
730
+ * Leave a channel.
687
731
  */
688
732
  async leaveChannel(channelName) {
689
- if (this.webSocketClient) {
690
- this.webSocketClient.send(`${UNSUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
691
- return Promise.resolve(void 0);
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
- const fluxNetworkConnection = await this.fluxWebSocketConnection.connectToNetwork();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persistica/flux-agent",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "Persistica Flux Agent",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -1,6 +1,3 @@
1
- declare global {
2
- var agentLoadCount: number | null | undefined;
3
- }
4
1
  import type { TMessageCallback } from '@flux/shared/ws';
5
2
  import type { FluxAgentNetworkConnection } from '@flux/shared/connection';
6
3
  export declare class FluxAgent {