@norskvideo/moq-net 0.1.8 → 0.2.1

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