@norskvideo/moq-net 0.1.8 → 0.2.1
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/announce.d.ts +7 -0
- package/announce.d.ts.map +1 -0
- package/announce.js +8 -0
- package/announce.js.map +1 -0
- package/announced.d.ts +49 -91
- package/announced.d.ts.map +1 -1
- package/announced.js +21 -156
- package/announced.js.map +1 -1
- package/bandwidth.d.ts +163 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +304 -0
- package/bandwidth.js.map +1 -0
- package/bandwidth_api.d.ts +7 -0
- package/bandwidth_api.d.ts.map +1 -0
- package/bandwidth_api.js +8 -0
- package/bandwidth_api.js.map +1 -0
- package/broadcast.d.ts +44 -35
- package/broadcast.d.ts.map +1 -1
- package/broadcast.js +104 -60
- package/broadcast.js.map +1 -1
- package/connection/accept.d.ts +16 -1
- package/connection/accept.d.ts.map +1 -1
- package/connection/accept.js +52 -28
- package/connection/accept.js.map +1 -1
- package/connection/browser.d.ts.map +1 -1
- package/connection/browser.js +9 -7
- package/connection/browser.js.map +1 -1
- package/connection/connect.d.ts +30 -6
- package/connection/connect.d.ts.map +1 -1
- package/connection/connect.js +110 -54
- package/connection/connect.js.map +1 -1
- package/connection/established.d.ts +17 -21
- package/connection/established.d.ts.map +1 -1
- package/connection/established.js.map +1 -1
- package/connection/forward.d.ts +2 -0
- package/connection/forward.d.ts.map +1 -0
- package/connection/forward.js +173 -0
- package/connection/forward.js.map +1 -0
- package/connection/handshake.d.ts +1 -0
- package/connection/handshake.d.ts.map +1 -1
- package/connection/handshake.js +5 -2
- package/connection/handshake.js.map +1 -1
- package/connection/index.d.ts +5 -5
- package/connection/index.d.ts.map +1 -1
- package/connection/index.js +4 -5
- package/connection/index.js.map +1 -1
- package/connection/pool.d.ts +186 -0
- package/connection/pool.d.ts.map +1 -0
- package/connection/pool.js +361 -0
- package/connection/pool.js.map +1 -0
- package/connection/reload.d.ts +14 -93
- package/connection/reload.d.ts.map +1 -1
- package/connection/reload.js +217 -81
- package/connection/reload.js.map +1 -1
- package/connection/stats.d.ts +3 -26
- package/connection/stats.d.ts.map +1 -1
- package/connection/stats.js.map +1 -1
- package/connection/transport.d.ts +0 -7
- package/connection/transport.d.ts.map +1 -1
- package/consume.d.ts +1 -43
- package/consume.d.ts.map +1 -1
- package/consume.js +1 -1
- package/consume.js.map +1 -1
- package/error.d.ts +180 -29
- package/error.d.ts.map +1 -1
- package/error.js +331 -16
- package/error.js.map +1 -1
- package/errors.d.ts +7 -0
- package/errors.d.ts.map +1 -0
- package/errors.js +8 -0
- package/errors.js.map +1 -0
- package/group.d.ts +9 -43
- package/group.d.ts.map +1 -1
- package/group.js +284 -69
- package/group.js.map +1 -1
- package/hop.d.ts +115 -0
- package/hop.d.ts.map +1 -0
- package/hop.js +119 -0
- package/hop.js.map +1 -0
- package/ietf/adapter.d.ts +5 -1
- package/ietf/adapter.d.ts.map +1 -1
- package/ietf/adapter.js +105 -60
- package/ietf/adapter.js.map +1 -1
- package/ietf/aliases.d.ts +1 -78
- package/ietf/aliases.d.ts.map +1 -1
- package/ietf/cluster.d.ts +9 -123
- package/ietf/cluster.d.ts.map +1 -1
- package/ietf/cluster.js +84 -44
- package/ietf/cluster.js.map +1 -1
- package/ietf/connection.d.ts +6 -72
- package/ietf/connection.d.ts.map +1 -1
- package/ietf/connection.js +62 -56
- package/ietf/connection.js.map +1 -1
- package/ietf/error.d.ts +11 -0
- package/ietf/error.d.ts.map +1 -0
- package/ietf/error.js +193 -0
- package/ietf/error.js.map +1 -0
- package/ietf/fetch.d.ts +7 -20
- package/ietf/fetch.d.ts.map +1 -1
- package/ietf/fetch.js +52 -22
- package/ietf/fetch.js.map +1 -1
- package/ietf/filter.d.ts +2 -0
- package/ietf/filter.d.ts.map +1 -1
- package/ietf/filter.js +10 -1
- package/ietf/filter.js.map +1 -1
- package/ietf/goaway.d.ts.map +1 -1
- package/ietf/goaway.js +22 -5
- package/ietf/goaway.js.map +1 -1
- package/ietf/hidden.d.ts +2 -0
- package/ietf/hidden.d.ts.map +1 -0
- package/ietf/hidden.js +30 -0
- package/ietf/hidden.js.map +1 -0
- package/ietf/index.d.ts +2 -0
- package/ietf/index.d.ts.map +1 -1
- package/ietf/index.js +2 -0
- package/ietf/index.js.map +1 -1
- package/ietf/object.d.ts +15 -8
- package/ietf/object.d.ts.map +1 -1
- package/ietf/object.js +51 -36
- package/ietf/object.js.map +1 -1
- package/ietf/parameters.d.ts +14 -2
- package/ietf/parameters.d.ts.map +1 -1
- package/ietf/parameters.js +97 -29
- package/ietf/parameters.js.map +1 -1
- package/ietf/properties.d.ts +1 -0
- package/ietf/properties.d.ts.map +1 -1
- package/ietf/properties.js +14 -0
- package/ietf/properties.js.map +1 -1
- package/ietf/publish.d.ts +19 -2
- package/ietf/publish.d.ts.map +1 -1
- package/ietf/publish.js +40 -6
- package/ietf/publish.js.map +1 -1
- package/ietf/publish_namespace.d.ts +25 -0
- package/ietf/publish_namespace.d.ts.map +1 -1
- package/ietf/publish_namespace.js +63 -0
- package/ietf/publish_namespace.js.map +1 -1
- package/ietf/publisher.d.ts +1 -82
- package/ietf/publisher.d.ts.map +1 -1
- package/ietf/publisher.js +483 -235
- package/ietf/publisher.js.map +1 -1
- package/ietf/solicit.d.ts +1 -40
- package/ietf/solicit.d.ts.map +1 -1
- package/ietf/subscribe.d.ts +8 -6
- package/ietf/subscribe.d.ts.map +1 -1
- package/ietf/subscribe.js +33 -27
- package/ietf/subscribe.js.map +1 -1
- package/ietf/subscribe_namespace.d.ts +8 -2
- package/ietf/subscribe_namespace.d.ts.map +1 -1
- package/ietf/subscribe_namespace.js +18 -8
- package/ietf/subscribe_namespace.js.map +1 -1
- package/ietf/subscriber.d.ts +1 -65
- package/ietf/subscriber.d.ts.map +1 -1
- package/ietf/subscriber.js +337 -123
- package/ietf/subscriber.js.map +1 -1
- package/ietf/token.d.ts +2 -0
- package/ietf/token.d.ts.map +1 -0
- package/ietf/token.js +99 -0
- package/ietf/token.js.map +1 -0
- package/ietf/track.d.ts +4 -0
- package/ietf/track.d.ts.map +1 -1
- package/ietf/track.js +6 -20
- package/ietf/track.js.map +1 -1
- package/ietf/version.d.ts +12 -1
- package/ietf/version.d.ts.map +1 -1
- package/ietf/version.js +13 -0
- package/ietf/version.js.map +1 -1
- package/index.d.ts +12 -7
- package/index.d.ts.map +1 -1
- package/index.js +10 -5
- package/index.js.map +1 -1
- package/internal.d.ts +115 -1
- package/internal.d.ts.map +1 -1
- package/internal.js +108 -0
- package/internal.js.map +1 -1
- package/lite/announce.d.ts +60 -10
- package/lite/announce.d.ts.map +1 -1
- package/lite/announce.js +176 -31
- package/lite/announce.js.map +1 -1
- package/lite/connection.d.ts +9 -59
- package/lite/connection.d.ts.map +1 -1
- package/lite/connection.js +34 -35
- package/lite/connection.js.map +1 -1
- package/lite/datagram.d.ts +3 -2
- package/lite/datagram.d.ts.map +1 -1
- package/lite/datagram.js +7 -8
- package/lite/datagram.js.map +1 -1
- package/lite/fetch.d.ts +15 -1
- package/lite/fetch.d.ts.map +1 -1
- package/lite/fetch.js +39 -7
- package/lite/fetch.js.map +1 -1
- package/lite/goaway.d.ts.map +1 -1
- package/lite/goaway.js +7 -1
- package/lite/goaway.js.map +1 -1
- package/lite/group.d.ts +30 -12
- package/lite/group.d.ts.map +1 -1
- package/lite/group.js +68 -26
- package/lite/group.js.map +1 -1
- package/lite/message.d.ts +2 -2
- package/lite/message.d.ts.map +1 -1
- package/lite/message.js +14 -5
- package/lite/message.js.map +1 -1
- package/lite/priority.d.ts +1 -61
- package/lite/priority.d.ts.map +1 -1
- package/lite/priority.js +4 -5
- package/lite/priority.js.map +1 -1
- package/lite/publisher.d.ts +1 -69
- package/lite/publisher.d.ts.map +1 -1
- package/lite/publisher.js +630 -262
- package/lite/publisher.js.map +1 -1
- package/lite/setup.d.ts +8 -8
- package/lite/setup.d.ts.map +1 -1
- package/lite/setup.js +33 -30
- package/lite/setup.js.map +1 -1
- package/lite/subscribe.d.ts +71 -17
- package/lite/subscribe.d.ts.map +1 -1
- package/lite/subscribe.js +205 -53
- package/lite/subscribe.js.map +1 -1
- package/lite/subscriber.d.ts +15 -56
- package/lite/subscriber.d.ts.map +1 -1
- package/lite/subscriber.js +451 -247
- package/lite/subscriber.js.map +1 -1
- package/lite/track.d.ts +4 -10
- package/lite/track.d.ts.map +1 -1
- package/lite/track.js +34 -29
- package/lite/track.js.map +1 -1
- package/lite/version.d.ts +42 -6
- package/lite/version.d.ts.map +1 -1
- package/lite/version.js +126 -10
- package/lite/version.js.map +1 -1
- package/origin.d.ts +256 -29
- package/origin.d.ts.map +1 -1
- package/origin.js +1427 -37
- package/origin.js.map +1 -1
- package/package.json +8 -3
- package/path.d.ts +25 -7
- package/path.d.ts.map +1 -1
- package/path.js +5 -3
- package/path.js.map +1 -1
- package/stream.d.ts +73 -14
- package/stream.d.ts.map +1 -1
- package/stream.js +372 -141
- package/stream.js.map +1 -1
- package/tail.d.ts +18 -0
- package/tail.d.ts.map +1 -0
- package/tail.js +167 -0
- package/tail.js.map +1 -0
- package/time.d.ts +15 -2
- package/time.d.ts.map +1 -1
- package/time.js +28 -9
- package/time.js.map +1 -1
- package/track.d.ts +211 -83
- package/track.d.ts.map +1 -1
- package/track.js +816 -205
- package/track.js.map +1 -1
- package/util/abort.d.ts +2 -0
- package/util/abort.d.ts.map +1 -0
- package/util/abort.js +20 -0
- package/util/abort.js.map +1 -0
- package/util/log.d.ts +5 -0
- package/util/log.d.ts.map +1 -0
- package/util/log.js +17 -0
- package/util/log.js.map +1 -0
- package/util/u64.d.ts +39 -0
- package/util/u64.d.ts.map +1 -0
- package/util/u64.js +83 -0
- package/util/u64.js.map +1 -0
- package/util/varint.d.ts +29 -0
- package/util/varint.d.ts.map +1 -0
- package/util/varint.js +198 -0
- package/util/varint.js.map +1 -0
- package/varint.d.ts +10 -6
- package/varint.d.ts.map +1 -1
- package/varint.js +40 -237
- package/varint.js.map +1 -1
- package/wire.d.ts +80 -0
- package/wire.d.ts.map +1 -0
- package/wire.js +32 -0
- package/wire.js.map +1 -0
- package/zod.d.ts +1 -1
- package/zod.d.ts.map +1 -1
- package/zod.js.map +1 -1
- package/mock.d.ts +0 -66
- package/mock.d.ts.map +0 -1
- package/mock.js +0 -243
- package/mock.js.map +0 -1
package/connection/reload.d.ts
CHANGED
|
@@ -1,104 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as Announce from "../announced";
|
|
3
|
-
import type * as Path from "../path";
|
|
4
|
-
import { type ConnectProps, type WebSocketOptions, type WebTransportProps } from "./connect";
|
|
5
|
-
import type { Established } from "./established";
|
|
6
|
-
import type { Probe, Stats } from "./stats";
|
|
1
|
+
import * as Time from "../time";
|
|
7
2
|
/**
|
|
8
|
-
* Exponential backoff settings for
|
|
3
|
+
* Exponential backoff settings for the reconnect loop.
|
|
9
4
|
*
|
|
10
5
|
* The delays carry jitter, so a fleet of tabs knocked offline together doesn't reconnect in
|
|
11
|
-
* lockstep. Every failure is retried; {@link ReloadDelay.timeout} is what stops the
|
|
6
|
+
* lockstep. Every failure is retried; {@link ReloadDelay.timeout} is what stops the current
|
|
7
|
+
* URL. A new URL or a disable/re-enable starts another sequence.
|
|
12
8
|
*/
|
|
13
9
|
export type ReloadDelay = {
|
|
14
|
-
/** The delay
|
|
15
|
-
initial
|
|
10
|
+
/** The delay before reconnecting (default: 1000ms). */
|
|
11
|
+
initial?: Time.Milli;
|
|
16
12
|
/** The multiplier for the delay (default: 2). */
|
|
17
|
-
multiplier
|
|
18
|
-
/** The maximum delay
|
|
19
|
-
max
|
|
13
|
+
multiplier?: number;
|
|
14
|
+
/** The maximum delay (default: 5000ms). */
|
|
15
|
+
max?: Time.Milli;
|
|
20
16
|
/**
|
|
21
|
-
* Maximum total time
|
|
22
|
-
*
|
|
17
|
+
* Maximum total time to spend retrying the current URL before giving up
|
|
18
|
+
* (default: 10000ms). Resets after each successful connection, a URL change, or a
|
|
19
|
+
* disable/re-enable. Set to 0 for unlimited retries.
|
|
23
20
|
*/
|
|
24
|
-
timeout?:
|
|
21
|
+
timeout?: Time.Milli;
|
|
25
22
|
};
|
|
26
|
-
/**
|
|
27
|
-
export type ReloadProps = Omit<ConnectProps, "signal"> & {
|
|
28
|
-
/** Whether to reload the connection when it disconnects (default: true). */
|
|
29
|
-
enabled?: boolean | Signal<boolean>;
|
|
30
|
-
/** The URL of the relay server. */
|
|
31
|
-
url?: URL | Signal<URL | undefined>;
|
|
32
|
-
/** Backoff settings for the reconnect loop. */
|
|
33
|
-
delay?: ReloadDelay;
|
|
34
|
-
};
|
|
35
|
-
/** Current state of a {@link Reload} connection. */
|
|
23
|
+
/** Current state of a reconnecting connection. */
|
|
36
24
|
export type ReloadStatus = "connecting" | "connected" | "disconnected";
|
|
37
|
-
/** Maintains a MoQ connection, reconnecting with exponential backoff when it drops. */
|
|
38
|
-
export declare class Reload {
|
|
39
|
-
#private;
|
|
40
|
-
/** Relay URL to connect to; updating it triggers a reconnect. */
|
|
41
|
-
url: Signal<URL | undefined>;
|
|
42
|
-
/** Whether reconnecting is active. */
|
|
43
|
-
enabled: Signal<boolean>;
|
|
44
|
-
/** Current connection status. */
|
|
45
|
-
status: Signal<ReloadStatus>;
|
|
46
|
-
/** The currently established session, or undefined while disconnected. */
|
|
47
|
-
established: Signal<Established | undefined>;
|
|
48
|
-
/**
|
|
49
|
-
* The current connection's PROBE estimates, spanning reconnects.
|
|
50
|
-
*
|
|
51
|
-
* Undefined while disconnected: the estimates belong to a single connection.
|
|
52
|
-
* See {@link Established.probe}.
|
|
53
|
-
*/
|
|
54
|
-
readonly probe: Getter<Probe | undefined>;
|
|
55
|
-
/** WebTransport options applied to each connection attempt (not reactive). */
|
|
56
|
-
webtransport?: WebTransportProps;
|
|
57
|
-
/** WebSocket fallback options applied to each connection attempt (not reactive). */
|
|
58
|
-
websocket: WebSocketOptions | undefined;
|
|
59
|
-
/**
|
|
60
|
-
* Whether the relay supports broadcast discovery, applied to each connection attempt (not
|
|
61
|
-
* reactive). Undefined defers to the default for the URL. See {@link Established.discovery}.
|
|
62
|
-
*/
|
|
63
|
-
discovery?: boolean;
|
|
64
|
-
/** Backoff settings for the reconnect loop. */
|
|
65
|
-
delay: ReloadDelay;
|
|
66
|
-
/**
|
|
67
|
-
* Resolves when the reconnect loop stops via {@link Reload.close}.
|
|
68
|
-
*
|
|
69
|
-
* Rejects when the loop gives up instead, carrying the failure that was in flight when the
|
|
70
|
-
* retry window expired.
|
|
71
|
-
*/
|
|
72
|
-
closed: Promise<void>;
|
|
73
|
-
constructor(props?: ReloadProps);
|
|
74
|
-
/**
|
|
75
|
-
* Subscribe to broadcast announcements under an optional prefix, spanning reconnects.
|
|
76
|
-
*
|
|
77
|
-
* The same {@link Announce.Consumer} stream as {@link Established.announced}, but everything active
|
|
78
|
-
* is retracted (an `active: false` update) whenever the connection drops and re-announced on
|
|
79
|
-
* reconnect, so a consumer draining `next()` never clings to a dead route across a reconnect.
|
|
80
|
-
*
|
|
81
|
-
* Stays empty while the relay lacks {@link Established.discovery}.
|
|
82
|
-
*/
|
|
83
|
-
announced(prefix?: Path.Valid): Announce.Consumer;
|
|
84
|
-
/**
|
|
85
|
-
* A reactive handle to one broadcast, spanning reconnects.
|
|
86
|
-
*
|
|
87
|
-
* The same {@link Announce.Broadcast} as {@link Established.announcedBroadcast}, but it
|
|
88
|
-
* follows the reconnect loop: the broadcast drops to `undefined` when the connection dies
|
|
89
|
-
* and resolves again once the new connection announces the path. Use it instead of
|
|
90
|
-
* consuming off {@link Reload.established} whenever the broadcast may come online after you
|
|
91
|
-
* do, which is exactly the case a blind `consume` loses.
|
|
92
|
-
*
|
|
93
|
-
* Close the handle when done; {@link Reload.close} only drops it to `undefined`.
|
|
94
|
-
*/
|
|
95
|
-
announcedBroadcast(path: Path.Valid): Announce.Broadcast;
|
|
96
|
-
/**
|
|
97
|
-
* Snapshot the live connection's transport counters, or undefined while disconnected.
|
|
98
|
-
* See {@link Established.stats}.
|
|
99
|
-
*/
|
|
100
|
-
stats(): Promise<Stats | undefined>;
|
|
101
|
-
/** Stop reconnecting, close the current connection, and resolve {@link Reload.closed}. */
|
|
102
|
-
close(): void;
|
|
103
|
-
}
|
|
104
25
|
//# sourceMappingURL=reload.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reload.d.ts","sourceRoot":"","sources":["../../src/connection/reload.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reload.d.ts","sourceRoot":"","sources":["../../src/connection/reload.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAMnC;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACzB,uDAAuD;IACvD,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;IAErB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,2CAA2C;IAC3C,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;CACrB,CAAC;AA+CF,kDAAkD;AAClD,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC"}
|
package/connection/reload.js
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
/* @ts-self-types="./reload.d.ts" */
|
|
2
|
-
import { Effect, Signal } from "@norskvideo/moq-signals";
|
|
2
|
+
import { Effect, Once, Signal } from "@norskvideo/moq-signals";
|
|
3
3
|
import * as Announce from "../announced.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Allocator } from "../bandwidth.js";
|
|
5
|
+
import { error, SessionCode, SessionError } from "../error.js";
|
|
6
|
+
import * as Path from "../path.js";
|
|
7
|
+
import * as Time from "../time.js";
|
|
8
|
+
import { wireOf } from "../wire.js";
|
|
6
9
|
import { connect } from "./connect.js";
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
11
|
+
* The backoff applied to whichever {@link ReloadDelay} fields a caller leaves out.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
* surface
|
|
13
|
+
* The timeout is short on purpose: a failure that clears within it was transient, and one that
|
|
14
|
+
* doesn't should surface on {@link Reload.error} rather than leave the page silently
|
|
15
|
+
* reconnecting for minutes. A loop nobody watches wants `timeout: 0` instead, since there is
|
|
16
|
+
* no one to react. Giving up does not dispose the loop: a new URL or a disable/re-enable
|
|
17
|
+
* starts another sequence.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_DELAY = {
|
|
20
|
+
initial: Time.Milli(1000),
|
|
21
|
+
multiplier: 2,
|
|
22
|
+
max: Time.Milli(5000),
|
|
23
|
+
timeout: Time.Milli(10000),
|
|
24
|
+
};
|
|
25
|
+
/** How often the send-rate estimate is sampled from the live transport. */
|
|
26
|
+
const BANDWIDTH_POLL = 100;
|
|
27
|
+
/**
|
|
28
|
+
* The reconnect loop behind a Connection handle: connects, waits for the session to end, then
|
|
29
|
+
* redials with exponential backoff.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
12
32
|
*/
|
|
13
|
-
const DEFAULT_TIMEOUT = 10000;
|
|
14
|
-
/** Maintains a MoQ connection, reconnecting with exponential backoff when it drops. */
|
|
15
33
|
export class Reload {
|
|
16
34
|
/** Relay URL to connect to; updating it triggers a reconnect. */
|
|
17
35
|
url;
|
|
@@ -19,6 +37,14 @@ export class Reload {
|
|
|
19
37
|
enabled;
|
|
20
38
|
/** Current connection status. */
|
|
21
39
|
status = new Signal("disconnected");
|
|
40
|
+
/**
|
|
41
|
+
* The failure that stopped retrying the current URL, or undefined while the loop is live.
|
|
42
|
+
*
|
|
43
|
+
* Set on an auth rejection or when the retry window expires. Cleared when a new URL or a
|
|
44
|
+
* disable/re-enable starts another sequence, not on a page hide/show. Transient drops
|
|
45
|
+
* that are still being retried leave this empty.
|
|
46
|
+
*/
|
|
47
|
+
error;
|
|
22
48
|
/** The currently established session, or undefined while disconnected. */
|
|
23
49
|
established = new Signal(undefined);
|
|
24
50
|
/**
|
|
@@ -28,6 +54,14 @@ export class Reload {
|
|
|
28
54
|
* See {@link Established.probe}.
|
|
29
55
|
*/
|
|
30
56
|
probe;
|
|
57
|
+
/**
|
|
58
|
+
* Divides this connection's send-rate estimate among the tracks sharing it.
|
|
59
|
+
*
|
|
60
|
+
* The same instance across reconnects, so reservations survive a drop. The
|
|
61
|
+
* estimate is `undefined` while disconnected, which tells a sender to hold
|
|
62
|
+
* its rate rather than encode at zero.
|
|
63
|
+
*/
|
|
64
|
+
bandwidth;
|
|
31
65
|
/** WebTransport options applied to each connection attempt (not reactive). */
|
|
32
66
|
webtransport;
|
|
33
67
|
/** WebSocket fallback options applied to each connection attempt (not reactive). */
|
|
@@ -37,23 +71,50 @@ export class Reload {
|
|
|
37
71
|
* reactive). Undefined defers to the default for the URL. See {@link Established.discovery}.
|
|
38
72
|
*/
|
|
39
73
|
discovery;
|
|
40
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The origin whose broadcasts are served, spanning reconnects (not reactive).
|
|
76
|
+
*
|
|
77
|
+
* Each session announces the origin's table when it attaches, so a broadcast published
|
|
78
|
+
* while offline surfaces on the next connection and a reconnect re-announces everything
|
|
79
|
+
* still published. See the `publish` connect option.
|
|
80
|
+
*/
|
|
81
|
+
publish;
|
|
82
|
+
/**
|
|
83
|
+
* The origin fed with the peer's announced broadcasts, spanning reconnects (not
|
|
84
|
+
* reactive).
|
|
85
|
+
*
|
|
86
|
+
* The entries a session fed retract when it dies, and the next session re-populates the
|
|
87
|
+
* table, so a consumer watching the origin sees offline/online transitions across a
|
|
88
|
+
* reconnect. See the `consume` connect option.
|
|
89
|
+
*/
|
|
90
|
+
consume;
|
|
91
|
+
/** Backoff settings for the reconnect loop; an unset field uses its default. */
|
|
41
92
|
delay;
|
|
42
93
|
/** The reactive effect scope driving the connect loop; closed by {@link Reload.close}. */
|
|
43
94
|
#signals = new Effect();
|
|
95
|
+
// Sampled from the live session; undefined while disconnected or the transport has none.
|
|
96
|
+
#estimate = new Signal(undefined);
|
|
44
97
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* retry window expired.
|
|
98
|
+
* Settles once this loop is disposed via {@link Reload.close}: `null` on a clean close,
|
|
99
|
+
* or the abort {@link Error}. Peek it synchronously (`undefined` while open), observe it
|
|
100
|
+
* reactively, or `await` it. Attempt failures live on {@link Reload.error} instead.
|
|
49
101
|
*/
|
|
50
|
-
closed
|
|
51
|
-
|
|
52
|
-
|
|
102
|
+
get closed() {
|
|
103
|
+
return this.#closed;
|
|
104
|
+
}
|
|
105
|
+
#closed = new Once();
|
|
106
|
+
#error = new Signal(undefined);
|
|
53
107
|
// The current wait between attempts, doubling per failure, and when the retry window expires.
|
|
54
108
|
// Both are undefined between sequences, so a later edit to `delay` applies to the next one.
|
|
55
109
|
#delay;
|
|
56
110
|
#deadline;
|
|
111
|
+
// The URL the current retry sequence is for. Cleared when disabled, URL-less, or given
|
|
112
|
+
// up, so the next attempt starts a fresh backoff window.
|
|
113
|
+
#sequenceHref;
|
|
114
|
+
// The href that exhausted its retry sequence. Survives a page hide/show so resume does
|
|
115
|
+
// not redial credentials the peer already refused. Cleared when the URL changes or the
|
|
116
|
+
// loop is disabled.
|
|
117
|
+
#givenUpHref;
|
|
57
118
|
// Increased by 1 each time to trigger a reload.
|
|
58
119
|
#tick = new Signal(0);
|
|
59
120
|
// True after the browser freezes or hides the page until it visibly resumes.
|
|
@@ -63,25 +124,30 @@ export class Reload {
|
|
|
63
124
|
#url;
|
|
64
125
|
constructor(props) {
|
|
65
126
|
this.url = Signal.from(props?.url);
|
|
66
|
-
this.enabled = Signal.from(props?.enabled ??
|
|
67
|
-
this.delay = props?.delay ?? {
|
|
127
|
+
this.enabled = Signal.from(props?.enabled ?? true);
|
|
128
|
+
this.delay = props?.delay ?? {};
|
|
68
129
|
this.webtransport = props?.webtransport;
|
|
69
130
|
this.websocket = props?.websocket;
|
|
70
131
|
this.discovery = props?.discovery;
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
132
|
+
this.publish = props?.publish;
|
|
133
|
+
this.consume = props?.consume;
|
|
134
|
+
// Requests on the consume origin stay pending across a reconnect, and before the
|
|
135
|
+
// first session establishes, rather than reading as unroutable the moment no session
|
|
136
|
+
// is attached. Released only when this loop is disposed: giving up the current URL
|
|
137
|
+
// is recoverable (a new URL or a disable/re-enable starts another sequence), so a
|
|
138
|
+
// request must keep waiting rather than go unroutable in the gap.
|
|
139
|
+
if (this.consume) {
|
|
140
|
+
this.#signals.cleanup(wireOf(this.consume).expect());
|
|
141
|
+
}
|
|
142
|
+
this.error = this.#error;
|
|
143
|
+
const win = globalThis.window;
|
|
144
|
+
const doc = globalThis.document;
|
|
145
|
+
if (typeof win !== "undefined" && typeof doc !== "undefined") {
|
|
146
|
+
this.#signals.event(win, "pagehide", () => this.#suspended.set(true));
|
|
147
|
+
this.#signals.event(win, "pageshow", () => this.#suspended.set(false));
|
|
148
|
+
this.#signals.event(win, "unload", () => this.#suspended.set(true));
|
|
149
|
+
this.#signals.event(doc, "visibilitychange", () => {
|
|
150
|
+
if (!doc.hidden)
|
|
85
151
|
this.#suspended.set(false);
|
|
86
152
|
});
|
|
87
153
|
}
|
|
@@ -89,6 +155,32 @@ export class Reload {
|
|
|
89
155
|
const connection = effect.get(this.established);
|
|
90
156
|
return connection && effect.get(connection.probe);
|
|
91
157
|
});
|
|
158
|
+
this.bandwidth = new Allocator(this.#estimate);
|
|
159
|
+
this.#signals.cleanup(() => this.bandwidth.close());
|
|
160
|
+
// The transport has no event for the send-rate estimate, so sample on our
|
|
161
|
+
// own schedule and skip a tick while the previous snapshot is outstanding.
|
|
162
|
+
this.#signals.run((effect) => {
|
|
163
|
+
effect.set(this.#estimate, undefined);
|
|
164
|
+
const connection = effect.get(this.established);
|
|
165
|
+
if (!connection)
|
|
166
|
+
return;
|
|
167
|
+
let pending = false;
|
|
168
|
+
const sample = async () => {
|
|
169
|
+
if (pending)
|
|
170
|
+
return;
|
|
171
|
+
pending = true;
|
|
172
|
+
try {
|
|
173
|
+
const stats = await effect.race(connection.stats());
|
|
174
|
+
if (stats)
|
|
175
|
+
this.#estimate.set(stats.estimatedSendRate);
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
pending = false;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
void sample();
|
|
182
|
+
effect.interval(() => void sample(), BANDWIDTH_POLL);
|
|
183
|
+
});
|
|
92
184
|
this.#url = this.#signals.computed((effect) => effect.get(this.url)?.href);
|
|
93
185
|
// Create a reactive root so cleanup is easier.
|
|
94
186
|
this.#signals.run(this.#connect.bind(this));
|
|
@@ -96,14 +188,34 @@ export class Reload {
|
|
|
96
188
|
#connect(effect) {
|
|
97
189
|
// Will retry when the tick changes.
|
|
98
190
|
effect.get(this.#tick);
|
|
99
|
-
const suspended = effect.get(this.#suspended);
|
|
100
191
|
const enabled = effect.get(this.enabled);
|
|
101
|
-
if (!enabled
|
|
192
|
+
if (!enabled) {
|
|
193
|
+
this.#givenUpHref = undefined;
|
|
194
|
+
this.#resetSequence();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Hide/freeze pauses an in-flight sequence without recovering from give-up.
|
|
198
|
+
const suspended = effect.get(this.#suspended);
|
|
199
|
+
if (suspended) {
|
|
200
|
+
this.#resetSequence();
|
|
102
201
|
return;
|
|
202
|
+
}
|
|
103
203
|
const href = effect.get(this.#url);
|
|
104
|
-
if (!href)
|
|
204
|
+
if (!href) {
|
|
205
|
+
this.#givenUpHref = undefined;
|
|
206
|
+
this.#resetSequence();
|
|
105
207
|
return;
|
|
208
|
+
}
|
|
106
209
|
const url = new URL(href);
|
|
210
|
+
if (this.#givenUpHref === href) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (this.#sequenceHref !== href) {
|
|
214
|
+
this.#resetSequence();
|
|
215
|
+
this.#sequenceHref = href;
|
|
216
|
+
this.#error.set(undefined);
|
|
217
|
+
this.#givenUpHref = undefined;
|
|
218
|
+
}
|
|
107
219
|
effect.set(this.status, "connecting", "disconnected");
|
|
108
220
|
// This run's teardown, handed to connect() so a rerun cancels the attempt in flight.
|
|
109
221
|
const signal = effect.abort;
|
|
@@ -112,10 +224,13 @@ export class Reload {
|
|
|
112
224
|
// later dropped from a connect failure or a peer that flaps immediately.
|
|
113
225
|
let connected;
|
|
114
226
|
try {
|
|
115
|
-
const connection = await connect(
|
|
227
|
+
const connection = await connect({
|
|
228
|
+
url,
|
|
116
229
|
websocket: this.websocket,
|
|
117
230
|
webtransport: this.webtransport,
|
|
118
231
|
discovery: this.discovery,
|
|
232
|
+
publish: this.publish,
|
|
233
|
+
consume: this.consume,
|
|
119
234
|
signal,
|
|
120
235
|
});
|
|
121
236
|
// Hand the connection to the effect, which closes it now if this run is already over.
|
|
@@ -126,12 +241,13 @@ export class Reload {
|
|
|
126
241
|
effect.set(this.status, "connected", "disconnected");
|
|
127
242
|
connected = performance.now();
|
|
128
243
|
// A cancelled effect resolves undefined, so the sentinel tells the session
|
|
129
|
-
// closing apart from this run being
|
|
130
|
-
|
|
131
|
-
|
|
244
|
+
// closing (null for clean, an Error otherwise) apart from this run being
|
|
245
|
+
// torn down.
|
|
246
|
+
const closed = await effect.race(connection.closed);
|
|
247
|
+
if (closed === undefined)
|
|
132
248
|
return;
|
|
133
249
|
console.warn("connection closed, reconnecting");
|
|
134
|
-
this.#retry(effect, connected);
|
|
250
|
+
this.#retry(effect, connected, closed ?? undefined);
|
|
135
251
|
}
|
|
136
252
|
catch (err) {
|
|
137
253
|
// Treat teardown as cancellation, not a connection failure.
|
|
@@ -148,6 +264,16 @@ export class Reload {
|
|
|
148
264
|
* `cause` the error that killed it, if it died with one.
|
|
149
265
|
*/
|
|
150
266
|
#retry(effect, connected, cause) {
|
|
267
|
+
// Resolved per sequence rather than at construction, so an edit to `delay` (including
|
|
268
|
+
// one that drops a field back to its default) applies to the next retry. Field by
|
|
269
|
+
// field rather than by spread: a caller building `{ initial: maybeInitial }` from an
|
|
270
|
+
// optional value passes an explicit undefined, which a spread would take as the
|
|
271
|
+
// answer, turning the backoff into NaN or the window into forever.
|
|
272
|
+
const delay = this.delay ?? {};
|
|
273
|
+
const initial = delay.initial ?? DEFAULT_DELAY.initial;
|
|
274
|
+
const multiplier = delay.multiplier ?? DEFAULT_DELAY.multiplier;
|
|
275
|
+
const max = delay.max ?? DEFAULT_DELAY.max;
|
|
276
|
+
const timeout = delay.timeout ?? DEFAULT_DELAY.timeout;
|
|
151
277
|
// Any session is dead now: report disconnected during the backoff rather than
|
|
152
278
|
// when the retry reruns the effect.
|
|
153
279
|
this.established.set(undefined);
|
|
@@ -156,44 +282,66 @@ export class Reload {
|
|
|
156
282
|
// start a fresh retry window: a one-off drop should reconnect promptly. Anything
|
|
157
283
|
// shorter is a peer that accepts and immediately severs, which has to keep
|
|
158
284
|
// escalating or we hammer it forever at the initial delay.
|
|
159
|
-
if (connected !== undefined && performance.now() - connected >=
|
|
285
|
+
if (connected !== undefined && performance.now() - connected >= initial) {
|
|
160
286
|
this.#delay = undefined;
|
|
161
287
|
this.#deadline = undefined;
|
|
162
288
|
}
|
|
289
|
+
// An auth rejection stops this URL however long the session lived. UNAUTHORIZED is a
|
|
290
|
+
// specified code rather than one we guessed at, so this is the peer saying these
|
|
291
|
+
// credentials will never work; retrying them just burns the window. Matches
|
|
292
|
+
// moq-tokio's reconnect loop, which stops on the same close. A new URL or a
|
|
293
|
+
// disable/re-enable starts another sequence; the handle itself is not disposed.
|
|
294
|
+
//
|
|
295
|
+
// Only a session close says that. The stream registry gives 2 to DELIVERY_TIMEOUT,
|
|
296
|
+
// so a stream reset during the SETUP exchange would otherwise suppress reconnect
|
|
297
|
+
// for good.
|
|
298
|
+
if (cause instanceof SessionError && cause.code === SessionCode.Unauthorized) {
|
|
299
|
+
console.warn("session rejected as unauthorized, not retrying");
|
|
300
|
+
this.#giveUp(cause);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
163
303
|
const now = performance.now();
|
|
164
|
-
|
|
165
|
-
this.#delay ??= this.delay.initial;
|
|
304
|
+
this.#delay ??= initial;
|
|
166
305
|
this.#deadline ??= timeout > 0 ? now + timeout : Number.POSITIVE_INFINITY;
|
|
167
306
|
if (now >= this.#deadline) {
|
|
168
307
|
console.warn("reconnect timed out");
|
|
169
308
|
// A graceful close has no error, so report the timeout itself.
|
|
170
|
-
this.#
|
|
309
|
+
this.#giveUp(cause === undefined ? new Error("reconnect timed out") : error(cause));
|
|
171
310
|
return;
|
|
172
311
|
}
|
|
173
312
|
// Equal jitter, so a fleet of tabs knocked offline together doesn't reconnect on the same
|
|
174
313
|
// tick, and never past the deadline the retry window promised.
|
|
175
314
|
const wait = Math.min(this.#delay * (0.5 + Math.random() / 2), this.#deadline - now);
|
|
176
|
-
this.#delay = Math.min(this.#delay *
|
|
315
|
+
this.#delay = Math.min(this.#delay * multiplier, max);
|
|
177
316
|
const tick = this.#tick.peek() + 1;
|
|
178
317
|
effect.timer(() => this.#tick.update((prev) => Math.max(prev, tick)), wait);
|
|
179
318
|
}
|
|
319
|
+
#giveUp(cause) {
|
|
320
|
+
this.#error.set(cause);
|
|
321
|
+
this.#givenUpHref = this.#sequenceHref;
|
|
322
|
+
this.#resetSequence();
|
|
323
|
+
}
|
|
324
|
+
#resetSequence() {
|
|
325
|
+
this.#delay = undefined;
|
|
326
|
+
this.#deadline = undefined;
|
|
327
|
+
this.#sequenceHref = undefined;
|
|
328
|
+
}
|
|
180
329
|
/**
|
|
181
|
-
* Subscribe to broadcast announcements
|
|
330
|
+
* Subscribe to broadcast announcements matching `scope`, spanning reconnects.
|
|
182
331
|
*
|
|
183
332
|
* The same {@link Announce.Consumer} stream as {@link Established.announced}, but everything active
|
|
184
|
-
* is retracted (
|
|
333
|
+
* is retracted (a `retracted` update) whenever the connection drops and re-announced on
|
|
185
334
|
* reconnect, so a consumer draining `next()` never clings to a dead route across a reconnect.
|
|
186
335
|
*
|
|
187
336
|
* Stays empty while the relay lacks {@link Established.discovery}.
|
|
188
337
|
*/
|
|
189
|
-
announced(
|
|
190
|
-
|
|
338
|
+
announced(scope = Path.Pattern.all(), options) {
|
|
339
|
+
// With a consume origin the table already spans reconnects (the forwarder retracts
|
|
340
|
+
// a dead session's entries), so its stream is the same thing with less machinery.
|
|
341
|
+
if (this.consume)
|
|
342
|
+
return this.consume.announced(scope, options);
|
|
343
|
+
const producer = new Announce.Producer();
|
|
191
344
|
const consumer = producer.consume();
|
|
192
|
-
// Closing the consumer closes the shared state, so stop appending after that.
|
|
193
|
-
let closed = false;
|
|
194
|
-
void consumer.closed.then(() => {
|
|
195
|
-
closed = true;
|
|
196
|
-
});
|
|
197
345
|
const pump = new Effect();
|
|
198
346
|
pump.run((effect) => {
|
|
199
347
|
const conn = effect.get(this.established);
|
|
@@ -203,20 +351,21 @@ export class Reload {
|
|
|
203
351
|
// consumer empty rather than opening a subscription that can't be answered.
|
|
204
352
|
if (!conn.discovery)
|
|
205
353
|
return;
|
|
206
|
-
const upstream = conn.announced(
|
|
354
|
+
const upstream = conn.announced(scope, options);
|
|
207
355
|
effect.cleanup(() => upstream.close());
|
|
208
|
-
// Track what this connection announced so we can retract it if the connection
|
|
209
|
-
|
|
356
|
+
// Track what this connection announced so we can retract it if the connection
|
|
357
|
+
// drops; the last event rides along for the retraction.
|
|
358
|
+
const active = new Map();
|
|
210
359
|
effect.spawn(async () => {
|
|
211
360
|
try {
|
|
212
361
|
for (;;) {
|
|
213
|
-
const entry = await
|
|
362
|
+
const entry = await effect.race(upstream.next());
|
|
214
363
|
if (!entry)
|
|
215
364
|
break;
|
|
216
|
-
if (entry.
|
|
217
|
-
active.
|
|
365
|
+
if (Announce.isActive(entry.kind))
|
|
366
|
+
active.set(entry.prefix, entry);
|
|
218
367
|
else
|
|
219
|
-
active.delete(entry.
|
|
368
|
+
active.delete(entry.prefix);
|
|
220
369
|
producer.append(entry);
|
|
221
370
|
}
|
|
222
371
|
}
|
|
@@ -226,9 +375,9 @@ export class Reload {
|
|
|
226
375
|
finally {
|
|
227
376
|
// Retract everything from the connection that just went away, so a per-broadcast
|
|
228
377
|
// watcher tears down instead of clinging to the dead route.
|
|
229
|
-
if (
|
|
230
|
-
for (const
|
|
231
|
-
producer.append({
|
|
378
|
+
if (consumer.closed.peek() === undefined) {
|
|
379
|
+
for (const entry of active.values()) {
|
|
380
|
+
producer.append({ ...entry, kind: "retracted" });
|
|
232
381
|
}
|
|
233
382
|
}
|
|
234
383
|
}
|
|
@@ -238,20 +387,6 @@ export class Reload {
|
|
|
238
387
|
void consumer.closed.then(() => pump.close());
|
|
239
388
|
return consumer;
|
|
240
389
|
}
|
|
241
|
-
/**
|
|
242
|
-
* A reactive handle to one broadcast, spanning reconnects.
|
|
243
|
-
*
|
|
244
|
-
* The same {@link Announce.Broadcast} as {@link Established.announcedBroadcast}, but it
|
|
245
|
-
* follows the reconnect loop: the broadcast drops to `undefined` when the connection dies
|
|
246
|
-
* and resolves again once the new connection announces the path. Use it instead of
|
|
247
|
-
* consuming off {@link Reload.established} whenever the broadcast may come online after you
|
|
248
|
-
* do, which is exactly the case a blind `consume` loses.
|
|
249
|
-
*
|
|
250
|
-
* Close the handle when done; {@link Reload.close} only drops it to `undefined`.
|
|
251
|
-
*/
|
|
252
|
-
announcedBroadcast(path) {
|
|
253
|
-
return new Announce.Broadcast({ connection: this.established, path });
|
|
254
|
-
}
|
|
255
390
|
/**
|
|
256
391
|
* Snapshot the live connection's transport counters, or undefined while disconnected.
|
|
257
392
|
* See {@link Established.stats}.
|
|
@@ -259,10 +394,11 @@ export class Reload {
|
|
|
259
394
|
async stats() {
|
|
260
395
|
return this.established.peek()?.stats();
|
|
261
396
|
}
|
|
262
|
-
/** Stop reconnecting, close the current connection, and
|
|
263
|
-
close() {
|
|
397
|
+
/** Stop reconnecting, close the current connection, and settle {@link Reload.closed}. Idempotent. */
|
|
398
|
+
close(abort) {
|
|
264
399
|
this.#signals.close();
|
|
265
|
-
this.#
|
|
400
|
+
if (this.#closed.peek() === undefined)
|
|
401
|
+
this.#closed.set(abort ?? null);
|
|
266
402
|
}
|
|
267
403
|
}
|
|
268
404
|
//# sourceMappingURL=reload.js.map
|