@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.
Files changed (274) hide show
  1. package/README.md +70 -0
  2. package/announced.d.ts +45 -0
  3. package/announced.d.ts.map +1 -0
  4. package/announced.js +70 -0
  5. package/announced.js.map +1 -0
  6. package/bandwidth.d.ts +11 -0
  7. package/bandwidth.d.ts.map +1 -0
  8. package/bandwidth.js +7 -0
  9. package/bandwidth.js.map +1 -0
  10. package/broadcast.d.ts +39 -0
  11. package/broadcast.d.ts.map +1 -0
  12. package/broadcast.js +76 -0
  13. package/broadcast.js.map +1 -0
  14. package/connection/accept.d.ts +15 -0
  15. package/connection/accept.d.ts.map +1 -0
  16. package/connection/accept.js +137 -0
  17. package/connection/accept.js.map +1 -0
  18. package/connection/cert.test.d.ts +2 -0
  19. package/connection/cert.test.d.ts.map +1 -0
  20. package/connection/cert.test.js +26 -0
  21. package/connection/cert.test.js.map +1 -0
  22. package/connection/connect.d.ts +37 -0
  23. package/connection/connect.d.ts.map +1 -0
  24. package/connection/connect.js +348 -0
  25. package/connection/connect.js.map +1 -0
  26. package/connection/established.d.ts +30 -0
  27. package/connection/established.d.ts.map +1 -0
  28. package/connection/established.js +3 -0
  29. package/connection/established.js.map +1 -0
  30. package/connection/handshake.d.ts +10 -0
  31. package/connection/handshake.d.ts.map +1 -0
  32. package/connection/handshake.js +48 -0
  33. package/connection/handshake.js.map +1 -0
  34. package/connection/index.d.ts +5 -0
  35. package/connection/index.d.ts.map +1 -0
  36. package/connection/index.js +6 -0
  37. package/connection/index.js.map +1 -0
  38. package/connection/reload.d.ts +47 -0
  39. package/connection/reload.d.ts.map +1 -0
  40. package/connection/reload.js +137 -0
  41. package/connection/reload.js.map +1 -0
  42. package/group.d.ts +66 -0
  43. package/group.d.ts.map +1 -0
  44. package/group.js +135 -0
  45. package/group.js.map +1 -0
  46. package/group.test.d.ts +2 -0
  47. package/group.test.d.ts.map +1 -0
  48. package/group.test.js +66 -0
  49. package/group.test.js.map +1 -0
  50. package/ietf/adapter.d.ts +65 -0
  51. package/ietf/adapter.d.ts.map +1 -0
  52. package/ietf/adapter.js +626 -0
  53. package/ietf/adapter.js.map +1 -0
  54. package/ietf/connection.d.ts +65 -0
  55. package/ietf/connection.d.ts.map +1 -0
  56. package/ietf/connection.js +248 -0
  57. package/ietf/connection.js.map +1 -0
  58. package/ietf/control.d.ts +135 -0
  59. package/ietf/control.d.ts.map +1 -0
  60. package/ietf/control.js +242 -0
  61. package/ietf/control.js.map +1 -0
  62. package/ietf/fetch.d.ts +64 -0
  63. package/ietf/fetch.d.ts.map +1 -0
  64. package/ietf/fetch.js +100 -0
  65. package/ietf/fetch.js.map +1 -0
  66. package/ietf/goaway.d.ts +15 -0
  67. package/ietf/goaway.d.ts.map +1 -0
  68. package/ietf/goaway.js +34 -0
  69. package/ietf/goaway.js.map +1 -0
  70. package/ietf/ietf.test.d.ts +2 -0
  71. package/ietf/ietf.test.d.ts.map +1 -0
  72. package/ietf/ietf.test.js +873 -0
  73. package/ietf/ietf.test.js.map +1 -0
  74. package/ietf/index.d.ts +18 -0
  75. package/ietf/index.d.ts.map +1 -0
  76. package/ietf/index.js +19 -0
  77. package/ietf/index.js.map +1 -0
  78. package/ietf/message.d.ts +4 -0
  79. package/ietf/message.d.ts.map +1 -0
  80. package/ietf/message.js +54 -0
  81. package/ietf/message.js.map +1 -0
  82. package/ietf/namespace.d.ts +5 -0
  83. package/ietf/namespace.d.ts.map +1 -0
  84. package/ietf/namespace.js +27 -0
  85. package/ietf/namespace.js.map +1 -0
  86. package/ietf/object.d.ts +40 -0
  87. package/ietf/object.d.ts.map +1 -0
  88. package/ietf/object.js +160 -0
  89. package/ietf/object.js.map +1 -0
  90. package/ietf/parameters.d.ts +56 -0
  91. package/ietf/parameters.d.ts.map +1 -0
  92. package/ietf/parameters.js +337 -0
  93. package/ietf/parameters.js.map +1 -0
  94. package/ietf/properties.d.ts +4 -0
  95. package/ietf/properties.d.ts.map +1 -0
  96. package/ietf/properties.js +33 -0
  97. package/ietf/properties.js.map +1 -0
  98. package/ietf/publish.d.ts +68 -0
  99. package/ietf/publish.d.ts.map +1 -0
  100. package/ietf/publish.js +192 -0
  101. package/ietf/publish.js.map +1 -0
  102. package/ietf/publish_namespace.d.ts +68 -0
  103. package/ietf/publish_namespace.d.ts.map +1 -0
  104. package/ietf/publish_namespace.js +176 -0
  105. package/ietf/publish_namespace.js.map +1 -0
  106. package/ietf/publisher.d.ts +50 -0
  107. package/ietf/publisher.d.ts.map +1 -0
  108. package/ietf/publisher.js +330 -0
  109. package/ietf/publisher.js.map +1 -0
  110. package/ietf/request.d.ts +52 -0
  111. package/ietf/request.d.ts.map +1 -0
  112. package/ietf/request.js +119 -0
  113. package/ietf/request.js.map +1 -0
  114. package/ietf/setup.d.ts +38 -0
  115. package/ietf/setup.d.ts.map +1 -0
  116. package/ietf/setup.js +129 -0
  117. package/ietf/setup.js.map +1 -0
  118. package/ietf/subscribe.d.ts +66 -0
  119. package/ietf/subscribe.d.ts.map +1 -0
  120. package/ietf/subscribe.js +276 -0
  121. package/ietf/subscribe.js.map +1 -0
  122. package/ietf/subscribe_namespace.d.ts +101 -0
  123. package/ietf/subscribe_namespace.d.ts.map +1 -0
  124. package/ietf/subscribe_namespace.js +256 -0
  125. package/ietf/subscribe_namespace.js.map +1 -0
  126. package/ietf/subscriber.d.ts +53 -0
  127. package/ietf/subscriber.d.ts.map +1 -0
  128. package/ietf/subscriber.js +404 -0
  129. package/ietf/subscriber.js.map +1 -0
  130. package/ietf/track.d.ts +40 -0
  131. package/ietf/track.d.ts.map +1 -0
  132. package/ietf/track.js +108 -0
  133. package/ietf/track.js.map +1 -0
  134. package/ietf/version.d.ts +56 -0
  135. package/ietf/version.d.ts.map +1 -0
  136. package/ietf/version.js +63 -0
  137. package/ietf/version.js.map +1 -0
  138. package/index.d.ts +22 -0
  139. package/index.d.ts.map +1 -0
  140. package/index.js +23 -0
  141. package/index.js.map +1 -0
  142. package/integration.test.d.ts +2 -0
  143. package/integration.test.d.ts.map +1 -0
  144. package/integration.test.js +90 -0
  145. package/integration.test.js.map +1 -0
  146. package/lite/announce.d.ts +35 -0
  147. package/lite/announce.d.ts.map +1 -0
  148. package/lite/announce.js +166 -0
  149. package/lite/announce.js.map +1 -0
  150. package/lite/connection.d.ts +47 -0
  151. package/lite/connection.d.ts.map +1 -0
  152. package/lite/connection.js +247 -0
  153. package/lite/connection.js.map +1 -0
  154. package/lite/fetch.d.ts +14 -0
  155. package/lite/fetch.d.ts.map +1 -0
  156. package/lite/fetch.js +47 -0
  157. package/lite/fetch.js.map +1 -0
  158. package/lite/goaway.d.ts +10 -0
  159. package/lite/goaway.d.ts.map +1 -0
  160. package/lite/goaway.js +37 -0
  161. package/lite/goaway.js.map +1 -0
  162. package/lite/group.d.ts +18 -0
  163. package/lite/group.d.ts.map +1 -0
  164. package/lite/group.js +46 -0
  165. package/lite/group.js.map +1 -0
  166. package/lite/index.d.ts +11 -0
  167. package/lite/index.d.ts.map +1 -0
  168. package/lite/index.js +12 -0
  169. package/lite/index.js.map +1 -0
  170. package/lite/message.d.ts +5 -0
  171. package/lite/message.d.ts.map +1 -0
  172. package/lite/message.js +51 -0
  173. package/lite/message.js.map +1 -0
  174. package/lite/origin.d.ts +25 -0
  175. package/lite/origin.d.ts.map +1 -0
  176. package/lite/origin.js +35 -0
  177. package/lite/origin.js.map +1 -0
  178. package/lite/probe.d.ts +12 -0
  179. package/lite/probe.d.ts.map +1 -0
  180. package/lite/probe.js +60 -0
  181. package/lite/probe.js.map +1 -0
  182. package/lite/publisher.d.ts +63 -0
  183. package/lite/publisher.d.ts.map +1 -0
  184. package/lite/publisher.js +350 -0
  185. package/lite/publisher.js.map +1 -0
  186. package/lite/session.d.ts +36 -0
  187. package/lite/session.d.ts.map +1 -0
  188. package/lite/session.js +130 -0
  189. package/lite/session.js.map +1 -0
  190. package/lite/setup.d.ts +18 -0
  191. package/lite/setup.d.ts.map +1 -0
  192. package/lite/setup.js +55 -0
  193. package/lite/setup.js.map +1 -0
  194. package/lite/stream.d.ts +23 -0
  195. package/lite/stream.d.ts.map +1 -0
  196. package/lite/stream.js +20 -0
  197. package/lite/stream.js.map +1 -0
  198. package/lite/subscribe.d.ts +111 -0
  199. package/lite/subscribe.d.ts.map +1 -0
  200. package/lite/subscribe.js +341 -0
  201. package/lite/subscribe.js.map +1 -0
  202. package/lite/subscriber.d.ts +80 -0
  203. package/lite/subscriber.d.ts.map +1 -0
  204. package/lite/subscriber.js +316 -0
  205. package/lite/subscriber.js.map +1 -0
  206. package/lite/track.d.ts +38 -0
  207. package/lite/track.d.ts.map +1 -0
  208. package/lite/track.js +81 -0
  209. package/lite/track.js.map +1 -0
  210. package/lite/track.test.d.ts +2 -0
  211. package/lite/track.test.d.ts.map +1 -0
  212. package/lite/track.test.js +53 -0
  213. package/lite/track.test.js.map +1 -0
  214. package/lite/version.d.ts +16 -0
  215. package/lite/version.d.ts.map +1 -0
  216. package/lite/version.js +41 -0
  217. package/lite/version.js.map +1 -0
  218. package/mock.d.ts +16 -0
  219. package/mock.d.ts.map +1 -0
  220. package/mock.js +154 -0
  221. package/mock.js.map +1 -0
  222. package/package.json +27 -0
  223. package/path.d.ts +111 -0
  224. package/path.d.ts.map +1 -0
  225. package/path.js +133 -0
  226. package/path.js.map +1 -0
  227. package/path.test.d.ts +2 -0
  228. package/path.test.d.ts.map +1 -0
  229. package/path.test.js +169 -0
  230. package/path.test.js.map +1 -0
  231. package/stream.d.ts +57 -0
  232. package/stream.d.ts.map +1 -0
  233. package/stream.js +326 -0
  234. package/stream.js.map +1 -0
  235. package/stream.test.d.ts +2 -0
  236. package/stream.test.d.ts.map +1 -0
  237. package/stream.test.js +210 -0
  238. package/stream.test.js.map +1 -0
  239. package/time.d.ts +109 -0
  240. package/time.d.ts.map +1 -0
  241. package/time.js +94 -0
  242. package/time.js.map +1 -0
  243. package/track.d.ts +85 -0
  244. package/track.d.ts.map +1 -0
  245. package/track.js +227 -0
  246. package/track.js.map +1 -0
  247. package/track.test.d.ts +2 -0
  248. package/track.test.d.ts.map +1 -0
  249. package/track.test.js +56 -0
  250. package/track.test.js.map +1 -0
  251. package/util/error.d.ts +3 -0
  252. package/util/error.d.ts.map +1 -0
  253. package/util/error.js +9 -0
  254. package/util/error.js.map +1 -0
  255. package/util/hex.d.ts +3 -0
  256. package/util/hex.d.ts.map +1 -0
  257. package/util/hex.js +16 -0
  258. package/util/hex.js.map +1 -0
  259. package/util/index.d.ts +2 -0
  260. package/util/index.d.ts.map +1 -0
  261. package/util/index.js +3 -0
  262. package/util/index.js.map +1 -0
  263. package/varint.d.ts +36 -0
  264. package/varint.d.ts.map +1 -0
  265. package/varint.js +288 -0
  266. package/varint.js.map +1 -0
  267. package/varint.test.d.ts +2 -0
  268. package/varint.test.d.ts.map +1 -0
  269. package/varint.test.js +106 -0
  270. package/varint.test.js.map +1 -0
  271. package/zod.d.ts +13 -0
  272. package/zod.d.ts.map +1 -0
  273. package/zod.js +19 -0
  274. package/zod.js.map +1 -0
@@ -0,0 +1,348 @@
1
+ /* @ts-self-types="./connect.d.ts" */
2
+ import Session from "@moq/qmux";
3
+ import * as Ietf from "../ietf/index.js";
4
+ import * as Lite from "../lite/index.js";
5
+ import { Stream } from "../stream.js";
6
+ import * as Hex from "../util/hex.js";
7
+ import { exchangeSetup } from "./handshake.js";
8
+ // Default head start for WebTransport before attempting the WebSocket fallback.
9
+ const DEFAULT_WEBSOCKET_DELAY_MS = 500;
10
+ // Save if WebSocket won the last race, so we won't give QUIC a head start next time.
11
+ const websocketWon = new Set();
12
+ // Firefox's WebTransport implementation drops server-initiated bidi streams,
13
+ // breaking publish (the relay opens a subscribe bidi back to us). Force WebSocket.
14
+ // TODO: remove once Firefox fixes incoming bidi delivery.
15
+ const isFirefox = typeof navigator !== "undefined" && navigator.userAgent.toLowerCase().includes("firefox");
16
+ /**
17
+ * Establishes a connection to a MOQ server.
18
+ *
19
+ * @param url - The URL of the server to connect to
20
+ * @returns A promise that resolves to a Connection instance
21
+ */
22
+ export async function connect(url, props) {
23
+ if (props?.transport) {
24
+ return connectTransport(url, props.transport);
25
+ }
26
+ // Create a cancel promise to kill whichever is still connecting.
27
+ const { promise: cancel, resolve: done } = Promise.withResolvers();
28
+ const webtransport = globalThis.WebTransport && !isFirefox ? connectWebTransport(url, cancel, props?.webtransport) : undefined;
29
+ // Give QUIC a head start to connect before trying WebSocket, unless WebSocket has won in the past.
30
+ // NOTE that QUIC should be faster because it involves 1/2 fewer RTTs.
31
+ const headstart = !webtransport || websocketWon.has(url.toString()) ? 0 : (props?.websocket?.delay ?? DEFAULT_WEBSOCKET_DELAY_MS);
32
+ const websocket = props?.websocket?.enabled !== false
33
+ ? connectWebSocket(props?.websocket?.url ?? url, headstart, cancel)
34
+ : undefined;
35
+ if (!websocket && !webtransport) {
36
+ throw new Error("no transport available; WebTransport not supported and WebSocket is disabled");
37
+ }
38
+ // Race the available transports, using `.any` to ignore if one participant has an error.
39
+ // `webtransport`/`websocket` are `Promise | undefined`, so test existence explicitly: a
40
+ // promise is always truthy, so bare truthiness here would be a misused-promise.
41
+ const session = await Promise.any(webtransport !== undefined
42
+ ? websocket !== undefined
43
+ ? [websocket, webtransport]
44
+ : [webtransport]
45
+ : [websocket]);
46
+ done();
47
+ if (!session)
48
+ throw new Error("no transport available");
49
+ // Save if WebSocket won the last race, so we won't give QUIC a head start next time.
50
+ if (session instanceof Session) {
51
+ console.warn(url.toString(), "connected via WebSocket");
52
+ websocketWon.add(url.toString());
53
+ }
54
+ else {
55
+ console.log(url.toString(), "connected via WebTransport");
56
+ }
57
+ // Get the negotiated protocol. qmux Session exposes it directly;
58
+ // native WebTransport doesn't have a standard .protocol property yet.
59
+ const protocol = session instanceof Session
60
+ ? session.protocol || undefined
61
+ : // @ts-expect-error - TODO: add protocol to WebTransport
62
+ session.protocol;
63
+ console.debug(url.toString(), "negotiated ALPN:", protocol ?? "(none)");
64
+ // Choose setup encoding based on negotiated WebTransport protocol (if any).
65
+ let setupVersion;
66
+ const modernVersion = protocol === Ietf.ALPN.DRAFT_19
67
+ ? Ietf.Version.DRAFT_19
68
+ : protocol === Ietf.ALPN.DRAFT_18
69
+ ? Ietf.Version.DRAFT_18
70
+ : protocol === Ietf.ALPN.DRAFT_17
71
+ ? Ietf.Version.DRAFT_17
72
+ : undefined;
73
+ if (modernVersion !== undefined) {
74
+ return await handshakeAlpn(url, session, modernVersion);
75
+ }
76
+ else if (protocol === Ietf.ALPN.DRAFT_16) {
77
+ setupVersion = Ietf.Version.DRAFT_16;
78
+ }
79
+ else if (protocol === Ietf.ALPN.DRAFT_15) {
80
+ setupVersion = Ietf.Version.DRAFT_15;
81
+ }
82
+ else if (protocol === Lite.ALPN_05_WIP) {
83
+ // moq-lite draft-05 exchanges SETUP on a dedicated unidirectional stream
84
+ // (handled inside Connection), not the session compat stream.
85
+ return new Lite.Connection(url, session, Lite.Version.DRAFT_05_WIP, undefined);
86
+ }
87
+ else if (protocol === Lite.ALPN_04) {
88
+ // moq-lite draft-04 doesn't use a session stream, so we return immediately.
89
+ return new Lite.Connection(url, session, Lite.Version.DRAFT_04, undefined);
90
+ }
91
+ else if (protocol === Lite.ALPN_03) {
92
+ // moq-lite draft-03 doesn't use a session stream, so we return immediately.
93
+ return new Lite.Connection(url, session, Lite.Version.DRAFT_03, undefined);
94
+ }
95
+ else if (protocol === Lite.ALPN || protocol === "" || protocol === undefined) {
96
+ // moq-lite ALPN (or no protocol) uses Draft14 encoding for SETUP,
97
+ // then negotiates the actual version via the SETUP message.
98
+ setupVersion = Ietf.Version.DRAFT_14;
99
+ }
100
+ else {
101
+ throw new Error(`unsupported WebTransport protocol: ${protocol}`);
102
+ }
103
+ // We're encoding 0x20 so it's backwards compatible with moq-transport-10+
104
+ const stream = await Stream.open(session);
105
+ await stream.writer.u53(Lite.StreamId.ClientCompat);
106
+ const encoder = new TextEncoder();
107
+ const params = new Ietf.SetupOptions();
108
+ params.setVarint(Ietf.SetupOption.MaxRequestId, 42069n); // Allow a ton of request IDs.
109
+ params.setBytes(Ietf.SetupOption.Implementation, encoder.encode("moq-lite-js")); // Put the implementation name in the parameters.
110
+ const client = new Ietf.ClientSetup({
111
+ // NOTE: draft 15 onwards does not use CLIENT_SETUP to negotiate the version.
112
+ // We still echo it just to make sure we're not accidentally trying to negotiate the version.
113
+ versions: setupVersion === Ietf.Version.DRAFT_16
114
+ ? [Ietf.Version.DRAFT_16]
115
+ : setupVersion === Ietf.Version.DRAFT_15
116
+ ? [Ietf.Version.DRAFT_15]
117
+ : [Lite.Version.DRAFT_02, Lite.Version.DRAFT_01, Ietf.Version.DRAFT_14],
118
+ parameters: params,
119
+ });
120
+ console.debug(url.toString(), "sending client setup", client);
121
+ await client.encode(stream.writer, setupVersion);
122
+ // And we expect 0x21 as the response.
123
+ const serverCompat = await stream.reader.u53();
124
+ if (serverCompat !== Lite.StreamId.ServerCompat) {
125
+ throw new Error(`unsupported server message type: ${serverCompat.toString()}`);
126
+ }
127
+ // Decode ServerSetup in Draft14 format (version + params)
128
+ const server = await Ietf.ServerSetup.decode(stream.reader, setupVersion);
129
+ console.debug(url.toString(), "received server setup", server);
130
+ if (Object.values(Lite.Version).includes(server.version)) {
131
+ return new Lite.Connection(url, session, server.version, stream);
132
+ }
133
+ else if (Object.values(Ietf.Version).includes(server.version)) {
134
+ const maxRequestId = server.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;
135
+ return new Ietf.Connection({
136
+ url,
137
+ quic: session,
138
+ control: stream,
139
+ maxRequestId,
140
+ version: server.version,
141
+ });
142
+ }
143
+ else {
144
+ throw new Error(`unsupported server version: ${server.version.toString()}`);
145
+ }
146
+ }
147
+ async function connectTransport(url, session) {
148
+ // @ts-expect-error - TODO: add protocol to WebTransport
149
+ const protocol = session.protocol;
150
+ // Choose setup encoding based on negotiated WebTransport protocol (if any).
151
+ let setupVersion;
152
+ const modernVersion = protocol === Ietf.ALPN.DRAFT_19
153
+ ? Ietf.Version.DRAFT_19
154
+ : protocol === Ietf.ALPN.DRAFT_18
155
+ ? Ietf.Version.DRAFT_18
156
+ : protocol === Ietf.ALPN.DRAFT_17
157
+ ? Ietf.Version.DRAFT_17
158
+ : undefined;
159
+ if (modernVersion !== undefined) {
160
+ return await handshakeAlpn(url, session, modernVersion);
161
+ }
162
+ else if (protocol === Ietf.ALPN.DRAFT_16) {
163
+ setupVersion = Ietf.Version.DRAFT_16;
164
+ }
165
+ else if (protocol === Ietf.ALPN.DRAFT_15) {
166
+ setupVersion = Ietf.Version.DRAFT_15;
167
+ }
168
+ else if (protocol === Lite.ALPN_05_WIP) {
169
+ return new Lite.Connection(url, session, Lite.Version.DRAFT_05_WIP, undefined);
170
+ }
171
+ else if (protocol === Lite.ALPN_04) {
172
+ return new Lite.Connection(url, session, Lite.Version.DRAFT_04, undefined);
173
+ }
174
+ else if (protocol === Lite.ALPN_03) {
175
+ return new Lite.Connection(url, session, Lite.Version.DRAFT_03, undefined);
176
+ }
177
+ else if (protocol === Lite.ALPN || protocol === "" || protocol === undefined) {
178
+ setupVersion = Ietf.Version.DRAFT_14;
179
+ }
180
+ else {
181
+ throw new Error(`unsupported WebTransport protocol: ${protocol}`);
182
+ }
183
+ const stream = await Stream.open(session);
184
+ await stream.writer.u53(Lite.StreamId.ClientCompat);
185
+ const encoder = new TextEncoder();
186
+ const params = new Ietf.SetupOptions();
187
+ params.setVarint(Ietf.SetupOption.MaxRequestId, 42069n);
188
+ params.setBytes(Ietf.SetupOption.Implementation, encoder.encode("moq-lite-js"));
189
+ const client = new Ietf.ClientSetup({
190
+ versions: setupVersion === Ietf.Version.DRAFT_16
191
+ ? [Ietf.Version.DRAFT_16]
192
+ : setupVersion === Ietf.Version.DRAFT_15
193
+ ? [Ietf.Version.DRAFT_15]
194
+ : [Lite.Version.DRAFT_02, Lite.Version.DRAFT_01, Ietf.Version.DRAFT_14],
195
+ parameters: params,
196
+ });
197
+ await client.encode(stream.writer, setupVersion);
198
+ const serverCompat = await stream.reader.u53();
199
+ if (serverCompat !== Lite.StreamId.ServerCompat) {
200
+ throw new Error(`unsupported server message type: ${serverCompat.toString()}`);
201
+ }
202
+ const server = await Ietf.ServerSetup.decode(stream.reader, setupVersion);
203
+ if (Object.values(Lite.Version).includes(server.version)) {
204
+ return new Lite.Connection(url, session, server.version, stream);
205
+ }
206
+ else if (Object.values(Ietf.Version).includes(server.version)) {
207
+ const maxRequestId = server.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;
208
+ return new Ietf.Connection({
209
+ url,
210
+ quic: session,
211
+ control: stream,
212
+ maxRequestId,
213
+ version: server.version,
214
+ });
215
+ }
216
+ else {
217
+ throw new Error(`unsupported server version: ${server.version.toString()}`);
218
+ }
219
+ }
220
+ /**
221
+ * Draft-17+ client handshake. ALPN already pinned the version; SETUP is
222
+ * exchanged over a pair of uni streams using stream type 0x2F00.
223
+ */
224
+ async function handshakeAlpn(url, session, version) {
225
+ const controlStream = await exchangeSetup(session, version, "moq-lite-js");
226
+ return new Ietf.Connection({
227
+ url,
228
+ quic: session,
229
+ control: controlStream,
230
+ // v17+ uses NativeSession which manages its own request IDs; maxRequestId is unused.
231
+ maxRequestId: 0n,
232
+ version,
233
+ });
234
+ }
235
+ // Strip PEM armor and base64-decode to the raw DER bytes.
236
+ function pemToDer(pem) {
237
+ const match = pem.match(/-----BEGIN CERTIFICATE-----([\s\S]+?)-----END CERTIFICATE-----/);
238
+ if (!match) {
239
+ throw new Error("invalid PEM certificate: missing -----BEGIN/END CERTIFICATE----- armor");
240
+ }
241
+ const binary = atob(match[1].replace(/\s+/g, ""));
242
+ const der = new Uint8Array(binary.length);
243
+ for (let i = 0; i < binary.length; i++) {
244
+ der[i] = binary.charCodeAt(i);
245
+ }
246
+ return der;
247
+ }
248
+ /**
249
+ * Compute the SHA-256 hash of a certificate, the value `serverCertificateHashes`
250
+ * pins. Accepts a PEM string or raw DER bytes. Matches the hex fingerprints a moq
251
+ * server reports, so `Hex.fromBytes(await certificateHash(pem))` round-trips.
252
+ */
253
+ export async function certificateHash(cert) {
254
+ const der = typeof cert === "string" ? pemToDer(cert) : cert;
255
+ const digest = await crypto.subtle.digest("SHA-256", der);
256
+ return new Uint8Array(digest);
257
+ }
258
+ // Normalize our friendlier pinning options into the DOM `serverCertificateHashes`.
259
+ async function resolveCertificateHashes(options) {
260
+ const hashes = [];
261
+ for (const hash of options?.serverCertificateHashes ?? []) {
262
+ const value = typeof hash.value === "string" ? Hex.toBytes(hash.value) : hash.value;
263
+ hashes.push({ algorithm: hash.algorithm ?? "sha-256", value });
264
+ }
265
+ if (options?.serverCertificate !== undefined) {
266
+ hashes.push({ algorithm: "sha-256", value: await certificateHash(options.serverCertificate) });
267
+ }
268
+ return hashes.length > 0 ? hashes : undefined;
269
+ }
270
+ async function connectWebTransport(url, cancel, options) {
271
+ let finalUrl = url;
272
+ // Our custom pinning fields are normalized separately; the rest are DOM options.
273
+ const { serverCertificate: _cert, serverCertificateHashes: _hashes, ...webtransport } = options ?? {};
274
+ const finalOptions = {
275
+ allowPooling: false,
276
+ congestionControl: "low-latency",
277
+ protocols: [
278
+ Lite.ALPN_04,
279
+ Lite.ALPN_03,
280
+ Lite.ALPN,
281
+ // NOTE(norsk): the IETF draft ALPNs (moqt-15..19) are intentionally NOT
282
+ // offered. norsk advertises moqt-16, and net's draft-16 data-plane
283
+ // reader can't yet consume norsk's (correct) draft-16 subgroup objects,
284
+ // so negotiating moqt-16 breaks media playback. Offering only the
285
+ // moq-lite ALPNs makes the WebTransport handshake settle on no IETF
286
+ // protocol → net's draft-14 fallback (connect.ts §"Lite.ALPN | ''"),
287
+ // which norsk serves fine. Restore these once net draft-16 works.
288
+ ],
289
+ ...webtransport,
290
+ };
291
+ // Accumulate caller-provided pins first, then append anything we fetch below,
292
+ // so a fetched fingerprint never clobbers hashes passed in via options.
293
+ const hashes = (await resolveCertificateHashes(options)) ?? [];
294
+ // Only perform certificate fetch and URL rewrite when polyfill is not needed
295
+ // This is needed because WebTransport is a butt to work with in local development.
296
+ if (url.protocol === "http:") {
297
+ const fingerprintUrl = new URL(url);
298
+ fingerprintUrl.pathname = "/certificate.sha256";
299
+ fingerprintUrl.search = "";
300
+ // Dev-only path: http:// can't be a real WebTransport origin, so we fetch the
301
+ // self-signed cert's hash over plain HTTP and pin it. Production uses https://
302
+ // and never reaches here. Keep this at debug so it doesn't read as a problem.
303
+ console.debug(fingerprintUrl.toString(), "performing an insecure fingerprint fetch; use https:// in production");
304
+ // Fetch the fingerprint from the server.
305
+ // TODO cancel the request if the effect is cancelled.
306
+ const fingerprint = await Promise.race([fetch(fingerprintUrl), cancel]);
307
+ if (!fingerprint)
308
+ return undefined;
309
+ const fingerprintText = await Promise.race([fingerprint.text(), cancel]);
310
+ if (fingerprintText === undefined)
311
+ return undefined;
312
+ hashes.push({ algorithm: "sha-256", value: Hex.toBytes(fingerprintText) });
313
+ finalUrl = new URL(url);
314
+ finalUrl.protocol = "https:";
315
+ }
316
+ if (hashes.length > 0) {
317
+ finalOptions.serverCertificateHashes = hashes;
318
+ }
319
+ const quic = new WebTransport(finalUrl, finalOptions);
320
+ // Both .ready and .closed reject on failure; catch .closed to avoid an unhandled rejection.
321
+ quic.closed.catch(() => { });
322
+ // Wait for the WebTransport to connect, or for the cancel promise to resolve.
323
+ // Close the connection if we lost the race.
324
+ const loaded = await Promise.race([quic.ready.then(() => true), cancel]);
325
+ if (!loaded) {
326
+ quic.close();
327
+ return undefined;
328
+ }
329
+ return quic;
330
+ }
331
+ // TODO accept arguments to control the port/path used.
332
+ async function connectWebSocket(url, delay, cancel) {
333
+ const timer = new Promise((resolve) => setTimeout(resolve, delay));
334
+ const active = await Promise.race([cancel, timer.then(() => true)]);
335
+ if (!active)
336
+ return undefined;
337
+ const quic = new Session(url);
338
+ // Wait for the WebSocket to connect, or for the cancel promise to resolve.
339
+ // `ready` rejects on a refused/failed connection, so a throw here is the caller's
340
+ // cue to retry; a lost cancel race instead resolves and we close the loser.
341
+ const loaded = await Promise.race([quic.ready.then(() => true), cancel]);
342
+ if (!loaded) {
343
+ quic.close();
344
+ return undefined;
345
+ }
346
+ return quic;
347
+ }
348
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../../src/connection/connect.ts"],"names":[],"mappings":"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","sourcesContent":["import Session from \"@moq/qmux\";\nimport * as Ietf from \"../ietf/index.ts\";\nimport * as Lite from \"../lite/index.ts\";\nimport { Stream } from \"../stream.ts\";\nimport * as Hex from \"../util/hex.ts\";\nimport type { Established } from \"./established.ts\";\nimport { exchangeSetup } from \"./handshake.ts\";\n\n// Default head start for WebTransport before attempting the WebSocket fallback.\nconst DEFAULT_WEBSOCKET_DELAY_MS = 500;\n\n/** Tuning for the WebSocket fallback used when WebTransport is unavailable or loses the connect race. */\nexport interface WebSocketOptions {\n\t// If true (default), enable the WebSocket fallback.\n\tenabled?: boolean;\n\n\t// Optional: Use a different URL than WebTransport.\n\t// By default, `https` => `wss` and `http` => `ws`.\n\turl?: URL;\n\n\t// The delay in milliseconds before attempting the WebSocket fallback. (default: 500)\n\t// If WebSocket won the previous race for a given URL, this will be 0.\n\tdelay?: DOMHighResTimeStamp;\n}\n\n// One entry of `serverCertificateHashes`, used to pin a self-signed server.\n// Unlike the DOM type, `value` also accepts a hex string (the format moq\n// servers report via their certificate fingerprints), decoded automatically.\n/** A server certificate hash used to pin a self-signed server. `value` accepts raw bytes or a hex string. */\nexport interface CertificateHash {\n\talgorithm?: \"sha-256\";\n\tvalue: BufferSource | string;\n}\n\n// WebTransport options, extended with friendlier certificate pinning.\n/** WebTransport options extended with friendlier certificate pinning (hex hashes or a raw certificate). */\nexport interface WebTransportProps extends Omit<WebTransportOptions, \"serverCertificateHashes\"> {\n\t// Pin the server to one or more certificate hashes. Each `value` may be raw\n\t// bytes or a hex string; the algorithm defaults to `sha-256`.\n\tserverCertificateHashes?: CertificateHash[];\n\n\t// Pin the server by supplying its certificate directly; the SHA-256 hash is\n\t// computed for you. Accepts a PEM string or raw DER bytes. Use this when you\n\t// have the certificate but not its precomputed fingerprint.\n\tserverCertificate?: string | BufferSource;\n}\n\n/** Options for {@link connect}. */\nexport interface ConnectProps {\n\t// WebTransport options.\n\twebtransport?: WebTransportProps;\n\n\t// WebSocket (fallback) options.\n\twebsocket?: WebSocketOptions;\n\n\t// Use a pre-existing WebTransport session instead of connecting.\n\t// When provided, skips WebTransport/WebSocket race and uses this directly.\n\ttransport?: WebTransport;\n}\n\n// Save if WebSocket won the last race, so we won't give QUIC a head start next time.\nconst websocketWon = new Set<string>();\n\n// Firefox's WebTransport implementation drops server-initiated bidi streams,\n// breaking publish (the relay opens a subscribe bidi back to us). Force WebSocket.\n// TODO: remove once Firefox fixes incoming bidi delivery.\nconst isFirefox = typeof navigator !== \"undefined\" && navigator.userAgent.toLowerCase().includes(\"firefox\");\n\n/**\n * Establishes a connection to a MOQ server.\n *\n * @param url - The URL of the server to connect to\n * @returns A promise that resolves to a Connection instance\n */\nexport async function connect(url: URL, props?: ConnectProps): Promise<Established> {\n\tif (props?.transport) {\n\t\treturn connectTransport(url, props.transport);\n\t}\n\n\t// Create a cancel promise to kill whichever is still connecting.\n\tconst { promise: cancel, resolve: done } = Promise.withResolvers<void>();\n\n\tconst webtransport =\n\t\tglobalThis.WebTransport && !isFirefox ? connectWebTransport(url, cancel, props?.webtransport) : undefined;\n\n\t// Give QUIC a head start to connect before trying WebSocket, unless WebSocket has won in the past.\n\t// NOTE that QUIC should be faster because it involves 1/2 fewer RTTs.\n\tconst headstart =\n\t\t!webtransport || websocketWon.has(url.toString()) ? 0 : (props?.websocket?.delay ?? DEFAULT_WEBSOCKET_DELAY_MS);\n\tconst websocket =\n\t\tprops?.websocket?.enabled !== false\n\t\t\t? connectWebSocket(props?.websocket?.url ?? url, headstart, cancel)\n\t\t\t: undefined;\n\n\tif (!websocket && !webtransport) {\n\t\tthrow new Error(\"no transport available; WebTransport not supported and WebSocket is disabled\");\n\t}\n\n\t// Race the available transports, using `.any` to ignore if one participant has an error.\n\t// `webtransport`/`websocket` are `Promise | undefined`, so test existence explicitly: a\n\t// promise is always truthy, so bare truthiness here would be a misused-promise.\n\tconst session = await Promise.any(\n\t\twebtransport !== undefined\n\t\t\t? websocket !== undefined\n\t\t\t\t? [websocket, webtransport]\n\t\t\t\t: [webtransport]\n\t\t\t: [websocket],\n\t);\n\tdone();\n\n\tif (!session) throw new Error(\"no transport available\");\n\n\t// Save if WebSocket won the last race, so we won't give QUIC a head start next time.\n\tif (session instanceof Session) {\n\t\tconsole.warn(url.toString(), \"connected via WebSocket\");\n\t\twebsocketWon.add(url.toString());\n\t} else {\n\t\tconsole.log(url.toString(), \"connected via WebTransport\");\n\t}\n\n\t// Get the negotiated protocol. qmux Session exposes it directly;\n\t// native WebTransport doesn't have a standard .protocol property yet.\n\tconst protocol: string | undefined =\n\t\tsession instanceof Session\n\t\t\t? session.protocol || undefined\n\t\t\t: // @ts-expect-error - TODO: add protocol to WebTransport\n\t\t\t\tsession.protocol;\n\tconsole.debug(url.toString(), \"negotiated ALPN:\", protocol ?? \"(none)\");\n\n\t// Choose setup encoding based on negotiated WebTransport protocol (if any).\n\tlet setupVersion: Ietf.Version;\n\tconst modernVersion =\n\t\tprotocol === Ietf.ALPN.DRAFT_19\n\t\t\t? Ietf.Version.DRAFT_19\n\t\t\t: protocol === Ietf.ALPN.DRAFT_18\n\t\t\t\t? Ietf.Version.DRAFT_18\n\t\t\t\t: protocol === Ietf.ALPN.DRAFT_17\n\t\t\t\t\t? Ietf.Version.DRAFT_17\n\t\t\t\t\t: undefined;\n\tif (modernVersion !== undefined) {\n\t\treturn await handshakeAlpn(url, session as WebTransport, modernVersion);\n\t} else if (protocol === Ietf.ALPN.DRAFT_16) {\n\t\tsetupVersion = Ietf.Version.DRAFT_16;\n\t} else if (protocol === Ietf.ALPN.DRAFT_15) {\n\t\tsetupVersion = Ietf.Version.DRAFT_15;\n\t} else if (protocol === Lite.ALPN_05_WIP) {\n\t\t// moq-lite draft-05 exchanges SETUP on a dedicated unidirectional stream\n\t\t// (handled inside Connection), not the session compat stream.\n\t\treturn new Lite.Connection(url, session, Lite.Version.DRAFT_05_WIP, undefined);\n\t} else if (protocol === Lite.ALPN_04) {\n\t\t// moq-lite draft-04 doesn't use a session stream, so we return immediately.\n\t\treturn new Lite.Connection(url, session, Lite.Version.DRAFT_04, undefined);\n\t} else if (protocol === Lite.ALPN_03) {\n\t\t// moq-lite draft-03 doesn't use a session stream, so we return immediately.\n\t\treturn new Lite.Connection(url, session, Lite.Version.DRAFT_03, undefined);\n\t} else if (protocol === Lite.ALPN || protocol === \"\" || protocol === undefined) {\n\t\t// moq-lite ALPN (or no protocol) uses Draft14 encoding for SETUP,\n\t\t// then negotiates the actual version via the SETUP message.\n\t\tsetupVersion = Ietf.Version.DRAFT_14;\n\t} else {\n\t\tthrow new Error(`unsupported WebTransport protocol: ${protocol}`);\n\t}\n\n\t// We're encoding 0x20 so it's backwards compatible with moq-transport-10+\n\tconst stream = await Stream.open(session);\n\tawait stream.writer.u53(Lite.StreamId.ClientCompat);\n\n\tconst encoder = new TextEncoder();\n\n\tconst params = new Ietf.SetupOptions();\n\tparams.setVarint(Ietf.SetupOption.MaxRequestId, 42069n); // Allow a ton of request IDs.\n\tparams.setBytes(Ietf.SetupOption.Implementation, encoder.encode(\"moq-lite-js\")); // Put the implementation name in the parameters.\n\n\tconst client = new Ietf.ClientSetup({\n\t\t// NOTE: draft 15 onwards does not use CLIENT_SETUP to negotiate the version.\n\t\t// We still echo it just to make sure we're not accidentally trying to negotiate the version.\n\t\tversions:\n\t\t\tsetupVersion === Ietf.Version.DRAFT_16\n\t\t\t\t? [Ietf.Version.DRAFT_16]\n\t\t\t\t: setupVersion === Ietf.Version.DRAFT_15\n\t\t\t\t\t? [Ietf.Version.DRAFT_15]\n\t\t\t\t\t: [Lite.Version.DRAFT_02, Lite.Version.DRAFT_01, Ietf.Version.DRAFT_14],\n\t\tparameters: params,\n\t});\n\tconsole.debug(url.toString(), \"sending client setup\", client);\n\tawait client.encode(stream.writer, setupVersion);\n\n\t// And we expect 0x21 as the response.\n\tconst serverCompat = await stream.reader.u53();\n\tif (serverCompat !== Lite.StreamId.ServerCompat) {\n\t\tthrow new Error(`unsupported server message type: ${serverCompat.toString()}`);\n\t}\n\n\t// Decode ServerSetup in Draft14 format (version + params)\n\tconst server = await Ietf.ServerSetup.decode(stream.reader, setupVersion);\n\tconsole.debug(url.toString(), \"received server setup\", server);\n\n\tif (Object.values(Lite.Version).includes(server.version as Lite.Version)) {\n\t\treturn new Lite.Connection(url, session, server.version as Lite.Version, stream);\n\t} else if (Object.values(Ietf.Version).includes(server.version as Ietf.Version)) {\n\t\tconst maxRequestId = server.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;\n\t\treturn new Ietf.Connection({\n\t\t\turl,\n\t\t\tquic: session,\n\t\t\tcontrol: stream,\n\t\t\tmaxRequestId,\n\t\t\tversion: server.version as Ietf.IetfVersion,\n\t\t});\n\t} else {\n\t\tthrow new Error(`unsupported server version: ${server.version.toString()}`);\n\t}\n}\n\nasync function connectTransport(url: URL, session: WebTransport): Promise<Established> {\n\t// @ts-expect-error - TODO: add protocol to WebTransport\n\tconst protocol: string | undefined = session.protocol;\n\n\t// Choose setup encoding based on negotiated WebTransport protocol (if any).\n\tlet setupVersion: Ietf.Version;\n\tconst modernVersion =\n\t\tprotocol === Ietf.ALPN.DRAFT_19\n\t\t\t? Ietf.Version.DRAFT_19\n\t\t\t: protocol === Ietf.ALPN.DRAFT_18\n\t\t\t\t? Ietf.Version.DRAFT_18\n\t\t\t\t: protocol === Ietf.ALPN.DRAFT_17\n\t\t\t\t\t? Ietf.Version.DRAFT_17\n\t\t\t\t\t: undefined;\n\tif (modernVersion !== undefined) {\n\t\treturn await handshakeAlpn(url, session, modernVersion);\n\t} else if (protocol === Ietf.ALPN.DRAFT_16) {\n\t\tsetupVersion = Ietf.Version.DRAFT_16;\n\t} else if (protocol === Ietf.ALPN.DRAFT_15) {\n\t\tsetupVersion = Ietf.Version.DRAFT_15;\n\t} else if (protocol === Lite.ALPN_05_WIP) {\n\t\treturn new Lite.Connection(url, session, Lite.Version.DRAFT_05_WIP, undefined);\n\t} else if (protocol === Lite.ALPN_04) {\n\t\treturn new Lite.Connection(url, session, Lite.Version.DRAFT_04, undefined);\n\t} else if (protocol === Lite.ALPN_03) {\n\t\treturn new Lite.Connection(url, session, Lite.Version.DRAFT_03, undefined);\n\t} else if (protocol === Lite.ALPN || protocol === \"\" || protocol === undefined) {\n\t\tsetupVersion = Ietf.Version.DRAFT_14;\n\t} else {\n\t\tthrow new Error(`unsupported WebTransport protocol: ${protocol}`);\n\t}\n\n\tconst stream = await Stream.open(session);\n\tawait stream.writer.u53(Lite.StreamId.ClientCompat);\n\n\tconst encoder = new TextEncoder();\n\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 client = new Ietf.ClientSetup({\n\t\tversions:\n\t\t\tsetupVersion === Ietf.Version.DRAFT_16\n\t\t\t\t? [Ietf.Version.DRAFT_16]\n\t\t\t\t: setupVersion === Ietf.Version.DRAFT_15\n\t\t\t\t\t? [Ietf.Version.DRAFT_15]\n\t\t\t\t\t: [Lite.Version.DRAFT_02, Lite.Version.DRAFT_01, Ietf.Version.DRAFT_14],\n\t\tparameters: params,\n\t});\n\tawait client.encode(stream.writer, setupVersion);\n\n\tconst serverCompat = await stream.reader.u53();\n\tif (serverCompat !== Lite.StreamId.ServerCompat) {\n\t\tthrow new Error(`unsupported server message type: ${serverCompat.toString()}`);\n\t}\n\n\tconst server = await Ietf.ServerSetup.decode(stream.reader, setupVersion);\n\n\tif (Object.values(Lite.Version).includes(server.version as Lite.Version)) {\n\t\treturn new Lite.Connection(url, session, server.version as Lite.Version, stream);\n\t} else if (Object.values(Ietf.Version).includes(server.version as Ietf.Version)) {\n\t\tconst maxRequestId = server.parameters.getVarint(Ietf.SetupOption.MaxRequestId) ?? 0n;\n\t\treturn new Ietf.Connection({\n\t\t\turl,\n\t\t\tquic: session,\n\t\t\tcontrol: stream,\n\t\t\tmaxRequestId,\n\t\t\tversion: server.version as Ietf.IetfVersion,\n\t\t});\n\t} else {\n\t\tthrow new Error(`unsupported server version: ${server.version.toString()}`);\n\t}\n}\n\n/**\n * Draft-17+ client handshake. ALPN already pinned the version; SETUP is\n * exchanged over a pair of uni streams using stream type 0x2F00.\n */\nasync function handshakeAlpn(url: URL, session: WebTransport, version: Ietf.IetfVersion): Promise<Established> {\n\tconst controlStream = await exchangeSetup(session, version, \"moq-lite-js\");\n\n\treturn new Ietf.Connection({\n\t\turl,\n\t\tquic: session,\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// One entry of the DOM `serverCertificateHashes`, derived without naming the lib type.\ntype WebTransportHash = NonNullable<WebTransportOptions[\"serverCertificateHashes\"]>[number];\n\n// Strip PEM armor and base64-decode to the raw DER bytes.\nfunction pemToDer(pem: string): Uint8Array<ArrayBuffer> {\n\tconst match = pem.match(/-----BEGIN CERTIFICATE-----([\\s\\S]+?)-----END CERTIFICATE-----/);\n\tif (!match) {\n\t\tthrow new Error(\"invalid PEM certificate: missing -----BEGIN/END CERTIFICATE----- armor\");\n\t}\n\n\tconst binary = atob(match[1].replace(/\\s+/g, \"\"));\n\tconst der = new Uint8Array(binary.length);\n\tfor (let i = 0; i < binary.length; i++) {\n\t\tder[i] = binary.charCodeAt(i);\n\t}\n\treturn der;\n}\n\n/**\n * Compute the SHA-256 hash of a certificate, the value `serverCertificateHashes`\n * pins. Accepts a PEM string or raw DER bytes. Matches the hex fingerprints a moq\n * server reports, so `Hex.fromBytes(await certificateHash(pem))` round-trips.\n */\nexport async function certificateHash(cert: string | BufferSource): Promise<Uint8Array<ArrayBuffer>> {\n\tconst der = typeof cert === \"string\" ? pemToDer(cert) : cert;\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", der);\n\treturn new Uint8Array(digest);\n}\n\n// Normalize our friendlier pinning options into the DOM `serverCertificateHashes`.\nasync function resolveCertificateHashes(options?: WebTransportProps): Promise<WebTransportHash[] | undefined> {\n\tconst hashes: WebTransportHash[] = [];\n\n\tfor (const hash of options?.serverCertificateHashes ?? []) {\n\t\tconst value = typeof hash.value === \"string\" ? Hex.toBytes(hash.value) : hash.value;\n\t\thashes.push({ algorithm: hash.algorithm ?? \"sha-256\", value });\n\t}\n\n\tif (options?.serverCertificate !== undefined) {\n\t\thashes.push({ algorithm: \"sha-256\", value: await certificateHash(options.serverCertificate) });\n\t}\n\n\treturn hashes.length > 0 ? hashes : undefined;\n}\n\nasync function connectWebTransport(\n\turl: URL,\n\tcancel: Promise<void>,\n\toptions?: WebTransportProps,\n): Promise<WebTransport | undefined> {\n\tlet finalUrl = url;\n\n\t// Our custom pinning fields are normalized separately; the rest are DOM options.\n\tconst { serverCertificate: _cert, serverCertificateHashes: _hashes, ...webtransport } = options ?? {};\n\n\tconst finalOptions: WebTransportOptions = {\n\t\tallowPooling: false,\n\t\tcongestionControl: \"low-latency\",\n\t\tprotocols: [\n\t\t\tLite.ALPN_04,\n\t\t\tLite.ALPN_03,\n\t\t\tLite.ALPN,\n\t\t\t// NOTE(norsk): the IETF draft ALPNs (moqt-15..19) are intentionally NOT\n\t\t\t// offered. norsk advertises moqt-16, and net's draft-16 data-plane\n\t\t\t// reader can't yet consume norsk's (correct) draft-16 subgroup objects,\n\t\t\t// so negotiating moqt-16 breaks media playback. Offering only the\n\t\t\t// moq-lite ALPNs makes the WebTransport handshake settle on no IETF\n\t\t\t// protocol → net's draft-14 fallback (connect.ts §\"Lite.ALPN | ''\"),\n\t\t\t// which norsk serves fine. Restore these once net draft-16 works.\n\t\t],\n\t\t...webtransport,\n\t};\n\n\t// Accumulate caller-provided pins first, then append anything we fetch below,\n\t// so a fetched fingerprint never clobbers hashes passed in via options.\n\tconst hashes = (await resolveCertificateHashes(options)) ?? [];\n\n\t// Only perform certificate fetch and URL rewrite when polyfill is not needed\n\t// This is needed because WebTransport is a butt to work with in local development.\n\tif (url.protocol === \"http:\") {\n\t\tconst fingerprintUrl = new URL(url);\n\t\tfingerprintUrl.pathname = \"/certificate.sha256\";\n\t\tfingerprintUrl.search = \"\";\n\t\t// Dev-only path: http:// can't be a real WebTransport origin, so we fetch the\n\t\t// self-signed cert's hash over plain HTTP and pin it. Production uses https://\n\t\t// and never reaches here. Keep this at debug so it doesn't read as a problem.\n\t\tconsole.debug(\n\t\t\tfingerprintUrl.toString(),\n\t\t\t\"performing an insecure fingerprint fetch; use https:// in production\",\n\t\t);\n\n\t\t// Fetch the fingerprint from the server.\n\t\t// TODO cancel the request if the effect is cancelled.\n\t\tconst fingerprint = await Promise.race([fetch(fingerprintUrl), cancel]);\n\t\tif (!fingerprint) return undefined;\n\n\t\tconst fingerprintText = await Promise.race([fingerprint.text(), cancel]);\n\t\tif (fingerprintText === undefined) return undefined;\n\n\t\thashes.push({ algorithm: \"sha-256\", value: Hex.toBytes(fingerprintText) });\n\n\t\tfinalUrl = new URL(url);\n\t\tfinalUrl.protocol = \"https:\";\n\t}\n\n\tif (hashes.length > 0) {\n\t\tfinalOptions.serverCertificateHashes = hashes;\n\t}\n\n\tconst quic = new WebTransport(finalUrl, finalOptions);\n\n\t// Both .ready and .closed reject on failure; catch .closed to avoid an unhandled rejection.\n\tquic.closed.catch(() => {});\n\n\t// Wait for the WebTransport to connect, or for the cancel promise to resolve.\n\t// Close the connection if we lost the race.\n\tconst loaded = await Promise.race([quic.ready.then(() => true), cancel]);\n\tif (!loaded) {\n\t\tquic.close();\n\t\treturn undefined;\n\t}\n\n\treturn quic;\n}\n\n// TODO accept arguments to control the port/path used.\nasync function connectWebSocket(url: URL, delay: number, cancel: Promise<void>): Promise<Session | undefined> {\n\tconst timer = new Promise<void>((resolve) => setTimeout(resolve, delay));\n\n\tconst active = await Promise.race([cancel, timer.then(() => true)]);\n\tif (!active) return undefined;\n\n\tconst quic = new Session(url);\n\n\t// Wait for the WebSocket to connect, or for the cancel promise to resolve.\n\t// `ready` rejects on a refused/failed connection, so a throw here is the caller's\n\t// cue to retry; a lost cancel race instead resolves and we close the loser.\n\tconst loaded = await Promise.race([quic.ready.then(() => true), cancel]);\n\tif (!loaded) {\n\t\tquic.close();\n\t\treturn undefined;\n\t}\n\n\treturn quic;\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { Signal } from "@norskvideo/moq-signals";
2
+ import type { Announced } from "../announced";
3
+ import type { Bandwidth } from "../bandwidth";
4
+ import type { Broadcast } from "../broadcast";
5
+ import type * as Path from "../path";
6
+ import type * as Time from "../time";
7
+ /** An established MoQ session, implemented by both the moq-lite and moq-ietf protocols. */
8
+ export interface Established {
9
+ /** URL of the connected server. */
10
+ readonly url: URL;
11
+ /** Negotiated wire protocol version. */
12
+ readonly version: string;
13
+ /** Estimated send bitrate from the congestion controller (if supported). */
14
+ readonly sendBandwidth?: Bandwidth;
15
+ /** Estimated receive bitrate from PROBE (moq-lite-03+ only). */
16
+ readonly recvBandwidth?: Bandwidth;
17
+ /** RTT in milliseconds from PROBE (moq-lite-04+ only). */
18
+ readonly rtt?: Signal<Time.Milli | undefined>;
19
+ /** Subscribe to broadcast announcements under an optional path prefix. */
20
+ announced(prefix?: Path.Valid): Announced;
21
+ /** Publish a broadcast at the given path. */
22
+ publish(path: Path.Valid, broadcast: Broadcast): void;
23
+ /** Consume the broadcast at the given path. */
24
+ consume(broadcast: Path.Valid): Broadcast;
25
+ /** Close the session. */
26
+ close(): void;
27
+ /** Resolves when the session closes. */
28
+ closed: Promise<void>;
29
+ }
30
+ //# sourceMappingURL=established.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"established.d.ts","sourceRoot":"","sources":["../../src/connection/established.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,KAAK,IAAI,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,KAAK,IAAI,MAAM,YAAY,CAAC;AAExC,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC3B,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;IAElB,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,4EAA4E;IAC5E,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAEnC,gEAAgE;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,CAAC;IAEnC,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAE9C,0EAA0E;IAC1E,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IAE1C,6CAA6C;IAC7C,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAEtD,+CAA+C;IAC/C,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IAE1C,yBAAyB;IACzB,KAAK,IAAI,IAAI,CAAC;IAEd,wCAAwC;IACxC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB"}
@@ -0,0 +1,3 @@
1
+ /* @ts-self-types="./established.d.ts" */
2
+ export {};
3
+ //# sourceMappingURL=established.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"established.js","sourceRoot":"","sources":["../../src/connection/established.ts"],"names":[],"mappings":"","sourcesContent":["import type { Signal } from \"@moq/signals\";\nimport type { Announced } from \"../announced.ts\";\nimport type { Bandwidth } from \"../bandwidth.ts\";\nimport type { Broadcast } from \"../broadcast.ts\";\nimport type * as Path from \"../path.ts\";\nimport type * as Time from \"../time.ts\";\n\n/** An established MoQ session, implemented by both the moq-lite and moq-ietf protocols. */\nexport interface Established {\n\t/** URL of the connected server. */\n\treadonly url: URL;\n\n\t/** Negotiated wire protocol version. */\n\treadonly version: string;\n\n\t/** Estimated send bitrate from the congestion controller (if supported). */\n\treadonly sendBandwidth?: Bandwidth;\n\n\t/** Estimated receive bitrate from PROBE (moq-lite-03+ only). */\n\treadonly recvBandwidth?: Bandwidth;\n\n\t/** RTT in milliseconds from PROBE (moq-lite-04+ only). */\n\treadonly rtt?: Signal<Time.Milli | undefined>;\n\n\t/** Subscribe to broadcast announcements under an optional path prefix. */\n\tannounced(prefix?: Path.Valid): Announced;\n\n\t/** Publish a broadcast at the given path. */\n\tpublish(path: Path.Valid, broadcast: Broadcast): void;\n\n\t/** Consume the broadcast at the given path. */\n\tconsume(broadcast: Path.Valid): Broadcast;\n\n\t/** Close the session. */\n\tclose(): void;\n\n\t/** Resolves when the session closes. */\n\tclosed: Promise<void>;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import * as Ietf from "../ietf/index";
2
+ import { Stream } from "../stream";
3
+ /**
4
+ * Draft-17+ SETUP exchange. Each side opens a uni stream, writes its Setup
5
+ * message, and reads the peer's Setup off an incoming uni stream. The two
6
+ * halves run in parallel and the protocol is symmetric, so both `connect`
7
+ * (client) and `accept` (server) use this same function.
8
+ */
9
+ export declare function exchangeSetup(transport: WebTransport, version: Ietf.IetfVersion, implementation: string): Promise<Stream>;
10
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/connection/handshake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAU,MAAM,EAAU,MAAM,cAAc,CAAC;AAEtD;;;;;GAKG;AACH,wBAAsB,aAAa,CAClC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,IAAI,CAAC,WAAW,EACzB,cAAc,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC,CAiBjB"}
@@ -0,0 +1,48 @@
1
+ /* @ts-self-types="./handshake.d.ts" */
2
+ import * as Ietf from "../ietf/index.js";
3
+ import { Reader, Stream, Writer } from "../stream.js";
4
+ /**
5
+ * Draft-17+ SETUP exchange. Each side opens a uni stream, writes its Setup
6
+ * message, and reads the peer's Setup off an incoming uni stream. The two
7
+ * halves run in parallel and the protocol is symmetric, so both `connect`
8
+ * (client) and `accept` (server) use this same function.
9
+ */
10
+ export async function exchangeSetup(transport, version, implementation) {
11
+ const encoder = new TextEncoder();
12
+ const params = new Ietf.SetupOptions();
13
+ params.setBytes(Ietf.SetupOption.Implementation, encoder.encode(implementation));
14
+ const setupMsg = new Ietf.Setup({ parameters: params });
15
+ const [sent, received] = await Promise.all([
16
+ sendSetup(transport, version, setupMsg),
17
+ receiveSetup(transport, version),
18
+ ]);
19
+ return new Stream({
20
+ writable: sent.writable,
21
+ readable: received.readable,
22
+ writer: sent.writer,
23
+ reader: received.reader,
24
+ });
25
+ }
26
+ async function sendSetup(transport, version, setupMsg) {
27
+ const writable = (await transport.createUnidirectionalStream());
28
+ const writer = new Writer(writable, version);
29
+ await writer.u53(Ietf.Setup.id); // 0x2F00 stream type
30
+ await setupMsg.encode(writer, version);
31
+ return { writable, writer };
32
+ }
33
+ async function receiveSetup(transport, version) {
34
+ const uniReader = transport.incomingUnidirectionalStreams.getReader();
35
+ const next = await uniReader.read();
36
+ uniReader.releaseLock();
37
+ if (next.done)
38
+ throw new Error("no incoming uni stream for SETUP");
39
+ const readable = next.value;
40
+ const reader = new Reader(readable, undefined, version);
41
+ const streamType = await reader.u53();
42
+ if (streamType !== Ietf.Setup.id) {
43
+ throw new Error(`unexpected stream type on setup uni: 0x${streamType.toString(16)}`);
44
+ }
45
+ await Ietf.Setup.decode(reader, version);
46
+ return { readable, reader };
47
+ }
48
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/connection/handshake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,SAAuB,EACvB,OAAyB,EACzB,cAAsB;IAEtB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;IACvC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IAExD,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC1C,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC;QACvC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC;KAChC,CAAC,CAAC;IAEH,OAAO,IAAI,MAAM,CAAC;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;KACvB,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,SAAS,CACvB,SAAuB,EACvB,OAAyB,EACzB,QAAoB;IAEpB,MAAM,QAAQ,GAAG,CAAC,MAAM,SAAS,CAAC,0BAA0B,EAAE,CAA+B,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB;IACtD,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,YAAY,CAC1B,SAAuB,EACvB,OAAyB;IAEzB,MAAM,SAAS,GAAG,SAAS,CAAC,6BAA6B,CAAC,SAAS,EAElE,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;IACpC,SAAS,CAAC,WAAW,EAAE,CAAC;IACxB,IAAI,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAEnE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAExD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACtC,IAAI,UAAU,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,0CAA0C,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC","sourcesContent":["import * as Ietf from \"../ietf/index.ts\";\nimport { Reader, Stream, Writer } from \"../stream.ts\";\n\n/**\n * Draft-17+ SETUP exchange. Each side opens a uni stream, writes its Setup\n * message, and reads the peer's Setup off an incoming uni stream. The two\n * halves run in parallel and the protocol is symmetric, so both `connect`\n * (client) and `accept` (server) use this same function.\n */\nexport async function exchangeSetup(\n\ttransport: WebTransport,\n\tversion: Ietf.IetfVersion,\n\timplementation: string,\n): Promise<Stream> {\n\tconst encoder = new TextEncoder();\n\tconst params = new Ietf.SetupOptions();\n\tparams.setBytes(Ietf.SetupOption.Implementation, encoder.encode(implementation));\n\tconst setupMsg = new Ietf.Setup({ parameters: params });\n\n\tconst [sent, received] = await Promise.all([\n\t\tsendSetup(transport, version, setupMsg),\n\t\treceiveSetup(transport, version),\n\t]);\n\n\treturn new Stream({\n\t\twritable: sent.writable,\n\t\treadable: received.readable,\n\t\twriter: sent.writer,\n\t\treader: received.reader,\n\t});\n}\n\nasync function sendSetup(\n\ttransport: WebTransport,\n\tversion: Ietf.IetfVersion,\n\tsetupMsg: Ietf.Setup,\n): Promise<{ writable: WritableStream<Uint8Array>; writer: Writer }> {\n\tconst writable = (await transport.createUnidirectionalStream()) as WritableStream<Uint8Array>;\n\tconst writer = new Writer(writable, version);\n\tawait writer.u53(Ietf.Setup.id); // 0x2F00 stream type\n\tawait setupMsg.encode(writer, version);\n\treturn { writable, writer };\n}\n\nasync function receiveSetup(\n\ttransport: WebTransport,\n\tversion: Ietf.IetfVersion,\n): Promise<{ readable: ReadableStream<Uint8Array>; reader: Reader }> {\n\tconst uniReader = transport.incomingUnidirectionalStreams.getReader() as ReadableStreamDefaultReader<\n\t\tReadableStream<Uint8Array>\n\t>;\n\tconst next = await uniReader.read();\n\tuniReader.releaseLock();\n\tif (next.done) throw new Error(\"no incoming uni stream for SETUP\");\n\n\tconst readable = next.value;\n\tconst reader = new Reader(readable, undefined, version);\n\n\tconst streamType = await reader.u53();\n\tif (streamType !== Ietf.Setup.id) {\n\t\tthrow new Error(`unexpected stream type on setup uni: 0x${streamType.toString(16)}`);\n\t}\n\tawait Ietf.Setup.decode(reader, version);\n\n\treturn { readable, reader };\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export * from "./accept";
2
+ export * from "./connect";
3
+ export * from "./established";
4
+ export * from "./reload";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ /* @ts-self-types="./index.d.ts" */
2
+ export * from "./accept.js";
3
+ export * from "./connect.js";
4
+ export * from "./established.js";
5
+ export * from "./reload.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./accept.ts\";\nexport * from \"./connect.ts\";\nexport * from \"./established.ts\";\nexport * from \"./reload.ts\";\n"]}
@@ -0,0 +1,47 @@
1
+ import { Effect, type Getter, Signal } from "@norskvideo/moq-signals";
2
+ import type * as Path from "../path";
3
+ import { type ConnectProps, type WebSocketOptions, type WebTransportProps } from "./connect";
4
+ import type { Established } from "./established";
5
+ /** Exponential backoff settings for {@link Reload}'s reconnect loop. */
6
+ export type ReloadDelay = {
7
+ initial: DOMHighResTimeStamp;
8
+ multiplier: number;
9
+ max: DOMHighResTimeStamp;
10
+ timeout?: DOMHighResTimeStamp;
11
+ };
12
+ /** Options for {@link Reload}: connect options plus reactive URL/enabled signals and backoff tuning. */
13
+ export type ReloadProps = ConnectProps & {
14
+ enabled?: boolean | Signal<boolean>;
15
+ url?: URL | Signal<URL | undefined>;
16
+ delay?: ReloadDelay;
17
+ };
18
+ /** Current state of a {@link Reload} connection. */
19
+ export type ReloadStatus = "connecting" | "connected" | "disconnected";
20
+ /** Maintains a MoQ connection, reconnecting with exponential backoff when it drops. */
21
+ export declare class Reload {
22
+ #private;
23
+ /** Relay URL to connect to; updating it triggers a reconnect. */
24
+ url: Signal<URL | undefined>;
25
+ /** Whether reconnecting is active. */
26
+ enabled: Signal<boolean>;
27
+ /** Current connection status. */
28
+ status: Signal<ReloadStatus>;
29
+ /** The currently established session, or undefined while disconnected. */
30
+ established: Signal<Established | undefined>;
31
+ /** The set of broadcast paths currently announced by the server, updated reactively. */
32
+ readonly announced: Getter<Set<Path.Valid>>;
33
+ /** WebTransport options applied to each connection attempt (not reactive). */
34
+ webtransport?: WebTransportProps;
35
+ /** WebSocket fallback options applied to each connection attempt (not reactive). */
36
+ websocket: WebSocketOptions | undefined;
37
+ /** Backoff settings for the reconnect loop. */
38
+ delay: ReloadDelay;
39
+ /** The reactive effect scope driving the connect loop; closed by {@link Reload.close}. */
40
+ signals: Effect;
41
+ /** Resolves when the reconnect loop stops via {@link Reload.close} or the retry timeout. */
42
+ closed: Promise<void>;
43
+ constructor(props?: ReloadProps);
44
+ /** Stop reconnecting, close the current connection, and resolve {@link Reload.closed}. */
45
+ close(): void;
46
+ }
47
+ //# sourceMappingURL=reload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reload.d.ts","sourceRoot":"","sources":["../../src/connection/reload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,KAAK,IAAI,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,KAAK,YAAY,EAAW,KAAK,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACzG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;IAGzB,OAAO,EAAE,mBAAmB,CAAC;IAI7B,UAAU,EAAE,MAAM,CAAC;IAInB,GAAG,EAAE,mBAAmB,CAAC;IAKzB,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC9B,CAAC;AAEF,wGAAwG;AACxG,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG;IAGxC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAGpC,GAAG,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;IAGpC,KAAK,CAAC,EAAE,WAAW,CAAC;CACpB,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAEvE,uFAAuF;AACvF,qBAAa,MAAM;;IAClB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC,GAAG,GAAG,SAAS,CAAC,CAAC;IAE7B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzB,iCAAiC;IACjC,MAAM,uBAA4C;IAElD,0EAA0E;IAC1E,WAAW,kCAAkD;IAK7D,wFAAwF;IACxF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAmB;IAE9D,8EAA8E;IAC9E,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAEjC,oFAAoF;IACpF,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAExC,+CAA+C;IAC/C,KAAK,EAAE,WAAW,CAAC;IAEnB,0FAA0F;IAC1F,OAAO,SAAgB;IAEvB,4FAA4F;IAC5F,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAYtB,YAAY,KAAK,CAAC,EAAE,WAAW,EAiB9B;IAgGD,0FAA0F;IAC1F,KAAK,SAGJ;CACD"}