@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.
- package/index.js +74 -10
- 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
|
-
|
|
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
|
-
|
|
761
|
-
|
|
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");
|