statewire 0.8.0 → 0.9.0
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/dist/client/connection.d.ts +23 -19
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +143 -103
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +30 -3
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +222 -259
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +34 -39
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +380 -373
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -0
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/client/timing.d.ts +1 -2
- package/dist/client/timing.d.ts.map +1 -1
- package/dist/client/timing.js +1 -5
- package/dist/client/timing.js.map +1 -1
- package/dist/host/draft.d.ts +2 -5
- package/dist/host/draft.d.ts.map +1 -1
- package/dist/host/draft.js.map +1 -1
- package/dist/host/host.d.ts +5 -8
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +23 -32
- package/dist/host/host.js.map +1 -1
- package/dist/host/socket.d.ts +3 -2
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +484 -424
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +4 -0
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host.d.ts +10 -10
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/statewire.d.ts +29 -24
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +8 -2
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fake-sse.d.ts +1 -1
- package/dist/transports/fake-sse.d.ts.map +1 -1
- package/dist/transports/fake-sse.js +5 -2
- package/dist/transports/fake-sse.js.map +1 -1
- package/dist/transports/fixtures.d.ts +1 -2
- package/dist/transports/fixtures.d.ts.map +1 -1
- package/dist/transports/fixtures.js +5 -7
- package/dist/transports/fixtures.js.map +1 -1
- package/dist/transports/remote.d.ts +2 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +10 -4
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +32 -46
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +56 -59
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +7 -5
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +41 -14
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +2 -3
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +25 -80
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +8 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -4
- package/dist/types.js.map +1 -1
- package/dist/wire/ops.d.ts +1 -5
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +1 -4
- package/dist/wire/ops.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +175 -128
- package/src/client/core.ts +287 -276
- package/src/client/session.ts +474 -480
- package/src/client/sessionStore.ts +1 -0
- package/src/client/timing.ts +0 -6
- package/src/host/draft.ts +1 -3
- package/src/host/host.ts +41 -58
- package/src/host/socket.ts +653 -448
- package/src/host-internal.ts +4 -0
- package/src/host.ts +13 -10
- package/src/index.ts +0 -1
- package/src/statewire.ts +38 -32
- package/src/transports/fake-sse.ts +2 -1
- package/src/transports/fixtures.ts +4 -9
- package/src/transports/remote.ts +12 -5
- package/src/transports/sse.ts +99 -115
- package/src/transports/testing.ts +47 -19
- package/src/transports/ws.ts +44 -115
- package/src/types.ts +15 -14
- package/src/wire/ops.ts +9 -16
package/src/client/connection.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import { flushTapSync } from "@assistant-ui/tap";
|
|
3
|
-
import { reviveOps,
|
|
4
|
-
import {
|
|
3
|
+
import { reviveOps, reviveValue } from "../wire/meta";
|
|
4
|
+
import { backoff } from "./timing";
|
|
5
5
|
import type { ConnectionState, StatewireRemoteProps } from "../transports/sse";
|
|
6
6
|
import type { StatewireClient } from "../statewire";
|
|
7
7
|
import type { StatewireHost } from "../host";
|
|
@@ -11,65 +11,72 @@ export type Episode = {
|
|
|
11
11
|
message?: string;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export type
|
|
14
|
+
export type StatewireCmd = {
|
|
15
15
|
seq: number;
|
|
16
|
-
type
|
|
16
|
+
type?: "applied" | "result" | "rejected" | "failed" | "crashed" | "expired";
|
|
17
17
|
code?: string;
|
|
18
18
|
message?: string;
|
|
19
19
|
payload?: unknown;
|
|
20
20
|
meta?: unknown;
|
|
21
|
+
/** Cumulative: resending through this seq will never be needed. */
|
|
22
|
+
dur?: boolean;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export type StatewireSyn = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
seq?: number;
|
|
27
|
+
ctxSeq?: number;
|
|
28
|
+
record?: "fresh" | "resumed" | "ephemeral";
|
|
29
|
+
id?: string;
|
|
30
|
+
idle?: boolean;
|
|
31
|
+
retry?: number;
|
|
26
32
|
};
|
|
27
33
|
|
|
28
34
|
export type StatewireEnvelope = {
|
|
29
35
|
ops?: unknown[];
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
cmd?: StatewireCmd[];
|
|
37
|
+
next?: true;
|
|
38
|
+
idle?: boolean;
|
|
32
39
|
syn?: StatewireSyn;
|
|
33
40
|
fin?: StatewireHost.Fin;
|
|
34
41
|
};
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
wait: (timeoutMs?: number) => {
|
|
46
|
-
if (latched) {
|
|
47
|
-
latched = false;
|
|
48
|
-
return Promise.resolve();
|
|
49
|
-
}
|
|
50
|
-
return new Promise<void>((resolve) => {
|
|
51
|
-
const timer =
|
|
52
|
-
timeoutMs === undefined
|
|
53
|
-
? undefined
|
|
54
|
-
: setTimeout(() => {
|
|
55
|
-
wake = undefined;
|
|
56
|
-
resolve();
|
|
57
|
-
}, timeoutMs);
|
|
58
|
-
wake = () => {
|
|
59
|
-
clearTimeout(timer);
|
|
60
|
-
wake = undefined;
|
|
61
|
-
resolve();
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
open: () => {
|
|
66
|
-
if (wake) wake();
|
|
67
|
-
else latched = true;
|
|
68
|
-
},
|
|
69
|
-
};
|
|
43
|
+
export type Gate = { latched: boolean; wake: (() => void) | undefined };
|
|
44
|
+
|
|
45
|
+
const newGate = (): Gate => ({ latched: false, wake: undefined });
|
|
46
|
+
|
|
47
|
+
const takeGate = (g: Gate) => {
|
|
48
|
+
const was = g.latched;
|
|
49
|
+
g.latched = false;
|
|
50
|
+
return was;
|
|
70
51
|
};
|
|
71
52
|
|
|
72
|
-
|
|
53
|
+
const waitGate = (g: Gate, timeoutMs?: number) => {
|
|
54
|
+
if (g.latched) {
|
|
55
|
+
g.latched = false;
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
}
|
|
58
|
+
return new Promise<void>((resolve) => {
|
|
59
|
+
const timer =
|
|
60
|
+
timeoutMs === undefined
|
|
61
|
+
? undefined
|
|
62
|
+
: setTimeout(() => {
|
|
63
|
+
g.wake = undefined;
|
|
64
|
+
resolve();
|
|
65
|
+
}, timeoutMs);
|
|
66
|
+
g.wake = () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
g.wake = undefined;
|
|
69
|
+
resolve();
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const openGate = (g: Gate) => {
|
|
75
|
+
if (g.wake) g.wake();
|
|
76
|
+
else g.latched = true;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type AttemptOutcome = "stop" | "reattach" | "ended" | "idle";
|
|
73
80
|
|
|
74
81
|
export type Conn = {
|
|
75
82
|
connection: StatewireClient.Connection;
|
|
@@ -77,14 +84,14 @@ export type Conn = {
|
|
|
77
84
|
lease: string | null;
|
|
78
85
|
attempt: number;
|
|
79
86
|
episode: Episode | null;
|
|
87
|
+
awaitingHello: boolean;
|
|
88
|
+
held: StatewireCmd[];
|
|
80
89
|
gate: Gate;
|
|
81
90
|
abort: AbortController | null;
|
|
82
91
|
lifetime: AbortController | null;
|
|
83
92
|
ctx: StatewireRemoteProps;
|
|
84
93
|
onChange: (connection: StatewireClient.Connection) => void;
|
|
85
|
-
attemptOnce: (
|
|
86
|
-
abort: AbortController,
|
|
87
|
-
) => Promise<"stop" | "reattach" | "ended">;
|
|
94
|
+
attemptOnce: (abort: AbortController) => Promise<AttemptOutcome>;
|
|
88
95
|
notify: (connection: StatewireClient.Connection) => void;
|
|
89
96
|
reconnect: () => void;
|
|
90
97
|
};
|
|
@@ -96,84 +103,131 @@ const setStatus = (c: Conn, next: ConnectionState) => {
|
|
|
96
103
|
if (changed) c.onChange(c.connection);
|
|
97
104
|
};
|
|
98
105
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
106
|
+
const protocolError = (message: string): never => {
|
|
107
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// A hold's release delivers, never settles: fin and bare close both drain it.
|
|
111
|
+
const releaseHeld = (c: Conn) => {
|
|
112
|
+
if (c.held.length === 0) return;
|
|
113
|
+
const held = c.held;
|
|
114
|
+
c.held = [];
|
|
115
|
+
c.ctx.onCmds(held);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const cmdsOf = (env: StatewireEnvelope): StatewireCmd[] => {
|
|
119
|
+
if (env.cmd === undefined) return [];
|
|
120
|
+
if (!Array.isArray(env.cmd)) return protocolError("cmd must be an array");
|
|
121
|
+
return env.cmd;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleHello = (c: Conn, env: StatewireEnvelope) => {
|
|
125
|
+
const syn = env.syn;
|
|
126
|
+
if (syn === undefined) return protocolError("expected hello");
|
|
127
|
+
c.awaitingHello = false;
|
|
128
|
+
c.hasSnapshot = true;
|
|
129
|
+
c.ctx.onHello({
|
|
130
|
+
ops: env.ops === undefined ? [] : reviveOps(env.ops),
|
|
131
|
+
syn,
|
|
132
|
+
});
|
|
133
|
+
c.ctx.onIdle(syn.idle ?? false);
|
|
134
|
+
c.attempt = 0;
|
|
135
|
+
c.episode = null;
|
|
136
|
+
setStatus(c, { status: "live" });
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const deliverEnvelope = (c: Conn, env: StatewireEnvelope) => {
|
|
107
140
|
if (env.syn !== undefined) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
141
|
+
const syn = env.syn;
|
|
142
|
+
if (
|
|
143
|
+
syn.seq !== undefined ||
|
|
144
|
+
syn.ctxSeq !== undefined ||
|
|
145
|
+
syn.retry !== undefined
|
|
146
|
+
) {
|
|
147
|
+
c.ctx.onRefusal(syn);
|
|
148
|
+
} else if (syn.id === undefined) {
|
|
149
|
+
protocolError("invalid mid-stream syn");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (env.ops !== undefined) {
|
|
153
|
+
const held = c.held;
|
|
154
|
+
c.held = [];
|
|
155
|
+
c.ctx.onOps({
|
|
156
|
+
ops: reviveOps(env.ops),
|
|
157
|
+
cmds: [...held, ...cmdsOf(env)],
|
|
119
158
|
});
|
|
159
|
+
} else if (env.cmd !== undefined) {
|
|
160
|
+
const cmds = cmdsOf(env);
|
|
161
|
+
if (env.next === true) c.held.push(...cmds);
|
|
162
|
+
else c.ctx.onCmds(cmds);
|
|
120
163
|
}
|
|
164
|
+
if (typeof env.idle === "boolean") c.ctx.onIdle(env.idle);
|
|
121
165
|
};
|
|
122
166
|
|
|
123
167
|
const isTransientFinish = (
|
|
124
168
|
reason: StatewireHost.Fin["reason"],
|
|
125
169
|
): reason is "evicted" | "error" => reason === "evicted" || reason === "error";
|
|
126
170
|
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (finish) {
|
|
133
|
-
if (finish.meta !== undefined) {
|
|
134
|
-
const { meta, ...rest } = finish;
|
|
135
|
-
finish = { ...rest, payload: reviveValue(rest.payload, meta) };
|
|
136
|
-
}
|
|
137
|
-
c.ctx.onFinish(finish);
|
|
138
|
-
if (isTransientFinish(finish.reason)) {
|
|
139
|
-
c.episode = {
|
|
140
|
-
cause: finish.reason,
|
|
141
|
-
...(finish.message !== undefined && { message: finish.message }),
|
|
142
|
-
};
|
|
143
|
-
return "reattach";
|
|
144
|
-
}
|
|
145
|
-
setStatus(c, {
|
|
146
|
-
status: "gone",
|
|
147
|
-
...(finish.message !== undefined && { message: finish.message }),
|
|
148
|
-
...(finish.payload !== undefined && { payload: finish.payload }),
|
|
149
|
-
});
|
|
150
|
-
return "stop";
|
|
171
|
+
const settleFin = (c: Conn, fin: StatewireHost.Fin): AttemptOutcome => {
|
|
172
|
+
let finish = fin;
|
|
173
|
+
if (finish.meta !== undefined) {
|
|
174
|
+
const { meta, ...rest } = finish;
|
|
175
|
+
finish = { ...rest, payload: reviveValue(rest.payload, meta) };
|
|
151
176
|
}
|
|
152
|
-
|
|
177
|
+
releaseHeld(c);
|
|
178
|
+
if (finish.reason === "idle") {
|
|
179
|
+
c.ctx.onIdle(true);
|
|
153
180
|
c.attempt = 0;
|
|
154
181
|
c.episode = null;
|
|
155
|
-
setStatus(c, { status: "
|
|
182
|
+
setStatus(c, { status: "idle" });
|
|
183
|
+
return "idle";
|
|
156
184
|
}
|
|
157
|
-
|
|
185
|
+
c.ctx.onFinish(finish);
|
|
186
|
+
if (isTransientFinish(finish.reason)) {
|
|
187
|
+
c.episode = {
|
|
188
|
+
cause: finish.reason,
|
|
189
|
+
...(finish.message !== undefined && { message: finish.message }),
|
|
190
|
+
};
|
|
191
|
+
return "reattach";
|
|
192
|
+
}
|
|
193
|
+
setStatus(c, {
|
|
194
|
+
status: "gone",
|
|
195
|
+
...(finish.message !== undefined && { message: finish.message }),
|
|
196
|
+
...(finish.payload !== undefined && { payload: finish.payload }),
|
|
197
|
+
});
|
|
198
|
+
return "stop";
|
|
158
199
|
};
|
|
159
200
|
|
|
160
201
|
export const handleEnvelope = (
|
|
161
202
|
c: Conn,
|
|
162
203
|
env: StatewireEnvelope,
|
|
163
|
-
):
|
|
204
|
+
): AttemptOutcome | null => {
|
|
205
|
+
if (typeof env !== "object" || env === null) {
|
|
206
|
+
return protocolError("packet must be an object");
|
|
207
|
+
}
|
|
208
|
+
if (c.awaitingHello) {
|
|
209
|
+
if (env.fin !== undefined) return settleFin(c, env.fin);
|
|
210
|
+
handleHello(c, env);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
164
213
|
deliverEnvelope(c, env);
|
|
165
|
-
return
|
|
214
|
+
if (env.fin !== undefined) return settleFin(c, env.fin);
|
|
215
|
+
if (c.connection.status !== "live") {
|
|
216
|
+
c.attempt = 0;
|
|
217
|
+
c.episode = null;
|
|
218
|
+
setStatus(c, { status: "live" });
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
166
221
|
};
|
|
167
222
|
|
|
168
223
|
const scheduleRetry = (
|
|
169
224
|
c: Conn,
|
|
170
225
|
failure: string | undefined,
|
|
171
226
|
immediate: boolean,
|
|
172
|
-
cause?: unknown,
|
|
173
227
|
) => {
|
|
174
228
|
c.attempt += 1;
|
|
175
229
|
c.episode ??= { cause: "dropped" };
|
|
176
|
-
const delay = immediate ? 0 :
|
|
230
|
+
const delay = immediate ? 0 : backoff(c.attempt);
|
|
177
231
|
if (c.hasSnapshot) {
|
|
178
232
|
setStatus(c, {
|
|
179
233
|
status: "reconnecting",
|
|
@@ -188,7 +242,7 @@ const scheduleRetry = (
|
|
|
188
242
|
|
|
189
243
|
const reconnectConn = (c: Conn) => {
|
|
190
244
|
if (!c.lifetime) return;
|
|
191
|
-
if (c.connection.status === "gone") {
|
|
245
|
+
if (c.connection.status === "gone" || c.connection.status === "idle") {
|
|
192
246
|
c.attempt = 0;
|
|
193
247
|
c.episode = { cause: "dropped" };
|
|
194
248
|
flushTapSync(() => {
|
|
@@ -196,7 +250,7 @@ const reconnectConn = (c: Conn) => {
|
|
|
196
250
|
else setStatus(c, { status: "connecting" });
|
|
197
251
|
});
|
|
198
252
|
}
|
|
199
|
-
c.gate
|
|
253
|
+
openGate(c.gate);
|
|
200
254
|
c.abort?.abort();
|
|
201
255
|
};
|
|
202
256
|
|
|
@@ -205,30 +259,33 @@ const run = async (c: Conn, lifetime: AbortController, gate: Gate) => {
|
|
|
205
259
|
while (alive()) {
|
|
206
260
|
const abort = new AbortController();
|
|
207
261
|
c.abort = abort;
|
|
262
|
+
c.awaitingHello = true;
|
|
263
|
+
c.held = [];
|
|
264
|
+
let outcome: AttemptOutcome | undefined;
|
|
208
265
|
let failure: string | undefined;
|
|
209
|
-
let cause: unknown;
|
|
210
|
-
let gone = false;
|
|
211
266
|
try {
|
|
212
|
-
|
|
267
|
+
outcome = await c.attemptOnce(abort);
|
|
213
268
|
} catch (error) {
|
|
214
269
|
if (!abort.signal.aborted) {
|
|
215
270
|
failure =
|
|
216
271
|
error instanceof Error && error.message ? error.message : "network";
|
|
217
|
-
cause = error;
|
|
218
272
|
}
|
|
219
273
|
}
|
|
220
274
|
abort.abort();
|
|
221
275
|
if (!alive()) return;
|
|
222
|
-
|
|
223
|
-
|
|
276
|
+
flushTapSync(() => {
|
|
277
|
+
releaseHeld(c);
|
|
278
|
+
c.ctx.onDetach();
|
|
279
|
+
});
|
|
280
|
+
if (!alive()) return;
|
|
281
|
+
if (outcome === "stop" || outcome === "idle") {
|
|
282
|
+
await waitGate(gate);
|
|
224
283
|
continue;
|
|
225
284
|
}
|
|
226
|
-
const immediate = gate
|
|
227
|
-
const delay = flushTapSync(() =>
|
|
228
|
-
scheduleRetry(c, failure, immediate, cause),
|
|
229
|
-
);
|
|
285
|
+
const immediate = takeGate(gate);
|
|
286
|
+
const delay = flushTapSync(() => scheduleRetry(c, failure, immediate));
|
|
230
287
|
if (delay === 0) continue;
|
|
231
|
-
await gate
|
|
288
|
+
await waitGate(gate, delay);
|
|
232
289
|
}
|
|
233
290
|
};
|
|
234
291
|
|
|
@@ -236,10 +293,7 @@ export const useStatewireConn = (props: {
|
|
|
236
293
|
url: string;
|
|
237
294
|
onChange?: ((connection: StatewireClient.Connection) => void) | undefined;
|
|
238
295
|
ctx: StatewireRemoteProps;
|
|
239
|
-
attemptOnce: (
|
|
240
|
-
c: Conn,
|
|
241
|
-
abort: AbortController,
|
|
242
|
-
) => Promise<"stop" | "reattach" | "ended">;
|
|
296
|
+
attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;
|
|
243
297
|
}): Conn => {
|
|
244
298
|
const latest = useRef(props);
|
|
245
299
|
latest.current = props;
|
|
@@ -250,10 +304,14 @@ export const useStatewireConn = (props: {
|
|
|
250
304
|
lease: null,
|
|
251
305
|
attempt: 0,
|
|
252
306
|
episode: null,
|
|
307
|
+
awaitingHello: true,
|
|
308
|
+
held: [],
|
|
253
309
|
abort: null,
|
|
254
|
-
gate:
|
|
310
|
+
gate: newGate(),
|
|
255
311
|
lifetime: null,
|
|
256
|
-
ctx
|
|
312
|
+
get ctx() {
|
|
313
|
+
return latest.current.ctx;
|
|
314
|
+
},
|
|
257
315
|
onChange: (connection) => latest.current.onChange?.(connection),
|
|
258
316
|
attemptOnce: (abort) => latest.current.attemptOnce(conn, abort),
|
|
259
317
|
notify: setConnection,
|
|
@@ -273,13 +331,13 @@ export const useStatewireConn = (props: {
|
|
|
273
331
|
}
|
|
274
332
|
const lifetime = new AbortController();
|
|
275
333
|
c.lifetime = lifetime;
|
|
276
|
-
c.gate =
|
|
334
|
+
c.gate = newGate();
|
|
277
335
|
void run(c, lifetime, c.gate);
|
|
278
336
|
return () => {
|
|
279
337
|
c.abort?.abort();
|
|
280
338
|
lifetime.abort();
|
|
281
339
|
if (c.lifetime === lifetime) c.lifetime = null;
|
|
282
|
-
c.gate
|
|
340
|
+
openGate(c.gate);
|
|
283
341
|
};
|
|
284
342
|
}, [c, props.url]);
|
|
285
343
|
|
|
@@ -288,14 +346,3 @@ export const useStatewireConn = (props: {
|
|
|
288
346
|
|
|
289
347
|
export const classify = (timeout: AbortSignal) =>
|
|
290
348
|
new Error(timeout.aborted ? "timeout" : "network");
|
|
291
|
-
|
|
292
|
-
export const parseRetryAfter = (header: string | null): number | undefined => {
|
|
293
|
-
if (!header) return undefined;
|
|
294
|
-
const value = header.trim();
|
|
295
|
-
if (/^\d+$/.test(value)) return Number(value) * 1000;
|
|
296
|
-
const date = Date.parse(value);
|
|
297
|
-
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
export const overloadedError = (retryAfterMs?: number) =>
|
|
301
|
-
Object.assign(new Error("overloaded"), { retryAfterMs });
|