@fails-components/webtransport 1.0.10 → 1.0.11

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 (47) 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.map +1 -1
  8. package/dist/lib/http2/node/websocketparser.d.ts +7 -6
  9. package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
  10. package/dist/lib/http2/parserbase.d.ts +16 -4
  11. package/dist/lib/http2/parserbase.d.ts.map +1 -1
  12. package/dist/lib/http2/parserbasehttp2.d.ts +9 -1
  13. package/dist/lib/http2/parserbasehttp2.d.ts.map +1 -1
  14. package/dist/lib/http2/session.d.ts.map +1 -1
  15. package/dist/lib/index.node.d.ts +1 -1
  16. package/dist/lib/index.node.d.ts.map +1 -1
  17. package/dist/lib/index.types.d.ts +1 -1
  18. package/dist/lib/index.types.d.ts.map +1 -1
  19. package/dist/lib/types.d.ts +9 -2
  20. package/dist/lib/types.d.ts.map +1 -1
  21. package/dist/lib/webtransport.node.d.ts +1 -0
  22. package/dist/lib/webtransport.node.d.ts.map +1 -1
  23. package/lib/http2/browser/browser.js +2 -1
  24. package/lib/http2/browser/browserparser.js +43 -7
  25. package/lib/http2/node/capsuleparser.js +41 -10
  26. package/lib/http2/node/client.js +23 -5
  27. package/lib/http2/node/server.js +53 -16
  28. package/lib/http2/node/websocketparser.js +42 -20
  29. package/lib/http2/parserbase.js +44 -7
  30. package/lib/http2/parserbasehttp2.js +15 -2
  31. package/lib/http2/session.js +1 -5
  32. package/lib/index.node.js +1 -1
  33. package/lib/index.types.js +1 -1
  34. package/lib/types.ts +10 -2
  35. package/lib/webtransport.node.js +10 -1
  36. package/lib/webtransportbase.js +1 -1
  37. package/old_test/test.js +13 -1
  38. package/package.json +3 -2
  39. package/readme.md +4 -1
  40. package/test/bidirectional-streams.spec.js +31 -0
  41. package/test/datagrams.spec.js +12 -0
  42. package/test/fixtures/quiche.browser.js +2 -0
  43. package/test/fixtures/quiche.js +1 -0
  44. package/test/fixtures/server.js +24 -0
  45. package/test/session.spec.js +13 -3
  46. package/test/stream-limits.spec.js +12 -0
  47. package/test/unidirectional-streams.spec.js +12 -0
@@ -65,7 +65,8 @@ export class Http2WebTransportServer {
65
65
  0x2b63: this.initialStreamFlowControlWindow, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAM_DATA_BIDI
66
66
  0x2b64: this.initialUnidirectionalStreams, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI
67
67
  0x2b65: this.initialBidirectionalStreams // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI
68
- }
68
+ },
69
+ remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65]
69
70
  }
70
71
  })
71
72
 
@@ -153,7 +154,8 @@ export class Http2WebTransportServer {
153
154
  stream,
154
155
  nativesession,
155
156
  isclient: false,
156
- initialStreamSendWindowOffset: 0,
157
+ initialStreamSendWindowOffsetBidi: 0,
158
+ initialStreamSendWindowOffsetUnidi: 0,
157
159
  initialStreamReceiveWindowOffset:
158
160
  this.initialStreamFlowControlWindow,
159
161
  streamShouldAutoTuneReceiveWindow:
@@ -244,6 +246,14 @@ export class Http2WebTransportServer {
244
246
  path = path?.slice(1)
245
247
  }
246
248
  if (this.paths[path]) {
249
+ const {
250
+ 0x2b65: remoteBidirectionalStreams = undefined,
251
+ 0x2b64: remoteUnidirectionalStreams = undefined,
252
+ 0x2b63: remoteBidirectionalStreamFlowControlWindow = undefined,
253
+ 0x2b62: remoteUnidirectionalStreamFlowControlWindow = undefined,
254
+ 0x2b61: remoteSessionFlowControlWindow = undefined
255
+ // @ts-ignore
256
+ } = stream?.session?.remoteSettings?.customSettings || {}
247
257
  const retObj = {
248
258
  session: new Http2WebTransportSession({
249
259
  stream,
@@ -256,8 +266,12 @@ export class Http2WebTransportServer {
256
266
  stream,
257
267
  nativesession,
258
268
  isclient: false,
259
- initialStreamSendWindowOffset:
260
- this.initialStreamFlowControlWindow, // TODO, once supported by node, use initial settings
269
+ initialStreamSendWindowOffsetBidi:
270
+ remoteBidirectionalStreamFlowControlWindow ||
271
+ this.initialStreamFlowControlWindow,
272
+ initialStreamSendWindowOffsetUnidi:
273
+ remoteUnidirectionalStreamFlowControlWindow ||
274
+ this.initialStreamFlowControlWindow,
261
275
  initialStreamReceiveWindowOffset:
262
276
  this.initialStreamFlowControlWindow,
263
277
  streamShouldAutoTuneReceiveWindow:
@@ -270,7 +284,8 @@ export class Http2WebTransportServer {
270
284
  stream,
271
285
  nativesession,
272
286
  isclient: false,
273
- initialStreamSendWindowOffset: 0,
287
+ initialStreamSendWindowOffsetBidi: 0,
288
+ initialStreamSendWindowOffsetUnidi: 0,
274
289
  initialStreamReceiveWindowOffset:
275
290
  this.initialStreamFlowControlWindow,
276
291
  streamShouldAutoTuneReceiveWindow:
@@ -280,18 +295,20 @@ export class Http2WebTransportServer {
280
295
  }))
281
296
  }
282
297
  },
283
- initialBidirectionalSendStreams: websocketProt // TODO, once supported by node, use initial settings
298
+ initialBidirectionalSendStreams: websocketProt
284
299
  ? 0
285
- : this.initialBidirectionalStreams,
300
+ : remoteBidirectionalStreams || this.initialBidirectionalStreams,
286
301
  initialBidirectionalReceiveStreams:
287
302
  this.initialBidirectionalStreams,
288
303
  initialUnidirectionalSendStreams: websocketProt
289
304
  ? 0
290
- : this.initialUnidirectionalStreams, // TODO, once supported by node, use initial settings
305
+ : remoteUnidirectionalStreams ||
306
+ this.initialUnidirectionalStreams,
291
307
  initialUnidirectionalReceiveStreams:
292
308
  this.initialUnidirectionalStreams,
293
309
  sendWindowOffset: !websocketProt
294
- ? this.sessionFlowControlWindowSizeLimit
310
+ ? remoteSessionFlowControlWindow ||
311
+ this.sessionFlowControlWindowSizeLimit
295
312
  : 0, // TODO, once supported by node, use initial settings
296
313
  receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
297
314
  shouldAutoTuneReceiveWindow:
@@ -443,7 +460,8 @@ export class Http2WebTransportServer {
443
460
  stream,
444
461
  nativesession,
445
462
  isclient: false,
446
- initialStreamSendWindowOffset: 0,
463
+ initialStreamSendWindowOffsetBidi: 0,
464
+ initialStreamSendWindowOffsetUnidi: 0,
447
465
  initialStreamReceiveWindowOffset:
448
466
  this.initialStreamFlowControlWindow,
449
467
  streamShouldAutoTuneReceiveWindow:
@@ -476,6 +494,14 @@ export class Http2WebTransportServer {
476
494
  log('sendHttp1Headers error', error)
477
495
  })
478
496
  } else {
497
+ const {
498
+ 0x2b65: remoteBidirectionalStreams = undefined,
499
+ 0x2b64: remoteUnidirectionalStreams = undefined,
500
+ 0x2b63: remoteBidirectionalStreamFlowControlWindow = undefined,
501
+ 0x2b62: remoteUnidirectionalStreamFlowControlWindow = undefined,
502
+ 0x2b61: remoteSessionFlowControlWindow = undefined
503
+ // @ts-ignore
504
+ } = stream?.session?.remoteSettings?.customSettings || {}
479
505
  const retObj = {
480
506
  session: new Http2WebTransportSession({
481
507
  stream,
@@ -488,8 +514,12 @@ export class Http2WebTransportServer {
488
514
  stream,
489
515
  nativesession,
490
516
  isclient: false,
491
- initialStreamSendWindowOffset:
492
- this.initialStreamFlowControlWindow, // TODO, once supported by node, use initial settings
517
+ initialStreamSendWindowOffsetBidi:
518
+ remoteBidirectionalStreamFlowControlWindow ||
519
+ this.initialStreamFlowControlWindow,
520
+ initialStreamSendWindowOffsetUnidi:
521
+ remoteUnidirectionalStreamFlowControlWindow ||
522
+ this.initialStreamFlowControlWindow,
493
523
  initialStreamReceiveWindowOffset:
494
524
  this.initialStreamFlowControlWindow,
495
525
  streamShouldAutoTuneReceiveWindow:
@@ -502,7 +532,8 @@ export class Http2WebTransportServer {
502
532
  stream,
503
533
  nativesession,
504
534
  isclient: false,
505
- initialStreamSendWindowOffset: 0,
535
+ initialStreamSendWindowOffsetBidi: 0,
536
+ initialStreamSendWindowOffsetUnidi: 0,
506
537
  initialStreamReceiveWindowOffset:
507
538
  this.initialStreamFlowControlWindow,
508
539
  streamShouldAutoTuneReceiveWindow:
@@ -513,16 +544,22 @@ export class Http2WebTransportServer {
513
544
  }
514
545
  },
515
546
  initialBidirectionalSendStreams:
516
- protocol !== 'websocket' ? this.initialBidirectionalStreams : 0, // TODO, once supported by node, use initial settings
547
+ protocol !== 'websocket'
548
+ ? remoteBidirectionalStreams || this.initialBidirectionalStreams
549
+ : 0, // TODO, once supported by node, use initial settings
517
550
  initialBidirectionalReceiveStreams:
518
551
  this.initialBidirectionalStreams,
519
552
  initialUnidirectionalSendStreams:
520
- protocol !== 'websocket' ? this.initialUnidirectionalStreams : 0, // TODO, once supported by node, use initial settings
553
+ protocol !== 'websocket'
554
+ ? remoteUnidirectionalStreams ||
555
+ this.initialUnidirectionalStreams
556
+ : 0, // TODO, once supported by node, use initial settings
521
557
  initialUnidirectionalReceiveStreams:
522
558
  this.initialUnidirectionalStreams,
523
559
  sendWindowOffset:
524
560
  protocol !== 'websocket'
525
- ? this.sessionFlowControlWindowSizeLimit
561
+ ? remoteSessionFlowControlWindow ||
562
+ this.sessionFlowControlWindowSizeLimit
526
563
  : 0, // TODO, once supported by node, use initial settings
527
564
  receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
528
565
  shouldAutoTuneReceiveWindow:
@@ -1,6 +1,11 @@
1
1
  import { randomBytes } from 'node:crypto'
2
2
  import { ParserBase, lengthVarInt } from '../parserbase.js'
3
- import { ParserBaseHttp2, readVarInt, writeVarInt } from '../parserbasehttp2.js'
3
+ import {
4
+ ParserBaseHttp2,
5
+ readVarInt,
6
+ writeVarInt,
7
+ readUint32
8
+ } from '../parserbasehttp2.js'
4
9
  import { logger } from '../../utils.js'
5
10
 
6
11
  const log = logger(`webtransport:http2:node:websocketparser(${process?.pid})`)
@@ -174,7 +179,8 @@ export class WebSocketParser extends ParserBaseHttp2 {
174
179
  stream,
175
180
  nativesession,
176
181
  isclient,
177
- initialStreamSendWindowOffset,
182
+ initialStreamSendWindowOffsetUnidi,
183
+ initialStreamSendWindowOffsetBidi,
178
184
  initialStreamReceiveWindowOffset,
179
185
  streamShouldAutoTuneReceiveWindow,
180
186
  streamReceiveWindowSizeLimit
@@ -183,7 +189,8 @@ export class WebSocketParser extends ParserBaseHttp2 {
183
189
  stream,
184
190
  nativesession,
185
191
  isclient,
186
- initialStreamSendWindowOffset,
192
+ initialStreamSendWindowOffsetUnidi,
193
+ initialStreamSendWindowOffsetBidi,
187
194
  initialStreamReceiveWindowOffset,
188
195
  streamShouldAutoTuneReceiveWindow,
189
196
  streamReceiveWindowSizeLimit
@@ -598,6 +605,23 @@ export class WebSocketParser extends ParserBaseHttp2 {
598
605
  case ParserBase.WT_STREAMS_BLOCKED_BIDI:
599
606
  this.onStreamsBlockedBidi(readVarInt(bufferstate))
600
607
  break
608
+ case ParserBase.CLOSE_WEBTRANSPORT_SESSION:
609
+ {
610
+ const code = readUint32(bufferstate) || 0
611
+ const decoder = new TextDecoder()
612
+ const reason = decoder.decode(
613
+ new Uint8Array(
614
+ bufferstate.buffer.buffer,
615
+ bufferstate.buffer.byteOffset + bufferstate.offset,
616
+ offsetend - bufferstate.offset
617
+ )
618
+ )
619
+ this.onCloseWebTransportSession({ code, reason })
620
+ }
621
+ break
622
+ case ParserBase.DRAIN_WEBTRANSPORT_SESSION:
623
+ this.onDrain()
624
+ break
601
625
  case ParserBase.DATAGRAM:
602
626
  if (wbufferstate) {
603
627
  this.session.jsobj.onDatagramReceived({
@@ -688,19 +712,12 @@ export class WebSocketParser extends ParserBaseHttp2 {
688
712
  * @param{{code: Number, reason: string}}arg
689
713
  */
690
714
  sendClose({ code, reason }) {
691
- const reasBuf = Buffer.from(
692
- (code || 0).toString() + ':' + (reason || ''),
693
- 'utf8'
694
- )
695
- const wbuf = Buffer.allocUnsafe(2 + reasBuf.byteLength)
696
- const errorcode = code === 0 ? 1000 : 1003
697
- wbuf.writeUint16BE(errorcode, 0)
698
- reasBuf.copy(wbuf, 2)
699
- this.sendCloseInt(wbuf)
715
+ super.sendClose({ code, reason })
716
+ this.sendCloseInt()
700
717
  }
701
718
 
702
719
  /**
703
- * @param {Uint8Array} payload
720
+ * @param {Uint8Array} [payload]
704
721
  */
705
722
  sendCloseInt(payload) {
706
723
  this.writeWSFrame({ opcode: WebSocketParser.WS_CLOSE, payload })
@@ -715,9 +732,9 @@ export class WebSocketParser extends ParserBaseHttp2 {
715
732
  }
716
733
 
717
734
  /**
718
- * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined}} bs
735
+ * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined, end?: () => void}} bs
719
736
  */
720
- writeCapsule({ type, headerVints, payload }) {
737
+ writeCapsule({ type, headerVints, payload, end }) {
721
738
  let plength = 0
722
739
  for (const ind in headerVints) plength += lengthVarInt(headerVints[ind])
723
740
  plength += lengthVarInt(type)
@@ -762,8 +779,13 @@ export class WebSocketParser extends ParserBaseHttp2 {
762
779
  payload.byteLength
763
780
  )
764
781
  }
765
- let blocked = !this.stream.write(cdata)
766
- if (payload) blocked = !this.stream.write(payload) || blocked
782
+ let blocked = false
783
+ if (payload) {
784
+ blocked = !this.stream.write(cdata)
785
+ blocked = !this.stream.write(payload, end) || blocked
786
+ } else {
787
+ blocked = !this.stream.write(cdata, end)
788
+ }
767
789
  // do something if blocked
768
790
  if (blocked) this.blocked = true
769
791
  return blocked
@@ -802,10 +824,10 @@ export class WebSocketParser extends ParserBaseHttp2 {
802
824
  }
803
825
 
804
826
  /**
805
- * @param {{ opcode: number; payload: Uint8Array; }} args
827
+ * @param {{ opcode: number; payload: Uint8Array|undefined; }} args
806
828
  */
807
829
  writeWSFrame({ opcode, payload }) {
808
- const plength = payload.byteLength
830
+ const plength = payload?.byteLength || 0
809
831
  let headlength = 2
810
832
  let mask = 0
811
833
  if (this.isclient) {
@@ -825,7 +847,7 @@ export class WebSocketParser extends ParserBaseHttp2 {
825
847
  throw new Error('Headlength does not match pos')
826
848
 
827
849
  let blocked = !this.stream.write(cdata)
828
- if (maskstate)
850
+ if (maskstate && payload)
829
851
  applyMask(
830
852
  maskstate,
831
853
  Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength),
@@ -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/types.ts CHANGED
@@ -266,10 +266,17 @@ export interface WebTransportSessionImpl extends WebTransportSession {
266
266
  state: WebTransportSessionState
267
267
  }
268
268
 
269
+ export type QUICHE_LOG_OFF = -1
270
+ export type QUICHE_LOG_INFO = 0
271
+ export type QUICHE_LOG_WARNING = 1
272
+ export type QUICHE_LOG_ERROR = 2
273
+ export type QUICHE_LOG_FATAL = 3
274
+ export type QUICHE_LOG = QUICHE_LOG_OFF | QUICHE_LOG_INFO | QUICHE_LOG_WARNING | QUICHE_LOG_ERROR | QUICHE_LOG_FATAL
275
+
269
276
  export interface HttpWebTransportInit extends WebTransportOptions {
270
277
  host: string
271
278
  port: string | number
272
- quicheLogVerbose?: number
279
+ quicheLogVerbose?: QUICHE_LOG
273
280
  forceIpv6?: boolean
274
281
  localPort?: number
275
282
  }
@@ -331,7 +338,8 @@ export type CreateParserFunction = (
331
338
  export interface ParserInit {
332
339
  isclient: boolean
333
340
  nativesession: any
334
- initialStreamSendWindowOffset: number
341
+ initialStreamSendWindowOffsetBidi: number
342
+ initialStreamSendWindowOffsetUnidi: number
335
343
  initialStreamReceiveWindowOffset: number
336
344
  streamShouldAutoTuneReceiveWindow: boolean
337
345
  streamReceiveWindowSizeLimit: number
@@ -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)
@@ -102,6 +109,8 @@ export class WebTransport extends WebTransportBase {
102
109
  */
103
110
  createClient(args) {
104
111
  let createUnreliableClient
112
+ if (!quicheLoadedReady)
113
+ throw new Error('Lib quiche loading attempt did not end')
105
114
  if (checkQuicheInit) {
106
115
  createUnreliableClient = function (/** @type {any} */ _client) {
107
116
  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.0.11",
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
  })