@owlmeans/client-socket 0.1.18-rc.5 → 0.1.18-rc.50

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 (65) hide show
  1. package/README.md +22 -16
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/client-socket/SKILL.md +173 -8
  4. package/build/consts.d.ts +8 -0
  5. package/build/consts.d.ts.map +1 -0
  6. package/build/consts.js +17 -0
  7. package/build/consts.js.map +1 -0
  8. package/build/consts.local.d.ts +3 -0
  9. package/build/consts.local.d.ts.map +1 -0
  10. package/build/consts.local.js +2 -0
  11. package/build/consts.local.js.map +1 -0
  12. package/build/helper/types.d.ts +22 -0
  13. package/build/helper/types.d.ts.map +1 -0
  14. package/build/helper/types.js +2 -0
  15. package/build/helper/types.js.map +1 -0
  16. package/build/helper.d.ts +8 -4
  17. package/build/helper.d.ts.map +1 -1
  18. package/build/helper.js +79 -49
  19. package/build/helper.js.map +1 -1
  20. package/build/hooks.d.ts +18 -0
  21. package/build/hooks.d.ts.map +1 -0
  22. package/build/hooks.js +66 -0
  23. package/build/hooks.js.map +1 -0
  24. package/build/index.d.ts +4 -0
  25. package/build/index.d.ts.map +1 -1
  26. package/build/index.js +3 -0
  27. package/build/index.js.map +1 -1
  28. package/build/status.d.ts +30 -0
  29. package/build/status.d.ts.map +1 -0
  30. package/build/status.js +151 -0
  31. package/build/status.js.map +1 -0
  32. package/build/types.d.ts +75 -0
  33. package/build/types.d.ts.map +1 -1
  34. package/build/utils/connection.d.ts +12 -3
  35. package/build/utils/connection.d.ts.map +1 -1
  36. package/build/utils/connection.js +264 -28
  37. package/build/utils/connection.js.map +1 -1
  38. package/build/utils/consts.local.d.ts +6 -0
  39. package/build/utils/consts.local.d.ts.map +1 -0
  40. package/build/utils/consts.local.js +6 -0
  41. package/build/utils/consts.local.js.map +1 -0
  42. package/build/utils/index.d.ts +1 -0
  43. package/build/utils/index.d.ts.map +1 -1
  44. package/build/utils/types.d.ts +31 -0
  45. package/build/utils/types.d.ts.map +1 -0
  46. package/build/utils/types.js +2 -0
  47. package/build/utils/types.js.map +1 -0
  48. package/package.json +15 -11
  49. package/src/consts.local.ts +3 -0
  50. package/src/consts.ts +20 -0
  51. package/src/helper/types.ts +20 -0
  52. package/src/helper.ts +95 -53
  53. package/src/hooks.ts +75 -0
  54. package/src/index.ts +4 -0
  55. package/src/status.ts +162 -0
  56. package/src/types.ts +84 -1
  57. package/src/utils/connection.ts +256 -33
  58. package/src/utils/consts.local.ts +5 -0
  59. package/src/utils/index.ts +1 -0
  60. package/src/utils/types.ts +33 -0
  61. package/tests/context.ts +20 -0
  62. package/tests/reconnect.spec.ts +372 -0
  63. package/tests/tsconfig.json +12 -0
  64. package/tsconfig.json +1 -1
  65. package/build/.gitkeep +0 -0
@@ -1,19 +1,272 @@
1
- import { createBasicConnection, MessageType } from '@owlmeans/socket';
2
- export const makeConnection = (conn, _context) => {
1
+ import { createBasicConnection, MessageType, SocketConnectionError, SocketSystemEvent, SOCKET_HEARTBEAT_TIMEOUT_CODE } from '@owlmeans/socket';
2
+ import { AuthenticationStage } from '@owlmeans/auth';
3
+ import { logger } from '@owlmeans/log';
4
+ import { TERMINAL_CLOSE_CODES } from './consts.local.js';
5
+ const log = logger('client-socket');
6
+ /**
7
+ * Build a `Connection` whose transport survives a dropped WebSocket.
8
+ *
9
+ * The connection MODEL (`createBasicConnection()`) is created exactly once and never replaced —
10
+ * every `observe`/`listen` a caller registered keeps firing across a reconnect, because only the
11
+ * underlying `WebSocket` is swapped underneath it. A server's own subscriptions do NOT survive a
12
+ * new TCP connection, so a caller whose server sets up state only after an in-band
13
+ * `authenticated` frame (as this platform's handlers do) must replay that frame itself on
14
+ * `SocketSystemEvent.Reconnected` — this carrier only re-opens the pipe.
15
+ */
16
+ export const makeConnection = (opts) => {
17
+ const { policy, retry, open, onStatus } = opts;
3
18
  const model = createBasicConnection();
19
+ let current = null;
20
+ let closedByClient = false;
21
+ let finished = false;
22
+ // Gave up after the budget, but still revivable — unlike `finished`, nothing here is final.
23
+ let lost = false;
24
+ let attempts = 0;
25
+ let outageStartedAt = null;
26
+ let outageBudget = policy.budget;
27
+ let stableTimer = null;
28
+ let retryTimer = null;
29
+ let heartbeatTimer = null;
30
+ // When the ping still waiting for an answer was sent, or null once anything came back.
31
+ let pingSentAt = null;
32
+ let resolveReady = () => void 0;
33
+ let rejectReady = () => void 0;
34
+ const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
35
+ let settled = false;
36
+ const settle = (error) => {
37
+ if (settled)
38
+ return;
39
+ settled = true;
40
+ if (error != null)
41
+ rejectReady(error);
42
+ else
43
+ resolveReady();
44
+ };
45
+ const emitSystem = async (event, payload) => {
46
+ const msg = { type: MessageType.System, event, payload };
47
+ model.prepare?.(msg);
48
+ await Promise.all(model.getListeners().map(async (listener) => {
49
+ try {
50
+ await listener(msg);
51
+ }
52
+ catch (error) {
53
+ log.error('Socket system listener failed', { error });
54
+ }
55
+ }));
56
+ };
57
+ const clearHeartbeat = () => {
58
+ if (heartbeatTimer != null) {
59
+ clearInterval(heartbeatTimer);
60
+ heartbeatTimer = null;
61
+ }
62
+ };
63
+ const clearTimers = () => {
64
+ if (retryTimer != null) {
65
+ clearTimeout(retryTimer);
66
+ retryTimer = null;
67
+ }
68
+ if (stableTimer != null) {
69
+ clearTimeout(stableTimer);
70
+ stableTimer = null;
71
+ }
72
+ clearHeartbeat();
73
+ };
74
+ const startHeartbeat = (socket) => {
75
+ pingSentAt = null;
76
+ heartbeatTimer = setInterval(() => {
77
+ if (socket.readyState !== WebSocket.OPEN)
78
+ return;
79
+ if (pingSentAt != null && Date.now() - pingSentAt > policy.pongTimeout) {
80
+ // Silent half-open TCP: the ping this socket owes an answer to went out long enough ago
81
+ // that any live peer would have replied — force the close its own stack would eventually
82
+ // notice on its own, minutes later.
83
+ socket.close(SOCKET_HEARTBEAT_TIMEOUT_CODE);
84
+ return;
85
+ }
86
+ // Measured from the PING rather than from the last frame: a hidden tab's timers are
87
+ // throttled to about one wake-up a minute, so "nothing has arrived for longer than a
88
+ // heartbeat interval" is what throttling looks like, not what a dead socket looks like —
89
+ // it used to kill a perfectly healthy backgrounded connection on every wake-up.
90
+ pingSentAt = Date.now();
91
+ socket.send(JSON.stringify({ type: 'ping' }));
92
+ }, policy.heartbeat);
93
+ };
94
+ const computeDelay = (attempt) => {
95
+ const raw = Math.min(policy.maxDelay, policy.minDelay * Math.pow(policy.factor, attempt - 1));
96
+ const span = raw * policy.jitter;
97
+ const jittered = raw + (Math.random() * 2 - 1) * span;
98
+ return Math.max(policy.minDelay, Math.min(policy.maxDelay, Math.round(jittered)));
99
+ };
100
+ const finish = async (code) => {
101
+ if (finished)
102
+ return;
103
+ finished = true;
104
+ closedByClient = true;
105
+ clearTimers();
106
+ onStatus?.('lost');
107
+ await emitSystem(SocketSystemEvent.Close, { code });
108
+ settle(new SocketConnectionError('lost'));
109
+ };
110
+ // No `Close` frame here: the connection stays revivable, and `close` means gone for good.
111
+ const giveUp = async () => {
112
+ lost = true;
113
+ clearTimers();
114
+ onStatus?.('lost');
115
+ await emitSystem(SocketSystemEvent.Lost, {});
116
+ settle(new SocketConnectionError('lost'));
117
+ };
118
+ const scheduleReconnect = () => {
119
+ if (outageStartedAt == null)
120
+ outageStartedAt = Date.now();
121
+ if (Date.now() - outageStartedAt >= outageBudget) {
122
+ void giveUp();
123
+ return;
124
+ }
125
+ attempts += 1;
126
+ const delay = computeDelay(attempts);
127
+ onStatus?.('reconnecting');
128
+ void emitSystem(SocketSystemEvent.Reconnecting, { attempt: attempts, delay });
129
+ retryTimer = setTimeout(() => { retryTimer = null; void attemptConnect(); }, delay);
130
+ };
131
+ const attemptConnect = async () => {
132
+ if (closedByClient)
133
+ return;
134
+ let socket;
135
+ try {
136
+ socket = await open();
137
+ }
138
+ catch {
139
+ if (retry)
140
+ scheduleReconnect();
141
+ else
142
+ void finish(1006);
143
+ return;
144
+ }
145
+ if (closedByClient) {
146
+ // Closed while this handshake was in flight — nobody is left to own the socket.
147
+ socket.close(1000);
148
+ return;
149
+ }
150
+ onOpened(socket);
151
+ };
152
+ const revive = () => {
153
+ if (finished)
154
+ return;
155
+ if (lost) {
156
+ lost = false;
157
+ attempts = 1;
158
+ outageStartedAt = Date.now();
159
+ outageBudget = policy.reviveBudget;
160
+ onStatus?.('reconnecting');
161
+ void emitSystem(SocketSystemEvent.Reconnecting, { attempt: attempts, delay: 0 });
162
+ void attemptConnect();
163
+ return;
164
+ }
165
+ // Still retrying: never give up sooner than a revived connection would — otherwise a retry of
166
+ // its lost siblings is undone moments later when this one's older budget runs out, even by an
167
+ // attempt already in flight — and attempt now rather than after the backoff.
168
+ if (current != null)
169
+ return;
170
+ const now = Date.now();
171
+ if (outageStartedAt != null && outageBudget - (now - outageStartedAt) < policy.reviveBudget) {
172
+ outageStartedAt = now;
173
+ outageBudget = policy.reviveBudget;
174
+ }
175
+ if (retryTimer == null)
176
+ return;
177
+ clearTimeout(retryTimer);
178
+ retryTimer = null;
179
+ void attemptConnect();
180
+ };
181
+ const onOpened = (socket) => {
182
+ current = socket;
183
+ const wasRetry = attempts > 0;
184
+ const receiveMessage = async (event) => {
185
+ // Any frame at all answers the outstanding ping — the point is liveness, not pongs.
186
+ pingSentAt = null;
187
+ if (typeof event.data === 'string') {
188
+ try {
189
+ if (JSON.parse(event.data)?.type === 'pong')
190
+ return;
191
+ }
192
+ catch {
193
+ // The shared parser below reports malformed JSON consistently.
194
+ }
195
+ }
196
+ await model.receive(event.data);
197
+ };
198
+ const messageHandler = (event) => {
199
+ void receiveMessage(event).catch(error => {
200
+ log.warn('WebSocket message rejected', { error });
201
+ socket.close(1008);
202
+ });
203
+ };
204
+ const errorHandler = () => {
205
+ // The close event that always follows carries the code; nothing actionable here beyond
206
+ // keeping the browser's own unhandled-error reporting quiet for it.
207
+ };
208
+ const closeHandler = async (event) => {
209
+ if (current !== socket)
210
+ return;
211
+ socket.removeEventListener('message', messageHandler);
212
+ socket.removeEventListener('close', closeHandler);
213
+ socket.removeEventListener('error', errorHandler);
214
+ clearHeartbeat();
215
+ current = null;
216
+ if (closedByClient) {
217
+ await emitSystem(SocketSystemEvent.Close, { code: event.code });
218
+ return;
219
+ }
220
+ if (!retry || TERMINAL_CLOSE_CODES.has(event.code)) {
221
+ await finish(event.code);
222
+ return;
223
+ }
224
+ if (model._authSequence != null) {
225
+ model._authSequence.reject(new SocketConnectionError('disconnected'));
226
+ model._authSequence = undefined;
227
+ }
228
+ model.stage = AuthenticationStage.Init;
229
+ await emitSystem(SocketSystemEvent.Disconnected, { code: event.code });
230
+ scheduleReconnect();
231
+ };
232
+ socket.addEventListener('message', messageHandler);
233
+ socket.addEventListener('close', closeHandler);
234
+ socket.addEventListener('error', errorHandler);
235
+ startHeartbeat(socket);
236
+ if (stableTimer != null)
237
+ clearTimeout(stableTimer);
238
+ stableTimer = setTimeout(() => {
239
+ attempts = 0;
240
+ outageStartedAt = null;
241
+ outageBudget = policy.budget;
242
+ }, policy.stableAfter);
243
+ onStatus?.('online');
244
+ settle();
245
+ if (wasRetry)
246
+ void emitSystem(SocketSystemEvent.Reconnected, { attempts });
247
+ };
4
248
  model.send = async (message) => {
5
- if (typeof message !== 'string') {
249
+ if (typeof message !== 'string')
6
250
  model.prepare?.(message);
7
- }
8
- if (conn.readyState === WebSocket.OPEN) {
9
- conn.send(typeof message === 'string' ? message : JSON.stringify(message));
251
+ if (current?.readyState === WebSocket.OPEN) {
252
+ current.send(typeof message === 'string' ? message : JSON.stringify(message));
10
253
  }
11
254
  };
12
255
  model.close = async () => {
13
- if (conn.readyState === WebSocket.OPEN || conn.readyState === WebSocket.CONNECTING) {
14
- conn.close();
256
+ if (finished)
257
+ return;
258
+ finished = true;
259
+ closedByClient = true;
260
+ clearTimers();
261
+ if (current != null && (current.readyState === WebSocket.OPEN || current.readyState === WebSocket.CONNECTING)) {
262
+ current.close(1000);
15
263
  }
16
- // @TODO Make sure it trigger close observers
264
+ else {
265
+ // No live socket to fire a close event from — mid-retry, or closed before the first open
266
+ // ever resolved — so report the same frame directly.
267
+ await emitSystem(SocketSystemEvent.Close, { code: 1000 });
268
+ }
269
+ settle(new SocketConnectionError('closed'));
17
270
  };
18
271
  model.authenticate = async (_stage, _payload) => {
19
272
  // @TODO Provide authentication flow logic here
@@ -24,24 +277,7 @@ export const makeConnection = (conn, _context) => {
24
277
  message.dt = message.dt ?? Date.now();
25
278
  return message;
26
279
  };
27
- const messageHandler = async (message) => {
28
- await model.receive(message.data);
29
- };
30
- const closeHandler = async (event) => {
31
- const msg = {
32
- type: MessageType.System,
33
- event: 'close',
34
- payload: { code: event.code }
35
- };
36
- if (model.prepare != null) {
37
- model.prepare(msg);
38
- }
39
- await Promise.all(model.getListeners().map(async (listener) => listener(msg)));
40
- conn.removeEventListener('message', messageHandler);
41
- conn.removeEventListener('close', closeHandler);
42
- };
43
- conn.addEventListener('message', messageHandler);
44
- conn.addEventListener('close', closeHandler);
45
- return model;
280
+ void attemptConnect();
281
+ return { connection: model, ready, revive };
46
282
  };
47
283
  //# sourceMappingURL=connection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAErE,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAe,EAAE,QAAW,EAChB,EAAE;IACd,MAAM,KAAK,GAAG,qBAAqB,EAAE,CAAA;IAErC,KAAK,CAAC,IAAI,GAAG,KAAK,EAAC,OAAO,EAAC,EAAE;QAC3B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAA;QAC1B,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC,CAAA;IAED,KAAK,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE;QACvB,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;YACnF,IAAI,CAAC,KAAK,EAAE,CAAA;QACd,CAAC;QACD,6CAA6C;IAC/C,CAAC,CAAA;IAED,KAAK,CAAC,YAAY,GAAG,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;QAC9C,+CAA+C;QAC/C,OAAO,EAAS,CAAA;IAClB,CAAC,CAAA;IAED,KAAK,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE;QACtC,wCAAwC;QACxC,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;QACrC,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;IAED,MAAM,cAAc,GAAG,KAAK,EAAE,OAAqB,EAAE,EAAE;QACrD,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAA;IACD,MAAM,YAAY,GAAG,KAAK,EAAE,KAAiB,EAAE,EAAE;QAC/C,MAAM,GAAG,GAA+B;YACtC,IAAI,EAAE,WAAW,CAAC,MAAM;YACxB,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;SAC9B,CAAA;QACD,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;YAC1B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACpB,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,KAAK,EAAC,QAAQ,EAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC5E,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;QACnD,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACjD,CAAC,CAAA;IACD,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;IAChD,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IAE5C,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** Close codes that mean "this connection is finished on purpose" — never worth retrying: a
2
+ * normal closure the SERVER initiated (1000, e.g. the protocol's own one-shot completion) and a
3
+ * policy violation (1008, e.g. the guard rejected the frame that opened it). A client-initiated
4
+ * close is handled separately, through `closedByClient`. */
5
+ export declare const TERMINAL_CLOSE_CODES: ReadonlySet<number>;
6
+ //# sourceMappingURL=consts.local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.d.ts","sourceRoot":"","sources":["../../src/utils/consts.local.ts"],"names":[],"mappings":"AAAA;;;6DAG6D;AAC7D,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAAyB,CAAA"}
@@ -0,0 +1,6 @@
1
+ /** Close codes that mean "this connection is finished on purpose" — never worth retrying: a
2
+ * normal closure the SERVER initiated (1000, e.g. the protocol's own one-shot completion) and a
3
+ * policy violation (1008, e.g. the guard rejected the frame that opened it). A client-initiated
4
+ * close is handled separately, through `closedByClient`. */
5
+ export const TERMINAL_CLOSE_CODES = new Set([1000, 1008]);
6
+ //# sourceMappingURL=consts.local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consts.local.js","sourceRoot":"","sources":["../../src/utils/consts.local.ts"],"names":[],"mappings":"AAAA;;;6DAG6D;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA"}
@@ -1,2 +1,3 @@
1
1
  export * from './connection.js';
2
+ export type * from './types.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAA;AAC/B,mBAAmB,YAAY,CAAA"}
@@ -0,0 +1,31 @@
1
+ import type { ReconnectPolicy, SocketConnectionState } from '../types.js';
2
+ import type { Connection } from '@owlmeans/socket';
3
+ export interface ConnectionOpener {
4
+ (): Promise<WebSocket>;
5
+ }
6
+ export interface ManagedConnectionOptions {
7
+ policy: ReconnectPolicy;
8
+ /** Whether a drop schedules a retry at all. `false` keeps the pre-reconnect-support
9
+ * behaviour — one attempt, report and stop — while the heartbeat/liveness check below still
10
+ * runs exactly as it always did. */
11
+ retry: boolean;
12
+ /** Opens a fresh socket for the first attempt and every retry — already reflecting whatever a
13
+ * caller's `beforeConnect` refreshed (a token, most often). Resolves once the socket is OPEN,
14
+ * rejects on a handshake failure. */
15
+ open: ConnectionOpener;
16
+ /** Told this connection's coarse health, if a caller wants to aggregate it — the status
17
+ * service `@owlmeans/web-panel`'s reload dialog reads. */
18
+ onStatus?: (state: SocketConnectionState) => void;
19
+ }
20
+ export interface ManagedConnection {
21
+ connection: Connection;
22
+ /** Settles once the first socket opens; rejects with `SocketConnectionError('lost')` once the
23
+ * retry budget elapses first (or the single attempt fails, with `retry: false`). */
24
+ ready: Promise<void>;
25
+ /** Restart retrying a connection that gave up after its budget: one attempt at once, then the
26
+ * usual backoff within `policy.reviveBudget`. On a connection still waiting out a backoff
27
+ * delay: attempt at once, and leave it at least `reviveBudget`. A no-op otherwise — a terminal
28
+ * close (client close, 1000/1008, `retry: false`) is never revived. */
29
+ revive: () => void;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,OAAO,CAAC,SAAS,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,eAAe,CAAA;IACvB;;yCAEqC;IACrC,KAAK,EAAE,OAAO,CAAA;IACd;;0CAEsC;IACtC,IAAI,EAAE,gBAAgB,CAAA;IACtB;+DAC2D;IAC3D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAA;CAClD;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,UAAU,CAAA;IACtB;yFACqF;IACrF,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IACpB;;;4EAGwE;IACxE,MAAM,EAAE,MAAM,IAAI,CAAA;CACnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/utils/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/client-socket",
3
- "version": "0.1.18-rc.5",
3
+ "version": "0.1.18-rc.50",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
8
8
  "dev": "sleep 132 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
- "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
10
11
  },
11
12
  "main": "build/index.js",
12
13
  "module": "build/index.js",
@@ -25,19 +26,22 @@
25
26
  },
26
27
  "devDependencies": {
27
28
  "@owlmeans/dep-config": "workspace:*",
28
- "@types/react": "^19.2.17",
29
+ "@types/bun": "^1.4.2",
30
+ "@types/react": "^19.3.0",
29
31
  "nodemon": "^3.1.14",
30
32
  "typescript": "^7.0.2"
31
33
  },
32
34
  "dependencies": {
33
- "@owlmeans/auth": "^0.1.18-rc.5",
34
- "@owlmeans/basic-envelope": "^0.1.18-rc.5",
35
- "@owlmeans/client": "^0.1.18-rc.5",
36
- "@owlmeans/client-context": "^0.1.18-rc.5",
37
- "@owlmeans/client-entrypoint": "^0.1.18-rc.5",
38
- "@owlmeans/context": "^0.1.18-rc.5",
39
- "@owlmeans/entrypoint": "^0.1.18-rc.5",
40
- "@owlmeans/socket": "^0.1.18-rc.5"
35
+ "@owlmeans/auth": "^0.1.18-rc.40",
36
+ "@owlmeans/basic-envelope": "^0.1.18-rc.47",
37
+ "@owlmeans/basic-ids": "^0.1.18-rc.38",
38
+ "@owlmeans/client": "^0.1.18-rc.52",
39
+ "@owlmeans/client-context": "^0.1.18-rc.49",
40
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.48",
41
+ "@owlmeans/context": "^0.1.18-rc.34",
42
+ "@owlmeans/entrypoint": "^0.1.18-rc.41",
43
+ "@owlmeans/log": "^0.1.18-rc.2",
44
+ "@owlmeans/socket": "^0.1.18-rc.41"
41
45
  },
42
46
  "publishConfig": {
43
47
  "access": "public"
@@ -0,0 +1,3 @@
1
+ import type { SocketConnectionState } from './types.js'
2
+
3
+ export const RANK: Record<SocketConnectionState, number> = { online: 0, reconnecting: 1, lost: 2 }
package/src/consts.ts ADDED
@@ -0,0 +1,20 @@
1
+
2
+ import type { ReconnectPolicy } from './types.js'
3
+
4
+ /**
5
+ * 0.2s → 3s geometric backoff, retried for up to 10 minutes before the carrier gives up and
6
+ * reports the connection lost. See the client-socket skill's "Disconnects" section.
7
+ */
8
+ export const DEFAULT_RECONNECT_POLICY: ReconnectPolicy = {
9
+ minDelay: 200,
10
+ maxDelay: 3_000,
11
+ factor: 2,
12
+ jitter: 0.1,
13
+ budget: 600_000,
14
+ reviveBudget: 15_000,
15
+ stableAfter: 10_000,
16
+ heartbeat: 30_000,
17
+ pongTimeout: 10_000,
18
+ }
19
+
20
+ export const SOCKET_STATUS = 'socket-status'
@@ -0,0 +1,20 @@
1
+ import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
2
+ import type { AbstractRequest } from '@owlmeans/entrypoint'
3
+ import type { Connection } from '@owlmeans/socket'
4
+ import type { ConnectOptions, Context, WsOptions } from '../types.js'
5
+
6
+ /** Opens managed sockets that restore themselves after a network failure. */
7
+ export interface SocketClientHelper {
8
+ /**
9
+ * Open a socket with no declared entrypoint behind it — the primitive `ws()` builds on, and what
10
+ * a test drives directly against a bare WebSocket server.
11
+ */
12
+ connect: (address: () => Promise<string> | string, ctx: Context, options?: ConnectOptions) => Promise<Connection>
13
+ /**
14
+ * Open a socket through a declared entrypoint protocol, restoring it on its own after a network
15
+ * failure — see the client-socket skill's "Disconnects" section for the full policy. Resolves
16
+ * once the first attempt opens; rejects with `SocketConnectionError('lost')` if the retry budget
17
+ * elapses before that ever happens (or the single attempt fails, with `reconnect: false`).
18
+ */
19
+ ws: (module: ClientEntrypoint<string>, request?: AbstractRequest<{ token?: string }>, options?: WsOptions) => Promise<Connection>
20
+ }