@fails-components/webtransport 0.2.1 → 0.3.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/CMakeLists.txt +3 -0
- package/dist/lib/client.d.ts +7 -3
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/dom.d.ts +15 -4
- package/dist/lib/dom.d.ts.map +1 -1
- package/dist/lib/error.d.ts +8 -0
- package/dist/lib/error.d.ts.map +1 -0
- package/dist/lib/event-loop.d.ts +4 -2
- package/dist/lib/event-loop.d.ts.map +1 -1
- package/dist/lib/native.d.ts.map +1 -1
- package/dist/lib/server.d.ts +5 -2
- package/dist/lib/server.d.ts.map +1 -1
- package/dist/lib/session.d.ts +36 -23
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/stream.d.ts +15 -4
- package/dist/lib/stream.d.ts.map +1 -1
- package/dist/lib/transport.d.ts +12 -3
- package/dist/lib/transport.d.ts.map +1 -1
- package/dist/lib/types.d.ts +43 -4
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/webtransport.d.ts +11 -21
- package/dist/lib/webtransport.d.ts.map +1 -1
- package/dist/test/datagrams.spec.d.ts.map +1 -1
- package/dist/test/event-loop.node.d.ts +2 -0
- package/dist/test/event-loop.node.d.ts.map +1 -0
- package/dist/test/fixtures/chai.d.ts.map +1 -1
- package/dist/test/fixtures/known-bytes.d.ts +6 -0
- package/dist/test/fixtures/known-bytes.d.ts.map +1 -0
- package/dist/test/fixtures/read-cert-hash.d.ts +6 -0
- package/dist/test/fixtures/read-cert-hash.d.ts.map +1 -0
- package/dist/test/fixtures/read-stream.d.ts +10 -1
- package/dist/test/fixtures/read-stream.d.ts.map +1 -1
- package/dist/test/fixtures/reader-value.d.ts +16 -1
- package/dist/test/fixtures/reader-value.d.ts.map +1 -1
- package/dist/test/fixtures/server.d.ts +0 -5
- package/dist/test/fixtures/server.d.ts.map +1 -1
- package/dist/test/fixtures/webtransport.browser.d.ts +3 -0
- package/dist/test/fixtures/webtransport.browser.d.ts.map +1 -0
- package/dist/test/fixtures/webtransport.d.ts +3 -0
- package/dist/test/fixtures/webtransport.d.ts.map +1 -0
- package/dist/test/fixtures/write-stream.d.ts.map +1 -1
- package/dist/test/index.d.ts +2 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/session.spec.d.ts +5 -0
- package/dist/test/session.spec.d.ts.map +1 -0
- package/dist/test/unidirectional-streams.spec.d.ts +1 -4
- package/dist/test/unidirectional-streams.spec.d.ts.map +1 -1
- package/lib/client.js +57 -43
- package/lib/dom.ts +18 -4
- package/lib/error.js +10 -0
- package/lib/event-loop.js +8 -5
- package/lib/native.js +4 -1
- package/lib/server.js +18 -10
- package/lib/session.js +145 -32
- package/lib/stream.js +52 -24
- package/lib/transport.js +48 -10
- package/lib/types.ts +53 -5
- package/lib/utils.js +13 -0
- package/lib/webtransport.js +10 -18
- package/{test → old_test}/echoserver.js +1 -2
- package/{test → old_test}/test.js +5 -3
- package/package.json +16 -5
- package/readme.md +64 -0
- package/src/http3client.cc +2 -2
- package/src/http3dispatcher.cc +3 -2
- package/src/http3dispatcher.h +2 -1
- package/src/http3eventloop.cc +106 -2
- package/src/http3eventloop.h +13 -0
- package/src/http3wtsessionvisitor.cc +2 -0
- package/src/http3wtsessionvisitor.h +41 -1
- package/test/bidirectional-streams.spec.js +41 -97
- package/test/datagrams.spec.js +50 -99
- package/test/fixtures/chai.js +2 -0
- package/test/fixtures/known-bytes.js +15 -0
- package/test/fixtures/read-cert-hash.js +7 -0
- package/test/fixtures/read-stream.js +5 -0
- package/test/fixtures/reader-value.js +32 -0
- package/test/fixtures/server.js +237 -21
- package/test/fixtures/webtransport.browser.js +2 -0
- package/test/fixtures/webtransport.js +3 -0
- package/test/fixtures/write-stream.js +8 -1
- package/test/index.js +87 -0
- package/test/session.spec.js +159 -0
- package/test/unidirectional-streams.spec.js +67 -138
- package/tsconfig.json +0 -1
- package/dist/test/echoclient.d.ts +0 -2
- package/dist/test/echoclient.d.ts.map +0 -1
- package/dist/test/echoserver.d.ts +0 -2
- package/dist/test/echoserver.d.ts.map +0 -1
- package/dist/test/event-loop.spec.d.ts +0 -2
- package/dist/test/event-loop.spec.d.ts.map +0 -1
- package/dist/test/test.d.ts +0 -2
- package/dist/test/test.d.ts.map +0 -1
- package/dist/test/testsuite.d.ts +0 -25
- package/dist/test/testsuite.d.ts.map +0 -1
- package/platform/base/strings/utf_ostream_operators.h +0 -5
- /package/{test → old_test}/echoclient.js +0 -0
- /package/{test → old_test}/testsuite.js +0 -0
- /package/test/{event-loop.spec.js → event-loop.node.js} +0 -0
package/lib/session.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ReadableStream, WritableStream } from 'node:stream/web'
|
|
2
2
|
import { Http3WTStream } from './stream.js'
|
|
3
|
+
import { WebTransportError } from './error.js'
|
|
4
|
+
import { logger } from './utils.js'
|
|
5
|
+
|
|
6
|
+
const log = logger(`webtransport:http3wtsession(${process.pid})`)
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* WebTransport session events
|
|
@@ -9,6 +13,8 @@ import { Http3WTStream } from './stream.js'
|
|
|
9
13
|
* @typedef {import('./types').DatagramReceivedEvent} DatagramReceivedEvent
|
|
10
14
|
* @typedef {import('./types').DatagramSendEvent} DatagramSendEvent
|
|
11
15
|
* @typedef {import('./types').GoawayReceivedEvent} GoawayReceivedEvent
|
|
16
|
+
* @typedef {import('./types').DatagramStatsEvent} DatagramStatsEvent
|
|
17
|
+
* @typedef {import('./types').SessionStatsEvent} SessionStatsEvent
|
|
12
18
|
* @typedef {import('./types').NewStreamEvent} NewStreamEvent
|
|
13
19
|
*
|
|
14
20
|
* @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo
|
|
@@ -19,6 +25,7 @@ import { Http3WTStream } from './stream.js'
|
|
|
19
25
|
* @typedef {import('./dom').WebTransportReliabilityMode} WebTransportReliabilityMode
|
|
20
26
|
* @typedef {import('./dom').WebTransportCongestionControl} WebTransportCongestionControl
|
|
21
27
|
* @typedef {import('./dom').WebTransportStats} WebTransportStats
|
|
28
|
+
* @typedef {import('./dom').WebTransportDatagramStats} WebTransportDatagramStats
|
|
22
29
|
*
|
|
23
30
|
* @typedef {import('./types').NativeHttp3WTSession} NativeHttp3WTSession
|
|
24
31
|
*
|
|
@@ -49,7 +56,7 @@ export class Http3WTSession {
|
|
|
49
56
|
}
|
|
50
57
|
this.parentobj = args.parentobj
|
|
51
58
|
/** @type {import('./types').WebTransportSessionState} */
|
|
52
|
-
this.state = '
|
|
59
|
+
this.state = 'connecting'
|
|
53
60
|
|
|
54
61
|
/** @type {((value?: any) => void) | null | undefined} */
|
|
55
62
|
this.readyResolve = null
|
|
@@ -96,7 +103,7 @@ export class Http3WTSession {
|
|
|
96
103
|
|
|
97
104
|
/** @type {Array<() => void>} */
|
|
98
105
|
this.writeDatagramRes = []
|
|
99
|
-
/** @type {Array<() => void>} */
|
|
106
|
+
/** @type {Array<(err?: Error) => void>} */
|
|
100
107
|
this.writeDatagramRej = []
|
|
101
108
|
/** @type {Array<Promise<void>>} */
|
|
102
109
|
this.writeDatagramProm = []
|
|
@@ -125,7 +132,7 @@ export class Http3WTSession {
|
|
|
125
132
|
this.writeDatagramRej.push(reject)
|
|
126
133
|
})
|
|
127
134
|
this.writeDatagramProm.push(ret)
|
|
128
|
-
|
|
135
|
+
log.trace('b4 datagram write', chunk)
|
|
129
136
|
if (this.objint == null) {
|
|
130
137
|
throw new Error('this.objint is not set')
|
|
131
138
|
}
|
|
@@ -148,6 +155,16 @@ export class Http3WTSession {
|
|
|
148
155
|
/** @type {Array<(err?: Error) => void>} */
|
|
149
156
|
this.rejectUniDi = []
|
|
150
157
|
|
|
158
|
+
/** @type {Array<(stats: WebTransportStats) => void>} */
|
|
159
|
+
this.resolveSessionStats = []
|
|
160
|
+
/** @type {Array<(err?: Error) => void>} */
|
|
161
|
+
this.rejectSessionStats = []
|
|
162
|
+
|
|
163
|
+
/** @type {Array<(stats: WebTransportDatagramStats) => void>} */
|
|
164
|
+
this.resolveDatagramStats = []
|
|
165
|
+
/** @type {Array<(err?: Error) => void>} */
|
|
166
|
+
this.rejectDatagramStats = []
|
|
167
|
+
|
|
151
168
|
/** @type {Set<WebTransportSendStream>} */
|
|
152
169
|
this.sendStreams = new Set()
|
|
153
170
|
/** @type {Set<WebTransportReceiveStream>} */
|
|
@@ -172,25 +189,72 @@ export class Http3WTSession {
|
|
|
172
189
|
}
|
|
173
190
|
|
|
174
191
|
getStats() {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
numIncomingStreamsCreated: 0,
|
|
182
|
-
bytesReceived: BigInt(0),
|
|
183
|
-
packetsReceived: BigInt(0),
|
|
184
|
-
smoothedRtt: 0,
|
|
185
|
-
rttVariation: 0,
|
|
186
|
-
minRtt: 0,
|
|
187
|
-
datagrams: {
|
|
188
|
-
timestamp: Date.now(),
|
|
189
|
-
expiredOutgoing: BigInt(0),
|
|
190
|
-
droppedIncoming: BigInt(0),
|
|
191
|
-
lostOutgoing: BigInt(0)
|
|
192
|
-
}
|
|
192
|
+
if (this.objint == null) {
|
|
193
|
+
throw new Error('this.objint not set')
|
|
194
|
+
}
|
|
195
|
+
const prom = new Promise((resolve, reject) => {
|
|
196
|
+
this.resolveSessionStats.push(resolve)
|
|
197
|
+
this.rejectSessionStats.push(reject)
|
|
193
198
|
})
|
|
199
|
+
this.objint.orderSessionStats()
|
|
200
|
+
return prom
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {SessionStatsEvent} evt
|
|
205
|
+
*/
|
|
206
|
+
onSessionStats({
|
|
207
|
+
timestamp,
|
|
208
|
+
expiredOutgoing = BigInt(0),
|
|
209
|
+
lostOutgoing = BigInt(0),
|
|
210
|
+
// non Datagram
|
|
211
|
+
minRtt = 0,
|
|
212
|
+
smoothedRtt = 0,
|
|
213
|
+
rttVariation = 0,
|
|
214
|
+
estimatedSendRateBps
|
|
215
|
+
}) {
|
|
216
|
+
const res = this.resolveSessionStats.pop()
|
|
217
|
+
this.rejectSessionStats.pop()
|
|
218
|
+
if (res)
|
|
219
|
+
res({
|
|
220
|
+
timestamp,
|
|
221
|
+
bytesSent: BigInt(0),
|
|
222
|
+
packetsSent: BigInt(0),
|
|
223
|
+
packetsLost: BigInt(0),
|
|
224
|
+
numOutgoingStreamsCreated: 0,
|
|
225
|
+
numIncomingStreamsCreated: 0,
|
|
226
|
+
bytesReceived: BigInt(0),
|
|
227
|
+
packetsReceived: BigInt(0),
|
|
228
|
+
smoothedRtt,
|
|
229
|
+
rttVariation,
|
|
230
|
+
minRtt,
|
|
231
|
+
estimatedSendRate: estimatedSendRateBps,
|
|
232
|
+
datagrams: {
|
|
233
|
+
timestamp,
|
|
234
|
+
expiredOutgoing,
|
|
235
|
+
droppedIncoming: BigInt(0),
|
|
236
|
+
lostOutgoing
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {DatagramStatsEvent} evt
|
|
243
|
+
*/
|
|
244
|
+
onDatagramStats({
|
|
245
|
+
timestamp,
|
|
246
|
+
expiredOutgoing = BigInt(0),
|
|
247
|
+
lostOutgoing = BigInt(0)
|
|
248
|
+
}) {
|
|
249
|
+
const res = this.resolveDatagramStats.pop()
|
|
250
|
+
this.rejectDatagramStats.pop()
|
|
251
|
+
if (res)
|
|
252
|
+
res({
|
|
253
|
+
timestamp,
|
|
254
|
+
expiredOutgoing,
|
|
255
|
+
droppedIncoming: BigInt(0),
|
|
256
|
+
lostOutgoing
|
|
257
|
+
})
|
|
194
258
|
}
|
|
195
259
|
|
|
196
260
|
async waitForDatagramsSend() {
|
|
@@ -198,7 +262,7 @@ export class Http3WTSession {
|
|
|
198
262
|
try {
|
|
199
263
|
await Promise.allSettled(this.writeDatagramProm)
|
|
200
264
|
} catch (error) {
|
|
201
|
-
|
|
265
|
+
log.error('datagram promise failed ', error)
|
|
202
266
|
}
|
|
203
267
|
}
|
|
204
268
|
}
|
|
@@ -299,7 +363,7 @@ export class Http3WTSession {
|
|
|
299
363
|
* @returns {void}
|
|
300
364
|
*/
|
|
301
365
|
close(closeInfo) {
|
|
302
|
-
|
|
366
|
+
log('closeinfo', closeInfo)
|
|
303
367
|
if (this.state === 'closed' || this.state === 'failed') return
|
|
304
368
|
if (this.objint) {
|
|
305
369
|
this.objint.close({
|
|
@@ -310,6 +374,7 @@ export class Http3WTSession {
|
|
|
310
374
|
}
|
|
311
375
|
|
|
312
376
|
onReady(/* error */) {
|
|
377
|
+
this.state = 'connected'
|
|
313
378
|
this.reliability = 'supports-unreliable'
|
|
314
379
|
if (this.readyResolve) this.readyResolve()
|
|
315
380
|
delete this.readyResolve
|
|
@@ -320,10 +385,36 @@ export class Http3WTSession {
|
|
|
320
385
|
*/
|
|
321
386
|
onClose(args) {
|
|
322
387
|
delete this.objint // not valid any more
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
388
|
+
|
|
389
|
+
if (this.state !== 'connected') {
|
|
390
|
+
log.error(
|
|
391
|
+
'session was closed before state was "connected" - it was "%s"',
|
|
392
|
+
this.state
|
|
393
|
+
)
|
|
394
|
+
this.state = 'failed'
|
|
395
|
+
|
|
396
|
+
// make sure the event loop can still exit
|
|
397
|
+
if (this.closeHook) {
|
|
398
|
+
this.closeHook()
|
|
399
|
+
delete this.closeHook
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// closed before connected
|
|
403
|
+
const error = new WebTransportError('Opening handshake failed.')
|
|
404
|
+
this.readyReject(error)
|
|
405
|
+
this.closedReject(error)
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
log('onClose')
|
|
410
|
+
const error = new WebTransportError('Session closed')
|
|
411
|
+
|
|
412
|
+
for (const rej of this.rejectBiDi) rej(error)
|
|
413
|
+
for (const rej of this.rejectUniDi) rej(error)
|
|
414
|
+
for (const rej of this.writeDatagramRej) rej(error)
|
|
415
|
+
for (const rej of this.rejectSessionStats) rej(error)
|
|
416
|
+
for (const rej of this.rejectDatagramStats) rej(error)
|
|
417
|
+
|
|
327
418
|
this.writeDatagramRej = []
|
|
328
419
|
this.writeDatagramRes = []
|
|
329
420
|
this.writeDatagramProm = []
|
|
@@ -332,14 +423,27 @@ export class Http3WTSession {
|
|
|
332
423
|
this.rejectBiDi = []
|
|
333
424
|
this.rejectUniDi = []
|
|
334
425
|
|
|
426
|
+
this.resolveSessionStats = []
|
|
427
|
+
this.rejectSessionStats = []
|
|
428
|
+
this.resolveDatagramStats = []
|
|
429
|
+
this.rejectDatagramStats = []
|
|
430
|
+
|
|
335
431
|
this.incomBiDiController.close()
|
|
336
432
|
this.incomUniDiController.close()
|
|
337
433
|
this.incomDatagramController.close()
|
|
338
434
|
// this.outgoDatagramController.error(errorcode)
|
|
339
435
|
this.state = 'closed'
|
|
340
436
|
|
|
341
|
-
|
|
342
|
-
|
|
437
|
+
const wtError = new WebTransportError(
|
|
438
|
+
`Session closed (on process ${process.pid}) with code ` +
|
|
439
|
+
args.errorcode +
|
|
440
|
+
' and reason' +
|
|
441
|
+
args.error
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
this.sendStreamsController.forEach((ele) => ele.error(wtError))
|
|
445
|
+
this.receiveStreamsController.forEach((ele) => ele.error(wtError))
|
|
446
|
+
|
|
343
447
|
this.streamObjs.forEach((ele) => (ele.readableclosed = true))
|
|
344
448
|
|
|
345
449
|
this.sendStreams.clear()
|
|
@@ -351,7 +455,7 @@ export class Http3WTSession {
|
|
|
351
455
|
if (this.closedResolve)
|
|
352
456
|
this.closedResolve({
|
|
353
457
|
closeCode: args.errorcode,
|
|
354
|
-
reason: args.error ? args.error : '
|
|
458
|
+
reason: args.error ? args.error : ''
|
|
355
459
|
})
|
|
356
460
|
if (this.closeHook) {
|
|
357
461
|
this.closeHook()
|
|
@@ -411,6 +515,7 @@ export class Http3WTSession {
|
|
|
411
515
|
* @param {DatagramReceivedEvent} args
|
|
412
516
|
*/
|
|
413
517
|
onDatagramReceived(args) {
|
|
518
|
+
log.trace('datagram received', args.datagram)
|
|
414
519
|
// console.log('datagram received', args.datagram, Date.now())
|
|
415
520
|
if (this.incomDatagramController.byobRequest) {
|
|
416
521
|
/** @type {ReadableStreamBYOBRequest} */
|
|
@@ -458,10 +563,12 @@ export class Http3WTSession {
|
|
|
458
563
|
}
|
|
459
564
|
|
|
460
565
|
/**
|
|
461
|
-
* @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | GoawayReceivedEvent | NewStreamEvent} args
|
|
566
|
+
* @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | GoawayReceivedEvent | SessionStatsEvent | DatagramStatsEvent | NewStreamEvent} args
|
|
462
567
|
*/
|
|
463
568
|
static callback(args) {
|
|
464
|
-
|
|
569
|
+
log('callback', args?.purpose)
|
|
570
|
+
log.trace(args)
|
|
571
|
+
|
|
465
572
|
if (!args || !args.object || !args.object.jsobj)
|
|
466
573
|
throw new Error('Session callback without jsobj')
|
|
467
574
|
const visitor = args.object.jsobj
|
|
@@ -483,6 +590,12 @@ export class Http3WTSession {
|
|
|
483
590
|
case 'GoawayReceived':
|
|
484
591
|
visitor.onGoAwayReceived(args)
|
|
485
592
|
break
|
|
593
|
+
case 'SessionStats':
|
|
594
|
+
visitor.onSessionStats(args)
|
|
595
|
+
break
|
|
596
|
+
case 'DatagramStats':
|
|
597
|
+
visitor.onDatagramStats(args)
|
|
598
|
+
break
|
|
486
599
|
case 'Http3WTStreamVisitor':
|
|
487
600
|
if (
|
|
488
601
|
visitor &&
|
package/lib/stream.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ReadableStream, WritableStream } from 'node:stream/web'
|
|
2
|
+
import { logger } from './utils.js'
|
|
3
|
+
import { WebTransportError } from './error.js'
|
|
4
|
+
|
|
5
|
+
const log = logger(`webtransport:http3wtstream(${process.pid})`)
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* WebTransport stream events
|
|
@@ -11,6 +15,9 @@ import { ReadableStream, WritableStream } from 'node:stream/web'
|
|
|
11
15
|
*
|
|
12
16
|
* @typedef {import('./types').NativeHttp3WTStream} NativeHttp3WTStream
|
|
13
17
|
*
|
|
18
|
+
* @typedef {import('./dom').WebTransportReceiveStream} WebTransportReceiveStream
|
|
19
|
+
* @typedef {import('./dom').WebTransportSendStream} WebTransportSendStream
|
|
20
|
+
*
|
|
14
21
|
* @typedef {import('./session').Http3WTSession} Http3WTSession
|
|
15
22
|
*
|
|
16
23
|
* @typedef {import('stream/web').WritableStreamDefaultController} WritableStreamDefaultController
|
|
@@ -38,6 +45,11 @@ export class Http3WTStream {
|
|
|
38
45
|
this.pendingoperation = null
|
|
39
46
|
this.pendingres = null
|
|
40
47
|
|
|
48
|
+
/** @type {WebTransportReceiveStream} */
|
|
49
|
+
this.readable // eslint-disable-line no-unused-expressions
|
|
50
|
+
/** @type {WebTransportSendStream} */
|
|
51
|
+
this.writable // eslint-disable-line no-unused-expressions
|
|
52
|
+
|
|
41
53
|
/** @type {Promise<void> | null} */
|
|
42
54
|
this.pendingoperationRead = null
|
|
43
55
|
this.pendingresRead = null
|
|
@@ -49,15 +61,14 @@ export class Http3WTStream {
|
|
|
49
61
|
this.incomingbufferreadpos = 0
|
|
50
62
|
if (!this.objint.readbuffer)
|
|
51
63
|
throw new Error('No readbuffer for read stream')
|
|
52
|
-
// @ts-expect-error `getStats` property is missing from ReadableStream
|
|
53
64
|
/** @type {WebTransportReceiveStream} */
|
|
65
|
+
// @ts-expect-error `getStats` property is missing from ReadableStream
|
|
54
66
|
this.readable = new ReadableStream(
|
|
55
67
|
{
|
|
56
68
|
start: (
|
|
57
69
|
/** @type {import("stream/web").ReadableByteStreamController} */ controller
|
|
58
70
|
) => {
|
|
59
71
|
this.readableController = controller
|
|
60
|
-
this.parentobj.addReceiveStream(this.readable, controller)
|
|
61
72
|
this.objint.startReading()
|
|
62
73
|
},
|
|
63
74
|
pull: async (
|
|
@@ -74,8 +85,7 @@ export class Http3WTStream {
|
|
|
74
85
|
})
|
|
75
86
|
await this.pendingoperationRead
|
|
76
87
|
}
|
|
77
|
-
if (this.incomingbufferfilled === 0)
|
|
78
|
-
throw new Error('error reading')
|
|
88
|
+
if (this.incomingbufferfilled === 0) return Promise.resolve()
|
|
79
89
|
|
|
80
90
|
this.drainBuffer()
|
|
81
91
|
},
|
|
@@ -103,18 +113,19 @@ export class Http3WTStream {
|
|
|
103
113
|
return Promise.resolve({
|
|
104
114
|
timestamp: 0,
|
|
105
115
|
bytesReceived: 0n,
|
|
106
|
-
|
|
116
|
+
bytesRead: 0n
|
|
107
117
|
})
|
|
108
118
|
}
|
|
119
|
+
// @ts-ignore
|
|
120
|
+
this.parentobj.addReceiveStream(this.readable, this.readableController)
|
|
109
121
|
}
|
|
110
122
|
if (this.bidirectional || !this.incoming) {
|
|
111
|
-
// @ts-expect-error `getStats` property is missing from WritableStream
|
|
112
123
|
/** @type {WebTransportSendStream} */
|
|
124
|
+
// @ts-expect-error `getStats` property is missing from WritableStream
|
|
113
125
|
this.writable = new WritableStream(
|
|
114
126
|
{
|
|
115
127
|
start: (controller) => {
|
|
116
128
|
this.writableController = controller
|
|
117
|
-
this.parentobj.addSendStream(this.writable, controller)
|
|
118
129
|
},
|
|
119
130
|
write: (chunk, controller) => {
|
|
120
131
|
if (this.writableclosed) {
|
|
@@ -124,13 +135,17 @@ export class Http3WTStream {
|
|
|
124
135
|
this.pendingoperation = new Promise((resolve, reject) => {
|
|
125
136
|
this.pendingres = resolve
|
|
126
137
|
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
this.parentobj
|
|
139
|
+
.waitForDatagramsSend()
|
|
140
|
+
.finally(() => {
|
|
141
|
+
this.objint.writeChunk(chunk)
|
|
142
|
+
})
|
|
143
|
+
.catch((/** @type {any} */ err) => {
|
|
144
|
+
log.error(err)
|
|
145
|
+
})
|
|
131
146
|
return this.pendingoperation
|
|
132
147
|
} else {
|
|
133
|
-
|
|
148
|
+
log.trace('chunk info:', chunk)
|
|
134
149
|
throw new Error('chunk is not of instanceof Uint8Array ')
|
|
135
150
|
}
|
|
136
151
|
},
|
|
@@ -174,6 +189,8 @@ export class Http3WTStream {
|
|
|
174
189
|
bytesAcknowledged: 0n
|
|
175
190
|
})
|
|
176
191
|
}
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
this.parentobj.addSendStream(this.writable, this.writableController)
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
/** @type {(() => void) | null} */
|
|
@@ -245,12 +262,13 @@ export class Http3WTStream {
|
|
|
245
262
|
* @returns {void}
|
|
246
263
|
*/
|
|
247
264
|
onStreamRecvSignal(args) {
|
|
248
|
-
|
|
265
|
+
log('callback', args?.purpose, args?.nettask)
|
|
266
|
+
log.trace('onStreamRecvSignal', args)
|
|
249
267
|
// check if transport is closed
|
|
250
268
|
let parentcleanup = true
|
|
251
269
|
const parentstate = this.parentobj.state
|
|
252
270
|
if (parentstate === 'closed' || parentstate === 'failed') {
|
|
253
|
-
|
|
271
|
+
log('no parent cleanup as parent was closed or failed')
|
|
254
272
|
parentcleanup = false
|
|
255
273
|
}
|
|
256
274
|
switch (args.nettask) {
|
|
@@ -262,8 +280,12 @@ export class Http3WTStream {
|
|
|
262
280
|
this.readableController
|
|
263
281
|
)
|
|
264
282
|
this.readableclosed = true
|
|
265
|
-
this.readableController.error(
|
|
266
|
-
|
|
283
|
+
this.readableController.error(
|
|
284
|
+
new WebTransportError('Resetstream with code:' + (args.code || 0))
|
|
285
|
+
)
|
|
286
|
+
} else {
|
|
287
|
+
log.error('resetStream without readable')
|
|
288
|
+
}
|
|
267
289
|
break
|
|
268
290
|
|
|
269
291
|
case 'stopSending':
|
|
@@ -275,11 +297,15 @@ export class Http3WTStream {
|
|
|
275
297
|
)
|
|
276
298
|
|
|
277
299
|
this.writableclosed = true
|
|
278
|
-
this.writableController.error(
|
|
279
|
-
|
|
300
|
+
this.writableController.error(
|
|
301
|
+
new WebTransportError('StopSending with code:' + (args.code || 0))
|
|
302
|
+
)
|
|
303
|
+
} else {
|
|
304
|
+
log.error('stopSending without writable')
|
|
305
|
+
}
|
|
280
306
|
break
|
|
281
307
|
default:
|
|
282
|
-
|
|
308
|
+
log.error('unhandled onStreamRecvSignal')
|
|
283
309
|
}
|
|
284
310
|
|
|
285
311
|
if (this.pendingoperation) {
|
|
@@ -306,6 +332,7 @@ export class Http3WTStream {
|
|
|
306
332
|
*/
|
|
307
333
|
onStreamRead(args) {
|
|
308
334
|
if (args.buffergrow && !this.readableclosed) {
|
|
335
|
+
log.trace('stream read received', args.buffergrow)
|
|
309
336
|
this.incomingbufferfilled += args.buffergrow
|
|
310
337
|
// console.log('stream read received', args.data, Date.now())
|
|
311
338
|
if (this.pendingoperationRead) {
|
|
@@ -323,7 +350,7 @@ export class Http3WTStream {
|
|
|
323
350
|
}
|
|
324
351
|
if (args.fin) {
|
|
325
352
|
if (this.incomingbufferfilled > 0) {
|
|
326
|
-
|
|
353
|
+
log.trace('Warning buffer filled and we got a fin')
|
|
327
354
|
if (this.pendingoperationRead || this.pendingresRead)
|
|
328
355
|
throw new Error('We have pendingoperationRead and a filled buffer?')
|
|
329
356
|
|
|
@@ -378,7 +405,8 @@ export class Http3WTStream {
|
|
|
378
405
|
* @param {StreamNetworkFinishEvent} args
|
|
379
406
|
*/
|
|
380
407
|
onStreamNetworkFinish(args) {
|
|
381
|
-
|
|
408
|
+
log('callback', args?.purpose)
|
|
409
|
+
log.trace('networkfinish args', args)
|
|
382
410
|
switch (args.nettask) {
|
|
383
411
|
case 'stopSending':
|
|
384
412
|
if (this.cancelres) {
|
|
@@ -408,7 +436,7 @@ export class Http3WTStream {
|
|
|
408
436
|
}
|
|
409
437
|
break
|
|
410
438
|
default:
|
|
411
|
-
|
|
439
|
+
log.error('onStreamNetworkFinish unknown task', args.nettask)
|
|
412
440
|
}
|
|
413
441
|
// we could differentiate....
|
|
414
442
|
}
|
|
@@ -417,7 +445,8 @@ export class Http3WTStream {
|
|
|
417
445
|
* @param {StreamRecvSignalEvent | StreamReadEvent | StreamWriteEvent | StreamResetEvent | StreamNetworkFinishEvent} args
|
|
418
446
|
*/
|
|
419
447
|
static callback(args) {
|
|
420
|
-
|
|
448
|
+
log('callback', args?.purpose)
|
|
449
|
+
log.trace(log)
|
|
421
450
|
if (!args || !args.object || !args.object.jsobj)
|
|
422
451
|
throw new Error('Stream callback without jsobj')
|
|
423
452
|
const visitor = args.object.jsobj
|
|
@@ -433,7 +462,6 @@ export class Http3WTStream {
|
|
|
433
462
|
) {
|
|
434
463
|
visitor.onStreamRead(args)
|
|
435
464
|
} else {
|
|
436
|
-
console.log('Stream callback called', visitor, args)
|
|
437
465
|
throw new Error('Malformed StreamRead')
|
|
438
466
|
}
|
|
439
467
|
break
|
package/lib/transport.js
CHANGED
|
@@ -1,24 +1,61 @@
|
|
|
1
1
|
import { Http3EventLoop } from './event-loop.js'
|
|
2
2
|
import { wtrouter } from './native.js'
|
|
3
|
+
import { WebTransportError } from './error.js'
|
|
4
|
+
import { logger } from './utils.js'
|
|
5
|
+
|
|
6
|
+
const log = logger(`webtransport:http3webtransport(${process.pid})`)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('./types').Http3WebTransportInit} Http3WebTransportInit
|
|
10
|
+
*/
|
|
3
11
|
|
|
4
12
|
export class Http3WebTransport {
|
|
5
13
|
/**
|
|
6
|
-
* @param {
|
|
14
|
+
* @param {Http3WebTransportInit} args
|
|
7
15
|
* @param {'server' | 'client'} purpose
|
|
8
16
|
*/
|
|
9
17
|
constructor(args, purpose) {
|
|
18
|
+
/** @type {Http3WebTransportInit | undefined} */
|
|
19
|
+
this.args = args
|
|
20
|
+
/** @type {'server' | 'client' | undefined} */
|
|
21
|
+
this.purpose = purpose
|
|
22
|
+
this.sessions = {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
createTransportInt() {
|
|
26
|
+
if (this.transportInt != null) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
10
29
|
const eventloop = Http3EventLoop.getGlobalEventLoop(this, {
|
|
11
|
-
quicheLogVerbose: args
|
|
30
|
+
quicheLogVerbose: this.args?.quicheLogVerbose
|
|
31
|
+
? this.args.quicheLogVerbose
|
|
32
|
+
: -1
|
|
12
33
|
}).eventloopInt
|
|
13
34
|
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
try {
|
|
36
|
+
if (this.purpose === 'server') {
|
|
37
|
+
this.transportInt = new wtrouter.Http3WebTransportServer(
|
|
38
|
+
this.args,
|
|
39
|
+
eventloop
|
|
40
|
+
)
|
|
41
|
+
} else if (this.purpose === 'client') {
|
|
42
|
+
this.transportInt = new wtrouter.Http3WebTransportClient(
|
|
43
|
+
this.args,
|
|
44
|
+
eventloop
|
|
45
|
+
)
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error('unknown purpose')
|
|
48
|
+
}
|
|
49
|
+
} catch (/** @type {any} */ err) {
|
|
50
|
+
const error = new WebTransportError('Opening handshake failed.')
|
|
51
|
+
error.stack = err.stack
|
|
20
52
|
|
|
21
|
-
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
delete this.purpose
|
|
57
|
+
delete this.args
|
|
58
|
+
this.transportInt.jsobj = this
|
|
22
59
|
}
|
|
23
60
|
|
|
24
61
|
/**
|
|
@@ -29,7 +66,8 @@ export class Http3WebTransport {
|
|
|
29
66
|
* @param {TransportCallbackEvent} args
|
|
30
67
|
*/
|
|
31
68
|
static transportCallback(args) {
|
|
32
|
-
|
|
69
|
+
log('callback', args?.purpose)
|
|
70
|
+
log.trace(args)
|
|
33
71
|
if (!args || !args.object || !args.object.jsobj)
|
|
34
72
|
throw new Error('Transport callback without jsobj')
|
|
35
73
|
const visitor = args.object.jsobj
|
package/lib/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WebTransport } from './dom'
|
|
1
|
+
import type { WebTransport, WebTransportOptions } from './dom'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Native Http3WTSession counterpart
|
|
@@ -8,7 +8,8 @@ import type { WebTransport } from './dom'
|
|
|
8
8
|
writeDatagram: (chunk: Uint8Array) => void
|
|
9
9
|
orderUnidiStream: () => void
|
|
10
10
|
orderBidiStream: () => void
|
|
11
|
-
|
|
11
|
+
orderSessionStats: () => void
|
|
12
|
+
orderDatagramStats: () => void
|
|
12
13
|
notifySessionDraining(): () => void
|
|
13
14
|
close: (arg: { code: number, reason: string }) => void
|
|
14
15
|
}
|
|
@@ -82,6 +83,28 @@ export interface SessionCloseEvent {
|
|
|
82
83
|
error: string
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
export interface SessionStatsEvent {
|
|
87
|
+
object: NativeHttp3WTSession
|
|
88
|
+
purpose: 'SessionStats'
|
|
89
|
+
timestamp: number
|
|
90
|
+
expiredOutgoing: bigint
|
|
91
|
+
lostOutgoing: bigint
|
|
92
|
+
|
|
93
|
+
// non Datagram
|
|
94
|
+
minRtt: number
|
|
95
|
+
smoothedRtt: number
|
|
96
|
+
rttVariation: number
|
|
97
|
+
estimatedSendRateBps: bigint
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface DatagramStatsEvent {
|
|
101
|
+
object: NativeHttp3WTSession
|
|
102
|
+
purpose: 'DatagramStats'
|
|
103
|
+
timestamp: number
|
|
104
|
+
expiredOutgoing: bigint
|
|
105
|
+
lostOutgoing: bigint
|
|
106
|
+
}
|
|
107
|
+
|
|
85
108
|
export interface DatagramReceivedEvent {
|
|
86
109
|
object: NativeHttp3WTSession
|
|
87
110
|
purpose: 'DatagramReceived'
|
|
@@ -113,6 +136,8 @@ export interface WebTransportSessionEventHandler {
|
|
|
113
136
|
onDatagramReceived: (evt: DatagramReceivedEvent) => void
|
|
114
137
|
onDatagramSend: (evt: DatagramSendEvent) => void
|
|
115
138
|
onGoAwayReceived: (evt: GoawayReceivedEvent) => void
|
|
139
|
+
onSessionStats: (evt: SessionStatsEvent) => void
|
|
140
|
+
onDatagramStats: (evt: DatagramStatsEvent) => void
|
|
116
141
|
onStream: (evt: NewStreamEvent) => void
|
|
117
142
|
closeHook?: (() => void) | null
|
|
118
143
|
}
|
|
@@ -185,16 +210,39 @@ export interface Deferred<T = unknown> {
|
|
|
185
210
|
reject: (reason?: any) => void
|
|
186
211
|
}
|
|
187
212
|
|
|
188
|
-
|
|
213
|
+
// https://www.w3.org/TR/webtransport/#dom-webtransport-state-slot
|
|
214
|
+
export type WebTransportSessionState = 'connecting' | 'connected' | 'draining' | 'closed' | 'failed'
|
|
189
215
|
|
|
190
216
|
export interface WebTransportSession extends WebTransport {
|
|
191
217
|
state: WebTransportSessionState
|
|
192
218
|
}
|
|
193
219
|
|
|
194
|
-
export interface
|
|
195
|
-
|
|
220
|
+
export interface Http3WebTransportInit extends WebTransportOptions {
|
|
221
|
+
host: string
|
|
222
|
+
port: string | number
|
|
223
|
+
quicheLogVerbose?: number
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// see Http3ServerJS C++ type
|
|
227
|
+
export interface Http3ServerInit extends Http3WebTransportInit {
|
|
228
|
+
port: string | number
|
|
196
229
|
host: string
|
|
197
230
|
secret: string
|
|
198
231
|
cert: string
|
|
199
232
|
privKey: string
|
|
233
|
+
maxConnections?: number
|
|
234
|
+
initialStreamFlowControlWindow?: number
|
|
235
|
+
initialSessionFlowControlWindow?: number
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// see Http3ClientJS C++ type
|
|
239
|
+
export interface Http3ClientInit extends Http3WebTransportInit {
|
|
240
|
+
forceIpv6?: boolean
|
|
241
|
+
localPort?: number
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface Logger {
|
|
245
|
+
(formatter: any, ...args: any[]): void
|
|
246
|
+
error: (formatter: any, ...args: any[]) => void
|
|
247
|
+
trace: (formatter: any, ...args: any[]) => void
|
|
200
248
|
}
|