@fails-components/webtransport 0.3.0 → 1.0.0-rc.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.
- package/lib/client.js +81 -40
- package/lib/dom.ts +7 -5
- package/lib/http2/browser/browser.js +168 -0
- package/lib/http2/browser/browserparser.js +248 -0
- package/lib/http2/flowcontroller.js +371 -0
- package/lib/http2/node/capsuleparser.js +291 -0
- package/lib/http2/node/client.js +239 -0
- package/lib/http2/node/index.js +2 -0
- package/lib/http2/node/server.js +499 -0
- package/lib/http2/node/websocketparser.js +829 -0
- package/lib/http2/parserbase.js +139 -0
- package/lib/http2/parserbasehttp2.js +128 -0
- package/lib/http2/session.js +225 -0
- package/lib/http2/stream.js +354 -0
- package/lib/http2/websocketcommon.js +1 -0
- package/lib/{index.js → index.browser.js} +6 -10
- package/lib/index.node.js +27 -0
- package/lib/server.js +252 -69
- package/lib/session.js +31 -68
- package/lib/stream.js +103 -179
- package/lib/types.ts +174 -87
- package/lib/utils.js +2 -1
- package/lib/webstreams.browser.js +6 -0
- package/lib/webstreams.js +7 -0
- package/lib/webtransport.browser.js +284 -0
- package/lib/webtransport.node.js +137 -0
- package/lib/{webtransport.js → webtransportbase.js} +47 -41
- package/old_test/test.js +31 -29
- package/old_test/testsuite.js +1 -1
- package/package.json +18 -17
- package/readme.md +94 -30
- package/test/bidirectional-streams.spec.js +51 -9
- package/test/datagrams.spec.js +8 -6
- package/test/fixtures/known-bytes.js +29 -0
- package/test/fixtures/read-stream.js +1 -1
- package/test/fixtures/reader-value.js +1 -1
- package/test/fixtures/server.js +24 -10
- package/test/fixtures/webtransport.browser.js +20 -2
- package/test/fixtures/webtransport.js +1 -1
- package/test/index.js +33 -6
- package/test/pw-no-https-errors.json +5 -0
- package/test/session.spec.js +71 -52
- package/test/testinone.js +2854 -0
- package/test/unidirectional-streams.spec.js +20 -4
- package/CMakeLists.txt +0 -991
- package/LICENSE.chromium +0 -27
- package/LICENSE.envoy +0 -207
- package/build.js +0 -313
- package/dist/lib/client.d.ts +0 -78
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/dom.d.ts +0 -87
- package/dist/lib/dom.d.ts.map +0 -1
- package/dist/lib/error.d.ts +0 -8
- package/dist/lib/error.d.ts.map +0 -1
- package/dist/lib/event-loop.d.ts +0 -33
- package/dist/lib/event-loop.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -79
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/native.d.ts +0 -2
- package/dist/lib/native.d.ts.map +0 -1
- package/dist/lib/server.d.ts +0 -92
- package/dist/lib/server.d.ts.map +0 -1
- package/dist/lib/session.d.ts +0 -298
- package/dist/lib/session.d.ts.map +0 -1
- package/dist/lib/stream.d.ts +0 -135
- package/dist/lib/stream.d.ts.map +0 -1
- package/dist/lib/transport.d.ts +0 -34
- package/dist/lib/transport.d.ts.map +0 -1
- package/dist/lib/types.d.ts +0 -212
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -11
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/webtransport.d.ts +0 -47
- package/dist/lib/webtransport.d.ts.map +0 -1
- package/dist/test/bidirectional-streams.spec.d.ts +0 -5
- package/dist/test/bidirectional-streams.spec.d.ts.map +0 -1
- package/dist/test/datagrams.spec.d.ts +0 -5
- package/dist/test/datagrams.spec.d.ts.map +0 -1
- package/dist/test/event-loop.node.d.ts +0 -2
- package/dist/test/event-loop.node.d.ts.map +0 -1
- package/dist/test/fixtures/certificate.d.ts +0 -21
- package/dist/test/fixtures/certificate.d.ts.map +0 -1
- package/dist/test/fixtures/chai.d.ts +0 -2
- package/dist/test/fixtures/chai.d.ts.map +0 -1
- package/dist/test/fixtures/known-bytes.d.ts +0 -6
- package/dist/test/fixtures/known-bytes.d.ts.map +0 -1
- package/dist/test/fixtures/p-timeout.d.ts +0 -8
- package/dist/test/fixtures/p-timeout.d.ts.map +0 -1
- package/dist/test/fixtures/read-cert-hash.d.ts +0 -6
- package/dist/test/fixtures/read-cert-hash.d.ts.map +0 -1
- package/dist/test/fixtures/read-stream.d.ts +0 -19
- package/dist/test/fixtures/read-stream.d.ts.map +0 -1
- package/dist/test/fixtures/reader-value.d.ts +0 -22
- package/dist/test/fixtures/reader-value.d.ts.map +0 -1
- package/dist/test/fixtures/server.d.ts +0 -5
- package/dist/test/fixtures/server.d.ts.map +0 -1
- package/dist/test/fixtures/webtransport.browser.d.ts +0 -3
- package/dist/test/fixtures/webtransport.browser.d.ts.map +0 -1
- package/dist/test/fixtures/webtransport.d.ts +0 -3
- package/dist/test/fixtures/webtransport.d.ts.map +0 -1
- package/dist/test/fixtures/write-stream.d.ts +0 -10
- package/dist/test/fixtures/write-stream.d.ts.map +0 -1
- package/dist/test/index.d.ts +0 -2
- package/dist/test/index.d.ts.map +0 -1
- package/dist/test/session.spec.d.ts +0 -5
- package/dist/test/session.spec.d.ts.map +0 -1
- package/dist/test/unidirectional-streams.spec.d.ts +0 -2
- package/dist/test/unidirectional-streams.spec.d.ts.map +0 -1
- package/failslogolicensereadme.txt +0 -4
- package/lib/event-loop.js +0 -88
- package/lib/native.js +0 -28
- package/lib/transport.js +0 -82
- package/platform/icufix/fakedata.cpp +0 -8
- package/platform/quiche/quic/core/io/socket_win.inc +0 -3
- package/platform/quiche_platform_impl/quiche_bug_tracker_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_client_stats_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_command_line_flags_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_containers_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_event_loop_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_export_impl.h +0 -6
- package/platform/quiche_platform_impl/quiche_flag_utils_impl.h +0 -11
- package/platform/quiche_platform_impl/quiche_flags_impl.h +0 -8
- package/platform/quiche_platform_impl/quiche_iovec_impl.h +0 -10
- package/platform/quiche_platform_impl/quiche_logging_impl.h +0 -21
- package/platform/quiche_platform_impl/quiche_mem_slice_impl.h +0 -2
- package/platform/quiche_platform_impl/quiche_mutex_impl.h +0 -8
- package/platform/quiche_platform_impl/quiche_prefetch_impl.h +0 -11
- package/platform/quiche_platform_impl/quiche_reference_counted_impl.h +0 -16
- package/platform/quiche_platform_impl/quiche_server_stats_impl.h +0 -7
- package/platform/quiche_platform_impl/quiche_stack_trace_impl.h +0 -12
- package/platform/quiche_platform_impl/quiche_stream_buffer_allocator_impl.h +0 -8
- package/platform/quiche_platform_impl/quiche_testvalue_impl.h +0 -1
- package/platform/quiche_platform_impl/quiche_thread_local_impl.h +0 -6
- package/platform/quiche_platform_impl/quiche_time_utils_impl.h +0 -13
- package/platform/quiche_platform_impl/quiche_udp_socket_platform_impl.h +0 -39
- package/src/http3backendresponse.cc +0 -36
- package/src/http3backendresponse.h +0 -109
- package/src/http3client.cc +0 -1529
- package/src/http3client.h +0 -557
- package/src/http3clientsession.cc +0 -65
- package/src/http3clientsession.h +0 -60
- package/src/http3clientstream.cc +0 -52
- package/src/http3clientstream.h +0 -42
- package/src/http3dispatcher.cc +0 -62
- package/src/http3dispatcher.h +0 -57
- package/src/http3eventloop.cc +0 -1107
- package/src/http3eventloop.h +0 -316
- package/src/http3server.cc +0 -514
- package/src/http3server.h +0 -151
- package/src/http3serverbackend.cc +0 -105
- package/src/http3serverbackend.h +0 -98
- package/src/http3serversession.cc +0 -123
- package/src/http3serversession.h +0 -95
- package/src/http3serverstream.cc +0 -463
- package/src/http3serverstream.h +0 -114
- package/src/http3sessioncache.cc +0 -85
- package/src/http3sessioncache.h +0 -59
- package/src/http3wtsessionvisitor.cc +0 -46
- package/src/http3wtsessionvisitor.h +0 -397
- package/src/http3wtstreamvisitor.cc +0 -155
- package/src/http3wtstreamvisitor.h +0 -404
- package/test/event-loop.node.js +0 -24
- 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
|
|
4
|
+
const pid = typeof process !== 'undefined' ? process.pid : 0
|
|
5
|
+
const log = logger(`webtransport:httpclient(${pid})`)
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @typedef {import('./session').
|
|
9
|
-
* @typedef {import('./types').
|
|
8
|
+
* @typedef {import('./session').HttpWTSession} HttpWTSession
|
|
9
|
+
* @typedef {import('./types').HttpClientInit} HttpClientInit
|
|
10
10
|
*
|
|
11
11
|
* Http3Client events
|
|
12
|
-
* @typedef {import('./types').
|
|
12
|
+
* @typedef {import('./types').HttpClientEventHandler} HttpClientEventHandler
|
|
13
13
|
* @typedef {import('./types').ClientConnectedEvent} ClientConnectedEvent
|
|
14
14
|
* @typedef {import('./types').ClientWebtransportSupportEvent} ClientWebtransportSupportEvent
|
|
15
|
-
* @typedef {import('./types').
|
|
15
|
+
* @typedef {import('./types').HttpWTSessionVisitorEvent} HttpWTSessionVisitorEvent
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* @implements {
|
|
19
|
+
* @implements {HttpClientEventHandler}
|
|
20
20
|
*/
|
|
21
|
-
export class
|
|
21
|
+
export class HttpClient {
|
|
22
22
|
/**
|
|
23
|
-
* @param {
|
|
23
|
+
* @param {HttpClientInit} args
|
|
24
24
|
*/
|
|
25
25
|
constructor(args) {
|
|
26
|
-
|
|
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 {
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
69
|
-
|
|
73
|
+
try {
|
|
74
|
+
await this.quicconnected
|
|
75
|
+
} finally {
|
|
76
|
+
clearTimeout(timeout)
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
/**
|
|
73
|
-
* @param {
|
|
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 {
|
|
155
|
+
* @param {HttpWTSessionVisitorEvent} args
|
|
138
156
|
*/
|
|
139
|
-
|
|
140
|
-
// create
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
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
|
+
}
|