@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,57 +0,0 @@
1
- /**
2
- * Read a stream contents to the end and return it
3
- *
4
- * @template T
5
- * @param {ReadableStream<T>} readable
6
- * @param {number} [expected]
7
- * @returns {T[]}
8
- */
9
- export async function readStream(readable, expected) {
10
- const reader = readable.getReader()
11
-
12
- try {
13
- /** @type {T[]} */
14
- const output = []
15
- let outputlength = 0
16
-
17
- while (true) {
18
- const { done, value } = await reader.read()
19
-
20
- if (done) {
21
- break
22
- }
23
-
24
- if (value != null) {
25
- outputlength += value.length
26
- output.push(value)
27
- }
28
-
29
- if (expected != null && outputlength >= expected) {
30
- break
31
- }
32
- }
33
-
34
- return output
35
- } finally {
36
- reader.releaseLock()
37
- }
38
- }
39
-
40
- /**
41
- * @param {ReadableStream<Uint8Array>} stream
42
- * @returns {string}
43
- */
44
- export async function readStringFromStream(stream) {
45
- const decoder = new TextDecoder()
46
- let output = ''
47
-
48
- for (const buf of await readStream(stream)) {
49
- output += decoder.decode(buf, {
50
- stream: true
51
- })
52
- }
53
-
54
- output += decoder.decode()
55
-
56
- return output
57
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * @template T
3
- * @param {ReadableStream<T>} readableStream
4
- * @returns {Promise<T>}
5
- */
6
- export async function getReaderValue(readableStream) {
7
- const reader = readableStream.getReader()
8
-
9
- try {
10
- const { done, value } = await reader.read()
11
-
12
- if (done) {
13
- throw new Error('Stream ended')
14
- }
15
-
16
- if (!value) {
17
- throw new Error('Stream value was undefined')
18
- }
19
-
20
- return value
21
- } finally {
22
- reader.releaseLock()
23
- }
24
- }
25
-
26
- /**
27
- * @template T
28
- * @param {ReadableStream<T>} readableStream
29
- * @returns {AsyncGenerator<T>}
30
- */
31
- export async function* getReaderStream(readableStream) {
32
- const reader = readableStream.getReader()
33
-
34
- try {
35
- while (true) {
36
- const { done, value } = await reader.read()
37
-
38
- if (done) {
39
- return
40
- }
41
-
42
- if (!value) {
43
- throw new Error('Stream value was undefined')
44
- }
45
-
46
- yield value
47
- }
48
- } finally {
49
- reader.releaseLock()
50
- }
51
- }
@@ -1,467 +0,0 @@
1
- import { generateWebTransportCertificate } from './certificate.js'
2
- import { Http2Server, Http3Server } from '../../lib/index.node.js'
3
- import { pTimeout } from './p-timeout.js'
4
- import { getReaderStream, getReaderValue } from './reader-value.js'
5
- import { writeStream } from './write-stream.js'
6
- import { readStream } from './read-stream.js'
7
- import * as ui8 from 'uint8arrays'
8
- import { KNOWN_BYTES, KNOWN_BYTES_LENGTH } from './known-bytes.js'
9
-
10
- export async function createServer() {
11
- const attrs = [
12
- { shortName: 'C', value: 'DE' },
13
- { shortName: 'ST', value: 'Berlin' },
14
- { shortName: 'L', value: 'Berlin' },
15
- { shortName: 'O', value: 'WebTransport Test Server' },
16
- { shortName: 'CN', value: '127.0.0.1' }
17
- ]
18
-
19
- const certificate = await generateWebTransportCertificate(attrs, {
20
- days: 13
21
- })
22
-
23
- if (certificate == null) {
24
- throw new Error('Certificate generation failed')
25
- }
26
-
27
- /** @type {Http2Server|Http3Server} */
28
- let server
29
- if (process.env.USE_HTTP2 === 'true') {
30
- server = new Http2Server({
31
- /* port: 8080,
32
- host: '0.0.0.0', */
33
- port: 0,
34
- host: '127.0.0.1',
35
- secret: 'mysecret',
36
- cert: certificate.cert, // unclear if it is the correct format
37
- privKey: certificate.private
38
- })
39
- } else {
40
- server = new Http3Server({
41
- /* port: 8080,
42
- host: '0.0.0.0', */
43
- port: 0,
44
- host: '127.0.0.1',
45
- secret: 'mysecret',
46
- cert: certificate.cert, // unclear if it is the correct format
47
- privKey: certificate.private
48
- })
49
- }
50
-
51
- let adjustlimit = 1
52
- if (process.env.USE_HTTP2 === 'true') {
53
- adjustlimit = 0
54
- }
55
-
56
- server.ready
57
- .then(async () => {
58
- server.setRequestCallback(async (args) => {
59
- const url = args.header[':path']
60
- const [path] = url.split('?')
61
-
62
- if (server.sessionController[path] == null) {
63
- return {
64
- ...args,
65
- path,
66
- status: 404
67
- }
68
- }
69
-
70
- return {
71
- ...args,
72
- path,
73
- userData: {
74
- search: url.substring(path.length)
75
- },
76
- header: {
77
- ...args.header,
78
- ':path': path
79
- },
80
- status: 200
81
- }
82
- })
83
-
84
- // set up listeners for the different server paths used by the tests
85
- console.log('server ready')
86
- await Promise.all(
87
- [
88
- // echo server, initiated by remote
89
- async () => {
90
- for await (const session of getReaderStream(
91
- server.sessionStream('/bidirectional_client_initiated_echo')
92
- )) {
93
- try {
94
- const bidiStream = await getReaderValue(
95
- session.incomingBidirectionalStreams
96
- )
97
-
98
- // redirect input to output
99
- await bidiStream.readable.pipeTo(bidiStream.writable)
100
- } catch {
101
- // in some tests the client closes the stream
102
- }
103
- }
104
- },
105
-
106
- // echo server, initiated by local
107
- async () => {
108
- for await (const session of getReaderStream(
109
- server.sessionStream('/bidirectional_server_initiated_echo')
110
- )) {
111
- try {
112
- const stream = await session.createBidirectionalStream()
113
-
114
- await writeStream(stream.writable, KNOWN_BYTES)
115
-
116
- const received = await readStream(
117
- stream.readable,
118
- KNOWN_BYTES_LENGTH
119
- )
120
- // await stream.readable.cancel() // cancel so that the client can progress
121
-
122
- // if we did not get the data we sent, close the session with a reason
123
- if (
124
- !ui8.equals(ui8.concat(KNOWN_BYTES), ui8.concat(received))
125
- ) {
126
- session.close({
127
- closeCode: 500,
128
- reason: 'data did not match'
129
- })
130
- } else {
131
- session.close()
132
- }
133
- } catch {
134
- // in some tests the client closes the stream
135
- }
136
- }
137
- },
138
-
139
- // echo server, initiated by local
140
- async () => {
141
- for await (const session of getReaderStream(
142
- server.sessionStream('/bidirectional_server_fin_send')
143
- )) {
144
- try {
145
- // adapted from issue of achingbrain
146
- const stream = await session.createBidirectionalStream()
147
-
148
- const writer = stream.writable.getWriter()
149
-
150
- await writer.ready
151
-
152
- writer.write(Uint8Array.from([0, 1, 2, 3])).catch((err) => {
153
- console.info('error writing to stream', err)
154
- })
155
-
156
- await writer.close()
157
- } catch {
158
- // in some tests the client closes the stream
159
- }
160
- }
161
- },
162
-
163
- // echo datagrams, initiated by remote
164
- async () => {
165
- for await (const session of getReaderStream(
166
- server.sessionStream('/datagrams_client_send')
167
- )) {
168
- // datagram transport is unreliable, at least one message should make it through
169
- const expected = 1
170
-
171
- try {
172
- const received = await pTimeout(
173
- readStream(session.datagrams.readable, expected),
174
- 1000
175
- )
176
-
177
- // if we did not get the data we sent, close the session with a reason
178
- if (received.length !== expected) {
179
- throw new Error('Did not receive enough bytes')
180
- }
181
-
182
- session.close()
183
- } catch (/** @type {any} */ err) {
184
- session.close({
185
- closeCode: 500,
186
- reason: err.message
187
- })
188
- }
189
- }
190
- },
191
-
192
- // echo datagrams, initiated by local
193
- async () => {
194
- for await (const session of getReaderStream(
195
- server.sessionStream('/datagrams_server_send')
196
- )) {
197
- const writer = session.datagrams.writable.getWriter()
198
- let closed = false
199
-
200
- // write datagrams until the client receives one and closes the connection
201
- // eslint-disable-next-line promise/catch-or-return
202
- Promise.resolve().then(async () => {
203
- while (!closed) {
204
- try {
205
- await writer.ready
206
- await writer.write(Uint8Array.from([0, 1, 2, 3, 4]))
207
- await new Promise((resolve) => setTimeout(resolve, 1)) // do not flood everything
208
- } catch {
209
- // the session can be closed while we are writing
210
- }
211
- }
212
- })
213
-
214
- await session.closed
215
- closed = true
216
- }
217
- },
218
-
219
- // receive 100+ bidi streams and block
220
- async () => {
221
- for await (const session of getReaderStream(
222
- server.sessionStream('/streamlimits_getbidis_wua')
223
- )) {
224
- try {
225
- await session.ready
226
- const bidistreams = []
227
- while (bidistreams.length < 100 - adjustlimit) {
228
- bidistreams.push(
229
- await getReaderValue(session.incomingBidirectionalStreams)
230
- )
231
- }
232
- await getReaderValue(session.incomingUnidirectionalStreams)
233
- await getReaderValue(session.incomingUnidirectionalStreams)
234
- for (let i = 0; i < 50 + adjustlimit; i++) {
235
- /* const curstream = */ bidistreams.shift()
236
- // await curstream.writable.close() // canceled by client
237
- }
238
- while (bidistreams.length < 100 - adjustlimit) {
239
- bidistreams.push(
240
- await getReaderValue(session.incomingBidirectionalStreams)
241
- )
242
- }
243
- await getReaderValue(session.incomingUnidirectionalStreams)
244
- await session.close()
245
- // eslint-disable-next-line no-unused-vars
246
- } catch (error) {
247
- // do not crash server, if a problem occurs...
248
- }
249
- }
250
- },
251
- // receive 100+ bidi streams and do not block
252
- async () => {
253
- for await (const session of getReaderStream(
254
- server.sessionStream('/streamlimits_getbidis')
255
- )) {
256
- try {
257
- await session.ready
258
- const bidistreams = []
259
- while (bidistreams.length < 150) {
260
- bidistreams.push(
261
- await getReaderValue(session.incomingBidirectionalStreams)
262
- )
263
- }
264
- await session.close()
265
- // eslint-disable-next-line no-unused-vars
266
- } catch (error) {
267
- // do not crash server, if a problem occurs...
268
- }
269
- }
270
- },
271
- async () => {
272
- for await (const session of getReaderStream(
273
- server.sessionStream('/streamlimits_getunidis_wua')
274
- )) {
275
- try {
276
- await session.ready
277
- const unidistreams = []
278
- while (unidistreams.length < 100 - adjustlimit) {
279
- unidistreams.push(
280
- await getReaderValue(session.incomingUnidirectionalStreams)
281
- )
282
- }
283
- await getReaderValue(session.incomingBidirectionalStreams)
284
- await getReaderValue(session.incomingBidirectionalStreams)
285
- for (let i = 0; i < 50 + adjustlimit; i++) {
286
- unidistreams.shift()
287
- }
288
- while (unidistreams.length < 100 - adjustlimit) {
289
- unidistreams.push(
290
- await getReaderValue(session.incomingUnidirectionalStreams)
291
- )
292
- }
293
- await getReaderValue(session.incomingBidirectionalStreams)
294
- await session.close()
295
- // eslint-disable-next-line no-unused-vars
296
- } catch (error) {
297
- // do not crash server, if a problem occurs...
298
- }
299
- }
300
- },
301
- // receive 100+ unidi streams and do not block
302
- async () => {
303
- for await (const session of getReaderStream(
304
- server.sessionStream('/streamlimits_getunidis')
305
- )) {
306
- try {
307
- await session.ready
308
- const unidistreams = []
309
- while (unidistreams.length < 150) {
310
- unidistreams.push(
311
- await getReaderValue(session.incomingUnidirectionalStreams)
312
- )
313
- }
314
- await session.close()
315
- // eslint-disable-next-line no-unused-vars
316
- } catch (error) {
317
- // do not crash server, if a problem occurs...
318
- }
319
- }
320
- },
321
- // cleanly close remote sessions
322
- async () => {
323
- for await (const session of getReaderStream(
324
- server.sessionStream('/session_close')
325
- )) {
326
- await session.close()
327
- }
328
- },
329
-
330
- // cleanly close remote sessions
331
- async () => {
332
- for await (const session of getReaderStream(
333
- server.sessionStream('/session_close_with_reason')
334
- )) {
335
- session.close({
336
- closeCode: 7,
337
- reason: 'this is the reason'
338
- })
339
- }
340
- },
341
-
342
- // support user data
343
- async () => {
344
- for await (const session of getReaderStream(
345
- server.sessionStream('/session_with_userdata')
346
- )) {
347
- try {
348
- const stream = await session.createUnidirectionalStream()
349
- await writeStream(stream, [
350
- new TextEncoder().encode(JSON.stringify(session.userData))
351
- ])
352
- // session.close() // do not close it here, the stream is closed before a reader is attached on client side
353
- } catch (err) {
354
- session.close({
355
- closeCode: 1,
356
- reason: err.stack
357
- })
358
- }
359
- }
360
- },
361
-
362
- // send data over unidirectional stream, initiated by remote
363
- async () => {
364
- for await (const session of getReaderStream(
365
- server.sessionStream('/unidirectional_client_send')
366
- )) {
367
- try {
368
- const stream = await getReaderValue(
369
- session.incomingUnidirectionalStreams
370
- )
371
- const received = await readStream(stream, KNOWN_BYTES_LENGTH)
372
- // await stream.cancel() // cancel so that the client can progress
373
- // if we did not get the data we sent, close the session with a reason
374
- if (
375
- !ui8.equals(ui8.concat(KNOWN_BYTES), ui8.concat(received))
376
- ) {
377
- session.close({
378
- closeCode: 500,
379
- reason: 'data did not match'
380
- })
381
- } else {
382
- session.close()
383
- }
384
- // eslint-disable-next-line no-unused-vars
385
- } catch (error) {
386
- // in some tests the client closes the stream
387
- }
388
- }
389
- },
390
-
391
- // send data over unidirectional stream, initiated by local
392
- async () => {
393
- for await (const session of getReaderStream(
394
- server.sessionStream('/unidirectional_server_send')
395
- )) {
396
- const stream = await session.createUnidirectionalStream()
397
-
398
- await writeStream(stream, KNOWN_BYTES)
399
- }
400
- },
401
-
402
- // delays reading from stream after client writes
403
- async () => {
404
- for await (const session of getReaderStream(
405
- server.sessionStream('/unidirectional_server_delay_before_read')
406
- )) {
407
- const stream = await getReaderValue(
408
- session.incomingUnidirectionalStreams
409
- )
410
-
411
- // wait before we read from the stream, should trigger backpressure
412
- // on the client
413
- await new Promise((resolve) => setTimeout(resolve, 1000))
414
-
415
- const received = await readStream(stream, KNOWN_BYTES_LENGTH)
416
- // await stream.cancel() // cancel so that the client can progress
417
-
418
- // if we did not get expected data, close the session with a reason
419
- if (!ui8.equals(ui8.concat(KNOWN_BYTES), ui8.concat(received))) {
420
- console.log('ERROR', KNOWN_BYTES, received)
421
- session.close({
422
- closeCode: 500,
423
- reason: 'data did not match'
424
- })
425
- } else {
426
- await new Promise((resolve) => setTimeout(resolve, 2000)) // time out is needed, since it can be received before the read is complete
427
- // and we want the client to close the session after it has processeed the read
428
- session.close()
429
- }
430
- }
431
- }
432
- ].map((fn) => fn())
433
- )
434
- })
435
- .catch((/** @type {any} */ err) => {
436
- console.error('server crashed', err)
437
- })
438
-
439
- return {
440
- server,
441
- certificate
442
- }
443
- }
444
-
445
- const { server, certificate } = await createServer()
446
- server.startServer()
447
- await server.ready
448
-
449
- const address = server.address()
450
-
451
- if (address == null) {
452
- throw new Error('Could not determine server address')
453
- }
454
-
455
- // tell the calling process how to contact us
456
- if (process.send)
457
- process.send({
458
- address: `https://${address.host}:${address.port}`,
459
- certificate: certificate.fingerprint
460
- })
461
- else {
462
- console.error('No IPC channel')
463
- console.log({
464
- address: `https://${address.host}:${address.port}`,
465
- certificate: certificate.fingerprint
466
- })
467
- }
@@ -1,19 +0,0 @@
1
- import {
2
- WebTransportPolyfill,
3
- WebTransportPonyfill
4
- } from '../../lib/webtransport.browser'
5
-
6
- /** @type {import('../../lib/dom').WebTransport} */
7
- // @ts-ignore
8
- let webtransport = globalThis.WebTransport
9
- if (process.env.USE_POLYFILL === 'true') {
10
- // @ts-ignore
11
- webtransport = WebTransportPolyfill
12
- }
13
-
14
- if (process.env.USE_PONYFILL === 'true') {
15
- // @ts-ignore
16
- webtransport = WebTransportPonyfill
17
- }
18
-
19
- export default webtransport
@@ -1,3 +0,0 @@
1
- import { WebTransport } from '../../lib/index.node.js'
2
-
3
- export default WebTransport
@@ -1,24 +0,0 @@
1
- /**
2
- * Write contents to a stream and close it
3
- *
4
- * @template T
5
- * @param {WritableStream<T>} writable
6
- * @param {T[]} input
7
- * @returns
8
- */
9
- export async function writeStream(writable, input) {
10
- const writer = writable.getWriter()
11
-
12
- for (const buf of input) {
13
- await writer.ready
14
- await writer.write(buf)
15
- }
16
-
17
- await writer.ready
18
- await writer.releaseLock()
19
- try {
20
- await writable.close()
21
- } catch (error) {
22
- console.log('Did we get a STOP_SENDING? ignore', error)
23
- }
24
- }