@fails-components/webtransport 1.0.10 → 1.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 (57) hide show
  1. package/dist/lib/http2/browser/browser.d.ts.map +1 -1
  2. package/dist/lib/http2/browser/browserparser.d.ts +4 -3
  3. package/dist/lib/http2/browser/browserparser.d.ts.map +1 -1
  4. package/dist/lib/http2/node/capsuleparser.d.ts +3 -2
  5. package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -1
  6. package/dist/lib/http2/node/client.d.ts.map +1 -1
  7. package/dist/lib/http2/node/server.d.ts +4 -4
  8. package/dist/lib/http2/node/server.d.ts.map +1 -1
  9. package/dist/lib/http2/node/websocketparser.d.ts +7 -6
  10. package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
  11. package/dist/lib/http2/parserbase.d.ts +16 -4
  12. package/dist/lib/http2/parserbase.d.ts.map +1 -1
  13. package/dist/lib/http2/parserbasehttp2.d.ts +9 -1
  14. package/dist/lib/http2/parserbasehttp2.d.ts.map +1 -1
  15. package/dist/lib/http2/session.d.ts.map +1 -1
  16. package/dist/lib/index.node.d.ts +1 -1
  17. package/dist/lib/index.node.d.ts.map +1 -1
  18. package/dist/lib/index.types.d.ts +1 -1
  19. package/dist/lib/index.types.d.ts.map +1 -1
  20. package/dist/lib/server.d.ts.map +1 -1
  21. package/dist/lib/session.d.ts +8 -4
  22. package/dist/lib/session.d.ts.map +1 -1
  23. package/dist/lib/types.d.ts +18 -6
  24. package/dist/lib/types.d.ts.map +1 -1
  25. package/dist/lib/webtransport.browser.d.ts.map +1 -1
  26. package/dist/lib/webtransport.node.d.ts +1 -0
  27. package/dist/lib/webtransport.node.d.ts.map +1 -1
  28. package/lib/http2/browser/browser.js +2 -1
  29. package/lib/http2/browser/browserparser.js +43 -7
  30. package/lib/http2/node/capsuleparser.js +41 -10
  31. package/lib/http2/node/client.js +28 -7
  32. package/lib/http2/node/server.js +117 -55
  33. package/lib/http2/node/websocketparser.js +42 -20
  34. package/lib/http2/parserbase.js +44 -7
  35. package/lib/http2/parserbasehttp2.js +15 -2
  36. package/lib/http2/session.js +1 -5
  37. package/lib/index.node.js +1 -1
  38. package/lib/index.types.js +1 -1
  39. package/lib/server.js +7 -4
  40. package/lib/session.js +5 -2
  41. package/lib/types.ts +19 -6
  42. package/lib/webtransport.browser.js +3 -2
  43. package/lib/webtransport.node.js +16 -5
  44. package/lib/webtransportbase.js +1 -1
  45. package/old_test/test.js +13 -1
  46. package/package.json +3 -2
  47. package/readme.md +4 -1
  48. package/test/bidirectional-streams.spec.js +31 -0
  49. package/test/datagrams.spec.js +12 -0
  50. package/test/fixtures/quiche.browser.js +2 -0
  51. package/test/fixtures/quiche.js +1 -0
  52. package/test/fixtures/read-stream.js +20 -7
  53. package/test/fixtures/reader-value.js +0 -5
  54. package/test/fixtures/server.js +70 -0
  55. package/test/session.spec.js +29 -3
  56. package/test/stream-limits.spec.js +12 -0
  57. package/test/unidirectional-streams.spec.js +12 -0
@@ -30,6 +30,8 @@ export class ParserBase {
30
30
  static WT_STREAM_DATA_BLOCKED = 0x190b4d42
31
31
  static WT_STREAMS_BLOCKED_UNIDI = 0x190b4d43
32
32
  static WT_STREAMS_BLOCKED_BIDI = 0x190b4d44
33
+ static CLOSE_WEBTRANSPORT_SESSION = 0x2843
34
+ static DRAIN_WEBTRANSPORT_SESSION = 0x78ae
33
35
  static DATAGRAM = 0x00
34
36
 
35
37
  /**
@@ -38,7 +40,8 @@ export class ParserBase {
38
40
  constructor({
39
41
  nativesession,
40
42
  isclient,
41
- initialStreamSendWindowOffset,
43
+ initialStreamSendWindowOffsetBidi,
44
+ initialStreamSendWindowOffsetUnidi,
42
45
  initialStreamReceiveWindowOffset,
43
46
  streamShouldAutoTuneReceiveWindow,
44
47
  streamReceiveWindowSizeLimit
@@ -48,7 +51,8 @@ export class ParserBase {
48
51
  /** @type {boolean} */
49
52
  this.blocked = false
50
53
 
51
- this.initialStreamSendWindowOffset = initialStreamSendWindowOffset
54
+ this.initialStreamSendWindowOffsetUnidi = initialStreamSendWindowOffsetUnidi
55
+ this.initialStreamSendWindowOffsetBidi = initialStreamSendWindowOffsetBidi
52
56
  this.initialStreamReceiveWindowOffset = initialStreamReceiveWindowOffset
53
57
  this.streamShouldAutoTuneReceiveWindow = streamShouldAutoTuneReceiveWindow
54
58
  this.streamReceiveWindowSizeLimit = streamReceiveWindowSizeLimit
@@ -67,16 +71,31 @@ export class ParserBase {
67
71
 
68
72
  /**
69
73
  * @abstract
70
- * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined}} bs
74
+ * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined, end?: () => void}} bs
71
75
  */
72
- writeCapsule({ type, headerVints, payload }) {
76
+ writeCapsule({ type, headerVints, payload, end }) {
73
77
  throw new Error('Implement writeCapsule in derived Class')
74
78
  }
75
79
 
76
80
  /**
77
81
  * @param{{code: Number, reason: string}}arg
78
82
  */
79
- sendClose({ code, reason }) {}
83
+ sendClose({ code, reason }) {
84
+ const encoder = new TextEncoder()
85
+ const payload = encoder.encode('AAAA' + reason)
86
+ payload[0] = (code >> 24) & 0xff
87
+ payload[1] = (code >> 16) & 0xff
88
+ payload[2] = (code >> 8) & 0xff
89
+ payload[3] = code & 0xff
90
+ this.writeCapsule({
91
+ type: ParserBase.CLOSE_WEBTRANSPORT_SESSION,
92
+ headerVints: [],
93
+ payload,
94
+ end: () => {
95
+ this.closeHttp2Stream(code)
96
+ }
97
+ })
98
+ }
80
99
 
81
100
  /**
82
101
  * @param {bigint} streamid
@@ -101,12 +120,15 @@ export class ParserBase {
101
120
  return undefined
102
121
  }
103
122
  }
123
+ const unidirectional = !!(streamid & 0x2n)
104
124
  const stream = new Http2WebTransportStream({
105
125
  streamid,
106
- unidirectional: !!(streamid & 0x2n),
126
+ unidirectional,
107
127
  incoming,
108
128
  capsuleParser: this,
109
- sendWindowOffset: this.initialStreamSendWindowOffset,
129
+ sendWindowOffset: unidirectional
130
+ ? this.initialStreamSendWindowOffsetUnidi
131
+ : this.initialStreamSendWindowOffsetBidi,
110
132
  receiveWindowOffset: this.initialStreamReceiveWindowOffset,
111
133
  shouldAutoTuneReceiveWindow: this.streamShouldAutoTuneReceiveWindow,
112
134
  receiveWindowSizeLimit: this.streamReceiveWindowSizeLimit,
@@ -212,6 +234,21 @@ export class ParserBase {
212
234
  }
213
235
  }
214
236
 
237
+ /**
238
+ * @param {{code: number, reason: string}} opts
239
+ */
240
+ onCloseWebTransportSession({ code, reason }) {
241
+ this.session.jsobj.onClose({
242
+ errorcode: code,
243
+ error: reason
244
+ })
245
+ this.closeHttp2Stream(code) // is this necessary
246
+ }
247
+
248
+ onDrain() {
249
+ this.session.jsobj.onGoAwayReceived()
250
+ }
251
+
215
252
  /**
216
253
  * @param {number} code
217
254
  */
@@ -4,6 +4,17 @@ import { ParserBase } from './parserbase.js'
4
4
  * @typedef {import('node:http2').Http2Stream} Http2Stream
5
5
  */
6
6
 
7
+ /**
8
+ * @param{{offset: Number, buffer: Buffer, size: Number}} bs
9
+ */
10
+
11
+ export function readUint32(bs) {
12
+ if (bs.offset + 4 > bs.size) return undefined
13
+ const toret = bs.buffer.readUInt32BE(bs.offset)
14
+ bs.offset += 4
15
+ return toret
16
+ }
17
+
7
18
  /**
8
19
  * @param{{offset: Number, buffer: Buffer, size: Number}} bs
9
20
  */
@@ -63,7 +74,8 @@ export class ParserBaseHttp2 extends ParserBase {
63
74
  stream,
64
75
  nativesession,
65
76
  isclient,
66
- initialStreamSendWindowOffset,
77
+ initialStreamSendWindowOffsetBidi,
78
+ initialStreamSendWindowOffsetUnidi,
67
79
  initialStreamReceiveWindowOffset,
68
80
  streamShouldAutoTuneReceiveWindow,
69
81
  streamReceiveWindowSizeLimit
@@ -71,7 +83,8 @@ export class ParserBaseHttp2 extends ParserBase {
71
83
  super({
72
84
  nativesession,
73
85
  isclient,
74
- initialStreamSendWindowOffset,
86
+ initialStreamSendWindowOffsetBidi,
87
+ initialStreamSendWindowOffsetUnidi,
75
88
  initialStreamReceiveWindowOffset,
76
89
  streamShouldAutoTuneReceiveWindow,
77
90
  streamReceiveWindowSizeLimit
@@ -209,10 +209,7 @@ export class Http2WebTransportSession {
209
209
  */
210
210
  close({ code, reason }) {
211
211
  this.capsParser.sendClose({ code, reason }) // this includes for ws closing the session!
212
- // what to do with the reason
213
- if (this.stream) {
214
- this.capsParser.closeHttp2Stream(code)
215
- }
212
+ // should also close the stream
216
213
  }
217
214
 
218
215
  /**
@@ -252,7 +249,6 @@ export class Http2WebTransportSession {
252
249
  * @param {{ code: number, reason: string }} arg
253
250
  */
254
251
  closeConnection({ code, reason }) {
255
- console.trace()
256
252
  // called in case of failure in parsing or flowcontrol
257
253
  this.jsobj.onClose({
258
254
  errorcode: code,
package/lib/index.node.js CHANGED
@@ -25,4 +25,4 @@
25
25
 
26
26
  // also edit index.types.js
27
27
  export { HttpServer, Http3Server, Http2Server } from './server.js'
28
- export { WebTransport } from './webtransport.node.js'
28
+ export { WebTransport, quicheLoaded } from './webtransport.node.js'
@@ -25,7 +25,7 @@
25
25
 
26
26
  // both imports from the browser side and for nodes to generate a joint type file
27
27
  export { HttpServer, Http3Server, Http2Server } from './server.js'
28
- export { WebTransport } from './webtransport.node.js'
28
+ export { WebTransport, quicheLoaded } from './webtransport.node.js'
29
29
 
30
30
  export {
31
31
  WebTransportPonyfill,
package/lib/server.js CHANGED
@@ -82,8 +82,8 @@ class TransportIntServerProxy {
82
82
  }
83
83
 
84
84
  /**
85
- * @param {string} cert
86
- * @param {string} privKey
85
+ * @param {string|string[]} cert
86
+ * @param {string|string[]} privKey
87
87
  * @param {boolean} http2only
88
88
  * */
89
89
  updateCert(cert, privKey, http2only) {
@@ -245,16 +245,18 @@ export class HttpServer {
245
245
  * @param {ServerSessionRequestEvent} args
246
246
  */
247
247
  onSessionRequest(args) {
248
- if (args.promise && args.header) {
248
+ if ((args.promise || args.protocol !== 'http3:libquiche') && args.header) {
249
249
  if (!this.requestHandler) throw new Error('Request handler not set')
250
250
  this.requestHandler({ header: args.header })
251
251
  .then((/** @type {any} */ result) => {
252
252
  log('oSR', result)
253
- args.object.finishSessionRequest({
253
+ this.transportInt.finishSessionRequest({
254
254
  promise: args.promise,
255
255
  header: args.header,
256
256
  session: args.session,
257
257
  head: args.head,
258
+ transportPrivate: args.transportPrivate,
259
+ protocol: args.protocol,
258
260
  ...result
259
261
  })
260
262
  })
@@ -272,6 +274,7 @@ export class HttpServer {
272
274
  const sesobj = new HttpWTSession({
273
275
  object: args.session,
274
276
  header: args.header,
277
+ userData: args.userData ?? {},
275
278
  parentobj: this
276
279
  })
277
280
  args.session.jsobj = sesobj
package/lib/session.js CHANGED
@@ -50,7 +50,8 @@ export class HttpWTSession {
50
50
  * @param {object} args
51
51
  * @param {import('./types').NativeHttpWTSession} [args.object]
52
52
  * @param {HttpServer | HttpClient} args.parentobj
53
- * @param {any | undefined} [args.header= undefined]
53
+ * @param {Object | undefined} [args.header= undefined]
54
+ * @param {Object | undefined} [args.userData= undefined]
54
55
  */
55
56
  constructor(args) {
56
57
  if (args.object) {
@@ -68,8 +69,10 @@ export class HttpWTSession {
68
69
  this.readyResolve = null
69
70
  /** @type {(() => void) | null | undefined} */
70
71
  this.closeHook = null
71
- /** @type {(any | null | undefined)} */
72
+ /** @type {(Object | null | undefined)} */
72
73
  this.header = args.header
74
+ /** @type {(Object | null | undefined)} */
75
+ this.userData = args.userData
73
76
 
74
77
  /** @type {Promise<void>} */
75
78
  this.ready = new Promise((resolve, reject) => {
package/lib/types.ts CHANGED
@@ -4,7 +4,7 @@ import type {
4
4
  WebTransportOptions,
5
5
  WebTransportSendGroup
6
6
  } from './dom'
7
- import type { IncomingHttpHeaders, Http2Stream } from 'http2'
7
+ import type { IncomingHttpHeaders, Http2Stream, ServerHttp2Stream } from 'http2'
8
8
  import { ParserBase } from './http2/parserbase'
9
9
  import { Http2WebTransportSession } from './http2/session'
10
10
  import { HttpClient } from './client'
@@ -103,11 +103,14 @@ export interface ReadBuffer {
103
103
  }
104
104
 
105
105
  export interface NativeFinishSessionRequest {
106
+ path: string
106
107
  header: IncomingHttpHeaders
107
- session: Http2Stream
108
+ session: ServerHttp2Stream
108
109
  status: number
109
110
  protocol: 'capsule' | 'websocket' | 'websocketoverhttp1' | 'http3'
110
111
  head?: Buffer
112
+ userData?: object
113
+ transportPrivate?: object
111
114
  }
112
115
 
113
116
  export type Purpose =
@@ -219,6 +222,7 @@ export interface HttpWTServerSessionVisitorEvent
219
222
  extends HttpWTSessionVisitorEvent {
220
223
  path: string
221
224
  header: Object
225
+ userData?: Object | undefined
222
226
  }
223
227
 
224
228
  export interface ServerSessionRequestEvent {
@@ -228,6 +232,7 @@ export interface ServerSessionRequestEvent {
228
232
  session: any
229
233
  object?: any // the actual transport object itself, actually present on all messages, but required here
230
234
  protocol: string //'capsule' | 'websocket' | 'http3'
235
+ transportPrivate?: Object //private information from the transport object
231
236
  }
232
237
 
233
238
  /**
@@ -266,10 +271,17 @@ export interface WebTransportSessionImpl extends WebTransportSession {
266
271
  state: WebTransportSessionState
267
272
  }
268
273
 
274
+ export type QUICHE_LOG_OFF = -1
275
+ export type QUICHE_LOG_INFO = 0
276
+ export type QUICHE_LOG_WARNING = 1
277
+ export type QUICHE_LOG_ERROR = 2
278
+ export type QUICHE_LOG_FATAL = 3
279
+ export type QUICHE_LOG = QUICHE_LOG_OFF | QUICHE_LOG_INFO | QUICHE_LOG_WARNING | QUICHE_LOG_ERROR | QUICHE_LOG_FATAL
280
+
269
281
  export interface HttpWebTransportInit extends WebTransportOptions {
270
282
  host: string
271
283
  port: string | number
272
- quicheLogVerbose?: number
284
+ quicheLogVerbose?: QUICHE_LOG
273
285
  forceIpv6?: boolean
274
286
  localPort?: number
275
287
  }
@@ -284,8 +296,8 @@ export interface HttpServerInit extends HttpWebTransportInit {
284
296
  port: string | number
285
297
  host: string
286
298
  secret: string
287
- cert: string
288
- privKey: string
299
+ cert: string | string[]
300
+ privKey: string | string[]
289
301
  maxConnections?: number
290
302
  initialStreamFlowControlWindow?: number
291
303
  streamShouldAutoTuneReceiveWindow?: boolean
@@ -331,7 +343,8 @@ export type CreateParserFunction = (
331
343
  export interface ParserInit {
332
344
  isclient: boolean
333
345
  nativesession: any
334
- initialStreamSendWindowOffset: number
346
+ initialStreamSendWindowOffsetBidi: number
347
+ initialStreamSendWindowOffsetUnidi: number
335
348
  initialStreamReceiveWindowOffset: number
336
349
  streamShouldAutoTuneReceiveWindow: boolean
337
350
  streamReceiveWindowSizeLimit: number
@@ -61,9 +61,10 @@ export class WebTransportPonyfill extends WebTransportBase {
61
61
  * @param{{client: HttpClient, sessionint: HttpWTSession, ourl: URL}} args
62
62
  */
63
63
  startUpConnection({ client, sessionint, ourl }) {
64
+ const path = ourl.pathname + (ourl.search ?? '')
64
65
  client
65
- .handleConnection({ createTransport: true, path: ourl.pathname })
66
- .then(() => client.createWTSession(sessionint, ourl.pathname))
66
+ .handleConnection({ createTransport: true, path })
67
+ .then(() => client.createWTSession(sessionint, path))
67
68
  .catch((error) => {
68
69
  client.closeHookSession()
69
70
  sessionint.readyReject(error)
@@ -18,9 +18,13 @@ let Http3WebTransportClientSocket
18
18
  * @type {new (arg0: import("./types").HttpWebTransportInit) => any}
19
19
  */
20
20
  let Http3WebTransportClient
21
+ /**
22
+ * @type {boolean}
23
+ */
24
+ let quicheLoadedReady
21
25
  // @ts-ignore
22
26
  // eslint-disable-next-line no-unused-vars
23
- const quicheLoaded = new Promise((resolve, reject) => {
27
+ export const quicheLoaded = new Promise((resolve, reject) => {
24
28
  // @ts-ignore
25
29
  import('@fails-components/webtransport-transport-http3-quiche')
26
30
  .then(
@@ -39,6 +43,9 @@ const quicheLoaded = new Promise((resolve, reject) => {
39
43
  resolve(undefined)
40
44
  }
41
45
  )
46
+ .finally(() => {
47
+ quicheLoadedReady = true
48
+ })
42
49
  .catch((error) => {
43
50
  log('Problem loading http3-quiche transport', error)
44
51
  reject(error)
@@ -68,17 +75,18 @@ export class WebTransport extends WebTransportBase {
68
75
  * @param{{client: HttpClient, sessionint: HttpWTSession, ourl: URL}} args
69
76
  */
70
77
  startUpConnection({ client, sessionint, ourl }) {
78
+ const path = ourl.pathname + (ourl.search ?? '')
71
79
  client
72
- .handleConnection({ createTransport: true, path: ourl.pathname })
73
- .then(() => client.createWTSession(sessionint, ourl.pathname))
80
+ .handleConnection({ createTransport: true, path })
81
+ .then(() => client.createWTSession(sessionint, path))
74
82
  .catch((error) => {
75
83
  if (client.transportIntSwitchToReliable) {
76
84
  log('Connecting to unreliable failed:', error)
77
85
  log('Now switching to reliable')
78
86
  client.transportIntSwitchToReliable()
79
87
  client
80
- .handleConnection({ createTransport: false, path: ourl.pathname })
81
- .then(() => client.createWTSession(sessionint, ourl.pathname))
88
+ .handleConnection({ createTransport: false, path })
89
+ .then(() => client.createWTSession(sessionint, path))
82
90
  .catch((error) => {
83
91
  client.closeHookSession()
84
92
  sessionint.readyReject(error)
@@ -102,6 +110,9 @@ export class WebTransport extends WebTransportBase {
102
110
  */
103
111
  createClient(args) {
104
112
  let createUnreliableClient
113
+ // @ts-ignore
114
+ if (!quicheLoadedReady && !args?.forceReliable)
115
+ throw new Error('Lib quiche loading attempt did not end')
105
116
  if (checkQuicheInit) {
106
117
  createUnreliableClient = function (/** @type {any} */ _client) {
107
118
  if (
@@ -68,7 +68,7 @@ export class WebTransportBase {
68
68
  * @abstract
69
69
  */
70
70
  startUpConnection({ client, sessionint, ourl }) {
71
- throw new Error('Implement createClient')
71
+ throw new Error('Implement startUpConnection')
72
72
  }
73
73
 
74
74
  get reliability() {
package/old_test/test.js CHANGED
@@ -5,7 +5,12 @@
5
5
  // this file runs various tests
6
6
 
7
7
  import { generateWebTransportCertificate } from '../test/fixtures/certificate.js'
8
- import { Http3Server, Http2Server, WebTransport } from '../lib/index.node.js'
8
+ import {
9
+ Http3Server,
10
+ Http2Server,
11
+ WebTransport,
12
+ quicheLoaded
13
+ } from '../lib/index.node.js'
9
14
  import { echoTestsConnection, runEchoServer } from './testsuite.js'
10
15
 
11
16
  let http2 = false
@@ -15,6 +20,13 @@ if (process.argv.some((el) => el === 'http2')) {
15
20
  }
16
21
 
17
22
  async function run() {
23
+ if (
24
+ process.env.USE_HTTP2 !== 'true' &&
25
+ process.env.USE_PONYFILL !== 'true' &&
26
+ process.env.USE_POLYFILL !== 'true'
27
+ ) {
28
+ await quicheLoaded
29
+ }
18
30
  console.log('try connecting to server that does not exist')
19
31
  const badClient = new WebTransport('https://127.0.0.1:49823/echo', {
20
32
  serverCertificateHashes: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fails-components/webtransport",
3
- "version": "1.0.10",
3
+ "version": "1.1.0",
4
4
  "description": "A component to add webtransport support (server and client) to node.js using libquiche",
5
5
  "exports": {
6
6
  ".": {
@@ -43,7 +43,7 @@
43
43
  "bugs": {
44
44
  "url": "https://github.com/fails-components/webtransport/issues"
45
45
  },
46
- "homepage": "https://github.com/fails-components/webtransport/main/#readme",
46
+ "homepage": "https://github.com/fails-components/webtransport/tree/master/main#readme",
47
47
  "dependencies": {
48
48
  "@types/debug": "^4.1.7",
49
49
  "bindings": "^1.5.0",
@@ -83,6 +83,7 @@
83
83
  },
84
84
  "browser": {
85
85
  "./test/fixtures/webtransport.js": "./test/fixtures/webtransport.browser.js",
86
+ "./test/fixtures/quiche.js": "./test/fixtures/quiche.browser.js",
86
87
  "./lib/webstreams.js": "./lib/webstreams.browser.js"
87
88
  }
88
89
  }
package/readme.md CHANGED
@@ -35,7 +35,7 @@ Note, that it is only partially implemented and also some http2 features require
35
35
  Use for the server side either `HttpServer` for http/2 and http/3 support (including WebTransport over WebSocket), Http3Server for http/3 only and Http2Server for http/2 only.
36
36
  For the client side pass the options `forceReliable`, `requireUnreliable` etc. to force/select a type of transport.
37
37
 
38
- **Note, that the http/2 webtransport protocol (not supported by any browser yet), is implemented without the support of receiving initial settings, which can cause a problem with the initial flow control of the WebTransport streams. This is a current limitation in node.js, but a patch to add support will soon be part of new node versions. Note, that for sending the required initial setting you need a very recent version (>=21.6.1) of node.js.** This limitation does not apply to the polyfill/ponyfill and the webtransport over websocket protocol.
38
+ **Note, that the http/2 webtransport protocol (not supported by any browser yet), requires support for sending and receiving initial a node version with support for receiving customSettings. Versions without support can cause a problem with the initial flow control of the WebTransport streams. Note, that for sending and receiving the required initial setting you need a very recent version (>=21.6.1 sending >= 21.7.0 receiving) of node.js.** This limitation does not apply to the polyfill/ponyfill and the webtransport over websocket protocol.
39
39
 
40
40
  The current proposal for the WebTransport over WebSocket protocol can be found [here] (https://datatracker.ietf.org/doc/draft-richter-webtransport-websocket/).
41
41
 
@@ -126,6 +126,9 @@ The `WebTransportSession` should behave most like a WebTransport object on the b
126
126
  ### Using a client
127
127
  The `WebTransport` object for node or the `WebTransportPonyfill`, `WebTransportPolyfill` objects for the browser, behave like the `WebTransport` client object for the browser with few additions and some missing features.
128
128
 
129
+ As the http/3 package is loaded dynamically and the WebTransport object is created synchronously, you may want to make sure, that the modules are already loaded. You can do so, by waiting for the promise `quicheLoaded` exported by the package.
130
+
131
+
129
132
  ## Specification divergence
130
133
 
131
134
  This module implements parts of the [WebTransport spec](https://datatracker.ietf.org/doc/html/draft-vvv-webtransport-quic-00) but not all of it.
@@ -6,6 +6,7 @@ import { readStream } from './fixtures/read-stream.js'
6
6
  import { writeStream } from './fixtures/write-stream.js'
7
7
  import { readCertHash } from './fixtures/read-cert-hash.js'
8
8
  import WebTransport from './fixtures/webtransport.js'
9
+ import { quicheLoaded } from './fixtures/quiche.js'
9
10
  import * as ui8 from 'uint8arrays'
10
11
  import {
11
12
  KNOWN_BYTES,
@@ -38,6 +39,17 @@ describe('bidirectional streams', function () {
38
39
  // @ts-ignore
39
40
  delete wtOptions.serverCertificateHashes
40
41
 
42
+ // @ts-ignore
43
+ beforeEach(async () => {
44
+ if (
45
+ process.env.USE_HTTP2 !== 'true' &&
46
+ process.env.USE_PONYFILL !== 'true' &&
47
+ process.env.USE_POLYFILL !== 'true'
48
+ ) {
49
+ await quicheLoaded
50
+ }
51
+ })
52
+
41
53
  // @ts-ignore
42
54
  afterEach(async () => {
43
55
  if (client != null) {
@@ -116,4 +128,23 @@ describe('bidirectional streams', function () {
116
128
  expect(result).to.have.property('reason', '')
117
129
  expect(result).to.have.property('closeCode', 0)
118
130
  })
131
+
132
+ it('receives data from server and checks receiving stream fin', async () => {
133
+ // client context - waits for the server to open a bidi stream then pipes it back to them
134
+ client = new WebTransport(
135
+ `${process.env.SERVER_URL}/bidirectional_server_fin_send`,
136
+ wtOptions
137
+ )
138
+ await client.ready
139
+
140
+ const bidiStream = await getReaderValue(client.incomingBidirectionalStreams)
141
+ const reader = bidiStream.readable.getReader()
142
+
143
+ while (true) {
144
+ const res = await reader.read()
145
+ if (res.done) {
146
+ break
147
+ }
148
+ }
149
+ })
119
150
  })
@@ -5,6 +5,7 @@ import { expect } from './fixtures/chai.js'
5
5
  import { readStream } from './fixtures/read-stream.js'
6
6
  import { readCertHash } from './fixtures/read-cert-hash.js'
7
7
  import { pTimeout } from './fixtures/p-timeout.js'
8
+ import { quicheLoaded } from './fixtures/quiche.js'
8
9
 
9
10
  /**
10
11
  * @template T
@@ -32,6 +33,17 @@ describe('datagrams', function () {
32
33
  // @ts-ignore
33
34
  delete wtOptions.serverCertificateHashes
34
35
 
36
+ // @ts-ignore
37
+ beforeEach(async () => {
38
+ if (
39
+ process.env.USE_HTTP2 !== 'true' &&
40
+ process.env.USE_PONYFILL !== 'true' &&
41
+ process.env.USE_POLYFILL !== 'true'
42
+ ) {
43
+ await quicheLoaded
44
+ }
45
+ })
46
+
35
47
  // @ts-ignore
36
48
  afterEach(async () => {
37
49
  if (client != null) {
@@ -0,0 +1,2 @@
1
+ const quicheLoaded = Promise.resolve()
2
+ export { quicheLoaded }
@@ -0,0 +1 @@
1
+ export { quicheLoaded } from '../../lib/index.node.js'
@@ -1,15 +1,10 @@
1
- /**
2
- * @template T
3
- * @typedef {import('../../lib/webstreams').ReadableStream<T>} ReadableStream<T>
4
- */
5
-
6
1
  /**
7
2
  * Read a stream contents to the end and return it
8
3
  *
9
4
  * @template T
10
5
  * @param {ReadableStream<T>} readable
11
6
  * @param {number} [expected]
12
- * @returns
7
+ * @returns {T[]}
13
8
  */
14
9
  export async function readStream(readable, expected) {
15
10
  const reader = readable.getReader()
@@ -27,7 +22,6 @@ export async function readStream(readable, expected) {
27
22
  }
28
23
 
29
24
  if (value != null) {
30
- // @ts-ignore
31
25
  outputlength += value.length
32
26
  output.push(value)
33
27
  }
@@ -42,3 +36,22 @@ export async function readStream(readable, expected) {
42
36
  reader.releaseLock()
43
37
  }
44
38
  }
39
+
40
+ /**
41
+ * @param {ReadableStream<Uint8Array>} stream
42
+ * @returns {string}
43
+ */
44
+ export async function readStringFromStream(stream) {
45
+ const decoder = new TextDecoder()
46
+ let output = ''
47
+
48
+ for (const buf of await readStream(stream)) {
49
+ output += decoder.decode(buf, {
50
+ stream: true
51
+ })
52
+ }
53
+
54
+ output += decoder.decode()
55
+
56
+ return output
57
+ }
@@ -1,8 +1,3 @@
1
- /**
2
- * @template T
3
- * @typedef {import('../../lib/webstreams').ReadableStream<T>} ReadableStream<T>
4
- */
5
-
6
1
  /**
7
2
  * @template T
8
3
  * @param {ReadableStream<T>} readableStream