@owlmeans/client-socket 0.1.18-rc.33 → 0.1.18-rc.35
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 +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client-socket/SKILL.md +36 -14
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +1 -0
- package/build/consts.js.map +1 -1
- package/build/helper.d.ts +3 -2
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +40 -12
- package/build/helper.js.map +1 -1
- package/build/status.d.ts +9 -0
- package/build/status.d.ts.map +1 -1
- package/build/status.js +83 -26
- package/build/status.js.map +1 -1
- package/build/types.d.ts +13 -2
- package/build/types.d.ts.map +1 -1
- package/build/utils/connection.d.ts +5 -0
- package/build/utils/connection.d.ts.map +1 -1
- package/build/utils/connection.js +73 -19
- package/build/utils/connection.js.map +1 -1
- package/package.json +10 -10
- package/src/consts.ts +1 -0
- package/src/helper.ts +40 -12
- package/src/status.ts +81 -25
- package/src/types.ts +13 -2
- package/src/utils/connection.ts +77 -19
- package/tests/reconnect.spec.ts +228 -6
|
@@ -21,12 +21,16 @@ export const makeConnection = (opts) => {
|
|
|
21
21
|
let current = null;
|
|
22
22
|
let closedByClient = false;
|
|
23
23
|
let finished = false;
|
|
24
|
+
// Gave up after the budget, but still revivable — unlike `finished`, nothing here is final.
|
|
25
|
+
let lost = false;
|
|
24
26
|
let attempts = 0;
|
|
25
27
|
let outageStartedAt = null;
|
|
28
|
+
let outageBudget = policy.budget;
|
|
26
29
|
let stableTimer = null;
|
|
27
30
|
let retryTimer = null;
|
|
28
31
|
let heartbeatTimer = null;
|
|
29
|
-
|
|
32
|
+
// When the ping still waiting for an answer was sent, or null once anything came back.
|
|
33
|
+
let pingSentAt = null;
|
|
30
34
|
let resolveReady = () => void 0;
|
|
31
35
|
let rejectReady = () => void 0;
|
|
32
36
|
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
|
|
@@ -70,17 +74,22 @@ export const makeConnection = (opts) => {
|
|
|
70
74
|
clearHeartbeat();
|
|
71
75
|
};
|
|
72
76
|
const startHeartbeat = (socket) => {
|
|
73
|
-
|
|
77
|
+
pingSentAt = null;
|
|
74
78
|
heartbeatTimer = setInterval(() => {
|
|
75
79
|
if (socket.readyState !== WebSocket.OPEN)
|
|
76
80
|
return;
|
|
77
|
-
if (Date.now() -
|
|
78
|
-
// Silent half-open TCP:
|
|
79
|
-
//
|
|
81
|
+
if (pingSentAt != null && Date.now() - pingSentAt > policy.pongTimeout) {
|
|
82
|
+
// Silent half-open TCP: the ping this socket owes an answer to went out long enough ago
|
|
83
|
+
// that any live peer would have replied — force the close its own stack would eventually
|
|
80
84
|
// notice on its own, minutes later.
|
|
81
85
|
socket.close(SOCKET_HEARTBEAT_TIMEOUT_CODE);
|
|
82
86
|
return;
|
|
83
87
|
}
|
|
88
|
+
// Measured from the PING rather than from the last frame: a hidden tab's timers are
|
|
89
|
+
// throttled to about one wake-up a minute, so "nothing has arrived for longer than a
|
|
90
|
+
// heartbeat interval" is what throttling looks like, not what a dead socket looks like —
|
|
91
|
+
// it used to kill a perfectly healthy backgrounded connection on every wake-up.
|
|
92
|
+
pingSentAt = Date.now();
|
|
84
93
|
socket.send(JSON.stringify({ type: 'ping' }));
|
|
85
94
|
}, policy.heartbeat);
|
|
86
95
|
};
|
|
@@ -100,15 +109,19 @@ export const makeConnection = (opts) => {
|
|
|
100
109
|
await emitSystem(SocketSystemEvent.Close, { code });
|
|
101
110
|
settle(new SocketConnectionError('lost'));
|
|
102
111
|
};
|
|
103
|
-
|
|
112
|
+
// No `Close` frame here: the connection stays revivable, and `close` means gone for good.
|
|
113
|
+
const giveUp = async () => {
|
|
114
|
+
lost = true;
|
|
115
|
+
clearTimers();
|
|
116
|
+
onStatus?.('lost');
|
|
117
|
+
await emitSystem(SocketSystemEvent.Lost, {});
|
|
118
|
+
settle(new SocketConnectionError('lost'));
|
|
119
|
+
};
|
|
120
|
+
const scheduleReconnect = () => {
|
|
104
121
|
if (outageStartedAt == null)
|
|
105
122
|
outageStartedAt = Date.now();
|
|
106
|
-
if (Date.now() - outageStartedAt >=
|
|
107
|
-
void (
|
|
108
|
-
onStatus?.('lost');
|
|
109
|
-
await emitSystem(SocketSystemEvent.Lost, {});
|
|
110
|
-
await finish(code);
|
|
111
|
-
})();
|
|
123
|
+
if (Date.now() - outageStartedAt >= outageBudget) {
|
|
124
|
+
void giveUp();
|
|
112
125
|
return;
|
|
113
126
|
}
|
|
114
127
|
attempts += 1;
|
|
@@ -120,22 +133,59 @@ export const makeConnection = (opts) => {
|
|
|
120
133
|
const attemptConnect = async () => {
|
|
121
134
|
if (closedByClient)
|
|
122
135
|
return;
|
|
136
|
+
let socket;
|
|
123
137
|
try {
|
|
124
|
-
|
|
125
|
-
onOpened(socket);
|
|
138
|
+
socket = await open();
|
|
126
139
|
}
|
|
127
140
|
catch {
|
|
128
141
|
if (retry)
|
|
129
|
-
scheduleReconnect(
|
|
142
|
+
scheduleReconnect();
|
|
130
143
|
else
|
|
131
144
|
void finish(1006);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (closedByClient) {
|
|
148
|
+
// Closed while this handshake was in flight — nobody is left to own the socket.
|
|
149
|
+
socket.close(1000);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
onOpened(socket);
|
|
153
|
+
};
|
|
154
|
+
const revive = () => {
|
|
155
|
+
if (finished)
|
|
156
|
+
return;
|
|
157
|
+
if (lost) {
|
|
158
|
+
lost = false;
|
|
159
|
+
attempts = 1;
|
|
160
|
+
outageStartedAt = Date.now();
|
|
161
|
+
outageBudget = policy.reviveBudget;
|
|
162
|
+
onStatus?.('reconnecting');
|
|
163
|
+
void emitSystem(SocketSystemEvent.Reconnecting, { attempt: attempts, delay: 0 });
|
|
164
|
+
void attemptConnect();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Still retrying: never give up sooner than a revived connection would — otherwise a retry of
|
|
168
|
+
// its lost siblings is undone moments later when this one's older budget runs out, even by an
|
|
169
|
+
// attempt already in flight — and attempt now rather than after the backoff.
|
|
170
|
+
if (current != null)
|
|
171
|
+
return;
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
if (outageStartedAt != null && outageBudget - (now - outageStartedAt) < policy.reviveBudget) {
|
|
174
|
+
outageStartedAt = now;
|
|
175
|
+
outageBudget = policy.reviveBudget;
|
|
132
176
|
}
|
|
177
|
+
if (retryTimer == null)
|
|
178
|
+
return;
|
|
179
|
+
clearTimeout(retryTimer);
|
|
180
|
+
retryTimer = null;
|
|
181
|
+
void attemptConnect();
|
|
133
182
|
};
|
|
134
183
|
const onOpened = (socket) => {
|
|
135
184
|
current = socket;
|
|
136
185
|
const wasRetry = attempts > 0;
|
|
137
186
|
const receiveMessage = async (event) => {
|
|
138
|
-
|
|
187
|
+
// Any frame at all answers the outstanding ping — the point is liveness, not pongs.
|
|
188
|
+
pingSentAt = null;
|
|
139
189
|
if (typeof event.data === 'string') {
|
|
140
190
|
try {
|
|
141
191
|
if (JSON.parse(event.data)?.type === 'pong')
|
|
@@ -179,7 +229,7 @@ export const makeConnection = (opts) => {
|
|
|
179
229
|
}
|
|
180
230
|
model.stage = AuthenticationStage.Init;
|
|
181
231
|
await emitSystem(SocketSystemEvent.Disconnected, { code: event.code });
|
|
182
|
-
scheduleReconnect(
|
|
232
|
+
scheduleReconnect();
|
|
183
233
|
};
|
|
184
234
|
socket.addEventListener('message', messageHandler);
|
|
185
235
|
socket.addEventListener('close', closeHandler);
|
|
@@ -187,7 +237,11 @@ export const makeConnection = (opts) => {
|
|
|
187
237
|
startHeartbeat(socket);
|
|
188
238
|
if (stableTimer != null)
|
|
189
239
|
clearTimeout(stableTimer);
|
|
190
|
-
stableTimer = setTimeout(() => {
|
|
240
|
+
stableTimer = setTimeout(() => {
|
|
241
|
+
attempts = 0;
|
|
242
|
+
outageStartedAt = null;
|
|
243
|
+
outageBudget = policy.budget;
|
|
244
|
+
}, policy.stableAfter);
|
|
191
245
|
onStatus?.('online');
|
|
192
246
|
settle();
|
|
193
247
|
if (wasRetry)
|
|
@@ -226,6 +280,6 @@ export const makeConnection = (opts) => {
|
|
|
226
280
|
return message;
|
|
227
281
|
};
|
|
228
282
|
void attemptConnect();
|
|
229
|
-
return { connection: model, ready };
|
|
283
|
+
return { connection: model, ready, revive };
|
|
230
284
|
};
|
|
231
285
|
//# sourceMappingURL=connection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EAAE,WAAW,EAAE,qBAAqB,EAAE,iBAAiB,EAC5E,6BAA6B,EAC9B,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAGpD;;;6DAG6D;AAC7D,MAAM,oBAAoB,GAAwB,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/client-socket",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.35",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -32,15 +32,15 @@
|
|
|
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/socket": "^0.1.18-rc.
|
|
35
|
+
"@owlmeans/auth": "^0.1.18-rc.32",
|
|
36
|
+
"@owlmeans/basic-envelope": "^0.1.18-rc.33",
|
|
37
|
+
"@owlmeans/basic-ids": "^0.1.18-rc.31",
|
|
38
|
+
"@owlmeans/client": "^0.1.18-rc.37",
|
|
39
|
+
"@owlmeans/client-context": "^0.1.18-rc.35",
|
|
40
|
+
"@owlmeans/client-entrypoint": "^0.1.18-rc.34",
|
|
41
|
+
"@owlmeans/context": "^0.1.18-rc.30",
|
|
42
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.33",
|
|
43
|
+
"@owlmeans/socket": "^0.1.18-rc.30"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/consts.ts
CHANGED
package/src/helper.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { Config, Context, ConnectOptions, ReconnectPolicy, SocketStatusServ
|
|
|
10
10
|
import { useContext, useValue } from '@owlmeans/client'
|
|
11
11
|
import { AUTH_QUERY } from '@owlmeans/auth'
|
|
12
12
|
import { entrypointUrl } from '@owlmeans/client-entrypoint/utils'
|
|
13
|
-
import { useEffect, useMemo } from 'react'
|
|
13
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
14
14
|
import { createIdOfLength } from '@owlmeans/basic-ids'
|
|
15
15
|
import { DEFAULT_RECONNECT_POLICY } from './consts.js'
|
|
16
16
|
import { SOCKET_STATUS } from './status.js'
|
|
@@ -47,9 +47,15 @@ const establish = async (
|
|
|
47
47
|
const status = ctx.hasService(SOCKET_STATUS)
|
|
48
48
|
? (ctx as unknown as SocketStatusServiceAppend).socketStatus() : null
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
let opened = false
|
|
51
|
+
let lost = false
|
|
52
|
+
const managed = makeConnection({
|
|
53
|
+
policy, retry, open, onStatus: state => {
|
|
54
|
+
lost = state === 'lost'
|
|
55
|
+
status?.report(statusId, state, state === 'online' ? undefined : revive)
|
|
56
|
+
}
|
|
52
57
|
})
|
|
58
|
+
const { connection, ready } = managed
|
|
53
59
|
|
|
54
60
|
const baseClose = connection.close
|
|
55
61
|
connection.close = async () => {
|
|
@@ -57,13 +63,22 @@ const establish = async (
|
|
|
57
63
|
status?.release(statusId)
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
const revive = () => {
|
|
67
|
+
if (opened || !lost) {
|
|
68
|
+
managed.revive()
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
// Never opened, so `ws()` already rejected and nobody holds this connection to hand a revived
|
|
72
|
+
// socket to: drop it for good and leave re-dialing to its owner (`useWs` does, on `onRetry`).
|
|
73
|
+
void baseClose()
|
|
74
|
+
status?.release(statusId)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A rejection leaves this id reporting `'lost'` rather than releasing it — there is no
|
|
78
|
+
// `Connection` to hand back for a caller to close, and the aggregate is what puts
|
|
79
|
+
// `SocketReloadDialog` up. It is released by `revive` above, on the next `retry()`.
|
|
66
80
|
await ready
|
|
81
|
+
opened = true
|
|
67
82
|
|
|
68
83
|
return connection
|
|
69
84
|
}
|
|
@@ -107,8 +122,9 @@ export const ws = async (
|
|
|
107
122
|
* entrypoint here at the transport boundary.
|
|
108
123
|
*
|
|
109
124
|
* Re-opens on its own after a network drop (see `ws()`). `null` until the first attempt opens,
|
|
110
|
-
* and again once the retry budget elapses without one ever opening —
|
|
111
|
-
*
|
|
125
|
+
* and again once the retry budget elapses without one ever opening — dialed afresh on the next
|
|
126
|
+
* `SocketStatusService.retry()`. A screen that must tell those two apart reads
|
|
127
|
+
* `useSocketStatus()` (from `./status.js`) alongside this.
|
|
112
128
|
*/
|
|
113
129
|
export const useWs = (
|
|
114
130
|
module: EntrypointReference | string, request?: Partial<AbstractRequest<any>>, options?: WsOptions
|
|
@@ -118,6 +134,8 @@ export const useWs = (
|
|
|
118
134
|
() => ctx.entrypoint<ClientEntrypoint>(module),
|
|
119
135
|
[module]
|
|
120
136
|
)
|
|
137
|
+
const [failed, setFailed] = useState(false)
|
|
138
|
+
const [dial, setDial] = useState(0)
|
|
121
139
|
const connection = useValue<Connection | null>(async (cancel) => {
|
|
122
140
|
const _request = provideRequest(mod.alias, mod.path())
|
|
123
141
|
Object.assign(_request, request)
|
|
@@ -132,14 +150,24 @@ export const useWs = (
|
|
|
132
150
|
}
|
|
133
151
|
return conn
|
|
134
152
|
} catch {
|
|
153
|
+
if (!cancel?.current) setFailed(true)
|
|
135
154
|
return null
|
|
136
155
|
}
|
|
137
156
|
}, [
|
|
138
157
|
mod.alias,
|
|
139
158
|
request?.query?.[AUTH_QUERY],
|
|
140
|
-
request?.params ? JSON.stringify(request.params) : undefined
|
|
159
|
+
request?.params ? JSON.stringify(request.params) : undefined,
|
|
160
|
+
dial
|
|
141
161
|
])
|
|
142
162
|
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!failed || !ctx.hasService(SOCKET_STATUS)) return
|
|
165
|
+
return (ctx as unknown as SocketStatusServiceAppend).socketStatus().onRetry(() => {
|
|
166
|
+
setFailed(false)
|
|
167
|
+
setDial(n => n + 1)
|
|
168
|
+
})
|
|
169
|
+
}, [failed])
|
|
170
|
+
|
|
143
171
|
useEffect(() => {
|
|
144
172
|
if (connection != null) {
|
|
145
173
|
return () => {
|
package/src/status.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createService } from '@owlmeans/context'
|
|
2
2
|
import { useContext } from '@owlmeans/client'
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
4
4
|
import type {
|
|
5
5
|
Config, Context, SocketConnectionState, SocketStatusService, SocketStatusServiceAppend
|
|
6
6
|
} from './types.js'
|
|
@@ -17,7 +17,9 @@ const RANK: Record<SocketConnectionState, number> = { online: 0, reconnecting: 1
|
|
|
17
17
|
*/
|
|
18
18
|
export const createSocketStatusService = (alias: string = SOCKET_STATUS): SocketStatusService => {
|
|
19
19
|
const states = new Map<string, SocketConnectionState>()
|
|
20
|
+
const revivers = new Map<string, () => void>()
|
|
20
21
|
const listeners = new Set<(state: SocketConnectionState) => void>()
|
|
22
|
+
const retryListeners = new Set<() => void>()
|
|
21
23
|
let current: SocketConnectionState = 'online'
|
|
22
24
|
|
|
23
25
|
const recompute = () => {
|
|
@@ -31,15 +33,49 @@ export const createSocketStatusService = (alias: string = SOCKET_STATUS): Socket
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// Connections still retrying are nudged too, so one whose older budget is about to run out does
|
|
37
|
+
// not report `'lost'` right after its lost siblings were revived.
|
|
38
|
+
const retry = (): boolean => {
|
|
39
|
+
const pending = [...states].filter(([, state]) => state !== 'online')
|
|
40
|
+
pending.forEach(([id]) => revivers.get(id)?.())
|
|
41
|
+
if (!pending.some(([, state]) => state === 'lost')) return false
|
|
42
|
+
retryListeners.forEach(listener => listener())
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// A backgrounded tab is where sockets die unnoticed (throttled timers, a sleeping machine), so
|
|
47
|
+
// the moment it is looked at again is the moment to retry — before anyone reaches for a button.
|
|
48
|
+
const watchActivation = () => {
|
|
49
|
+
if (typeof document === 'undefined' || typeof window === 'undefined') return
|
|
50
|
+
document.addEventListener('visibilitychange', () => {
|
|
51
|
+
if (document.visibilityState === 'visible') retry()
|
|
52
|
+
})
|
|
53
|
+
window.addEventListener('focus', () => { retry() })
|
|
54
|
+
window.addEventListener('online', () => { retry() })
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
return createService<SocketStatusService>(alias, {
|
|
35
|
-
report: (id, state) => {
|
|
36
|
-
|
|
58
|
+
report: (id, state, revive) => {
|
|
59
|
+
states.set(id, state)
|
|
60
|
+
if (revive != null) revivers.set(id, revive)
|
|
61
|
+
else revivers.delete(id)
|
|
62
|
+
recompute()
|
|
63
|
+
},
|
|
64
|
+
release: (id) => { states.delete(id); revivers.delete(id); recompute() },
|
|
37
65
|
state: () => current,
|
|
38
66
|
subscribe: listener => {
|
|
39
67
|
listeners.add(listener)
|
|
40
68
|
return () => { listeners.delete(listener) }
|
|
41
69
|
},
|
|
42
|
-
|
|
70
|
+
retry,
|
|
71
|
+
onRetry: listener => {
|
|
72
|
+
retryListeners.add(listener)
|
|
73
|
+
return () => { retryListeners.delete(listener) }
|
|
74
|
+
},
|
|
75
|
+
}, service => async () => {
|
|
76
|
+
watchActivation()
|
|
77
|
+
service.initialized = true
|
|
78
|
+
})
|
|
43
79
|
}
|
|
44
80
|
|
|
45
81
|
/**
|
|
@@ -74,35 +110,55 @@ const readSocketStatusService = (
|
|
|
74
110
|
}
|
|
75
111
|
}
|
|
76
112
|
|
|
113
|
+
/** The status service once the context can hand it out; `null` while it cannot, or never will. */
|
|
114
|
+
const useStatusService = (): SocketStatusService | null => {
|
|
115
|
+
const ctx = useContext() as unknown as Context & Partial<SocketStatusServiceAppend>
|
|
116
|
+
const [service, setService] = useState<SocketStatusService | null>(() => readSocketStatusService(ctx))
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (service != null) return
|
|
120
|
+
let cancelled = false
|
|
121
|
+
void ctx.waitForInitialized?.().then(() => { if (!cancelled) setService(readSocketStatusService(ctx)) })
|
|
122
|
+
return () => { cancelled = true }
|
|
123
|
+
}, [ctx, service])
|
|
124
|
+
|
|
125
|
+
return service
|
|
126
|
+
}
|
|
127
|
+
|
|
77
128
|
/**
|
|
78
129
|
* The worst state across every live `ws()`/`useWs()` connection, or `'online'` if the app never
|
|
79
130
|
* registered the status service (`appendSocketStatus`) or has not finished initializing yet.
|
|
80
131
|
*/
|
|
81
132
|
export const useSocketStatus = (): SocketConnectionState => {
|
|
82
|
-
const
|
|
83
|
-
const [state, setState] = useState<SocketConnectionState>(() =>
|
|
133
|
+
const service = useStatusService()
|
|
134
|
+
const [state, setState] = useState<SocketConnectionState>(() => service?.state() ?? 'online')
|
|
84
135
|
|
|
85
136
|
useEffect(() => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const service = readSocketStatusService(ctx)
|
|
91
|
-
if (service == null) return false
|
|
92
|
-
setState(service.state())
|
|
93
|
-
unsubscribe = service.subscribe(next => { if (!cancelled) setState(next) })
|
|
94
|
-
return true
|
|
95
|
-
}
|
|
137
|
+
if (service == null) return
|
|
138
|
+
setState(service.state())
|
|
139
|
+
return service.subscribe(setState)
|
|
140
|
+
}, [service])
|
|
96
141
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
142
|
+
return state
|
|
143
|
+
}
|
|
100
144
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
145
|
+
/**
|
|
146
|
+
* `retry` revives every lost connection now (`SocketStatusService.retry()`); `retrying` is true
|
|
147
|
+
* from any retry — this one, or the service's own on tab activation — until the aggregate settles
|
|
148
|
+
* back on `'online'` or `'lost'`.
|
|
149
|
+
*/
|
|
150
|
+
export const useSocketRetry = (): { retry: () => void, retrying: boolean } => {
|
|
151
|
+
const service = useStatusService()
|
|
152
|
+
const [retrying, setRetrying] = useState(false)
|
|
106
153
|
|
|
107
|
-
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (service == null) return
|
|
156
|
+
const stopRetry = service.onRetry(() => setRetrying(service.state() === 'reconnecting'))
|
|
157
|
+
const stopState = service.subscribe(state => { if (state !== 'reconnecting') setRetrying(false) })
|
|
158
|
+
return () => { stopRetry(); stopState() }
|
|
159
|
+
}, [service])
|
|
160
|
+
|
|
161
|
+
const retry = useCallback(() => { service?.retry() }, [service])
|
|
162
|
+
|
|
163
|
+
return { retry, retrying }
|
|
108
164
|
}
|
package/src/types.ts
CHANGED
|
@@ -32,6 +32,10 @@ export interface ReconnectPolicy {
|
|
|
32
32
|
/** Total time, in ms, the carrier keeps retrying before it reports the socket lost. Default
|
|
33
33
|
* 600000 (10 minutes). */
|
|
34
34
|
budget: number
|
|
35
|
+
/** Retry window, in ms, of an outage restarted on a lost socket by `SocketStatusService.retry()`
|
|
36
|
+
* (a "Try again" press, the tab coming back) — short, so a retry that cannot succeed reports
|
|
37
|
+
* `'lost'` again while someone is still looking. Default 15000. */
|
|
38
|
+
reviveBudget: number
|
|
35
39
|
/** How long a reopened socket must stay open before the attempt count and the outage clock
|
|
36
40
|
* reset, in ms. Default 10000. */
|
|
37
41
|
stableAfter: number
|
|
@@ -65,13 +69,20 @@ export interface WsOptions extends ConnectOptions {
|
|
|
65
69
|
export type SocketConnectionState = 'online' | 'reconnecting' | 'lost'
|
|
66
70
|
|
|
67
71
|
export interface SocketStatusService extends InitializedService {
|
|
68
|
-
/** Record this connection's current state under `id` — call again to update it.
|
|
69
|
-
|
|
72
|
+
/** Record this connection's current state under `id` — call again to update it. `revive` is
|
|
73
|
+
* how `retry()` brings it back (`'lost'`) or hurries it (`'reconnecting'`); every report
|
|
74
|
+
* replaces the previous one. */
|
|
75
|
+
report: (id: string, state: SocketConnectionState, revive?: () => void) => void
|
|
70
76
|
/** Stop tracking `id` (the connection closed on purpose). */
|
|
71
77
|
release: (id: string) => void
|
|
72
78
|
/** The worst state across every tracked connection; `'online'` when none are tracked. */
|
|
73
79
|
state: () => SocketConnectionState
|
|
74
80
|
subscribe: (listener: (state: SocketConnectionState) => void) => () => void
|
|
81
|
+
/** Revive every `'lost'` connection and hurry every `'reconnecting'` one, then tell `onRetry`
|
|
82
|
+
* listeners. `false` (and no listener call) when nothing was lost. Also fires by itself when a
|
|
83
|
+
* browser tab becomes visible, its window gains focus, or the network comes back online. */
|
|
84
|
+
retry: () => boolean
|
|
85
|
+
onRetry: (listener: () => void) => () => void
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
export interface SocketStatusServiceAppend {
|