@fails-components/webtransport 0.3.1 → 1.0.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 (162) hide show
  1. package/lib/client.js +81 -40
  2. package/lib/dom.ts +7 -5
  3. package/lib/http2/browser/browser.js +168 -0
  4. package/lib/http2/browser/browserparser.js +248 -0
  5. package/lib/http2/flowcontroller.js +371 -0
  6. package/lib/http2/node/capsuleparser.js +291 -0
  7. package/lib/http2/node/client.js +239 -0
  8. package/lib/http2/node/index.js +2 -0
  9. package/lib/http2/node/server.js +499 -0
  10. package/lib/http2/node/websocketparser.js +829 -0
  11. package/lib/http2/parserbase.js +139 -0
  12. package/lib/http2/parserbasehttp2.js +128 -0
  13. package/lib/http2/session.js +225 -0
  14. package/lib/http2/stream.js +354 -0
  15. package/lib/http2/websocketcommon.js +1 -0
  16. package/lib/{index.js → index.browser.js} +6 -10
  17. package/lib/index.node.js +27 -0
  18. package/lib/server.js +252 -69
  19. package/lib/session.js +31 -68
  20. package/lib/stream.js +103 -179
  21. package/lib/types.ts +174 -87
  22. package/lib/utils.js +2 -1
  23. package/lib/webstreams.browser.js +6 -0
  24. package/lib/webstreams.js +7 -0
  25. package/lib/webtransport.browser.js +284 -0
  26. package/lib/webtransport.node.js +137 -0
  27. package/lib/{webtransport.js → webtransportbase.js} +47 -41
  28. package/old_test/test.js +31 -29
  29. package/old_test/testsuite.js +1 -1
  30. package/package.json +18 -17
  31. package/readme.md +94 -30
  32. package/test/bidirectional-streams.spec.js +51 -9
  33. package/test/datagrams.spec.js +8 -2
  34. package/test/fixtures/known-bytes.js +29 -0
  35. package/test/fixtures/read-stream.js +1 -1
  36. package/test/fixtures/reader-value.js +1 -1
  37. package/test/fixtures/server.js +24 -10
  38. package/test/fixtures/webtransport.browser.js +20 -2
  39. package/test/fixtures/webtransport.js +1 -1
  40. package/test/index.js +33 -6
  41. package/test/pw-no-https-errors.json +5 -0
  42. package/test/session.spec.js +71 -52
  43. package/test/unidirectional-streams.spec.js +20 -4
  44. package/CMakeLists.txt +0 -991
  45. package/LICENSE.chromium +0 -27
  46. package/LICENSE.envoy +0 -207
  47. package/build.js +0 -313
  48. package/dist/lib/client.d.ts +0 -78
  49. package/dist/lib/client.d.ts.map +0 -1
  50. package/dist/lib/dom.d.ts +0 -87
  51. package/dist/lib/dom.d.ts.map +0 -1
  52. package/dist/lib/error.d.ts +0 -8
  53. package/dist/lib/error.d.ts.map +0 -1
  54. package/dist/lib/event-loop.d.ts +0 -33
  55. package/dist/lib/event-loop.d.ts.map +0 -1
  56. package/dist/lib/index.d.ts +0 -79
  57. package/dist/lib/index.d.ts.map +0 -1
  58. package/dist/lib/native.d.ts +0 -2
  59. package/dist/lib/native.d.ts.map +0 -1
  60. package/dist/lib/server.d.ts +0 -92
  61. package/dist/lib/server.d.ts.map +0 -1
  62. package/dist/lib/session.d.ts +0 -298
  63. package/dist/lib/session.d.ts.map +0 -1
  64. package/dist/lib/stream.d.ts +0 -135
  65. package/dist/lib/stream.d.ts.map +0 -1
  66. package/dist/lib/transport.d.ts +0 -34
  67. package/dist/lib/transport.d.ts.map +0 -1
  68. package/dist/lib/types.d.ts +0 -212
  69. package/dist/lib/types.d.ts.map +0 -1
  70. package/dist/lib/utils.d.ts +0 -11
  71. package/dist/lib/utils.d.ts.map +0 -1
  72. package/dist/lib/webtransport.d.ts +0 -47
  73. package/dist/lib/webtransport.d.ts.map +0 -1
  74. package/dist/test/bidirectional-streams.spec.d.ts +0 -5
  75. package/dist/test/bidirectional-streams.spec.d.ts.map +0 -1
  76. package/dist/test/datagrams.spec.d.ts +0 -5
  77. package/dist/test/datagrams.spec.d.ts.map +0 -1
  78. package/dist/test/event-loop.node.d.ts +0 -2
  79. package/dist/test/event-loop.node.d.ts.map +0 -1
  80. package/dist/test/fixtures/certificate.d.ts +0 -21
  81. package/dist/test/fixtures/certificate.d.ts.map +0 -1
  82. package/dist/test/fixtures/chai.d.ts +0 -2
  83. package/dist/test/fixtures/chai.d.ts.map +0 -1
  84. package/dist/test/fixtures/known-bytes.d.ts +0 -6
  85. package/dist/test/fixtures/known-bytes.d.ts.map +0 -1
  86. package/dist/test/fixtures/p-timeout.d.ts +0 -8
  87. package/dist/test/fixtures/p-timeout.d.ts.map +0 -1
  88. package/dist/test/fixtures/read-cert-hash.d.ts +0 -6
  89. package/dist/test/fixtures/read-cert-hash.d.ts.map +0 -1
  90. package/dist/test/fixtures/read-stream.d.ts +0 -19
  91. package/dist/test/fixtures/read-stream.d.ts.map +0 -1
  92. package/dist/test/fixtures/reader-value.d.ts +0 -22
  93. package/dist/test/fixtures/reader-value.d.ts.map +0 -1
  94. package/dist/test/fixtures/server.d.ts +0 -5
  95. package/dist/test/fixtures/server.d.ts.map +0 -1
  96. package/dist/test/fixtures/webtransport.browser.d.ts +0 -3
  97. package/dist/test/fixtures/webtransport.browser.d.ts.map +0 -1
  98. package/dist/test/fixtures/webtransport.d.ts +0 -3
  99. package/dist/test/fixtures/webtransport.d.ts.map +0 -1
  100. package/dist/test/fixtures/write-stream.d.ts +0 -10
  101. package/dist/test/fixtures/write-stream.d.ts.map +0 -1
  102. package/dist/test/index.d.ts +0 -2
  103. package/dist/test/index.d.ts.map +0 -1
  104. package/dist/test/session.spec.d.ts +0 -5
  105. package/dist/test/session.spec.d.ts.map +0 -1
  106. package/dist/test/unidirectional-streams.spec.d.ts +0 -2
  107. package/dist/test/unidirectional-streams.spec.d.ts.map +0 -1
  108. package/failslogolicensereadme.txt +0 -4
  109. package/lib/event-loop.js +0 -88
  110. package/lib/native.js +0 -28
  111. package/lib/transport.js +0 -82
  112. package/platform/icufix/fakedata.cpp +0 -8
  113. package/platform/quiche/quic/core/io/socket_win.inc +0 -3
  114. package/platform/quiche_platform_impl/quiche_bug_tracker_impl.h +0 -7
  115. package/platform/quiche_platform_impl/quiche_client_stats_impl.h +0 -7
  116. package/platform/quiche_platform_impl/quiche_command_line_flags_impl.h +0 -7
  117. package/platform/quiche_platform_impl/quiche_containers_impl.h +0 -7
  118. package/platform/quiche_platform_impl/quiche_event_loop_impl.h +0 -7
  119. package/platform/quiche_platform_impl/quiche_export_impl.h +0 -6
  120. package/platform/quiche_platform_impl/quiche_flag_utils_impl.h +0 -11
  121. package/platform/quiche_platform_impl/quiche_flags_impl.h +0 -8
  122. package/platform/quiche_platform_impl/quiche_iovec_impl.h +0 -10
  123. package/platform/quiche_platform_impl/quiche_logging_impl.h +0 -21
  124. package/platform/quiche_platform_impl/quiche_mem_slice_impl.h +0 -2
  125. package/platform/quiche_platform_impl/quiche_mutex_impl.h +0 -8
  126. package/platform/quiche_platform_impl/quiche_prefetch_impl.h +0 -11
  127. package/platform/quiche_platform_impl/quiche_reference_counted_impl.h +0 -16
  128. package/platform/quiche_platform_impl/quiche_server_stats_impl.h +0 -7
  129. package/platform/quiche_platform_impl/quiche_stack_trace_impl.h +0 -12
  130. package/platform/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h +0 -8
  131. package/platform/quiche_platform_impl/quiche_testvalue_impl.h +0 -1
  132. package/platform/quiche_platform_impl/quiche_thread_local_impl.h +0 -6
  133. package/platform/quiche_platform_impl/quiche_time_utils_impl.h +0 -13
  134. package/platform/quiche_platform_impl/quiche_udp_socket_platform_impl.h +0 -39
  135. package/src/http3backendresponse.cc +0 -36
  136. package/src/http3backendresponse.h +0 -109
  137. package/src/http3client.cc +0 -1529
  138. package/src/http3client.h +0 -557
  139. package/src/http3clientsession.cc +0 -65
  140. package/src/http3clientsession.h +0 -60
  141. package/src/http3clientstream.cc +0 -52
  142. package/src/http3clientstream.h +0 -42
  143. package/src/http3dispatcher.cc +0 -62
  144. package/src/http3dispatcher.h +0 -57
  145. package/src/http3eventloop.cc +0 -1107
  146. package/src/http3eventloop.h +0 -316
  147. package/src/http3server.cc +0 -514
  148. package/src/http3server.h +0 -151
  149. package/src/http3serverbackend.cc +0 -105
  150. package/src/http3serverbackend.h +0 -98
  151. package/src/http3serversession.cc +0 -123
  152. package/src/http3serversession.h +0 -95
  153. package/src/http3serverstream.cc +0 -463
  154. package/src/http3serverstream.h +0 -114
  155. package/src/http3sessioncache.cc +0 -85
  156. package/src/http3sessioncache.h +0 -59
  157. package/src/http3wtsessionvisitor.cc +0 -46
  158. package/src/http3wtsessionvisitor.h +0 -397
  159. package/src/http3wtstreamvisitor.cc +0 -155
  160. package/src/http3wtstreamvisitor.h +0 -404
  161. package/test/event-loop.node.js +0 -24
  162. package/tsconfig.json +0 -39
package/lib/client.js CHANGED
@@ -1,29 +1,30 @@
1
- import { Http3WebTransport } from './transport.js'
2
1
  import { WebTransportError } from './error.js'
3
2
  import { logger } from './utils.js'
4
3
 
5
- const log = logger(`webtransport:http3client(${process.pid})`)
4
+ const pid = typeof process !== 'undefined' ? process.pid : 0
5
+ const log = logger(`webtransport:httpclient(${pid})`)
6
6
 
7
7
  /**
8
- * @typedef {import('./session').Http3WTSession} Http3WTSession
9
- * @typedef {import('./types').Http3ClientInit} Http3ClientInit
8
+ * @typedef {import('./session').HttpWTSession} HttpWTSession
9
+ * @typedef {import('./types').HttpClientInit} HttpClientInit
10
10
  *
11
11
  * Http3Client events
12
- * @typedef {import('./types').Http3ClientEventHandler} Http3ClientEventHandler
12
+ * @typedef {import('./types').HttpClientEventHandler} HttpClientEventHandler
13
13
  * @typedef {import('./types').ClientConnectedEvent} ClientConnectedEvent
14
14
  * @typedef {import('./types').ClientWebtransportSupportEvent} ClientWebtransportSupportEvent
15
- * @typedef {import('./types').Http3WTSessionVisitorEvent} Http3WTSessionVisitorEvent
15
+ * @typedef {import('./types').HttpWTSessionVisitorEvent} HttpWTSessionVisitorEvent
16
16
  */
17
17
 
18
18
  /**
19
- * @implements {Http3ClientEventHandler}
19
+ * @implements {HttpClientEventHandler}
20
20
  */
21
- export class Http3Client extends Http3WebTransport {
21
+ export class HttpClient {
22
22
  /**
23
- * @param {Http3ClientInit} args
23
+ * @param {HttpClientInit} args
24
24
  */
25
25
  constructor(args) {
26
- super(args, 'client')
26
+ /** @type {HttpClientInit| undefined} */
27
+ this.args = args
27
28
 
28
29
  /** @type {{ resolve: (value?: any) => void, reject: (err?: Error) => void} | null | undefined} */
29
30
  this.sessionProm = null
@@ -32,7 +33,7 @@ export class Http3Client extends Http3WebTransport {
32
33
  this.sessionobj = new Promise((resolve, reject) => {
33
34
  this.sessionProm = { resolve, reject }
34
35
  }).catch(() => {}) // add default handler if no one cares
35
- /** @type {Http3WTSession | null | undefined} */
36
+ /** @type {HttpWTSession | null | undefined} */
36
37
  this.sessionobjint = null
37
38
  this.closeHookSession = this.closeHookSession.bind(this)
38
39
 
@@ -45,8 +46,13 @@ export class Http3Client extends Http3WebTransport {
45
46
  this._webTransportConnectTimeout = args.webTransportConnectTimeout ?? 2000
46
47
  }
47
48
 
48
- async handleConnection() {
49
- this.createTransportInt()
49
+ /**
50
+ * @param {Object} args
51
+ * @param {boolean} args.createTransport
52
+ * @param {string} args.path
53
+ */
54
+ async handleConnection({ createTransport, path }) {
55
+ if (createTransport) this.createTransportInt({ path })
50
56
 
51
57
  this.quicconnected = new Promise((resolve, reject) => {
52
58
  this.quicconnectedProm = { resolve, reject }
@@ -64,13 +70,15 @@ export class Http3Client extends Http3WebTransport {
64
70
  delete this.quicconnectedProm
65
71
  }
66
72
  }, this._quicConnectTimeout)
67
-
68
- await this.quicconnected
69
- clearTimeout(timeout)
73
+ try {
74
+ await this.quicconnected
75
+ } finally {
76
+ clearTimeout(timeout)
77
+ }
70
78
  }
71
79
 
72
80
  /**
73
- * @param {Http3WTSession} sessionobj
81
+ * @param {HttpWTSession} sessionobj
74
82
  * @param {string} path
75
83
  * @returns
76
84
  */
@@ -108,10 +116,20 @@ export class Http3Client extends Http3WebTransport {
108
116
  this.stopped = true
109
117
  }
110
118
 
119
+ /**
120
+ * @param {import('./types').SessionCloseEvent} args
121
+ */
122
+ onClientError(args) {
123
+ if (this.sessionobjint != null) {
124
+ this.sessionobjint.onClose(args)
125
+ }
126
+ }
127
+
111
128
  /**
112
129
  * @param {ClientConnectedEvent} args
113
130
  */
114
131
  onClientConnected(args) {
132
+ this.transportIntSwitchToReliable = undefined
115
133
  if (this.quicconnectedProm) {
116
134
  if (args.success) this.quicconnectedProm.resolve()
117
135
  else
@@ -119,7 +137,7 @@ export class Http3Client extends Http3WebTransport {
119
137
  new WebTransportError('Opening handshake failed.')
120
138
  )
121
139
  delete this.quicconnectedProm
122
- } else
140
+ } else if (args.success)
123
141
  throw new WebTransportError('Client connected with no pending promise')
124
142
  }
125
143
 
@@ -134,12 +152,12 @@ export class Http3Client extends Http3WebTransport {
134
152
  }
135
153
 
136
154
  /**
137
- * @param {Http3WTSessionVisitorEvent} args
155
+ * @param {HttpWTSessionVisitorEvent} args
138
156
  */
139
- onHttp3WTSessionVisitor(args) {
140
- // create Http3 Visitor
157
+ onHttpWTSessionVisitor(args) {
158
+ // create Http Visitor
141
159
  if (args.session && this.sessionProm && this.sessionobjint) {
142
- this.sessionobjint.setSessionObj(args.session)
160
+ this.sessionobjint.setSessionObj(args.session, !!args.reliable)
143
161
  args.session.jsobj.closeHook = this.closeHookSession
144
162
  delete this.sessionobjint
145
163
  this.sessionProm.resolve(args.session)
@@ -152,26 +170,49 @@ export class Http3Client extends Http3WebTransport {
152
170
  }
153
171
 
154
172
  /**
155
- * @param {ClientConnectedEvent | ClientWebtransportSupportEvent | Http3WTSessionVisitorEvent} args
156
- */
157
- customCallback(args) {
158
- log('callback', args?.purpose)
159
- log.trace(args)
160
- if (args.purpose) {
161
- switch (args.purpose) {
162
- case 'ClientConnected':
163
- this.onClientConnected(args)
164
- break
165
- case 'ClientWebtransportSupport':
166
- this.onClientWebTransportSupport(args)
167
- break
168
- case 'Http3WTSessionVisitor':
169
- this.onHttp3WTSessionVisitor(args)
170
- break
171
- default: {
172
- throw new WebTransportError('unknown purpose')
173
+ * @param{{path: string}} [args]
174
+ **/
175
+ createTransportInt(args) {
176
+ const path = args?.path
177
+ if (this.transportInt != null) {
178
+ return
179
+ }
180
+
181
+ try {
182
+ // @ts-ignore
183
+ if (this.args?.forceReliable || !this.args.createUnreliableClient) {
184
+ // internal option for unit tests only
185
+ // @ts-ignore
186
+ this.transportInt = this.args.createReliableClient(this)
187
+ } else {
188
+ // @ts-ignore
189
+ this.transportInt = this.args.createUnreliableClient(this)
190
+ if (!this.args?.requireUnreliable) {
191
+ const args = this.args
192
+ this.transportIntSwitchToReliable = () => {
193
+ if (this.transportInt != null) {
194
+ this.transportInt.closeClient()
195
+ }
196
+ // @ts-ignore
197
+ this.transportInt = args.createReliableClient(this)
198
+ this.transportInt.jsobj = this
199
+ if (this.transportInt.createTransport) {
200
+ this.transportInt.createTransport({ path })
201
+ }
202
+ this.transportIntSwitchToReliable = undefined
203
+ }
173
204
  }
174
205
  }
206
+ } catch (/** @type {any} */ err) {
207
+ const error = new WebTransportError('Opening handshake failed.')
208
+ error.stack = err.stack
209
+
210
+ throw error
211
+ }
212
+ delete this.args
213
+ this.transportInt.jsobj = this
214
+ if (this.transportInt.createTransport) {
215
+ this.transportInt.createTransport({ path })
175
216
  }
176
217
  }
177
218
  }
package/lib/dom.ts CHANGED
@@ -1,5 +1,3 @@
1
- import type { ReadableStream } from 'node:stream/web'
2
-
3
1
  // Types taken from https://www.w3.org/TR/webtransport
4
2
  // These can be removed when they are added to the default typescript types
5
3
 
@@ -79,18 +77,18 @@ export interface WebTransportOptions {
79
77
  serverCertificateHashes?: WebTransportHash[]
80
78
 
81
79
  /**
82
- * Nonstandard option - when a new connection is opened, how long to wait for the quic handshake to complete in ms before rejecting
80
+ * Nonstandard option - when a new connection is opened, how long to wait for the quic handshake to complete in ms before rejecting or switching to http2
83
81
  */
84
82
  quicConnectTimeout?: number
85
83
 
86
84
  /**
87
- * Nonstandard option - when a new connection is opened, how long to wait for the webtransport handshake to complete in ms before rejecting
85
+ * Nonstandard option - when a new connection is opened, how long to wait for the webtransport handshake to complete in ms before rejecting or switching to http2
88
86
  */
89
87
  webTransportConnectTimeout?: number
90
88
  congestionControl?: WebTransportCongestionControl
91
89
  }
92
90
 
93
- export interface WebTransport {
91
+ export interface WebTransportSession {
94
92
  getStats: () => Promise<WebTransportStats>
95
93
  readonly ready: Promise<void>
96
94
  readonly reliability: WebTransportReliabilityMode
@@ -109,6 +107,10 @@ export interface WebTransport {
109
107
  readonly incomingUnidirectionalStreams: ReadableStream<WebTransportReceiveStream>
110
108
  }
111
109
 
110
+ export interface WebTransport extends WebTransportSession {
111
+ readonly supportsReliableOnly: boolean
112
+ }
113
+
112
114
  export type WebTransportReliabilityMode = 'pending' | 'reliable-only' | 'supports-unreliable'
113
115
  export type WebTransportCongestionControl = 'default' | 'throughput' | 'low-latency';
114
116
 
@@ -0,0 +1,168 @@
1
+ import { Http2WebTransportSession } from '../session.js'
2
+ import { BrowserParser } from './browserparser.js'
3
+ import { supportedVersions } from '../websocketcommon.js'
4
+ import { logger } from '../../utils.js'
5
+
6
+ const log = logger(`webtransport:http2:browser`)
7
+
8
+ export class Http2WebTransportBrowser {
9
+ /**
10
+ * @param {import('../../types.js').NativeClientOptions} args
11
+ */
12
+ constructor(args) {
13
+ this.port = args?.port || 443
14
+ this.hostname = args?.host || 'localhost'
15
+ this.initialStreamFlowControlWindow =
16
+ args?.initialStreamFlowControlWindow || 16 * 1024 // 16 KB
17
+ this.initialSessionFlowControlWindow =
18
+ args?.initialSessionFlowControlWindow || 16 * 1024 // 16 KB
19
+
20
+ this.streamShouldAutoTuneReceiveWindow =
21
+ args.streamShouldAutoTuneReceiveWindow || false
22
+ this.streamFlowControlWindowSizeLimit =
23
+ args?.streamFlowControlWindowSizeLimit || 6 * 1024 * 1024
24
+
25
+ this.sessionShouldAutoTuneReceiveWindow =
26
+ args.sessionShouldAutoTuneReceiveWindow || false
27
+ this.sessionFlowControlWindowSizeLimit =
28
+ args?.sessionFlowControlWindowSizeLimit || 15 * 1024 * 1024
29
+ /** @type {import('../../session.js').HttpClient} */
30
+ // @ts-ignore
31
+ this.jsobj = undefined // the transport will set this
32
+ /** @type {WebSocket} */
33
+ // @ts-ignore
34
+ this.clientInt = undefined
35
+ }
36
+
37
+ /**
38
+ * @param {{path: string}} arg
39
+ */
40
+ createTransport({ path }) {
41
+ try {
42
+ let url = 'wss://' + this.hostname + ':' + this.port
43
+ if (path) url = url + '/' + path
44
+ // eslint-disable-next-line no-undef
45
+ this.clientInt = new WebSocket(
46
+ url,
47
+ supportedVersions.map(
48
+ (/** @type {string} */ el) => 'webtransport_' + el
49
+ )
50
+ )
51
+ } catch (error) {
52
+ log('Failed on WebTransport/Websocket:', error)
53
+ this.jsobj.onClientConnected({
54
+ success: false
55
+ })
56
+
57
+ return
58
+ }
59
+ this.clientInt.binaryType = 'arraybuffer'
60
+
61
+ this.clientInt.addEventListener('open', (event) => {
62
+ const protocol = this.clientInt?.protocol
63
+ if (!protocol) {
64
+ if (this.clientInt) this.clientInt.close()
65
+ this.jsobj.onClientConnected({
66
+ success: false
67
+ })
68
+ }
69
+ const aprotocol = protocol.split('_')
70
+ if (
71
+ aprotocol.length !== 2 ||
72
+ aprotocol[0] !== 'webtransport' ||
73
+ !supportedVersions.includes(aprotocol[1])
74
+ ) {
75
+ if (this.clientInt) this.clientInt.close()
76
+ this.jsobj.onClientConnected({
77
+ success: false
78
+ })
79
+ } else {
80
+ this.jsobj.onClientWebTransportSupport({})
81
+ this.jsobj.onClientConnected({
82
+ success: true
83
+ })
84
+ }
85
+ })
86
+
87
+ this.clientInt.addEventListener('error', (error) => {
88
+ log('Failed on WebTransport/Websocket:', error)
89
+ if (
90
+ !this.jsobj?.sessionobjint ||
91
+ this.jsobj?.sessionobjint?.state === 'connecting'
92
+ )
93
+ this.jsobj.onClientConnected({
94
+ success: false
95
+ })
96
+ else {
97
+ if (this?.jsobj?.sessionobjint?.objint)
98
+ this.jsobj.sessionobjint.close({
99
+ closeCode: 0,
100
+ reason: error.toString()
101
+ })
102
+ }
103
+ })
104
+ }
105
+
106
+ /**
107
+ * @param {string} path
108
+ */
109
+ openWTSession(path) {
110
+ if (!this.clientInt) throw new Error('clientInt not present')
111
+ let sessobj
112
+
113
+ const retObj = {
114
+ session: new Http2WebTransportSession({
115
+ ws: this.clientInt,
116
+ isclient: true,
117
+ createParser: (
118
+ /** @type {Http2WebTransportSession} */ nativesession
119
+ ) => {
120
+ sessobj = nativesession
121
+ const session = new BrowserParser({
122
+ ws: this.clientInt,
123
+ nativesession,
124
+ isclient: true,
125
+ initialStreamSendWindowOffset: this.initialStreamFlowControlWindow,
126
+ initialStreamReceiveWindowOffset:
127
+ this.initialStreamFlowControlWindow,
128
+ streamShouldAutoTuneReceiveWindow:
129
+ this.streamShouldAutoTuneReceiveWindow,
130
+ streamReceiveWindowSizeLimit: this.streamFlowControlWindowSizeLimit
131
+ })
132
+ if (this.clientInt)
133
+ this.clientInt.addEventListener('close', (event) => {
134
+ let code = event.code
135
+ let error = 'Session WebSocket closed'
136
+ if (event.reason) {
137
+ let tokens = event.reason.split(':')
138
+ if (tokens.length > 1) {
139
+ code = parseInt(tokens[0])
140
+ tokens = tokens.slice(1)
141
+ }
142
+ error = tokens.join(':')
143
+ }
144
+ nativesession.jsobj.onClose({
145
+ errorcode: code,
146
+ error
147
+ })
148
+ })
149
+ return session
150
+ },
151
+ sendWindowOffset: this.sessionFlowControlWindowSizeLimit,
152
+ receiveWindowOffset: this.sessionFlowControlWindowSizeLimit,
153
+ shouldAutoTuneReceiveWindow: this.sessionShouldAutoTuneReceiveWindow,
154
+ receiveWindowSizeLimit: this.sessionFlowControlWindowSizeLimit
155
+ }),
156
+ reliable: true
157
+ }
158
+ this.jsobj.onHttpWTSessionVisitor(retObj)
159
+
160
+ // @ts-ignore
161
+ sessobj.jsobj.onReady({})
162
+ }
163
+
164
+ closeClient() {
165
+ if (this.clientInt && this.clientInt.readyState === 1)
166
+ this.clientInt.close()
167
+ }
168
+ }
@@ -0,0 +1,248 @@
1
+ import { ParserBase, lengthVarInt } from '../parserbase.js'
2
+ import { logger } from '../../utils.js'
3
+
4
+ const log = logger(`webtransport:http2:browserparser`)
5
+ /**
6
+ * @param{{offset: Number, buffer: Uint8Array, size: Number}} bs
7
+ */
8
+ function readVarInt(bs) {
9
+ if (bs.offset + 1 > bs.size) return undefined
10
+ let val = BigInt(bs.buffer[bs.offset])
11
+ bs.offset++
12
+ const prefix = Number(val) >>> 6
13
+ const intlength = 1 << prefix
14
+
15
+ if (bs.offset + intlength - 1 > bs.size) {
16
+ return undefined
17
+ }
18
+ val = val & 0x3fn
19
+ for (let i = 0; i < intlength - 1; i++) {
20
+ val = (val << 8n) | BigInt(bs.buffer[bs.offset])
21
+ bs.offset++
22
+ }
23
+ return val
24
+ }
25
+
26
+ /**
27
+ * @param{{offset: Number, buffer: Uint8Array, size: Number}} bs
28
+ * @param{Number|bigint} int
29
+ */
30
+ export function writeVarInt(bs, int) {
31
+ let numbytes = 8n
32
+ let msb = 0xc0n
33
+ const bint = BigInt(int)
34
+ if (bint < 64n) {
35
+ numbytes = 1n
36
+ msb = 0x0n
37
+ } else if (bint < 16384n) {
38
+ numbytes = 2n
39
+ msb = 0x40n
40
+ } else if (bint < 1073741824n) {
41
+ numbytes = 4n
42
+ msb = 0x80n
43
+ }
44
+ bs.buffer[bs.offset] = Number(
45
+ msb | ((bint >> ((numbytes - 1n) * 8n)) & 0xffn)
46
+ )
47
+ bs.offset++
48
+
49
+ for (let i = numbytes - 2n; i >= 0n; i--) {
50
+ bs.buffer[bs.offset] = Number((bint >> (i * 8n)) & 0xffn)
51
+ bs.offset++
52
+ }
53
+ }
54
+
55
+ export class BrowserParser extends ParserBase {
56
+ static WS_CONTINUE = 0x0
57
+ static WS_TEXT = 0x1
58
+ static WS_BINARY = 0x2
59
+ static WS_CLOSE = 0x8
60
+ static WS_PING = 0x9
61
+ static WS_PONG = 0xa
62
+ /**
63
+ * @param {import('../../types.js').ParserWebsocketInit} stream
64
+ */
65
+ constructor({
66
+ ws,
67
+ nativesession,
68
+ isclient,
69
+ initialStreamSendWindowOffset,
70
+ initialStreamReceiveWindowOffset,
71
+ streamShouldAutoTuneReceiveWindow,
72
+ streamReceiveWindowSizeLimit
73
+ }) {
74
+ super({
75
+ nativesession,
76
+ isclient,
77
+ initialStreamSendWindowOffset,
78
+ initialStreamReceiveWindowOffset,
79
+ streamShouldAutoTuneReceiveWindow,
80
+ streamReceiveWindowSizeLimit
81
+ })
82
+ this.ws = ws
83
+ /** @type {Buffer|undefined} */
84
+ this.saveddata = undefined
85
+ /** @type {Number|undefined} */
86
+ this.rtype = undefined
87
+
88
+ this.closesend = false
89
+
90
+ this.ws.addEventListener('message', (event) => {
91
+ if (event.data instanceof ArrayBuffer) {
92
+ // binary frame
93
+ this.parseData(new Uint8Array(event.data, 0, event.data.byteLength))
94
+ } else {
95
+ // text frame
96
+ log('Illegal text frame', event.data)
97
+ }
98
+ })
99
+ }
100
+
101
+ /**
102
+ * @param {Uint8Array} data
103
+ */
104
+ parseData(data) {
105
+ const bufferstate = { offset: 0, size: data.byteLength, buffer: data }
106
+
107
+ const offsetend = bufferstate.size
108
+
109
+ const type = Number(readVarInt(bufferstate))
110
+
111
+ switch (type) {
112
+ case ParserBase.PADDING:
113
+ // only padding do nothing
114
+ break
115
+ case ParserBase.WT_RESET_STREAM:
116
+ case ParserBase.WT_STOP_SENDING:
117
+ {
118
+ const streamid = readVarInt(bufferstate)
119
+ const stream = this.wtstreams.get(streamid)
120
+ const code = readVarInt(bufferstate)
121
+ if (stream && typeof code !== 'undefined')
122
+ stream.jsobj.onStreamRecvSignal({
123
+ code,
124
+ nettask:
125
+ type === ParserBase.WT_RESET_STREAM
126
+ ? 'resetStream'
127
+ : 'stopSending'
128
+ })
129
+ }
130
+ break
131
+ case ParserBase.WT_STREAM_WOFIN:
132
+ case ParserBase.WT_STREAM_WFIN:
133
+ {
134
+ const streamid = Number(readVarInt(bufferstate))
135
+
136
+ if (typeof streamid !== 'undefined') {
137
+ let object = this.wtstreams.get(streamid)
138
+ if (!object) {
139
+ object = this.newStream(streamid)
140
+ }
141
+ // TODO submit data
142
+ if (offsetend - bufferstate.offset >= 0) {
143
+ object.recvData({
144
+ data: new Uint8Array(
145
+ bufferstate.buffer.buffer,
146
+ bufferstate.buffer.byteOffset + bufferstate.offset,
147
+ offsetend - bufferstate.offset
148
+ ),
149
+ fin: type === ParserBase.WT_STREAM_WFIN
150
+ })
151
+ }
152
+ }
153
+ }
154
+ break
155
+ case ParserBase.WT_MAX_DATA:
156
+ this.onMaxData(readVarInt(bufferstate))
157
+ break
158
+ case ParserBase.WT_MAX_STREAM_DATA:
159
+ this.onMaxStreamData(readVarInt(bufferstate), readVarInt(bufferstate))
160
+ break
161
+ case ParserBase.WT_MAX_STREAMS_BIDI:
162
+ // this.recvSession({ maxstreams: readVarInt(bufferstate), type })
163
+ break
164
+ case ParserBase.WT_MAX_STREAMS_UNIDI:
165
+ // this.recvSession({ maxstreams: readVarInt(bufferstate), type })
166
+ break
167
+ case ParserBase.WT_DATA_BLOCKED:
168
+ this.onDataBlocked(readVarInt(bufferstate))
169
+ break
170
+ case ParserBase.WT_STREAM_DATA_BLOCKED:
171
+ this.onStreamDataBlocked(
172
+ readVarInt(bufferstate),
173
+ readVarInt(bufferstate)
174
+ )
175
+ break
176
+ case ParserBase.WT_STREAMS_BLOCKED_UNIDI:
177
+ /* {
178
+ const streamid = readVarInt(bufferstate)
179
+ const object = this.wtstreams.get(streamid)
180
+ if (object)
181
+ this.recvStream({
182
+ maxstreams: readVarInt(bufferstate),
183
+ type,
184
+ object
185
+ })
186
+ } */
187
+ break
188
+ case ParserBase.WT_STREAMS_BLOCKED_BIDI:
189
+ /* {
190
+ const streamid = readVarInt(bufferstate)
191
+ const object = this.wtstreams.get(streamid)
192
+ if (object)
193
+ this.recvStream({
194
+ maxstreams: readVarInt(bufferstate),
195
+ type,
196
+ streamid
197
+ })
198
+ } */
199
+ break
200
+ case ParserBase.DATAGRAM:
201
+ this.session.jsobj.onDatagramReceived({
202
+ datagram: new Uint8Array(
203
+ bufferstate.buffer.buffer,
204
+ bufferstate.buffer.byteOffset + bufferstate.offset,
205
+ offsetend - bufferstate.offset
206
+ )
207
+ })
208
+
209
+ break
210
+ default:
211
+ // do nothing
212
+ }
213
+
214
+ bufferstate.offset = offsetend
215
+ }
216
+
217
+ /**
218
+ * @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined}} bs
219
+ */
220
+ writeCapsule({ type, headerVints, payload }) {
221
+ let plength = 0
222
+ for (const ind in headerVints) plength += lengthVarInt(headerVints[ind])
223
+ plength += lengthVarInt(type)
224
+ const hlength = plength
225
+ if (payload) plength += payload.byteLength
226
+
227
+ const cdata = new Uint8Array(plength)
228
+ const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
229
+ writeVarInt(bufferstate, type)
230
+ for (const ind in headerVints) writeVarInt(bufferstate, headerVints[ind])
231
+ const dest = new Uint8Array(cdata.buffer, cdata.byteOffset + hlength)
232
+ if (payload) dest.set(payload)
233
+ this.ws.send(cdata)
234
+
235
+ /* const blocked = this.ws.bufferedAmount > 1024 * 256
236
+ // do something if blocked
237
+ if (blocked) this.blocked = true
238
+ return blocked */
239
+ return false
240
+ }
241
+
242
+ /**
243
+ * @param{{code: Number, reason: string}}arg
244
+ */
245
+ sendClose({ code, reason }) {
246
+ this.ws.close(1000, code.toString() + ':' + reason)
247
+ }
248
+ }