@fails-components/webtransport 1.0.10 → 1.1.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.
- package/dist/lib/http2/browser/browser.d.ts.map +1 -1
- package/dist/lib/http2/browser/browserparser.d.ts +4 -3
- package/dist/lib/http2/browser/browserparser.d.ts.map +1 -1
- package/dist/lib/http2/node/capsuleparser.d.ts +3 -2
- package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -1
- package/dist/lib/http2/node/client.d.ts.map +1 -1
- package/dist/lib/http2/node/server.d.ts +4 -4
- package/dist/lib/http2/node/server.d.ts.map +1 -1
- package/dist/lib/http2/node/websocketparser.d.ts +7 -6
- package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
- package/dist/lib/http2/parserbase.d.ts +16 -4
- package/dist/lib/http2/parserbase.d.ts.map +1 -1
- package/dist/lib/http2/parserbasehttp2.d.ts +9 -1
- package/dist/lib/http2/parserbasehttp2.d.ts.map +1 -1
- package/dist/lib/http2/session.d.ts.map +1 -1
- package/dist/lib/index.node.d.ts +1 -1
- package/dist/lib/index.node.d.ts.map +1 -1
- package/dist/lib/index.types.d.ts +1 -1
- package/dist/lib/index.types.d.ts.map +1 -1
- package/dist/lib/server.d.ts.map +1 -1
- package/dist/lib/session.d.ts +8 -4
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/types.d.ts +18 -6
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/webtransport.browser.d.ts.map +1 -1
- package/dist/lib/webtransport.node.d.ts +1 -0
- package/dist/lib/webtransport.node.d.ts.map +1 -1
- package/lib/http2/browser/browser.js +2 -1
- package/lib/http2/browser/browserparser.js +43 -7
- package/lib/http2/node/capsuleparser.js +41 -10
- package/lib/http2/node/client.js +28 -7
- package/lib/http2/node/server.js +117 -55
- package/lib/http2/node/websocketparser.js +42 -20
- package/lib/http2/parserbase.js +44 -7
- package/lib/http2/parserbasehttp2.js +15 -2
- package/lib/http2/session.js +1 -5
- package/lib/index.node.js +1 -1
- package/lib/index.types.js +1 -1
- package/lib/server.js +7 -4
- package/lib/session.js +5 -2
- package/lib/types.ts +19 -6
- package/lib/webtransport.browser.js +3 -2
- package/lib/webtransport.node.js +16 -5
- package/lib/webtransportbase.js +1 -1
- package/old_test/test.js +13 -1
- package/package.json +3 -2
- package/readme.md +4 -1
- package/test/bidirectional-streams.spec.js +31 -0
- package/test/datagrams.spec.js +12 -0
- package/test/fixtures/quiche.browser.js +2 -0
- package/test/fixtures/quiche.js +1 -0
- package/test/fixtures/read-stream.js +20 -7
- package/test/fixtures/reader-value.js +0 -5
- package/test/fixtures/server.js +70 -0
- package/test/session.spec.js +29 -3
- package/test/stream-limits.spec.js +12 -0
- package/test/unidirectional-streams.spec.js +12 -0
package/lib/http2/parserbase.js
CHANGED
|
@@ -30,6 +30,8 @@ export class ParserBase {
|
|
|
30
30
|
static WT_STREAM_DATA_BLOCKED = 0x190b4d42
|
|
31
31
|
static WT_STREAMS_BLOCKED_UNIDI = 0x190b4d43
|
|
32
32
|
static WT_STREAMS_BLOCKED_BIDI = 0x190b4d44
|
|
33
|
+
static CLOSE_WEBTRANSPORT_SESSION = 0x2843
|
|
34
|
+
static DRAIN_WEBTRANSPORT_SESSION = 0x78ae
|
|
33
35
|
static DATAGRAM = 0x00
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -38,7 +40,8 @@ export class ParserBase {
|
|
|
38
40
|
constructor({
|
|
39
41
|
nativesession,
|
|
40
42
|
isclient,
|
|
41
|
-
|
|
43
|
+
initialStreamSendWindowOffsetBidi,
|
|
44
|
+
initialStreamSendWindowOffsetUnidi,
|
|
42
45
|
initialStreamReceiveWindowOffset,
|
|
43
46
|
streamShouldAutoTuneReceiveWindow,
|
|
44
47
|
streamReceiveWindowSizeLimit
|
|
@@ -48,7 +51,8 @@ export class ParserBase {
|
|
|
48
51
|
/** @type {boolean} */
|
|
49
52
|
this.blocked = false
|
|
50
53
|
|
|
51
|
-
this.
|
|
54
|
+
this.initialStreamSendWindowOffsetUnidi = initialStreamSendWindowOffsetUnidi
|
|
55
|
+
this.initialStreamSendWindowOffsetBidi = initialStreamSendWindowOffsetBidi
|
|
52
56
|
this.initialStreamReceiveWindowOffset = initialStreamReceiveWindowOffset
|
|
53
57
|
this.streamShouldAutoTuneReceiveWindow = streamShouldAutoTuneReceiveWindow
|
|
54
58
|
this.streamReceiveWindowSizeLimit = streamReceiveWindowSizeLimit
|
|
@@ -67,16 +71,31 @@ export class ParserBase {
|
|
|
67
71
|
|
|
68
72
|
/**
|
|
69
73
|
* @abstract
|
|
70
|
-
* @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined}} bs
|
|
74
|
+
* @param{{type: Number, headerVints: Array<Number|bigint>, payload: Uint8Array|undefined, end?: () => void}} bs
|
|
71
75
|
*/
|
|
72
|
-
writeCapsule({ type, headerVints, payload }) {
|
|
76
|
+
writeCapsule({ type, headerVints, payload, end }) {
|
|
73
77
|
throw new Error('Implement writeCapsule in derived Class')
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
/**
|
|
77
81
|
* @param{{code: Number, reason: string}}arg
|
|
78
82
|
*/
|
|
79
|
-
sendClose({ code, reason }) {
|
|
83
|
+
sendClose({ code, reason }) {
|
|
84
|
+
const encoder = new TextEncoder()
|
|
85
|
+
const payload = encoder.encode('AAAA' + reason)
|
|
86
|
+
payload[0] = (code >> 24) & 0xff
|
|
87
|
+
payload[1] = (code >> 16) & 0xff
|
|
88
|
+
payload[2] = (code >> 8) & 0xff
|
|
89
|
+
payload[3] = code & 0xff
|
|
90
|
+
this.writeCapsule({
|
|
91
|
+
type: ParserBase.CLOSE_WEBTRANSPORT_SESSION,
|
|
92
|
+
headerVints: [],
|
|
93
|
+
payload,
|
|
94
|
+
end: () => {
|
|
95
|
+
this.closeHttp2Stream(code)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
}
|
|
80
99
|
|
|
81
100
|
/**
|
|
82
101
|
* @param {bigint} streamid
|
|
@@ -101,12 +120,15 @@ export class ParserBase {
|
|
|
101
120
|
return undefined
|
|
102
121
|
}
|
|
103
122
|
}
|
|
123
|
+
const unidirectional = !!(streamid & 0x2n)
|
|
104
124
|
const stream = new Http2WebTransportStream({
|
|
105
125
|
streamid,
|
|
106
|
-
unidirectional
|
|
126
|
+
unidirectional,
|
|
107
127
|
incoming,
|
|
108
128
|
capsuleParser: this,
|
|
109
|
-
sendWindowOffset:
|
|
129
|
+
sendWindowOffset: unidirectional
|
|
130
|
+
? this.initialStreamSendWindowOffsetUnidi
|
|
131
|
+
: this.initialStreamSendWindowOffsetBidi,
|
|
110
132
|
receiveWindowOffset: this.initialStreamReceiveWindowOffset,
|
|
111
133
|
shouldAutoTuneReceiveWindow: this.streamShouldAutoTuneReceiveWindow,
|
|
112
134
|
receiveWindowSizeLimit: this.streamReceiveWindowSizeLimit,
|
|
@@ -212,6 +234,21 @@ export class ParserBase {
|
|
|
212
234
|
}
|
|
213
235
|
}
|
|
214
236
|
|
|
237
|
+
/**
|
|
238
|
+
* @param {{code: number, reason: string}} opts
|
|
239
|
+
*/
|
|
240
|
+
onCloseWebTransportSession({ code, reason }) {
|
|
241
|
+
this.session.jsobj.onClose({
|
|
242
|
+
errorcode: code,
|
|
243
|
+
error: reason
|
|
244
|
+
})
|
|
245
|
+
this.closeHttp2Stream(code) // is this necessary
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
onDrain() {
|
|
249
|
+
this.session.jsobj.onGoAwayReceived()
|
|
250
|
+
}
|
|
251
|
+
|
|
215
252
|
/**
|
|
216
253
|
* @param {number} code
|
|
217
254
|
*/
|
|
@@ -4,6 +4,17 @@ import { ParserBase } from './parserbase.js'
|
|
|
4
4
|
* @typedef {import('node:http2').Http2Stream} Http2Stream
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @param{{offset: Number, buffer: Buffer, size: Number}} bs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function readUint32(bs) {
|
|
12
|
+
if (bs.offset + 4 > bs.size) return undefined
|
|
13
|
+
const toret = bs.buffer.readUInt32BE(bs.offset)
|
|
14
|
+
bs.offset += 4
|
|
15
|
+
return toret
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
/**
|
|
8
19
|
* @param{{offset: Number, buffer: Buffer, size: Number}} bs
|
|
9
20
|
*/
|
|
@@ -63,7 +74,8 @@ export class ParserBaseHttp2 extends ParserBase {
|
|
|
63
74
|
stream,
|
|
64
75
|
nativesession,
|
|
65
76
|
isclient,
|
|
66
|
-
|
|
77
|
+
initialStreamSendWindowOffsetBidi,
|
|
78
|
+
initialStreamSendWindowOffsetUnidi,
|
|
67
79
|
initialStreamReceiveWindowOffset,
|
|
68
80
|
streamShouldAutoTuneReceiveWindow,
|
|
69
81
|
streamReceiveWindowSizeLimit
|
|
@@ -71,7 +83,8 @@ export class ParserBaseHttp2 extends ParserBase {
|
|
|
71
83
|
super({
|
|
72
84
|
nativesession,
|
|
73
85
|
isclient,
|
|
74
|
-
|
|
86
|
+
initialStreamSendWindowOffsetBidi,
|
|
87
|
+
initialStreamSendWindowOffsetUnidi,
|
|
75
88
|
initialStreamReceiveWindowOffset,
|
|
76
89
|
streamShouldAutoTuneReceiveWindow,
|
|
77
90
|
streamReceiveWindowSizeLimit
|
package/lib/http2/session.js
CHANGED
|
@@ -209,10 +209,7 @@ export class Http2WebTransportSession {
|
|
|
209
209
|
*/
|
|
210
210
|
close({ code, reason }) {
|
|
211
211
|
this.capsParser.sendClose({ code, reason }) // this includes for ws closing the session!
|
|
212
|
-
//
|
|
213
|
-
if (this.stream) {
|
|
214
|
-
this.capsParser.closeHttp2Stream(code)
|
|
215
|
-
}
|
|
212
|
+
// should also close the stream
|
|
216
213
|
}
|
|
217
214
|
|
|
218
215
|
/**
|
|
@@ -252,7 +249,6 @@ export class Http2WebTransportSession {
|
|
|
252
249
|
* @param {{ code: number, reason: string }} arg
|
|
253
250
|
*/
|
|
254
251
|
closeConnection({ code, reason }) {
|
|
255
|
-
console.trace()
|
|
256
252
|
// called in case of failure in parsing or flowcontrol
|
|
257
253
|
this.jsobj.onClose({
|
|
258
254
|
errorcode: code,
|
package/lib/index.node.js
CHANGED
package/lib/index.types.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
// both imports from the browser side and for nodes to generate a joint type file
|
|
27
27
|
export { HttpServer, Http3Server, Http2Server } from './server.js'
|
|
28
|
-
export { WebTransport } from './webtransport.node.js'
|
|
28
|
+
export { WebTransport, quicheLoaded } from './webtransport.node.js'
|
|
29
29
|
|
|
30
30
|
export {
|
|
31
31
|
WebTransportPonyfill,
|
package/lib/server.js
CHANGED
|
@@ -82,8 +82,8 @@ class TransportIntServerProxy {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* @param {string} cert
|
|
86
|
-
* @param {string} privKey
|
|
85
|
+
* @param {string|string[]} cert
|
|
86
|
+
* @param {string|string[]} privKey
|
|
87
87
|
* @param {boolean} http2only
|
|
88
88
|
* */
|
|
89
89
|
updateCert(cert, privKey, http2only) {
|
|
@@ -245,16 +245,18 @@ export class HttpServer {
|
|
|
245
245
|
* @param {ServerSessionRequestEvent} args
|
|
246
246
|
*/
|
|
247
247
|
onSessionRequest(args) {
|
|
248
|
-
if (args.promise && args.header) {
|
|
248
|
+
if ((args.promise || args.protocol !== 'http3:libquiche') && args.header) {
|
|
249
249
|
if (!this.requestHandler) throw new Error('Request handler not set')
|
|
250
250
|
this.requestHandler({ header: args.header })
|
|
251
251
|
.then((/** @type {any} */ result) => {
|
|
252
252
|
log('oSR', result)
|
|
253
|
-
|
|
253
|
+
this.transportInt.finishSessionRequest({
|
|
254
254
|
promise: args.promise,
|
|
255
255
|
header: args.header,
|
|
256
256
|
session: args.session,
|
|
257
257
|
head: args.head,
|
|
258
|
+
transportPrivate: args.transportPrivate,
|
|
259
|
+
protocol: args.protocol,
|
|
258
260
|
...result
|
|
259
261
|
})
|
|
260
262
|
})
|
|
@@ -272,6 +274,7 @@ export class HttpServer {
|
|
|
272
274
|
const sesobj = new HttpWTSession({
|
|
273
275
|
object: args.session,
|
|
274
276
|
header: args.header,
|
|
277
|
+
userData: args.userData ?? {},
|
|
275
278
|
parentobj: this
|
|
276
279
|
})
|
|
277
280
|
args.session.jsobj = sesobj
|
package/lib/session.js
CHANGED
|
@@ -50,7 +50,8 @@ export class HttpWTSession {
|
|
|
50
50
|
* @param {object} args
|
|
51
51
|
* @param {import('./types').NativeHttpWTSession} [args.object]
|
|
52
52
|
* @param {HttpServer | HttpClient} args.parentobj
|
|
53
|
-
* @param {
|
|
53
|
+
* @param {Object | undefined} [args.header= undefined]
|
|
54
|
+
* @param {Object | undefined} [args.userData= undefined]
|
|
54
55
|
*/
|
|
55
56
|
constructor(args) {
|
|
56
57
|
if (args.object) {
|
|
@@ -68,8 +69,10 @@ export class HttpWTSession {
|
|
|
68
69
|
this.readyResolve = null
|
|
69
70
|
/** @type {(() => void) | null | undefined} */
|
|
70
71
|
this.closeHook = null
|
|
71
|
-
/** @type {(
|
|
72
|
+
/** @type {(Object | null | undefined)} */
|
|
72
73
|
this.header = args.header
|
|
74
|
+
/** @type {(Object | null | undefined)} */
|
|
75
|
+
this.userData = args.userData
|
|
73
76
|
|
|
74
77
|
/** @type {Promise<void>} */
|
|
75
78
|
this.ready = new Promise((resolve, reject) => {
|
package/lib/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
WebTransportOptions,
|
|
5
5
|
WebTransportSendGroup
|
|
6
6
|
} from './dom'
|
|
7
|
-
import type { IncomingHttpHeaders, Http2Stream } from 'http2'
|
|
7
|
+
import type { IncomingHttpHeaders, Http2Stream, ServerHttp2Stream } from 'http2'
|
|
8
8
|
import { ParserBase } from './http2/parserbase'
|
|
9
9
|
import { Http2WebTransportSession } from './http2/session'
|
|
10
10
|
import { HttpClient } from './client'
|
|
@@ -103,11 +103,14 @@ export interface ReadBuffer {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export interface NativeFinishSessionRequest {
|
|
106
|
+
path: string
|
|
106
107
|
header: IncomingHttpHeaders
|
|
107
|
-
session:
|
|
108
|
+
session: ServerHttp2Stream
|
|
108
109
|
status: number
|
|
109
110
|
protocol: 'capsule' | 'websocket' | 'websocketoverhttp1' | 'http3'
|
|
110
111
|
head?: Buffer
|
|
112
|
+
userData?: object
|
|
113
|
+
transportPrivate?: object
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
export type Purpose =
|
|
@@ -219,6 +222,7 @@ export interface HttpWTServerSessionVisitorEvent
|
|
|
219
222
|
extends HttpWTSessionVisitorEvent {
|
|
220
223
|
path: string
|
|
221
224
|
header: Object
|
|
225
|
+
userData?: Object | undefined
|
|
222
226
|
}
|
|
223
227
|
|
|
224
228
|
export interface ServerSessionRequestEvent {
|
|
@@ -228,6 +232,7 @@ export interface ServerSessionRequestEvent {
|
|
|
228
232
|
session: any
|
|
229
233
|
object?: any // the actual transport object itself, actually present on all messages, but required here
|
|
230
234
|
protocol: string //'capsule' | 'websocket' | 'http3'
|
|
235
|
+
transportPrivate?: Object //private information from the transport object
|
|
231
236
|
}
|
|
232
237
|
|
|
233
238
|
/**
|
|
@@ -266,10 +271,17 @@ export interface WebTransportSessionImpl extends WebTransportSession {
|
|
|
266
271
|
state: WebTransportSessionState
|
|
267
272
|
}
|
|
268
273
|
|
|
274
|
+
export type QUICHE_LOG_OFF = -1
|
|
275
|
+
export type QUICHE_LOG_INFO = 0
|
|
276
|
+
export type QUICHE_LOG_WARNING = 1
|
|
277
|
+
export type QUICHE_LOG_ERROR = 2
|
|
278
|
+
export type QUICHE_LOG_FATAL = 3
|
|
279
|
+
export type QUICHE_LOG = QUICHE_LOG_OFF | QUICHE_LOG_INFO | QUICHE_LOG_WARNING | QUICHE_LOG_ERROR | QUICHE_LOG_FATAL
|
|
280
|
+
|
|
269
281
|
export interface HttpWebTransportInit extends WebTransportOptions {
|
|
270
282
|
host: string
|
|
271
283
|
port: string | number
|
|
272
|
-
quicheLogVerbose?:
|
|
284
|
+
quicheLogVerbose?: QUICHE_LOG
|
|
273
285
|
forceIpv6?: boolean
|
|
274
286
|
localPort?: number
|
|
275
287
|
}
|
|
@@ -284,8 +296,8 @@ export interface HttpServerInit extends HttpWebTransportInit {
|
|
|
284
296
|
port: string | number
|
|
285
297
|
host: string
|
|
286
298
|
secret: string
|
|
287
|
-
cert: string
|
|
288
|
-
privKey: string
|
|
299
|
+
cert: string | string[]
|
|
300
|
+
privKey: string | string[]
|
|
289
301
|
maxConnections?: number
|
|
290
302
|
initialStreamFlowControlWindow?: number
|
|
291
303
|
streamShouldAutoTuneReceiveWindow?: boolean
|
|
@@ -331,7 +343,8 @@ export type CreateParserFunction = (
|
|
|
331
343
|
export interface ParserInit {
|
|
332
344
|
isclient: boolean
|
|
333
345
|
nativesession: any
|
|
334
|
-
|
|
346
|
+
initialStreamSendWindowOffsetBidi: number
|
|
347
|
+
initialStreamSendWindowOffsetUnidi: number
|
|
335
348
|
initialStreamReceiveWindowOffset: number
|
|
336
349
|
streamShouldAutoTuneReceiveWindow: boolean
|
|
337
350
|
streamReceiveWindowSizeLimit: number
|
|
@@ -61,9 +61,10 @@ export class WebTransportPonyfill extends WebTransportBase {
|
|
|
61
61
|
* @param{{client: HttpClient, sessionint: HttpWTSession, ourl: URL}} args
|
|
62
62
|
*/
|
|
63
63
|
startUpConnection({ client, sessionint, ourl }) {
|
|
64
|
+
const path = ourl.pathname + (ourl.search ?? '')
|
|
64
65
|
client
|
|
65
|
-
.handleConnection({ createTransport: true, path
|
|
66
|
-
.then(() => client.createWTSession(sessionint,
|
|
66
|
+
.handleConnection({ createTransport: true, path })
|
|
67
|
+
.then(() => client.createWTSession(sessionint, path))
|
|
67
68
|
.catch((error) => {
|
|
68
69
|
client.closeHookSession()
|
|
69
70
|
sessionint.readyReject(error)
|
package/lib/webtransport.node.js
CHANGED
|
@@ -18,9 +18,13 @@ let Http3WebTransportClientSocket
|
|
|
18
18
|
* @type {new (arg0: import("./types").HttpWebTransportInit) => any}
|
|
19
19
|
*/
|
|
20
20
|
let Http3WebTransportClient
|
|
21
|
+
/**
|
|
22
|
+
* @type {boolean}
|
|
23
|
+
*/
|
|
24
|
+
let quicheLoadedReady
|
|
21
25
|
// @ts-ignore
|
|
22
26
|
// eslint-disable-next-line no-unused-vars
|
|
23
|
-
const quicheLoaded = new Promise((resolve, reject) => {
|
|
27
|
+
export const quicheLoaded = new Promise((resolve, reject) => {
|
|
24
28
|
// @ts-ignore
|
|
25
29
|
import('@fails-components/webtransport-transport-http3-quiche')
|
|
26
30
|
.then(
|
|
@@ -39,6 +43,9 @@ const quicheLoaded = new Promise((resolve, reject) => {
|
|
|
39
43
|
resolve(undefined)
|
|
40
44
|
}
|
|
41
45
|
)
|
|
46
|
+
.finally(() => {
|
|
47
|
+
quicheLoadedReady = true
|
|
48
|
+
})
|
|
42
49
|
.catch((error) => {
|
|
43
50
|
log('Problem loading http3-quiche transport', error)
|
|
44
51
|
reject(error)
|
|
@@ -68,17 +75,18 @@ export class WebTransport extends WebTransportBase {
|
|
|
68
75
|
* @param{{client: HttpClient, sessionint: HttpWTSession, ourl: URL}} args
|
|
69
76
|
*/
|
|
70
77
|
startUpConnection({ client, sessionint, ourl }) {
|
|
78
|
+
const path = ourl.pathname + (ourl.search ?? '')
|
|
71
79
|
client
|
|
72
|
-
.handleConnection({ createTransport: true, path
|
|
73
|
-
.then(() => client.createWTSession(sessionint,
|
|
80
|
+
.handleConnection({ createTransport: true, path })
|
|
81
|
+
.then(() => client.createWTSession(sessionint, path))
|
|
74
82
|
.catch((error) => {
|
|
75
83
|
if (client.transportIntSwitchToReliable) {
|
|
76
84
|
log('Connecting to unreliable failed:', error)
|
|
77
85
|
log('Now switching to reliable')
|
|
78
86
|
client.transportIntSwitchToReliable()
|
|
79
87
|
client
|
|
80
|
-
.handleConnection({ createTransport: false, path
|
|
81
|
-
.then(() => client.createWTSession(sessionint,
|
|
88
|
+
.handleConnection({ createTransport: false, path })
|
|
89
|
+
.then(() => client.createWTSession(sessionint, path))
|
|
82
90
|
.catch((error) => {
|
|
83
91
|
client.closeHookSession()
|
|
84
92
|
sessionint.readyReject(error)
|
|
@@ -102,6 +110,9 @@ export class WebTransport extends WebTransportBase {
|
|
|
102
110
|
*/
|
|
103
111
|
createClient(args) {
|
|
104
112
|
let createUnreliableClient
|
|
113
|
+
// @ts-ignore
|
|
114
|
+
if (!quicheLoadedReady && !args?.forceReliable)
|
|
115
|
+
throw new Error('Lib quiche loading attempt did not end')
|
|
105
116
|
if (checkQuicheInit) {
|
|
106
117
|
createUnreliableClient = function (/** @type {any} */ _client) {
|
|
107
118
|
if (
|
package/lib/webtransportbase.js
CHANGED
package/old_test/test.js
CHANGED
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
// this file runs various tests
|
|
6
6
|
|
|
7
7
|
import { generateWebTransportCertificate } from '../test/fixtures/certificate.js'
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Http3Server,
|
|
10
|
+
Http2Server,
|
|
11
|
+
WebTransport,
|
|
12
|
+
quicheLoaded
|
|
13
|
+
} from '../lib/index.node.js'
|
|
9
14
|
import { echoTestsConnection, runEchoServer } from './testsuite.js'
|
|
10
15
|
|
|
11
16
|
let http2 = false
|
|
@@ -15,6 +20,13 @@ if (process.argv.some((el) => el === 'http2')) {
|
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
async function run() {
|
|
23
|
+
if (
|
|
24
|
+
process.env.USE_HTTP2 !== 'true' &&
|
|
25
|
+
process.env.USE_PONYFILL !== 'true' &&
|
|
26
|
+
process.env.USE_POLYFILL !== 'true'
|
|
27
|
+
) {
|
|
28
|
+
await quicheLoaded
|
|
29
|
+
}
|
|
18
30
|
console.log('try connecting to server that does not exist')
|
|
19
31
|
const badClient = new WebTransport('https://127.0.0.1:49823/echo', {
|
|
20
32
|
serverCertificateHashes: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fails-components/webtransport",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A component to add webtransport support (server and client) to node.js using libquiche",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"bugs": {
|
|
44
44
|
"url": "https://github.com/fails-components/webtransport/issues"
|
|
45
45
|
},
|
|
46
|
-
"homepage": "https://github.com/fails-components/webtransport/main
|
|
46
|
+
"homepage": "https://github.com/fails-components/webtransport/tree/master/main#readme",
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@types/debug": "^4.1.7",
|
|
49
49
|
"bindings": "^1.5.0",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"browser": {
|
|
85
85
|
"./test/fixtures/webtransport.js": "./test/fixtures/webtransport.browser.js",
|
|
86
|
+
"./test/fixtures/quiche.js": "./test/fixtures/quiche.browser.js",
|
|
86
87
|
"./lib/webstreams.js": "./lib/webstreams.browser.js"
|
|
87
88
|
}
|
|
88
89
|
}
|
package/readme.md
CHANGED
|
@@ -35,7 +35,7 @@ Note, that it is only partially implemented and also some http2 features require
|
|
|
35
35
|
Use for the server side either `HttpServer` for http/2 and http/3 support (including WebTransport over WebSocket), Http3Server for http/3 only and Http2Server for http/2 only.
|
|
36
36
|
For the client side pass the options `forceReliable`, `requireUnreliable` etc. to force/select a type of transport.
|
|
37
37
|
|
|
38
|
-
**Note, that the http/2 webtransport protocol (not supported by any browser yet),
|
|
38
|
+
**Note, that the http/2 webtransport protocol (not supported by any browser yet), requires support for sending and receiving initial a node version with support for receiving customSettings. Versions without support can cause a problem with the initial flow control of the WebTransport streams. Note, that for sending and receiving the required initial setting you need a very recent version (>=21.6.1 sending >= 21.7.0 receiving) of node.js.** This limitation does not apply to the polyfill/ponyfill and the webtransport over websocket protocol.
|
|
39
39
|
|
|
40
40
|
The current proposal for the WebTransport over WebSocket protocol can be found [here] (https://datatracker.ietf.org/doc/draft-richter-webtransport-websocket/).
|
|
41
41
|
|
|
@@ -126,6 +126,9 @@ The `WebTransportSession` should behave most like a WebTransport object on the b
|
|
|
126
126
|
### Using a client
|
|
127
127
|
The `WebTransport` object for node or the `WebTransportPonyfill`, `WebTransportPolyfill` objects for the browser, behave like the `WebTransport` client object for the browser with few additions and some missing features.
|
|
128
128
|
|
|
129
|
+
As the http/3 package is loaded dynamically and the WebTransport object is created synchronously, you may want to make sure, that the modules are already loaded. You can do so, by waiting for the promise `quicheLoaded` exported by the package.
|
|
130
|
+
|
|
131
|
+
|
|
129
132
|
## Specification divergence
|
|
130
133
|
|
|
131
134
|
This module implements parts of the [WebTransport spec](https://datatracker.ietf.org/doc/html/draft-vvv-webtransport-quic-00) but not all of it.
|
|
@@ -6,6 +6,7 @@ import { readStream } from './fixtures/read-stream.js'
|
|
|
6
6
|
import { writeStream } from './fixtures/write-stream.js'
|
|
7
7
|
import { readCertHash } from './fixtures/read-cert-hash.js'
|
|
8
8
|
import WebTransport from './fixtures/webtransport.js'
|
|
9
|
+
import { quicheLoaded } from './fixtures/quiche.js'
|
|
9
10
|
import * as ui8 from 'uint8arrays'
|
|
10
11
|
import {
|
|
11
12
|
KNOWN_BYTES,
|
|
@@ -38,6 +39,17 @@ describe('bidirectional streams', function () {
|
|
|
38
39
|
// @ts-ignore
|
|
39
40
|
delete wtOptions.serverCertificateHashes
|
|
40
41
|
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
if (
|
|
45
|
+
process.env.USE_HTTP2 !== 'true' &&
|
|
46
|
+
process.env.USE_PONYFILL !== 'true' &&
|
|
47
|
+
process.env.USE_POLYFILL !== 'true'
|
|
48
|
+
) {
|
|
49
|
+
await quicheLoaded
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
41
53
|
// @ts-ignore
|
|
42
54
|
afterEach(async () => {
|
|
43
55
|
if (client != null) {
|
|
@@ -116,4 +128,23 @@ describe('bidirectional streams', function () {
|
|
|
116
128
|
expect(result).to.have.property('reason', '')
|
|
117
129
|
expect(result).to.have.property('closeCode', 0)
|
|
118
130
|
})
|
|
131
|
+
|
|
132
|
+
it('receives data from server and checks receiving stream fin', async () => {
|
|
133
|
+
// client context - waits for the server to open a bidi stream then pipes it back to them
|
|
134
|
+
client = new WebTransport(
|
|
135
|
+
`${process.env.SERVER_URL}/bidirectional_server_fin_send`,
|
|
136
|
+
wtOptions
|
|
137
|
+
)
|
|
138
|
+
await client.ready
|
|
139
|
+
|
|
140
|
+
const bidiStream = await getReaderValue(client.incomingBidirectionalStreams)
|
|
141
|
+
const reader = bidiStream.readable.getReader()
|
|
142
|
+
|
|
143
|
+
while (true) {
|
|
144
|
+
const res = await reader.read()
|
|
145
|
+
if (res.done) {
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
})
|
|
119
150
|
})
|
package/test/datagrams.spec.js
CHANGED
|
@@ -5,6 +5,7 @@ import { expect } from './fixtures/chai.js'
|
|
|
5
5
|
import { readStream } from './fixtures/read-stream.js'
|
|
6
6
|
import { readCertHash } from './fixtures/read-cert-hash.js'
|
|
7
7
|
import { pTimeout } from './fixtures/p-timeout.js'
|
|
8
|
+
import { quicheLoaded } from './fixtures/quiche.js'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @template T
|
|
@@ -32,6 +33,17 @@ describe('datagrams', function () {
|
|
|
32
33
|
// @ts-ignore
|
|
33
34
|
delete wtOptions.serverCertificateHashes
|
|
34
35
|
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
if (
|
|
39
|
+
process.env.USE_HTTP2 !== 'true' &&
|
|
40
|
+
process.env.USE_PONYFILL !== 'true' &&
|
|
41
|
+
process.env.USE_POLYFILL !== 'true'
|
|
42
|
+
) {
|
|
43
|
+
await quicheLoaded
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
35
47
|
// @ts-ignore
|
|
36
48
|
afterEach(async () => {
|
|
37
49
|
if (client != null) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { quicheLoaded } from '../../lib/index.node.js'
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @template T
|
|
3
|
-
* @typedef {import('../../lib/webstreams').ReadableStream<T>} ReadableStream<T>
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* Read a stream contents to the end and return it
|
|
8
3
|
*
|
|
9
4
|
* @template T
|
|
10
5
|
* @param {ReadableStream<T>} readable
|
|
11
6
|
* @param {number} [expected]
|
|
12
|
-
* @returns
|
|
7
|
+
* @returns {T[]}
|
|
13
8
|
*/
|
|
14
9
|
export async function readStream(readable, expected) {
|
|
15
10
|
const reader = readable.getReader()
|
|
@@ -27,7 +22,6 @@ export async function readStream(readable, expected) {
|
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
if (value != null) {
|
|
30
|
-
// @ts-ignore
|
|
31
25
|
outputlength += value.length
|
|
32
26
|
output.push(value)
|
|
33
27
|
}
|
|
@@ -42,3 +36,22 @@ export async function readStream(readable, expected) {
|
|
|
42
36
|
reader.releaseLock()
|
|
43
37
|
}
|
|
44
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
|
+
}
|