doover-js 0.1.2 → 0.2.0
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/README.md +104 -10
- package/dist/apis/connections-api.d.ts +6 -0
- package/dist/apis/connections-api.js +8 -0
- package/dist/auth/auth-profile.d.ts +45 -0
- package/dist/auth/auth-profile.js +72 -0
- package/dist/auth/auth-store.d.ts +14 -0
- package/dist/auth/auth-store.js +2 -0
- package/dist/auth/build-auth.d.ts +37 -0
- package/dist/auth/build-auth.js +75 -0
- package/dist/auth/cookie-auth.d.ts +17 -0
- package/dist/auth/cookie-auth.js +29 -0
- package/dist/auth/doover-auth.d.ts +41 -0
- package/dist/auth/doover-auth.js +24 -0
- package/dist/auth/doover-token-auth.d.ts +35 -0
- package/dist/auth/doover-token-auth.js +178 -0
- package/dist/auth/errors.d.ts +3 -0
- package/dist/auth/errors.js +10 -0
- package/dist/auth/jwt.d.ts +6 -0
- package/dist/auth/jwt.js +29 -0
- package/dist/client/doover-client.d.ts +15 -0
- package/dist/client/doover-client.js +45 -4
- package/dist/client/singleton.d.ts +26 -0
- package/dist/client/singleton.js +59 -0
- package/dist/client/stats.d.ts +55 -0
- package/dist/client/stats.js +97 -0
- package/dist/gateway/gateway-client.d.ts +34 -8
- package/dist/gateway/gateway-client.js +147 -52
- package/dist/gateway/types.d.ts +0 -1
- package/dist/http/rest-client.d.ts +21 -3
- package/dist/http/rest-client.js +33 -2
- package/dist/index.d.ts +12 -0
- package/dist/index.js +23 -5
- package/dist/node/config-manager.d.ts +34 -0
- package/dist/node/config-manager.js +177 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +5 -0
- package/dist/react/context.d.ts +19 -0
- package/dist/react/context.js +28 -0
- package/dist/react/index.d.ts +24 -0
- package/dist/react/index.js +40 -0
- package/dist/react/sharedQueryClient.d.ts +21 -0
- package/dist/react/sharedQueryClient.js +39 -0
- package/dist/react/useAgentChannel.d.ts +7 -0
- package/dist/react/useAgentChannel.js +12 -0
- package/dist/react/useAgentConnections.d.ts +8 -0
- package/dist/react/useAgentConnections.js +24 -0
- package/dist/react/useChannelAggregate.d.ts +13 -0
- package/dist/react/useChannelAggregate.js +38 -0
- package/dist/react/useChannelMessages.d.ts +23 -0
- package/dist/react/useChannelMessages.js +56 -0
- package/dist/react/useChannelSubscription.d.ts +16 -0
- package/dist/react/useChannelSubscription.js +43 -0
- package/dist/react/useConnectionState.d.ts +21 -0
- package/dist/react/useConnectionState.js +43 -0
- package/dist/react/useMultiAgentAggregates.d.ts +22 -0
- package/dist/react/useMultiAgentAggregates.js +82 -0
- package/dist/react/useMultiAgentChannelMessages.d.ts +18 -0
- package/dist/react/useMultiAgentChannelMessages.js +78 -0
- package/dist/react/useSendMessage.d.ts +9 -0
- package/dist/react/useSendMessage.js +16 -0
- package/dist/react/useSendRpc.d.ts +61 -0
- package/dist/react/useSendRpc.js +158 -0
- package/dist/react/useTurnCredentials.d.ts +12 -0
- package/dist/react/useTurnCredentials.js +25 -0
- package/dist/react/useUpdateAggregate.d.ts +14 -0
- package/dist/react/useUpdateAggregate.js +25 -0
- package/dist/react/useUpdateMessage.d.ts +17 -0
- package/dist/react/useUpdateMessage.js +24 -0
- package/dist/test/apis.test.js +7 -1
- package/dist/test/auth.test.d.ts +1 -0
- package/dist/test/auth.test.js +502 -0
- package/dist/test/config-manager.test.d.ts +1 -0
- package/dist/test/config-manager.test.js +160 -0
- package/dist/test/doover-client.test.js +13 -0
- package/dist/test/doover-data-provider.test.js +155 -0
- package/dist/test/gateway-client.test.js +122 -8
- package/dist/test/react.test.d.ts +1 -0
- package/dist/test/react.test.js +353 -0
- package/dist/test/singleton.test.d.ts +1 -0
- package/dist/test/singleton.test.js +62 -0
- package/dist/types/common.d.ts +40 -0
- package/dist/viewer/doover-data-provider.d.ts +34 -5
- package/dist/viewer/doover-data-provider.js +96 -9
- package/package.json +33 -2
|
@@ -26,4 +26,17 @@ const helpers_1 = require("./helpers");
|
|
|
26
26
|
(0, chai_1.expect)(client.agents).to.exist;
|
|
27
27
|
(0, chai_1.expect)(client.gateway).to.exist;
|
|
28
28
|
});
|
|
29
|
+
(0, mocha_1.it)("shares a single GatewayClient between client.gateway and client.viewer.gateway", () => {
|
|
30
|
+
const client = new doover_client_1.DooverClient({
|
|
31
|
+
dataRestUrl: "https://api.example.com",
|
|
32
|
+
controlApiUrl: "https://control.example.com",
|
|
33
|
+
dataWssUrl: "wss://ws.example.com",
|
|
34
|
+
fetchImpl: (0, helpers_1.createFetchMock)(),
|
|
35
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
36
|
+
disableBrowserLifecycleHooks: true,
|
|
37
|
+
});
|
|
38
|
+
// Regression: these used to be separate GatewayClient instances,
|
|
39
|
+
// each opening its own WebSocket on connect().
|
|
40
|
+
(0, chai_1.expect)(client.gateway).to.equal(client.viewer.gateway);
|
|
41
|
+
});
|
|
29
42
|
});
|
|
@@ -104,6 +104,23 @@ const helpers_1 = require("./helpers");
|
|
|
104
104
|
(0, chai_1.expect)(messages?.map((message) => message.id)).to.deep.equal([oldId, newId]);
|
|
105
105
|
(0, chai_1.expect)(fetchMock.getCall(0).args[0]).to.equal("https://api.example.com/agents/a1/channels/c1/messages?before=cursor&limit=10");
|
|
106
106
|
});
|
|
107
|
+
(0, mocha_1.it)("forwards listMessages options (limit, after, field_name)", async () => {
|
|
108
|
+
const fetchMock = (0, helpers_1.createFetchMock)(() => (0, helpers_1.createJsonResponse)([]));
|
|
109
|
+
const provider = new doover_data_provider_1.DooverDataProvider({
|
|
110
|
+
dataRestUrl: "https://api.example.com",
|
|
111
|
+
controlApiUrl: "https://control.example.com",
|
|
112
|
+
dataWssUrl: "wss://ws.example.com",
|
|
113
|
+
fetchImpl: fetchMock,
|
|
114
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
115
|
+
});
|
|
116
|
+
await provider.getMessages({ agentId: "a1", channelName: "c1" }, { before: "b1", after: "a1", limit: 50, field_name: ["x", "y"] });
|
|
117
|
+
const url = fetchMock.getCall(0).args[0];
|
|
118
|
+
(0, chai_1.expect)(url).to.include("before=b1");
|
|
119
|
+
(0, chai_1.expect)(url).to.include("after=a1");
|
|
120
|
+
(0, chai_1.expect)(url).to.include("limit=50");
|
|
121
|
+
(0, chai_1.expect)(url).to.include("field_name=x");
|
|
122
|
+
(0, chai_1.expect)(url).to.include("field_name=y");
|
|
123
|
+
});
|
|
107
124
|
(0, mocha_1.it)("prefers channel aggregate data and falls back to the aggregate endpoint", async () => {
|
|
108
125
|
const fetchMock = (0, helpers_1.createFetchMock)((url) => {
|
|
109
126
|
if (url.endsWith("/channels/c1")) {
|
|
@@ -227,4 +244,142 @@ const helpers_1 = require("./helpers");
|
|
|
227
244
|
aggregatePath: "path/to/value",
|
|
228
245
|
});
|
|
229
246
|
});
|
|
247
|
+
(0, mocha_1.it)("sendRPC posts an rpc message, correlates the response by id, and resolves on success", async () => {
|
|
248
|
+
const rpcId = (0, snowflake_1.generateSnowflakeIdAtTime)(new Date("2026-01-01T00:00:00.000Z"));
|
|
249
|
+
const fetchMock = (0, helpers_1.createFetchMock)((url) => {
|
|
250
|
+
if (url.endsWith("/messages")) {
|
|
251
|
+
return (0, helpers_1.createJsonResponse)({
|
|
252
|
+
id: rpcId,
|
|
253
|
+
author_id: "u1",
|
|
254
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
255
|
+
data: {
|
|
256
|
+
type: "rpc",
|
|
257
|
+
method: "ping",
|
|
258
|
+
request: {},
|
|
259
|
+
status: { code: "sent" },
|
|
260
|
+
response: {},
|
|
261
|
+
},
|
|
262
|
+
attachments: [],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return (0, helpers_1.createJsonResponse)({});
|
|
266
|
+
});
|
|
267
|
+
const provider = new doover_data_provider_1.DooverDataProvider({
|
|
268
|
+
dataRestUrl: "https://api.example.com",
|
|
269
|
+
controlApiUrl: "https://control.example.com",
|
|
270
|
+
dataWssUrl: "wss://ws.example.com",
|
|
271
|
+
fetchImpl: fetchMock,
|
|
272
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
273
|
+
});
|
|
274
|
+
const statuses = [];
|
|
275
|
+
const promise = provider.sendRPC({ agentId: "a1", channelName: "c1" }, { method: "ping", request: {} }, { onStatus: (status) => statuses.push(status.code) });
|
|
276
|
+
// Let subscribe + POST resolve.
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
278
|
+
const ws = helpers_1.MockWebSocket.instances[0];
|
|
279
|
+
ws.open();
|
|
280
|
+
ws.receive({ op: 0, t: "Hello", d: {} });
|
|
281
|
+
ws.receive({
|
|
282
|
+
op: 0,
|
|
283
|
+
t: "Ready",
|
|
284
|
+
d: { session_id: "s1", session_token: "t1", subscriptions: [] },
|
|
285
|
+
});
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
287
|
+
// Progress updates.
|
|
288
|
+
ws.receive({
|
|
289
|
+
op: 0,
|
|
290
|
+
t: "MessageUpdate",
|
|
291
|
+
d: {
|
|
292
|
+
id: rpcId,
|
|
293
|
+
author_id: "u1",
|
|
294
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
295
|
+
data: {
|
|
296
|
+
type: "rpc",
|
|
297
|
+
method: "ping",
|
|
298
|
+
request: {},
|
|
299
|
+
status: { code: "acknowledged", message: { timestamp: 1 } },
|
|
300
|
+
response: {},
|
|
301
|
+
},
|
|
302
|
+
attachments: [],
|
|
303
|
+
request_data: {},
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
ws.receive({
|
|
307
|
+
op: 0,
|
|
308
|
+
t: "MessageUpdate",
|
|
309
|
+
d: {
|
|
310
|
+
id: rpcId,
|
|
311
|
+
author_id: "u1",
|
|
312
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
313
|
+
data: {
|
|
314
|
+
type: "rpc",
|
|
315
|
+
method: "ping",
|
|
316
|
+
request: {},
|
|
317
|
+
status: { code: "success" },
|
|
318
|
+
response: { pong: true },
|
|
319
|
+
},
|
|
320
|
+
attachments: [],
|
|
321
|
+
request_data: {},
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
await (0, chai_1.expect)(promise).to.eventually.deep.equal({ pong: true });
|
|
325
|
+
(0, chai_1.expect)(statuses).to.deep.equal(["acknowledged", "success"]);
|
|
326
|
+
});
|
|
327
|
+
(0, mocha_1.it)("sendRPC rejects with the status message on error", async () => {
|
|
328
|
+
const rpcId = (0, snowflake_1.generateSnowflakeIdAtTime)(new Date("2026-01-01T00:00:00.000Z"));
|
|
329
|
+
const fetchMock = (0, helpers_1.createFetchMock)((url) => {
|
|
330
|
+
if (url.endsWith("/messages")) {
|
|
331
|
+
return (0, helpers_1.createJsonResponse)({
|
|
332
|
+
id: rpcId,
|
|
333
|
+
author_id: "u1",
|
|
334
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
335
|
+
data: {
|
|
336
|
+
type: "rpc",
|
|
337
|
+
method: "ping",
|
|
338
|
+
request: {},
|
|
339
|
+
status: { code: "sent" },
|
|
340
|
+
response: {},
|
|
341
|
+
},
|
|
342
|
+
attachments: [],
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return (0, helpers_1.createJsonResponse)({});
|
|
346
|
+
});
|
|
347
|
+
const provider = new doover_data_provider_1.DooverDataProvider({
|
|
348
|
+
dataRestUrl: "https://api.example.com",
|
|
349
|
+
controlApiUrl: "https://control.example.com",
|
|
350
|
+
dataWssUrl: "wss://ws.example.com",
|
|
351
|
+
fetchImpl: fetchMock,
|
|
352
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
353
|
+
});
|
|
354
|
+
const promise = provider.sendRPC({ agentId: "a1", channelName: "c1" }, { method: "ping", request: {} });
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
356
|
+
const ws = helpers_1.MockWebSocket.instances[0];
|
|
357
|
+
ws.open();
|
|
358
|
+
ws.receive({ op: 0, t: "Hello", d: {} });
|
|
359
|
+
ws.receive({
|
|
360
|
+
op: 0,
|
|
361
|
+
t: "Ready",
|
|
362
|
+
d: { session_id: "s1", session_token: "t1", subscriptions: [] },
|
|
363
|
+
});
|
|
364
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
365
|
+
ws.receive({
|
|
366
|
+
op: 0,
|
|
367
|
+
t: "MessageUpdate",
|
|
368
|
+
d: {
|
|
369
|
+
id: rpcId,
|
|
370
|
+
author_id: "u1",
|
|
371
|
+
channel: { agent_id: "a1", name: "c1" },
|
|
372
|
+
data: {
|
|
373
|
+
type: "rpc",
|
|
374
|
+
method: "ping",
|
|
375
|
+
request: {},
|
|
376
|
+
status: { code: "error", message: "boom" },
|
|
377
|
+
response: {},
|
|
378
|
+
},
|
|
379
|
+
attachments: [],
|
|
380
|
+
request_data: {},
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
await (0, chai_1.expect)(promise).to.eventually.be.rejectedWith("boom");
|
|
384
|
+
});
|
|
230
385
|
});
|
|
@@ -203,16 +203,14 @@ const helpers_1 = require("./helpers");
|
|
|
203
203
|
(0, chai_1.expect)(unsubscribed.calledOnce).to.equal(true);
|
|
204
204
|
(0, chai_1.expect)(wssError.calledWith({ message: "boom" })).to.equal(true);
|
|
205
205
|
});
|
|
206
|
-
(0, mocha_1.it)("
|
|
206
|
+
(0, mocha_1.it)("handles session cancellation (op 3)", () => {
|
|
207
207
|
const client = new gateway_client_1.GatewayClient({
|
|
208
208
|
dataRestUrl: "https://api.example.com",
|
|
209
209
|
controlApiUrl: "https://control.example.com",
|
|
210
210
|
dataWssUrl: "wss://ws.example.com",
|
|
211
211
|
webSocketImpl: helpers_1.MockWebSocket,
|
|
212
212
|
});
|
|
213
|
-
const ack = sinon_1.default.spy();
|
|
214
213
|
const cancelled = sinon_1.default.spy();
|
|
215
|
-
client.on("heartbeatAck", ack);
|
|
216
214
|
client.on("sessionCancelled", cancelled);
|
|
217
215
|
client.connect();
|
|
218
216
|
const ws = helpers_1.MockWebSocket.instances[0];
|
|
@@ -223,13 +221,129 @@ const helpers_1 = require("./helpers");
|
|
|
223
221
|
t: "Ready",
|
|
224
222
|
d: { session_id: "s1", session_token: "token", subscriptions: [] },
|
|
225
223
|
});
|
|
226
|
-
sinon_1.default.clock.tick(20000);
|
|
227
|
-
(0, chai_1.expect)(JSON.parse(ws.sent[ws.sent.length - 1])).to.deep.equal({ op: 1, d: {} });
|
|
228
|
-
sinon_1.default.clock.tick(10);
|
|
229
|
-
ws.receive({ op: 2, d: {} });
|
|
230
|
-
(0, chai_1.expect)(ack.calledWith(10)).to.equal(true);
|
|
231
224
|
ws.receive({ op: 3, d: {} });
|
|
232
225
|
(0, chai_1.expect)(cancelled.calledOnce).to.equal(true);
|
|
233
226
|
(0, chai_1.expect)(client.getSession()).to.equal(null);
|
|
234
227
|
});
|
|
228
|
+
(0, mocha_1.it)("reconnects with exponential backoff + jitter on unexpected close", () => {
|
|
229
|
+
// Make jitter deterministic: Math.random() → 1 means "wait the full backoff window".
|
|
230
|
+
const randomStub = sinon_1.default.stub(Math, "random").returns(1);
|
|
231
|
+
const client = new gateway_client_1.GatewayClient({
|
|
232
|
+
dataRestUrl: "https://api.example.com",
|
|
233
|
+
controlApiUrl: "https://control.example.com",
|
|
234
|
+
dataWssUrl: "wss://ws.example.com",
|
|
235
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
236
|
+
});
|
|
237
|
+
client.connect();
|
|
238
|
+
helpers_1.MockWebSocket.instances[0].open();
|
|
239
|
+
helpers_1.MockWebSocket.instances[0].close(1006, "transport closed");
|
|
240
|
+
// 1st attempt: ~1s window
|
|
241
|
+
sinon_1.default.clock.tick(999);
|
|
242
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(1);
|
|
243
|
+
sinon_1.default.clock.tick(1);
|
|
244
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(2);
|
|
245
|
+
helpers_1.MockWebSocket.instances[1].open();
|
|
246
|
+
helpers_1.MockWebSocket.instances[1].close(1006, "transport closed");
|
|
247
|
+
// 2nd attempt: ~2s window
|
|
248
|
+
sinon_1.default.clock.tick(1999);
|
|
249
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(2);
|
|
250
|
+
sinon_1.default.clock.tick(1);
|
|
251
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(3);
|
|
252
|
+
// Successful Ready should reset the attempt counter.
|
|
253
|
+
helpers_1.MockWebSocket.instances[2].open();
|
|
254
|
+
helpers_1.MockWebSocket.instances[2].receive({ op: 0, t: "Hello", d: {} });
|
|
255
|
+
helpers_1.MockWebSocket.instances[2].receive({
|
|
256
|
+
op: 0,
|
|
257
|
+
t: "Ready",
|
|
258
|
+
d: { session_id: "s1", session_token: "token", subscriptions: [] },
|
|
259
|
+
});
|
|
260
|
+
helpers_1.MockWebSocket.instances[2].close(1006, "transport closed");
|
|
261
|
+
sinon_1.default.clock.tick(999);
|
|
262
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(3);
|
|
263
|
+
sinon_1.default.clock.tick(1);
|
|
264
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(4);
|
|
265
|
+
randomStub.restore();
|
|
266
|
+
});
|
|
267
|
+
(0, mocha_1.it)("reconnects immediately on visibilitychange → visible", () => {
|
|
268
|
+
const listeners = new Map();
|
|
269
|
+
const fakeDocument = {
|
|
270
|
+
visibilityState: "hidden",
|
|
271
|
+
addEventListener: (event, handler) => {
|
|
272
|
+
listeners.set(event, handler);
|
|
273
|
+
},
|
|
274
|
+
removeEventListener: (event) => {
|
|
275
|
+
listeners.delete(event);
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
Object.defineProperty(globalThis, "document", {
|
|
279
|
+
value: fakeDocument,
|
|
280
|
+
configurable: true,
|
|
281
|
+
writable: true,
|
|
282
|
+
});
|
|
283
|
+
sinon_1.default.stub(Math, "random").returns(1);
|
|
284
|
+
const client = new gateway_client_1.GatewayClient({
|
|
285
|
+
dataRestUrl: "https://api.example.com",
|
|
286
|
+
controlApiUrl: "https://control.example.com",
|
|
287
|
+
dataWssUrl: "wss://ws.example.com",
|
|
288
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
289
|
+
});
|
|
290
|
+
client.connect();
|
|
291
|
+
helpers_1.MockWebSocket.instances[0].open();
|
|
292
|
+
helpers_1.MockWebSocket.instances[0].close(1006, "transport closed");
|
|
293
|
+
// Pending backoff would wait up to 1s.
|
|
294
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(1);
|
|
295
|
+
// Tab returns to visible — bypass backoff, reconnect now.
|
|
296
|
+
fakeDocument.visibilityState = "visible";
|
|
297
|
+
listeners.get("visibilitychange")?.(new Event("visibilitychange"));
|
|
298
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(2);
|
|
299
|
+
// @ts-expect-error — cleanup test global
|
|
300
|
+
delete globalThis.document;
|
|
301
|
+
});
|
|
302
|
+
(0, mocha_1.it)("skips lifecycle listeners when disableBrowserLifecycleHooks is true", () => {
|
|
303
|
+
const addSpy = sinon_1.default.spy();
|
|
304
|
+
const fakeDocument = {
|
|
305
|
+
visibilityState: "visible",
|
|
306
|
+
addEventListener: addSpy,
|
|
307
|
+
removeEventListener: sinon_1.default.spy(),
|
|
308
|
+
};
|
|
309
|
+
Object.defineProperty(globalThis, "document", {
|
|
310
|
+
value: fakeDocument,
|
|
311
|
+
configurable: true,
|
|
312
|
+
writable: true,
|
|
313
|
+
});
|
|
314
|
+
new gateway_client_1.GatewayClient({
|
|
315
|
+
dataRestUrl: "https://api.example.com",
|
|
316
|
+
controlApiUrl: "https://control.example.com",
|
|
317
|
+
dataWssUrl: "wss://ws.example.com",
|
|
318
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
319
|
+
disableBrowserLifecycleHooks: true,
|
|
320
|
+
});
|
|
321
|
+
(0, chai_1.expect)(addSpy.called).to.equal(false);
|
|
322
|
+
// @ts-expect-error — cleanup test global
|
|
323
|
+
delete globalThis.document;
|
|
324
|
+
});
|
|
325
|
+
(0, mocha_1.it)("caps backoff at 30s and does not reconnect after explicit disconnect", () => {
|
|
326
|
+
const randomStub = sinon_1.default.stub(Math, "random").returns(1);
|
|
327
|
+
const client = new gateway_client_1.GatewayClient({
|
|
328
|
+
dataRestUrl: "https://api.example.com",
|
|
329
|
+
controlApiUrl: "https://control.example.com",
|
|
330
|
+
dataWssUrl: "wss://ws.example.com",
|
|
331
|
+
webSocketImpl: helpers_1.MockWebSocket,
|
|
332
|
+
});
|
|
333
|
+
client.connect();
|
|
334
|
+
// Force many failed attempts to push backoff past the cap.
|
|
335
|
+
for (let i = 0; i < 10; i += 1) {
|
|
336
|
+
const ws = helpers_1.MockWebSocket.instances[i];
|
|
337
|
+
ws.open();
|
|
338
|
+
ws.close(1006, "transport closed");
|
|
339
|
+
sinon_1.default.clock.tick(30000);
|
|
340
|
+
}
|
|
341
|
+
// After 10 attempts we should have 11 sockets, and the delay never exceeds 30s.
|
|
342
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(11);
|
|
343
|
+
// Explicit disconnect must not schedule a further reconnect.
|
|
344
|
+
client.disconnect();
|
|
345
|
+
sinon_1.default.clock.tick(60000);
|
|
346
|
+
(0, chai_1.expect)(helpers_1.MockWebSocket.instances.length).to.equal(11);
|
|
347
|
+
randomStub.restore();
|
|
348
|
+
});
|
|
235
349
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|