@persistica/flux-agent 0.0.38 → 0.0.40

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.
Files changed (2) hide show
  1. package/index.js +89 -30
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -703,6 +703,18 @@ var ChannelStateManager = class {
703
703
  }
704
704
  return Promise.reject(new Error("Not connected"));
705
705
  }
706
+ /**
707
+ * Re-subscribes every joined channel on a fresh socket. The mesh ties
708
+ * channel membership to the socket, so after a re-sign-on nothing is joined
709
+ * even though the channels — and their use counts — live on here unchanged.
710
+ * The mesh answers each with a subscribe ack nobody awaits, which
711
+ * `FluxWebSocketConnection.handleMessage` swallows.
712
+ */
713
+ resubscribeJoinedChannels(webSocketClient) {
714
+ for (const channelName of this.joinedChannels.keys()) {
715
+ webSocketClient.send(`${SUBSCRIBE_NETWORK_CHANNEL_NAME}:${channelName}`);
716
+ }
717
+ }
706
718
  /**
707
719
  * Leave a channel.
708
720
  */
@@ -754,6 +766,7 @@ var ChannelStateManager = class {
754
766
 
755
767
  // libs/flux/shared/connection/src/lib/flux-ws-connection.ts
756
768
  var RECONNECT_DELAY_MS = 2e3;
769
+ var MAX_SIGN_ON_RETRY_DELAY_MS = 3e4;
757
770
  var createWSConnection = (id, ticket, stateManager, onReconnectCallback, options) => {
758
771
  return new FluxWebSocketConnection(
759
772
  id,
@@ -779,20 +792,7 @@ var FluxWebSocketConnection = class {
779
792
  secretKey: this.options.secretKey,
780
793
  retries: this.options.retries ?? 1e4
781
794
  };
782
- const url = new URL(this.options.domain);
783
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
784
- url.searchParams.set("token", this.token);
785
- this.socket = new FluxWebSocketClientConnection(
786
- {
787
- url: url.toString(),
788
- // The URL carries a mesh-issued ticket that expires (15 minutes)
789
- // long before this connection does. Re-dialling it is pointless —
790
- // the mesh answers 'jwt expired' every time — so coming back is
791
- // `onReconnectCallback`'s job: it signs on again from scratch.
792
- autoReconnect: false,
793
- retries: this.options.retries
794
- }
795
- );
795
+ this.socket = this.createSocket(this.token);
796
796
  this.interceptPackageTypeMessages("isReady", this.readyInterceptor);
797
797
  }
798
798
  fluxInstanceId;
@@ -803,6 +803,9 @@ var FluxWebSocketConnection = class {
803
803
  // ****************************************************************************
804
804
  // *** State
805
805
  // ****************************************************************************
806
+ // Mutable: a re-sign-on builds a fresh socket — the ticket is baked into its
807
+ // URL — but keeps this connection object, so every handle and listener
808
+ // registered against it survives (#536).
806
809
  socket;
807
810
  callbacks = /* @__PURE__ */ new Set();
808
811
  channelCallbacks = /* @__PURE__ */ new Map();
@@ -812,6 +815,9 @@ var FluxWebSocketConnection = class {
812
815
  connectPromise;
813
816
  connectPromiseResolver;
814
817
  channelStateManager = new ChannelStateManager();
818
+ // Whether the authority-side channel-change subscription was requested. The
819
+ // mesh ties it to the socket, so a re-signed-on connection has to ask again.
820
+ subscribedToChannelChanges = false;
815
821
  // ****************************************************************************
816
822
  // *** Reused socket callbacks
817
823
  // ****************************************************************************
@@ -823,6 +829,27 @@ var FluxWebSocketConnection = class {
823
829
  socketConnectFailedHandler = () => this.handleSocketConnectFailed();
824
830
  socketConnectingHandler = (retryAttempt) => this.handleSocketConnecting(retryAttempt);
825
831
  socketErrorHandler = (error) => this.handleSocketError(error);
832
+ /**
833
+ * Builds the low-level socket. The URL embeds the single-use, expiring
834
+ * ticket, so every sign-on — the first one and every one after a drop —
835
+ * needs its own socket. `reconnect()` swaps the fresh one in.
836
+ */
837
+ createSocket(ticket) {
838
+ const url = new URL(this.options.domain);
839
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
840
+ url.searchParams.set("token", ticket);
841
+ return new FluxWebSocketClientConnection(
842
+ {
843
+ url: url.toString(),
844
+ // The URL carries a mesh-issued ticket that expires (15 minutes)
845
+ // long before this connection does. Re-dialling it is pointless —
846
+ // the mesh answers 'jwt expired' every time — so coming back is
847
+ // `onReconnectCallback`'s job: it signs on again from scratch.
848
+ autoReconnect: false,
849
+ retries: this.options.retries
850
+ }
851
+ );
852
+ }
826
853
  /**
827
854
  * Intercepts messages of a given package type.
828
855
  */
@@ -866,6 +893,23 @@ var FluxWebSocketConnection = class {
866
893
  this.socket.connect().catch(this.socketErrorHandler);
867
894
  return this.connectPromise;
868
895
  }
896
+ /**
897
+ * Signs on again with a fresh ticket, keeping this connection object — and
898
+ * with it every channel handle and listener registered before the drop.
899
+ *
900
+ * A sign-on used to build a whole new connection, whose empty channel state
901
+ * left the old handles pointing at a dead socket: `publish()` was a silent
902
+ * no-op and `onPublish` listeners never fired again (#536).
903
+ *
904
+ * Only swaps the socket in; the caller's next `connect()` dials it.
905
+ */
906
+ reconnect(ticket) {
907
+ this.socket.close();
908
+ this.webSocketClient = void 0;
909
+ this.connectPromise = void 0;
910
+ this.connectPromiseResolver = void 0;
911
+ this.socket = this.createSocket(ticket);
912
+ }
869
913
  /**
870
914
  * Disconnects from the Flux platform.
871
915
  */
@@ -928,6 +972,8 @@ var FluxWebSocketConnection = class {
928
972
  if (this.webSocketClient) {
929
973
  const messageString = typeof message === "string" ? `s:${message}` : `o:${JSON.stringify(message)}`;
930
974
  this.webSocketClient.send(`${NETWORK_CHANNEL_PUBLISH}:${channelName}:${messageString}`);
975
+ } else {
976
+ console.warn(`Cannot publish to channel "${channelName}": the connection is down. Message dropped.`);
931
977
  }
932
978
  }
933
979
  /**
@@ -965,6 +1011,7 @@ var FluxWebSocketConnection = class {
965
1011
  * Subscribes to the channel changes.
966
1012
  */
967
1013
  subscribeToChannelChanges() {
1014
+ this.subscribedToChannelChanges = true;
968
1015
  if (this.webSocketClient) {
969
1016
  this.webSocketClient.send(AUTHORITY_CHANNEL_SUBSCRIBE);
970
1017
  } else {
@@ -990,6 +1037,10 @@ var FluxWebSocketConnection = class {
990
1037
  resolveConnectedSocket() {
991
1038
  this.stateManager.emitNetworkState("connected");
992
1039
  this.webSocketClient = this.socket;
1040
+ this.channelStateManager.resubscribeJoinedChannels(this.socket);
1041
+ if (this.subscribedToChannelChanges) {
1042
+ this.socket.send(AUTHORITY_CHANNEL_SUBSCRIBE);
1043
+ }
993
1044
  const connectPromiseResolver = this.connectPromiseResolver;
994
1045
  this.connectPromiseResolver = void 0;
995
1046
  this.connectPromise = void 0;
@@ -1036,6 +1087,7 @@ var FluxWebSocketConnection = class {
1036
1087
  `Failed to sign on again: ${error instanceof Error ? error.message : String(error)}`,
1037
1088
  this.fluxInstanceId
1038
1089
  );
1090
+ this.scheduleSignOn(Math.min(delayMs * 2, MAX_SIGN_ON_RETRY_DELAY_MS));
1039
1091
  });
1040
1092
  },
1041
1093
  delayMs
@@ -1097,6 +1149,9 @@ var FluxWebSocketConnection = class {
1097
1149
  }
1098
1150
  return;
1099
1151
  }
1152
+ case SUBSCRIBED_NETWORK_CHANNEL_NAME: {
1153
+ return;
1154
+ }
1100
1155
  case RPC_REQUEST: {
1101
1156
  const payload = JSON.parse(message_.substring(message_.indexOf(":") + 1));
1102
1157
  void this.socket.handleMessage(payload, (data) => {
@@ -1532,22 +1587,26 @@ var FluxAgent = class {
1532
1587
  }
1533
1588
  }
1534
1589
  );
1535
- this.fluxWebSocketConnection = createWSConnection(
1536
- this.id,
1537
- ticket,
1538
- this.stateManager,
1539
- async () => {
1540
- await this.connect(
1541
- identification,
1542
- clientUId
1543
- );
1544
- },
1545
- {
1546
- ...this.options,
1547
- domain
1548
- }
1549
- );
1550
- this.fluxClientData.updateWsConnection(this.fluxWebSocketConnection);
1590
+ if (this.fluxWebSocketConnection) {
1591
+ this.fluxWebSocketConnection.reconnect(ticket);
1592
+ } else {
1593
+ this.fluxWebSocketConnection = createWSConnection(
1594
+ this.id,
1595
+ ticket,
1596
+ this.stateManager,
1597
+ async () => {
1598
+ await this.connect(
1599
+ identification,
1600
+ clientUId
1601
+ );
1602
+ },
1603
+ {
1604
+ ...this.options,
1605
+ domain
1606
+ }
1607
+ );
1608
+ this.fluxClientData.updateWsConnection(this.fluxWebSocketConnection);
1609
+ }
1551
1610
  return await this.fluxWebSocketConnection.connectToNetwork();
1552
1611
  } catch (error) {
1553
1612
  this.stateManager.emitNetworkState("auth-error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persistica/flux-agent",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "description": "Persistica Flux Agent",
5
5
  "type": "module",
6
6
  "keywords": [],