@persistica/flux-agent 0.0.36 → 0.0.38

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 +74 -10
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -98,6 +98,14 @@ var isClientId = (id) => {
98
98
  return regex.test(id);
99
99
  };
100
100
 
101
+ // libs/flux/shared/types/src/lib/channels/channel-capacity.utils.ts
102
+ var MAX_CHANNEL_MEMBERS_BY_SUBSCRIPTION_TYPE = {
103
+ free: 25,
104
+ medium: 500,
105
+ high: 1e5
106
+ };
107
+ var MAX_CHANNEL_MEMBERS = MAX_CHANNEL_MEMBERS_BY_SUBSCRIPTION_TYPE.high;
108
+
101
109
  // libs/flux/shared/ws/src/lib/rpc/rpc-server.class.ts
102
110
  var RPCServer = class {
103
111
  methods = /* @__PURE__ */ new Map();
@@ -142,6 +150,7 @@ var WebSocketClient = class extends RPCServer {
142
150
  open: [],
143
151
  message: [],
144
152
  close: [],
153
+ "connect-failed": [],
145
154
  error: [],
146
155
  connecting: []
147
156
  };
@@ -158,6 +167,10 @@ var WebSocketClient = class extends RPCServer {
158
167
  // The server proved it answers pings on this connection
159
168
  closeHandled = false;
160
169
  // Guards against running the close path twice (heartbeat + late onclose)
170
+ // The current `connect()` promise's reject, held so the close path can settle a
171
+ // socket that dies before it opens — `onclose` cancels the connection timeout,
172
+ // so without this the promise has nothing left to settle it (see #508).
173
+ pendingConnectReject;
161
174
  constructor(options) {
162
175
  super();
163
176
  this.options = {
@@ -175,6 +188,7 @@ var WebSocketClient = class extends RPCServer {
175
188
  connect() {
176
189
  return new Promise((resolve, reject) => {
177
190
  this.closeHandled = false;
191
+ this.pendingConnectReject = reject;
178
192
  this.emit("connecting", this.reconnectAttempts);
179
193
  const socket = new WebSocket(
180
194
  this.options.url
@@ -183,6 +197,7 @@ var WebSocketClient = class extends RPCServer {
183
197
  const timeout = setTimeout(() => {
184
198
  if (socket.readyState !== WebSocket.OPEN) {
185
199
  socket.close();
200
+ this.pendingConnectReject = void 0;
186
201
  reject(new Error("Connection timeout"));
187
202
  }
188
203
  }, this.options.connectionTimeout);
@@ -193,6 +208,7 @@ var WebSocketClient = class extends RPCServer {
193
208
  }
194
209
  this.reconnectAttempts = 0;
195
210
  this.isOpen = true;
211
+ this.pendingConnectReject = void 0;
196
212
  this.startHeartbeat();
197
213
  this.emit("open");
198
214
  resolve();
@@ -234,6 +250,7 @@ var WebSocketClient = class extends RPCServer {
234
250
  }
235
251
  this.closeHandled = true;
236
252
  this.stopHeartbeat();
253
+ const neverOpened = !this.isOpen;
237
254
  if (this.isOpen) {
238
255
  this.emit(
239
256
  "close",
@@ -241,7 +258,16 @@ var WebSocketClient = class extends RPCServer {
241
258
  );
242
259
  this.isOpen = false;
243
260
  }
244
- if (this.options.autoReconnect && (this.options.retries === void 0 || this.reconnectAttempts < this.options.retries)) {
261
+ const willRetry = this.options.autoReconnect === true && (this.options.retries === void 0 || this.reconnectAttempts < this.options.retries);
262
+ if (neverOpened) {
263
+ const rejectPendingConnect = this.pendingConnectReject;
264
+ this.pendingConnectReject = void 0;
265
+ rejectPendingConnect?.(new Error("WebSocket closed before the connection was established"));
266
+ if (!willRetry) {
267
+ this.emit("connect-failed");
268
+ }
269
+ }
270
+ if (willRetry) {
245
271
  const delay = wasKicked ? RECONNECTION_DELAY_ON_KICK_MS : Math.min(
246
272
  this.options.maxReconnectDelay ?? Number.POSITIVE_INFINITY,
247
273
  (this.options.reconnectDelay ?? 0) * 2 ** this.reconnectAttempts
@@ -323,6 +349,7 @@ var WebSocketClient = class extends RPCServer {
323
349
  this.eventListeners.open = [];
324
350
  this.eventListeners.message = [];
325
351
  this.eventListeners.close = [];
352
+ this.eventListeners["connect-failed"] = [];
326
353
  this.eventListeners.connecting = [];
327
354
  this.eventListeners.error = [];
328
355
  }
@@ -726,6 +753,7 @@ var ChannelStateManager = class {
726
753
  };
727
754
 
728
755
  // libs/flux/shared/connection/src/lib/flux-ws-connection.ts
756
+ var RECONNECT_DELAY_MS = 2e3;
729
757
  var createWSConnection = (id, ticket, stateManager, onReconnectCallback, options) => {
730
758
  return new FluxWebSocketConnection(
731
759
  id,
@@ -757,8 +785,11 @@ var FluxWebSocketConnection = class {
757
785
  this.socket = new FluxWebSocketClientConnection(
758
786
  {
759
787
  url: url.toString(),
760
- autoReconnect: true,
761
- reconnectDelay: 2e3,
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,
762
793
  retries: this.options.retries
763
794
  }
764
795
  );
@@ -780,21 +811,16 @@ var FluxWebSocketConnection = class {
780
811
  webSocketClient;
781
812
  connectPromise;
782
813
  connectPromiseResolver;
783
- first = true;
784
814
  channelStateManager = new ChannelStateManager();
785
815
  // ****************************************************************************
786
816
  // *** Reused socket callbacks
787
817
  // ****************************************************************************
788
818
  readyInterceptor = () => {
789
819
  this.resolveConnectedSocket();
790
- if (this.first) {
791
- this.first = false;
792
- return;
793
- }
794
- this.onReconnectCallback();
795
820
  };
796
821
  socketMessageHandler = this.handleMessage.bind(this);
797
822
  socketCloseHandler = (reason) => this.handleSocketClose(reason);
823
+ socketConnectFailedHandler = () => this.handleSocketConnectFailed();
798
824
  socketConnectingHandler = (retryAttempt) => this.handleSocketConnecting(retryAttempt);
799
825
  socketErrorHandler = (error) => this.handleSocketError(error);
800
826
  /**
@@ -836,7 +862,7 @@ var FluxWebSocketConnection = class {
836
862
  this.connectPromiseResolver = resolve;
837
863
  });
838
864
  this.socket.clearEventSubscribers();
839
- this.socket.on("message", this.socketMessageHandler).on("close", this.socketCloseHandler).on("connecting", this.socketConnectingHandler).on("error", this.socketErrorHandler);
865
+ this.socket.on("message", this.socketMessageHandler).on("close", this.socketCloseHandler).on("connect-failed", this.socketConnectFailedHandler).on("connecting", this.socketConnectingHandler).on("error", this.socketErrorHandler);
840
866
  this.socket.connect().catch(this.socketErrorHandler);
841
867
  return this.connectPromise;
842
868
  }
@@ -976,6 +1002,44 @@ var FluxWebSocketConnection = class {
976
1002
  this.fluxInstanceId
977
1003
  );
978
1004
  this.stateManager.emitNetworkState(reason === "kicked" ? "kicked" : "disconnected");
1005
+ this.scheduleSignOn(reason === "kicked" ? RECONNECTION_DELAY_ON_KICK_MS : RECONNECT_DELAY_MS);
1006
+ }
1007
+ /**
1008
+ * A socket that died before it ever opened. It emits no 'close' — there was no
1009
+ * open connection to lose — and the low-level client does not re-dial it, since
1010
+ * the ticket in its URL is single-use and expiring. Signing on again is the only
1011
+ * way back, and nothing else will start it: before this, a socket that failed
1012
+ * this way left the Authority up, silent and unregistered, until a human
1013
+ * restarted the process (#508).
1014
+ */
1015
+ handleSocketConnectFailed() {
1016
+ this.webSocketClient = void 0;
1017
+ this.connectPromiseResolver = void 0;
1018
+ this.connectPromise = void 0;
1019
+ PicoLogger.log("\u{1F50C}\u{1F534} The connection failed before it was established", this.fluxInstanceId);
1020
+ this.stateManager.emitNetworkState("disconnected");
1021
+ this.scheduleSignOn(RECONNECT_DELAY_MS);
1022
+ }
1023
+ /**
1024
+ * Signs on again from scratch: new handshake, new ticket, new connection.
1025
+ * Driven by the disconnect, not by a successful reconnect — with an expired
1026
+ * ticket there is no successful reconnect to hang this off.
1027
+ *
1028
+ * `disconnect()` does not reach here: it clears the socket's event subscribers,
1029
+ * so nothing is emitted and nothing signs on again.
1030
+ */
1031
+ scheduleSignOn(delayMs) {
1032
+ setTimeout(
1033
+ () => {
1034
+ Promise.resolve(this.onReconnectCallback()).catch((error) => {
1035
+ PicoLogger.error(
1036
+ `Failed to sign on again: ${error instanceof Error ? error.message : String(error)}`,
1037
+ this.fluxInstanceId
1038
+ );
1039
+ });
1040
+ },
1041
+ delayMs
1042
+ );
979
1043
  }
980
1044
  handleSocketConnecting(retryAttempt) {
981
1045
  this.stateManager.emitNetworkState("connecting");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@persistica/flux-agent",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "Persistica Flux Agent",
5
5
  "type": "module",
6
6
  "keywords": [],