@fails-components/webtransport 1.1.3 → 1.2.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 (75) hide show
  1. package/dist/lib/dom.d.ts +27 -10
  2. package/dist/lib/dom.d.ts.map +1 -1
  3. package/dist/lib/http2/browser/browser.d.ts +5 -2
  4. package/dist/lib/http2/browser/browser.d.ts.map +1 -1
  5. package/dist/lib/http2/browser/browserparser.d.ts.map +1 -1
  6. package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -1
  7. package/dist/lib/http2/node/client.d.ts +3 -2
  8. package/dist/lib/http2/node/client.d.ts.map +1 -1
  9. package/dist/lib/http2/node/server.d.ts +10 -5
  10. package/dist/lib/http2/node/server.d.ts.map +1 -1
  11. package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
  12. package/dist/lib/http2/parserbase.d.ts +33 -1
  13. package/dist/lib/http2/parserbase.d.ts.map +1 -1
  14. package/dist/lib/http2/priorityscheduler.d.ts +167 -0
  15. package/dist/lib/http2/priorityscheduler.d.ts.map +1 -0
  16. package/dist/lib/http2/session.d.ts +25 -16
  17. package/dist/lib/http2/session.d.ts.map +1 -1
  18. package/dist/lib/http2/stream.d.ts +10 -0
  19. package/dist/lib/http2/stream.d.ts.map +1 -1
  20. package/dist/lib/http2/websocketcommon.d.ts.map +1 -1
  21. package/dist/lib/server.d.ts.map +1 -1
  22. package/dist/lib/session.d.ts +31 -17
  23. package/dist/lib/session.d.ts.map +1 -1
  24. package/dist/lib/stream.d.ts +13 -0
  25. package/dist/lib/stream.d.ts.map +1 -1
  26. package/dist/lib/types.d.ts +18 -15
  27. package/dist/lib/types.d.ts.map +1 -1
  28. package/dist/lib/webtransport.browser.d.ts +3 -2
  29. package/dist/lib/webtransport.browser.d.ts.map +1 -1
  30. package/dist/lib/webtransportbase.d.ts +1 -0
  31. package/dist/lib/webtransportbase.d.ts.map +1 -1
  32. package/eslint.config.js +20 -22
  33. package/lib/dom.ts +31 -13
  34. package/lib/http2/browser/browser.js +28 -13
  35. package/lib/http2/browser/browserparser.js +4 -1
  36. package/lib/http2/node/capsuleparser.js +4 -1
  37. package/lib/http2/node/client.js +38 -27
  38. package/lib/http2/node/server.js +56 -19
  39. package/lib/http2/node/websocketparser.js +5 -2
  40. package/lib/http2/parserbase.js +76 -7
  41. package/lib/http2/priorityscheduler.js +654 -0
  42. package/lib/http2/session.js +69 -20
  43. package/lib/http2/stream.js +60 -10
  44. package/lib/http2/websocketcommon.js +1 -1
  45. package/lib/server.js +0 -1
  46. package/lib/session.js +128 -64
  47. package/lib/stream.js +50 -8
  48. package/lib/types.ts +15 -8
  49. package/lib/webtransport.browser.js +77 -18
  50. package/lib/webtransportbase.js +7 -0
  51. package/old_test/echoserver.js +1 -1
  52. package/old_test/test.js +1 -1
  53. package/old_test/testsuite.js +6 -2
  54. package/package.json +7 -28
  55. package/readme.md +5 -3
  56. package/test/bidirectional-streams.spec.js +0 -150
  57. package/test/datagrams.spec.js +0 -104
  58. package/test/fixtures/certificate.js +0 -456
  59. package/test/fixtures/chai.js +0 -8
  60. package/test/fixtures/known-bytes.js +0 -44
  61. package/test/fixtures/p-timeout.js +0 -33
  62. package/test/fixtures/quiche.browser.js +0 -2
  63. package/test/fixtures/quiche.js +0 -1
  64. package/test/fixtures/read-cert-hash.js +0 -7
  65. package/test/fixtures/read-stream.js +0 -57
  66. package/test/fixtures/reader-value.js +0 -51
  67. package/test/fixtures/server.js +0 -467
  68. package/test/fixtures/webtransport.browser.js +0 -19
  69. package/test/fixtures/webtransport.js +0 -3
  70. package/test/fixtures/write-stream.js +0 -24
  71. package/test/index.js +0 -124
  72. package/test/pw-no-https-errors.json +0 -5
  73. package/test/session.spec.js +0 -199
  74. package/test/stream-limits.spec.js +0 -259
  75. package/test/unidirectional-streams.spec.js +0 -133
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @typedef {import('http2').Http2Stream} Http2Stream
3
3
  * @typedef {import('../dom.js').WebTransportSendGroup} WebTransportSendGroup
4
+ * @typedef {import('../dom.js').WebTransportSendStreamOptions} WebTransportSendStreamOptions
4
5
  */
5
6
  import { ParserBase } from './parserbase.js'
6
7
  import { FlowController } from './flowcontroller.js'
@@ -72,15 +73,25 @@ export class Http2WebTransportSession {
72
73
  maxAllowedIncomingStreams: initialBidirectionalReceiveStreams,
73
74
  maxAllowedOutgoingStreams: initialBidirectionalSendStreams
74
75
  })
75
- this.orderUniStreams = 0
76
- this.orderBiStreams = 0
76
+ /** @type {Array<Uint8Array>} */
77
+ this.datagramsWaiting_ = []
78
+ /** @type {Array<{sendOrder: bigint, sendGroupId: bigint}>} */
79
+ this.orderUniStreams = []
80
+ /** @type {Array<{sendOrder: bigint, sendGroupId: bigint}>} */
81
+ this.orderBiStreams = []
77
82
  if (stream) {
78
83
  if (isclient) {
79
84
  stream.on('response', (headers) => {
80
85
  processnextTick(() => {
81
86
  if (headers[':status'] === 200) {
87
+ const beReady = {}
88
+ if (stream && headers['wt-protocol']) {
89
+ // http/2 case
90
+ // @ts-ignore
91
+ beReady.protocol = headers['wt-protocol']
92
+ }
82
93
  // on ready
83
- this.jsobj.onReady({})
94
+ this.jsobj.onReady(beReady)
84
95
  } else {
85
96
  this.jsobj.onClose({
86
97
  errorcode: headers[':status'],
@@ -103,46 +114,71 @@ export class Http2WebTransportSession {
103
114
  this.streamIdMngrUni.sendMaxStreamsFrameInitial()
104
115
  }
105
116
 
117
+ drainWrites() {
118
+ while (!this.capsParser.blocked && this.datagramsWaiting_.length > 0) {
119
+ const outChunk = this.datagramsWaiting_.shift()
120
+ this.capsParser.writeCapsule({
121
+ type: ParserBase.DATAGRAM,
122
+ headerVints: [],
123
+ payload: outChunk
124
+ })
125
+ }
126
+ if (this.datagramsWaiting_.length > 0) {
127
+ this.capsParser.scheduleDrainWrites()
128
+ }
129
+ }
130
+
106
131
  /**
107
132
  * @param {Uint8Array} chunk
133
+ * @return {{ code: "success" | "blocked" | "internalError" | "tooBig", message?: string | undefined; }}
108
134
  */
109
135
  writeDatagram(chunk) {
136
+ if (chunk.byteLength > this.getMaxDatagramSize()) return { code: 'tooBig' }
137
+ if (this.capsParser.blocked) {
138
+ this.datagramsWaiting_.push(chunk)
139
+ this.capsParser.scheduleDrainWrites()
140
+ return { code: 'blocked' }
141
+ }
110
142
  this.capsParser.writeCapsule({
111
143
  type: ParserBase.DATAGRAM,
112
144
  headerVints: [],
113
145
  payload: chunk
114
146
  })
115
- processnextTick(() => {
116
- this.jsobj.onDatagramSend({})
117
- })
147
+ return { code: 'success' }
118
148
  }
119
149
 
120
150
  trySendingUnidirectionalStreams() {
121
151
  while (
122
- this.orderUniStreams > 0 &&
152
+ this.orderUniStreams.length > 0 &&
123
153
  this.streamIdMngrUni.canOpenNextOutgoingStream()
124
154
  ) {
125
155
  const streamid = this.streamIdMngrUni.getNextOutgoingStreamId()
156
+ const priority = this.orderUniStreams.pop()
126
157
  this.capsParser.writeCapsule({
127
158
  type: ParserBase.WT_STREAM_WOFIN,
128
159
  headerVints: [streamid],
129
160
  payload: undefined
130
161
  })
131
- this.capsParser.newStream(streamid)
132
- this.orderUniStreams--
162
+ this.capsParser.newStream(
163
+ streamid,
164
+ priority || { sendGroupId: 0n, sendOrder: 0n }
165
+ )
133
166
  }
134
167
  }
135
168
 
136
169
  /**
137
- * @param {{sendGroup: WebTransportSendGroup|null, sendOrder: number, waitUntilAvailable: boolean}} opts
170
+ * @param {WebTransportSendStreamOptions} opts
138
171
  */
139
- // eslint-disable-next-line no-unused-vars
140
172
  orderUnidiStream({ sendGroup, sendOrder, waitUntilAvailable }) {
141
173
  const canopen = this.streamIdMngrUni.canOpenNextOutgoingStream()
142
174
  const maxset = this.streamIdMngrUni.isMaxStreamSet() // we block if the maxsetting did not arrive
143
175
 
144
176
  if (canopen || waitUntilAvailable || !maxset) {
145
- this.orderUniStreams++
177
+ this.orderUniStreams.push({
178
+ // @ts-ignore
179
+ sendGroupId: sendGroup?._sendGroupId || 0n,
180
+ sendOrder: sendOrder ?? 0n
181
+ })
146
182
  this.trySendingUnidirectionalStreams()
147
183
  return true
148
184
  }
@@ -151,30 +187,36 @@ export class Http2WebTransportSession {
151
187
 
152
188
  trySendingBidirectionalStreams() {
153
189
  while (
154
- this.orderBiStreams > 0 &&
190
+ this.orderBiStreams.length > 0 &&
155
191
  this.streamIdMngrBi.canOpenNextOutgoingStream()
156
192
  ) {
157
193
  const streamid = this.streamIdMngrBi.getNextOutgoingStreamId()
194
+ const priority = this.orderBiStreams.pop()
158
195
  this.capsParser.writeCapsule({
159
196
  type: ParserBase.WT_STREAM_WOFIN,
160
197
  headerVints: [streamid],
161
198
  payload: undefined
162
199
  })
163
- this.capsParser.newStream(streamid)
164
- this.orderBiStreams--
200
+ this.capsParser.newStream(
201
+ streamid,
202
+ priority || { sendGroupId: 0n, sendOrder: 0n }
203
+ )
165
204
  }
166
205
  }
167
206
 
168
207
  /**
169
- * @param {{sendGroup: WebTransportSendGroup|null, sendOrder: number, waitUntilAvailable: boolean}} opts
208
+ * @param {WebTransportSendStreamOptions} opts
170
209
  */
171
- // eslint-disable-next-line no-unused-vars
172
210
  orderBidiStream({ sendGroup, sendOrder, waitUntilAvailable }) {
173
211
  const canopen = this.streamIdMngrBi.canOpenNextOutgoingStream()
174
212
  const maxset = this.streamIdMngrBi.isMaxStreamSet() // we block if the maxsetting did not arrive
175
213
 
176
214
  if (canopen || waitUntilAvailable || !maxset) {
177
- this.orderBiStreams++
215
+ this.orderBiStreams.push({
216
+ // @ts-ignore
217
+ sendGroupId: sendGroup?._sendGroupId || 0n,
218
+ sendOrder: sendOrder ?? 0n
219
+ })
178
220
  this.trySendingBidirectionalStreams()
179
221
  return true
180
222
  }
@@ -202,6 +244,10 @@ export class Http2WebTransportSession {
202
244
  })
203
245
  }
204
246
 
247
+ getMaxDatagramSize() {
248
+ return 16384 // this completly arbitry, we do not have a real restriction, but we choose more than quiche, to make things interesting
249
+ }
250
+
205
251
  /*
206
252
  * @returns {void}
207
253
  */
@@ -260,16 +306,19 @@ export class Http2WebTransportSession {
260
306
  }
261
307
 
262
308
  smoothedRtt() {
309
+ let toret
263
310
  if (this.stream) {
264
311
  // we are on node
265
312
  // @ts-ignore
266
- return this.stream.session?.WTrtt || 26
313
+ toret = this.stream.session?.WTrtt || 25
267
314
  } else if (this.ws) {
268
315
  // we are at the Browser, so we use the connection rtt?
269
316
  // @ts-ignore
270
317
  // eslint-disable-next-line no-undef
271
- return navigator?.connection?.rtt || 26
318
+ toret = navigator?.connection?.rtt || 25
272
319
  }
320
+ toret = Math.ceil(toret / 25) * 25 // to do be to accurate!
321
+ return toret
273
322
  }
274
323
 
275
324
  /**
@@ -1,6 +1,7 @@
1
1
  import { FlowController } from './flowcontroller.js'
2
2
  import { ParserBase } from './parserbase.js'
3
3
  import { logger } from '../utils.js'
4
+ import { WebTransportError } from '../error.js'
4
5
 
5
6
  const pid = typeof process !== 'undefined' ? process.pid : 0
6
7
  const log = logger(`webtransport:http2webtransportstream(${pid})`)
@@ -69,6 +70,7 @@ export class Http2WebTransportStream {
69
70
  this.streamIdManager = streamIdManager
70
71
 
71
72
  this.final = false
73
+ this.finalmessagesend = false
72
74
  this.stopReading_ = true
73
75
  this.drainReads_ = true
74
76
  this.recvBytes = 0
@@ -285,20 +287,34 @@ export class Http2WebTransportStream {
285
287
  */
286
288
  writeChunk(buf) {
287
289
  this.outgochunks.push({ buf, fin: false })
288
- this.drainWrites()
290
+ this.capsuleParser.scheduler.Schedule(this.streamid)
291
+ this.capsuleParser.scheduleDrainWrites()
292
+ }
293
+
294
+ hasPendingData() {
295
+ return this.outgochunks.length > 0
289
296
  }
290
297
 
291
298
  drainWrites() {
299
+ let finsend = false
292
300
  while (
293
301
  this.outgochunks.length > 0 &&
294
- (!this.capsuleParser.blocked || this.final) &&
302
+ (!this.capsuleParser.blocked ||
303
+ this.final ||
304
+ !this.capsuleParser.shouldYieldStream(this.streamid)) &&
295
305
  this.flowController.sendWindowSize() > 0n &&
296
306
  this.sessionFlowController.sendWindowSize() > 0n
297
307
  ) {
298
308
  const cur = this.outgochunks.shift()
309
+ let outgoChunkSend = true
299
310
  if (cur) {
300
311
  let payload = cur.buf
301
312
  if (payload) {
313
+ if (payload.byteLength === 0 && !cur.fin) {
314
+ throw new WebTransportError(
315
+ 'Trying to send zero length capsule without a fin'
316
+ )
317
+ }
302
318
  const sessWindow = this.sessionFlowController.sendWindowSize()
303
319
  const streamWindow = this.flowController.sendWindowSize()
304
320
  if (
@@ -319,6 +335,7 @@ export class Http2WebTransportStream {
319
335
  const dest = new Uint8Array(payload.byteLength - len)
320
336
  dest.set(src)
321
337
  this.outgochunks.unshift({ fin: cur.fin, buf: dest })
338
+ outgoChunkSend = false // remaing part is send later
322
339
  }
323
340
  cur.fin = false
324
341
  payload = cur.buf = new Uint8Array(
@@ -335,27 +352,60 @@ export class Http2WebTransportStream {
335
352
  headerVints: [this.streamid],
336
353
  payload
337
354
  })
355
+ finsend ||= !!cur?.fin
338
356
 
339
357
  if (payload) {
340
358
  this.flowController.addBytesSent(payload?.byteLength)
341
359
  this.sessionFlowController.addBytesSent(payload?.byteLength)
342
360
  }
343
361
  }
344
- this.jsobj.onStreamWrite({
345
- success: true
362
+ if (outgoChunkSend) {
363
+ this.jsobj.onStreamWrite({
364
+ success: true
365
+ })
366
+ }
367
+ }
368
+ if (finsend) {
369
+ this.capsuleParser.removeStream(this.streamid)
370
+ return
371
+ }
372
+
373
+ if (
374
+ !(
375
+ !this.capsuleParser.blocked ||
376
+ this.final ||
377
+ !this.capsuleParser.shouldYieldStream(this.streamid)
378
+ ) &&
379
+ this.outgochunks.length > 0
380
+ ) {
381
+ this.capsuleParser.scheduleDrainWriteStream(this.streamid)
382
+ }
383
+ if (this.final && this.outgochunks.length === 0 && !this.finalmessagesend) {
384
+ processnextTick(() => {
385
+ this.jsobj.onStreamNetworkFinish({
386
+ nettask: 'streamFinal'
387
+ })
346
388
  })
389
+ this.finalmessagesend = true
347
390
  }
348
391
  }
349
392
 
350
393
  streamFinal() {
351
394
  this.final = true
352
395
  this.outgochunks.push({ fin: true })
353
- this.drainWrites()
354
- processnextTick(() =>
355
- this.jsobj.onStreamNetworkFinish({
356
- nettask: 'streamFinal'
357
- })
358
- )
396
+ this.capsuleParser.scheduleDrainWriteStream(this.streamid)
397
+ this.capsuleParser.scheduleDrainWrites()
398
+ }
399
+
400
+ /**
401
+ *
402
+ * @param {{sendOrder: bigint, sendGroupId: bigint}} args
403
+ */
404
+ updateSendOrderAndGroup({ sendOrder, sendGroupId }) {
405
+ this.capsuleParser.streamUpdateSendOrderAndGroup(this.streamid, {
406
+ sendOrder,
407
+ sendGroupId
408
+ })
359
409
  }
360
410
 
361
411
  /**
@@ -1 +1 @@
1
- export const supportedVersions = ['kDraft1']
1
+ export const supportedVersions = ['kDraft1', 'kDraft2']
package/lib/server.js CHANGED
@@ -214,7 +214,6 @@ export class HttpServer {
214
214
  setRequestCallback(callback) {
215
215
  this.requestHandler = callback
216
216
 
217
- this.transportInt.setJSRequestHandler(!!callback)
218
217
  if (this.transportInt) this.transportInt.setJSRequestHandler(!!callback)
219
218
  else this._pendingRequestCallback = !!callback
220
219
  }
package/lib/session.js CHANGED
@@ -13,7 +13,6 @@ const log = logger(`webtransport:httpwtsession(${pid})`)
13
13
  * @typedef {import('./types').SessionReadyEvent} SessionReadyEvent
14
14
  * @typedef {import('./types').SessionCloseEvent} SessionCloseEvent
15
15
  * @typedef {import('./types').DatagramReceivedEvent} DatagramReceivedEvent
16
- * @typedef {import('./types').DatagramSendEvent} DatagramSendEvent
17
16
  * @typedef {import('./types').GoawayReceivedEvent} GoawayReceivedEvent
18
17
  * @typedef {import('./types').DatagramStatsEvent} DatagramStatsEvent
19
18
  * @typedef {import('./types').SessionStatsEvent} SessionStatsEvent
@@ -27,8 +26,11 @@ const log = logger(`webtransport:httpwtsession(${pid})`)
27
26
  * @typedef {import('./dom').WebTransportDatagramDuplexStream} WebTransportDatagramDuplexStream
28
27
  * @typedef {import('./dom').WebTransportReliabilityMode} WebTransportReliabilityMode
29
28
  * @typedef {import('./dom').WebTransportCongestionControl} WebTransportCongestionControl
29
+ * @typedef {import('./dom').WebTransportSendGroup} WebTransportSendGroup
30
30
  * @typedef {import('./dom').WebTransportStats} WebTransportStats
31
31
  * @typedef {import('./dom').WebTransportDatagramStats} WebTransportDatagramStats
32
+ * @typedef {import('./dom').WebTransportSendOptions} WebTransportSendOptions
33
+ * @typedef {import('./dom').WebTransportDatagramsWritable} WebTransportDatagramsWritable
32
34
  *
33
35
  * @typedef {import('./types').NativeHttpWTSession} NativeHttpWTSession
34
36
  *
@@ -112,12 +114,6 @@ export class HttpWTSession {
112
114
  }
113
115
  })
114
116
 
115
- /** @type {Array<() => void>} */
116
- this.writeDatagramRes = []
117
- /** @type {Array<(err?: Error) => void>} */
118
- this.writeDatagramRej = []
119
- /** @type {Array<Promise<void>>} */
120
- this.writeDatagramProm = []
121
117
  const readableopts = {
122
118
  start: (
123
119
  /** @type {import("stream/web").ReadableByteStreamController} */ controller
@@ -135,32 +131,85 @@ export class HttpWTSession {
135
131
  /** @type {ReadableStream<Uint8Array>} */
136
132
  // @ts-ignore
137
133
  readable: new ReadableStream(readableopts),
138
- writable: new WritableStream({
139
- start: (controller) => {
140
- this.outgoDatagramController = controller
141
- },
142
- // eslint-disable-next-line no-unused-vars
143
- write: (chunk, controller) => {
144
- if (this.state === 'closed') throw new Error('Session is closed')
145
- if (chunk instanceof Uint8Array) {
146
- /** @type {Promise<void>} */
147
- const ret = new Promise((resolve, reject) => {
148
- this.writeDatagramRes.push(resolve)
149
- this.writeDatagramRej.push(reject)
150
- })
151
- this.writeDatagramProm.push(ret)
152
- log.trace('b4 datagram write', chunk)
153
- if (this.objint == null) {
154
- throw new Error('this.objint is not set')
134
+ /**
135
+ * @param {WebTransportSendOptions|undefined} options
136
+ * @return {import('./dom').WebTransportDatagramsWritable}
137
+ */
138
+ createWritable: (options) => {
139
+ let sendOrder = options?.sendOrder ?? 0n
140
+ let sendGroup = options?.sendGroup
141
+ /** @type {WebTransportSendStream} */
142
+ // @ts-expect-error some props are initially missing
143
+ const retWritable = new WritableStream({
144
+ start: (controller) => {
145
+ this.outgoDatagramController = controller
146
+ },
147
+ // eslint-disable-next-line no-unused-vars
148
+ write: (chunk, controller) => {
149
+ if (this.state === 'closed') throw new Error('Session is closed')
150
+ if (chunk instanceof Uint8Array) {
151
+ /** @type {Promise<void>} */
152
+ if (this.objint == null) {
153
+ throw new Error('this.objint is not set')
154
+ }
155
+ const { code, message } = this.objint.writeDatagram(chunk)
156
+ if (code !== 'success') {
157
+ throw new WebTransportError(code + ':' + message)
158
+ }
159
+ } else throw new Error('chunk is not of type Uint8Array')
160
+ },
161
+ close: () => {
162
+ // do nothing
163
+ }
164
+ })
165
+ Object.defineProperties(retWritable, {
166
+ sendOrder: {
167
+ get: () => {
168
+ return sendOrder
169
+ },
170
+ /**
171
+ * @param {bigint} value
172
+ */
173
+ set: (value) => {
174
+ sendOrder = value
175
+ }
176
+ },
177
+ sendGroup: {
178
+ get: () => {
179
+ return sendGroup
180
+ },
181
+ /**
182
+ * @param {WebTransportSendGroup} value
183
+ */
184
+ set: (value) => {
185
+ if (value !== sendGroup) {
186
+ sendGroup = value
187
+ }
155
188
  }
156
- this.objint.writeDatagram(chunk)
157
- return ret
158
- } else throw new Error('chunk is not of type Uint8Array')
159
- },
160
- close: () => {
161
- // do nothing
189
+ }
190
+ })
191
+ return retWritable
192
+ },
193
+ // @ts-ignore
194
+ get writable() {
195
+ // @ts-ignore
196
+ if (!this.datagramwritablepolyfilled_) {
197
+ console.warn('datagrams.writable is deprecated')
162
198
  }
163
- })
199
+ // @ts-ignore
200
+ return (this.datagramwritablepolyfilled_ ||= this.createWritable())
201
+ },
202
+ get maxDatagramSize() {
203
+ // @ts-ignore
204
+ return this._getMaxDatagramSize()
205
+ },
206
+ // @ts-ignore
207
+ _getMaxDatagramSize: () => {
208
+ if (this.objint == null) {
209
+ throw new Error('this.objint is not set')
210
+ }
211
+ return this.objint.getMaxDatagramSize()
212
+ }
164
213
  }
165
214
 
166
215
  /** @type {Array<(stream: WebTransportBidirectionalStream) => void>} */
@@ -193,6 +242,13 @@ export class HttpWTSession {
193
242
  this.sendStreamsController = new Set()
194
243
  /** @type {Set<ReadableStreamDefaultController>} */
195
244
  this.receiveStreamsController = new Set()
245
+
246
+ this._sendGroupNum = 1n // 0n is reserved for no sendgroup
247
+ /** @type {Map<bigint,WebTransportSendGroup>} */
248
+ this._sendGroupIndex = new Map()
249
+
250
+ /** @type {undefined|string} */
251
+ this._selectedProtocol = undefined
196
252
  }
197
253
 
198
254
  /**
@@ -210,6 +266,10 @@ export class HttpWTSession {
210
266
  }
211
267
  }
212
268
 
269
+ get protocol() {
270
+ return this._selectedProtocol
271
+ }
272
+
213
273
  getStats() {
214
274
  if (this.objint == null) {
215
275
  throw new Error('this.objint not set')
@@ -279,16 +339,6 @@ export class HttpWTSession {
279
339
  })
280
340
  }
281
341
 
282
- async waitForDatagramsSend() {
283
- while (this.writeDatagramProm.length > 0) {
284
- try {
285
- await Promise.allSettled(this.writeDatagramProm)
286
- } catch (error) {
287
- log.error('datagram promise failed ', error)
288
- }
289
- }
290
- }
291
-
292
342
  notifySessionDraining() {
293
343
  if (this.objint == null) {
294
344
  throw new Error('this.objint not set')
@@ -361,7 +411,7 @@ export class HttpWTSession {
361
411
  })
362
412
  const notblocked = this.objint.orderBidiStream({
363
413
  sendGroup: opts?.sendGroup || null, // maybe replace, when implemented
364
- sendOrder: opts?.sendOrder || 0,
414
+ sendOrder: BigInt(opts?.sendOrder || 0n),
365
415
  waitUntilAvailable: opts?.waitUntilAvailable || false
366
416
  })
367
417
  if (!notblocked) {
@@ -388,7 +438,7 @@ export class HttpWTSession {
388
438
  })
389
439
  const notblocked = this.objint.orderUnidiStream({
390
440
  sendGroup: opts?.sendGroup || null, // maybe replace, when implemented
391
- sendOrder: opts?.sendOrder || 0,
441
+ sendOrder: opts?.sendOrder || 0n,
392
442
  waitUntilAvailable: opts?.waitUntilAvailable || false
393
443
  })
394
444
  if (!notblocked) {
@@ -417,7 +467,33 @@ export class HttpWTSession {
417
467
  }
418
468
  }
419
469
 
420
- onReady(/* error */) {
470
+ /**
471
+ * @returns {WebTransportSendGroup}
472
+ */
473
+ createSendGroup() {
474
+ if (this.state === 'closed' || this.state === 'failed')
475
+ throw new Error('InvalidState')
476
+ const _sendGroupId = this._sendGroupNum++
477
+ const sendGroup = {
478
+ // @ts-ignore
479
+ _sendGroupId,
480
+ getStats: async () => {
481
+ // TODO implement
482
+ return {
483
+ bytesWritten: 0n,
484
+ bytesSent: 0n,
485
+ bytesAcknowledged: 0n
486
+ }
487
+ }
488
+ }
489
+ this._sendGroupIndex.set(_sendGroupId, sendGroup)
490
+ return sendGroup
491
+ }
492
+ /**
493
+ * @param {{protocol?: string}} arg
494
+ **/
495
+ onReady({ protocol }) {
496
+ if (protocol) this._selectedProtocol = protocol
421
497
  this.state = 'connected'
422
498
  if (!this.reliable) this.reliability = 'supports-unreliable'
423
499
  else this.reliability = 'reliable-only'
@@ -458,13 +534,9 @@ export class HttpWTSession {
458
534
 
459
535
  for (const rej of this.rejectBiDi) rej(error)
460
536
  for (const rej of this.rejectUniDi) rej(error)
461
- for (const rej of this.writeDatagramRej) rej(error)
462
537
  for (const rej of this.rejectSessionStats) rej(error)
463
538
  for (const rej of this.rejectDatagramStats) rej(error)
464
539
 
465
- this.writeDatagramRej = []
466
- this.writeDatagramRes = []
467
- this.writeDatagramProm = []
468
540
  this.resolveBiDi = []
469
541
  this.resolveUniDi = []
470
542
  this.rejectBiDi = []
@@ -519,7 +591,9 @@ export class HttpWTSession {
519
591
  parentobj: this,
520
592
  transport: this.parentobj,
521
593
  bidirectional: args.bidirectional,
522
- incoming: args.incoming
594
+ incoming: args.incoming,
595
+ sendGroup: this._sendGroupIndex.get(args.sendGroupId || 0n),
596
+ sendOrder: args.sendOrder
523
597
  })
524
598
  this.addStreamObj(strobj)
525
599
  if (args.incoming) {
@@ -563,6 +637,11 @@ export class HttpWTSession {
563
637
  */
564
638
  onDatagramReceived(args) {
565
639
  log.trace('datagram received', args.datagram)
640
+ // streams spec says zero length chunk on byob stream is illegal
641
+ if (args.datagram.byteLength === 0) {
642
+ log.trace('zerolength datagram dropped')
643
+ return
644
+ }
566
645
  // console.log('datagram received', args.datagram, Date.now())
567
646
  if (this.incomDatagramController.byobRequest) {
568
647
  /** @type {ReadableStreamBYOBRequest} */
@@ -588,21 +667,6 @@ export class HttpWTSession {
588
667
  }
589
668
  }
590
669
 
591
- /**
592
- * @param {DatagramSendEvent} args
593
- */
594
- // eslint-disable-next-line no-unused-vars
595
- onDatagramSend(args) {
596
- if (this.state === 'closed') return
597
- this.writeDatagramRej.shift()
598
- this.writeDatagramProm.shift()
599
- const res = this.writeDatagramRes.shift()
600
-
601
- if (res != null) {
602
- res()
603
- }
604
- }
605
-
606
670
  /**
607
671
  * @param {GoawayReceivedEvent} args
608
672
  */