@owlmeans/client-socket 0.1.18-rc.48 → 0.1.18-rc.49

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 CHANGED
@@ -4,7 +4,7 @@ React hook and factory for WebSocket connections via OwlMeans entrypoint routing
4
4
 
5
5
  ## Overview
6
6
 
7
- - `ws(entrypoint, request?)` — creates a `Connection` from the entrypoint's URL and opens a WebSocket
7
+ - `socketClientHelper.ws(entrypoint, request?)` — creates a `Connection` from the entrypoint's URL and opens a WebSocket
8
8
  - `useWs(entrypoint, request?)` — React hook wrapping `ws()` with lifecycle management; pass a
9
9
  protocol-bound entrypoint directly
10
10
  - The returned `Connection` implements `@owlmeans/socket`'s `Connection` interface
@@ -12,7 +12,7 @@ React hook and factory for WebSocket connections via OwlMeans entrypoint routing
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/client-socket@^0.1.18-rc.48
15
+ bun add @owlmeans/client-socket@^0.1.18-rc.49
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -42,16 +42,16 @@ function ThinkingPanel({ storyId }: { storyId: string }) {
42
42
  Direct connection (non-hook):
43
43
 
44
44
  ```typescript
45
- import { ws } from '@owlmeans/client-socket'
45
+ import { socketClientHelper } from '@owlmeans/client-socket'
46
46
  import { appProtocols } from 'my-app-common'
47
47
 
48
48
  const wsEntrypoint = context.entrypoint(appProtocols.api.storyThinking)
49
- const connection = await ws(wsEntrypoint, { params: { id: storyId } })
49
+ const connection = await socketClientHelper.ws(wsEntrypoint, { params: { id: storyId } })
50
50
  ```
51
51
 
52
52
  ## API
53
53
 
54
- ### `ws(entrypoint, request?): Promise<Connection>`
54
+ ### `socketClientHelper.ws(entrypoint, request?): Promise<Connection>`
55
55
 
56
56
  Builds the entrypoint's URL, opens a WebSocket, and returns a `Connection` once the socket opens.
57
57
 
@@ -75,7 +75,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
75
75
  your project's skill store (`.agents/skills/`):
76
76
 
77
77
  ```sh
78
- npx @owlmeans/agent-skills@^0.1.18-rc.48
78
+ npx @owlmeans/agent-skills@^0.1.18-rc.49
79
79
  ```
80
80
 
81
81
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/client-socket",
4
- "version": "0.1.18-rc.48",
5
- "generatedAt": "2026-10-05T08:32:19.104Z",
4
+ "version": "0.1.18-rc.49",
5
+ "generatedAt": "2026-10-05T21:03:06.385Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/client-socket
9
9
 
10
10
  **Layer:** Client
11
- **Install:** `"@owlmeans/client-socket": "^0.1.18-rc.48"` in `dependencies`
11
+ **Install:** `"@owlmeans/client-socket": "^0.1.18-rc.49"` in `dependencies`
12
12
 
13
13
  The browser carrier for `@owlmeans/socket`. It supplies the four members the connection model
14
14
  leaves abstract — `send`, `close`, `prepare`, `authenticate` — and hands back a plain `Connection`,
@@ -0,0 +1,22 @@
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
+ /** Opens managed sockets that restore themselves after a network failure. */
6
+ export interface SocketClientHelper {
7
+ /**
8
+ * Open a socket with no declared entrypoint behind it — the primitive `ws()` builds on, and what
9
+ * a test drives directly against a bare WebSocket server.
10
+ */
11
+ connect: (address: () => Promise<string> | string, ctx: Context, options?: ConnectOptions) => Promise<Connection>;
12
+ /**
13
+ * Open a socket through a declared entrypoint protocol, restoring it on its own after a network
14
+ * failure — see the client-socket skill's "Disconnects" section for the full policy. Resolves
15
+ * once the first attempt opens; rejects with `SocketConnectionError('lost')` if the retry budget
16
+ * elapses before that ever happens (or the single attempt fails, with `reconnect: false`).
17
+ */
18
+ ws: (module: ClientEntrypoint<string>, request?: AbstractRequest<{
19
+ token?: string;
20
+ }>, options?: WsOptions) => Promise<Connection>;
21
+ }
22
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/helper/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAErE,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;IACjH;;;;;OAKG;IACH,EAAE,EAAE,CAAC,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,OAAO,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;CAClI"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/helper/types.ts"],"names":[],"mappings":""}
package/build/helper.d.ts CHANGED
@@ -1,33 +1,12 @@
1
1
  import { type ClientEntrypoint } from '@owlmeans/client-entrypoint';
2
2
  import type { AbstractRequest } from '@owlmeans/entrypoint';
3
3
  import { type Connection } from '@owlmeans/socket';
4
- import { type EntrypointReference } from '@owlmeans/context';
5
- import type { Context, ConnectOptions, WsOptions } from './types.js';
6
- /**
7
- * Open a socket with no declared entrypoint behind it — the primitive `ws()` builds on, and what
8
- * a test drives directly against a bare WebSocket server.
9
- */
10
- export declare const connect: (address: () => Promise<string> | string, ctx: Context, options?: ConnectOptions) => Promise<Connection>;
11
- /**
12
- * Open a socket through a declared entrypoint protocol, restoring it on its own after a network
13
- * failure — see the client-socket skill's "Disconnects" section for the full policy. Resolves
14
- * once the first attempt opens; rejects with `SocketConnectionError('lost')` if the retry budget
15
- * elapses before that ever happens (or the single attempt fails, with `reconnect: false`).
16
- */
4
+ import type { WsOptions } from './types.js';
5
+ import type { SocketClientHelper } from './helper/types.js';
6
+ export declare const createSocketClientHelper: () => SocketClientHelper;
7
+ export declare const socketClientHelper: SocketClientHelper;
8
+ /** @deprecated compat:factory-refactor — use `socketClientHelper.ws(…)` */
17
9
  export declare const ws: (module: ClientEntrypoint<string>, request?: AbstractRequest<{
18
10
  token?: string;
19
11
  }>, options?: WsOptions) => Promise<Connection>;
20
- /**
21
- * Open a socket through a declared entrypoint protocol.
22
- *
23
- * String aliases remain an adapter input for dynamically addressed integrations, while
24
- * application callers pass their immutable protocol declaration and receive its bound client
25
- * entrypoint here at the transport boundary.
26
- *
27
- * Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
28
- * and again once the retry budget elapses without one ever opening — dialed afresh on the next
29
- * `SocketStatusService.retry()`. A screen that must tell those two apart reads
30
- * `useSocketStatus()` (from `./status.js`) alongside this.
31
- */
32
- export declare const useWs: (module: EntrypointReference | string, request?: Partial<AbstractRequest<any>>, options?: WsOptions) => Connection | null;
33
12
  //# sourceMappingURL=helper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAkB,MAAM,6BAA6B,CAAA;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,kBAAkB,CAAA;AAEzE,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,KAAK,EAAU,OAAO,EAAE,cAAc,EAA8C,SAAS,EAAE,MAAM,YAAY,CAAA;AA4ExH;;;GAGG;AACH,eAAO,MAAM,OAAO,YACT,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,OAAO,OAAO,YAAY,cAAc,KAC9E,OAAO,CAAC,UAAU,CAGpB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,EAAE,WACL,gBAAgB,CAAC,MAAM,CAAC,YAAY,eAAe,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,YAAY,SAAS,KACnG,OAAO,CAAC,UAAU,CAUpB,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,WACR,mBAAmB,GAAG,MAAM,YAAY,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,YAAY,SAAS,KACjG,UAAU,GAAG,IAiDf,CAAA"}
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAuB,MAAM,6BAA6B,CAAA;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,kBAAkB,CAAA;AAGzE,OAAO,KAAK,EAA+E,SAAS,EAAE,MAAM,YAAY,CAAA;AACxH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAK3D,eAAO,MAAM,wBAAwB,QAAO,kBA2F3C,CAAA;AAED,eAAO,MAAM,kBAAkB,oBAA6B,CAAA;AAE5D,2EAA2E;AAC3E,eAAO,MAAM,EAAE,WACL,gBAAgB,CAAC,MAAM,CAAC,YAAY,eAAe,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,YAAY,SAAS,KACnG,OAAO,CAAC,UAAU,CAAoD,CAAA"}
package/build/helper.js CHANGED
@@ -1,150 +1,84 @@
1
- import { provideRequest } from '@owlmeans/client-entrypoint';
1
+ import { clientRequestHelper } from '@owlmeans/client-entrypoint';
2
2
  import { SocketConnectionError } from '@owlmeans/socket';
3
3
  import { makeConnection } from './utils/connection.js';
4
4
  import { assertContext } from '@owlmeans/context';
5
- import { useContext, useValue } from '@owlmeans/client';
6
- import { AUTH_QUERY } from '@owlmeans/auth';
7
- import { entrypointUrl } from '@owlmeans/client-entrypoint/utils';
8
- import { useEffect, useMemo, useState } from 'react';
5
+ import { makeApiCallHelper } from '@owlmeans/client-entrypoint/utils';
9
6
  import { createIdOfLength } from '@owlmeans/basic-ids';
10
7
  import { DEFAULT_RECONNECT_POLICY, SOCKET_STATUS } from './consts.js';
11
- const resolvePolicy = (ctx, options) => {
12
- const requested = options?.reconnect !== undefined ? options.reconnect : ctx.cfg.socket?.reconnect;
13
- const retry = requested !== false;
14
- const overrides = requested === false || requested == null ? {} : requested;
15
- return { policy: { ...DEFAULT_RECONNECT_POLICY, ...overrides }, retry };
16
- };
17
- const openRawSocket = async (url) => {
18
- const socket = new WebSocket(url);
19
- return await new Promise((resolve, reject) => {
20
- const cleanup = () => {
21
- socket.removeEventListener('open', onOpen);
22
- socket.removeEventListener('error', onFail);
23
- socket.removeEventListener('close', onFail);
24
- };
25
- const onOpen = () => { cleanup(); resolve(socket); };
26
- const onFail = () => { cleanup(); reject(new SocketConnectionError('handshake')); };
27
- socket.addEventListener('open', onOpen);
28
- socket.addEventListener('error', onFail);
29
- socket.addEventListener('close', onFail);
30
- });
31
- };
32
- const establish = async (ctx, policy, retry, open) => {
33
- const statusId = createIdOfLength(8);
34
- const status = ctx.hasService(SOCKET_STATUS)
35
- ? ctx.socketStatus() : null;
36
- let opened = false;
37
- let lost = false;
38
- const managed = makeConnection({
39
- policy, retry, open, onStatus: state => {
40
- lost = state === 'lost';
41
- status?.report(statusId, state, state === 'online' ? undefined : revive);
42
- }
43
- });
44
- const { connection, ready } = managed;
45
- const baseClose = connection.close;
46
- connection.close = async () => {
47
- await baseClose();
48
- status?.release(statusId);
8
+ export const createSocketClientHelper = () => {
9
+ const resolvePolicy = (ctx, options) => {
10
+ const requested = options?.reconnect !== undefined ? options.reconnect : ctx.cfg.socket?.reconnect;
11
+ const retry = requested !== false;
12
+ const overrides = requested === false || requested == null ? {} : requested;
13
+ return { policy: { ...DEFAULT_RECONNECT_POLICY, ...overrides }, retry };
49
14
  };
50
- const revive = () => {
51
- if (opened || !lost) {
52
- managed.revive();
53
- return;
54
- }
55
- // Never opened, so `ws()` already rejected and nobody holds this connection to hand a revived
56
- // socket to: drop it for good and leave re-dialing to its owner (`useWs` does, on `onRetry`).
57
- void baseClose();
58
- status?.release(statusId);
15
+ const openRawSocket = async (url) => {
16
+ const socket = new WebSocket(url);
17
+ return await new Promise((resolve, reject) => {
18
+ const cleanup = () => {
19
+ socket.removeEventListener('open', onOpen);
20
+ socket.removeEventListener('error', onFail);
21
+ socket.removeEventListener('close', onFail);
22
+ };
23
+ const onOpen = () => { cleanup(); resolve(socket); };
24
+ const onFail = () => { cleanup(); reject(new SocketConnectionError('handshake')); };
25
+ socket.addEventListener('open', onOpen);
26
+ socket.addEventListener('error', onFail);
27
+ socket.addEventListener('close', onFail);
28
+ });
59
29
  };
60
- // A rejection leaves this id reporting `'lost'` rather than releasing it — there is no
61
- // `Connection` to hand back for a caller to close, and the aggregate is what puts
62
- // `SocketReloadDialog` up. It is released by `revive` above, on the next `retry()`.
63
- await ready;
64
- opened = true;
65
- return connection;
66
- };
67
- /**
68
- * Open a socket with no declared entrypoint behind it — the primitive `ws()` builds on, and what
69
- * a test drives directly against a bare WebSocket server.
70
- */
71
- export const connect = async (address, ctx, options) => {
72
- const { policy, retry } = resolvePolicy(ctx, options);
73
- return await establish(ctx, policy, retry, async () => openRawSocket(await address()));
74
- };
75
- /**
76
- * Open a socket through a declared entrypoint protocol, restoring it on its own after a network
77
- * failure — see the client-socket skill's "Disconnects" section for the full policy. Resolves
78
- * once the first attempt opens; rejects with `SocketConnectionError('lost')` if the retry budget
79
- * elapses before that ever happens (or the single attempt fails, with `reconnect: false`).
80
- */
81
- export const ws = async (module, request, options) => {
82
- const ctx = assertContext(module.ctx, 'client-ws');
83
- request = request ?? provideRequest(module.alias, module.path());
84
- const { policy, retry } = resolvePolicy(ctx, options);
85
- return await establish(ctx, policy, retry, async () => {
86
- await options?.beforeConnect?.(request);
87
- const url = await entrypointUrl({ ref: module }, request);
88
- return await openRawSocket(url);
89
- });
90
- };
91
- /**
92
- * Open a socket through a declared entrypoint protocol.
93
- *
94
- * String aliases remain an adapter input for dynamically addressed integrations, while
95
- * application callers pass their immutable protocol declaration and receive its bound client
96
- * entrypoint here at the transport boundary.
97
- *
98
- * Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
99
- * and again once the retry budget elapses without one ever opening — dialed afresh on the next
100
- * `SocketStatusService.retry()`. A screen that must tell those two apart reads
101
- * `useSocketStatus()` (from `./status.js`) alongside this.
102
- */
103
- export const useWs = (module, request, options) => {
104
- const ctx = useContext();
105
- const mod = useMemo(() => ctx.entrypoint(module), [module]);
106
- const [failed, setFailed] = useState(false);
107
- const [dial, setDial] = useState(0);
108
- const connection = useValue(async (cancel) => {
109
- const _request = provideRequest(mod.alias, mod.path());
110
- Object.assign(_request, request);
111
- try {
112
- const conn = await ws(mod, _request, options);
113
- if (cancel?.current) {
114
- // The component unmounted while the handshake (or its retries) were still in flight —
115
- // there is no `useEffect` cleanup coming for this value, so close it here instead of
116
- // leaving a live socket and its retry timers attached to nothing.
117
- void conn.close();
118
- return null;
30
+ const establish = async (ctx, policy, retry, open) => {
31
+ const statusId = createIdOfLength(8);
32
+ const status = ctx.hasService(SOCKET_STATUS)
33
+ ? ctx.socketStatus() : null;
34
+ let opened = false;
35
+ let lost = false;
36
+ const managed = makeConnection({
37
+ policy, retry, open, onStatus: state => {
38
+ lost = state === 'lost';
39
+ status?.report(statusId, state, state === 'online' ? undefined : revive);
119
40
  }
120
- return conn;
121
- }
122
- catch {
123
- if (!cancel?.current)
124
- setFailed(true);
125
- return null;
126
- }
127
- }, [
128
- mod.alias,
129
- request?.query?.[AUTH_QUERY],
130
- request?.params ? JSON.stringify(request.params) : undefined,
131
- dial
132
- ]);
133
- useEffect(() => {
134
- if (!failed || !ctx.hasService(SOCKET_STATUS))
135
- return;
136
- return ctx.socketStatus().onRetry(() => {
137
- setFailed(false);
138
- setDial(n => n + 1);
139
41
  });
140
- }, [failed]);
141
- useEffect(() => {
142
- if (connection != null) {
143
- return () => {
144
- void connection.close();
145
- };
146
- }
147
- }, [connection]);
148
- return connection;
42
+ const { connection, ready } = managed;
43
+ const baseClose = connection.close;
44
+ connection.close = async () => {
45
+ await baseClose();
46
+ status?.release(statusId);
47
+ };
48
+ const revive = () => {
49
+ if (opened || !lost) {
50
+ managed.revive();
51
+ return;
52
+ }
53
+ // Never opened, so `ws()` already rejected and nobody holds this connection to hand a revived
54
+ // socket to: drop it for good and leave re-dialing to its owner (`useWs` does, on `onRetry`).
55
+ void baseClose();
56
+ status?.release(statusId);
57
+ };
58
+ // A rejection leaves this id reporting `'lost'` rather than releasing it — there is no
59
+ // `Connection` to hand back for a caller to close, and the aggregate is what puts
60
+ // `SocketReloadDialog` up. It is released by `revive` above, on the next `retry()`.
61
+ await ready;
62
+ opened = true;
63
+ return connection;
64
+ };
65
+ const connect = async (address, ctx, options) => {
66
+ const { policy, retry } = resolvePolicy(ctx, options);
67
+ return await establish(ctx, policy, retry, async () => openRawSocket(await address()));
68
+ };
69
+ const ws = async (module, request, options) => {
70
+ const ctx = assertContext(module.ctx, 'client-ws');
71
+ request = request ?? clientRequestHelper.provideRequest(module.alias, module.path());
72
+ const { policy, retry } = resolvePolicy(ctx, options);
73
+ return await establish(ctx, policy, retry, async () => {
74
+ await options?.beforeConnect?.(request);
75
+ const url = await makeApiCallHelper({ ref: module }).entrypointUrl(request);
76
+ return await openRawSocket(url);
77
+ });
78
+ };
79
+ return { connect, ws };
149
80
  };
81
+ export const socketClientHelper = createSocketClientHelper();
82
+ /** @deprecated compat:factory-refactor — use `socketClientHelper.ws(…)` */
83
+ export const ws = (module, request, options) => socketClientHelper.ws(module, request, options);
150
84
  //# sourceMappingURL=helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { AbstractRequest } from '@owlmeans/entrypoint';
2
+ import type { Connection } from '@owlmeans/socket';
3
+ import type { EntrypointReference } from '@owlmeans/context';
4
+ import type { WsOptions } from './types.js';
5
+ /**
6
+ * Open a socket through a declared entrypoint protocol.
7
+ *
8
+ * String aliases remain an adapter input for dynamically addressed integrations, while
9
+ * application callers pass their immutable protocol declaration and receive its bound client
10
+ * entrypoint here at the transport boundary.
11
+ *
12
+ * Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
13
+ * and again once the retry budget elapses without one ever opening — dialed afresh on the next
14
+ * `SocketStatusService.retry()`. A screen that must tell those two apart reads
15
+ * `useSocketStatus()` (from `./status.js`) alongside this.
16
+ */
17
+ export declare const useWs: (module: EntrypointReference | string, request?: Partial<AbstractRequest<any>>, options?: WsOptions) => Connection | null;
18
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,YAAY,CAAA;AAOtE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,WACR,mBAAmB,GAAG,MAAM,YAAY,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,YAAY,SAAS,KACjG,UAAU,GAAG,IAiDf,CAAA"}
package/build/hooks.js ADDED
@@ -0,0 +1,66 @@
1
+ import { clientRequestHelper } from '@owlmeans/client-entrypoint';
2
+ import { useContext, useValue } from '@owlmeans/client';
3
+ import { AUTH_QUERY } from '@owlmeans/auth';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ import { SOCKET_STATUS } from './consts.js';
6
+ import { socketClientHelper } from './helper.js';
7
+ /**
8
+ * Open a socket through a declared entrypoint protocol.
9
+ *
10
+ * String aliases remain an adapter input for dynamically addressed integrations, while
11
+ * application callers pass their immutable protocol declaration and receive its bound client
12
+ * entrypoint here at the transport boundary.
13
+ *
14
+ * Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
15
+ * and again once the retry budget elapses without one ever opening — dialed afresh on the next
16
+ * `SocketStatusService.retry()`. A screen that must tell those two apart reads
17
+ * `useSocketStatus()` (from `./status.js`) alongside this.
18
+ */
19
+ export const useWs = (module, request, options) => {
20
+ const ctx = useContext();
21
+ const mod = useMemo(() => ctx.entrypoint(module), [module]);
22
+ const [failed, setFailed] = useState(false);
23
+ const [dial, setDial] = useState(0);
24
+ const connection = useValue(async (cancel) => {
25
+ const _request = clientRequestHelper.provideRequest(mod.alias, mod.path());
26
+ Object.assign(_request, request);
27
+ try {
28
+ const conn = await socketClientHelper.ws(mod, _request, options);
29
+ if (cancel?.current) {
30
+ // The component unmounted while the handshake (or its retries) were still in flight —
31
+ // there is no `useEffect` cleanup coming for this value, so close it here instead of
32
+ // leaving a live socket and its retry timers attached to nothing.
33
+ void conn.close();
34
+ return null;
35
+ }
36
+ return conn;
37
+ }
38
+ catch {
39
+ if (!cancel?.current)
40
+ setFailed(true);
41
+ return null;
42
+ }
43
+ }, [
44
+ mod.alias,
45
+ request?.query?.[AUTH_QUERY],
46
+ request?.params ? JSON.stringify(request.params) : undefined,
47
+ dial
48
+ ]);
49
+ useEffect(() => {
50
+ if (!failed || !ctx.hasService(SOCKET_STATUS))
51
+ return;
52
+ return ctx.socketStatus().onRetry(() => {
53
+ setFailed(false);
54
+ setDial(n => n + 1);
55
+ });
56
+ }, [failed]);
57
+ useEffect(() => {
58
+ if (connection != null) {
59
+ return () => {
60
+ void connection.close();
61
+ };
62
+ }
63
+ }, [connection]);
64
+ return connection;
65
+ };
66
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAKxF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,MAAoC,EAAE,OAAuC,EAAE,OAAmB,EAC/E,EAAE;IACrB,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;IACxB,MAAM,GAAG,GAAG,OAAO,CACjB,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,CAAmB,MAAM,CAAC,EAC9C,CAAC,MAAM,CAAC,CACT,CAAA;IACD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IACnC,MAAM,UAAU,GAAG,QAAQ,CAAoB,KAAK,EAAE,MAAM,EAAE,EAAE;QAC9D,MAAM,QAAQ,GAAG,mBAAmB,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;QAC1E,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAChC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;YAChE,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,sFAAsF;gBACtF,qFAAqF;gBACrF,kEAAkE;gBAClE,KAAK,IAAI,CAAC,KAAK,EAAE,CAAA;gBACjB,OAAO,IAAI,CAAA;YACb,CAAC;YACD,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,MAAM,EAAE,OAAO;gBAAE,SAAS,CAAC,IAAI,CAAC,CAAA;YACrC,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,EAAE;QACD,GAAG,CAAC,KAAK;QACT,OAAO,EAAE,KAAK,EAAE,CAAC,UAAU,CAAC;QAC5B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QAC5D,IAAI;KACL,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC;YAAE,OAAM;QACrD,OAAQ,GAA4C,CAAC,YAAY,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC/E,SAAS,CAAC,KAAK,CAAC,CAAA;YAChB,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACrB,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,GAAG,EAAE;gBACV,KAAK,UAAU,CAAC,KAAK,EAAE,CAAA;YACzB,CAAC,CAAA;QACH,CAAC;IACH,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export type * from './types.js';
2
+ export type * from './helper/types.js';
2
3
  export * from './consts.js';
3
4
  export * from './helper.js';
5
+ export * from './hooks.js';
4
6
  export * from './status.js';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,mBAAmB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
package/build/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './consts.js';
2
2
  export * from './helper.js';
3
+ export * from './hooks.js';
3
4
  export * from './status.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/client-socket",
3
- "version": "0.1.18-rc.48",
3
+ "version": "0.1.18-rc.49",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -32,16 +32,16 @@
32
32
  "typescript": "^7.0.2"
33
33
  },
34
34
  "dependencies": {
35
- "@owlmeans/auth": "^0.1.18-rc.39",
36
- "@owlmeans/basic-envelope": "^0.1.18-rc.45",
37
- "@owlmeans/basic-ids": "^0.1.18-rc.36",
38
- "@owlmeans/client": "^0.1.18-rc.50",
39
- "@owlmeans/client-context": "^0.1.18-rc.47",
40
- "@owlmeans/client-entrypoint": "^0.1.18-rc.46",
41
- "@owlmeans/context": "^0.1.18-rc.33",
42
- "@owlmeans/entrypoint": "^0.1.18-rc.40",
43
- "@owlmeans/log": "^0.1.18-rc.1",
44
- "@owlmeans/socket": "^0.1.18-rc.39"
35
+ "@owlmeans/auth": "^0.1.18-rc.40",
36
+ "@owlmeans/basic-envelope": "^0.1.18-rc.46",
37
+ "@owlmeans/basic-ids": "^0.1.18-rc.37",
38
+ "@owlmeans/client": "^0.1.18-rc.51",
39
+ "@owlmeans/client-context": "^0.1.18-rc.48",
40
+ "@owlmeans/client-entrypoint": "^0.1.18-rc.47",
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.40"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
@@ -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
+ }
package/src/helper.ts CHANGED
@@ -1,176 +1,110 @@
1
- import { type ClientEntrypoint, provideRequest } from '@owlmeans/client-entrypoint'
1
+ import { type ClientEntrypoint, clientRequestHelper } from '@owlmeans/client-entrypoint'
2
2
  import type { AbstractRequest } from '@owlmeans/entrypoint'
3
3
  import { type Connection, SocketConnectionError } from '@owlmeans/socket'
4
4
  import { makeConnection } from './utils/connection.js'
5
- import { assertContext, type EntrypointReference } from '@owlmeans/context'
5
+ import { assertContext } from '@owlmeans/context'
6
6
  import type { Config, Context, ConnectOptions, ReconnectPolicy, SocketStatusServiceAppend, WsOptions } from './types.js'
7
- import { useContext, useValue } from '@owlmeans/client'
8
- import { AUTH_QUERY } from '@owlmeans/auth'
9
- import { entrypointUrl } from '@owlmeans/client-entrypoint/utils'
10
- import { useEffect, useMemo, useState } from 'react'
7
+ import type { SocketClientHelper } from './helper/types.js'
8
+ import { makeApiCallHelper } from '@owlmeans/client-entrypoint/utils'
11
9
  import { createIdOfLength } from '@owlmeans/basic-ids'
12
10
  import { DEFAULT_RECONNECT_POLICY, SOCKET_STATUS } from './consts.js'
13
11
 
14
- const resolvePolicy = (
15
- ctx: Context, options?: ConnectOptions
16
- ): { policy: ReconnectPolicy, retry: boolean } => {
17
- const requested = options?.reconnect !== undefined ? options.reconnect : ctx.cfg.socket?.reconnect
18
- const retry = requested !== false
19
- const overrides = requested === false || requested == null ? {} : requested
20
- return { policy: { ...DEFAULT_RECONNECT_POLICY, ...overrides }, retry }
21
- }
22
-
23
- const openRawSocket = async (url: string): Promise<WebSocket> => {
24
- const socket = new WebSocket(url)
25
- return await new Promise<WebSocket>((resolve, reject) => {
26
- const cleanup = () => {
27
- socket.removeEventListener('open', onOpen)
28
- socket.removeEventListener('error', onFail)
29
- socket.removeEventListener('close', onFail)
30
- }
31
- const onOpen = () => { cleanup(); resolve(socket) }
32
- const onFail = () => { cleanup(); reject(new SocketConnectionError('handshake')) }
33
- socket.addEventListener('open', onOpen)
34
- socket.addEventListener('error', onFail)
35
- socket.addEventListener('close', onFail)
36
- })
37
- }
12
+ export const createSocketClientHelper = (): SocketClientHelper => {
13
+ const resolvePolicy = (
14
+ ctx: Context, options?: ConnectOptions
15
+ ): { policy: ReconnectPolicy, retry: boolean } => {
16
+ const requested = options?.reconnect !== undefined ? options.reconnect : ctx.cfg.socket?.reconnect
17
+ const retry = requested !== false
18
+ const overrides = requested === false || requested == null ? {} : requested
19
+ return { policy: { ...DEFAULT_RECONNECT_POLICY, ...overrides }, retry }
20
+ }
38
21
 
39
- const establish = async (
40
- ctx: Context, policy: ReconnectPolicy, retry: boolean, open: () => Promise<WebSocket>
41
- ): Promise<Connection> => {
42
- const statusId = createIdOfLength(8)
43
- const status = ctx.hasService(SOCKET_STATUS)
44
- ? (ctx as unknown as SocketStatusServiceAppend).socketStatus() : null
22
+ const openRawSocket = async (url: string): Promise<WebSocket> => {
23
+ const socket = new WebSocket(url)
24
+ return await new Promise<WebSocket>((resolve, reject) => {
25
+ const cleanup = () => {
26
+ socket.removeEventListener('open', onOpen)
27
+ socket.removeEventListener('error', onFail)
28
+ socket.removeEventListener('close', onFail)
29
+ }
30
+ const onOpen = () => { cleanup(); resolve(socket) }
31
+ const onFail = () => { cleanup(); reject(new SocketConnectionError('handshake')) }
32
+ socket.addEventListener('open', onOpen)
33
+ socket.addEventListener('error', onFail)
34
+ socket.addEventListener('close', onFail)
35
+ })
36
+ }
45
37
 
46
- let opened = false
47
- let lost = false
48
- const managed = makeConnection({
49
- policy, retry, open, onStatus: state => {
50
- lost = state === 'lost'
51
- status?.report(statusId, state, state === 'online' ? undefined : revive)
52
- }
53
- })
54
- const { connection, ready } = managed
38
+ const establish = async (
39
+ ctx: Context, policy: ReconnectPolicy, retry: boolean, open: () => Promise<WebSocket>
40
+ ): Promise<Connection> => {
41
+ const statusId = createIdOfLength(8)
42
+ const status = ctx.hasService(SOCKET_STATUS)
43
+ ? (ctx as unknown as SocketStatusServiceAppend).socketStatus() : null
55
44
 
56
- const baseClose = connection.close
57
- connection.close = async () => {
58
- await baseClose()
59
- status?.release(statusId)
60
- }
45
+ let opened = false
46
+ let lost = false
47
+ const managed = makeConnection({
48
+ policy, retry, open, onStatus: state => {
49
+ lost = state === 'lost'
50
+ status?.report(statusId, state, state === 'online' ? undefined : revive)
51
+ }
52
+ })
53
+ const { connection, ready } = managed
61
54
 
62
- const revive = () => {
63
- if (opened || !lost) {
64
- managed.revive()
65
- return
55
+ const baseClose = connection.close
56
+ connection.close = async () => {
57
+ await baseClose()
58
+ status?.release(statusId)
66
59
  }
67
- // Never opened, so `ws()` already rejected and nobody holds this connection to hand a revived
68
- // socket to: drop it for good and leave re-dialing to its owner (`useWs` does, on `onRetry`).
69
- void baseClose()
70
- status?.release(statusId)
71
- }
72
-
73
- // A rejection leaves this id reporting `'lost'` rather than releasing it — there is no
74
- // `Connection` to hand back for a caller to close, and the aggregate is what puts
75
- // `SocketReloadDialog` up. It is released by `revive` above, on the next `retry()`.
76
- await ready
77
- opened = true
78
60
 
79
- return connection
80
- }
61
+ const revive = () => {
62
+ if (opened || !lost) {
63
+ managed.revive()
64
+ return
65
+ }
66
+ // Never opened, so `ws()` already rejected and nobody holds this connection to hand a revived
67
+ // socket to: drop it for good and leave re-dialing to its owner (`useWs` does, on `onRetry`).
68
+ void baseClose()
69
+ status?.release(statusId)
70
+ }
81
71
 
82
- /**
83
- * Open a socket with no declared entrypoint behind it — the primitive `ws()` builds on, and what
84
- * a test drives directly against a bare WebSocket server.
85
- */
86
- export const connect = async (
87
- address: () => Promise<string> | string, ctx: Context, options?: ConnectOptions
88
- ): Promise<Connection> => {
89
- const { policy, retry } = resolvePolicy(ctx, options)
90
- return await establish(ctx, policy, retry, async () => openRawSocket(await address()))
91
- }
72
+ // A rejection leaves this id reporting `'lost'` rather than releasing it — there is no
73
+ // `Connection` to hand back for a caller to close, and the aggregate is what puts
74
+ // `SocketReloadDialog` up. It is released by `revive` above, on the next `retry()`.
75
+ await ready
76
+ opened = true
92
77
 
93
- /**
94
- * Open a socket through a declared entrypoint protocol, restoring it on its own after a network
95
- * failure — see the client-socket skill's "Disconnects" section for the full policy. Resolves
96
- * once the first attempt opens; rejects with `SocketConnectionError('lost')` if the retry budget
97
- * elapses before that ever happens (or the single attempt fails, with `reconnect: false`).
98
- */
99
- export const ws = async (
100
- module: ClientEntrypoint<string>, request?: AbstractRequest<{ token?: string }>, options?: WsOptions
101
- ): Promise<Connection> => {
102
- const ctx = assertContext<Config, Context>(module.ctx as Context, 'client-ws')
103
- request = request ?? provideRequest(module.alias, module.path())
104
- const { policy, retry } = resolvePolicy(ctx, options)
78
+ return connection
79
+ }
105
80
 
106
- return await establish(ctx, policy, retry, async () => {
107
- await options?.beforeConnect?.(request!)
108
- const url = await entrypointUrl({ ref: module }, request)
109
- return await openRawSocket(url)
110
- })
111
- }
81
+ const connect = async (
82
+ address: () => Promise<string> | string, ctx: Context, options?: ConnectOptions
83
+ ): Promise<Connection> => {
84
+ const { policy, retry } = resolvePolicy(ctx, options)
85
+ return await establish(ctx, policy, retry, async () => openRawSocket(await address()))
86
+ }
112
87
 
113
- /**
114
- * Open a socket through a declared entrypoint protocol.
115
- *
116
- * String aliases remain an adapter input for dynamically addressed integrations, while
117
- * application callers pass their immutable protocol declaration and receive its bound client
118
- * entrypoint here at the transport boundary.
119
- *
120
- * Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
121
- * and again once the retry budget elapses without one ever opening — dialed afresh on the next
122
- * `SocketStatusService.retry()`. A screen that must tell those two apart reads
123
- * `useSocketStatus()` (from `./status.js`) alongside this.
124
- */
125
- export const useWs = (
126
- module: EntrypointReference | string, request?: Partial<AbstractRequest<any>>, options?: WsOptions
127
- ): Connection | null => {
128
- const ctx = useContext()
129
- const mod = useMemo(
130
- () => ctx.entrypoint<ClientEntrypoint>(module),
131
- [module]
132
- )
133
- const [failed, setFailed] = useState(false)
134
- const [dial, setDial] = useState(0)
135
- const connection = useValue<Connection | null>(async (cancel) => {
136
- const _request = provideRequest(mod.alias, mod.path())
137
- Object.assign(_request, request)
138
- try {
139
- const conn = await ws(mod, _request, options)
140
- if (cancel?.current) {
141
- // The component unmounted while the handshake (or its retries) were still in flight —
142
- // there is no `useEffect` cleanup coming for this value, so close it here instead of
143
- // leaving a live socket and its retry timers attached to nothing.
144
- void conn.close()
145
- return null
146
- }
147
- return conn
148
- } catch {
149
- if (!cancel?.current) setFailed(true)
150
- return null
151
- }
152
- }, [
153
- mod.alias,
154
- request?.query?.[AUTH_QUERY],
155
- request?.params ? JSON.stringify(request.params) : undefined,
156
- dial
157
- ])
88
+ const ws = async (
89
+ module: ClientEntrypoint<string>, request?: AbstractRequest<{ token?: string }>, options?: WsOptions
90
+ ): Promise<Connection> => {
91
+ const ctx = assertContext<Config, Context>(module.ctx as Context, 'client-ws')
92
+ request = request ?? clientRequestHelper.provideRequest(module.alias, module.path())
93
+ const { policy, retry } = resolvePolicy(ctx, options)
158
94
 
159
- useEffect(() => {
160
- if (!failed || !ctx.hasService(SOCKET_STATUS)) return
161
- return (ctx as unknown as SocketStatusServiceAppend).socketStatus().onRetry(() => {
162
- setFailed(false)
163
- setDial(n => n + 1)
95
+ return await establish(ctx, policy, retry, async () => {
96
+ await options?.beforeConnect?.(request!)
97
+ const url = await makeApiCallHelper({ ref: module }).entrypointUrl(request)
98
+ return await openRawSocket(url)
164
99
  })
165
- }, [failed])
166
-
167
- useEffect(() => {
168
- if (connection != null) {
169
- return () => {
170
- void connection.close()
171
- }
172
- }
173
- }, [connection])
100
+ }
174
101
 
175
- return connection
102
+ return { connect, ws }
176
103
  }
104
+
105
+ export const socketClientHelper = createSocketClientHelper()
106
+
107
+ /** @deprecated compat:factory-refactor — use `socketClientHelper.ws(…)` */
108
+ export const ws = (
109
+ module: ClientEntrypoint<string>, request?: AbstractRequest<{ token?: string }>, options?: WsOptions
110
+ ): Promise<Connection> => socketClientHelper.ws(module, request, options)
package/src/hooks.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { type ClientEntrypoint, clientRequestHelper } from '@owlmeans/client-entrypoint'
2
+ import type { AbstractRequest } from '@owlmeans/entrypoint'
3
+ import type { Connection } from '@owlmeans/socket'
4
+ import type { EntrypointReference } from '@owlmeans/context'
5
+ import type { SocketStatusServiceAppend, WsOptions } from './types.js'
6
+ import { useContext, useValue } from '@owlmeans/client'
7
+ import { AUTH_QUERY } from '@owlmeans/auth'
8
+ import { useEffect, useMemo, useState } from 'react'
9
+ import { SOCKET_STATUS } from './consts.js'
10
+ import { socketClientHelper } from './helper.js'
11
+
12
+ /**
13
+ * Open a socket through a declared entrypoint protocol.
14
+ *
15
+ * String aliases remain an adapter input for dynamically addressed integrations, while
16
+ * application callers pass their immutable protocol declaration and receive its bound client
17
+ * entrypoint here at the transport boundary.
18
+ *
19
+ * Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
20
+ * and again once the retry budget elapses without one ever opening — dialed afresh on the next
21
+ * `SocketStatusService.retry()`. A screen that must tell those two apart reads
22
+ * `useSocketStatus()` (from `./status.js`) alongside this.
23
+ */
24
+ export const useWs = (
25
+ module: EntrypointReference | string, request?: Partial<AbstractRequest<any>>, options?: WsOptions
26
+ ): Connection | null => {
27
+ const ctx = useContext()
28
+ const mod = useMemo(
29
+ () => ctx.entrypoint<ClientEntrypoint>(module),
30
+ [module]
31
+ )
32
+ const [failed, setFailed] = useState(false)
33
+ const [dial, setDial] = useState(0)
34
+ const connection = useValue<Connection | null>(async (cancel) => {
35
+ const _request = clientRequestHelper.provideRequest(mod.alias, mod.path())
36
+ Object.assign(_request, request)
37
+ try {
38
+ const conn = await socketClientHelper.ws(mod, _request, options)
39
+ if (cancel?.current) {
40
+ // The component unmounted while the handshake (or its retries) were still in flight —
41
+ // there is no `useEffect` cleanup coming for this value, so close it here instead of
42
+ // leaving a live socket and its retry timers attached to nothing.
43
+ void conn.close()
44
+ return null
45
+ }
46
+ return conn
47
+ } catch {
48
+ if (!cancel?.current) setFailed(true)
49
+ return null
50
+ }
51
+ }, [
52
+ mod.alias,
53
+ request?.query?.[AUTH_QUERY],
54
+ request?.params ? JSON.stringify(request.params) : undefined,
55
+ dial
56
+ ])
57
+
58
+ useEffect(() => {
59
+ if (!failed || !ctx.hasService(SOCKET_STATUS)) return
60
+ return (ctx as unknown as SocketStatusServiceAppend).socketStatus().onRetry(() => {
61
+ setFailed(false)
62
+ setDial(n => n + 1)
63
+ })
64
+ }, [failed])
65
+
66
+ useEffect(() => {
67
+ if (connection != null) {
68
+ return () => {
69
+ void connection.close()
70
+ }
71
+ }
72
+ }, [connection])
73
+
74
+ return connection
75
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
 
2
2
  export type * from './types.js'
3
+ export type * from './helper/types.js'
3
4
  export * from './consts.js'
4
5
  export * from './helper.js'
6
+ export * from './hooks.js'
5
7
  export * from './status.js'
@@ -1,10 +1,10 @@
1
1
  import { afterEach, describe, expect, test } from 'bun:test'
2
2
  import type { Server } from 'bun'
3
- import { connect } from '../src/helper.js'
3
+ import { socketClientHelper } from '../src/helper.js'
4
4
  import { makeConnection } from '../src/utils/connection.js'
5
5
  import { appendSocketStatus } from '../src/status.js'
6
6
  import { SocketSystemEvent } from '@owlmeans/socket'
7
- import type { EventMessage } from '@owlmeans/socket'
7
+ import type { Connection, EventMessage } from '@owlmeans/socket'
8
8
  import { makeTestContext } from './context.js'
9
9
 
10
10
  /**
@@ -64,7 +64,7 @@ const waitFor = async (check: () => boolean, timeout: number, step = 20): Promis
64
64
  }
65
65
  }
66
66
 
67
- const systemEvents = (connection: Awaited<ReturnType<typeof connect>>): string[] => {
67
+ const systemEvents = (connection: Connection): string[] => {
68
68
  const seen: string[] = []
69
69
  connection.listen(async message => {
70
70
  if (typeof message === 'object' && (message as EventMessage<unknown>).type === 'system') {
@@ -80,7 +80,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
80
80
  const port = initial.port
81
81
 
82
82
  const ctx = makeTestContext({ reconnect: { minDelay: 50, maxDelay: 150, stableAfter: 200, budget: 30_000 } })
83
- const connection = await connect(() => `ws://localhost:${port}`, ctx)
83
+ const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
84
84
  const events = systemEvents(connection)
85
85
 
86
86
  // Register an observer BEFORE the drop — it must still be the one that fires after the
@@ -143,7 +143,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
143
143
  const port = initial.port
144
144
 
145
145
  const ctx = makeTestContext({ reconnect: { minDelay: 50, maxDelay: 150 } })
146
- const connection = await connect(() => `ws://localhost:${port}`, ctx)
146
+ const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
147
147
  const events = systemEvents(connection)
148
148
 
149
149
  await connection.close()
@@ -160,7 +160,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
160
160
  const ctx = makeTestContext({
161
161
  reconnect: { heartbeat: 60, pongTimeout: 30, minDelay: 20, maxDelay: 40, budget: 10_000 }
162
162
  })
163
- const connection = await connect(() => `ws://localhost:${silent.port}`, ctx)
163
+ const connection = await socketClientHelper.connect(() => `ws://localhost:${silent.port}`, ctx)
164
164
  const events = systemEvents(connection)
165
165
 
166
166
  await waitFor(() => events.includes(SocketSystemEvent.Disconnected), 3_000)
@@ -176,7 +176,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
176
176
  const ctx = makeTestContext({
177
177
  reconnect: { heartbeat: 100, pongTimeout: 50, minDelay: 20, maxDelay: 40, budget: 10_000 }
178
178
  })
179
- const connection = await connect(() => `ws://localhost:${port}`, ctx)
179
+ const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
180
180
  const events = systemEvents(connection)
181
181
  let observed = 0
182
182
  connection.observe('probe', async () => { observed += 1 })
@@ -213,7 +213,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
213
213
  ctx.configure()
214
214
  await ctx.init()
215
215
 
216
- await expect(connect(() => 'ws://127.0.0.1:1', ctx)).rejects.toBeTruthy()
216
+ await expect(socketClientHelper.connect(() => 'ws://127.0.0.1:1', ctx)).rejects.toBeTruthy()
217
217
 
218
218
  expect(ctx.socketStatus().state()).toBe('lost')
219
219
  }, 5_000)
@@ -229,7 +229,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
229
229
  ctx.configure()
230
230
  await ctx.init()
231
231
 
232
- const connection = await connect(() => `ws://localhost:${port}`, ctx)
232
+ const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
233
233
  const events = systemEvents(connection)
234
234
  let observed = 0
235
235
  connection.observe('probe', async () => { observed += 1 })
@@ -271,7 +271,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
271
271
  ctx.configure()
272
272
  await ctx.init()
273
273
 
274
- const connection = await connect(() => `ws://localhost:${port}`, ctx)
274
+ const connection = await socketClientHelper.connect(() => `ws://localhost:${port}`, ctx)
275
275
  const events = systemEvents(connection)
276
276
 
277
277
  stopServer()
@@ -358,7 +358,7 @@ describe('@owlmeans/client-socket — reconnect', () => {
358
358
  ctx.configure()
359
359
  await ctx.init()
360
360
 
361
- await expect(connect(() => 'ws://127.0.0.1:1', ctx)).rejects.toBeTruthy()
361
+ await expect(socketClientHelper.connect(() => 'ws://127.0.0.1:1', ctx)).rejects.toBeTruthy()
362
362
  expect(ctx.socketStatus().state()).toBe('lost')
363
363
 
364
364
  let retried = 0