@norskvideo/moq-net 0.1.8 → 0.2.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/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/broadcast.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,cAAc,CAAC;AAG7D,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAiJpC;;;;GAIG;AACH,qBAAa,QAAQ;;IAKpB,cAEC;IAYD;;;OAGG;IACH,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAED,oFAAoF;IACpF,OAAO,IAAI,QAAQ,CAElB;IAeD,uFAAuF;IACvF,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAiBvC;IAED,6EAA6E;IAC7E,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAM,GAAG,KAAK,CAAC,QAAQ,CAIxE;IAED,kDAAkD;IAClD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9B;IAED,wDAAwD;IACxD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAElC;IAWD;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,GAAE,KAAK,GAAG;QAAE,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;KAAkB,GAAG,IAAI,CAMrG;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED,oHAAoH;IACpH,KAAK,IAAI,IAAI,CAAC;IACd,gFAAgF;IAChF,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CAM3B;AAYD;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IAOpB,SAAS,aAAa,MAAM,CAAC,EAAE,KAAK,EAAE;IAqBtC;;;;;;;OAOG;IACH,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,CAErB;IAED;;;;;;OAMG;IACH,IAAI,MAAM,IAAI,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAErC;IAED;;;;;;;OAOG;IACH,KAAK,IAAI,QAAQ,CAEhB;IAID,SAAS,CAAC,UAAU,IAAI,KAAK,CAE5B;IAED,mHAAmH;IACnH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAElC;IAeD;;;OAGG;IACH,KAAK,IAAI,IAAI,CAAC;IACd,gFAAgF;IAChF,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CAO3B"}
|
package/broadcast.js
CHANGED
|
@@ -5,11 +5,19 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { Once, Signal } from "@norskvideo/moq-signals";
|
|
8
|
+
import { NotFound } from "./error.js";
|
|
9
|
+
import { Route } from "./hop.js";
|
|
8
10
|
import { hooks } from "./internal.js";
|
|
11
|
+
import * as Path from "./path.js";
|
|
9
12
|
import * as track from "./track.js";
|
|
13
|
+
import { untilAborted } from "./util/abort.js";
|
|
14
|
+
import { registerWire, trackOf } from "./wire.js";
|
|
15
|
+
let attachAnnouncer;
|
|
16
|
+
let stampProducer;
|
|
10
17
|
/** Reactive backing state shared by broadcast producers and consumers. */
|
|
11
18
|
class BroadcastState {
|
|
12
19
|
requested = new Signal([]);
|
|
20
|
+
pending = new Set();
|
|
13
21
|
closed = new Once();
|
|
14
22
|
tracks = new Map();
|
|
15
23
|
sequences = new Map();
|
|
@@ -32,9 +40,9 @@ function closeState(state, abort) {
|
|
|
32
40
|
if (state.closed.peek() !== undefined)
|
|
33
41
|
return;
|
|
34
42
|
state.closed.set(abort ?? null);
|
|
43
|
+
for (const request of state.pending)
|
|
44
|
+
request.reject(abort);
|
|
35
45
|
state.requested.mutate((requests) => {
|
|
36
|
-
for (const request of requests)
|
|
37
|
-
request.reject(abort);
|
|
38
46
|
requests.length = 0;
|
|
39
47
|
});
|
|
40
48
|
}
|
|
@@ -67,7 +75,7 @@ function subscribe(state, name, options = {}, register = false) {
|
|
|
67
75
|
});
|
|
68
76
|
}
|
|
69
77
|
state.requested.mutate((requested) => {
|
|
70
|
-
requested.push(hooks.makeRequest({ name, producer, sequences: state.sequences }));
|
|
78
|
+
requested.push(hooks.makeRequest({ name, producer, sequences: state.sequences, pending: state.pending }));
|
|
71
79
|
});
|
|
72
80
|
return subscriber;
|
|
73
81
|
}
|
|
@@ -81,7 +89,7 @@ async function resolveTrackInfo(state, name) {
|
|
|
81
89
|
}
|
|
82
90
|
const producer = new track.Producer(name);
|
|
83
91
|
state.requested.mutate((requested) => {
|
|
84
|
-
requested.push(hooks.makeRequest({ name, producer, sequences: state.sequences }));
|
|
92
|
+
requested.push(hooks.makeRequest({ name, producer, sequences: state.sequences, pending: state.pending }));
|
|
85
93
|
});
|
|
86
94
|
try {
|
|
87
95
|
return await producer.info();
|
|
@@ -94,12 +102,14 @@ async function resolveTrackInfo(state, name) {
|
|
|
94
102
|
// requested sequence. The default for a produced broadcast; the consuming wire layer
|
|
95
103
|
// overrides it to fetch over the network (or to reject when the transport has no FETCH).
|
|
96
104
|
async function fetchGroup(state, name, sequence, options = {}) {
|
|
105
|
+
options.signal?.throwIfAborted();
|
|
97
106
|
const subscriber = subscribe(state, name, { priority: options.priority });
|
|
107
|
+
hooks.exemptFetch(subscriber);
|
|
98
108
|
try {
|
|
99
109
|
for (;;) {
|
|
100
|
-
const group = await subscriber.recvGroup();
|
|
110
|
+
const group = await untilAborted(subscriber.recvGroup(), options.signal);
|
|
101
111
|
if (!group)
|
|
102
|
-
throw new
|
|
112
|
+
throw new NotFound(`group ${sequence}`);
|
|
103
113
|
if (group.sequence === sequence) {
|
|
104
114
|
// Close the subscription when the returned group finishes, not now: an
|
|
105
115
|
// in-progress group must keep receiving frames for its lifetime (mirrors
|
|
@@ -109,7 +119,7 @@ async function fetchGroup(state, name, sequence, options = {}) {
|
|
|
109
119
|
}
|
|
110
120
|
group.close();
|
|
111
121
|
if (group.sequence > sequence)
|
|
112
|
-
throw new
|
|
122
|
+
throw new NotFound(`group ${sequence}`);
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
catch (err) {
|
|
@@ -124,6 +134,20 @@ async function fetchGroup(state, name, sequence, options = {}) {
|
|
|
124
134
|
*/
|
|
125
135
|
export class Producer {
|
|
126
136
|
#state = new BroadcastState();
|
|
137
|
+
#announcer;
|
|
138
|
+
#path = Path.empty();
|
|
139
|
+
constructor() {
|
|
140
|
+
registerWire(this, this.#wire(false));
|
|
141
|
+
}
|
|
142
|
+
static {
|
|
143
|
+
attachAnnouncer = (producer, announcer) => {
|
|
144
|
+
producer.#announcer = announcer;
|
|
145
|
+
};
|
|
146
|
+
hooks.attachAnnouncer = attachAnnouncer;
|
|
147
|
+
stampProducer = (producer, path) => {
|
|
148
|
+
producer.#path = path;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
127
151
|
/**
|
|
128
152
|
* Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.
|
|
129
153
|
* Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.
|
|
@@ -131,12 +155,11 @@ export class Producer {
|
|
|
131
155
|
get closed() {
|
|
132
156
|
return this.#state.closed;
|
|
133
157
|
}
|
|
134
|
-
/** A read handle for this broadcast. */
|
|
158
|
+
/** A read handle for this broadcast, named by the path the origin created it at. */
|
|
135
159
|
consume() {
|
|
136
|
-
return makeConsumer(this.#state);
|
|
160
|
+
return makeConsumer({ state: this.#state, path: this.#path });
|
|
137
161
|
}
|
|
138
|
-
|
|
139
|
-
async requested() {
|
|
162
|
+
async #requested() {
|
|
140
163
|
for (;;) {
|
|
141
164
|
const request = dequeueRequest(this.#state);
|
|
142
165
|
if (request)
|
|
@@ -175,24 +198,44 @@ export class Producer {
|
|
|
175
198
|
removeTrack(name) {
|
|
176
199
|
this.#state.tracks.delete(name);
|
|
177
200
|
}
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
return
|
|
201
|
+
/** A lazy read handle for a track on this broadcast. */
|
|
202
|
+
track(name) {
|
|
203
|
+
return trackOf(name, this);
|
|
181
204
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
#wire(register) {
|
|
206
|
+
return {
|
|
207
|
+
subscribe: (name, options) => subscribe(this.#state, name, options, register),
|
|
208
|
+
resolveTrackInfo: (name) => resolveTrackInfo(this.#state, name),
|
|
209
|
+
fetchGroup: (name, sequence, options) => fetchGroup(this.#state, name, sequence, options),
|
|
210
|
+
requested: () => this.#requested(),
|
|
211
|
+
};
|
|
185
212
|
}
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Advertise this broadcast's exact path, or re-price a standing advertisement in place.
|
|
215
|
+
*
|
|
216
|
+
* Call it once the tracks a subscriber needs first (a catalog) exist. Until then the
|
|
217
|
+
* broadcast exists for nobody, on its own origin or at a peer. Retracts on
|
|
218
|
+
* {@link unannounce} or {@link close}. Throws if this producer was not created through an
|
|
219
|
+
* origin, or if the broadcast is already closed.
|
|
220
|
+
*/
|
|
221
|
+
announce(route = Route.default) {
|
|
222
|
+
if (this.#state.closed.peek() !== undefined) {
|
|
223
|
+
throw new Error(`broadcast is closed: ${this.#state.closed.peek()}`);
|
|
224
|
+
}
|
|
225
|
+
if (!this.#announcer)
|
|
226
|
+
throw new Error("broadcast is not attached to an origin");
|
|
227
|
+
this.#announcer.announce(Route.normalize(route));
|
|
189
228
|
}
|
|
190
|
-
/**
|
|
191
|
-
|
|
192
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Retract the advertisement of this broadcast's path, if any, from local consumers and
|
|
231
|
+
* peers alike. {@link announce} brings it back.
|
|
232
|
+
*/
|
|
233
|
+
unannounce() {
|
|
234
|
+
this.#announcer?.unannounce();
|
|
193
235
|
}
|
|
194
|
-
/** Close the broadcast, optionally with an error to abort waiters. Idempotent. */
|
|
195
236
|
close(abort) {
|
|
237
|
+
this.#announcer?.unannounce();
|
|
238
|
+
this.#announcer = undefined;
|
|
196
239
|
closeState(this.#state, abort);
|
|
197
240
|
}
|
|
198
241
|
}
|
|
@@ -202,21 +245,46 @@ let makeConsumer;
|
|
|
202
245
|
/**
|
|
203
246
|
* The read side of a broadcast.
|
|
204
247
|
*
|
|
205
|
-
* Created internally: obtain one from {@link Producer.consume} or
|
|
206
|
-
*
|
|
248
|
+
* Created internally: obtain one from {@link Producer.consume} or an origin request.
|
|
249
|
+
* The wire layers subclass it to resolve tracks over the network.
|
|
207
250
|
*
|
|
208
251
|
* @public
|
|
209
252
|
*/
|
|
210
253
|
export class Consumer {
|
|
211
254
|
#state;
|
|
255
|
+
#path;
|
|
212
256
|
// Guards against a double close() on this handle over-decrementing the consumer count.
|
|
213
257
|
#closed = false;
|
|
214
|
-
constructor(
|
|
215
|
-
this.#state = state ?? new BroadcastState();
|
|
258
|
+
constructor(shared) {
|
|
259
|
+
this.#state = shared?.state ?? new BroadcastState();
|
|
260
|
+
this.#path = shared?.path ?? Path.empty();
|
|
216
261
|
this.#state.consumers++;
|
|
262
|
+
registerWire(this, {
|
|
263
|
+
subscribe: (name, options) => subscribe(this.#state, name, options, true),
|
|
264
|
+
resolveTrackInfo: (name) => resolveTrackInfo(this.#state, name),
|
|
265
|
+
fetchGroup: (name, sequence, options) => fetchGroup(this.#state, name, sequence, options),
|
|
266
|
+
requested: () => this.#requested(),
|
|
267
|
+
});
|
|
217
268
|
}
|
|
218
269
|
static {
|
|
219
|
-
makeConsumer = (
|
|
270
|
+
makeConsumer = (shared) => new Consumer(shared);
|
|
271
|
+
hooks.stampPath = (target, path) => {
|
|
272
|
+
if (target instanceof Consumer)
|
|
273
|
+
target.#path = path;
|
|
274
|
+
else
|
|
275
|
+
stampProducer(target, path);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The path this handle names the broadcast by, which relative references in its catalog
|
|
280
|
+
* (hang's `broadcast` field) resolve against.
|
|
281
|
+
*
|
|
282
|
+
* An origin stamps each handle it hands out with the path it was requested at, relative to
|
|
283
|
+
* that origin handle's scope root, and a broadcast it created with the path it was created at.
|
|
284
|
+
* Empty for a standalone broadcast, which is then its own root: any `..` reference escapes.
|
|
285
|
+
*/
|
|
286
|
+
get path() {
|
|
287
|
+
return this.#path;
|
|
220
288
|
}
|
|
221
289
|
/**
|
|
222
290
|
* Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.
|
|
@@ -231,29 +299,24 @@ export class Consumer {
|
|
|
231
299
|
/**
|
|
232
300
|
* Return another handle to the same broadcast, reference-counted with this one.
|
|
233
301
|
*
|
|
234
|
-
* Both handles read the same tracks and share one {@link closed}
|
|
235
|
-
* closes only once *every* handle has {@link close}d. Used by the connection's per-path
|
|
302
|
+
* Both handles read the same tracks, carry the same {@link path}, and share one {@link closed}
|
|
303
|
+
* state; the broadcast closes only once *every* handle has {@link close}d. Used by the connection's per-path
|
|
236
304
|
* consume cache to share one subscription across callers. Subclasses that resolve info over
|
|
237
305
|
* the wire override this to preserve their type (see the wire layer's consumed broadcast).
|
|
238
306
|
*/
|
|
239
307
|
clone() {
|
|
240
308
|
return new Consumer(this.shareState());
|
|
241
309
|
}
|
|
242
|
-
// Hand this consumer's backing state to a clone. Opaque (`never`) so the state type
|
|
243
|
-
// unexported; a subclass passes it straight back into its own `super(...)`.
|
|
310
|
+
// Hand this consumer's backing state and path to a clone. Opaque (`never`) so the state type
|
|
311
|
+
// stays unexported; a subclass passes it straight back into its own `super(...)`.
|
|
244
312
|
shareState() {
|
|
245
|
-
return this.#state;
|
|
313
|
+
return { state: this.#state, path: this.#path };
|
|
246
314
|
}
|
|
247
315
|
/** Get a lazy handle for a track on this broadcast. Repeat subscriptions dedupe onto one upstream subscription. */
|
|
248
316
|
track(name) {
|
|
249
|
-
return
|
|
250
|
-
}
|
|
251
|
-
/** Open a live subscription to a track. Used by the subscribing wire layer. Repeat subscriptions to the same track share one upstream subscription. */
|
|
252
|
-
subscribe(name, options) {
|
|
253
|
-
return subscribe(this.#state, name, options, true);
|
|
317
|
+
return trackOf(name, this);
|
|
254
318
|
}
|
|
255
|
-
|
|
256
|
-
async requested() {
|
|
319
|
+
async #requested() {
|
|
257
320
|
for (;;) {
|
|
258
321
|
const request = dequeueRequest(this.#state);
|
|
259
322
|
if (request)
|
|
@@ -266,25 +329,6 @@ export class Consumer {
|
|
|
266
329
|
await Signal.race(this.#state.requested, this.#state.closed);
|
|
267
330
|
}
|
|
268
331
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Resolve a track's immutable info. Used by track handles. This base resolves it from
|
|
271
|
-
* the local producers; the consuming wire layer overrides it to fetch over the wire.
|
|
272
|
-
*/
|
|
273
|
-
resolveTrackInfo(name) {
|
|
274
|
-
return resolveTrackInfo(this.#state, name);
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Fetch a single group by sequence. Used by track handles. This base serves from the
|
|
278
|
-
* local retained window; the consuming wire layer overrides it to fetch over the wire
|
|
279
|
-
* (or to reject when the transport has no FETCH).
|
|
280
|
-
*/
|
|
281
|
-
fetchGroup(name, sequence, options) {
|
|
282
|
-
return fetchGroup(this.#state, name, sequence, options);
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Release this handle. The broadcast is closed (optionally with an error to abort waiters)
|
|
286
|
-
* once this was the last live handle; while other {@link clone}s remain open it stays live.
|
|
287
|
-
*/
|
|
288
332
|
close(abort) {
|
|
289
333
|
if (this.#closed)
|
|
290
334
|
return;
|
package/broadcast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Broadcast role handles: a named collection of tracks produced by a publisher.\n *\n * @module\n */\nimport { type GetPromise, Once, Signal } from \"@moq/signals\";\nimport type { Consumer as GroupConsumer } from \"./group.ts\";\nimport { hooks, type TrackSequence } from \"./internal.ts\";\nimport * as track from \"./track.ts\";\n\n/** Reactive backing state shared by broadcast producers and consumers. */\nclass BroadcastState {\n\trequested = new Signal<track.Request[]>([]);\n\tclosed = new Once<Error | null>();\n\ttracks = new Map<string, track.Producer>();\n\tsequences = new Map<string, TrackSequence>();\n\t// Live consumer handles sharing this state (see {@link Consumer.clone}). The broadcast\n\t// closes once the last one closes, so a shared consumer can be handed to several callers.\n\tconsumers = 0;\n}\n\nfunction dequeueRequest(state: BroadcastState): track.Request | undefined {\n\tconst requested = state.requested.peek();\n\trequested.sort((a, b) => a.priority - b.priority);\n\treturn requested.pop();\n}\n\n// Close the broadcast and reject any requests still pending in the queue, so a\n// subscriber blocked on the track's info() or group reads is unblocked rather\n// than left waiting on a producer that will never be served.\n//\n// Once.set throws on a second settle, and the producer and each consumer handle close\n// independently, so this has to be idempotent.\nfunction closeState(state: BroadcastState, abort?: Error) {\n\tif (state.closed.peek() !== undefined) return;\n\tstate.closed.set(abort ?? null);\n\tstate.requested.mutate((requests) => {\n\t\tfor (const request of requests) request.reject(abort);\n\t\trequests.length = 0;\n\t});\n}\n\n// `register` is set on the subscribing (consumer) side: the fresh producer is cached in\n// `state.tracks` so repeat subscriptions to the same track fan out from one upstream subscription\n// instead of opening a new one, mirroring the Rust `broadcast::Consumer::track` weak-dedup. The\n// consumer wire watches the producer's demand ({@link track.Producer.used}) and tears the upstream\n// down once its last subscriber leaves, closing the producer, which evicts the cache entry below.\n// The publishing side leaves `register` false: `state.tracks` there holds only the tracks the app\n// inserted, and a dynamic serve stays one request per peer subscription.\nfunction subscribe(\n\tstate: BroadcastState,\n\tname: string,\n\toptions: track.Subscription = {},\n\tregister = false,\n): track.Subscriber {\n\tif (state.closed.peek() !== undefined) {\n\t\tthrow new Error(`broadcast is closed: ${state.closed.peek()}`);\n\t}\n\n\tconst existing = state.tracks.get(name);\n\tif (existing) {\n\t\tif (existing.closed.peek() === undefined) return existing.subscribe(options);\n\t\tstate.tracks.delete(name);\n\t}\n\n\tconst producer = new track.Producer(name);\n\tconst subscriber = producer.subscribe(options);\n\n\tif (register) {\n\t\tstate.tracks.set(name, producer);\n\t\t// Drop the cache entry once the upstream closes (the wire tears it down when its last\n\t\t// subscriber leaves), so a later subscribe re-opens it.\n\t\tvoid producer.closed.then(() => {\n\t\t\tif (state.tracks.get(name) === producer) state.tracks.delete(name);\n\t\t});\n\t}\n\n\tstate.requested.mutate((requested) => {\n\t\trequested.push(hooks.makeRequest({ name, producer, sequences: state.sequences }));\n\t});\n\n\treturn subscriber;\n}\n\nasync function resolveTrackInfo(state: BroadcastState, name: string): Promise<track.Info> {\n\tconst existing = state.tracks.get(name);\n\tif (existing && existing.closed.peek() === undefined) {\n\t\treturn existing.info();\n\t}\n\n\tif (state.closed.peek() !== undefined) {\n\t\treturn Promise.reject(new Error(`broadcast is closed: ${state.closed.peek()}`));\n\t}\n\n\tconst producer = new track.Producer(name);\n\tstate.requested.mutate((requested) => {\n\t\trequested.push(hooks.makeRequest({ name, producer, sequences: state.sequences }));\n\t});\n\n\ttry {\n\t\treturn await producer.info();\n\t} finally {\n\t\tproducer.close();\n\t}\n}\n\n// Serve a group from the local retained window by subscribing and scanning to the\n// requested sequence. The default for a produced broadcast; the consuming wire layer\n// overrides it to fetch over the network (or to reject when the transport has no FETCH).\nasync function fetchGroup(\n\tstate: BroadcastState,\n\tname: string,\n\tsequence: number,\n\toptions: track.FetchGroupOptions = {},\n): Promise<GroupConsumer> {\n\tconst subscriber = subscribe(state, name, { priority: options.priority });\n\ttry {\n\t\tfor (;;) {\n\t\t\tconst group = await subscriber.recvGroup();\n\t\t\tif (!group) throw new Error(`group not found: ${sequence}`);\n\t\t\tif (group.sequence === sequence) {\n\t\t\t\t// Close the subscription when the returned group finishes, not now: an\n\t\t\t\t// in-progress group must keep receiving frames for its lifetime (mirrors\n\t\t\t\t// Rust poll_fetch). Also fires if the caller closes the group early.\n\t\t\t\tvoid group.closed.then(() => subscriber.close());\n\t\t\t\treturn group;\n\t\t\t}\n\n\t\t\tgroup.close();\n\t\t\tif (group.sequence > sequence) throw new Error(`group not found: ${sequence}`);\n\t\t}\n\t} catch (err) {\n\t\tsubscriber.close();\n\t\tthrow err;\n\t}\n}\n\n/**\n * The write side of a broadcast.\n *\n * @public\n */\nexport class Producer implements track.Broadcast {\n\t#state = new BroadcastState();\n\n\t/**\n\t * Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.\n\t * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.\n\t */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\t/** A read handle for this broadcast. */\n\tconsume(): Consumer {\n\t\treturn makeConsumer(this.#state);\n\t}\n\n\t/** Return the next track requested by a peer. */\n\tasync requested(): Promise<track.Request | undefined> {\n\t\tfor (;;) {\n\t\t\tconst request = dequeueRequest(this.#state);\n\t\t\tif (request) return request;\n\n\t\t\tconst closed = this.#state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed !== undefined) return undefined;\n\n\t\t\tawait Signal.race(this.#state.requested, this.#state.closed);\n\t\t}\n\t}\n\n\t/** Insert a track that is served directly, without an on-demand request round-trip. */\n\tinsertTrack(track: track.Producer): void {\n\t\tif (this.#state.closed.peek() !== undefined) {\n\t\t\tthrow new Error(`broadcast is closed: ${this.#state.closed.peek()}`);\n\t\t}\n\n\t\tconst existing = this.#state.tracks.get(track.name);\n\t\tif (existing && existing.closed.peek() === undefined) {\n\t\t\tthrow new Error(`duplicate track: ${track.name}`);\n\t\t}\n\n\t\tthis.#state.tracks.set(track.name, track);\n\n\t\tvoid track.closed.then(() => {\n\t\t\tif (this.#state.tracks.get(track.name) === track) {\n\t\t\t\tthis.#state.tracks.delete(track.name);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Create a track, insert it into the broadcast, and return its producer. */\n\tcreateTrack(name: string, info: Partial<track.Info> = {}): track.Producer {\n\t\tconst producer = new track.Producer(name).accept(info);\n\t\tthis.insertTrack(producer);\n\t\treturn producer;\n\t}\n\n\t/** Remove a statically inserted track by name. */\n\tremoveTrack(name: string): void {\n\t\tthis.#state.tracks.delete(name);\n\t}\n\n\t/** Open a live subscription to a track. Used by the publishing wire layer. */\n\tsubscribe(name: string, options?: track.Subscription): track.Subscriber {\n\t\treturn subscribe(this.#state, name, options);\n\t}\n\n\t/** Resolve a track's immutable info. Used by the publishing wire layer. */\n\tresolveTrackInfo(name: string): Promise<track.Info> {\n\t\treturn resolveTrackInfo(this.#state, name);\n\t}\n\n\t/** Fetch a single group from the local retained window. Used by track handles. */\n\tfetchGroup(name: string, sequence: number, options?: track.FetchGroupOptions): Promise<GroupConsumer> {\n\t\treturn fetchGroup(this.#state, name, sequence, options);\n\t}\n\n\t/** A lazy read handle for a track on this broadcast. */\n\ttrack(name: string): track.Consumer {\n\t\treturn new track.Consumer(name, this);\n\t}\n\n\t/** Close the broadcast, optionally with an error to abort waiters. Idempotent. */\n\tclose(abort?: Error) {\n\t\tcloseState(this.#state, abort);\n\t}\n}\n\n// Constructs a Consumer from within this module without exposing a public constructor\n// that would leak the unexported BroadcastState. Assigned in the class's static block.\nlet makeConsumer: (state: BroadcastState) => Consumer;\n\n/**\n * The read side of a broadcast.\n *\n * Created internally: obtain one from {@link Producer.consume} or the connection's\n * `consume(path)`. The wire layers subclass it to resolve tracks over the network.\n *\n * @public\n */\nexport class Consumer implements track.Broadcast {\n\t#state: BroadcastState;\n\n\t// Guards against a double close() on this handle over-decrementing the consumer count.\n\t#closed = false;\n\n\tprotected constructor(state?: never);\n\tprotected constructor(state?: BroadcastState) {\n\t\tthis.#state = state ?? new BroadcastState();\n\t\tthis.#state.consumers++;\n\t}\n\n\tstatic {\n\t\tmakeConsumer = (state) => new Consumer(state as never);\n\t}\n\n\t/**\n\t * Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.\n\t * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.\n\t *\n\t * Shared by every {@link clone}: it settles once the last handle closes. The subscribing\n\t * wire layer peeks it to evict a closed entry from its per-path consume cache.\n\t */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\t/**\n\t * Return another handle to the same broadcast, reference-counted with this one.\n\t *\n\t * Both handles read the same tracks and share one {@link closed} state; the broadcast\n\t * closes only once *every* handle has {@link close}d. Used by the connection's per-path\n\t * consume cache to share one subscription across callers. Subclasses that resolve info over\n\t * the wire override this to preserve their type (see the wire layer's consumed broadcast).\n\t */\n\tclone(): Consumer {\n\t\treturn new Consumer(this.shareState());\n\t}\n\n\t// Hand this consumer's backing state to a clone. Opaque (`never`) so the state type stays\n\t// unexported; a subclass passes it straight back into its own `super(...)`.\n\tprotected shareState(): never {\n\t\treturn this.#state as never;\n\t}\n\n\t/** Get a lazy handle for a track on this broadcast. Repeat subscriptions dedupe onto one upstream subscription. */\n\ttrack(name: string): track.Consumer {\n\t\treturn new track.Consumer(name, this);\n\t}\n\n\t/** Open a live subscription to a track. Used by the subscribing wire layer. Repeat subscriptions to the same track share one upstream subscription. */\n\tsubscribe(name: string, options?: track.Subscription): track.Subscriber {\n\t\treturn subscribe(this.#state, name, options, true);\n\t}\n\n\t/** Return the next track requested by the local consumer. Used by the subscribing wire layer. */\n\tasync requested(): Promise<track.Request | undefined> {\n\t\tfor (;;) {\n\t\t\tconst request = dequeueRequest(this.#state);\n\t\t\tif (request) return request;\n\n\t\t\tconst closed = this.#state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed !== undefined) return undefined;\n\n\t\t\tawait Signal.race(this.#state.requested, this.#state.closed);\n\t\t}\n\t}\n\n\t/**\n\t * Resolve a track's immutable info. Used by track handles. This base resolves it from\n\t * the local producers; the consuming wire layer overrides it to fetch over the wire.\n\t */\n\tresolveTrackInfo(name: string): Promise<track.Info> {\n\t\treturn resolveTrackInfo(this.#state, name);\n\t}\n\n\t/**\n\t * Fetch a single group by sequence. Used by track handles. This base serves from the\n\t * local retained window; the consuming wire layer overrides it to fetch over the wire\n\t * (or to reject when the transport has no FETCH).\n\t */\n\tfetchGroup(name: string, sequence: number, options?: track.FetchGroupOptions): Promise<GroupConsumer> {\n\t\treturn fetchGroup(this.#state, name, sequence, options);\n\t}\n\n\t/**\n\t * Release this handle. The broadcast is closed (optionally with an error to abort waiters)\n\t * once this was the last live handle; while other {@link clone}s remain open it stays live.\n\t */\n\tclose(abort?: Error) {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tif (--this.#state.consumers > 0) return;\n\t\tcloseState(this.#state, abort);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Broadcast role handles: a named collection of tracks produced by a publisher.\n *\n * @module\n */\nimport { type GetPromise, Once, Signal } from \"@moq/signals\";\nimport { NotFound } from \"./error.ts\";\nimport type { Consumer as GroupConsumer } from \"./group.ts\";\nimport { Route } from \"./hop.ts\";\nimport { hooks, type TrackSequence } from \"./internal.ts\";\nimport * as Path from \"./path.ts\";\nimport * as track from \"./track.ts\";\nimport { untilAborted } from \"./util/abort.ts\";\nimport { registerWire, trackOf, type Broadcast as Wire } from \"./wire.ts\";\n\n/** The origin callback a created broadcast uses to advertise its exact path. @internal */\nexport interface Announcer {\n\t/** Advertise or re-price this broadcast's path. */\n\tannounce(route: Route): void;\n\t/** Retract the advertisement from local consumers and peers alike. */\n\tunannounce(): void;\n}\n\nlet attachAnnouncer: (producer: Producer, announcer: Announcer) => void;\nlet stampProducer: (producer: Producer, path: Path.Valid) => void;\n\n/** Reactive backing state shared by broadcast producers and consumers. */\nclass BroadcastState {\n\trequested = new Signal<track.Request[]>([]);\n\tpending = new Set<track.Request>();\n\tclosed = new Once<Error | null>();\n\ttracks = new Map<string, track.Producer>();\n\tsequences = new Map<string, TrackSequence>();\n\t// Live consumer handles sharing this state (see {@link Consumer.clone}). The broadcast\n\t// closes once the last one closes, so a shared consumer can be handed to several callers.\n\tconsumers = 0;\n}\n\nfunction dequeueRequest(state: BroadcastState): track.Request | undefined {\n\tconst requested = state.requested.peek();\n\trequested.sort((a, b) => a.priority - b.priority);\n\treturn requested.pop();\n}\n\n// Close the broadcast and reject any requests still pending in the queue, so a\n// subscriber blocked on the track's info() or group reads is unblocked rather\n// than left waiting on a producer that will never be served.\n//\n// Once.set throws on a second settle, and the producer and each consumer handle close\n// independently, so this has to be idempotent.\nfunction closeState(state: BroadcastState, abort?: Error) {\n\tif (state.closed.peek() !== undefined) return;\n\tstate.closed.set(abort ?? null);\n\tfor (const request of state.pending) request.reject(abort);\n\tstate.requested.mutate((requests) => {\n\t\trequests.length = 0;\n\t});\n}\n\n// `register` is set on the subscribing (consumer) side: the fresh producer is cached in\n// `state.tracks` so repeat subscriptions to the same track fan out from one upstream subscription\n// instead of opening a new one, mirroring the Rust `broadcast::Consumer::track` weak-dedup. The\n// consumer wire watches the producer's demand ({@link track.Producer.used}) and tears the upstream\n// down once its last subscriber leaves, closing the producer, which evicts the cache entry below.\n// The publishing side leaves `register` false: `state.tracks` there holds only the tracks the app\n// inserted, and a dynamic serve stays one request per peer subscription.\nfunction subscribe(\n\tstate: BroadcastState,\n\tname: string,\n\toptions: track.Subscription = {},\n\tregister = false,\n): track.Subscriber {\n\tif (state.closed.peek() !== undefined) {\n\t\tthrow new Error(`broadcast is closed: ${state.closed.peek()}`);\n\t}\n\n\tconst existing = state.tracks.get(name);\n\tif (existing) {\n\t\tif (existing.closed.peek() === undefined) return existing.subscribe(options);\n\t\tstate.tracks.delete(name);\n\t}\n\n\tconst producer = new track.Producer(name);\n\tconst subscriber = producer.subscribe(options);\n\n\tif (register) {\n\t\tstate.tracks.set(name, producer);\n\t\t// Drop the cache entry once the upstream closes (the wire tears it down when its last\n\t\t// subscriber leaves), so a later subscribe re-opens it.\n\t\tvoid producer.closed.then(() => {\n\t\t\tif (state.tracks.get(name) === producer) state.tracks.delete(name);\n\t\t});\n\t}\n\n\tstate.requested.mutate((requested) => {\n\t\trequested.push(hooks.makeRequest({ name, producer, sequences: state.sequences, pending: state.pending }));\n\t});\n\n\treturn subscriber;\n}\n\nasync function resolveTrackInfo(state: BroadcastState, name: string): Promise<track.Info> {\n\tconst existing = state.tracks.get(name);\n\tif (existing && existing.closed.peek() === undefined) {\n\t\treturn existing.info();\n\t}\n\n\tif (state.closed.peek() !== undefined) {\n\t\treturn Promise.reject(new Error(`broadcast is closed: ${state.closed.peek()}`));\n\t}\n\n\tconst producer = new track.Producer(name);\n\tstate.requested.mutate((requested) => {\n\t\trequested.push(hooks.makeRequest({ name, producer, sequences: state.sequences, pending: state.pending }));\n\t});\n\n\ttry {\n\t\treturn await producer.info();\n\t} finally {\n\t\tproducer.close();\n\t}\n}\n\n// Serve a group from the local retained window by subscribing and scanning to the\n// requested sequence. The default for a produced broadcast; the consuming wire layer\n// overrides it to fetch over the network (or to reject when the transport has no FETCH).\nasync function fetchGroup(\n\tstate: BroadcastState,\n\tname: string,\n\tsequence: number,\n\toptions: track.FetchGroupOptions = {},\n): Promise<GroupConsumer> {\n\toptions.signal?.throwIfAborted();\n\tconst subscriber = subscribe(state, name, { priority: options.priority });\n\thooks.exemptFetch(subscriber);\n\ttry {\n\t\tfor (;;) {\n\t\t\tconst group = await untilAborted(subscriber.recvGroup(), options.signal);\n\t\t\tif (!group) throw new NotFound(`group ${sequence}`);\n\t\t\tif (group.sequence === sequence) {\n\t\t\t\t// Close the subscription when the returned group finishes, not now: an\n\t\t\t\t// in-progress group must keep receiving frames for its lifetime (mirrors\n\t\t\t\t// Rust poll_fetch). Also fires if the caller closes the group early.\n\t\t\t\tvoid group.closed.then(() => subscriber.close());\n\t\t\t\treturn group;\n\t\t\t}\n\n\t\t\tgroup.close();\n\t\t\tif (group.sequence > sequence) throw new NotFound(`group ${sequence}`);\n\t\t}\n\t} catch (err) {\n\t\tsubscriber.close();\n\t\tthrow err;\n\t}\n}\n\n/**\n * The write side of a broadcast.\n *\n * @public\n */\nexport class Producer {\n\t#state = new BroadcastState();\n\t#announcer?: Announcer;\n\t#path = Path.empty();\n\n\tconstructor() {\n\t\tregisterWire(this, this.#wire(false));\n\t}\n\n\tstatic {\n\t\tattachAnnouncer = (producer, announcer) => {\n\t\t\tproducer.#announcer = announcer;\n\t\t};\n\t\thooks.attachAnnouncer = attachAnnouncer;\n\t\tstampProducer = (producer, path) => {\n\t\t\tproducer.#path = path;\n\t\t};\n\t}\n\n\t/**\n\t * Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.\n\t * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.\n\t */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\t/** A read handle for this broadcast, named by the path the origin created it at. */\n\tconsume(): Consumer {\n\t\treturn makeConsumer({ state: this.#state, path: this.#path });\n\t}\n\n\tasync #requested(): Promise<track.Request | undefined> {\n\t\tfor (;;) {\n\t\t\tconst request = dequeueRequest(this.#state);\n\t\t\tif (request) return request;\n\n\t\t\tconst closed = this.#state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed !== undefined) return undefined;\n\n\t\t\tawait Signal.race(this.#state.requested, this.#state.closed);\n\t\t}\n\t}\n\n\t/** Insert a track that is served directly, without an on-demand request round-trip. */\n\tinsertTrack(track: track.Producer): void {\n\t\tif (this.#state.closed.peek() !== undefined) {\n\t\t\tthrow new Error(`broadcast is closed: ${this.#state.closed.peek()}`);\n\t\t}\n\n\t\tconst existing = this.#state.tracks.get(track.name);\n\t\tif (existing && existing.closed.peek() === undefined) {\n\t\t\tthrow new Error(`duplicate track: ${track.name}`);\n\t\t}\n\n\t\tthis.#state.tracks.set(track.name, track);\n\n\t\tvoid track.closed.then(() => {\n\t\t\tif (this.#state.tracks.get(track.name) === track) {\n\t\t\t\tthis.#state.tracks.delete(track.name);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Create a track, insert it into the broadcast, and return its producer. */\n\tcreateTrack(name: string, info: Partial<track.Info> = {}): track.Producer {\n\t\tconst producer = new track.Producer(name).accept(info);\n\t\tthis.insertTrack(producer);\n\t\treturn producer;\n\t}\n\n\t/** Remove a statically inserted track by name. */\n\tremoveTrack(name: string): void {\n\t\tthis.#state.tracks.delete(name);\n\t}\n\n\t/** A lazy read handle for a track on this broadcast. */\n\ttrack(name: string): track.Consumer {\n\t\treturn trackOf(name, this);\n\t}\n\n\t#wire(register: boolean): Wire {\n\t\treturn {\n\t\t\tsubscribe: (name, options) => subscribe(this.#state, name, options, register),\n\t\t\tresolveTrackInfo: (name) => resolveTrackInfo(this.#state, name),\n\t\t\tfetchGroup: (name, sequence, options) => fetchGroup(this.#state, name, sequence, options),\n\t\t\trequested: () => this.#requested(),\n\t\t};\n\t}\n\n\t/**\n\t * Advertise this broadcast's exact path, or re-price a standing advertisement in place.\n\t *\n\t * Call it once the tracks a subscriber needs first (a catalog) exist. Until then the\n\t * broadcast exists for nobody, on its own origin or at a peer. Retracts on\n\t * {@link unannounce} or {@link close}. Throws if this producer was not created through an\n\t * origin, or if the broadcast is already closed.\n\t */\n\tannounce(route: Route | { hops?: Route[\"hops\"]; cost?: Route[\"cost\"] | bigint } = Route.default): void {\n\t\tif (this.#state.closed.peek() !== undefined) {\n\t\t\tthrow new Error(`broadcast is closed: ${this.#state.closed.peek()}`);\n\t\t}\n\t\tif (!this.#announcer) throw new Error(\"broadcast is not attached to an origin\");\n\t\tthis.#announcer.announce(Route.normalize(route));\n\t}\n\n\t/**\n\t * Retract the advertisement of this broadcast's path, if any, from local consumers and\n\t * peers alike. {@link announce} brings it back.\n\t */\n\tunannounce(): void {\n\t\tthis.#announcer?.unannounce();\n\t}\n\n\t/** End the broadcast for good: retract it, serve no new tracks, and refuse a later {@link announce}. Idempotent. */\n\tclose(): void;\n\t/** @deprecated A broadcast end carries no cause; call `close()` without one. */\n\tclose(abort?: Error): void;\n\tclose(abort?: Error) {\n\t\tthis.#announcer?.unannounce();\n\t\tthis.#announcer = undefined;\n\t\tcloseState(this.#state, abort);\n\t}\n}\n\n// What a new consumer handle inherits: the shared broadcast plus the path naming it.\ninterface Shared {\n\tstate: BroadcastState;\n\tpath: Path.Valid;\n}\n\n// Constructs a Consumer from within this module without exposing a public constructor\n// that would leak the unexported BroadcastState. Assigned in the class's static block.\nlet makeConsumer: (shared: Shared) => Consumer;\n\n/**\n * The read side of a broadcast.\n *\n * Created internally: obtain one from {@link Producer.consume} or an origin request.\n * The wire layers subclass it to resolve tracks over the network.\n *\n * @public\n */\nexport class Consumer {\n\t#state: BroadcastState;\n\t#path: Path.Valid;\n\n\t// Guards against a double close() on this handle over-decrementing the consumer count.\n\t#closed = false;\n\n\tprotected constructor(shared?: never);\n\tprotected constructor(shared?: Shared) {\n\t\tthis.#state = shared?.state ?? new BroadcastState();\n\t\tthis.#path = shared?.path ?? Path.empty();\n\t\tthis.#state.consumers++;\n\t\tregisterWire(this, {\n\t\t\tsubscribe: (name, options) => subscribe(this.#state, name, options, true),\n\t\t\tresolveTrackInfo: (name) => resolveTrackInfo(this.#state, name),\n\t\t\tfetchGroup: (name, sequence, options) => fetchGroup(this.#state, name, sequence, options),\n\t\t\trequested: () => this.#requested(),\n\t\t});\n\t}\n\n\tstatic {\n\t\tmakeConsumer = (shared) => new Consumer(shared as never);\n\t\thooks.stampPath = (target, path) => {\n\t\t\tif (target instanceof Consumer) target.#path = path;\n\t\t\telse stampProducer(target, path);\n\t\t};\n\t}\n\n\t/**\n\t * The path this handle names the broadcast by, which relative references in its catalog\n\t * (hang's `broadcast` field) resolve against.\n\t *\n\t * An origin stamps each handle it hands out with the path it was requested at, relative to\n\t * that origin handle's scope root, and a broadcast it created with the path it was created at.\n\t * Empty for a standalone broadcast, which is then its own root: any `..` reference escapes.\n\t */\n\tget path(): Path.Valid {\n\t\treturn this.#path;\n\t}\n\n\t/**\n\t * Settles once the broadcast closes: `null` on a clean close, or the abort {@link Error}.\n\t * Peek it synchronously (`undefined` while open), observe it reactively, or `await` it.\n\t *\n\t * Shared by every {@link clone}: it settles once the last handle closes. The subscribing\n\t * wire layer peeks it to evict a closed entry from its per-path consume cache.\n\t */\n\tget closed(): GetPromise<Error | null> {\n\t\treturn this.#state.closed;\n\t}\n\n\t/**\n\t * Return another handle to the same broadcast, reference-counted with this one.\n\t *\n\t * Both handles read the same tracks, carry the same {@link path}, and share one {@link closed}\n\t * state; the broadcast closes only once *every* handle has {@link close}d. Used by the connection's per-path\n\t * consume cache to share one subscription across callers. Subclasses that resolve info over\n\t * the wire override this to preserve their type (see the wire layer's consumed broadcast).\n\t */\n\tclone(): Consumer {\n\t\treturn new Consumer(this.shareState());\n\t}\n\n\t// Hand this consumer's backing state and path to a clone. Opaque (`never`) so the state type\n\t// stays unexported; a subclass passes it straight back into its own `super(...)`.\n\tprotected shareState(): never {\n\t\treturn { state: this.#state, path: this.#path } satisfies Shared as never;\n\t}\n\n\t/** Get a lazy handle for a track on this broadcast. Repeat subscriptions dedupe onto one upstream subscription. */\n\ttrack(name: string): track.Consumer {\n\t\treturn trackOf(name, this);\n\t}\n\n\tasync #requested(): Promise<track.Request | undefined> {\n\t\tfor (;;) {\n\t\t\tconst request = dequeueRequest(this.#state);\n\t\t\tif (request) return request;\n\n\t\t\tconst closed = this.#state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed !== undefined) return undefined;\n\n\t\t\tawait Signal.race(this.#state.requested, this.#state.closed);\n\t\t}\n\t}\n\n\t/**\n\t * Release this handle. The broadcast is closed once this was the last live handle;\n\t * while other {@link clone}s remain open it stays live.\n\t */\n\tclose(): void;\n\t/** @deprecated A broadcast end carries no cause; call `close()` without one. */\n\tclose(abort?: Error): void;\n\tclose(abort?: Error) {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tif (--this.#state.consumers > 0) return;\n\t\tcloseState(this.#state, abort);\n\t}\n}\n"]}
|
package/connection/accept.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import type { Consumer as OriginConsumer, Producer as OriginProducer } from "../origin";
|
|
1
2
|
import type { Established } from "./established";
|
|
2
3
|
/** Options for {@link accept}. */
|
|
3
4
|
export interface AcceptProps {
|
|
5
|
+
/** The accepted transport. */
|
|
6
|
+
transport: WebTransport;
|
|
7
|
+
/** The request URL associated with the transport. */
|
|
8
|
+
url: URL;
|
|
4
9
|
/** Version to select during SETUP negotiation (for non-ALPN paths). */
|
|
5
10
|
version?: number;
|
|
6
11
|
/**
|
|
@@ -8,6 +13,16 @@ export interface AcceptProps {
|
|
|
8
13
|
* Defaults to true.
|
|
9
14
|
*/
|
|
10
15
|
discovery?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The origin whose broadcasts the session announces and serves to the peer. Omit to
|
|
18
|
+
* publish nothing. Borrowed, not owned: closing the session leaves its broadcasts alone.
|
|
19
|
+
*/
|
|
20
|
+
publish?: OriginConsumer;
|
|
21
|
+
/**
|
|
22
|
+
* The origin the session feeds with the peer's announced broadcasts. Omit to discover
|
|
23
|
+
* nothing. The entries retract when the session dies; see the `consume` connect option.
|
|
24
|
+
*/
|
|
25
|
+
consume?: OriginProducer;
|
|
11
26
|
}
|
|
12
27
|
/**
|
|
13
28
|
* Server-side handshake: accepts a transport and performs the server half of the SETUP exchange.
|
|
@@ -17,5 +32,5 @@ export interface AcceptProps {
|
|
|
17
32
|
* @param props - Optional configuration
|
|
18
33
|
* @returns A promise that resolves to a Connection instance
|
|
19
34
|
*/
|
|
20
|
-
export declare function accept(transport
|
|
35
|
+
export declare function accept({ transport, url, ...props }: AcceptProps): Promise<Established>;
|
|
21
36
|
//# sourceMappingURL=accept.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accept.d.ts","sourceRoot":"","sources":["../../src/connection/accept.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"accept.d.ts","sourceRoot":"","sources":["../../src/connection/accept.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC3B,8BAA8B;IAC9B,SAAS,EAAE,YAAY,CAAC;IACxB,qDAAqD;IACrD,GAAG,EAAE,GAAG,CAAC;IAET,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB;;;OAGG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;CACzB;AAQD;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAI5F"}
|
package/connection/accept.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as Ietf from "../ietf/index.js";
|
|
3
3
|
import * as Lite from "../lite/index.js";
|
|
4
4
|
import { Stream } from "../stream.js";
|
|
5
|
+
import { forwardAnnounced } from "./forward.js";
|
|
5
6
|
import { exchangeSetup } from "./handshake.js";
|
|
6
7
|
/**
|
|
7
8
|
* Server-side handshake: accepts a transport and performs the server half of the SETUP exchange.
|
|
@@ -11,42 +12,61 @@ import { exchangeSetup } from "./handshake.js";
|
|
|
11
12
|
* @param props - Optional configuration
|
|
12
13
|
* @returns A promise that resolves to a Connection instance
|
|
13
14
|
*/
|
|
14
|
-
export async function accept(transport, url, props) {
|
|
15
|
-
|
|
15
|
+
export async function accept({ transport, url, ...props }) {
|
|
16
|
+
const connection = await acceptInner(transport, url, props);
|
|
17
|
+
if (props.consume)
|
|
18
|
+
forwardAnnounced(connection, props.consume);
|
|
19
|
+
return connection;
|
|
20
|
+
}
|
|
21
|
+
async function acceptInner(transport, url, props) {
|
|
22
|
+
// The DOM lib has no `protocol` property yet. It is "" when none was negotiated, and
|
|
23
|
+
// undefined in a browser that predates subprotocols (Firefox before 155).
|
|
16
24
|
const protocol = transport.protocol;
|
|
17
|
-
const
|
|
25
|
+
const wiring = {
|
|
26
|
+
discovery: props.discovery ?? true,
|
|
27
|
+
publish: props.publish,
|
|
28
|
+
};
|
|
29
|
+
if (protocol === Ietf.ALPN.DRAFT_22) {
|
|
30
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_22, wiring);
|
|
31
|
+
}
|
|
32
|
+
if (protocol === Ietf.ALPN.DRAFT_21) {
|
|
33
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_21, wiring);
|
|
34
|
+
}
|
|
18
35
|
if (protocol === Ietf.ALPN.DRAFT_20) {
|
|
19
|
-
return acceptAlpn(transport, url, Ietf.Version.DRAFT_20,
|
|
36
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_20, wiring);
|
|
20
37
|
}
|
|
21
38
|
if (protocol === Ietf.ALPN.DRAFT_19) {
|
|
22
|
-
return acceptAlpn(transport, url, Ietf.Version.DRAFT_19,
|
|
39
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_19, wiring);
|
|
23
40
|
}
|
|
24
41
|
else if (protocol === Ietf.ALPN.DRAFT_18) {
|
|
25
|
-
return acceptAlpn(transport, url, Ietf.Version.DRAFT_18,
|
|
42
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_18, wiring);
|
|
26
43
|
}
|
|
27
44
|
else if (protocol === Ietf.ALPN.DRAFT_17) {
|
|
28
|
-
return acceptAlpn(transport, url, Ietf.Version.DRAFT_17,
|
|
45
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_17, wiring);
|
|
29
46
|
}
|
|
30
47
|
else if (protocol === Ietf.ALPN.DRAFT_16) {
|
|
31
|
-
return acceptSetup(transport, url, Ietf.Version.DRAFT_16,
|
|
48
|
+
return acceptSetup(transport, url, Ietf.Version.DRAFT_16, wiring);
|
|
32
49
|
}
|
|
33
50
|
else if (protocol === Ietf.ALPN.DRAFT_15) {
|
|
34
|
-
return acceptSetup(transport, url, Ietf.Version.DRAFT_15,
|
|
51
|
+
return acceptSetup(transport, url, Ietf.Version.DRAFT_15, wiring);
|
|
52
|
+
}
|
|
53
|
+
else if (protocol === Lite.ALPN_07_WIP) {
|
|
54
|
+
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_07, ...wiring });
|
|
35
55
|
}
|
|
36
|
-
else if (protocol === Lite.
|
|
37
|
-
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_06,
|
|
56
|
+
else if (protocol === Lite.ALPN_06) {
|
|
57
|
+
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_06, ...wiring });
|
|
38
58
|
}
|
|
39
59
|
else if (protocol === Lite.ALPN_05) {
|
|
40
|
-
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_05,
|
|
60
|
+
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_05, ...wiring });
|
|
41
61
|
}
|
|
42
62
|
else if (protocol === Lite.ALPN_04) {
|
|
43
|
-
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_04,
|
|
63
|
+
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_04, ...wiring });
|
|
44
64
|
}
|
|
45
65
|
else if (protocol === Lite.ALPN_03) {
|
|
46
|
-
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_03,
|
|
66
|
+
return new Lite.Connection({ url, quic: transport, version: Lite.Version.DRAFT_03, ...wiring });
|
|
47
67
|
}
|
|
48
68
|
else if (protocol === Lite.ALPN || protocol === "" || protocol === undefined) {
|
|
49
|
-
return acceptNegotiated(transport, url, props);
|
|
69
|
+
return acceptNegotiated(transport, url, wiring, props.version);
|
|
50
70
|
}
|
|
51
71
|
else {
|
|
52
72
|
throw new Error(`unsupported WebTransport protocol: ${protocol}`);
|
|
@@ -56,15 +76,16 @@ export async function accept(transport, url, props) {
|
|
|
56
76
|
* Draft-17+ accept: ALPN already pinned the version. SETUP is exchanged over
|
|
57
77
|
* a pair of uni streams using stream type 0x2F00.
|
|
58
78
|
*/
|
|
59
|
-
async function acceptAlpn(transport, url, version,
|
|
60
|
-
const { control, solicit, cluster } = await exchangeSetup(transport, version, "moq-lite-js");
|
|
79
|
+
async function acceptAlpn(transport, url, version, wiring) {
|
|
80
|
+
const { control, solicit, hidden, cluster } = await exchangeSetup(transport, version, "moq-lite-js");
|
|
61
81
|
return new Ietf.Connection({
|
|
62
|
-
|
|
82
|
+
...wiring,
|
|
63
83
|
client: false,
|
|
64
84
|
url,
|
|
65
85
|
quic: transport,
|
|
66
86
|
control,
|
|
67
87
|
solicit,
|
|
88
|
+
hidden,
|
|
68
89
|
cluster,
|
|
69
90
|
// v17+ uses NativeSession which manages its own request IDs; maxRequestId is unused.
|
|
70
91
|
maxRequestId: 0n,
|
|
@@ -75,9 +96,9 @@ async function acceptAlpn(transport, url, version, discovery) {
|
|
|
75
96
|
* Legacy accept (draft-15/16): ALPN pinned the version, but the SETUP message
|
|
76
97
|
* is still exchanged over a bidi stream wrapped in the moq-lite compat envelope.
|
|
77
98
|
*/
|
|
78
|
-
async function acceptSetup(transport, url, version,
|
|
99
|
+
async function acceptSetup(transport, url, version, wiring) {
|
|
79
100
|
// Accept bidi, read ClientSetup, write ServerSetup
|
|
80
|
-
const stream = await Stream.accept(transport);
|
|
101
|
+
const stream = await Stream.accept(transport, version);
|
|
81
102
|
if (!stream)
|
|
82
103
|
throw new Error("no incoming bidi stream for SETUP");
|
|
83
104
|
const clientCompat = await stream.reader.u53();
|
|
@@ -91,11 +112,12 @@ async function acceptSetup(transport, url, version, discovery) {
|
|
|
91
112
|
params.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);
|
|
92
113
|
params.setBytes(Ietf.SetupOption.Implementation, encoder.encode("moq-lite-js"));
|
|
93
114
|
Ietf.solicitIntoSetup(params);
|
|
115
|
+
Ietf.hiddenIntoSetup(params);
|
|
94
116
|
const server = new Ietf.ServerSetup({ version, parameters: params });
|
|
95
117
|
await server.encode(stream.writer, version);
|
|
96
118
|
const maxRequestId = 42069n;
|
|
97
119
|
return new Ietf.Connection({
|
|
98
|
-
|
|
120
|
+
...wiring,
|
|
99
121
|
client: false,
|
|
100
122
|
url,
|
|
101
123
|
quic: transport,
|
|
@@ -103,12 +125,12 @@ async function acceptSetup(transport, url, version, discovery) {
|
|
|
103
125
|
maxRequestId,
|
|
104
126
|
version,
|
|
105
127
|
solicit: Ietf.solicitFromSetup(client.parameters),
|
|
128
|
+
hidden: Ietf.hiddenFromSetup(client.parameters),
|
|
106
129
|
});
|
|
107
130
|
}
|
|
108
|
-
async function acceptNegotiated(transport, url,
|
|
109
|
-
const discovery = props?.discovery ?? true;
|
|
131
|
+
async function acceptNegotiated(transport, url, wiring, version) {
|
|
110
132
|
const setupVersion = Ietf.Version.DRAFT_14;
|
|
111
|
-
const stream = await Stream.accept(transport);
|
|
133
|
+
const stream = await Stream.accept(transport, setupVersion);
|
|
112
134
|
if (!stream)
|
|
113
135
|
throw new Error("no incoming bidi stream for SETUP");
|
|
114
136
|
const clientCompat = await stream.reader.u53();
|
|
@@ -119,8 +141,8 @@ async function acceptNegotiated(transport, url, props) {
|
|
|
119
141
|
// Pick the requested version, or first matching version from client's list
|
|
120
142
|
const allVersions = [...Object.values(Lite.Version), ...Object.values(Ietf.Version)];
|
|
121
143
|
let selectedVersion;
|
|
122
|
-
if (
|
|
123
|
-
selectedVersion =
|
|
144
|
+
if (version !== undefined) {
|
|
145
|
+
selectedVersion = version;
|
|
124
146
|
}
|
|
125
147
|
else {
|
|
126
148
|
const match = client.versions.find((v) => allVersions.includes(v));
|
|
@@ -135,6 +157,7 @@ async function acceptNegotiated(transport, url, props) {
|
|
|
135
157
|
params.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);
|
|
136
158
|
params.setBytes(Ietf.SetupOption.Implementation, encoder.encode("moq-lite-js"));
|
|
137
159
|
Ietf.solicitIntoSetup(params);
|
|
160
|
+
Ietf.hiddenIntoSetup(params);
|
|
138
161
|
const server = new Ietf.ServerSetup({ version: selectedVersion, parameters: params });
|
|
139
162
|
await server.encode(stream.writer, setupVersion);
|
|
140
163
|
if (Object.values(Lite.Version).includes(selectedVersion)) {
|
|
@@ -143,13 +166,13 @@ async function acceptNegotiated(transport, url, props) {
|
|
|
143
166
|
quic: transport,
|
|
144
167
|
version: selectedVersion,
|
|
145
168
|
session: stream,
|
|
146
|
-
|
|
169
|
+
...wiring,
|
|
147
170
|
});
|
|
148
171
|
}
|
|
149
172
|
else if (Object.values(Ietf.Version).includes(selectedVersion)) {
|
|
150
173
|
const maxRequestId = client.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;
|
|
151
174
|
return new Ietf.Connection({
|
|
152
|
-
|
|
175
|
+
...wiring,
|
|
153
176
|
client: false,
|
|
154
177
|
url,
|
|
155
178
|
quic: transport,
|
|
@@ -157,6 +180,7 @@ async function acceptNegotiated(transport, url, props) {
|
|
|
157
180
|
maxRequestId,
|
|
158
181
|
version: selectedVersion,
|
|
159
182
|
solicit: Ietf.solicitFromSetup(client.parameters),
|
|
183
|
+
hidden: Ietf.hiddenFromSetup(client.parameters),
|
|
160
184
|
});
|
|
161
185
|
}
|
|
162
186
|
else {
|