@norskvideo/moq-net 0.1.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 +70 -0
- package/announced.d.ts +45 -0
- package/announced.d.ts.map +1 -0
- package/announced.js +70 -0
- package/announced.js.map +1 -0
- package/bandwidth.d.ts +11 -0
- package/bandwidth.d.ts.map +1 -0
- package/bandwidth.js +7 -0
- package/bandwidth.js.map +1 -0
- package/broadcast.d.ts +39 -0
- package/broadcast.d.ts.map +1 -0
- package/broadcast.js +76 -0
- package/broadcast.js.map +1 -0
- package/connection/accept.d.ts +15 -0
- package/connection/accept.d.ts.map +1 -0
- package/connection/accept.js +137 -0
- package/connection/accept.js.map +1 -0
- package/connection/cert.test.d.ts +2 -0
- package/connection/cert.test.d.ts.map +1 -0
- package/connection/cert.test.js +26 -0
- package/connection/cert.test.js.map +1 -0
- package/connection/connect.d.ts +37 -0
- package/connection/connect.d.ts.map +1 -0
- package/connection/connect.js +348 -0
- package/connection/connect.js.map +1 -0
- package/connection/established.d.ts +30 -0
- package/connection/established.d.ts.map +1 -0
- package/connection/established.js +3 -0
- package/connection/established.js.map +1 -0
- package/connection/handshake.d.ts +10 -0
- package/connection/handshake.d.ts.map +1 -0
- package/connection/handshake.js +48 -0
- package/connection/handshake.js.map +1 -0
- package/connection/index.d.ts +5 -0
- package/connection/index.d.ts.map +1 -0
- package/connection/index.js +6 -0
- package/connection/index.js.map +1 -0
- package/connection/reload.d.ts +47 -0
- package/connection/reload.d.ts.map +1 -0
- package/connection/reload.js +137 -0
- package/connection/reload.js.map +1 -0
- package/group.d.ts +66 -0
- package/group.d.ts.map +1 -0
- package/group.js +135 -0
- package/group.js.map +1 -0
- package/group.test.d.ts +2 -0
- package/group.test.d.ts.map +1 -0
- package/group.test.js +66 -0
- package/group.test.js.map +1 -0
- package/ietf/adapter.d.ts +65 -0
- package/ietf/adapter.d.ts.map +1 -0
- package/ietf/adapter.js +626 -0
- package/ietf/adapter.js.map +1 -0
- package/ietf/connection.d.ts +65 -0
- package/ietf/connection.d.ts.map +1 -0
- package/ietf/connection.js +248 -0
- package/ietf/connection.js.map +1 -0
- package/ietf/control.d.ts +135 -0
- package/ietf/control.d.ts.map +1 -0
- package/ietf/control.js +242 -0
- package/ietf/control.js.map +1 -0
- package/ietf/fetch.d.ts +64 -0
- package/ietf/fetch.d.ts.map +1 -0
- package/ietf/fetch.js +100 -0
- package/ietf/fetch.js.map +1 -0
- package/ietf/goaway.d.ts +15 -0
- package/ietf/goaway.d.ts.map +1 -0
- package/ietf/goaway.js +34 -0
- package/ietf/goaway.js.map +1 -0
- package/ietf/ietf.test.d.ts +2 -0
- package/ietf/ietf.test.d.ts.map +1 -0
- package/ietf/ietf.test.js +873 -0
- package/ietf/ietf.test.js.map +1 -0
- package/ietf/index.d.ts +18 -0
- package/ietf/index.d.ts.map +1 -0
- package/ietf/index.js +19 -0
- package/ietf/index.js.map +1 -0
- package/ietf/message.d.ts +4 -0
- package/ietf/message.d.ts.map +1 -0
- package/ietf/message.js +54 -0
- package/ietf/message.js.map +1 -0
- package/ietf/namespace.d.ts +5 -0
- package/ietf/namespace.d.ts.map +1 -0
- package/ietf/namespace.js +27 -0
- package/ietf/namespace.js.map +1 -0
- package/ietf/object.d.ts +40 -0
- package/ietf/object.d.ts.map +1 -0
- package/ietf/object.js +160 -0
- package/ietf/object.js.map +1 -0
- package/ietf/parameters.d.ts +56 -0
- package/ietf/parameters.d.ts.map +1 -0
- package/ietf/parameters.js +337 -0
- package/ietf/parameters.js.map +1 -0
- package/ietf/properties.d.ts +4 -0
- package/ietf/properties.d.ts.map +1 -0
- package/ietf/properties.js +33 -0
- package/ietf/properties.js.map +1 -0
- package/ietf/publish.d.ts +68 -0
- package/ietf/publish.d.ts.map +1 -0
- package/ietf/publish.js +192 -0
- package/ietf/publish.js.map +1 -0
- package/ietf/publish_namespace.d.ts +68 -0
- package/ietf/publish_namespace.d.ts.map +1 -0
- package/ietf/publish_namespace.js +176 -0
- package/ietf/publish_namespace.js.map +1 -0
- package/ietf/publisher.d.ts +50 -0
- package/ietf/publisher.d.ts.map +1 -0
- package/ietf/publisher.js +330 -0
- package/ietf/publisher.js.map +1 -0
- package/ietf/request.d.ts +52 -0
- package/ietf/request.d.ts.map +1 -0
- package/ietf/request.js +119 -0
- package/ietf/request.js.map +1 -0
- package/ietf/setup.d.ts +38 -0
- package/ietf/setup.d.ts.map +1 -0
- package/ietf/setup.js +129 -0
- package/ietf/setup.js.map +1 -0
- package/ietf/subscribe.d.ts +66 -0
- package/ietf/subscribe.d.ts.map +1 -0
- package/ietf/subscribe.js +276 -0
- package/ietf/subscribe.js.map +1 -0
- package/ietf/subscribe_namespace.d.ts +101 -0
- package/ietf/subscribe_namespace.d.ts.map +1 -0
- package/ietf/subscribe_namespace.js +256 -0
- package/ietf/subscribe_namespace.js.map +1 -0
- package/ietf/subscriber.d.ts +53 -0
- package/ietf/subscriber.d.ts.map +1 -0
- package/ietf/subscriber.js +404 -0
- package/ietf/subscriber.js.map +1 -0
- package/ietf/track.d.ts +40 -0
- package/ietf/track.d.ts.map +1 -0
- package/ietf/track.js +108 -0
- package/ietf/track.js.map +1 -0
- package/ietf/version.d.ts +56 -0
- package/ietf/version.d.ts.map +1 -0
- package/ietf/version.js +63 -0
- package/ietf/version.js.map +1 -0
- package/index.d.ts +22 -0
- package/index.d.ts.map +1 -0
- package/index.js +23 -0
- package/index.js.map +1 -0
- package/integration.test.d.ts +2 -0
- package/integration.test.d.ts.map +1 -0
- package/integration.test.js +90 -0
- package/integration.test.js.map +1 -0
- package/lite/announce.d.ts +35 -0
- package/lite/announce.d.ts.map +1 -0
- package/lite/announce.js +166 -0
- package/lite/announce.js.map +1 -0
- package/lite/connection.d.ts +47 -0
- package/lite/connection.d.ts.map +1 -0
- package/lite/connection.js +247 -0
- package/lite/connection.js.map +1 -0
- package/lite/fetch.d.ts +14 -0
- package/lite/fetch.d.ts.map +1 -0
- package/lite/fetch.js +47 -0
- package/lite/fetch.js.map +1 -0
- package/lite/goaway.d.ts +10 -0
- package/lite/goaway.d.ts.map +1 -0
- package/lite/goaway.js +37 -0
- package/lite/goaway.js.map +1 -0
- package/lite/group.d.ts +18 -0
- package/lite/group.d.ts.map +1 -0
- package/lite/group.js +46 -0
- package/lite/group.js.map +1 -0
- package/lite/index.d.ts +11 -0
- package/lite/index.d.ts.map +1 -0
- package/lite/index.js +12 -0
- package/lite/index.js.map +1 -0
- package/lite/message.d.ts +5 -0
- package/lite/message.d.ts.map +1 -0
- package/lite/message.js +51 -0
- package/lite/message.js.map +1 -0
- package/lite/origin.d.ts +25 -0
- package/lite/origin.d.ts.map +1 -0
- package/lite/origin.js +35 -0
- package/lite/origin.js.map +1 -0
- package/lite/probe.d.ts +12 -0
- package/lite/probe.d.ts.map +1 -0
- package/lite/probe.js +60 -0
- package/lite/probe.js.map +1 -0
- package/lite/publisher.d.ts +63 -0
- package/lite/publisher.d.ts.map +1 -0
- package/lite/publisher.js +350 -0
- package/lite/publisher.js.map +1 -0
- package/lite/session.d.ts +36 -0
- package/lite/session.d.ts.map +1 -0
- package/lite/session.js +130 -0
- package/lite/session.js.map +1 -0
- package/lite/setup.d.ts +18 -0
- package/lite/setup.d.ts.map +1 -0
- package/lite/setup.js +55 -0
- package/lite/setup.js.map +1 -0
- package/lite/stream.d.ts +23 -0
- package/lite/stream.d.ts.map +1 -0
- package/lite/stream.js +20 -0
- package/lite/stream.js.map +1 -0
- package/lite/subscribe.d.ts +111 -0
- package/lite/subscribe.d.ts.map +1 -0
- package/lite/subscribe.js +341 -0
- package/lite/subscribe.js.map +1 -0
- package/lite/subscriber.d.ts +80 -0
- package/lite/subscriber.d.ts.map +1 -0
- package/lite/subscriber.js +316 -0
- package/lite/subscriber.js.map +1 -0
- package/lite/track.d.ts +38 -0
- package/lite/track.d.ts.map +1 -0
- package/lite/track.js +81 -0
- package/lite/track.js.map +1 -0
- package/lite/track.test.d.ts +2 -0
- package/lite/track.test.d.ts.map +1 -0
- package/lite/track.test.js +53 -0
- package/lite/track.test.js.map +1 -0
- package/lite/version.d.ts +16 -0
- package/lite/version.d.ts.map +1 -0
- package/lite/version.js +41 -0
- package/lite/version.js.map +1 -0
- package/mock.d.ts +16 -0
- package/mock.d.ts.map +1 -0
- package/mock.js +154 -0
- package/mock.js.map +1 -0
- package/package.json +27 -0
- package/path.d.ts +111 -0
- package/path.d.ts.map +1 -0
- package/path.js +133 -0
- package/path.js.map +1 -0
- package/path.test.d.ts +2 -0
- package/path.test.d.ts.map +1 -0
- package/path.test.js +169 -0
- package/path.test.js.map +1 -0
- package/stream.d.ts +57 -0
- package/stream.d.ts.map +1 -0
- package/stream.js +326 -0
- package/stream.js.map +1 -0
- package/stream.test.d.ts +2 -0
- package/stream.test.d.ts.map +1 -0
- package/stream.test.js +210 -0
- package/stream.test.js.map +1 -0
- package/time.d.ts +109 -0
- package/time.d.ts.map +1 -0
- package/time.js +94 -0
- package/time.js.map +1 -0
- package/track.d.ts +85 -0
- package/track.d.ts.map +1 -0
- package/track.js +227 -0
- package/track.js.map +1 -0
- package/track.test.d.ts +2 -0
- package/track.test.d.ts.map +1 -0
- package/track.test.js +56 -0
- package/track.test.js.map +1 -0
- package/util/error.d.ts +3 -0
- package/util/error.d.ts.map +1 -0
- package/util/error.js +9 -0
- package/util/error.js.map +1 -0
- package/util/hex.d.ts +3 -0
- package/util/hex.d.ts.map +1 -0
- package/util/hex.js +16 -0
- package/util/hex.js.map +1 -0
- package/util/index.d.ts +2 -0
- package/util/index.d.ts.map +1 -0
- package/util/index.js +3 -0
- package/util/index.js.map +1 -0
- package/varint.d.ts +36 -0
- package/varint.d.ts.map +1 -0
- package/varint.js +288 -0
- package/varint.js.map +1 -0
- package/varint.test.d.ts +2 -0
- package/varint.test.d.ts.map +1 -0
- package/varint.test.js +106 -0
- package/varint.test.js.map +1 -0
- package/zod.d.ts +13 -0
- package/zod.d.ts.map +1 -0
- package/zod.js +19 -0
- package/zod.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img height="128px" src="https://github.com/moq-dev/moq/blob/main/.github/logo.svg" alt="Media over QUIC">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @moq/net
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@moq/net)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+
A TypeScript [Media over QUIC](https://moq.dev/) (MoQ) client for both browsers and server JS/TS environments.
|
|
11
|
+
`@moq/net` is the **networking layer**: real-time pub/sub with built-in caching, fan-out, and prioritization, on top of QUIC. At session setup it negotiates one of two wire protocols, either the simplified [moq-lite](https://doc.moq.dev/concept/layer/moq-lite) protocol or the full IETF [moq-transport](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/) draft.
|
|
12
|
+
|
|
13
|
+
Check out [hang](../hang) for a higher-level media library that uses this package.
|
|
14
|
+
|
|
15
|
+
> **Note:** moq-lite is a subset of moq-transport and is forwards compatible with it, so this client works with any moq-transport CDN (ex. [Cloudflare](https://moq.dev/blog/first-cdn/)). See the [compatibility docs](https://doc.moq.dev/concept/layer/moq-lite#compatibility) for details.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm add @moq/net
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @moq/net
|
|
23
|
+
bun add @moq/net
|
|
24
|
+
yarn add @moq/net
|
|
25
|
+
# etc
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Server-side usage
|
|
29
|
+
|
|
30
|
+
`@moq/net` works on both browsers and servers, however in JS/TS server environments (Node, Bun) WebTransport is not yet available, so `@moq/net` will default to WebSockets communication with the relay.
|
|
31
|
+
|
|
32
|
+
Bun and Node v21+ have `WebSockets` built in, but older versions of Node do not, so for older versions of Node you will need the WebSockets polyfill to use `@moq/net`
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import WebSocket from 'ws';
|
|
36
|
+
import * as Moq from '@moq/net';
|
|
37
|
+
// Polyfill WebSocket for MoQ
|
|
38
|
+
globalThis.WebSocket = WebSocket;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can optionally enable `WebTransport` and full HTTP3/Quic on server environments with the following (experimental) [polyfill](https://github.com/fails-components/webtransport)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @fails-components/webtransport
|
|
45
|
+
npm install @fails-components/webtransport-transport-http3-quiche
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Which you would load as follows
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { WebTransport, quicheLoaded } from '@fails-components/webtransport';
|
|
52
|
+
global.WebTransport = WebTransport;
|
|
53
|
+
import * as Moq from '@moq/net'
|
|
54
|
+
await quicheLoaded; //This is a promise, connect after it resolves
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
- **[Connection](examples/connection.ts)** - Connect to a MoQ relay server
|
|
60
|
+
- **[Publishing](examples/publish.ts)** - Publish data to a broadcast
|
|
61
|
+
- **[Subscribing](examples/subscribe.ts)** - Subscribe to and receive broadcast data
|
|
62
|
+
- **[Discovery](examples/discovery.ts)** - Discover broadcasts announced by the server
|
|
63
|
+
- **[Server side usage](https://github.com/sb2702/webcodecs-examples/tree/main/src/moq-server)** - Publish from browser to a server
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
Licensed under either:
|
|
68
|
+
|
|
69
|
+
- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
|
70
|
+
- MIT license ([LICENSE-MIT](../../LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
package/announced.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Signal } from "@norskvideo/moq-signals";
|
|
2
|
+
import * as Path from "./path.js";
|
|
3
|
+
/**
|
|
4
|
+
* The availability of a broadcast.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export interface AnnouncedEntry {
|
|
9
|
+
path: Path.Valid;
|
|
10
|
+
active: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Reactive backing state for an {@link Announced}: the pending queue plus a closed flag. */
|
|
13
|
+
export declare class AnnouncedState {
|
|
14
|
+
queue: Signal<AnnouncedEntry[]>;
|
|
15
|
+
closed: Signal<boolean | Error>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Handles writing announcements to the announcement queue.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
export declare class Announced {
|
|
23
|
+
/** Reactive backing state. */
|
|
24
|
+
state: AnnouncedState;
|
|
25
|
+
/** Path prefix this stream is scoped to. */
|
|
26
|
+
prefix: Path.Valid;
|
|
27
|
+
/** Resolves with the abort error (or undefined) once closed. */
|
|
28
|
+
readonly closed: Promise<Error | undefined>;
|
|
29
|
+
constructor(prefix?: Path.Valid);
|
|
30
|
+
/**
|
|
31
|
+
* Writes an announcement to the queue.
|
|
32
|
+
* @param announcement - The announcement to write
|
|
33
|
+
*/
|
|
34
|
+
append(announcement: AnnouncedEntry): void;
|
|
35
|
+
/**
|
|
36
|
+
* Closes the writer.
|
|
37
|
+
* @param abort - If provided, throw this exception instead of returning undefined.
|
|
38
|
+
*/
|
|
39
|
+
close(abort?: Error): void;
|
|
40
|
+
/**
|
|
41
|
+
* Returns the next announcement.
|
|
42
|
+
*/
|
|
43
|
+
next(): Promise<AnnouncedEntry | undefined>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=announced.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announced.d.ts","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,6FAA6F;AAC7F,qBAAa,cAAc;IAC1B,KAAK,2BAAoC;IACzC,MAAM,0BAAsC;CAC5C;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACrB,8BAA8B;IAC9B,KAAK,iBAAwB;IAE7B,4CAA4C;IAC5C,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;IAEnB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAE5C,YAAY,MAAM,aAAe,EAShC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,EAAE,cAAc,QAKlC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAKlB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAWhD;CACD"}
|
package/announced.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* @ts-self-types="./announced.d.ts" */
|
|
2
|
+
import { Signal } from "@norskvideo/moq-signals";
|
|
3
|
+
import * as Path from "./path.js";
|
|
4
|
+
/** Reactive backing state for an {@link Announced}: the pending queue plus a closed flag. */
|
|
5
|
+
export class AnnouncedState {
|
|
6
|
+
queue = new Signal([]);
|
|
7
|
+
closed = new Signal(false);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Handles writing announcements to the announcement queue.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export class Announced {
|
|
15
|
+
/** Reactive backing state. */
|
|
16
|
+
state = new AnnouncedState();
|
|
17
|
+
/** Path prefix this stream is scoped to. */
|
|
18
|
+
prefix;
|
|
19
|
+
/** Resolves with the abort error (or undefined) once closed. */
|
|
20
|
+
closed;
|
|
21
|
+
constructor(prefix = Path.empty()) {
|
|
22
|
+
this.prefix = prefix;
|
|
23
|
+
this.closed = new Promise((resolve) => {
|
|
24
|
+
const dispose = this.state.closed.subscribe((closed) => {
|
|
25
|
+
if (!closed)
|
|
26
|
+
return;
|
|
27
|
+
resolve(closed instanceof Error ? closed : undefined);
|
|
28
|
+
dispose();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Writes an announcement to the queue.
|
|
34
|
+
* @param announcement - The announcement to write
|
|
35
|
+
*/
|
|
36
|
+
append(announcement) {
|
|
37
|
+
if (this.state.closed.peek())
|
|
38
|
+
throw new Error("announced is closed");
|
|
39
|
+
this.state.queue.mutate((queue) => {
|
|
40
|
+
queue.push(announcement);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Closes the writer.
|
|
45
|
+
* @param abort - If provided, throw this exception instead of returning undefined.
|
|
46
|
+
*/
|
|
47
|
+
close(abort) {
|
|
48
|
+
this.state.closed.set(abort ?? true);
|
|
49
|
+
this.state.queue.mutate((queue) => {
|
|
50
|
+
queue.length = 0;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Returns the next announcement.
|
|
55
|
+
*/
|
|
56
|
+
async next() {
|
|
57
|
+
for (;;) {
|
|
58
|
+
const announce = this.state.queue.peek().shift();
|
|
59
|
+
if (announce)
|
|
60
|
+
return announce;
|
|
61
|
+
const closed = this.state.closed.peek();
|
|
62
|
+
if (closed instanceof Error)
|
|
63
|
+
throw closed;
|
|
64
|
+
if (closed)
|
|
65
|
+
return undefined;
|
|
66
|
+
await Signal.race(this.state.queue, this.state.closed);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=announced.js.map
|
package/announced.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"announced.js","sourceRoot":"","sources":["../src/announced.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAYlC,6FAA6F;AAC7F,MAAM,OAAO,cAAc;IAC1B,KAAK,GAAG,IAAI,MAAM,CAAmB,EAAE,CAAC,CAAC;IACzC,MAAM,GAAG,IAAI,MAAM,CAAkB,KAAK,CAAC,CAAC;CAC5C;AAED;;;;GAIG;AACH,MAAM,OAAO,SAAS;IACrB,8BAA8B;IAC9B,KAAK,GAAG,IAAI,cAAc,EAAE,CAAC;IAE7B,4CAA4C;IAC5C,MAAM,CAAa;IAEnB,gEAAgE;IACvD,MAAM,CAA6B;IAE5C,YAAY,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE;gBACtD,IAAI,CAAC,MAAM;oBAAE,OAAO;gBACpB,OAAO,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBACtD,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAA4B;QAClC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACrE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAa;QAClB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACT,SAAS,CAAC;YACT,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YACjD,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,MAAM,YAAY,KAAK;gBAAE,MAAM,MAAM,CAAC;YAC1C,IAAI,MAAM;gBAAE,OAAO,SAAS,CAAC;YAE7B,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC;IACF,CAAC;CACD","sourcesContent":["import { Signal } from \"@moq/signals\";\nimport * as Path from \"./path.js\";\n\n/**\n * The availability of a broadcast.\n *\n * @public\n */\nexport interface AnnouncedEntry {\n\tpath: Path.Valid;\n\tactive: boolean;\n}\n\n/** Reactive backing state for an {@link Announced}: the pending queue plus a closed flag. */\nexport class AnnouncedState {\n\tqueue = new Signal<AnnouncedEntry[]>([]);\n\tclosed = new Signal<boolean | Error>(false);\n}\n\n/**\n * Handles writing announcements to the announcement queue.\n *\n * @public\n */\nexport class Announced {\n\t/** Reactive backing state. */\n\tstate = new AnnouncedState();\n\n\t/** Path prefix this stream is scoped to. */\n\tprefix: Path.Valid;\n\n\t/** Resolves with the abort error (or undefined) once closed. */\n\treadonly closed: Promise<Error | undefined>;\n\n\tconstructor(prefix = Path.empty()) {\n\t\tthis.prefix = prefix;\n\t\tthis.closed = new Promise((resolve) => {\n\t\t\tconst dispose = this.state.closed.subscribe((closed) => {\n\t\t\t\tif (!closed) return;\n\t\t\t\tresolve(closed instanceof Error ? closed : undefined);\n\t\t\t\tdispose();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Writes an announcement to the queue.\n\t * @param announcement - The announcement to write\n\t */\n\tappend(announcement: AnnouncedEntry) {\n\t\tif (this.state.closed.peek()) throw new Error(\"announced is closed\");\n\t\tthis.state.queue.mutate((queue) => {\n\t\t\tqueue.push(announcement);\n\t\t});\n\t}\n\n\t/**\n\t * Closes the writer.\n\t * @param abort - If provided, throw this exception instead of returning undefined.\n\t */\n\tclose(abort?: Error) {\n\t\tthis.state.closed.set(abort ?? true);\n\t\tthis.state.queue.mutate((queue) => {\n\t\t\tqueue.length = 0;\n\t\t});\n\t}\n\n\t/**\n\t * Returns the next announcement.\n\t */\n\tasync next(): Promise<AnnouncedEntry | undefined> {\n\t\tfor (;;) {\n\t\t\tconst announce = this.state.queue.peek().shift();\n\t\t\tif (announce) return announce;\n\n\t\t\tconst closed = this.state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed) return undefined;\n\n\t\t\tawait Signal.race(this.state.queue, this.state.closed);\n\t\t}\n\t}\n}\n"]}
|
package/bandwidth.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Signal } from "@norskvideo/moq-signals";
|
|
2
|
+
/**
|
|
3
|
+
* A bandwidth estimate in bits per second, or undefined if unknown.
|
|
4
|
+
*
|
|
5
|
+
* This is a Signal that can be read synchronously via `peek()`,
|
|
6
|
+
* observed reactively via `effect.get()`, or updated via `set()`.
|
|
7
|
+
*/
|
|
8
|
+
export type Bandwidth = Signal<number | undefined>;
|
|
9
|
+
/** Create a new bandwidth signal. */
|
|
10
|
+
export declare function createBandwidth(): Bandwidth;
|
|
11
|
+
//# sourceMappingURL=bandwidth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth.d.ts","sourceRoot":"","sources":["../src/bandwidth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;AAEnD,qCAAqC;AACrC,wBAAgB,eAAe,IAAI,SAAS,CAE3C"}
|
package/bandwidth.js
ADDED
package/bandwidth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bandwidth.js","sourceRoot":"","sources":["../src/bandwidth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAUtC,qCAAqC;AACrC,MAAM,UAAU,eAAe;IAC9B,OAAO,IAAI,MAAM,CAAqB,SAAS,CAAC,CAAC;AAClD,CAAC","sourcesContent":["import { Signal } from \"@moq/signals\";\n\n/**\n * A bandwidth estimate in bits per second, or undefined if unknown.\n *\n * This is a Signal that can be read synchronously via `peek()`,\n * observed reactively via `effect.get()`, or updated via `set()`.\n */\nexport type Bandwidth = Signal<number | undefined>;\n\n/** Create a new bandwidth signal. */\nexport function createBandwidth(): Bandwidth {\n\treturn new Signal<number | undefined>(undefined);\n}\n"]}
|
package/broadcast.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Signal } from "@norskvideo/moq-signals";
|
|
2
|
+
import { Track } from "./track";
|
|
3
|
+
/** A track a subscriber asked for, along with its requested delivery priority. */
|
|
4
|
+
export interface TrackRequest {
|
|
5
|
+
track: Track;
|
|
6
|
+
priority: number;
|
|
7
|
+
}
|
|
8
|
+
/** Reactive backing state for a {@link Broadcast}: requested tracks plus a closed flag. */
|
|
9
|
+
export declare class BroadcastState {
|
|
10
|
+
requested: Signal<TrackRequest[]>;
|
|
11
|
+
closed: Signal<boolean | Error>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Handles writing and managing tracks in a broadcast.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export declare class Broadcast {
|
|
19
|
+
/** Reactive backing state. */
|
|
20
|
+
state: BroadcastState;
|
|
21
|
+
/** Resolves with the abort error (or undefined) once closed. */
|
|
22
|
+
readonly closed: Promise<Error | undefined>;
|
|
23
|
+
constructor();
|
|
24
|
+
/**
|
|
25
|
+
* A track requested over the network.
|
|
26
|
+
*/
|
|
27
|
+
requested(): Promise<TrackRequest | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Populates the provided track over the network.
|
|
30
|
+
*/
|
|
31
|
+
subscribe(name: string, priority: number): Track;
|
|
32
|
+
/**
|
|
33
|
+
* Closes the writer and all associated tracks.
|
|
34
|
+
*
|
|
35
|
+
* @param abort - If provided, throw this exception instead of returning undefined.
|
|
36
|
+
*/
|
|
37
|
+
close(abort?: Error): void;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=broadcast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,2FAA2F;AAC3F,qBAAa,cAAc;IAC1B,SAAS,yBAAkC;IAC3C,MAAM,0BAAsC;CAC5C;AAED;;;;GAIG;AACH,qBAAa,SAAS;IACrB,8BAA8B;IAC9B,KAAK,iBAAwB;IAE7B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAE5C,cAQC;IAED;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAYnD;IAED;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAa/C;IAED;;;;OAIG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,QAQlB;CACD"}
|
package/broadcast.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* @ts-self-types="./broadcast.d.ts" */
|
|
2
|
+
import { Signal } from "@norskvideo/moq-signals";
|
|
3
|
+
import { Track } from "./track.js";
|
|
4
|
+
/** Reactive backing state for a {@link Broadcast}: requested tracks plus a closed flag. */
|
|
5
|
+
export class BroadcastState {
|
|
6
|
+
requested = new Signal([]);
|
|
7
|
+
closed = new Signal(false);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Handles writing and managing tracks in a broadcast.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
export class Broadcast {
|
|
15
|
+
/** Reactive backing state. */
|
|
16
|
+
state = new BroadcastState();
|
|
17
|
+
/** Resolves with the abort error (or undefined) once closed. */
|
|
18
|
+
closed;
|
|
19
|
+
constructor() {
|
|
20
|
+
this.closed = new Promise((resolve) => {
|
|
21
|
+
const dispose = this.state.closed.subscribe((closed) => {
|
|
22
|
+
if (!closed)
|
|
23
|
+
return;
|
|
24
|
+
resolve(closed instanceof Error ? closed : undefined);
|
|
25
|
+
dispose();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A track requested over the network.
|
|
31
|
+
*/
|
|
32
|
+
async requested() {
|
|
33
|
+
for (;;) {
|
|
34
|
+
// We use pop instead of shift because it's slightly more efficient.
|
|
35
|
+
const track = this.state.requested.peek().pop();
|
|
36
|
+
if (track)
|
|
37
|
+
return track;
|
|
38
|
+
const closed = this.state.closed.peek();
|
|
39
|
+
if (closed instanceof Error)
|
|
40
|
+
throw closed;
|
|
41
|
+
if (closed)
|
|
42
|
+
return undefined;
|
|
43
|
+
await Signal.race(this.state.requested, this.state.closed);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Populates the provided track over the network.
|
|
48
|
+
*/
|
|
49
|
+
subscribe(name, priority) {
|
|
50
|
+
const track = new Track(name);
|
|
51
|
+
if (this.state.closed.peek()) {
|
|
52
|
+
throw new Error(`broadcast is closed: ${this.state.closed.peek()}`);
|
|
53
|
+
}
|
|
54
|
+
this.state.requested.mutate((requested) => {
|
|
55
|
+
requested.push({ track, priority });
|
|
56
|
+
// Sort the tracks by priority in ascending order (we will pop)
|
|
57
|
+
requested.sort((a, b) => a.priority - b.priority);
|
|
58
|
+
});
|
|
59
|
+
return track;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Closes the writer and all associated tracks.
|
|
63
|
+
*
|
|
64
|
+
* @param abort - If provided, throw this exception instead of returning undefined.
|
|
65
|
+
*/
|
|
66
|
+
close(abort) {
|
|
67
|
+
this.state.closed.set(abort ?? true);
|
|
68
|
+
for (const { track } of this.state.requested.peek()) {
|
|
69
|
+
track.close(abort);
|
|
70
|
+
}
|
|
71
|
+
this.state.requested.mutate((requested) => {
|
|
72
|
+
requested.length = 0;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=broadcast.js.map
|
package/broadcast.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../src/broadcast.ts"],"names":[],"mappings":"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","sourcesContent":["import { Signal } from \"@moq/signals\";\nimport { Track } from \"./track.ts\";\n\n/** A track a subscriber asked for, along with its requested delivery priority. */\nexport interface TrackRequest {\n\ttrack: Track;\n\tpriority: number;\n}\n\n/** Reactive backing state for a {@link Broadcast}: requested tracks plus a closed flag. */\nexport class BroadcastState {\n\trequested = new Signal<TrackRequest[]>([]);\n\tclosed = new Signal<boolean | Error>(false);\n}\n\n/**\n * Handles writing and managing tracks in a broadcast.\n *\n * @public\n */\nexport class Broadcast {\n\t/** Reactive backing state. */\n\tstate = new BroadcastState();\n\n\t/** Resolves with the abort error (or undefined) once closed. */\n\treadonly closed: Promise<Error | undefined>;\n\n\tconstructor() {\n\t\tthis.closed = new Promise((resolve) => {\n\t\t\tconst dispose = this.state.closed.subscribe((closed) => {\n\t\t\t\tif (!closed) return;\n\t\t\t\tresolve(closed instanceof Error ? closed : undefined);\n\t\t\t\tdispose();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * A track requested over the network.\n\t */\n\tasync requested(): Promise<TrackRequest | undefined> {\n\t\tfor (;;) {\n\t\t\t// We use pop instead of shift because it's slightly more efficient.\n\t\t\tconst track = this.state.requested.peek().pop();\n\t\t\tif (track) return track;\n\n\t\t\tconst closed = this.state.closed.peek();\n\t\t\tif (closed instanceof Error) throw closed;\n\t\t\tif (closed) 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 * Populates the provided track over the network.\n\t */\n\tsubscribe(name: string, priority: number): Track {\n\t\tconst track = new Track(name);\n\n\t\tif (this.state.closed.peek()) {\n\t\t\tthrow new Error(`broadcast is closed: ${this.state.closed.peek()}`);\n\t\t}\n\t\tthis.state.requested.mutate((requested) => {\n\t\t\trequested.push({ track, priority });\n\t\t\t// Sort the tracks by priority in ascending order (we will pop)\n\t\t\trequested.sort((a, b) => a.priority - b.priority);\n\t\t});\n\n\t\treturn track;\n\t}\n\n\t/**\n\t * Closes the writer and all associated tracks.\n\t *\n\t * @param abort - If provided, throw this exception instead of returning undefined.\n\t */\n\tclose(abort?: Error) {\n\t\tthis.state.closed.set(abort ?? true);\n\t\tfor (const { track } of this.state.requested.peek()) {\n\t\t\ttrack.close(abort);\n\t\t}\n\t\tthis.state.requested.mutate((requested) => {\n\t\t\trequested.length = 0;\n\t\t});\n\t}\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Established } from "./established";
|
|
2
|
+
/** Options for {@link accept}. */
|
|
3
|
+
export interface AcceptProps {
|
|
4
|
+
version?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Server-side handshake: accepts a transport and performs the server half of the SETUP exchange.
|
|
8
|
+
*
|
|
9
|
+
* @param transport - The WebTransport session to accept
|
|
10
|
+
* @param url - The URL of the connection
|
|
11
|
+
* @param props - Optional configuration
|
|
12
|
+
* @returns A promise that resolves to a Connection instance
|
|
13
|
+
*/
|
|
14
|
+
export declare function accept(transport: WebTransport, url: URL, props?: AcceptProps): Promise<Established>;
|
|
15
|
+
//# sourceMappingURL=accept.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accept.d.ts","sourceRoot":"","sources":["../../src/connection/accept.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAE3B,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAuBzG"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* @ts-self-types="./accept.d.ts" */
|
|
2
|
+
import * as Ietf from "../ietf/index.js";
|
|
3
|
+
import * as Lite from "../lite/index.js";
|
|
4
|
+
import { Stream } from "../stream.js";
|
|
5
|
+
import { exchangeSetup } from "./handshake.js";
|
|
6
|
+
/**
|
|
7
|
+
* Server-side handshake: accepts a transport and performs the server half of the SETUP exchange.
|
|
8
|
+
*
|
|
9
|
+
* @param transport - The WebTransport session to accept
|
|
10
|
+
* @param url - The URL of the connection
|
|
11
|
+
* @param props - Optional configuration
|
|
12
|
+
* @returns A promise that resolves to a Connection instance
|
|
13
|
+
*/
|
|
14
|
+
export async function accept(transport, url, props) {
|
|
15
|
+
// @ts-expect-error - TODO: add protocol to WebTransport
|
|
16
|
+
const protocol = transport.protocol;
|
|
17
|
+
if (protocol === Ietf.ALPN.DRAFT_19) {
|
|
18
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_19);
|
|
19
|
+
}
|
|
20
|
+
else if (protocol === Ietf.ALPN.DRAFT_18) {
|
|
21
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_18);
|
|
22
|
+
}
|
|
23
|
+
else if (protocol === Ietf.ALPN.DRAFT_17) {
|
|
24
|
+
return acceptAlpn(transport, url, Ietf.Version.DRAFT_17);
|
|
25
|
+
}
|
|
26
|
+
else if (protocol === Ietf.ALPN.DRAFT_16) {
|
|
27
|
+
return acceptSetup(transport, url, Ietf.Version.DRAFT_16);
|
|
28
|
+
}
|
|
29
|
+
else if (protocol === Ietf.ALPN.DRAFT_15) {
|
|
30
|
+
return acceptSetup(transport, url, Ietf.Version.DRAFT_15);
|
|
31
|
+
}
|
|
32
|
+
else if (protocol === Lite.ALPN_04) {
|
|
33
|
+
return new Lite.Connection(url, transport, Lite.Version.DRAFT_04, undefined);
|
|
34
|
+
}
|
|
35
|
+
else if (protocol === Lite.ALPN_03) {
|
|
36
|
+
return new Lite.Connection(url, transport, Lite.Version.DRAFT_03, undefined);
|
|
37
|
+
}
|
|
38
|
+
else if (protocol === Lite.ALPN || protocol === "" || protocol === undefined) {
|
|
39
|
+
return acceptNegotiated(transport, url, props);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw new Error(`unsupported WebTransport protocol: ${protocol}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Draft-17+ accept: ALPN already pinned the version. SETUP is exchanged over
|
|
47
|
+
* a pair of uni streams using stream type 0x2F00.
|
|
48
|
+
*/
|
|
49
|
+
async function acceptAlpn(transport, url, version) {
|
|
50
|
+
const controlStream = await exchangeSetup(transport, version, "moq-lite-js");
|
|
51
|
+
return new Ietf.Connection({
|
|
52
|
+
url,
|
|
53
|
+
quic: transport,
|
|
54
|
+
control: controlStream,
|
|
55
|
+
// v17+ uses NativeSession which manages its own request IDs; maxRequestId is unused.
|
|
56
|
+
maxRequestId: 0n,
|
|
57
|
+
version,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Legacy accept (draft-15/16): ALPN pinned the version, but the SETUP message
|
|
62
|
+
* is still exchanged over a bidi stream wrapped in the moq-lite compat envelope.
|
|
63
|
+
*/
|
|
64
|
+
async function acceptSetup(transport, url, version) {
|
|
65
|
+
// Accept bidi, read ClientSetup, write ServerSetup
|
|
66
|
+
const stream = await Stream.accept(transport);
|
|
67
|
+
if (!stream)
|
|
68
|
+
throw new Error("no incoming bidi stream for SETUP");
|
|
69
|
+
const clientCompat = await stream.reader.u53();
|
|
70
|
+
if (clientCompat !== Lite.StreamId.ClientCompat) {
|
|
71
|
+
throw new Error(`unexpected client message type: 0x${clientCompat.toString(16)}`);
|
|
72
|
+
}
|
|
73
|
+
await Ietf.ClientSetup.decode(stream.reader, version);
|
|
74
|
+
await stream.writer.u53(Lite.StreamId.ServerCompat);
|
|
75
|
+
const encoder = new TextEncoder();
|
|
76
|
+
const params = new Ietf.SetupOptions();
|
|
77
|
+
params.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);
|
|
78
|
+
params.setBytes(Ietf.SetupOption.Implementation, encoder.encode("moq-lite-js"));
|
|
79
|
+
const server = new Ietf.ServerSetup({ version, parameters: params });
|
|
80
|
+
await server.encode(stream.writer, version);
|
|
81
|
+
const maxRequestId = 42069n;
|
|
82
|
+
return new Ietf.Connection({
|
|
83
|
+
url,
|
|
84
|
+
quic: transport,
|
|
85
|
+
control: stream,
|
|
86
|
+
maxRequestId,
|
|
87
|
+
version,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function acceptNegotiated(transport, url, props) {
|
|
91
|
+
const setupVersion = Ietf.Version.DRAFT_14;
|
|
92
|
+
const stream = await Stream.accept(transport);
|
|
93
|
+
if (!stream)
|
|
94
|
+
throw new Error("no incoming bidi stream for SETUP");
|
|
95
|
+
const clientCompat = await stream.reader.u53();
|
|
96
|
+
if (clientCompat !== Lite.StreamId.ClientCompat) {
|
|
97
|
+
throw new Error(`unexpected client message type: 0x${clientCompat.toString(16)}`);
|
|
98
|
+
}
|
|
99
|
+
const client = await Ietf.ClientSetup.decode(stream.reader, setupVersion);
|
|
100
|
+
// Pick the requested version, or first matching version from client's list
|
|
101
|
+
const allVersions = [...Object.values(Lite.Version), ...Object.values(Ietf.Version)];
|
|
102
|
+
let selectedVersion;
|
|
103
|
+
if (props?.version !== undefined) {
|
|
104
|
+
selectedVersion = props.version;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const match = client.versions.find((v) => allVersions.includes(v));
|
|
108
|
+
if (match === undefined) {
|
|
109
|
+
throw new Error(`no common version found; client offered: ${client.versions.map((v) => v.toString(16)).join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
selectedVersion = match;
|
|
112
|
+
}
|
|
113
|
+
await stream.writer.u53(Lite.StreamId.ServerCompat);
|
|
114
|
+
const encoder = new TextEncoder();
|
|
115
|
+
const params = new Ietf.SetupOptions();
|
|
116
|
+
params.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);
|
|
117
|
+
params.setBytes(Ietf.SetupOption.Implementation, encoder.encode("moq-lite-js"));
|
|
118
|
+
const server = new Ietf.ServerSetup({ version: selectedVersion, parameters: params });
|
|
119
|
+
await server.encode(stream.writer, setupVersion);
|
|
120
|
+
if (Object.values(Lite.Version).includes(selectedVersion)) {
|
|
121
|
+
return new Lite.Connection(url, transport, selectedVersion, stream);
|
|
122
|
+
}
|
|
123
|
+
else if (Object.values(Ietf.Version).includes(selectedVersion)) {
|
|
124
|
+
const maxRequestId = client.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;
|
|
125
|
+
return new Ietf.Connection({
|
|
126
|
+
url,
|
|
127
|
+
quic: transport,
|
|
128
|
+
control: stream,
|
|
129
|
+
maxRequestId,
|
|
130
|
+
version: selectedVersion,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
throw new Error(`unsupported version: ${selectedVersion.toString(16)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=accept.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accept.js","sourceRoot":"","sources":["../../src/connection/accept.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Ietf from \"../ietf/index.ts\";\nimport * as Lite from \"../lite/index.ts\";\nimport { Stream } from \"../stream.ts\";\nimport type { Established } from \"./established.ts\";\nimport { exchangeSetup } from \"./handshake.ts\";\n\n/** Options for {@link accept}. */\nexport interface AcceptProps {\n\t// Version to select during SETUP negotiation (for non-ALPN paths).\n\tversion?: number;\n}\n\n/**\n * Server-side handshake: accepts a transport and performs the server half of the SETUP exchange.\n *\n * @param transport - The WebTransport session to accept\n * @param url - The URL of the connection\n * @param props - Optional configuration\n * @returns A promise that resolves to a Connection instance\n */\nexport async function accept(transport: WebTransport, url: URL, props?: AcceptProps): Promise<Established> {\n\t// @ts-expect-error - TODO: add protocol to WebTransport\n\tconst protocol: string | undefined = transport.protocol;\n\n\tif (protocol === Ietf.ALPN.DRAFT_19) {\n\t\treturn acceptAlpn(transport, url, Ietf.Version.DRAFT_19);\n\t} else if (protocol === Ietf.ALPN.DRAFT_18) {\n\t\treturn acceptAlpn(transport, url, Ietf.Version.DRAFT_18);\n\t} else if (protocol === Ietf.ALPN.DRAFT_17) {\n\t\treturn acceptAlpn(transport, url, Ietf.Version.DRAFT_17);\n\t} else if (protocol === Ietf.ALPN.DRAFT_16) {\n\t\treturn acceptSetup(transport, url, Ietf.Version.DRAFT_16);\n\t} else if (protocol === Ietf.ALPN.DRAFT_15) {\n\t\treturn acceptSetup(transport, url, Ietf.Version.DRAFT_15);\n\t} else if (protocol === Lite.ALPN_04) {\n\t\treturn new Lite.Connection(url, transport, Lite.Version.DRAFT_04, undefined);\n\t} else if (protocol === Lite.ALPN_03) {\n\t\treturn new Lite.Connection(url, transport, Lite.Version.DRAFT_03, undefined);\n\t} else if (protocol === Lite.ALPN || protocol === \"\" || protocol === undefined) {\n\t\treturn acceptNegotiated(transport, url, props);\n\t} else {\n\t\tthrow new Error(`unsupported WebTransport protocol: ${protocol}`);\n\t}\n}\n\n/**\n * Draft-17+ accept: ALPN already pinned the version. SETUP is exchanged over\n * a pair of uni streams using stream type 0x2F00.\n */\nasync function acceptAlpn(transport: WebTransport, url: URL, version: Ietf.IetfVersion): Promise<Established> {\n\tconst controlStream = await exchangeSetup(transport, version, \"moq-lite-js\");\n\n\treturn new Ietf.Connection({\n\t\turl,\n\t\tquic: transport,\n\t\tcontrol: controlStream,\n\t\t// v17+ uses NativeSession which manages its own request IDs; maxRequestId is unused.\n\t\tmaxRequestId: 0n,\n\t\tversion,\n\t});\n}\n\n/**\n * Legacy accept (draft-15/16): ALPN pinned the version, but the SETUP message\n * is still exchanged over a bidi stream wrapped in the moq-lite compat envelope.\n */\nasync function acceptSetup(transport: WebTransport, url: URL, version: Ietf.IetfVersion): Promise<Established> {\n\t// Accept bidi, read ClientSetup, write ServerSetup\n\tconst stream = await Stream.accept(transport);\n\tif (!stream) throw new Error(\"no incoming bidi stream for SETUP\");\n\n\tconst clientCompat = await stream.reader.u53();\n\tif (clientCompat !== Lite.StreamId.ClientCompat) {\n\t\tthrow new Error(`unexpected client message type: 0x${clientCompat.toString(16)}`);\n\t}\n\n\tawait Ietf.ClientSetup.decode(stream.reader, version);\n\n\tawait stream.writer.u53(Lite.StreamId.ServerCompat);\n\n\tconst encoder = new TextEncoder();\n\tconst params = new Ietf.SetupOptions();\n\tparams.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);\n\tparams.setBytes(Ietf.SetupOption.Implementation, encoder.encode(\"moq-lite-js\"));\n\n\tconst server = new Ietf.ServerSetup({ version, parameters: params });\n\tawait server.encode(stream.writer, version);\n\n\tconst maxRequestId = 42069n;\n\n\treturn new Ietf.Connection({\n\t\turl,\n\t\tquic: transport,\n\t\tcontrol: stream,\n\t\tmaxRequestId,\n\t\tversion,\n\t});\n}\n\nasync function acceptNegotiated(transport: WebTransport, url: URL, props?: AcceptProps): Promise<Established> {\n\tconst setupVersion = Ietf.Version.DRAFT_14;\n\n\tconst stream = await Stream.accept(transport);\n\tif (!stream) throw new Error(\"no incoming bidi stream for SETUP\");\n\n\tconst clientCompat = await stream.reader.u53();\n\tif (clientCompat !== Lite.StreamId.ClientCompat) {\n\t\tthrow new Error(`unexpected client message type: 0x${clientCompat.toString(16)}`);\n\t}\n\n\tconst client = await Ietf.ClientSetup.decode(stream.reader, setupVersion);\n\n\t// Pick the requested version, or first matching version from client's list\n\tconst allVersions = [...Object.values(Lite.Version), ...Object.values(Ietf.Version)] as number[];\n\tlet selectedVersion: number;\n\tif (props?.version !== undefined) {\n\t\tselectedVersion = props.version;\n\t} else {\n\t\tconst match = client.versions.find((v) => allVersions.includes(v));\n\t\tif (match === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`no common version found; client offered: ${client.versions.map((v) => v.toString(16)).join(\", \")}`,\n\t\t\t);\n\t\t}\n\t\tselectedVersion = match;\n\t}\n\n\tawait stream.writer.u53(Lite.StreamId.ServerCompat);\n\n\tconst encoder = new TextEncoder();\n\tconst params = new Ietf.SetupOptions();\n\tparams.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);\n\tparams.setBytes(Ietf.SetupOption.Implementation, encoder.encode(\"moq-lite-js\"));\n\n\tconst server = new Ietf.ServerSetup({ version: selectedVersion, parameters: params });\n\tawait server.encode(stream.writer, setupVersion);\n\n\tif (Object.values(Lite.Version).includes(selectedVersion as Lite.Version)) {\n\t\treturn new Lite.Connection(url, transport, selectedVersion as Lite.Version, stream);\n\t} else if (Object.values(Ietf.Version).includes(selectedVersion as Ietf.Version)) {\n\t\tconst maxRequestId = client.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;\n\t\treturn new Ietf.Connection({\n\t\t\turl,\n\t\t\tquic: transport,\n\t\t\tcontrol: stream,\n\t\t\tmaxRequestId,\n\t\t\tversion: selectedVersion as Ietf.IetfVersion,\n\t\t});\n\t} else {\n\t\tthrow new Error(`unsupported version: ${selectedVersion.toString(16)}`);\n\t}\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert.test.d.ts","sourceRoot":"","sources":["../../src/connection/cert.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* @ts-self-types="./cert.test.d.ts" */
|
|
2
|
+
import { expect, test } from "bun:test";
|
|
3
|
+
import * as Hex from "../util/hex.js";
|
|
4
|
+
import { certificateHash } from "./connect.js";
|
|
5
|
+
// SHA-256("abc"), a well-known test vector. We feed "abc" as the cert bytes so
|
|
6
|
+
// the expected digest is independent of our own implementation.
|
|
7
|
+
const ABC = new TextEncoder().encode("abc");
|
|
8
|
+
const ABC_SHA256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad";
|
|
9
|
+
test("certificateHash hashes raw DER bytes", async () => {
|
|
10
|
+
const hash = await certificateHash(ABC);
|
|
11
|
+
expect(Hex.fromBytes(hash)).toBe(ABC_SHA256);
|
|
12
|
+
});
|
|
13
|
+
test("certificateHash decodes PEM armor before hashing", async () => {
|
|
14
|
+
// base64("abc") === "YWJj"
|
|
15
|
+
const pem = "-----BEGIN CERTIFICATE-----\nYWJj\n-----END CERTIFICATE-----\n";
|
|
16
|
+
const hash = await certificateHash(pem);
|
|
17
|
+
expect(Hex.fromBytes(hash)).toBe(ABC_SHA256);
|
|
18
|
+
});
|
|
19
|
+
test("certificateHash rejects a PEM string without armor", async () => {
|
|
20
|
+
// Valid base64, but no -----BEGIN CERTIFICATE----- wrapper.
|
|
21
|
+
await expect(certificateHash("YWJj")).rejects.toThrow(/armor/);
|
|
22
|
+
});
|
|
23
|
+
test("hex round-trips through fromBytes/toBytes", () => {
|
|
24
|
+
expect(Hex.fromBytes(Hex.toBytes(ABC_SHA256))).toBe(ABC_SHA256);
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=cert.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cert.test.js","sourceRoot":"","sources":["../../src/connection/cert.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,+EAA+E;AAC/E,gEAAgE;AAChE,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5C,MAAM,UAAU,GAAG,kEAAkE,CAAC;AAEtF,IAAI,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;IACvD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;IACnE,2BAA2B;IAC3B,MAAM,GAAG,GAAG,gEAAgE,CAAC;IAC7E,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;IACrE,4DAA4D;IAC5D,MAAM,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACtD,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC","sourcesContent":["import { expect, test } from \"bun:test\";\nimport * as Hex from \"../util/hex.ts\";\nimport { certificateHash } from \"./connect.ts\";\n\n// SHA-256(\"abc\"), a well-known test vector. We feed \"abc\" as the cert bytes so\n// the expected digest is independent of our own implementation.\nconst ABC = new TextEncoder().encode(\"abc\");\nconst ABC_SHA256 = \"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad\";\n\ntest(\"certificateHash hashes raw DER bytes\", async () => {\n\tconst hash = await certificateHash(ABC);\n\texpect(Hex.fromBytes(hash)).toBe(ABC_SHA256);\n});\n\ntest(\"certificateHash decodes PEM armor before hashing\", async () => {\n\t// base64(\"abc\") === \"YWJj\"\n\tconst pem = \"-----BEGIN CERTIFICATE-----\\nYWJj\\n-----END CERTIFICATE-----\\n\";\n\tconst hash = await certificateHash(pem);\n\texpect(Hex.fromBytes(hash)).toBe(ABC_SHA256);\n});\n\ntest(\"certificateHash rejects a PEM string without armor\", async () => {\n\t// Valid base64, but no -----BEGIN CERTIFICATE----- wrapper.\n\tawait expect(certificateHash(\"YWJj\")).rejects.toThrow(/armor/);\n});\n\ntest(\"hex round-trips through fromBytes/toBytes\", () => {\n\texpect(Hex.fromBytes(Hex.toBytes(ABC_SHA256))).toBe(ABC_SHA256);\n});\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Established } from "./established";
|
|
2
|
+
/** Tuning for the WebSocket fallback used when WebTransport is unavailable or loses the connect race. */
|
|
3
|
+
export interface WebSocketOptions {
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
url?: URL;
|
|
6
|
+
delay?: DOMHighResTimeStamp;
|
|
7
|
+
}
|
|
8
|
+
/** A server certificate hash used to pin a self-signed server. `value` accepts raw bytes or a hex string. */
|
|
9
|
+
export interface CertificateHash {
|
|
10
|
+
algorithm?: "sha-256";
|
|
11
|
+
value: BufferSource | string;
|
|
12
|
+
}
|
|
13
|
+
/** WebTransport options extended with friendlier certificate pinning (hex hashes or a raw certificate). */
|
|
14
|
+
export interface WebTransportProps extends Omit<WebTransportOptions, "serverCertificateHashes"> {
|
|
15
|
+
serverCertificateHashes?: CertificateHash[];
|
|
16
|
+
serverCertificate?: string | BufferSource;
|
|
17
|
+
}
|
|
18
|
+
/** Options for {@link connect}. */
|
|
19
|
+
export interface ConnectProps {
|
|
20
|
+
webtransport?: WebTransportProps;
|
|
21
|
+
websocket?: WebSocketOptions;
|
|
22
|
+
transport?: WebTransport;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Establishes a connection to a MOQ server.
|
|
26
|
+
*
|
|
27
|
+
* @param url - The URL of the server to connect to
|
|
28
|
+
* @returns A promise that resolves to a Connection instance
|
|
29
|
+
*/
|
|
30
|
+
export declare function connect(url: URL, props?: ConnectProps): Promise<Established>;
|
|
31
|
+
/**
|
|
32
|
+
* Compute the SHA-256 hash of a certificate, the value `serverCertificateHashes`
|
|
33
|
+
* pins. Accepts a PEM string or raw DER bytes. Matches the hex fingerprints a moq
|
|
34
|
+
* server reports, so `Hex.fromBytes(await certificateHash(pem))` round-trips.
|
|
35
|
+
*/
|
|
36
|
+
export declare function certificateHash(cert: string | BufferSource): Promise<Uint8Array<ArrayBuffer>>;
|
|
37
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/connection/connect.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAMpD,yGAAyG;AACzG,MAAM,WAAW,gBAAgB;IAEhC,OAAO,CAAC,EAAE,OAAO,CAAC;IAIlB,GAAG,CAAC,EAAE,GAAG,CAAC;IAIV,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC5B;AAKD,6GAA6G;AAC7G,MAAM,WAAW,eAAe;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC;CAC7B;AAGD,2GAA2G;AAC3G,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;IAG9F,uBAAuB,CAAC,EAAE,eAAe,EAAE,CAAC;IAK5C,iBAAiB,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;CAC1C;AAED,mCAAmC;AACnC,MAAM,WAAW,YAAY;IAE5B,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAGjC,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAI7B,SAAS,CAAC,EAAE,YAAY,CAAC;CACzB;AAUD;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAyIlF;AAgHD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAInG"}
|