@owlmeans/client-socket 0.1.18-rc.21 → 0.1.18-rc.23

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.
@@ -0,0 +1,94 @@
1
+ import { createService } from '@owlmeans/context';
2
+ import { useContext } from '@owlmeans/client';
3
+ import { useEffect, useState } from 'react';
4
+ export const SOCKET_STATUS = 'socket-status';
5
+ const RANK = { online: 0, reconnecting: 1, lost: 2 };
6
+ /**
7
+ * Aggregates the coarse health of every `ws()`/`useWs()` connection an app has open into one
8
+ * worst-of-all state. A page with several sockets (thinking stream, file watch, notifications…)
9
+ * still shows one dialog, not one per socket — so the aggregate, not any single connection, is
10
+ * what a UI like `SocketReloadDialog` reads.
11
+ */
12
+ export const createSocketStatusService = (alias = SOCKET_STATUS) => {
13
+ const states = new Map();
14
+ const listeners = new Set();
15
+ let current = 'online';
16
+ const recompute = () => {
17
+ let worst = 'online';
18
+ for (const state of states.values()) {
19
+ if (RANK[state] > RANK[worst])
20
+ worst = state;
21
+ }
22
+ if (worst !== current) {
23
+ current = worst;
24
+ listeners.forEach(listener => listener(current));
25
+ }
26
+ };
27
+ return createService(alias, {
28
+ report: (id, state) => { states.set(id, state); recompute(); },
29
+ release: (id) => { states.delete(id); recompute(); },
30
+ state: () => current,
31
+ subscribe: listener => {
32
+ listeners.add(listener);
33
+ return () => { listeners.delete(listener); };
34
+ },
35
+ }, service => async () => { service.initialized = true; });
36
+ };
37
+ /**
38
+ * Register the shared socket-status aggregator. Idempotent, and cheap enough for a host package
39
+ * (`@owlmeans/web-panel`) to call unconditionally on every context: every `ws()`/`useWs()`
40
+ * connection reports itself here if — and only if — the context carries this service, so an app
41
+ * that never calls this behaves exactly as it did before reconnect support existed.
42
+ */
43
+ export const appendSocketStatus = (ctx, alias = SOCKET_STATUS) => {
44
+ const _ctx = ctx;
45
+ if (!ctx.hasService(alias)) {
46
+ ctx.registerService(createSocketStatusService(alias));
47
+ }
48
+ _ctx.socketStatus = () => ctx.service(alias);
49
+ return _ctx;
50
+ };
51
+ const readSocketStatusService = (ctx) => {
52
+ try {
53
+ return typeof ctx.hasService === 'function' && ctx.hasService(SOCKET_STATUS)
54
+ ? ctx.socketStatus?.() ?? null : null;
55
+ }
56
+ catch {
57
+ // Registered but not yet run through the context's OWN `init()` — a component that renders
58
+ // as a sibling of the router (as `SocketReloadDialog` does) mounts before that finishes,
59
+ // since `context.configure().init()` runs from the router's effect, not before React's
60
+ // first commit. Treat it as "not ready yet" rather than crash the render.
61
+ return null;
62
+ }
63
+ };
64
+ /**
65
+ * The worst state across every live `ws()`/`useWs()` connection, or `'online'` if the app never
66
+ * registered the status service (`appendSocketStatus`) or has not finished initializing yet.
67
+ */
68
+ export const useSocketStatus = () => {
69
+ const ctx = useContext();
70
+ const [state, setState] = useState(() => readSocketStatusService(ctx)?.state() ?? 'online');
71
+ useEffect(() => {
72
+ let cancelled = false;
73
+ let unsubscribe = null;
74
+ const attach = () => {
75
+ const service = readSocketStatusService(ctx);
76
+ if (service == null)
77
+ return false;
78
+ setState(service.state());
79
+ unsubscribe = service.subscribe(next => { if (!cancelled)
80
+ setState(next); });
81
+ return true;
82
+ };
83
+ if (!attach()) {
84
+ void ctx.waitForInitialized?.().then(() => { if (!cancelled)
85
+ attach(); });
86
+ }
87
+ return () => {
88
+ cancelled = true;
89
+ unsubscribe?.();
90
+ };
91
+ }, [ctx]);
92
+ return state;
93
+ };
94
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -1,7 +1,71 @@
1
1
  import type { ClientConfig } from '@owlmeans/client-context';
2
2
  import type { ClientContext } from '@owlmeans/client';
3
+ import type { AbstractRequest } from '@owlmeans/entrypoint';
4
+ import type { InitializedService } from '@owlmeans/context';
3
5
  export interface Config extends ClientConfig {
6
+ socket?: SocketClientSettings;
4
7
  }
5
8
  export interface Context<C extends Config = Config> extends ClientContext<C> {
6
9
  }
10
+ /**
11
+ * How aggressively a dropped socket is retried, and when the carrier gives up on it.
12
+ *
13
+ * The delay grows geometrically from `minDelay` to `maxDelay` (±`jitter`, a fraction of the
14
+ * computed delay) and stays there. `budget` is measured from the connection's FIRST failed
15
+ * attempt in an outage — a socket that opens and drops again right away does not get a fresh
16
+ * budget — and `stableAfter` is how long a reopened socket must stay up before the attempt
17
+ * counter and the outage clock reset, so a flapping link is never read as "recovered" between
18
+ * two drops a second apart.
19
+ */
20
+ export interface ReconnectPolicy {
21
+ /** First retry delay, in ms. Default 200. */
22
+ minDelay: number;
23
+ /** Delay ceiling, in ms — the backoff never grows past this. Default 3000. */
24
+ maxDelay: number;
25
+ /** Geometric growth factor applied to the delay after every failed attempt. Default 2. */
26
+ factor: number;
27
+ /** Randomization applied to each computed delay, as a fraction of it. Default 0.1 (±10%). */
28
+ jitter: number;
29
+ /** Total time, in ms, the carrier keeps retrying before it reports the socket lost. Default
30
+ * 600000 (10 minutes). */
31
+ budget: number;
32
+ /** How long a reopened socket must stay open before the attempt count and the outage clock
33
+ * reset, in ms. Default 10000. */
34
+ stableAfter: number;
35
+ /** Heartbeat ping interval, in ms. Default 30000. */
36
+ heartbeat: number;
37
+ /** How long to wait for ANY inbound frame after a ping before treating the socket as dead and
38
+ * forcing it closed, in ms. Default 10000. */
39
+ pongTimeout: number;
40
+ }
41
+ export interface SocketClientSettings {
42
+ /** `false` disables reconnection outright — a dropped socket is reported gone and stays gone,
43
+ * the pre-reconnect-support behaviour. Anything else merges over the defaults. */
44
+ reconnect?: false | Partial<ReconnectPolicy>;
45
+ }
46
+ export interface ConnectOptions {
47
+ reconnect?: false | Partial<ReconnectPolicy>;
48
+ }
49
+ export interface WsOptions extends ConnectOptions {
50
+ /**
51
+ * Run before every connection attempt, the first one included, given the request that will be
52
+ * turned into the socket URL — where a caller refreshes a token or other query/param value
53
+ * that may have changed since the previous attempt.
54
+ */
55
+ beforeConnect?: (request: AbstractRequest<any>) => void | Promise<void>;
56
+ }
57
+ /** The coarse health of a socket connection — the worst of everything a status service tracks. */
58
+ export type SocketConnectionState = 'online' | 'reconnecting' | 'lost';
59
+ export interface SocketStatusService extends InitializedService {
60
+ /** Record this connection's current state under `id` — call again to update it. */
61
+ report: (id: string, state: SocketConnectionState) => void;
62
+ /** Stop tracking `id` (the connection closed on purpose). */
63
+ release: (id: string) => void;
64
+ /** The worst state across every tracked connection; `'online'` when none are tracked. */
65
+ state: () => SocketConnectionState;
66
+ subscribe: (listener: (state: SocketConnectionState) => void) => () => void;
67
+ }
68
+ export interface SocketStatusServiceAppend {
69
+ socketStatus: () => SocketStatusService;
70
+ }
7
71
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,MAAM,WAAW,MAAO,SAAQ,YAAY;CAAI;AAEhD,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAE3D,MAAM,WAAW,MAAO,SAAQ,YAAY;IAC1C,MAAM,CAAC,EAAE,oBAAoB,CAAA;CAC9B;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;CAAI;AAEhF;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAA;IAChB,0FAA0F;IAC1F,MAAM,EAAE,MAAM,CAAA;IACd,6FAA6F;IAC7F,MAAM,EAAE,MAAM,CAAA;IACd;+BAC2B;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd;uCACmC;IACnC,WAAW,EAAE,MAAM,CAAA;IACnB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB;mDAC+C;IAC/C,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC;uFACmF;IACnF,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,SAAU,SAAQ,cAAc;IAC/C;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxE;AAED,kGAAkG;AAClG,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,cAAc,GAAG,MAAM,CAAA;AAEtE,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,mFAAmF;IACnF,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAA;IAC1D,6DAA6D;IAC7D,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,yFAAyF;IACzF,KAAK,EAAE,MAAM,qBAAqB,CAAA;IAClC,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CAC5E;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,MAAM,mBAAmB,CAAA;CACxC"}
@@ -1,4 +1,37 @@
1
- import type { Config, Context } from '../types.js';
2
1
  import type { Connection } from '@owlmeans/socket';
3
- export declare const makeConnection: <C extends Config = Config, T extends Context<C> = Context<C>>(conn: WebSocket, _context: T) => Connection;
2
+ import type { ReconnectPolicy, SocketConnectionState } from '../types.js';
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
+ }
26
+ /**
27
+ * Build a `Connection` whose transport survives a dropped WebSocket.
28
+ *
29
+ * The connection MODEL (`createBasicConnection()`) is created exactly once and never replaced —
30
+ * every `observe`/`listen` a caller registered keeps firing across a reconnect, because only the
31
+ * underlying `WebSocket` is swapped underneath it. A server's own subscriptions do NOT survive a
32
+ * new TCP connection, so a caller whose server sets up state only after an in-band
33
+ * `authenticated` frame (as this platform's handlers do) must replay that frame itself on
34
+ * `SocketSystemEvent.Reconnected` — this carrier only re-opens the pipe.
35
+ */
36
+ export declare const makeConnection: (opts: ManagedConnectionOptions) => ManagedConnection;
4
37
  //# sourceMappingURL=connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,kBAAkB,CAAA;AAG5E,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,QACnF,SAAS,YAAY,CAAC,KAC3B,UAkDF,CAAA"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,kBAAkB,CAAA;AAM5E,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAQzE,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;CACrB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,SAAU,wBAAwB,KAAG,iBA6L/D,CAAA"}
@@ -1,19 +1,198 @@
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
+ /** Close codes that mean "this connection is finished on purpose" — never worth retrying: a
4
+ * normal closure the SERVER initiated (1000, e.g. the protocol's own one-shot completion) and a
5
+ * policy violation (1008, e.g. the guard rejected the frame that opened it). A client-initiated
6
+ * close is handled separately, through `closedByClient`. */
7
+ const TERMINAL_CLOSE_CODES = new Set([1000, 1008]);
8
+ /**
9
+ * Build a `Connection` whose transport survives a dropped WebSocket.
10
+ *
11
+ * The connection MODEL (`createBasicConnection()`) is created exactly once and never replaced —
12
+ * every `observe`/`listen` a caller registered keeps firing across a reconnect, because only the
13
+ * underlying `WebSocket` is swapped underneath it. A server's own subscriptions do NOT survive a
14
+ * new TCP connection, so a caller whose server sets up state only after an in-band
15
+ * `authenticated` frame (as this platform's handlers do) must replay that frame itself on
16
+ * `SocketSystemEvent.Reconnected` — this carrier only re-opens the pipe.
17
+ */
18
+ export const makeConnection = (opts) => {
19
+ const { policy, retry, open, onStatus } = opts;
3
20
  const model = createBasicConnection();
21
+ let current = null;
22
+ let closedByClient = false;
23
+ let finished = false;
24
+ let attempts = 0;
25
+ let outageStartedAt = null;
26
+ let stableTimer = null;
27
+ let retryTimer = null;
28
+ let heartbeatTimer = null;
29
+ let lastFrameAt = Date.now();
30
+ let resolveReady = () => void 0;
31
+ let rejectReady = () => void 0;
32
+ const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
33
+ let settled = false;
34
+ const settle = (error) => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ if (error != null)
39
+ rejectReady(error);
40
+ else
41
+ resolveReady();
42
+ };
43
+ const emitSystem = async (event, payload) => {
44
+ const msg = { type: MessageType.System, event, payload };
45
+ model.prepare?.(msg);
46
+ await Promise.all(model.getListeners().map(async (listener) => listener(msg)));
47
+ };
48
+ const clearHeartbeat = () => {
49
+ if (heartbeatTimer != null) {
50
+ clearInterval(heartbeatTimer);
51
+ heartbeatTimer = null;
52
+ }
53
+ };
54
+ const clearTimers = () => {
55
+ if (retryTimer != null) {
56
+ clearTimeout(retryTimer);
57
+ retryTimer = null;
58
+ }
59
+ if (stableTimer != null) {
60
+ clearTimeout(stableTimer);
61
+ stableTimer = null;
62
+ }
63
+ clearHeartbeat();
64
+ };
65
+ const startHeartbeat = (socket) => {
66
+ lastFrameAt = Date.now();
67
+ heartbeatTimer = setInterval(() => {
68
+ if (socket.readyState !== WebSocket.OPEN)
69
+ return;
70
+ if (Date.now() - lastFrameAt > policy.heartbeat + policy.pongTimeout) {
71
+ // Silent half-open TCP: nothing at all has arrived since well past a heartbeat interval
72
+ // plus its grace period — force the close this socket's own stack would eventually
73
+ // notice on its own, minutes later.
74
+ socket.close(SOCKET_HEARTBEAT_TIMEOUT_CODE);
75
+ return;
76
+ }
77
+ socket.send(JSON.stringify({ type: 'ping' }));
78
+ }, policy.heartbeat);
79
+ };
80
+ const computeDelay = (attempt) => {
81
+ const raw = Math.min(policy.maxDelay, policy.minDelay * Math.pow(policy.factor, attempt - 1));
82
+ const span = raw * policy.jitter;
83
+ const jittered = raw + (Math.random() * 2 - 1) * span;
84
+ return Math.max(policy.minDelay, Math.min(policy.maxDelay, Math.round(jittered)));
85
+ };
86
+ const finish = async (code) => {
87
+ if (finished)
88
+ return;
89
+ finished = true;
90
+ closedByClient = true;
91
+ clearTimers();
92
+ onStatus?.('lost');
93
+ await emitSystem(SocketSystemEvent.Close, { code });
94
+ settle(new SocketConnectionError('lost'));
95
+ };
96
+ const scheduleReconnect = (code) => {
97
+ if (outageStartedAt == null)
98
+ outageStartedAt = Date.now();
99
+ if (Date.now() - outageStartedAt >= policy.budget) {
100
+ void (async () => {
101
+ onStatus?.('lost');
102
+ await emitSystem(SocketSystemEvent.Lost, {});
103
+ await finish(code);
104
+ })();
105
+ return;
106
+ }
107
+ attempts += 1;
108
+ const delay = computeDelay(attempts);
109
+ onStatus?.('reconnecting');
110
+ void emitSystem(SocketSystemEvent.Reconnecting, { attempt: attempts, delay });
111
+ retryTimer = setTimeout(() => { retryTimer = null; void attemptConnect(); }, delay);
112
+ };
113
+ const attemptConnect = async () => {
114
+ if (closedByClient)
115
+ return;
116
+ try {
117
+ const socket = await open();
118
+ onOpened(socket);
119
+ }
120
+ catch {
121
+ if (retry)
122
+ scheduleReconnect(1006);
123
+ else
124
+ void finish(1006);
125
+ }
126
+ };
127
+ const onOpened = (socket) => {
128
+ current = socket;
129
+ const wasRetry = attempts > 0;
130
+ const messageHandler = async (event) => {
131
+ lastFrameAt = Date.now();
132
+ await model.receive(event.data);
133
+ };
134
+ const errorHandler = () => {
135
+ // The close event that always follows carries the code; nothing actionable here beyond
136
+ // keeping the browser's own unhandled-error reporting quiet for it.
137
+ };
138
+ const closeHandler = async (event) => {
139
+ if (current !== socket)
140
+ return;
141
+ socket.removeEventListener('message', messageHandler);
142
+ socket.removeEventListener('close', closeHandler);
143
+ socket.removeEventListener('error', errorHandler);
144
+ clearHeartbeat();
145
+ current = null;
146
+ if (closedByClient) {
147
+ await emitSystem(SocketSystemEvent.Close, { code: event.code });
148
+ return;
149
+ }
150
+ if (!retry || TERMINAL_CLOSE_CODES.has(event.code)) {
151
+ await finish(event.code);
152
+ return;
153
+ }
154
+ if (model._authSequence != null) {
155
+ model._authSequence.reject(new SocketConnectionError('disconnected'));
156
+ model._authSequence = undefined;
157
+ }
158
+ model.stage = AuthenticationStage.Init;
159
+ await emitSystem(SocketSystemEvent.Disconnected, { code: event.code });
160
+ scheduleReconnect(event.code);
161
+ };
162
+ socket.addEventListener('message', messageHandler);
163
+ socket.addEventListener('close', closeHandler);
164
+ socket.addEventListener('error', errorHandler);
165
+ startHeartbeat(socket);
166
+ if (stableTimer != null)
167
+ clearTimeout(stableTimer);
168
+ stableTimer = setTimeout(() => { attempts = 0; outageStartedAt = null; }, policy.stableAfter);
169
+ onStatus?.('online');
170
+ settle();
171
+ if (wasRetry)
172
+ void emitSystem(SocketSystemEvent.Reconnected, { attempts });
173
+ };
4
174
  model.send = async (message) => {
5
- if (typeof message !== 'string') {
175
+ if (typeof message !== 'string')
6
176
  model.prepare?.(message);
7
- }
8
- if (conn.readyState === WebSocket.OPEN) {
9
- conn.send(typeof message === 'string' ? message : JSON.stringify(message));
177
+ if (current?.readyState === WebSocket.OPEN) {
178
+ current.send(typeof message === 'string' ? message : JSON.stringify(message));
10
179
  }
11
180
  };
12
181
  model.close = async () => {
13
- if (conn.readyState === WebSocket.OPEN || conn.readyState === WebSocket.CONNECTING) {
14
- conn.close();
182
+ if (finished)
183
+ return;
184
+ finished = true;
185
+ closedByClient = true;
186
+ clearTimers();
187
+ if (current != null && (current.readyState === WebSocket.OPEN || current.readyState === WebSocket.CONNECTING)) {
188
+ current.close(1000);
15
189
  }
16
- // @TODO Make sure it trigger close observers
190
+ else {
191
+ // No live socket to fire a close event from — mid-retry, or closed before the first open
192
+ // ever resolved — so report the same frame directly.
193
+ await emitSystem(SocketSystemEvent.Close, { code: 1000 });
194
+ }
195
+ settle(new SocketConnectionError('closed'));
17
196
  };
18
197
  model.authenticate = async (_stage, _payload) => {
19
198
  // @TODO Provide authentication flow logic here
@@ -24,24 +203,7 @@ export const makeConnection = (conn, _context) => {
24
203
  message.dt = message.dt ?? Date.now();
25
204
  return message;
26
205
  };
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;
206
+ void attemptConnect();
207
+ return { connection: model, ready };
46
208
  };
47
209
  //# 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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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/client-socket",
3
- "version": "0.1.18-rc.21",
3
+ "version": "0.1.18-rc.23",
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,21 @@
25
26
  },
26
27
  "devDependencies": {
27
28
  "@owlmeans/dep-config": "workspace:*",
29
+ "@types/bun": "^1.4.0",
28
30
  "@types/react": "^19.2.17",
29
31
  "nodemon": "^3.1.14",
30
32
  "typescript": "^7.0.2"
31
33
  },
32
34
  "dependencies": {
33
- "@owlmeans/auth": "^0.1.18-rc.16",
34
- "@owlmeans/basic-envelope": "^0.1.18-rc.19",
35
- "@owlmeans/client": "^0.1.18-rc.23",
36
- "@owlmeans/client-context": "^0.1.18-rc.20",
37
- "@owlmeans/client-entrypoint": "^0.1.18-rc.20",
38
- "@owlmeans/context": "^0.1.18-rc.15",
39
- "@owlmeans/entrypoint": "^0.1.18-rc.18",
40
- "@owlmeans/socket": "^0.1.18-rc.16"
35
+ "@owlmeans/auth": "^0.1.18-rc.18",
36
+ "@owlmeans/basic-envelope": "^0.1.18-rc.21",
37
+ "@owlmeans/basic-ids": "^0.1.18-rc.18",
38
+ "@owlmeans/client": "^0.1.18-rc.25",
39
+ "@owlmeans/client-context": "^0.1.18-rc.22",
40
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.22",
41
+ "@owlmeans/context": "^0.1.18-rc.17",
42
+ "@owlmeans/entrypoint": "^0.1.18-rc.20",
43
+ "@owlmeans/socket": "^0.1.18-rc.18"
41
44
  },
42
45
  "publishConfig": {
43
46
  "access": "public"
package/src/consts.ts ADDED
@@ -0,0 +1,17 @@
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
+ stableAfter: 10_000,
15
+ heartbeat: 30_000,
16
+ pongTimeout: 10_000,
17
+ }