@fails-components/webtransport 1.1.4 → 1.2.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 (53) 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 +38 -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 +32 -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 +11 -2
  36. package/lib/http2/node/capsuleparser.js +14 -5
  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 +10 -3
  40. package/lib/http2/parserbase.js +84 -7
  41. package/lib/http2/priorityscheduler.js +654 -0
  42. package/lib/http2/session.js +83 -23
  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 +133 -64
  47. package/lib/stream.js +50 -8
  48. package/lib/types.ts +15 -8
  49. package/lib/webtransport.browser.js +84 -18
  50. package/lib/webtransportbase.js +7 -0
  51. package/old_test/testsuite.js +6 -2
  52. package/package.json +6 -6
  53. package/readme.md +3 -1
@@ -124,7 +124,9 @@ export class BrowserParser extends ParserBase {
124
124
 
125
125
  const offsetend = bufferstate.size
126
126
 
127
- const type = Number(readVarInt(bufferstate))
127
+ const rtype = readVarInt(bufferstate)
128
+ if (typeof rtype === 'undefined') return
129
+ const type = Number(rtype)
128
130
 
129
131
  switch (type) {
130
132
  case ParserBase.PADDING:
@@ -162,7 +164,10 @@ export class BrowserParser extends ParserBase {
162
164
  if (typeof streamid !== 'undefined') {
163
165
  let object = this.wtstreams.get(streamid)
164
166
  if (!object) {
165
- object = this.newStream(streamid)
167
+ object = this.newStream(streamid, {
168
+ sendOrder: 0n,
169
+ sendGroupId: 0n
170
+ })
166
171
  if (!object) return // stream broken
167
172
  }
168
173
  // TODO submit data
@@ -282,4 +287,8 @@ export class BrowserParser extends ParserBase {
282
287
  closeHttp2Stream(code) {
283
288
  this.ws.close(1000)
284
289
  }
290
+
291
+ initialParametersMandatory() {
292
+ return true
293
+ }
285
294
  }
@@ -64,9 +64,9 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
64
64
  )
65
65
  return
66
66
  }
67
- const type = Number(readVarInt(bufferstate))
67
+ const rtype = readVarInt(bufferstate)
68
68
  if (
69
- typeof type === 'undefined' ||
69
+ typeof rtype === 'undefined' ||
70
70
  bufferstate.size < 1 + bufferstate.offset
71
71
  ) {
72
72
  this.saveddata = Buffer.from(
@@ -76,8 +76,9 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
76
76
  )
77
77
  return
78
78
  }
79
- const length = Number(readVarInt(bufferstate))
80
- if (typeof length === 'undefined') {
79
+ const type = Number(rtype)
80
+ const rlength = readVarInt(bufferstate)
81
+ if (typeof rlength === 'undefined') {
81
82
  this.saveddata = Buffer.from(
82
83
  bufferstate.buffer.buffer,
83
84
  capsulestart,
@@ -85,6 +86,7 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
85
86
  )
86
87
  return
87
88
  }
89
+ const length = Number(rlength)
88
90
  let checklength = length // we want to read most times the full capsule
89
91
  const offsetend = Math.min(
90
92
  bufferstate.offset + length,
@@ -154,7 +156,10 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
154
156
  if (typeof streamid !== 'undefined') {
155
157
  let object = this.wtstreams.get(streamid)
156
158
  if (!object) {
157
- object = this.newStream(streamid)
159
+ object = this.newStream(streamid, {
160
+ sendOrder: 0n,
161
+ sendGroupId: 0n
162
+ })
158
163
  if (!object) return // stream broken
159
164
  }
160
165
  // TODO submit data
@@ -334,4 +339,8 @@ export class Http2CapsuleParser extends ParserBaseHttp2 {
334
339
  this.stream.close(code)
335
340
  this.stream.destroy()
336
341
  }
342
+
343
+ initialParametersMandatory() {
344
+ return false
345
+ }
337
346
  }
@@ -15,6 +15,7 @@ export class Http2WebTransportClient {
15
15
  this.port = Number(port)
16
16
  this.hostname = args?.host || 'localhost'
17
17
  this.serverCertificateHashes = args?.serverCertificateHashes || undefined
18
+ this.protocols = args?.protocols || []
18
19
  this.localPort = Number(args?.localPort) || undefined
19
20
  this.allowPooling = args?.allowPooling || false
20
21
  this.forceIpv6 = args?.forceIpv6 || false
@@ -29,12 +30,12 @@ export class Http2WebTransportClient {
29
30
  args?.initialUnidirectionalSendStreams || 100
30
31
 
31
32
  this.streamShouldAutoTuneReceiveWindow =
32
- args.streamShouldAutoTuneReceiveWindow || false
33
+ args.streamShouldAutoTuneReceiveWindow || true
33
34
  this.streamFlowControlWindowSizeLimit =
34
35
  args?.streamFlowControlWindowSizeLimit || 6 * 1024 * 1024
35
36
 
36
37
  this.sessionShouldAutoTuneReceiveWindow =
37
- args.sessionShouldAutoTuneReceiveWindow || false
38
+ args.sessionShouldAutoTuneReceiveWindow || true
38
39
  this.sessionFlowControlWindowSizeLimit =
39
40
  args?.sessionFlowControlWindowSizeLimit || 15 * 1024 * 1024
40
41
  /** @type {import('../../session.js').HttpClient} */
@@ -92,9 +93,9 @@ export class Http2WebTransportClient {
92
93
  0x2b63: this.initialStreamFlowControlWindow, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAM_DATA_BIDI
93
94
  0x2b64: this.initialUnidirectionalStreams, // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_UNI
94
95
  0x2b65: this.initialBidirectionalStreams // SETTINGS_WEBTRANSPORT_INITIAL_MAX_STREAMS_BIDI
95
- },
96
- remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65]
96
+ }
97
97
  },
98
+ remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65],
98
99
  localPort: this.localPort,
99
100
  // TODO: REMOVE BEFORE RELEASE; UNSAFE SETTING
100
101
  rejectUnauthorized: !this.serverCertificateHashes
@@ -109,26 +110,8 @@ export class Http2WebTransportClient {
109
110
  http2Options
110
111
  )
111
112
 
112
- let rtt = 100
113
- let adjust = 1
114
- // ok we got a session and want to measure RTT
115
- let pingsender = setInterval(() => {
116
- if (this.clientInt && !this.clientInt.closed) {
117
- // eslint-disable-next-line no-unused-vars
118
- this.clientInt.ping((err, duration, payload) => {
119
- if (!err) {
120
- rtt = adjust * duration + (1 - adjust) * rtt
121
- adjust = 0.2
122
- // @ts-ignore
123
- this.clientInt.WTrtt = rtt
124
- }
125
- })
126
- } else {
127
- clearInterval(pingsender)
128
- // @ts-ignore
129
- pingsender = undefined
130
- }
131
- }, 1000)
113
+ /** @type {NodeJS.Timeout|undefined} */
114
+ let pingsender
132
115
 
133
116
  this.clientInt.on('close', () => {
134
117
  if (pingsender) clearInterval(pingsender)
@@ -180,6 +163,28 @@ export class Http2WebTransportClient {
180
163
  this.jsobj.onClientConnected({
181
164
  success: true
182
165
  })
166
+ let rtt = 100
167
+ let adjust = 1
168
+ // ok we got a session and want to measure RTT
169
+ const pingupdater = () => {
170
+ if (this.clientInt && !this.clientInt.closed) {
171
+ // eslint-disable-next-line no-unused-vars
172
+ this.clientInt.ping((err, duration, payload) => {
173
+ if (!err) {
174
+ rtt = adjust * duration + (1 - adjust) * rtt
175
+ adjust = 0.2
176
+ // @ts-ignore
177
+ this.clientInt.WTrtt = rtt
178
+ }
179
+ })
180
+ } else {
181
+ clearInterval(pingsender)
182
+ // @ts-ignore
183
+ pingsender = undefined
184
+ }
185
+ }
186
+ pingsender = setInterval(pingupdater, 1000)
187
+ pingupdater()
183
188
  }
184
189
  })
185
190
  this.clientInt.on('error', (error) => {
@@ -207,14 +212,20 @@ export class Http2WebTransportClient {
207
212
  openWTSession(path) {
208
213
  if (!this.clientInt) throw new Error('clientInt not present')
209
214
 
210
- const stream = this.clientInt.request({
215
+ const requestOpts = {
211
216
  ':method': 'CONNECT',
212
217
  ':protocol': 'webtransport',
213
218
  ':scheme': 'https',
214
219
  ':path': path,
215
220
  authority: this.hostname,
216
221
  origin: this.hostname
217
- })
222
+ }
223
+ if (this.protocols.length > 0) {
224
+ // @ts-ignore
225
+ requestOpts['wt-available-protocols'] = this.protocols.join(',')
226
+ }
227
+
228
+ const stream = this.clientInt.request(requestOpts)
218
229
  const {
219
230
  0x2b65: remoteBidirectionalStreams = undefined,
220
231
  0x2b64: remoteUnidirectionalStreams = undefined,
@@ -255,7 +266,7 @@ export class Http2WebTransportClient {
255
266
  sendWindowOffset:
256
267
  remoteSessionFlowControlWindow ||
257
268
  this.sessionFlowControlWindowSizeLimit,
258
- receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
269
+ receiveWindowOffset: this.initialSessionFlowControlWindow,
259
270
  shouldAutoTuneReceiveWindow: this.sessionShouldAutoTuneReceiveWindow,
260
271
  receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
261
272
  }),
@@ -33,12 +33,12 @@ export class Http2WebTransportServer {
33
33
  args?.initialUnidirectionalStreams || 100
34
34
 
35
35
  this.streamShouldAutoTuneReceiveWindow =
36
- args.streamShouldAutoTuneReceiveWindow || false
36
+ args.streamShouldAutoTuneReceiveWindow || true
37
37
  this.streamFlowControlWindowSizeLimit =
38
38
  args?.streamFlowControlWindowSizeLimit || 6 * 1024 * 1024
39
39
 
40
40
  this.sessionShouldAutoTuneReceiveWindow =
41
- args.sessionShouldAutoTuneReceiveWindow || false
41
+ args.sessionShouldAutoTuneReceiveWindow || true
42
42
  this.sessionFlowControlWindowSizeLimit =
43
43
  args?.sessionFlowControlWindowSizeLimit || 15 * 1024 * 1024
44
44
 
@@ -65,9 +65,9 @@ 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
- },
69
- remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65]
70
- }
68
+ }
69
+ },
70
+ remoteCustomSettings: [0x2b60, 0x2b61, 0x2b62, 0x2b63, 0x2b64, 0x2b65]
71
71
  })
72
72
 
73
73
  this.serverInt.on('listening', () => {
@@ -95,7 +95,7 @@ export class Http2WebTransportServer {
95
95
  let rtt = 100
96
96
  let adjust = 1
97
97
  // ok we got a session and want to measure RTT
98
- let pingsender = setInterval(() => {
98
+ const pingupdater = () => {
99
99
  if (!session.closed)
100
100
  // eslint-disable-next-line no-unused-vars
101
101
  session.ping((err, duration, payload) => {
@@ -111,7 +111,9 @@ export class Http2WebTransportServer {
111
111
  // @ts-ignore
112
112
  pingsender = undefined
113
113
  }
114
- }, 1000)
114
+ }
115
+ let pingsender = setInterval(pingupdater, 1000)
116
+ pingupdater()
115
117
 
116
118
  session.on('close', () => {
117
119
  if (pingsender) clearInterval(pingsender)
@@ -136,7 +138,11 @@ export class Http2WebTransportServer {
136
138
  path = path?.slice(1)
137
139
  }
138
140
  const header = { ...request.headers, ':path': path }
139
- const websocketProt = this.checkProtocolHeader(header)
141
+ const { websocketProt, webtransportProt } =
142
+ this.checkProtocolHeader(header)
143
+ if (webtransportProt) {
144
+ header['wt-available-protocols'] = webtransportProt
145
+ }
140
146
  if (!websocketProt) {
141
147
  stream.destroy()
142
148
  return
@@ -227,7 +233,11 @@ export class Http2WebTransportServer {
227
233
  header[':protocol'] === 'websocket' &&
228
234
  header['sec-websocket-protocol']
229
235
  ) {
230
- websocketProt = this.checkProtocolHeader(header)
236
+ let obj = this.checkProtocolHeader(header)
237
+ websocketProt = obj.websocketProt
238
+ if (obj.webtransportProt) {
239
+ header['wt-available-protocols'] = obj.webtransportProt
240
+ }
231
241
  }
232
242
  if (header[':protocol'] !== 'webtransport' && !websocketProt) {
233
243
  stream.respond({
@@ -334,8 +344,11 @@ export class Http2WebTransportServer {
334
344
  const resp = {
335
345
  ':status': '200'
336
346
  }
337
- // @ts-ignore
338
- if (websocketProt) resp['sec-websocket-protocol'] = websocketProt
347
+
348
+ if (websocketProt) {
349
+ // @ts-ignore
350
+ resp['sec-websocket-protocol'] = websocketProt
351
+ }
339
352
  stream.respond(resp)
340
353
  this.jsobj.onHttpWTSessionVisitor(retObj)
341
354
  } else {
@@ -352,9 +365,12 @@ export class Http2WebTransportServer {
352
365
 
353
366
  /**
354
367
  * @param {import("http2").IncomingHttpHeaders} header
368
+ * @return {{websocketProt: string|undefined, webtransportProt?: string}}
355
369
  */
356
370
  checkProtocolHeader(header) {
357
371
  const sechead = header['sec-websocket-protocol']
372
+ ?.split?.(',')
373
+ ?.map?.((el) => el.trim())
358
374
  let prots
359
375
  if (!Array.isArray(sechead)) {
360
376
  prots = [sechead]
@@ -365,8 +381,19 @@ export class Http2WebTransportServer {
365
381
  .map((el) => (el ? el.split('_') : [undefined, undefined]))
366
382
  .filter((el) => el[0] === 'webtransport')
367
383
  .filter((el) => (el[1] ? supportedVersions.includes(el[1]) : false))
368
- if (prots.length > 0) return prots[0].join('_')
369
- else return undefined
384
+ if (prots.length > 0) {
385
+ const selWtVersion = prots[0][1]
386
+ prots = prots.filter((el) => el[1] === selWtVersion)
387
+ /** @type {{websocketProt: string|undefined, webtransportProt?: string}} */
388
+ const retObj = { websocketProt: prots[0].slice(0, 2).join('_') }
389
+ if (prots[0][2]) {
390
+ // @ts-ignore
391
+ retObj.webtransportProt = [
392
+ ...new Set(prots.map((el) => el.slice(2).join('_')))
393
+ ].join(',')
394
+ }
395
+ return retObj
396
+ } else return { websocketProt: undefined }
370
397
  }
371
398
 
372
399
  startServer() {
@@ -444,7 +471,8 @@ export class Http2WebTransportServer {
444
471
  protocol,
445
472
  head,
446
473
  path,
447
- transportPrivate
474
+ transportPrivate,
475
+ selectedProtocol
448
476
  }) {
449
477
  if (status !== 200) {
450
478
  if (protocol === 'websocketoverhttp1') {
@@ -462,8 +490,11 @@ export class Http2WebTransportServer {
462
490
  // @ts-ignore
463
491
  stream,
464
492
  header,
465
- // @ts-ignore
466
- protocol: transportPrivate.websocketProt
493
+ protocol: selectedProtocol
494
+ ? // @ts-ignore
495
+ transportPrivate.websocketProt + '_' + selectedProtocol
496
+ : // @ts-ignore
497
+ transportPrivate.websocketProt
467
498
  })
468
499
  .then(() => {
469
500
  const retObj = {
@@ -496,7 +527,7 @@ export class Http2WebTransportServer {
496
527
  initialUnidirectionalReceiveStreams:
497
528
  this.initialUnidirectionalStreams,
498
529
  sendWindowOffset: 0,
499
- receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
530
+ receiveWindowOffset: this.initialSessionFlowControlWindow,
500
531
  shouldAutoTuneReceiveWindow:
501
532
  this.sessionShouldAutoTuneReceiveWindow,
502
533
  receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
@@ -518,7 +549,13 @@ export class Http2WebTransportServer {
518
549
  // @ts-ignore
519
550
  if (transportPrivate?.websocketProt)
520
551
  // @ts-ignore
521
- resp['sec-websocket-protocol'] = transportPrivate.websocketProt
552
+ resp['sec-websocket-protocol'] = selectedProtocol
553
+ ? // @ts-ignore
554
+ transportPrivate.websocketProt + '_' + selectedProtocol
555
+ : // @ts-ignore
556
+ transportPrivate.websocketProt
557
+ // @ts-ignore
558
+ else if (selectedProtocol) resp['wt-protocol'] = selectedProtocol
522
559
  stream.respond(resp)
523
560
  const {
524
561
  0x2b65: remoteBidirectionalStreams = undefined,
@@ -587,7 +624,7 @@ export class Http2WebTransportServer {
587
624
  ? remoteSessionFlowControlWindow ||
588
625
  this.sessionFlowControlWindowSizeLimit
589
626
  : 0, // TODO, once supported by node, use initial settings
590
- receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
627
+ receiveWindowOffset: this.initialSessionFlowControlWindow,
591
628
  shouldAutoTuneReceiveWindow:
592
629
  this.sessionShouldAutoTuneReceiveWindow,
593
630
  receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
@@ -132,8 +132,8 @@ function applyMask(ms, buffer, offset, length) {
132
132
  for (let run = 0 /* Math.round(length / 4) * 4 */; run < length; run++) {
133
133
  data[run] ^= workmask[run % 4]
134
134
  }
135
- ms.offset += length
136
135
  }
136
+ ms.offset += length
137
137
  }
138
138
  /*
139
139
  function readDWord(bs) {
@@ -458,7 +458,7 @@ export class WebSocketParser extends ParserBaseHttp2 {
458
458
  let wbufferstate
459
459
  if (
460
460
  type !== ParserBase.WT_STREAM_WOFIN ||
461
- type !== ParserBase.WT_STREAM_WOFIN
461
+ type !== ParserBase.WT_STREAM_WFIN
462
462
  ) {
463
463
  if (fin) {
464
464
  wbufferstate = bufferstate
@@ -541,7 +541,10 @@ export class WebSocketParser extends ParserBaseHttp2 {
541
541
  if (typeof streamid !== 'undefined') {
542
542
  let object = this.wtstreams.get(streamid)
543
543
  if (!object) {
544
- object = this.newStream(streamid)
544
+ object = this.newStream(streamid, {
545
+ sendOrder: 0n,
546
+ sendGroupId: 0n
547
+ })
545
548
  if (!object) return // stream broken
546
549
  }
547
550
  // TODO submit data
@@ -873,4 +876,8 @@ export class WebSocketParser extends ParserBaseHttp2 {
873
876
  } else if (stream.end) stream.end()
874
877
  else throw new Error('http2:session not close method')
875
878
  }
879
+
880
+ initialParametersMandatory() {
881
+ return true
882
+ }
876
883
  }
@@ -1,5 +1,6 @@
1
1
  import { Http2WebTransportStream } from './stream.js'
2
2
  import { logger } from '../utils.js'
3
+ import { PriorityScheduler } from './priorityscheduler.js'
3
4
 
4
5
  const pid = typeof process !== 'undefined' ? process.pid : 0
5
6
  const log = logger(`webtransport:parserbase(${pid})`)
@@ -59,6 +60,8 @@ export class ParserBase {
59
60
 
60
61
  /** @type {Map<bigint,Http2WebTransportStream>} */
61
62
  this.wtstreams = new Map()
63
+
64
+ this.scheduler = new PriorityScheduler()
62
65
  }
63
66
 
64
67
  /**
@@ -79,6 +82,14 @@ export class ParserBase {
79
82
  throw new Error('Implement writeCapsule in derived Class')
80
83
  }
81
84
 
85
+ /**
86
+ * @abstract
87
+ * @return{boolean}
88
+ */
89
+ initialParametersMandatory() {
90
+ throw new Error('Implement initialParametersMandatory in derived Class')
91
+ }
92
+
82
93
  /**
83
94
  * @param{{code: Number, reason: string}}arg
84
95
  */
@@ -101,8 +112,9 @@ export class ParserBase {
101
112
 
102
113
  /**
103
114
  * @param {bigint} streamid
115
+ * @param {{sendOrder: bigint,sendGroupId: bigint}} priority
104
116
  */
105
- newStream(streamid) {
117
+ newStream(streamid, priority) {
106
118
  const incoming = this.isclient ? !(streamid & 0x1n) : !!(streamid & 0x1n)
107
119
  const streamIdManager =
108
120
  streamid & 0x2n
@@ -138,16 +150,36 @@ export class ParserBase {
138
150
  streamIdManager
139
151
  })
140
152
  this.wtstreams.set(streamid, stream)
153
+ this.scheduler.Register(streamid, priority)
141
154
  this.session.jsobj.onStream({
142
155
  bidirectional: !(streamid & 0x2n),
143
156
  incoming,
144
- stream
157
+ stream,
158
+ sendGroupId: priority.sendGroupId,
159
+ sendOrder: priority.sendOrder
145
160
  })
146
161
  return stream
147
162
  }
148
163
 
164
+ scheduleDrainWrites() {
165
+ if (this._scheduledDrainWriteCall) return
166
+ const prom = Promise.resolve()
167
+ this._scheduledDrainWriteCall = prom
168
+ prom
169
+ .then(() => {
170
+ delete this._scheduledDrainWriteCall
171
+ this.drainWrites()
172
+ })
173
+ .catch((error) => log('Error in drainWrites', error))
174
+ }
175
+
149
176
  drainWrites() {
150
- for (const stream of this.wtstreams.values()) {
177
+ if (!this.blocked) this.session.drainWrites()
178
+ while (!this.blocked) {
179
+ const frontId = this.scheduler.PopFront()
180
+ if (typeof frontId === 'undefined') break
181
+ const stream = this.wtstreams.get(frontId)
182
+ if (!stream) break
151
183
  stream.drainWrites()
152
184
  }
153
185
  }
@@ -156,8 +188,16 @@ export class ParserBase {
156
188
  * @param {bigint|undefined} val
157
189
  */
158
190
  onMaxData(val) {
159
- if (val && this.session.flowController.updateSendWindowOffset(val))
160
- this.drainWrites()
191
+ if (val && this.session.flowController.updateSendWindowOffset(val)) {
192
+ let pending = false
193
+ this.wtstreams.forEach((stream, streamid) => {
194
+ if (stream.hasPendingData()) {
195
+ pending = true
196
+ this.scheduleDrainWriteStream(streamid)
197
+ }
198
+ })
199
+ if (pending) this.drainWrites()
200
+ }
161
201
  }
162
202
 
163
203
  /**
@@ -167,8 +207,10 @@ export class ParserBase {
167
207
  onMaxStreamData(streamid, offset) {
168
208
  const object = this.wtstreams.get(streamid)
169
209
  if (object && offset) {
170
- if (object.flowController.updateSendWindowOffset(offset))
171
- object.drainWrites()
210
+ if (object.flowController.updateSendWindowOffset(offset)) {
211
+ this.scheduleDrainWriteStream(streamid)
212
+ this.drainWrites()
213
+ }
172
214
  }
173
215
  }
174
216
 
@@ -251,6 +293,41 @@ export class ParserBase {
251
293
  this.session.jsobj.onGoAwayReceived()
252
294
  }
253
295
 
296
+ /**
297
+ *
298
+ * @param {bigint} streamid
299
+ */
300
+ shouldYieldStream(streamid) {
301
+ return this.scheduler.ShouldYield(streamid)
302
+ }
303
+
304
+ /**
305
+ *
306
+ * @param {bigint} streamid
307
+ */
308
+ scheduleDrainWriteStream(streamid) {
309
+ this.scheduler.Schedule(streamid)
310
+ }
311
+
312
+ /**
313
+ *
314
+ * @param {bigint} streamid
315
+ */
316
+ removeStream(streamid) {
317
+ this.scheduler.Unregister(streamid)
318
+ //this.wtstreams.delete(streamid) // why does this create troubles
319
+ }
320
+
321
+ /**
322
+ *
323
+ * @param {bigint} streamid
324
+ * @param {{sendOrder: bigint, sendGroupId: bigint}} arg2
325
+ */
326
+ streamUpdateSendOrderAndGroup(streamid, { sendOrder, sendGroupId }) {
327
+ this.scheduler.UpdateSendGroup(streamid, sendGroupId)
328
+ this.scheduler.UpdateSendOrder(streamid, sendOrder)
329
+ }
330
+
254
331
  /**
255
332
  * @param {number} code
256
333
  */