@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
@@ -1,136 +1,79 @@
1
1
  /* eslint-env mocha */
2
2
 
3
- import { createServer } from './fixtures/server.js'
4
3
  import { getReaderValue } from './fixtures/reader-value.js'
5
- import { WebTransport } from '../lib/index.js'
6
4
  import { expect } from './fixtures/chai.js'
7
5
  import { readStream } from './fixtures/read-stream.js'
8
6
  import { writeStream } from './fixtures/write-stream.js'
9
- import { defer } from '../lib/utils.js'
7
+ import { readCertHash } from './fixtures/read-cert-hash.js'
8
+ import WebTransport from './fixtures/webtransport.js'
10
9
  import * as ui8 from 'uint8arrays'
10
+ import { KNOWN_BYTES, KNOWN_BYTES_LENGTH } from './fixtures/known-bytes.js'
11
11
 
12
12
  /**
13
13
  * @template T
14
14
  * @typedef {import('../lib/types').Deferred<T>} Deferred<T>
15
15
  */
16
16
 
17
- const SERVER_PATH = '/bidirectional-streams'
18
-
19
17
  describe('bidirectional streams', function () {
20
- /** @type {import('../lib/server').Http3Server} */
21
- let server
22
- /** @type {import('./fixtures/certificate.js').Certificate} */
23
- let certificate
18
+ // FIXME: sometimes there are seemingly arbitrary 5s delays in
19
+ // communicating with the server under node.js
20
+ this.timeout(30000)
21
+
24
22
  /** @type {import('../lib/dom').WebTransport | undefined} */
25
23
  let client
26
- /** @type {string} */
27
- let url
28
-
29
- this.timeout(30000) // this can vary depending on the setup and is not an error
30
- // but it smells like a work around regarding a deeper problem
31
-
32
- beforeEach(async () => {
33
- this.timeout(2000)
34
- ;({ server, certificate } = await createServer())
35
- server.startServer()
36
- await server.ready
37
-
38
- const address = server.address()
39
-
40
- if (address == null || address.port == null) {
41
- throw new Error('No address')
42
- }
43
-
44
- url = `https://${address.host}:${address.port}`
45
- })
46
24
 
47
25
  // @ts-ignore
48
26
  afterEach(async () => {
49
27
  if (client != null) {
50
28
  client.close()
51
- }
52
-
53
- if (server != null) {
54
- server.stopServer()
55
- await server.closed
29
+ client = undefined
56
30
  }
57
31
  })
58
32
 
59
33
  it('sends and receives data over an outgoing bidirectional stream', async () => {
60
- this.timeout(200)
61
- // server context - waits for the client to open a bidi stream and pipes it back to them
62
- Promise.resolve().then(async () => {
63
- const session = await getReaderValue(server.sessionStream(SERVER_PATH))
64
- if (!session) throw new Error('Got no session')
65
- const bidiStream = await getReaderValue(
66
- session.incomingBidirectionalStreams
67
- )
68
- // redirect input to output
69
- await bidiStream.readable.pipeTo(bidiStream.writable)
70
- })
71
-
72
34
  // client context - connects to the server, opens a bidi stream, sends some data and reads the response
73
- client = new WebTransport(`${url}${SERVER_PATH}`, {
74
- serverCertificateHashes: [
75
- {
76
- algorithm: 'sha-256',
77
- value: certificate.hash
78
- }
79
- ]
80
- })
35
+ client = new WebTransport(
36
+ `${process.env.SERVER_URL}/bidirectional_client_initiated_echo`,
37
+ {
38
+ serverCertificateHashes: [
39
+ {
40
+ algorithm: 'sha-256',
41
+ value: readCertHash(process.env.CERT_HASH)
42
+ }
43
+ ]
44
+ }
45
+ )
81
46
  await client.ready
82
47
 
83
- const input = [
84
- Uint8Array.from([0, 1, 2, 3, 4]),
85
- Uint8Array.from([5, 6, 7, 8, 9]),
86
- Uint8Array.from([10, 11, 12, 13, 14])
87
- ]
88
-
89
48
  const stream = await client.createBidirectionalStream()
90
- await writeStream(stream.writable, input)
49
+ await writeStream(stream.writable, KNOWN_BYTES)
91
50
 
92
- const output = await readStream(stream.readable, ui8.concat(input).length)
93
- expect(ui8.concat(output)).to.deep.equal(
94
- ui8.concat(input),
51
+ const output = await readStream(stream.readable, KNOWN_BYTES_LENGTH)
52
+ expect(ui8.concat(KNOWN_BYTES)).to.deep.equal(
53
+ ui8.concat(output),
95
54
  'Did not receive the same bytes we sent'
96
55
  )
97
56
  })
98
57
 
99
58
  it('sends and receives data over an incoming bidirectional stream', async () => {
100
- this.timeout(200)
101
59
  /** @type {Deferred<Uint8Array[]>} */
102
- const serverData = defer()
103
- const input = [
104
- Uint8Array.from([0, 1, 2, 3, 4]),
105
- Uint8Array.from([5, 6, 7, 8, 9]),
106
- Uint8Array.from([10, 11, 12, 13, 14])
107
- ]
108
-
109
- // server context - waits for the client to connect, opens a bidi stream, sends some data and reads the response
110
- Promise.resolve().then(async () => {
111
- const session = await getReaderValue(server.sessionStream(SERVER_PATH))
112
- if (!session) throw new Error('Got no session')
113
- const stream = await session.createBidirectionalStream()
114
-
115
- await writeStream(stream.writable, input)
116
-
117
- const output = await readStream(stream.readable, ui8.concat(input).length)
118
- serverData.resolve(output)
119
- await stream.readable.cancel() // cancel so that the client can progress
120
- })
121
60
 
122
61
  // client context - waits for the server to open a bidi stream then pipes it back to them
123
- client = new WebTransport(`${url}${SERVER_PATH}`, {
124
- serverCertificateHashes: [
125
- {
126
- algorithm: 'sha-256',
127
- value: certificate.hash
128
- }
129
- ]
130
- })
62
+ client = new WebTransport(
63
+ `${process.env.SERVER_URL}/bidirectional_server_initiated_echo`,
64
+ {
65
+ serverCertificateHashes: [
66
+ {
67
+ algorithm: 'sha-256',
68
+ value: readCertHash(process.env.CERT_HASH)
69
+ }
70
+ ]
71
+ }
72
+ )
131
73
  await client.ready
132
74
 
133
75
  const bidiStream = await getReaderValue(client.incomingBidirectionalStreams)
76
+
134
77
  // redirect input to output
135
78
  try {
136
79
  await bidiStream.readable.pipeTo(bidiStream.writable)
@@ -138,10 +81,11 @@ describe('bidirectional streams', function () {
138
81
  console.log('Pipe to error (ignore)', error) // Actually all you can get is, that the fin is catched
139
82
  }
140
83
 
141
- const received = await serverData.promise
142
- expect(ui8.concat(received)).to.deep.equal(
143
- ui8.concat(input),
144
- 'Did not receive the same bytes we sent'
145
- )
84
+ // the remote will close the session
85
+ const result = await client.closed
86
+
87
+ // should receive the default close info
88
+ expect(result).to.have.property('reason', '')
89
+ expect(result).to.have.property('closeCode', 0)
146
90
  })
147
91
  })
@@ -1,12 +1,9 @@
1
1
  /* eslint-env mocha */
2
2
 
3
- import { createServer } from './fixtures/server.js'
4
- import { getReaderValue } from './fixtures/reader-value.js'
5
- import { WebTransport } from '../lib/index.js'
3
+ import WebTransport from './fixtures/webtransport.js'
6
4
  import { expect } from './fixtures/chai.js'
7
5
  import { readStream } from './fixtures/read-stream.js'
8
- import { writeStream } from './fixtures/write-stream.js'
9
- import * as ui8 from 'uint8arrays'
6
+ import { readCertHash } from './fixtures/read-cert-hash.js'
10
7
  import { pTimeout } from './fixtures/p-timeout.js'
11
8
 
12
9
  /**
@@ -14,119 +11,71 @@ import { pTimeout } from './fixtures/p-timeout.js'
14
11
  * @typedef {import('../lib/types').Deferred<T>} Deferred<T>
15
12
  */
16
13
 
17
- const SERVER_PATH = '/datagrams'
18
-
19
14
  describe('datagrams', function () {
20
- /** @type {import('../lib/server').Http3Server} */
21
- let server
22
- /** @type {import('./fixtures/certificate.js').Certificate} */
23
- let certificate
24
15
  /** @type {import('../lib/dom').WebTransport | undefined} */
25
16
  let client
26
- /** @type {string} */
27
- let url
28
-
29
- beforeEach(async () => {
30
- ;({ server, certificate } = await createServer())
31
- server.startServer()
32
- await server.ready
33
-
34
- const address = server.address()
35
-
36
- if (address == null || address.port == null) {
37
- throw new Error('No address')
38
- }
39
-
40
- url = `https://${address.host}:${address.port}`
41
- })
42
17
 
43
18
  // @ts-ignore
44
19
  afterEach(async () => {
45
20
  if (client != null) {
46
21
  client.close()
47
- }
48
-
49
- if (server != null) {
50
- server.stopServer()
51
- await server.closed
22
+ client = undefined
52
23
  }
53
24
  })
54
-
55
25
  it('client sends datagrams to the server', async () => {
56
- this.timeout(200)
57
- // server context - waits for the client to connect and pipes their datagrams back to them
58
- Promise.resolve().then(async () => {
59
- const session = await getReaderValue(server.sessionStream(SERVER_PATH))
26
+ // client context - connects to the server, sends some datagrams and reads the response
27
+ client = new WebTransport(
28
+ `${process.env.SERVER_URL}/datagrams_client_send`,
29
+ {
30
+ serverCertificateHashes: [
31
+ {
32
+ algorithm: 'sha-256',
33
+ value: readCertHash(process.env.CERT_HASH)
34
+ }
35
+ ]
36
+ }
37
+ )
38
+ await client.ready
60
39
 
61
- // redirect input to output
62
- await session.datagrams.readable.pipeTo(session.datagrams.writable)
63
- })
40
+ const writer = client.datagrams.writable.getWriter()
41
+ let closed = false
64
42
 
65
- // client context - connects to the server, sends some datagrams and reads the response
66
- client = new WebTransport(`${url}${SERVER_PATH}`, {
67
- serverCertificateHashes: [
68
- {
69
- algorithm: 'sha-256',
70
- value: certificate.hash
43
+ // write datagrams until the server receives one and closes the connection
44
+ // eslint-disable-next-line promise/catch-or-return
45
+ Promise.resolve().then(async () => {
46
+ // eslint-disable-next-line no-unmodified-loop-condition
47
+ while (!closed) {
48
+ try {
49
+ await writer.ready
50
+ await writer.write(Uint8Array.from([0, 1, 2, 3, 4]))
51
+ await new Promise((resolve) => setTimeout(resolve, 100))
52
+ } catch {
53
+ // the session can be closed while we are writing
71
54
  }
72
- ]
55
+ }
73
56
  })
74
- await client.ready
75
-
76
- const input = [
77
- Uint8Array.from([0, 1, 2, 3, 4]),
78
- Uint8Array.from([5, 6, 7, 8, 9]),
79
- Uint8Array.from([10, 11, 12, 13, 14])
80
- ]
81
57
 
82
- await writeStream(client.datagrams.writable, input)
58
+ const result = await client.closed
59
+ closed = true
83
60
 
84
- const output = await readStream(
85
- client.datagrams.readable,
86
- ui8.concat(input).length
87
- )
88
- expect(output).to.deep.equal(
89
- input,
90
- 'Did not receive the same bytes we sent'
91
- )
61
+ // should receive the default close info
62
+ expect(result).to.have.property('reason', '')
63
+ expect(result).to.have.property('closeCode', 0)
92
64
  })
93
65
 
94
66
  it('receives datagrams from the server', async () => {
95
- this.timeout(200)
96
-
97
- // server context - waits for the client to connect, sends some datagrams and reads the response
98
- Promise.resolve().then(async () => {
99
- const session = await getReaderValue(server.sessionStream(SERVER_PATH))
100
-
101
- let closed = false
102
- const writer = session.datagrams.writable.getWriter()
103
- Promise.resolve().then(async () => {
104
- // eslint-disable-next-line no-unmodified-loop-condition
105
- while (!closed) {
106
- try {
107
- await writer.ready
108
- await writer.write(Uint8Array.from([0, 1, 2, 3, 4]))
109
- await new Promise((resolve) => setTimeout(resolve, 1)) // do not flood everything
110
- } catch {
111
- // the session can be closed while we are writing
112
- }
113
- }
114
- })
115
-
116
- // have to close the session to end the client's datagram stream
117
- await session.closed
118
- closed = true
119
- })
120
-
121
67
  // client context - pipes the server's datagrams back to them
122
- client = new WebTransport(`${url}${SERVER_PATH}`, {
123
- serverCertificateHashes: [
124
- {
125
- algorithm: 'sha-256',
126
- value: certificate.hash
127
- }
128
- ]
129
- })
68
+ client = new WebTransport(
69
+ `${process.env.SERVER_URL}/datagrams_server_send`,
70
+ {
71
+ serverCertificateHashes: [
72
+ {
73
+ algorithm: 'sha-256',
74
+ value: readCertHash(process.env.CERT_HASH)
75
+ }
76
+ ]
77
+ }
78
+ )
130
79
  await client.ready
131
80
 
132
81
  // datagram transport is unreliable, at least one message should make it through
@@ -136,8 +85,6 @@ describe('datagrams', function () {
136
85
  readStream(client.datagrams.readable, expected),
137
86
  1000
138
87
  )
139
- await client.close()
140
- client = undefined
141
88
  expect(received).to.have.lengthOf(expected)
142
89
  })
143
90
  })
@@ -1,6 +1,8 @@
1
1
  import chai from 'chai'
2
2
  import dirtyChai from 'dirty-chai'
3
+ import chaiAsPromised from 'chai-as-promised'
3
4
 
4
5
  chai.use(dirtyChai)
6
+ chai.use(chaiAsPromised)
5
7
 
6
8
  export const expect = chai.expect
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Bytes send by tests
3
+ */
4
+ export const KNOWN_BYTES = [
5
+ Uint8Array.from([0, 1, 2, 3, 4]),
6
+ Uint8Array.from([5, 6, 7, 8, 9]),
7
+ Uint8Array.from([10, 11, 12, 13, 14]),
8
+ Uint8Array.from([15, 16, 17, 18, 19]),
9
+ Uint8Array.from([20, 21, 22, 23, 24])
10
+ ]
11
+
12
+ export const KNOWN_BYTES_LENGTH = KNOWN_BYTES.reduce(
13
+ (accumulator, currentValue) => accumulator + currentValue.length,
14
+ 0
15
+ )
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param {string} [certHash]
3
+ * @returns {Uint8Array}
4
+ */
5
+ export function readCertHash(certHash) {
6
+ return Uint8Array.from(`${certHash}`.split(':').map((i) => parseInt(i, 16)))
7
+ }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @template T
3
+ * @typedef {import('node:stream/web').ReadableStream<T>} ReadableStream<T>
4
+ */
5
+
1
6
  /**
2
7
  * Read a stream contents to the end and return it
3
8
  *
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @template T
3
+ * @typedef {import('node:stream/web').ReadableStream<T>} ReadableStream<T>
4
+ */
5
+
1
6
  /**
2
7
  * @template T
3
8
  * @param {ReadableStream<T>} readableStream
@@ -22,3 +27,30 @@ export async function getReaderValue(readableStream) {
22
27
  reader.releaseLock()
23
28
  }
24
29
  }
30
+
31
+ /**
32
+ * @template T
33
+ * @param {ReadableStream<T>} readableStream
34
+ * @returns {AsyncGenerator<T>}
35
+ */
36
+ export async function* getReaderStream(readableStream) {
37
+ const reader = readableStream.getReader()
38
+
39
+ try {
40
+ while (true) {
41
+ const { done, value } = await reader.read()
42
+
43
+ if (done) {
44
+ return
45
+ }
46
+
47
+ if (!value) {
48
+ throw new Error('Stream value was undefined')
49
+ }
50
+
51
+ yield value
52
+ }
53
+ } finally {
54
+ reader.releaseLock()
55
+ }
56
+ }