@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 +6 -6
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client-socket/SKILL.md +1 -1
- package/build/helper/types.d.ts +22 -0
- package/build/helper/types.d.ts.map +1 -0
- package/build/helper/types.js +2 -0
- package/build/helper/types.js.map +1 -0
- package/build/helper.d.ts +5 -26
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +73 -139
- package/build/helper.js.map +1 -1
- package/build/hooks.d.ts +18 -0
- package/build/hooks.d.ts.map +1 -0
- package/build/hooks.js +66 -0
- package/build/hooks.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/package.json +11 -11
- package/src/helper/types.ts +20 -0
- package/src/helper.ts +89 -155
- package/src/hooks.ts +75 -0
- package/src/index.ts +2 -0
- package/tests/reconnect.spec.ts +11 -11
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.
|
|
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 {
|
|
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.
|
|
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
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/client-socket",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
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.
|
|
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 @@
|
|
|
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 {
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
package/build/helper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,
|
|
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 {
|
|
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 {
|
|
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
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
package/build/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAExF,OAAO,EAAmB,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAErE,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAuB,EAAE;IAC/D,MAAM,aAAa,GAAG,CACpB,GAAY,EAAE,OAAwB,EACO,EAAE;QAC/C,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAA;QAClG,MAAM,KAAK,GAAG,SAAS,KAAK,KAAK,CAAA;QACjC,MAAM,SAAS,GAAG,SAAS,KAAK,KAAK,IAAI,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;QAC3E,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,wBAAwB,EAAE,GAAG,SAAS,EAAE,EAAE,KAAK,EAAE,CAAA;IACzE,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,KAAK,EAAE,GAAW,EAAsB,EAAE;QAC9D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAA;QACjC,OAAO,MAAM,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtD,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,MAAM,CAAC,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAC1C,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBAC3C,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAC7C,CAAC,CAAA;YACD,MAAM,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA,CAAC,CAAC,CAAA;YACnD,MAAM,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,qBAAqB,CAAC,WAAW,CAAC,CAAC,CAAA,CAAC,CAAC,CAAA;YAClF,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YACvC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YACxC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,MAAM,SAAS,GAAG,KAAK,EACrB,GAAY,EAAE,MAAuB,EAAE,KAAc,EAAE,IAA8B,EAChE,EAAE;QACvB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAA;QACpC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC;YAC1C,CAAC,CAAE,GAA4C,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;QAEvE,IAAI,MAAM,GAAG,KAAK,CAAA;QAClB,IAAI,IAAI,GAAG,KAAK,CAAA;QAChB,MAAM,OAAO,GAAG,cAAc,CAAC;YAC7B,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE;gBACrC,IAAI,GAAG,KAAK,KAAK,MAAM,CAAA;gBACvB,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;YAC1E,CAAC;SACF,CAAC,CAAA;QACF,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;QAErC,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAA;QAClC,UAAU,CAAC,KAAK,GAAG,KAAK,IAAI,EAAE;YAC5B,MAAM,SAAS,EAAE,CAAA;YACjB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC3B,CAAC,CAAA;QAED,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,IAAI,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;gBACpB,OAAO,CAAC,MAAM,EAAE,CAAA;gBAChB,OAAM;YACR,CAAC;YACD,8FAA8F;YAC9F,8FAA8F;YAC9F,KAAK,SAAS,EAAE,CAAA;YAChB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC3B,CAAC,CAAA;QAED,uFAAuF;QACvF,kFAAkF;QAClF,oFAAoF;QACpF,MAAM,KAAK,CAAA;QACX,MAAM,GAAG,IAAI,CAAA;QAEb,OAAO,UAAU,CAAA;IACnB,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,KAAK,EACnB,OAAuC,EAAE,GAAY,EAAE,OAAwB,EAC1D,EAAE;QACvB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QACrD,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,aAAa,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAA;IACxF,CAAC,CAAA;IAED,MAAM,EAAE,GAAG,KAAK,EACd,MAAgC,EAAE,OAA6C,EAAE,OAAmB,EAC/E,EAAE;QACvB,MAAM,GAAG,GAAG,aAAa,CAAkB,MAAM,CAAC,GAAc,EAAE,WAAW,CAAC,CAAA;QAC9E,OAAO,GAAG,OAAO,IAAI,mBAAmB,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;QACpF,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAErD,OAAO,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,OAAO,EAAE,aAAa,EAAE,CAAC,OAAQ,CAAC,CAAA;YACxC,MAAM,GAAG,GAAG,MAAM,iBAAiB,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;YAC3E,OAAO,MAAM,aAAa,CAAC,GAAG,CAAC,CAAA;QACjC,CAAC,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;AACxB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,EAAE,CAAA;AAE5D,2EAA2E;AAC3E,MAAM,CAAC,MAAM,EAAE,GAAG,CAChB,MAAgC,EAAE,OAA6C,EAAE,OAAmB,EAC/E,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA"}
|
package/build/hooks.d.ts
ADDED
|
@@ -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
package/build/index.d.ts.map
CHANGED
|
@@ -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
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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.
|
|
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.
|
|
36
|
-
"@owlmeans/basic-envelope": "^0.1.18-rc.
|
|
37
|
-
"@owlmeans/basic-ids": "^0.1.18-rc.
|
|
38
|
-
"@owlmeans/client": "^0.1.18-rc.
|
|
39
|
-
"@owlmeans/client-context": "^0.1.18-rc.
|
|
40
|
-
"@owlmeans/client-entrypoint": "^0.1.18-rc.
|
|
41
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
42
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
43
|
-
"@owlmeans/log": "^0.1.18-rc.
|
|
44
|
-
"@owlmeans/socket": "^0.1.18-rc.
|
|
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,
|
|
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
|
|
5
|
+
import { assertContext } from '@owlmeans/context'
|
|
6
6
|
import type { Config, Context, ConnectOptions, ReconnectPolicy, SocketStatusServiceAppend, WsOptions } from './types.js'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
}
|
|
166
|
-
|
|
167
|
-
useEffect(() => {
|
|
168
|
-
if (connection != null) {
|
|
169
|
-
return () => {
|
|
170
|
-
void connection.close()
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}, [connection])
|
|
100
|
+
}
|
|
174
101
|
|
|
175
|
-
return
|
|
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
package/tests/reconnect.spec.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from 'bun:test'
|
|
2
2
|
import type { Server } from 'bun'
|
|
3
|
-
import {
|
|
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:
|
|
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
|