@fails-components/webtransport 0.2.2 → 0.3.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 (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 +51 -22
  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 +46 -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 (
@@ -102,18 +113,19 @@ export class Http3WTStream {
102
113
  return Promise.resolve({
103
114
  timestamp: 0,
104
115
  bytesReceived: 0n,
105
- bytesResd: 0n
116
+ bytesRead: 0n
106
117
  })
107
118
  }
119
+ // @ts-ignore
120
+ this.parentobj.addReceiveStream(this.readable, this.readableController)
108
121
  }
109
122
  if (this.bidirectional || !this.incoming) {
110
- // @ts-expect-error `getStats` property is missing from WritableStream
111
123
  /** @type {WebTransportSendStream} */
124
+ // @ts-expect-error `getStats` property is missing from WritableStream
112
125
  this.writable = new WritableStream(
113
126
  {
114
127
  start: (controller) => {
115
128
  this.writableController = controller
116
- this.parentobj.addSendStream(this.writable, controller)
117
129
  },
118
130
  write: (chunk, controller) => {
119
131
  if (this.writableclosed) {
@@ -123,13 +135,17 @@ export class Http3WTStream {
123
135
  this.pendingoperation = new Promise((resolve, reject) => {
124
136
  this.pendingres = resolve
125
137
  })
126
- const dataprom = this.parentobj.waitForDatagramsSend()
127
- dataprom.finally(() => {
128
- this.objint.writeChunk(chunk)
129
- })
138
+ this.parentobj
139
+ .waitForDatagramsSend()
140
+ .finally(() => {
141
+ this.objint.writeChunk(chunk)
142
+ })
143
+ .catch((/** @type {any} */ err) => {
144
+ log.error(err)
145
+ })
130
146
  return this.pendingoperation
131
147
  } else {
132
- console.log('chunk info:', chunk)
148
+ log.trace('chunk info:', chunk)
133
149
  throw new Error('chunk is not of instanceof Uint8Array ')
134
150
  }
135
151
  },
@@ -173,6 +189,8 @@ export class Http3WTStream {
173
189
  bytesAcknowledged: 0n
174
190
  })
175
191
  }
192
+ // @ts-ignore
193
+ this.parentobj.addSendStream(this.writable, this.writableController)
176
194
  }
177
195
 
178
196
  /** @type {(() => void) | null} */
@@ -244,12 +262,13 @@ export class Http3WTStream {
244
262
  * @returns {void}
245
263
  */
246
264
  onStreamRecvSignal(args) {
247
- // console.log('onStreamRecvSignal', args)
265
+ log('callback', args?.purpose, args?.nettask)
266
+ log.trace('onStreamRecvSignal', args)
248
267
  // check if transport is closed
249
268
  let parentcleanup = true
250
269
  const parentstate = this.parentobj.state
251
270
  if (parentstate === 'closed' || parentstate === 'failed') {
252
- console.log('no parent cleanup as parent was closed or failed')
271
+ log('no parent cleanup as parent was closed or failed')
253
272
  parentcleanup = false
254
273
  }
255
274
  switch (args.nettask) {
@@ -261,8 +280,12 @@ export class Http3WTStream {
261
280
  this.readableController
262
281
  )
263
282
  this.readableclosed = true
264
- this.readableController.error(args.code || 0)
265
- } 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
+ }
266
289
  break
267
290
 
268
291
  case 'stopSending':
@@ -274,11 +297,15 @@ export class Http3WTStream {
274
297
  )
275
298
 
276
299
  this.writableclosed = true
277
- this.writableController.error(args.code || 0)
278
- } 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
+ }
279
306
  break
280
307
  default:
281
- console.log('unhandled onStreamRecvSignal')
308
+ log.error('unhandled onStreamRecvSignal')
282
309
  }
283
310
 
284
311
  if (this.pendingoperation) {
@@ -305,6 +332,7 @@ export class Http3WTStream {
305
332
  */
306
333
  onStreamRead(args) {
307
334
  if (args.buffergrow && !this.readableclosed) {
335
+ log.trace('stream read received', args.buffergrow)
308
336
  this.incomingbufferfilled += args.buffergrow
309
337
  // console.log('stream read received', args.data, Date.now())
310
338
  if (this.pendingoperationRead) {
@@ -322,7 +350,7 @@ export class Http3WTStream {
322
350
  }
323
351
  if (args.fin) {
324
352
  if (this.incomingbufferfilled > 0) {
325
- console.log('Warning buffer filled and we got a fin')
353
+ log.trace('Warning buffer filled and we got a fin')
326
354
  if (this.pendingoperationRead || this.pendingresRead)
327
355
  throw new Error('We have pendingoperationRead and a filled buffer?')
328
356
 
@@ -377,7 +405,8 @@ export class Http3WTStream {
377
405
  * @param {StreamNetworkFinishEvent} args
378
406
  */
379
407
  onStreamNetworkFinish(args) {
380
- // console.log('networkfinish args', args)
408
+ log('callback', args?.purpose)
409
+ log.trace('networkfinish args', args)
381
410
  switch (args.nettask) {
382
411
  case 'stopSending':
383
412
  if (this.cancelres) {
@@ -407,7 +436,7 @@ export class Http3WTStream {
407
436
  }
408
437
  break
409
438
  default:
410
- console.log('onStreamNetworkFinish unknown task')
439
+ log.error('onStreamNetworkFinish unknown task', args.nettask)
411
440
  }
412
441
  // we could differentiate....
413
442
  }
@@ -416,7 +445,8 @@ export class Http3WTStream {
416
445
  * @param {StreamRecvSignalEvent | StreamReadEvent | StreamWriteEvent | StreamResetEvent | StreamNetworkFinishEvent} args
417
446
  */
418
447
  static callback(args) {
419
- // console.log('Stream callback called', args)
448
+ log('callback', args?.purpose)
449
+ log.trace(log)
420
450
  if (!args || !args.object || !args.object.jsobj)
421
451
  throw new Error('Stream callback without jsobj')
422
452
  const visitor = args.object.jsobj
@@ -432,7 +462,6 @@ export class Http3WTStream {
432
462
  ) {
433
463
  visitor.onStreamRead(args)
434
464
  } else {
435
- console.log('Stream callback called', visitor, args)
436
465
  throw new Error('Malformed StreamRead')
437
466
  }
438
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
  }