@fails-components/webtransport 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +51 -22
- 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 +46 -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 (
|
|
@@ -102,18 +113,19 @@ export class Http3WTStream {
|
|
|
102
113
|
return Promise.resolve({
|
|
103
114
|
timestamp: 0,
|
|
104
115
|
bytesReceived: 0n,
|
|
105
|
-
|
|
116
|
+
bytesRead: 0n
|
|
106
117
|
})
|
|
107
118
|
}
|
|
119
|
+
// @ts-ignore
|
|
120
|
+
this.parentobj.addReceiveStream(this.readable, this.readableController)
|
|
108
121
|
}
|
|
109
122
|
if (this.bidirectional || !this.incoming) {
|
|
110
|
-
// @ts-expect-error `getStats` property is missing from WritableStream
|
|
111
123
|
/** @type {WebTransportSendStream} */
|
|
124
|
+
// @ts-expect-error `getStats` property is missing from WritableStream
|
|
112
125
|
this.writable = new WritableStream(
|
|
113
126
|
{
|
|
114
127
|
start: (controller) => {
|
|
115
128
|
this.writableController = controller
|
|
116
|
-
this.parentobj.addSendStream(this.writable, controller)
|
|
117
129
|
},
|
|
118
130
|
write: (chunk, controller) => {
|
|
119
131
|
if (this.writableclosed) {
|
|
@@ -123,13 +135,17 @@ export class Http3WTStream {
|
|
|
123
135
|
this.pendingoperation = new Promise((resolve, reject) => {
|
|
124
136
|
this.pendingres = resolve
|
|
125
137
|
})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
138
|
+
this.parentobj
|
|
139
|
+
.waitForDatagramsSend()
|
|
140
|
+
.finally(() => {
|
|
141
|
+
this.objint.writeChunk(chunk)
|
|
142
|
+
})
|
|
143
|
+
.catch((/** @type {any} */ err) => {
|
|
144
|
+
log.error(err)
|
|
145
|
+
})
|
|
130
146
|
return this.pendingoperation
|
|
131
147
|
} else {
|
|
132
|
-
|
|
148
|
+
log.trace('chunk info:', chunk)
|
|
133
149
|
throw new Error('chunk is not of instanceof Uint8Array ')
|
|
134
150
|
}
|
|
135
151
|
},
|
|
@@ -173,6 +189,8 @@ export class Http3WTStream {
|
|
|
173
189
|
bytesAcknowledged: 0n
|
|
174
190
|
})
|
|
175
191
|
}
|
|
192
|
+
// @ts-ignore
|
|
193
|
+
this.parentobj.addSendStream(this.writable, this.writableController)
|
|
176
194
|
}
|
|
177
195
|
|
|
178
196
|
/** @type {(() => void) | null} */
|
|
@@ -244,12 +262,13 @@ export class Http3WTStream {
|
|
|
244
262
|
* @returns {void}
|
|
245
263
|
*/
|
|
246
264
|
onStreamRecvSignal(args) {
|
|
247
|
-
|
|
265
|
+
log('callback', args?.purpose, args?.nettask)
|
|
266
|
+
log.trace('onStreamRecvSignal', args)
|
|
248
267
|
// check if transport is closed
|
|
249
268
|
let parentcleanup = true
|
|
250
269
|
const parentstate = this.parentobj.state
|
|
251
270
|
if (parentstate === 'closed' || parentstate === 'failed') {
|
|
252
|
-
|
|
271
|
+
log('no parent cleanup as parent was closed or failed')
|
|
253
272
|
parentcleanup = false
|
|
254
273
|
}
|
|
255
274
|
switch (args.nettask) {
|
|
@@ -261,8 +280,12 @@ export class Http3WTStream {
|
|
|
261
280
|
this.readableController
|
|
262
281
|
)
|
|
263
282
|
this.readableclosed = true
|
|
264
|
-
this.readableController.error(
|
|
265
|
-
|
|
283
|
+
this.readableController.error(
|
|
284
|
+
new WebTransportError('Resetstream with code:' + (args.code || 0))
|
|
285
|
+
)
|
|
286
|
+
} else {
|
|
287
|
+
log.error('resetStream without readable')
|
|
288
|
+
}
|
|
266
289
|
break
|
|
267
290
|
|
|
268
291
|
case 'stopSending':
|
|
@@ -274,11 +297,15 @@ export class Http3WTStream {
|
|
|
274
297
|
)
|
|
275
298
|
|
|
276
299
|
this.writableclosed = true
|
|
277
|
-
this.writableController.error(
|
|
278
|
-
|
|
300
|
+
this.writableController.error(
|
|
301
|
+
new WebTransportError('StopSending with code:' + (args.code || 0))
|
|
302
|
+
)
|
|
303
|
+
} else {
|
|
304
|
+
log.error('stopSending without writable')
|
|
305
|
+
}
|
|
279
306
|
break
|
|
280
307
|
default:
|
|
281
|
-
|
|
308
|
+
log.error('unhandled onStreamRecvSignal')
|
|
282
309
|
}
|
|
283
310
|
|
|
284
311
|
if (this.pendingoperation) {
|
|
@@ -305,6 +332,7 @@ export class Http3WTStream {
|
|
|
305
332
|
*/
|
|
306
333
|
onStreamRead(args) {
|
|
307
334
|
if (args.buffergrow && !this.readableclosed) {
|
|
335
|
+
log.trace('stream read received', args.buffergrow)
|
|
308
336
|
this.incomingbufferfilled += args.buffergrow
|
|
309
337
|
// console.log('stream read received', args.data, Date.now())
|
|
310
338
|
if (this.pendingoperationRead) {
|
|
@@ -322,7 +350,7 @@ export class Http3WTStream {
|
|
|
322
350
|
}
|
|
323
351
|
if (args.fin) {
|
|
324
352
|
if (this.incomingbufferfilled > 0) {
|
|
325
|
-
|
|
353
|
+
log.trace('Warning buffer filled and we got a fin')
|
|
326
354
|
if (this.pendingoperationRead || this.pendingresRead)
|
|
327
355
|
throw new Error('We have pendingoperationRead and a filled buffer?')
|
|
328
356
|
|
|
@@ -377,7 +405,8 @@ export class Http3WTStream {
|
|
|
377
405
|
* @param {StreamNetworkFinishEvent} args
|
|
378
406
|
*/
|
|
379
407
|
onStreamNetworkFinish(args) {
|
|
380
|
-
|
|
408
|
+
log('callback', args?.purpose)
|
|
409
|
+
log.trace('networkfinish args', args)
|
|
381
410
|
switch (args.nettask) {
|
|
382
411
|
case 'stopSending':
|
|
383
412
|
if (this.cancelres) {
|
|
@@ -407,7 +436,7 @@ export class Http3WTStream {
|
|
|
407
436
|
}
|
|
408
437
|
break
|
|
409
438
|
default:
|
|
410
|
-
|
|
439
|
+
log.error('onStreamNetworkFinish unknown task', args.nettask)
|
|
411
440
|
}
|
|
412
441
|
// we could differentiate....
|
|
413
442
|
}
|
|
@@ -416,7 +445,8 @@ export class Http3WTStream {
|
|
|
416
445
|
* @param {StreamRecvSignalEvent | StreamReadEvent | StreamWriteEvent | StreamResetEvent | StreamNetworkFinishEvent} args
|
|
417
446
|
*/
|
|
418
447
|
static callback(args) {
|
|
419
|
-
|
|
448
|
+
log('callback', args?.purpose)
|
|
449
|
+
log.trace(log)
|
|
420
450
|
if (!args || !args.object || !args.object.jsobj)
|
|
421
451
|
throw new Error('Stream callback without jsobj')
|
|
422
452
|
const visitor = args.object.jsobj
|
|
@@ -432,7 +462,6 @@ export class Http3WTStream {
|
|
|
432
462
|
) {
|
|
433
463
|
visitor.onStreamRead(args)
|
|
434
464
|
} else {
|
|
435
|
-
console.log('Stream callback called', visitor, args)
|
|
436
465
|
throw new Error('Malformed StreamRead')
|
|
437
466
|
}
|
|
438
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
|
}
|