@persistica/flux-agent 0.0.39 → 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 +87 -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
  */
@@ -780,20 +792,7 @@ var FluxWebSocketConnection = class {
780
792
  secretKey: this.options.secretKey,
781
793
  retries: this.options.retries ?? 1e4
782
794
  };
783
- const url = new URL(this.options.domain);
784
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
785
- url.searchParams.set("token", this.token);
786
- this.socket = new FluxWebSocketClientConnection(
787
- {
788
- url: url.toString(),
789
- // The URL carries a mesh-issued ticket that expires (15 minutes)
790
- // long before this connection does. Re-dialling it is pointless —
791
- // the mesh answers 'jwt expired' every time — so coming back is
792
- // `onReconnectCallback`'s job: it signs on again from scratch.
793
- autoReconnect: false,
794
- retries: this.options.retries
795
- }
796
- );
795
+ this.socket = this.createSocket(this.token);
797
796
  this.interceptPackageTypeMessages("isReady", this.readyInterceptor);
798
797
  }
799
798
  fluxInstanceId;
@@ -804,6 +803,9 @@ var FluxWebSocketConnection = class {
804
803
  // ****************************************************************************
805
804
  // *** State
806
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).
807
809
  socket;
808
810
  callbacks = /* @__PURE__ */ new Set();
809
811
  channelCallbacks = /* @__PURE__ */ new Map();
@@ -813,6 +815,9 @@ var FluxWebSocketConnection = class {
813
815
  connectPromise;
814
816
  connectPromiseResolver;
815
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;
816
821
  // ****************************************************************************
817
822
  // *** Reused socket callbacks
818
823
  // ****************************************************************************
@@ -824,6 +829,27 @@ var FluxWebSocketConnection = class {
824
829
  socketConnectFailedHandler = () => this.handleSocketConnectFailed();
825
830
  socketConnectingHandler = (retryAttempt) => this.handleSocketConnecting(retryAttempt);
826
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
+ }
827
853
  /**
828
854
  * Intercepts messages of a given package type.
829
855
  */
@@ -867,6 +893,23 @@ var FluxWebSocketConnection = class {
867
893
  this.socket.connect().catch(this.socketErrorHandler);
868
894
  return this.connectPromise;
869
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
+ }
870
913
  /**
871
914
  * Disconnects from the Flux platform.
872
915
  */
@@ -929,6 +972,8 @@ var FluxWebSocketConnection = class {
929
972
  if (this.webSocketClient) {
930
973
  const messageString = typeof message === "string" ? `s:${message}` : `o:${JSON.stringify(message)}`;
931
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.`);
932
977
  }
933
978
  }
934
979
  /**
@@ -966,6 +1011,7 @@ var FluxWebSocketConnection = class {
966
1011
  * Subscribes to the channel changes.
967
1012
  */
968
1013
  subscribeToChannelChanges() {
1014
+ this.subscribedToChannelChanges = true;
969
1015
  if (this.webSocketClient) {
970
1016
  this.webSocketClient.send(AUTHORITY_CHANNEL_SUBSCRIBE);
971
1017
  } else {
@@ -991,6 +1037,10 @@ var FluxWebSocketConnection = class {
991
1037
  resolveConnectedSocket() {
992
1038
  this.stateManager.emitNetworkState("connected");
993
1039
  this.webSocketClient = this.socket;
1040
+ this.channelStateManager.resubscribeJoinedChannels(this.socket);
1041
+ if (this.subscribedToChannelChanges) {
1042
+ this.socket.send(AUTHORITY_CHANNEL_SUBSCRIBE);
1043
+ }
994
1044
  const connectPromiseResolver = this.connectPromiseResolver;
995
1045
  this.connectPromiseResolver = void 0;
996
1046
  this.connectPromise = void 0;
@@ -1099,6 +1149,9 @@ var FluxWebSocketConnection = class {
1099
1149
  }
1100
1150
  return;
1101
1151
  }
1152
+ case SUBSCRIBED_NETWORK_CHANNEL_NAME: {
1153
+ return;
1154
+ }
1102
1155
  case RPC_REQUEST: {
1103
1156
  const payload = JSON.parse(message_.substring(message_.indexOf(":") + 1));
1104
1157
  void this.socket.handleMessage(payload, (data) => {
@@ -1534,22 +1587,26 @@ var FluxAgent = class {
1534
1587
  }
1535
1588
  }
1536
1589
  );
1537
- this.fluxWebSocketConnection = createWSConnection(
1538
- this.id,
1539
- ticket,
1540
- this.stateManager,
1541
- async () => {
1542
- await this.connect(
1543
- identification,
1544
- clientUId
1545
- );
1546
- },
1547
- {
1548
- ...this.options,
1549
- domain
1550
- }
1551
- );
1552
- 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
+ }
1553
1610
  return await this.fluxWebSocketConnection.connectToNetwork();
1554
1611
  } catch (error) {
1555
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.39",
3
+ "version": "0.0.40",
4
4
  "description": "Persistica Flux Agent",
5
5
  "type": "module",
6
6
  "keywords": [],