@fails-components/webtransport 0.2.1 → 0.3.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 (101) hide show
  1. package/CMakeLists.txt +3 -0
  2. package/dist/lib/client.d.ts +7 -3
  3. package/dist/lib/client.d.ts.map +1 -1
  4. package/dist/lib/dom.d.ts +15 -4
  5. package/dist/lib/dom.d.ts.map +1 -1
  6. package/dist/lib/error.d.ts +8 -0
  7. package/dist/lib/error.d.ts.map +1 -0
  8. package/dist/lib/event-loop.d.ts +4 -2
  9. package/dist/lib/event-loop.d.ts.map +1 -1
  10. package/dist/lib/native.d.ts.map +1 -1
  11. package/dist/lib/server.d.ts +5 -2
  12. package/dist/lib/server.d.ts.map +1 -1
  13. package/dist/lib/session.d.ts +36 -23
  14. package/dist/lib/session.d.ts.map +1 -1
  15. package/dist/lib/stream.d.ts +15 -4
  16. package/dist/lib/stream.d.ts.map +1 -1
  17. package/dist/lib/transport.d.ts +12 -3
  18. package/dist/lib/transport.d.ts.map +1 -1
  19. package/dist/lib/types.d.ts +43 -4
  20. package/dist/lib/types.d.ts.map +1 -1
  21. package/dist/lib/utils.d.ts +5 -0
  22. package/dist/lib/utils.d.ts.map +1 -1
  23. package/dist/lib/webtransport.d.ts +11 -21
  24. package/dist/lib/webtransport.d.ts.map +1 -1
  25. package/dist/test/datagrams.spec.d.ts.map +1 -1
  26. package/dist/test/event-loop.node.d.ts +2 -0
  27. package/dist/test/event-loop.node.d.ts.map +1 -0
  28. package/dist/test/fixtures/chai.d.ts.map +1 -1
  29. package/dist/test/fixtures/known-bytes.d.ts +6 -0
  30. package/dist/test/fixtures/known-bytes.d.ts.map +1 -0
  31. package/dist/test/fixtures/read-cert-hash.d.ts +6 -0
  32. package/dist/test/fixtures/read-cert-hash.d.ts.map +1 -0
  33. package/dist/test/fixtures/read-stream.d.ts +10 -1
  34. package/dist/test/fixtures/read-stream.d.ts.map +1 -1
  35. package/dist/test/fixtures/reader-value.d.ts +16 -1
  36. package/dist/test/fixtures/reader-value.d.ts.map +1 -1
  37. package/dist/test/fixtures/server.d.ts +0 -5
  38. package/dist/test/fixtures/server.d.ts.map +1 -1
  39. package/dist/test/fixtures/webtransport.browser.d.ts +3 -0
  40. package/dist/test/fixtures/webtransport.browser.d.ts.map +1 -0
  41. package/dist/test/fixtures/webtransport.d.ts +3 -0
  42. package/dist/test/fixtures/webtransport.d.ts.map +1 -0
  43. package/dist/test/fixtures/write-stream.d.ts.map +1 -1
  44. package/dist/test/index.d.ts +2 -0
  45. package/dist/test/index.d.ts.map +1 -0
  46. package/dist/test/session.spec.d.ts +5 -0
  47. package/dist/test/session.spec.d.ts.map +1 -0
  48. package/dist/test/unidirectional-streams.spec.d.ts +1 -4
  49. package/dist/test/unidirectional-streams.spec.d.ts.map +1 -1
  50. package/lib/client.js +57 -43
  51. package/lib/dom.ts +18 -4
  52. package/lib/error.js +10 -0
  53. package/lib/event-loop.js +8 -5
  54. package/lib/native.js +4 -1
  55. package/lib/server.js +18 -10
  56. package/lib/session.js +145 -32
  57. package/lib/stream.js +52 -24
  58. package/lib/transport.js +48 -10
  59. package/lib/types.ts +53 -5
  60. package/lib/utils.js +13 -0
  61. package/lib/webtransport.js +10 -18
  62. package/{test → old_test}/echoserver.js +1 -2
  63. package/{test → old_test}/test.js +5 -3
  64. package/package.json +16 -5
  65. package/readme.md +64 -0
  66. package/src/http3client.cc +2 -2
  67. package/src/http3dispatcher.cc +3 -2
  68. package/src/http3dispatcher.h +2 -1
  69. package/src/http3eventloop.cc +106 -2
  70. package/src/http3eventloop.h +13 -0
  71. package/src/http3wtsessionvisitor.cc +2 -0
  72. package/src/http3wtsessionvisitor.h +41 -1
  73. package/test/bidirectional-streams.spec.js +41 -97
  74. package/test/datagrams.spec.js +50 -99
  75. package/test/fixtures/chai.js +2 -0
  76. package/test/fixtures/known-bytes.js +15 -0
  77. package/test/fixtures/read-cert-hash.js +7 -0
  78. package/test/fixtures/read-stream.js +5 -0
  79. package/test/fixtures/reader-value.js +32 -0
  80. package/test/fixtures/server.js +237 -21
  81. package/test/fixtures/webtransport.browser.js +2 -0
  82. package/test/fixtures/webtransport.js +3 -0
  83. package/test/fixtures/write-stream.js +8 -1
  84. package/test/index.js +87 -0
  85. package/test/session.spec.js +159 -0
  86. package/test/unidirectional-streams.spec.js +67 -138
  87. package/tsconfig.json +0 -1
  88. package/dist/test/echoclient.d.ts +0 -2
  89. package/dist/test/echoclient.d.ts.map +0 -1
  90. package/dist/test/echoserver.d.ts +0 -2
  91. package/dist/test/echoserver.d.ts.map +0 -1
  92. package/dist/test/event-loop.spec.d.ts +0 -2
  93. package/dist/test/event-loop.spec.d.ts.map +0 -1
  94. package/dist/test/test.d.ts +0 -2
  95. package/dist/test/test.d.ts.map +0 -1
  96. package/dist/test/testsuite.d.ts +0 -25
  97. package/dist/test/testsuite.d.ts.map +0 -1
  98. package/platform/base/strings/utf_ostream_operators.h +0 -5
  99. /package/{test → old_test}/echoclient.js +0 -0
  100. /package/{test → old_test}/testsuite.js +0 -0
  101. /package/test/{event-loop.spec.js → event-loop.node.js} +0 -0
package/lib/session.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { ReadableStream, WritableStream } from 'node:stream/web'
2
2
  import { Http3WTStream } from './stream.js'
3
+ import { WebTransportError } from './error.js'
4
+ import { logger } from './utils.js'
5
+
6
+ const log = logger(`webtransport:http3wtsession(${process.pid})`)
3
7
 
4
8
  /**
5
9
  * WebTransport session events
@@ -9,6 +13,8 @@ import { Http3WTStream } from './stream.js'
9
13
  * @typedef {import('./types').DatagramReceivedEvent} DatagramReceivedEvent
10
14
  * @typedef {import('./types').DatagramSendEvent} DatagramSendEvent
11
15
  * @typedef {import('./types').GoawayReceivedEvent} GoawayReceivedEvent
16
+ * @typedef {import('./types').DatagramStatsEvent} DatagramStatsEvent
17
+ * @typedef {import('./types').SessionStatsEvent} SessionStatsEvent
12
18
  * @typedef {import('./types').NewStreamEvent} NewStreamEvent
13
19
  *
14
20
  * @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo
@@ -19,6 +25,7 @@ import { Http3WTStream } from './stream.js'
19
25
  * @typedef {import('./dom').WebTransportReliabilityMode} WebTransportReliabilityMode
20
26
  * @typedef {import('./dom').WebTransportCongestionControl} WebTransportCongestionControl
21
27
  * @typedef {import('./dom').WebTransportStats} WebTransportStats
28
+ * @typedef {import('./dom').WebTransportDatagramStats} WebTransportDatagramStats
22
29
  *
23
30
  * @typedef {import('./types').NativeHttp3WTSession} NativeHttp3WTSession
24
31
  *
@@ -49,7 +56,7 @@ export class Http3WTSession {
49
56
  }
50
57
  this.parentobj = args.parentobj
51
58
  /** @type {import('./types').WebTransportSessionState} */
52
- this.state = 'connected'
59
+ this.state = 'connecting'
53
60
 
54
61
  /** @type {((value?: any) => void) | null | undefined} */
55
62
  this.readyResolve = null
@@ -96,7 +103,7 @@ export class Http3WTSession {
96
103
 
97
104
  /** @type {Array<() => void>} */
98
105
  this.writeDatagramRes = []
99
- /** @type {Array<() => void>} */
106
+ /** @type {Array<(err?: Error) => void>} */
100
107
  this.writeDatagramRej = []
101
108
  /** @type {Array<Promise<void>>} */
102
109
  this.writeDatagramProm = []
@@ -125,7 +132,7 @@ export class Http3WTSession {
125
132
  this.writeDatagramRej.push(reject)
126
133
  })
127
134
  this.writeDatagramProm.push(ret)
128
- // console.log('b4 datagram write', chunk, Date.now())
135
+ log.trace('b4 datagram write', chunk)
129
136
  if (this.objint == null) {
130
137
  throw new Error('this.objint is not set')
131
138
  }
@@ -148,6 +155,16 @@ export class Http3WTSession {
148
155
  /** @type {Array<(err?: Error) => void>} */
149
156
  this.rejectUniDi = []
150
157
 
158
+ /** @type {Array<(stats: WebTransportStats) => void>} */
159
+ this.resolveSessionStats = []
160
+ /** @type {Array<(err?: Error) => void>} */
161
+ this.rejectSessionStats = []
162
+
163
+ /** @type {Array<(stats: WebTransportDatagramStats) => void>} */
164
+ this.resolveDatagramStats = []
165
+ /** @type {Array<(err?: Error) => void>} */
166
+ this.rejectDatagramStats = []
167
+
151
168
  /** @type {Set<WebTransportSendStream>} */
152
169
  this.sendStreams = new Set()
153
170
  /** @type {Set<WebTransportReceiveStream>} */
@@ -172,25 +189,72 @@ export class Http3WTSession {
172
189
  }
173
190
 
174
191
  getStats() {
175
- return Promise.resolve({
176
- timestamp: Date.now(),
177
- bytesSent: BigInt(0),
178
- packetsSent: BigInt(0),
179
- packetsLost: BigInt(0),
180
- numOutgoingStreamsCreated: 0,
181
- numIncomingStreamsCreated: 0,
182
- bytesReceived: BigInt(0),
183
- packetsReceived: BigInt(0),
184
- smoothedRtt: 0,
185
- rttVariation: 0,
186
- minRtt: 0,
187
- datagrams: {
188
- timestamp: Date.now(),
189
- expiredOutgoing: BigInt(0),
190
- droppedIncoming: BigInt(0),
191
- lostOutgoing: BigInt(0)
192
- }
192
+ if (this.objint == null) {
193
+ throw new Error('this.objint not set')
194
+ }
195
+ const prom = new Promise((resolve, reject) => {
196
+ this.resolveSessionStats.push(resolve)
197
+ this.rejectSessionStats.push(reject)
193
198
  })
199
+ this.objint.orderSessionStats()
200
+ return prom
201
+ }
202
+
203
+ /**
204
+ * @param {SessionStatsEvent} evt
205
+ */
206
+ onSessionStats({
207
+ timestamp,
208
+ expiredOutgoing = BigInt(0),
209
+ lostOutgoing = BigInt(0),
210
+ // non Datagram
211
+ minRtt = 0,
212
+ smoothedRtt = 0,
213
+ rttVariation = 0,
214
+ estimatedSendRateBps
215
+ }) {
216
+ const res = this.resolveSessionStats.pop()
217
+ this.rejectSessionStats.pop()
218
+ if (res)
219
+ res({
220
+ timestamp,
221
+ bytesSent: BigInt(0),
222
+ packetsSent: BigInt(0),
223
+ packetsLost: BigInt(0),
224
+ numOutgoingStreamsCreated: 0,
225
+ numIncomingStreamsCreated: 0,
226
+ bytesReceived: BigInt(0),
227
+ packetsReceived: BigInt(0),
228
+ smoothedRtt,
229
+ rttVariation,
230
+ minRtt,
231
+ estimatedSendRate: estimatedSendRateBps,
232
+ datagrams: {
233
+ timestamp,
234
+ expiredOutgoing,
235
+ droppedIncoming: BigInt(0),
236
+ lostOutgoing
237
+ }
238
+ })
239
+ }
240
+
241
+ /**
242
+ * @param {DatagramStatsEvent} evt
243
+ */
244
+ onDatagramStats({
245
+ timestamp,
246
+ expiredOutgoing = BigInt(0),
247
+ lostOutgoing = BigInt(0)
248
+ }) {
249
+ const res = this.resolveDatagramStats.pop()
250
+ this.rejectDatagramStats.pop()
251
+ if (res)
252
+ res({
253
+ timestamp,
254
+ expiredOutgoing,
255
+ droppedIncoming: BigInt(0),
256
+ lostOutgoing
257
+ })
194
258
  }
195
259
 
196
260
  async waitForDatagramsSend() {
@@ -198,7 +262,7 @@ export class Http3WTSession {
198
262
  try {
199
263
  await Promise.allSettled(this.writeDatagramProm)
200
264
  } catch (error) {
201
- console.log('datagram promise failed ', error)
265
+ log.error('datagram promise failed ', error)
202
266
  }
203
267
  }
204
268
  }
@@ -299,7 +363,7 @@ export class Http3WTSession {
299
363
  * @returns {void}
300
364
  */
301
365
  close(closeInfo) {
302
- // console.log('closeinfo', closeInfo)
366
+ log('closeinfo', closeInfo)
303
367
  if (this.state === 'closed' || this.state === 'failed') return
304
368
  if (this.objint) {
305
369
  this.objint.close({
@@ -310,6 +374,7 @@ export class Http3WTSession {
310
374
  }
311
375
 
312
376
  onReady(/* error */) {
377
+ this.state = 'connected'
313
378
  this.reliability = 'supports-unreliable'
314
379
  if (this.readyResolve) this.readyResolve()
315
380
  delete this.readyResolve
@@ -320,10 +385,36 @@ export class Http3WTSession {
320
385
  */
321
386
  onClose(args) {
322
387
  delete this.objint // not valid any more
323
- // console.log('onClose')
324
- for (const rej of this.rejectBiDi) rej()
325
- for (const rej of this.rejectUniDi) rej()
326
- for (const rej of this.writeDatagramRej) rej()
388
+
389
+ if (this.state !== 'connected') {
390
+ log.error(
391
+ 'session was closed before state was "connected" - it was "%s"',
392
+ this.state
393
+ )
394
+ this.state = 'failed'
395
+
396
+ // make sure the event loop can still exit
397
+ if (this.closeHook) {
398
+ this.closeHook()
399
+ delete this.closeHook
400
+ }
401
+
402
+ // closed before connected
403
+ const error = new WebTransportError('Opening handshake failed.')
404
+ this.readyReject(error)
405
+ this.closedReject(error)
406
+ return
407
+ }
408
+
409
+ log('onClose')
410
+ const error = new WebTransportError('Session closed')
411
+
412
+ for (const rej of this.rejectBiDi) rej(error)
413
+ for (const rej of this.rejectUniDi) rej(error)
414
+ for (const rej of this.writeDatagramRej) rej(error)
415
+ for (const rej of this.rejectSessionStats) rej(error)
416
+ for (const rej of this.rejectDatagramStats) rej(error)
417
+
327
418
  this.writeDatagramRej = []
328
419
  this.writeDatagramRes = []
329
420
  this.writeDatagramProm = []
@@ -332,14 +423,27 @@ export class Http3WTSession {
332
423
  this.rejectBiDi = []
333
424
  this.rejectUniDi = []
334
425
 
426
+ this.resolveSessionStats = []
427
+ this.rejectSessionStats = []
428
+ this.resolveDatagramStats = []
429
+ this.rejectDatagramStats = []
430
+
335
431
  this.incomBiDiController.close()
336
432
  this.incomUniDiController.close()
337
433
  this.incomDatagramController.close()
338
434
  // this.outgoDatagramController.error(errorcode)
339
435
  this.state = 'closed'
340
436
 
341
- this.sendStreamsController.forEach((ele) => ele.error(args.errorcode))
342
- this.receiveStreamsController.forEach((ele) => ele.error(args.errorcode))
437
+ const wtError = new WebTransportError(
438
+ `Session closed (on process ${process.pid}) with code ` +
439
+ args.errorcode +
440
+ ' and reason' +
441
+ args.error
442
+ )
443
+
444
+ this.sendStreamsController.forEach((ele) => ele.error(wtError))
445
+ this.receiveStreamsController.forEach((ele) => ele.error(wtError))
446
+
343
447
  this.streamObjs.forEach((ele) => (ele.readableclosed = true))
344
448
 
345
449
  this.sendStreams.clear()
@@ -351,7 +455,7 @@ export class Http3WTSession {
351
455
  if (this.closedResolve)
352
456
  this.closedResolve({
353
457
  closeCode: args.errorcode,
354
- reason: args.error ? args.error : 'closed'
458
+ reason: args.error ? args.error : ''
355
459
  })
356
460
  if (this.closeHook) {
357
461
  this.closeHook()
@@ -411,6 +515,7 @@ export class Http3WTSession {
411
515
  * @param {DatagramReceivedEvent} args
412
516
  */
413
517
  onDatagramReceived(args) {
518
+ log.trace('datagram received', args.datagram)
414
519
  // console.log('datagram received', args.datagram, Date.now())
415
520
  if (this.incomDatagramController.byobRequest) {
416
521
  /** @type {ReadableStreamBYOBRequest} */
@@ -458,10 +563,12 @@ export class Http3WTSession {
458
563
  }
459
564
 
460
565
  /**
461
- * @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | GoawayReceivedEvent | NewStreamEvent} args
566
+ * @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | GoawayReceivedEvent | SessionStatsEvent | DatagramStatsEvent | NewStreamEvent} args
462
567
  */
463
568
  static callback(args) {
464
- // console.log('Session callback called', args)
569
+ log('callback', args?.purpose)
570
+ log.trace(args)
571
+
465
572
  if (!args || !args.object || !args.object.jsobj)
466
573
  throw new Error('Session callback without jsobj')
467
574
  const visitor = args.object.jsobj
@@ -483,6 +590,12 @@ export class Http3WTSession {
483
590
  case 'GoawayReceived':
484
591
  visitor.onGoAwayReceived(args)
485
592
  break
593
+ case 'SessionStats':
594
+ visitor.onSessionStats(args)
595
+ break
596
+ case 'DatagramStats':
597
+ visitor.onDatagramStats(args)
598
+ break
486
599
  case 'Http3WTStreamVisitor':
487
600
  if (
488
601
  visitor &&
package/lib/stream.js CHANGED
@@ -1,4 +1,8 @@
1
1
  import { ReadableStream, WritableStream } from 'node:stream/web'
2
+ import { logger } from './utils.js'
3
+ import { WebTransportError } from './error.js'
4
+
5
+ const log = logger(`webtransport:http3wtstream(${process.pid})`)
2
6
 
3
7
  /**
4
8
  * WebTransport stream events
@@ -11,6 +15,9 @@ import { ReadableStream, WritableStream } from 'node:stream/web'
11
15
  *
12
16
  * @typedef {import('./types').NativeHttp3WTStream} NativeHttp3WTStream
13
17
  *
18
+ * @typedef {import('./dom').WebTransportReceiveStream} WebTransportReceiveStream
19
+ * @typedef {import('./dom').WebTransportSendStream} WebTransportSendStream
20
+ *
14
21
  * @typedef {import('./session').Http3WTSession} Http3WTSession
15
22
  *
16
23
  * @typedef {import('stream/web').WritableStreamDefaultController} WritableStreamDefaultController
@@ -38,6 +45,11 @@ export class Http3WTStream {
38
45
  this.pendingoperation = null
39
46
  this.pendingres = null
40
47
 
48
+ /** @type {WebTransportReceiveStream} */
49
+ this.readable // eslint-disable-line no-unused-expressions
50
+ /** @type {WebTransportSendStream} */
51
+ this.writable // eslint-disable-line no-unused-expressions
52
+
41
53
  /** @type {Promise<void> | null} */
42
54
  this.pendingoperationRead = null
43
55
  this.pendingresRead = null
@@ -49,15 +61,14 @@ export class Http3WTStream {
49
61
  this.incomingbufferreadpos = 0
50
62
  if (!this.objint.readbuffer)
51
63
  throw new Error('No readbuffer for read stream')
52
- // @ts-expect-error `getStats` property is missing from ReadableStream
53
64
  /** @type {WebTransportReceiveStream} */
65
+ // @ts-expect-error `getStats` property is missing from ReadableStream
54
66
  this.readable = new ReadableStream(
55
67
  {
56
68
  start: (
57
69
  /** @type {import("stream/web").ReadableByteStreamController} */ controller
58
70
  ) => {
59
71
  this.readableController = controller
60
- this.parentobj.addReceiveStream(this.readable, controller)
61
72
  this.objint.startReading()
62
73
  },
63
74
  pull: async (
@@ -74,8 +85,7 @@ export class Http3WTStream {
74
85
  })
75
86
  await this.pendingoperationRead
76
87
  }
77
- if (this.incomingbufferfilled === 0)
78
- throw new Error('error reading')
88
+ if (this.incomingbufferfilled === 0) return Promise.resolve()
79
89
 
80
90
  this.drainBuffer()
81
91
  },
@@ -103,18 +113,19 @@ export class Http3WTStream {
103
113
  return Promise.resolve({
104
114
  timestamp: 0,
105
115
  bytesReceived: 0n,
106
- bytesResd: 0n
116
+ bytesRead: 0n
107
117
  })
108
118
  }
119
+ // @ts-ignore
120
+ this.parentobj.addReceiveStream(this.readable, this.readableController)
109
121
  }
110
122
  if (this.bidirectional || !this.incoming) {
111
- // @ts-expect-error `getStats` property is missing from WritableStream
112
123
  /** @type {WebTransportSendStream} */
124
+ // @ts-expect-error `getStats` property is missing from WritableStream
113
125
  this.writable = new WritableStream(
114
126
  {
115
127
  start: (controller) => {
116
128
  this.writableController = controller
117
- this.parentobj.addSendStream(this.writable, controller)
118
129
  },
119
130
  write: (chunk, controller) => {
120
131
  if (this.writableclosed) {
@@ -124,13 +135,17 @@ export class Http3WTStream {
124
135
  this.pendingoperation = new Promise((resolve, reject) => {
125
136
  this.pendingres = resolve
126
137
  })
127
- const dataprom = this.parentobj.waitForDatagramsSend()
128
- dataprom.finally(() => {
129
- this.objint.writeChunk(chunk)
130
- })
138
+ this.parentobj
139
+ .waitForDatagramsSend()
140
+ .finally(() => {
141
+ this.objint.writeChunk(chunk)
142
+ })
143
+ .catch((/** @type {any} */ err) => {
144
+ log.error(err)
145
+ })
131
146
  return this.pendingoperation
132
147
  } else {
133
- console.log('chunk info:', chunk)
148
+ log.trace('chunk info:', chunk)
134
149
  throw new Error('chunk is not of instanceof Uint8Array ')
135
150
  }
136
151
  },
@@ -174,6 +189,8 @@ export class Http3WTStream {
174
189
  bytesAcknowledged: 0n
175
190
  })
176
191
  }
192
+ // @ts-ignore
193
+ this.parentobj.addSendStream(this.writable, this.writableController)
177
194
  }
178
195
 
179
196
  /** @type {(() => void) | null} */
@@ -245,12 +262,13 @@ export class Http3WTStream {
245
262
  * @returns {void}
246
263
  */
247
264
  onStreamRecvSignal(args) {
248
- // console.log('onStreamRecvSignal', args)
265
+ log('callback', args?.purpose, args?.nettask)
266
+ log.trace('onStreamRecvSignal', args)
249
267
  // check if transport is closed
250
268
  let parentcleanup = true
251
269
  const parentstate = this.parentobj.state
252
270
  if (parentstate === 'closed' || parentstate === 'failed') {
253
- console.log('no parent cleanup as parent was closed or failed')
271
+ log('no parent cleanup as parent was closed or failed')
254
272
  parentcleanup = false
255
273
  }
256
274
  switch (args.nettask) {
@@ -262,8 +280,12 @@ export class Http3WTStream {
262
280
  this.readableController
263
281
  )
264
282
  this.readableclosed = true
265
- this.readableController.error(args.code || 0)
266
- } else console.log('stopSending without readable')
283
+ this.readableController.error(
284
+ new WebTransportError('Resetstream with code:' + (args.code || 0))
285
+ )
286
+ } else {
287
+ log.error('resetStream without readable')
288
+ }
267
289
  break
268
290
 
269
291
  case 'stopSending':
@@ -275,11 +297,15 @@ export class Http3WTStream {
275
297
  )
276
298
 
277
299
  this.writableclosed = true
278
- this.writableController.error(args.code || 0)
279
- } else console.log('stopSending wihtout writable')
300
+ this.writableController.error(
301
+ new WebTransportError('StopSending with code:' + (args.code || 0))
302
+ )
303
+ } else {
304
+ log.error('stopSending without writable')
305
+ }
280
306
  break
281
307
  default:
282
- console.log('unhandled onStreamRecvSignal')
308
+ log.error('unhandled onStreamRecvSignal')
283
309
  }
284
310
 
285
311
  if (this.pendingoperation) {
@@ -306,6 +332,7 @@ export class Http3WTStream {
306
332
  */
307
333
  onStreamRead(args) {
308
334
  if (args.buffergrow && !this.readableclosed) {
335
+ log.trace('stream read received', args.buffergrow)
309
336
  this.incomingbufferfilled += args.buffergrow
310
337
  // console.log('stream read received', args.data, Date.now())
311
338
  if (this.pendingoperationRead) {
@@ -323,7 +350,7 @@ export class Http3WTStream {
323
350
  }
324
351
  if (args.fin) {
325
352
  if (this.incomingbufferfilled > 0) {
326
- console.log('Warning buffer filled and we got a fin')
353
+ log.trace('Warning buffer filled and we got a fin')
327
354
  if (this.pendingoperationRead || this.pendingresRead)
328
355
  throw new Error('We have pendingoperationRead and a filled buffer?')
329
356
 
@@ -378,7 +405,8 @@ export class Http3WTStream {
378
405
  * @param {StreamNetworkFinishEvent} args
379
406
  */
380
407
  onStreamNetworkFinish(args) {
381
- // console.log('networkfinish args', args)
408
+ log('callback', args?.purpose)
409
+ log.trace('networkfinish args', args)
382
410
  switch (args.nettask) {
383
411
  case 'stopSending':
384
412
  if (this.cancelres) {
@@ -408,7 +436,7 @@ export class Http3WTStream {
408
436
  }
409
437
  break
410
438
  default:
411
- console.log('onStreamNetworkFinish unknown task')
439
+ log.error('onStreamNetworkFinish unknown task', args.nettask)
412
440
  }
413
441
  // we could differentiate....
414
442
  }
@@ -417,7 +445,8 @@ export class Http3WTStream {
417
445
  * @param {StreamRecvSignalEvent | StreamReadEvent | StreamWriteEvent | StreamResetEvent | StreamNetworkFinishEvent} args
418
446
  */
419
447
  static callback(args) {
420
- // console.log('Stream callback called', args)
448
+ log('callback', args?.purpose)
449
+ log.trace(log)
421
450
  if (!args || !args.object || !args.object.jsobj)
422
451
  throw new Error('Stream callback without jsobj')
423
452
  const visitor = args.object.jsobj
@@ -433,7 +462,6 @@ export class Http3WTStream {
433
462
  ) {
434
463
  visitor.onStreamRead(args)
435
464
  } else {
436
- console.log('Stream callback called', visitor, args)
437
465
  throw new Error('Malformed StreamRead')
438
466
  }
439
467
  break
package/lib/transport.js CHANGED
@@ -1,24 +1,61 @@
1
1
  import { Http3EventLoop } from './event-loop.js'
2
2
  import { wtrouter } from './native.js'
3
+ import { WebTransportError } from './error.js'
4
+ import { logger } from './utils.js'
5
+
6
+ const log = logger(`webtransport:http3webtransport(${process.pid})`)
7
+
8
+ /**
9
+ * @typedef {import('./types').Http3WebTransportInit} Http3WebTransportInit
10
+ */
3
11
 
4
12
  export class Http3WebTransport {
5
13
  /**
6
- * @param {*} args
14
+ * @param {Http3WebTransportInit} args
7
15
  * @param {'server' | 'client'} purpose
8
16
  */
9
17
  constructor(args, purpose) {
18
+ /** @type {Http3WebTransportInit | undefined} */
19
+ this.args = args
20
+ /** @type {'server' | 'client' | undefined} */
21
+ this.purpose = purpose
22
+ this.sessions = {}
23
+ }
24
+
25
+ createTransportInt() {
26
+ if (this.transportInt != null) {
27
+ return
28
+ }
10
29
  const eventloop = Http3EventLoop.getGlobalEventLoop(this, {
11
- quicheLogVerbose: args.quicheLogVerbose
30
+ quicheLogVerbose: this.args?.quicheLogVerbose
31
+ ? this.args.quicheLogVerbose
32
+ : -1
12
33
  }).eventloopInt
13
34
 
14
- if (purpose === 'server')
15
- this.transportInt = new wtrouter.Http3WebTransportServer(args, eventloop)
16
- else if (purpose === 'client')
17
- this.transportInt = new wtrouter.Http3WebTransportClient(args, eventloop)
18
- else throw new Error('unknown purpose')
19
- this.transportInt.jsobj = this
35
+ try {
36
+ if (this.purpose === 'server') {
37
+ this.transportInt = new wtrouter.Http3WebTransportServer(
38
+ this.args,
39
+ eventloop
40
+ )
41
+ } else if (this.purpose === 'client') {
42
+ this.transportInt = new wtrouter.Http3WebTransportClient(
43
+ this.args,
44
+ eventloop
45
+ )
46
+ } else {
47
+ throw new Error('unknown purpose')
48
+ }
49
+ } catch (/** @type {any} */ err) {
50
+ const error = new WebTransportError('Opening handshake failed.')
51
+ error.stack = err.stack
20
52
 
21
- this.sessions = {}
53
+ throw error
54
+ }
55
+
56
+ delete this.purpose
57
+ delete this.args
58
+ this.transportInt.jsobj = this
22
59
  }
23
60
 
24
61
  /**
@@ -29,7 +66,8 @@ export class Http3WebTransport {
29
66
  * @param {TransportCallbackEvent} args
30
67
  */
31
68
  static transportCallback(args) {
32
- // console.log('incoming callback transport', args)
69
+ log('callback', args?.purpose)
70
+ log.trace(args)
33
71
  if (!args || !args.object || !args.object.jsobj)
34
72
  throw new Error('Transport callback without jsobj')
35
73
  const visitor = args.object.jsobj
package/lib/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { WebTransport } from './dom'
1
+ import type { WebTransport, WebTransportOptions } from './dom'
2
2
 
3
3
  /**
4
4
  * Native Http3WTSession counterpart
@@ -8,7 +8,8 @@ import type { WebTransport } from './dom'
8
8
  writeDatagram: (chunk: Uint8Array) => void
9
9
  orderUnidiStream: () => void
10
10
  orderBidiStream: () => void
11
- // orderStats: () => void
11
+ orderSessionStats: () => void
12
+ orderDatagramStats: () => void
12
13
  notifySessionDraining(): () => void
13
14
  close: (arg: { code: number, reason: string }) => void
14
15
  }
@@ -82,6 +83,28 @@ export interface SessionCloseEvent {
82
83
  error: string
83
84
  }
84
85
 
86
+ export interface SessionStatsEvent {
87
+ object: NativeHttp3WTSession
88
+ purpose: 'SessionStats'
89
+ timestamp: number
90
+ expiredOutgoing: bigint
91
+ lostOutgoing: bigint
92
+
93
+ // non Datagram
94
+ minRtt: number
95
+ smoothedRtt: number
96
+ rttVariation: number
97
+ estimatedSendRateBps: bigint
98
+ }
99
+
100
+ export interface DatagramStatsEvent {
101
+ object: NativeHttp3WTSession
102
+ purpose: 'DatagramStats'
103
+ timestamp: number
104
+ expiredOutgoing: bigint
105
+ lostOutgoing: bigint
106
+ }
107
+
85
108
  export interface DatagramReceivedEvent {
86
109
  object: NativeHttp3WTSession
87
110
  purpose: 'DatagramReceived'
@@ -113,6 +136,8 @@ export interface WebTransportSessionEventHandler {
113
136
  onDatagramReceived: (evt: DatagramReceivedEvent) => void
114
137
  onDatagramSend: (evt: DatagramSendEvent) => void
115
138
  onGoAwayReceived: (evt: GoawayReceivedEvent) => void
139
+ onSessionStats: (evt: SessionStatsEvent) => void
140
+ onDatagramStats: (evt: DatagramStatsEvent) => void
116
141
  onStream: (evt: NewStreamEvent) => void
117
142
  closeHook?: (() => void) | null
118
143
  }
@@ -185,16 +210,39 @@ export interface Deferred<T = unknown> {
185
210
  reject: (reason?: any) => void
186
211
  }
187
212
 
188
- export type WebTransportSessionState = 'connected' | 'draining' | 'closed' | 'failed'
213
+ // https://www.w3.org/TR/webtransport/#dom-webtransport-state-slot
214
+ export type WebTransportSessionState = 'connecting' | 'connected' | 'draining' | 'closed' | 'failed'
189
215
 
190
216
  export interface WebTransportSession extends WebTransport {
191
217
  state: WebTransportSessionState
192
218
  }
193
219
 
194
- export interface Http3ServerInit {
195
- port: number
220
+ export interface Http3WebTransportInit extends WebTransportOptions {
221
+ host: string
222
+ port: string | number
223
+ quicheLogVerbose?: number
224
+ }
225
+
226
+ // see Http3ServerJS C++ type
227
+ export interface Http3ServerInit extends Http3WebTransportInit {
228
+ port: string | number
196
229
  host: string
197
230
  secret: string
198
231
  cert: string
199
232
  privKey: string
233
+ maxConnections?: number
234
+ initialStreamFlowControlWindow?: number
235
+ initialSessionFlowControlWindow?: number
236
+ }
237
+
238
+ // see Http3ClientJS C++ type
239
+ export interface Http3ClientInit extends Http3WebTransportInit {
240
+ forceIpv6?: boolean
241
+ localPort?: number
242
+ }
243
+
244
+ export interface Logger {
245
+ (formatter: any, ...args: any[]): void
246
+ error: (formatter: any, ...args: any[]) => void
247
+ trace: (formatter: any, ...args: any[]) => void
200
248
  }