@fails-components/webtransport 1.0.0-rc.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/test/testinone.js +0 -2854
package/test/testinone.js
DELETED
|
@@ -1,2854 +0,0 @@
|
|
|
1
|
-
const SERVER_URL = 'https://127.0.0.1:8080'
|
|
2
|
-
const CERT_HASH =
|
|
3
|
-
'CA:02:33:76:6C:0D:9B:2F:3C:49:82:CF:DE:7F:ED:CE:39:2B:48:9A:FE:67:8D:DD:2B:09:AF:33:E8:99:FE:EB'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* WebTransport stream events
|
|
7
|
-
* @typedef {import('../types').WebTransportStreamEventHandler} WebTransportStreamEventHandler
|
|
8
|
-
* @typedef {import('../types').StreamRecvSignalEvent} StreamRecvSignalEvent
|
|
9
|
-
* @typedef {import('../types').StreamReadEvent} StreamReadEvent
|
|
10
|
-
* @typedef {import('../types').StreamWriteEvent} StreamWriteEvent
|
|
11
|
-
* @typedef {import('../types').StreamNetworkFinishEvent} StreamNetworkFinishEvent
|
|
12
|
-
*
|
|
13
|
-
* @typedef {import('../types').ReadDataInt} ReadDataInt
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
let processnextTick = (func) => setTimeout(func, 0)
|
|
17
|
-
if (typeof process !== 'undefined') processnextTick = process.nextTick
|
|
18
|
-
|
|
19
|
-
export class Http2WebTransportStream {
|
|
20
|
-
/**
|
|
21
|
-
* @param {{streamid: Number, capsuleParser: ParserBase}} args
|
|
22
|
-
* */
|
|
23
|
-
constructor({ streamid, capsuleParser }) {
|
|
24
|
-
/** @type {import('../stream').HttpWTStream} */
|
|
25
|
-
// @ts-ignore
|
|
26
|
-
this.jsobj = undefined // the creator will set this
|
|
27
|
-
this.readbuffer = new ArrayBuffer(64 * 1024)
|
|
28
|
-
this.readpos_ = 0
|
|
29
|
-
this.writepos_ = 0
|
|
30
|
-
this.bufferlen_ = 0
|
|
31
|
-
this.readbufsize_ = this.readbuffer.byteLength
|
|
32
|
-
this.streamid = streamid
|
|
33
|
-
/** @type {Array<ReadDataInt>} */
|
|
34
|
-
this.incomdata = []
|
|
35
|
-
|
|
36
|
-
this.capsuleParser = capsuleParser
|
|
37
|
-
/** @type {Array<Uint8Array>} */
|
|
38
|
-
this.outgochunks = []
|
|
39
|
-
|
|
40
|
-
this.final = false
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @param {Object} obj
|
|
45
|
-
* @param {Uint8Array} obj.data
|
|
46
|
-
* @param {Boolean} obj.fin
|
|
47
|
-
*/
|
|
48
|
-
recvData({ data, fin }) {
|
|
49
|
-
this.incomdata.push({ data, fin })
|
|
50
|
-
this.processRead()
|
|
51
|
-
if (this.incomdata.length > 0) {
|
|
52
|
-
// TODO tell the peer to stop sending by sending a capsule
|
|
53
|
-
// TODO SEND WT_STREAM_DATA_BLOCKED:
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
processRead() {
|
|
58
|
-
let bytesRead = 0
|
|
59
|
-
let fin = false
|
|
60
|
-
|
|
61
|
-
while (this.incomdata.length > 0 && this.bufferlen_ < this.readbufsize_) {
|
|
62
|
-
const cur = this.incomdata.shift()
|
|
63
|
-
if (cur.data) {
|
|
64
|
-
let len
|
|
65
|
-
|
|
66
|
-
if (this.writepos_ >= this.readpos_) {
|
|
67
|
-
len = Math.min(
|
|
68
|
-
this.readbufsize_ - this.writepos_,
|
|
69
|
-
cur.data.byteLength
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const destview = new Uint8Array(
|
|
73
|
-
this.readbuffer,
|
|
74
|
-
0 + this.writepos_,
|
|
75
|
-
len
|
|
76
|
-
)
|
|
77
|
-
const srcview = new Uint8Array(
|
|
78
|
-
cur.data.buffer,
|
|
79
|
-
cur.data.byteOffset,
|
|
80
|
-
len
|
|
81
|
-
)
|
|
82
|
-
destview.set(srcview)
|
|
83
|
-
|
|
84
|
-
this.writepos_ = (this.writepos_ + len) % this.readbufsize_
|
|
85
|
-
this.bufferlen_ = this.bufferlen_ + len
|
|
86
|
-
bytesRead += len
|
|
87
|
-
} else {
|
|
88
|
-
// readpos_ > writepos_
|
|
89
|
-
len = Math.min(this.readpos_ - this.writepos_, cur.data.byteLength)
|
|
90
|
-
const destview = new Uint8Array(
|
|
91
|
-
this.readbuffer,
|
|
92
|
-
0 + this.writepos_,
|
|
93
|
-
len
|
|
94
|
-
)
|
|
95
|
-
const srcview = new Uint8Array(
|
|
96
|
-
cur.data.buffer,
|
|
97
|
-
cur.data.byteOffset,
|
|
98
|
-
len
|
|
99
|
-
)
|
|
100
|
-
destview.set(srcview)
|
|
101
|
-
|
|
102
|
-
this.writepos_ = (this.writepos_ + len) % this.readbufsize_
|
|
103
|
-
this.bufferlen_ = this.bufferlen_ + len
|
|
104
|
-
bytesRead += len
|
|
105
|
-
}
|
|
106
|
-
if (cur.data.byteLength !== len) {
|
|
107
|
-
this.incomdata.unshift({
|
|
108
|
-
data: new Uint8Array(
|
|
109
|
-
cur.data.buffer,
|
|
110
|
-
cur.data.byteOffset + len,
|
|
111
|
-
cur.data.byteLength - len
|
|
112
|
-
),
|
|
113
|
-
fin: cur.fin
|
|
114
|
-
})
|
|
115
|
-
fin = false // next round
|
|
116
|
-
} else {
|
|
117
|
-
fin = fin || cur.fin
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
fin = fin || cur.fin
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (bytesRead > 0 || fin) {
|
|
125
|
-
this.jsobj.onStreamRead({
|
|
126
|
-
buffergrow: bytesRead,
|
|
127
|
-
fin,
|
|
128
|
-
success: true
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* @param {Number} bytesread
|
|
135
|
-
* @param {Number} pos
|
|
136
|
-
*/
|
|
137
|
-
updateReadPos(bytesread, pos) {
|
|
138
|
-
this.readpos_ = pos
|
|
139
|
-
this.bufferlen_ -= bytesread
|
|
140
|
-
// well a good time to try to read again
|
|
141
|
-
this.processRead()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
startReading() {}
|
|
145
|
-
stopReading() {}
|
|
146
|
-
/**
|
|
147
|
-
* @param {Number} code
|
|
148
|
-
*/
|
|
149
|
-
stopSending(code) {
|
|
150
|
-
this.capsuleParser.writeCapsule({
|
|
151
|
-
type: ParserBase.WT_STOP_SENDING,
|
|
152
|
-
headerVints: [this.streamid, code],
|
|
153
|
-
payload: undefined
|
|
154
|
-
})
|
|
155
|
-
processnextTick(() =>
|
|
156
|
-
this.jsobj.onStreamNetworkFinish({
|
|
157
|
-
nettask: 'stopSending'
|
|
158
|
-
})
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* @param {Number} code
|
|
164
|
-
*/
|
|
165
|
-
resetStream(code) {
|
|
166
|
-
this.capsuleParser.writeCapsule({
|
|
167
|
-
type: ParserBase.WT_RESET_STREAM,
|
|
168
|
-
headerVints: [this.streamid, code],
|
|
169
|
-
payload: undefined
|
|
170
|
-
})
|
|
171
|
-
processnextTick(() =>
|
|
172
|
-
this.jsobj.onStreamNetworkFinish({
|
|
173
|
-
nettask: 'resetStream'
|
|
174
|
-
})
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* @param {Uint8Array} buf
|
|
180
|
-
*/
|
|
181
|
-
writeChunk(buf) {
|
|
182
|
-
this.outgochunks.push({ buf, fin: false })
|
|
183
|
-
this.drainWrites()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
drainWrites() {
|
|
187
|
-
while (
|
|
188
|
-
this.outgochunks.length > 0 &&
|
|
189
|
-
(!this.capsuleParser.blocked || this.final)
|
|
190
|
-
) {
|
|
191
|
-
const cur = this.outgochunks.shift()
|
|
192
|
-
const payload = cur.buf
|
|
193
|
-
this.capsuleParser.writeCapsule({
|
|
194
|
-
type: cur?.fin ? ParserBase.WT_STREAM_WFIN : ParserBase.WT_STREAM_WOFIN,
|
|
195
|
-
headerVints: [this.streamid],
|
|
196
|
-
payload
|
|
197
|
-
})
|
|
198
|
-
this.jsobj.onStreamWrite({
|
|
199
|
-
success: true
|
|
200
|
-
})
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
streamFinal() {
|
|
205
|
-
this.final = true
|
|
206
|
-
this.outgochunks.push({ fin: true })
|
|
207
|
-
this.drainWrites()
|
|
208
|
-
processnextTick(() =>
|
|
209
|
-
this.jsobj.onStreamNetworkFinish({
|
|
210
|
-
nettask: 'streamFinal'
|
|
211
|
-
})
|
|
212
|
-
)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* @param{Number} int
|
|
218
|
-
* @returns {Number}
|
|
219
|
-
*/
|
|
220
|
-
export function lengthVarInt(int) {
|
|
221
|
-
if (int < 64) return 1
|
|
222
|
-
if (int < 16384) return 2
|
|
223
|
-
if (int < 1073741824) return 4
|
|
224
|
-
/* if (int < 4611686018427387904 ) */
|
|
225
|
-
return 8
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export class ParserBase {
|
|
229
|
-
static PADDING = 0x190b4d38
|
|
230
|
-
static WT_RESET_STREAM = 0x190b4d39
|
|
231
|
-
static WT_STOP_SENDING = 0x190b4d3a
|
|
232
|
-
static WT_STREAM_WOFIN = 0x190b4d3b
|
|
233
|
-
static WT_STREAM_WFIN = 0x190b4d3c
|
|
234
|
-
static WT_MAX_DATA = 0x190b4d3d
|
|
235
|
-
static WT_MAX_STREAM_DATA = 0x190b4d3e
|
|
236
|
-
static WT_MAX_STREAMS_BIDI = 0x190b4d3f
|
|
237
|
-
static WT_MAX_STREAMS_UNIDI = 0x190b4d40
|
|
238
|
-
static WT_DATA_BLOCKED = 0x190b4d41
|
|
239
|
-
static WT_STREAM_DATA_BLOCKED = 0x190b4d42
|
|
240
|
-
static WT_STREAMS_BLOCKED_UNIDI = 0x190b4d43
|
|
241
|
-
static WT_STREAMS_BLOCKED_BIDI = 0x190b4d44
|
|
242
|
-
static DATAGRAM = 0x00
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* @param {import('../types').ParserInit} arg
|
|
246
|
-
*/
|
|
247
|
-
constructor({ nativesession, isclient }) {
|
|
248
|
-
this.session = nativesession
|
|
249
|
-
this.isclient = isclient
|
|
250
|
-
/** @type {boolean} */
|
|
251
|
-
this.blocked = false
|
|
252
|
-
|
|
253
|
-
this.wtstreams = new Map()
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* @abstract
|
|
258
|
-
* @param {Buffer|Uint8Array} data
|
|
259
|
-
*/
|
|
260
|
-
parseData(data) {
|
|
261
|
-
throw new Error('Implement parseData in derived Class')
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* @abstract
|
|
266
|
-
* @param{{type: Number, headerVints: Array<Number>, payload: Uint8Array|undefined}} bs
|
|
267
|
-
*/
|
|
268
|
-
writeCapsule({ type, headerVints, payload }) {
|
|
269
|
-
throw new Error('Implement writeCapsule in derived Class')
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* @param{{code: Number, reason: string}}arg
|
|
274
|
-
*/
|
|
275
|
-
sendClose({ code, reason }) {}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* @param {Number} streamid
|
|
279
|
-
*/
|
|
280
|
-
newStream(streamid) {
|
|
281
|
-
const stream = new Http2WebTransportStream({
|
|
282
|
-
streamid,
|
|
283
|
-
capsuleParser: this
|
|
284
|
-
})
|
|
285
|
-
this.wtstreams.set(streamid, stream)
|
|
286
|
-
this.session.jsobj.onStream({
|
|
287
|
-
bidirectional: !(streamid & 0x2),
|
|
288
|
-
incoming: this.isclient ? !(streamid & 0x1) : !!(streamid & 0x1),
|
|
289
|
-
stream
|
|
290
|
-
})
|
|
291
|
-
return stream
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
drainWrites() {
|
|
295
|
-
for (const stream of this.wtstreams.values()) {
|
|
296
|
-
stream.drainWrites()
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* @param{{offset: Number, buffer: Uint8Array, size: Number}} bs
|
|
303
|
-
*/
|
|
304
|
-
function readVarInt(bs) {
|
|
305
|
-
let val = bs.buffer[bs.offset]
|
|
306
|
-
bs.offset++
|
|
307
|
-
const prefix = val >>> 6
|
|
308
|
-
const intlength = 1 << prefix
|
|
309
|
-
|
|
310
|
-
if (bs.offset + intlength - 1 > bs.size) {
|
|
311
|
-
return undefined
|
|
312
|
-
}
|
|
313
|
-
val = val & 0x3f
|
|
314
|
-
for (let i = 0; i < intlength - 1; i++) {
|
|
315
|
-
val = (val << 8) | bs.buffer[bs.offset]
|
|
316
|
-
bs.offset++
|
|
317
|
-
}
|
|
318
|
-
return val
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* @param{{offset: Number, buffer: Uint8Array, size: Number}} bs
|
|
323
|
-
* @param{Number} int
|
|
324
|
-
*/
|
|
325
|
-
export function writeVarInt(bs, int) {
|
|
326
|
-
let numbytes = 8
|
|
327
|
-
let msb = 0xc0
|
|
328
|
-
if (int < 64) {
|
|
329
|
-
numbytes = 1
|
|
330
|
-
msb = 0x0
|
|
331
|
-
} else if (int < 16384) {
|
|
332
|
-
numbytes = 2
|
|
333
|
-
msb = 0x40
|
|
334
|
-
} else if (int < 1073741824) {
|
|
335
|
-
numbytes = 4
|
|
336
|
-
msb = 0x80
|
|
337
|
-
}
|
|
338
|
-
bs.buffer[bs.offset] = msb | ((int >>> ((numbytes - 1) * 8)) & 0xff)
|
|
339
|
-
bs.offset++
|
|
340
|
-
|
|
341
|
-
for (let i = numbytes - 2; i >= 0; i--) {
|
|
342
|
-
bs.buffer[bs.offset] = (int >>> (i * 8)) & 0xff
|
|
343
|
-
bs.offset++
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export class BrowserParser extends ParserBase {
|
|
348
|
-
static WS_CONTINUE = 0x0
|
|
349
|
-
static WS_TEXT = 0x1
|
|
350
|
-
static WS_BINARY = 0x2
|
|
351
|
-
static WS_CLOSE = 0x8
|
|
352
|
-
static WS_PING = 0x9
|
|
353
|
-
static WS_PONG = 0xa
|
|
354
|
-
/**
|
|
355
|
-
* @param {import('../../types.js').ParserWebsocketInit} stream
|
|
356
|
-
*/
|
|
357
|
-
constructor({ ws, nativesession, isclient }) {
|
|
358
|
-
super({ nativesession, isclient })
|
|
359
|
-
this.ws = ws
|
|
360
|
-
/** @type {Buffer|undefined} */
|
|
361
|
-
this.saveddata = undefined
|
|
362
|
-
/** @type {Number|undefined} */
|
|
363
|
-
this.rtype = undefined
|
|
364
|
-
|
|
365
|
-
this.closesend = false
|
|
366
|
-
|
|
367
|
-
this.ws.addEventListener('message', (event) => {
|
|
368
|
-
if (event.data instanceof ArrayBuffer) {
|
|
369
|
-
// binary frame
|
|
370
|
-
this.parseData(new Uint8Array(event.data, 0, event.data.byteLength))
|
|
371
|
-
} else {
|
|
372
|
-
// text frame
|
|
373
|
-
console.log('Illegal text frame', event.data)
|
|
374
|
-
}
|
|
375
|
-
})
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* @param {Uint8Array} data
|
|
380
|
-
*/
|
|
381
|
-
parseData(data) {
|
|
382
|
-
const bufferstate = { offset: 0, size: data.byteLength, buffer: data }
|
|
383
|
-
|
|
384
|
-
const offsetend = bufferstate.size
|
|
385
|
-
|
|
386
|
-
const type = readVarInt(bufferstate)
|
|
387
|
-
|
|
388
|
-
// all safeguards passed now apply the mask
|
|
389
|
-
|
|
390
|
-
switch (type) {
|
|
391
|
-
case ParserBase.PADDING:
|
|
392
|
-
// only padding do nothing
|
|
393
|
-
break
|
|
394
|
-
case ParserBase.WT_RESET_STREAM:
|
|
395
|
-
case ParserBase.WT_STOP_SENDING:
|
|
396
|
-
{
|
|
397
|
-
const streamid = readVarInt(bufferstate)
|
|
398
|
-
const stream = this.wtstreams.get(streamid)
|
|
399
|
-
const code = readVarInt(bufferstate)
|
|
400
|
-
if (stream && typeof code !== 'undefined')
|
|
401
|
-
stream.jsobj.onStreamRecvSignal({
|
|
402
|
-
code,
|
|
403
|
-
nettask:
|
|
404
|
-
type === ParserBase.WT_RESET_STREAM
|
|
405
|
-
? 'resetStream'
|
|
406
|
-
: 'stopSending'
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
break
|
|
410
|
-
case ParserBase.WT_STREAM_WOFIN:
|
|
411
|
-
case ParserBase.WT_STREAM_WFIN:
|
|
412
|
-
{
|
|
413
|
-
const streamid = readVarInt(bufferstate)
|
|
414
|
-
|
|
415
|
-
if (typeof streamid !== 'undefined') {
|
|
416
|
-
let object = this.wtstreams.get(streamid)
|
|
417
|
-
if (!object) {
|
|
418
|
-
object = this.newStream(streamid)
|
|
419
|
-
}
|
|
420
|
-
// TODO submit data
|
|
421
|
-
if (offsetend - bufferstate.offset >= 0) {
|
|
422
|
-
object.recvData({
|
|
423
|
-
data: new Uint8Array(
|
|
424
|
-
bufferstate.buffer.buffer,
|
|
425
|
-
bufferstate.buffer.byteOffset + bufferstate.offset,
|
|
426
|
-
offsetend - bufferstate.offset
|
|
427
|
-
),
|
|
428
|
-
fin: type === ParserBase.WT_STREAM_WFIN
|
|
429
|
-
})
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
break
|
|
434
|
-
case ParserBase.WT_MAX_DATA:
|
|
435
|
-
// this.recvSession({ maxdata: readVarInt(bufferstate), type })
|
|
436
|
-
break
|
|
437
|
-
case ParserBase.WT_MAX_STREAM_DATA:
|
|
438
|
-
/* {
|
|
439
|
-
const streamid = readVarInt(bufferstate)
|
|
440
|
-
const object = this.wtstreams.get(streamid)
|
|
441
|
-
if (object)
|
|
442
|
-
this.recvStream({
|
|
443
|
-
maxstreamdata: readVarInt(bufferstate),
|
|
444
|
-
type,
|
|
445
|
-
object
|
|
446
|
-
})
|
|
447
|
-
} */
|
|
448
|
-
break
|
|
449
|
-
case ParserBase.WT_MAX_STREAMS_BIDI:
|
|
450
|
-
// this.recvSession({ maxstreams: readVarInt(bufferstate), type })
|
|
451
|
-
break
|
|
452
|
-
case ParserBase.WT_MAX_STREAMS_UNIDI:
|
|
453
|
-
// this.recvSession({ maxstreams: readVarInt(bufferstate), type })
|
|
454
|
-
break
|
|
455
|
-
case ParserBase.WT_DATA_BLOCKED: // TODO
|
|
456
|
-
// this.recvSession({ maxdata: readVarInt(bufferstate), type })
|
|
457
|
-
break
|
|
458
|
-
case ParserBase.WT_STREAM_DATA_BLOCKED: // TODO
|
|
459
|
-
/* {
|
|
460
|
-
const streamid = readVarInt(bufferstate)
|
|
461
|
-
const object = this.wtstreams.get(streamid)
|
|
462
|
-
if (object)
|
|
463
|
-
this.recvStream({
|
|
464
|
-
maxstreamdata: readVarInt(bufferstate),
|
|
465
|
-
type,
|
|
466
|
-
object
|
|
467
|
-
})
|
|
468
|
-
} */
|
|
469
|
-
break
|
|
470
|
-
case ParserBase.WT_STREAMS_BLOCKED_UNIDI:
|
|
471
|
-
/* {
|
|
472
|
-
const streamid = readVarInt(bufferstate)
|
|
473
|
-
const object = this.wtstreams.get(streamid)
|
|
474
|
-
if (object)
|
|
475
|
-
this.recvStream({
|
|
476
|
-
maxstreams: readVarInt(bufferstate),
|
|
477
|
-
type,
|
|
478
|
-
object
|
|
479
|
-
})
|
|
480
|
-
} */
|
|
481
|
-
break
|
|
482
|
-
case ParserBase.WT_STREAMS_BLOCKED_BIDI:
|
|
483
|
-
/* {
|
|
484
|
-
const streamid = readVarInt(bufferstate)
|
|
485
|
-
const object = this.wtstreams.get(streamid)
|
|
486
|
-
if (object)
|
|
487
|
-
this.recvStream({
|
|
488
|
-
maxstreams: readVarInt(bufferstate),
|
|
489
|
-
type,
|
|
490
|
-
streamid
|
|
491
|
-
})
|
|
492
|
-
} */
|
|
493
|
-
break
|
|
494
|
-
case ParserBase.DATAGRAM:
|
|
495
|
-
this.session.jsobj.onDatagramReceived({
|
|
496
|
-
datagram: new Uint8Array(
|
|
497
|
-
bufferstate.buffer.buffer,
|
|
498
|
-
bufferstate.buffer.byteOffset + bufferstate.offset,
|
|
499
|
-
offsetend - bufferstate.offset
|
|
500
|
-
)
|
|
501
|
-
})
|
|
502
|
-
|
|
503
|
-
break
|
|
504
|
-
default:
|
|
505
|
-
// do nothing
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
bufferstate.offset = offsetend
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* @param{{type: Number, headerVints: Array<Number>, payload: Uint8Array|undefined}} bs
|
|
513
|
-
*/
|
|
514
|
-
writeCapsule({ type, headerVints, payload }) {
|
|
515
|
-
let plength = 0
|
|
516
|
-
for (const ind in headerVints) plength += lengthVarInt(headerVints[ind])
|
|
517
|
-
plength += lengthVarInt(type)
|
|
518
|
-
const hlength = plength
|
|
519
|
-
if (payload) plength += payload.byteLength
|
|
520
|
-
|
|
521
|
-
const cdata = new Uint8Array(plength)
|
|
522
|
-
const bufferstate = { offset: 0, size: cdata.length, buffer: cdata }
|
|
523
|
-
writeVarInt(bufferstate, type)
|
|
524
|
-
for (const ind in headerVints) writeVarInt(bufferstate, headerVints[ind])
|
|
525
|
-
const dest = new Uint8Array(cdata.buffer, cdata.byteOffset + hlength)
|
|
526
|
-
if (payload) dest.set(payload)
|
|
527
|
-
this.ws.send(cdata)
|
|
528
|
-
|
|
529
|
-
/* const blocked = this.ws.bufferedAmount > 1024 * 256
|
|
530
|
-
// do something if blocked
|
|
531
|
-
if (blocked) this.blocked = true
|
|
532
|
-
return blocked */
|
|
533
|
-
return false
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* @param{{code: Number, reason: string}}arg
|
|
538
|
-
*/
|
|
539
|
-
sendClose({ code, reason }) {
|
|
540
|
-
this.ws.close(1000, code.toString() + ':' + reason)
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* @typedef {import('http2').Http2Stream} Http2Stream
|
|
546
|
-
*/
|
|
547
|
-
|
|
548
|
-
export class Http2WebTransportSession {
|
|
549
|
-
/**
|
|
550
|
-
* @param {Object} obj
|
|
551
|
-
* @param {Http2Stream} [obj.stream]
|
|
552
|
-
* @param {WebSocket} [obj.ws]
|
|
553
|
-
* @param {boolean} obj.isclient
|
|
554
|
-
* @param {import('../types.js').CreateParserFunction} obj.createParser
|
|
555
|
-
*/
|
|
556
|
-
constructor({ stream, ws, isclient, createParser }) {
|
|
557
|
-
// @ts-ignore
|
|
558
|
-
this.jsobj = undefined // the creator will set this
|
|
559
|
-
if (stream) {
|
|
560
|
-
this.stream = stream
|
|
561
|
-
} else if (ws) {
|
|
562
|
-
this.ws = ws
|
|
563
|
-
} else throw new Error('Neither stream or websocket supplied')
|
|
564
|
-
this.capsParser = createParser(this)
|
|
565
|
-
this.unidiId = 0
|
|
566
|
-
this.bidiId = 0
|
|
567
|
-
this.isclient = isclient
|
|
568
|
-
if (stream) {
|
|
569
|
-
if (isclient) {
|
|
570
|
-
stream.on('response', (headers) => {
|
|
571
|
-
processnextTick(() => {
|
|
572
|
-
if (headers[':status'] === 200) {
|
|
573
|
-
// on ready
|
|
574
|
-
this.jsobj.onReady({})
|
|
575
|
-
} else {
|
|
576
|
-
this.jsobj.onClose({
|
|
577
|
-
errorcode: headers[':status'],
|
|
578
|
-
error: 'Session stream errored'
|
|
579
|
-
})
|
|
580
|
-
}
|
|
581
|
-
})
|
|
582
|
-
})
|
|
583
|
-
} else {
|
|
584
|
-
processnextTick(() => {
|
|
585
|
-
this.jsobj.onReady({})
|
|
586
|
-
})
|
|
587
|
-
}
|
|
588
|
-
stream.on('close', () => {
|
|
589
|
-
if (!(this.jsobj.state === 'failed' || this.jsobj.state === 'closed')) {
|
|
590
|
-
this.jsobj.onClose({
|
|
591
|
-
errorcode: 0,
|
|
592
|
-
error: 'Session http/2 stream closed'
|
|
593
|
-
})
|
|
594
|
-
}
|
|
595
|
-
})
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* @param {Uint8Array} chunk
|
|
601
|
-
*/
|
|
602
|
-
writeDatagram(chunk) {
|
|
603
|
-
this.capsParser.writeCapsule({
|
|
604
|
-
type: ParserBase.DATAGRAM,
|
|
605
|
-
headerVints: [],
|
|
606
|
-
payload: chunk
|
|
607
|
-
})
|
|
608
|
-
processnextTick(() => {
|
|
609
|
-
this.jsobj.onDatagramSend({})
|
|
610
|
-
})
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
orderUnidiStream() {
|
|
614
|
-
let streamid = 0x2 | (this.unidiId << 2)
|
|
615
|
-
if (this.isclient) streamid = streamid | 0x1
|
|
616
|
-
this.capsParser.writeCapsule({
|
|
617
|
-
type: ParserBase.WT_STREAM_WOFIN,
|
|
618
|
-
headerVints: [streamid],
|
|
619
|
-
payload: undefined
|
|
620
|
-
})
|
|
621
|
-
this.capsParser.newStream(streamid)
|
|
622
|
-
this.unidiId++
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
orderBidiStream() {
|
|
626
|
-
let streamid = 0x0 | (this.bidiId << 2)
|
|
627
|
-
if (this.isclient) streamid = streamid | 0x1
|
|
628
|
-
this.capsParser.writeCapsule({
|
|
629
|
-
type: ParserBase.WT_STREAM_WOFIN,
|
|
630
|
-
headerVints: [streamid],
|
|
631
|
-
payload: undefined
|
|
632
|
-
})
|
|
633
|
-
this.capsParser.newStream(streamid)
|
|
634
|
-
this.bidiId++
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
orderSessionStats() {
|
|
638
|
-
this.jsobj.onSessionStats({
|
|
639
|
-
timestamp: 0,
|
|
640
|
-
expiredOutgoing: 0n,
|
|
641
|
-
lostOutgoing: 0n,
|
|
642
|
-
// non Datagram
|
|
643
|
-
minRtt: 0,
|
|
644
|
-
smoothedRtt: 0,
|
|
645
|
-
rttVariation: 0,
|
|
646
|
-
estimatedSendRateBps: 0n
|
|
647
|
-
})
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
orderDatagramStats() {
|
|
651
|
-
this.jsobj.onDatagramStats({
|
|
652
|
-
timestamp: 0,
|
|
653
|
-
expiredOutgoing: 0n,
|
|
654
|
-
lostOutgoing: 0n
|
|
655
|
-
})
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
/*
|
|
659
|
-
* @returns {void}
|
|
660
|
-
*/
|
|
661
|
-
notifySessionDraining() {}
|
|
662
|
-
/**
|
|
663
|
-
* @param {{ code: number, reason: string }} arg
|
|
664
|
-
*/
|
|
665
|
-
close({ code, reason }) {
|
|
666
|
-
this.capsParser.sendClose({ code, reason }) // thid includes for ws closing the session!
|
|
667
|
-
// what to do with the reason
|
|
668
|
-
if (this.stream) {
|
|
669
|
-
if (this.stream.close) this.stream.close(code)
|
|
670
|
-
else if (this.stream.end) this.stream.end()
|
|
671
|
-
else throw new Error('http2:session not close method')
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
export class Http2WebTransportBrowser {
|
|
677
|
-
/**
|
|
678
|
-
* @param {import('../../types.js').NativeClientOptions} args
|
|
679
|
-
*/
|
|
680
|
-
constructor(args) {
|
|
681
|
-
this.port = args?.port || 443
|
|
682
|
-
this.hostname = args?.host || 'localhost'
|
|
683
|
-
/** @type {import('../../session.js').HttpClient} */
|
|
684
|
-
// @ts-ignore
|
|
685
|
-
this.jsobj = undefined // the transport will set this
|
|
686
|
-
/** @type {WebSocket} */
|
|
687
|
-
// @ts-ignore
|
|
688
|
-
this.clientInt = undefined
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* @param {{path: string}} arg
|
|
693
|
-
*/
|
|
694
|
-
createTransport({ path }) {
|
|
695
|
-
try {
|
|
696
|
-
let url = 'wss://' + this.hostname + ':' + this.port
|
|
697
|
-
if (path) url = url + '/' + path
|
|
698
|
-
// eslint-disable-next-line no-undef
|
|
699
|
-
this.clientInt = new WebSocket(url, ['webtransport'])
|
|
700
|
-
} catch (error) {
|
|
701
|
-
this.jsobj.onClientConnected({
|
|
702
|
-
success: false
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
return
|
|
706
|
-
}
|
|
707
|
-
this.clientInt.binaryType = 'arraybuffer'
|
|
708
|
-
|
|
709
|
-
this.clientInt.addEventListener('open', (event) => {
|
|
710
|
-
if (this.clientInt?.protocol === 'webtransport') {
|
|
711
|
-
this.jsobj.onClientWebTransportSupport({})
|
|
712
|
-
this.jsobj.onClientConnected({
|
|
713
|
-
success: true
|
|
714
|
-
})
|
|
715
|
-
} else {
|
|
716
|
-
if (this.clientInt) this.clientInt.close()
|
|
717
|
-
this.jsobj.onClientConnected({
|
|
718
|
-
success: false
|
|
719
|
-
})
|
|
720
|
-
}
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
this.clientInt.addEventListener('error', () => {
|
|
724
|
-
this.jsobj.onClientConnected({
|
|
725
|
-
success: false
|
|
726
|
-
})
|
|
727
|
-
})
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* @param {string} path
|
|
732
|
-
*/
|
|
733
|
-
openWTSession(path) {
|
|
734
|
-
if (!this.clientInt) throw new Error('clientInt not present')
|
|
735
|
-
let sessobj
|
|
736
|
-
|
|
737
|
-
const retObj = {
|
|
738
|
-
session: new Http2WebTransportSession({
|
|
739
|
-
ws: this.clientInt,
|
|
740
|
-
isclient: true,
|
|
741
|
-
createParser: (
|
|
742
|
-
/** @type {Http2WebTransportSession} */ nativesession
|
|
743
|
-
) => {
|
|
744
|
-
sessobj = nativesession
|
|
745
|
-
const session = new BrowserParser({
|
|
746
|
-
ws: this.clientInt,
|
|
747
|
-
nativesession,
|
|
748
|
-
isclient: true
|
|
749
|
-
})
|
|
750
|
-
if (this.clientInt)
|
|
751
|
-
this.clientInt.addEventListener('close', (event) => {
|
|
752
|
-
let code = event.code
|
|
753
|
-
let error = 'Session WebSocket closed'
|
|
754
|
-
if (event.reason) {
|
|
755
|
-
let tokens = event.reason.split(':')
|
|
756
|
-
if (tokens.length > 1) {
|
|
757
|
-
code = parseInt(tokens[0])
|
|
758
|
-
tokens = tokens.slice(1)
|
|
759
|
-
}
|
|
760
|
-
error = tokens.join(':')
|
|
761
|
-
}
|
|
762
|
-
nativesession.jsobj.onClose({
|
|
763
|
-
errorcode: code,
|
|
764
|
-
error
|
|
765
|
-
})
|
|
766
|
-
})
|
|
767
|
-
return session
|
|
768
|
-
}
|
|
769
|
-
}),
|
|
770
|
-
reliable: true
|
|
771
|
-
}
|
|
772
|
-
this.jsobj.onHttpWTSessionVisitor(retObj)
|
|
773
|
-
|
|
774
|
-
// @ts-ignore
|
|
775
|
-
sessobj.jsobj.onReady({})
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
closeClient() {
|
|
779
|
-
if (this.clientInt) this.clientInt.close()
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
/** @type {WeakMap<WebTransportBase, import('./session.js').HttpWTSession>} */
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* @typedef {import('./dom.js').WebTransportCloseInfo} WebTransportCloseInfo
|
|
787
|
-
* @typedef {import('./dom.js').WebTransportBidirectionalStream} WebTransportBidirectionalStream
|
|
788
|
-
* @typedef {import('./dom.js').WebTransportReceiveStream} WebTransportReceiveStream
|
|
789
|
-
* @typedef {import('./session.js').HttpWTSession} HttpWTSession
|
|
790
|
-
* @typedef { import('./session.js').HttpClient} HttpClient
|
|
791
|
-
*/
|
|
792
|
-
|
|
793
|
-
/**
|
|
794
|
-
* @template T
|
|
795
|
-
* @typedef {import('node:stream/web').ReadableStream<T>} ReadableStream<T>
|
|
796
|
-
*/
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* @typedef {import('./dom.js').WebTransport} WebTransportInterface
|
|
800
|
-
*
|
|
801
|
-
* @implements {WebTransportInterface}
|
|
802
|
-
*/
|
|
803
|
-
export class WebTransportBase {
|
|
804
|
-
/**
|
|
805
|
-
* @param {string} url
|
|
806
|
-
* @param {import('./dom.js').WebTransportOptions} [args]
|
|
807
|
-
*/
|
|
808
|
-
constructor(url, args) {
|
|
809
|
-
if (!url) throw new Error('no URL supplied')
|
|
810
|
-
|
|
811
|
-
const ourl = new URL(url)
|
|
812
|
-
|
|
813
|
-
if (ourl.protocol !== 'https:') {
|
|
814
|
-
throw new Error('URL is not supported for webtransport')
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
const host = ourl.hostname
|
|
818
|
-
let port = ourl.port
|
|
819
|
-
if (port === '') port = '443'
|
|
820
|
-
const { sessionint, client } = this.createClient({ host, port, ...args })
|
|
821
|
-
this.ready = sessionint.ready
|
|
822
|
-
this.closed = sessionint.closed
|
|
823
|
-
this.draining = sessionint.draining
|
|
824
|
-
|
|
825
|
-
this.datagrams = sessionint.datagrams
|
|
826
|
-
|
|
827
|
-
this.incomingBidirectionalStreams = sessionint.incomingBidirectionalStreams
|
|
828
|
-
|
|
829
|
-
this.incomingUnidirectionalStreams =
|
|
830
|
-
sessionint.incomingUnidirectionalStreams
|
|
831
|
-
|
|
832
|
-
this.sessionint = sessionint
|
|
833
|
-
|
|
834
|
-
this.startUpConnection({ client, sessionint, ourl })
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
/**
|
|
838
|
-
* @param{import('./types.js').HttpWebTransportInit} args
|
|
839
|
-
* @return {{sessionint: HttpWTSession, client: HttpClient}}
|
|
840
|
-
* @abstract
|
|
841
|
-
*/
|
|
842
|
-
createClient(args) {
|
|
843
|
-
throw new Error('Implement createClient')
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* @param{{client: HttpClient, sessionint: HttpWTSession, ourl: URL}} args
|
|
848
|
-
* @abstract
|
|
849
|
-
*/
|
|
850
|
-
startUpConnection({ client, sessionint, ourl }) {
|
|
851
|
-
throw new Error('Implement createClient')
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
get reliability() {
|
|
855
|
-
const session = this.sessionint
|
|
856
|
-
|
|
857
|
-
if (!session) {
|
|
858
|
-
// should never happen as session is only removed when this instance is garbage collected
|
|
859
|
-
throw new Error('Http3WTSession was undefined')
|
|
860
|
-
}
|
|
861
|
-
return session.reliability
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
get congestionControl() {
|
|
865
|
-
const session = this.sessionint
|
|
866
|
-
|
|
867
|
-
if (!session) {
|
|
868
|
-
// should never happen as session is only removed when this instance is garbage collected
|
|
869
|
-
throw new Error('Http3WTSession was undefined')
|
|
870
|
-
}
|
|
871
|
-
return session.congestionControl
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
getStats() {
|
|
875
|
-
const session = this.sessionint
|
|
876
|
-
|
|
877
|
-
if (!session) {
|
|
878
|
-
// should never happen as session is only removed when this instance is garbage collected
|
|
879
|
-
throw new Error('Http3WTSession was undefined')
|
|
880
|
-
}
|
|
881
|
-
return session.getStats()
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
/**
|
|
885
|
-
* @param {WebTransportCloseInfo} [closeinfo]
|
|
886
|
-
*/
|
|
887
|
-
close(closeinfo) {
|
|
888
|
-
const session = this.sessionint
|
|
889
|
-
|
|
890
|
-
if (!session) {
|
|
891
|
-
// should never happen as session is only removed when this instance is garbage collected
|
|
892
|
-
throw new Error('Http3WTSession was undefined')
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
return session.close(closeinfo)
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
createBidirectionalStream() {
|
|
899
|
-
const session = this.sessionint
|
|
900
|
-
|
|
901
|
-
if (!session) {
|
|
902
|
-
// should never happen as session is only removed when this instance is garbage collected
|
|
903
|
-
throw new Error('Http3WTSession was undefined')
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
return session.createBidirectionalStream()
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
createUnidirectionalStream() {
|
|
910
|
-
const session = this.sessionint
|
|
911
|
-
|
|
912
|
-
if (!session) {
|
|
913
|
-
// should never happen as session is only removed when this instance is garbage collected
|
|
914
|
-
throw new Error('Http3WTSession was undefined')
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
return session.createUnidirectionalStream()
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
export class WebTransportError extends Error {
|
|
922
|
-
/**
|
|
923
|
-
* @param {string} message
|
|
924
|
-
*/
|
|
925
|
-
constructor(message) {
|
|
926
|
-
super(message)
|
|
927
|
-
|
|
928
|
-
this.name = this[Symbol.toStringTag] = 'WebTransportError'
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
/**
|
|
933
|
-
* WebTransport stream events
|
|
934
|
-
* @typedef {import('./types').WebTransportStreamEventHandler} WebTransportStreamEventHandler
|
|
935
|
-
* @typedef {import('./types').StreamRecvSignalEvent} StreamRecvSignalEvent
|
|
936
|
-
* @typedef {import('./types').StreamReadEvent} StreamReadEvent
|
|
937
|
-
* @typedef {import('./types').StreamWriteEvent} StreamWriteEvent
|
|
938
|
-
* @typedef {import('./types').StreamNetworkFinishEvent} StreamNetworkFinishEvent
|
|
939
|
-
*
|
|
940
|
-
* @typedef {import('./types').NativeHttpWTStream} NativeHttpWTStream
|
|
941
|
-
*
|
|
942
|
-
* @typedef {import('./dom').WebTransportReceiveStream} WebTransportReceiveStream
|
|
943
|
-
* @typedef {import('./dom').WebTransportSendStream} WebTransportSendStream
|
|
944
|
-
*
|
|
945
|
-
* @typedef {import('./session').HttpWTSession} HttpWTSession
|
|
946
|
-
*
|
|
947
|
-
* @typedef {import('stream/web').WritableStreamDefaultController} WritableStreamDefaultController
|
|
948
|
-
*/
|
|
949
|
-
|
|
950
|
-
export class HttpWTStream {
|
|
951
|
-
/**
|
|
952
|
-
* @param {object} args
|
|
953
|
-
* @param {NativeHttpWTStream} args.object
|
|
954
|
-
* @param {HttpWTSession} args.parentobj
|
|
955
|
-
* @param {object} args.transport
|
|
956
|
-
* @param {boolean} args.bidirectional
|
|
957
|
-
* @param {boolean} args.incoming
|
|
958
|
-
*/
|
|
959
|
-
constructor(args) {
|
|
960
|
-
this.objint = args.object
|
|
961
|
-
this.objint.jsobj = this
|
|
962
|
-
this.parentobj = args.parentobj
|
|
963
|
-
this.transport = args.transport
|
|
964
|
-
this.bidirectional = args.bidirectional
|
|
965
|
-
this.incoming = args.incoming
|
|
966
|
-
this.closed = false
|
|
967
|
-
|
|
968
|
-
/** @type {Promise<void> | null} */
|
|
969
|
-
this.pendingoperation = null
|
|
970
|
-
this.pendingres = null
|
|
971
|
-
|
|
972
|
-
/** @type {WebTransportReceiveStream} */
|
|
973
|
-
this.readable // eslint-disable-line no-unused-expressions
|
|
974
|
-
/** @type {WebTransportSendStream} */
|
|
975
|
-
this.writable // eslint-disable-line no-unused-expressions
|
|
976
|
-
|
|
977
|
-
/** @type {Promise<void> | null} */
|
|
978
|
-
this.pendingoperationRead = null
|
|
979
|
-
this.pendingresRead = null
|
|
980
|
-
|
|
981
|
-
if (this.bidirectional || this.incoming) {
|
|
982
|
-
/** @type {Number} */
|
|
983
|
-
this.incomingbufferfilled = 0
|
|
984
|
-
/** @type {Number} */
|
|
985
|
-
this.incomingbufferreadpos = 0
|
|
986
|
-
if (!this.objint.readbuffer)
|
|
987
|
-
throw new Error('No readbuffer for read stream')
|
|
988
|
-
/** @type {WebTransportReceiveStream} */
|
|
989
|
-
// @ts-expect-error `getStats` property is missing from ReadableStream
|
|
990
|
-
this.readable = new ReadableStream(
|
|
991
|
-
{
|
|
992
|
-
start: (
|
|
993
|
-
/** @type {import("stream/web").ReadableByteStreamController} */ controller
|
|
994
|
-
) => {
|
|
995
|
-
this.readableController = controller
|
|
996
|
-
this.objint.startReading()
|
|
997
|
-
},
|
|
998
|
-
pull: async (
|
|
999
|
-
/** @type {import("stream/web").ReadableByteStreamController} */ controller
|
|
1000
|
-
) => {
|
|
1001
|
-
if (this.readableclosed) {
|
|
1002
|
-
return Promise.resolve()
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
/** @type {Uint8Array} */
|
|
1006
|
-
if (this.incomingbufferfilled === 0) {
|
|
1007
|
-
this.pendingoperationRead = new Promise((resolve, reject) => {
|
|
1008
|
-
this.pendingresRead = resolve
|
|
1009
|
-
})
|
|
1010
|
-
await this.pendingoperationRead
|
|
1011
|
-
}
|
|
1012
|
-
if (this.incomingbufferfilled === 0) return Promise.resolve()
|
|
1013
|
-
|
|
1014
|
-
this.drainBuffer()
|
|
1015
|
-
},
|
|
1016
|
-
cancel: (/** @type {{ code: number; }} */ reason) => {
|
|
1017
|
-
/** @type {Promise<void>} */
|
|
1018
|
-
const promise = new Promise((resolve, reject) => {
|
|
1019
|
-
this.cancelres = resolve
|
|
1020
|
-
})
|
|
1021
|
-
let code = 0
|
|
1022
|
-
if (reason && reason.code) {
|
|
1023
|
-
if (reason.code < 0) code = 0
|
|
1024
|
-
else if (reason.code > 255) code = 255
|
|
1025
|
-
else code = reason.code
|
|
1026
|
-
}
|
|
1027
|
-
this.readableclosed = true
|
|
1028
|
-
this.objint.stopSending(code)
|
|
1029
|
-
return promise
|
|
1030
|
-
},
|
|
1031
|
-
type: 'bytes',
|
|
1032
|
-
autoAllocateChunkSize: 4096 // lets take this as buffer size
|
|
1033
|
-
}
|
|
1034
|
-
// TODO fix stretegy
|
|
1035
|
-
)
|
|
1036
|
-
this.readable.getStats = () => {
|
|
1037
|
-
return Promise.resolve({
|
|
1038
|
-
timestamp: 0,
|
|
1039
|
-
bytesReceived: 0n,
|
|
1040
|
-
bytesRead: 0n
|
|
1041
|
-
})
|
|
1042
|
-
}
|
|
1043
|
-
// @ts-ignore
|
|
1044
|
-
this.parentobj.addReceiveStream(this.readable, this.readableController)
|
|
1045
|
-
}
|
|
1046
|
-
if (this.bidirectional || !this.incoming) {
|
|
1047
|
-
/** @type {WebTransportSendStream} */
|
|
1048
|
-
// @ts-expect-error `getStats` property is missing from WritableStream
|
|
1049
|
-
this.writable = new WritableStream(
|
|
1050
|
-
{
|
|
1051
|
-
start: (controller) => {
|
|
1052
|
-
this.writableController = controller
|
|
1053
|
-
},
|
|
1054
|
-
write: (chunk, controller) => {
|
|
1055
|
-
if (this.writableclosed) {
|
|
1056
|
-
return Promise.resolve()
|
|
1057
|
-
}
|
|
1058
|
-
let wchunk = chunk
|
|
1059
|
-
if (wchunk instanceof ArrayBuffer) {
|
|
1060
|
-
wchunk = new Uint8Array(wchunk)
|
|
1061
|
-
}
|
|
1062
|
-
if (wchunk instanceof Uint8Array) {
|
|
1063
|
-
this.pendingoperation = new Promise((resolve, reject) => {
|
|
1064
|
-
this.pendingres = resolve
|
|
1065
|
-
})
|
|
1066
|
-
this.parentobj
|
|
1067
|
-
.waitForDatagramsSend()
|
|
1068
|
-
.finally(() => {
|
|
1069
|
-
this.objint.writeChunk(wchunk)
|
|
1070
|
-
})
|
|
1071
|
-
.catch((/** @type {any} */ err) => {
|
|
1072
|
-
log.error(err)
|
|
1073
|
-
})
|
|
1074
|
-
return this.pendingoperation
|
|
1075
|
-
} else {
|
|
1076
|
-
log.trace('chunk info:', chunk)
|
|
1077
|
-
throw new Error(
|
|
1078
|
-
'chunk is not of instanceof Uint8Array or Arraybuffer'
|
|
1079
|
-
)
|
|
1080
|
-
}
|
|
1081
|
-
},
|
|
1082
|
-
close: () => {
|
|
1083
|
-
if (this.writableclosed) {
|
|
1084
|
-
return Promise.resolve()
|
|
1085
|
-
}
|
|
1086
|
-
this.objint.streamFinal()
|
|
1087
|
-
this.pendingoperation = new Promise((resolve, reject) => {
|
|
1088
|
-
this.pendingres = resolve
|
|
1089
|
-
})
|
|
1090
|
-
return this.pendingoperation
|
|
1091
|
-
},
|
|
1092
|
-
abort: (reason) => {
|
|
1093
|
-
if (this.writableclosed) {
|
|
1094
|
-
return new Promise((resolve, reject) => {
|
|
1095
|
-
resolve()
|
|
1096
|
-
})
|
|
1097
|
-
}
|
|
1098
|
-
let code = 0
|
|
1099
|
-
if (reason && reason.code) {
|
|
1100
|
-
if (reason.code < 0) code = 0
|
|
1101
|
-
else if (reason.code > 255) code = 255
|
|
1102
|
-
else code = reason.code
|
|
1103
|
-
}
|
|
1104
|
-
/** @type {Promise<void>} */
|
|
1105
|
-
const promise = new Promise((resolve, reject) => {
|
|
1106
|
-
this.abortres = resolve
|
|
1107
|
-
})
|
|
1108
|
-
this.objint.resetStream(code)
|
|
1109
|
-
return promise
|
|
1110
|
-
}
|
|
1111
|
-
},
|
|
1112
|
-
{ highWaterMark: 4 }
|
|
1113
|
-
)
|
|
1114
|
-
this.writable.getStats = () => {
|
|
1115
|
-
return Promise.resolve({
|
|
1116
|
-
timestamp: 0,
|
|
1117
|
-
bytesWritten: 0n,
|
|
1118
|
-
bytesSent: 0n,
|
|
1119
|
-
bytesAcknowledged: 0n
|
|
1120
|
-
})
|
|
1121
|
-
}
|
|
1122
|
-
// @ts-ignore
|
|
1123
|
-
this.parentobj.addSendStream(this.writable, this.writableController)
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
/** @type {(() => void) | null} */
|
|
1127
|
-
this.cancelres = null
|
|
1128
|
-
/** @type {(() => void) | null} */
|
|
1129
|
-
this.pendingres = null
|
|
1130
|
-
/** @type {(() => void) | null} */
|
|
1131
|
-
this.abortres = null
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
drainBuffer() {
|
|
1135
|
-
const byob = this.readableController.byobRequest
|
|
1136
|
-
if (byob) {
|
|
1137
|
-
// @ts-ignore
|
|
1138
|
-
const view = byob?.view
|
|
1139
|
-
// @ts-ignore
|
|
1140
|
-
if (!(view instanceof Uint8Array)) {
|
|
1141
|
-
throw new Error('byob view is not a Uint8Array')
|
|
1142
|
-
}
|
|
1143
|
-
let toread = Math.min(view.byteLength, this.incomingbufferfilled)
|
|
1144
|
-
let read = 0
|
|
1145
|
-
if (!this.objint.readbuffer)
|
|
1146
|
-
throw new Error('No readbuffer in read for read stream')
|
|
1147
|
-
if (
|
|
1148
|
-
this.incomingbufferreadpos + toread >
|
|
1149
|
-
this.objint.readbuffer.byteLength
|
|
1150
|
-
) {
|
|
1151
|
-
/** @type {Number} */
|
|
1152
|
-
const firstread =
|
|
1153
|
-
this.objint.readbuffer.byteLength - this.incomingbufferreadpos
|
|
1154
|
-
read += firstread
|
|
1155
|
-
toread -= firstread
|
|
1156
|
-
const destview = new Uint8Array(
|
|
1157
|
-
view.buffer,
|
|
1158
|
-
0 + view.byteOffset,
|
|
1159
|
-
firstread
|
|
1160
|
-
)
|
|
1161
|
-
const srcview = new Uint8Array(
|
|
1162
|
-
this.objint.readbuffer,
|
|
1163
|
-
this.incomingbufferreadpos,
|
|
1164
|
-
firstread
|
|
1165
|
-
)
|
|
1166
|
-
destview.set(srcview)
|
|
1167
|
-
this.incomingbufferreadpos = 0
|
|
1168
|
-
}
|
|
1169
|
-
{
|
|
1170
|
-
const destview = new Uint8Array(
|
|
1171
|
-
view.buffer,
|
|
1172
|
-
read + view.byteOffset,
|
|
1173
|
-
toread
|
|
1174
|
-
)
|
|
1175
|
-
const srcview = new Uint8Array(
|
|
1176
|
-
this.objint.readbuffer,
|
|
1177
|
-
this.incomingbufferreadpos,
|
|
1178
|
-
toread
|
|
1179
|
-
)
|
|
1180
|
-
destview.set(srcview)
|
|
1181
|
-
read += toread
|
|
1182
|
-
this.incomingbufferreadpos =
|
|
1183
|
-
(this.incomingbufferreadpos + toread) %
|
|
1184
|
-
this.objint.readbuffer.byteLength
|
|
1185
|
-
}
|
|
1186
|
-
// @ts-ignore
|
|
1187
|
-
byob.respond(read)
|
|
1188
|
-
this.incomingbufferfilled -= read
|
|
1189
|
-
this.objint.updateReadPos(read, this.incomingbufferreadpos)
|
|
1190
|
-
} else {
|
|
1191
|
-
let toread = this.incomingbufferfilled
|
|
1192
|
-
const toqueue = new Uint8Array(toread)
|
|
1193
|
-
let read = 0
|
|
1194
|
-
if (!this.objint.readbuffer)
|
|
1195
|
-
throw new Error('No readbuffer in read for read stream')
|
|
1196
|
-
if (
|
|
1197
|
-
this.incomingbufferreadpos + toread >
|
|
1198
|
-
this.objint.readbuffer.byteLength
|
|
1199
|
-
) {
|
|
1200
|
-
/** @type {Number} */
|
|
1201
|
-
const firstread =
|
|
1202
|
-
this.objint.readbuffer.byteLength - this.incomingbufferreadpos
|
|
1203
|
-
read += firstread
|
|
1204
|
-
toread -= firstread
|
|
1205
|
-
const destview = new Uint8Array(
|
|
1206
|
-
toqueue.buffer,
|
|
1207
|
-
0 + toqueue.byteOffset,
|
|
1208
|
-
firstread
|
|
1209
|
-
)
|
|
1210
|
-
const srcview = new Uint8Array(
|
|
1211
|
-
this.objint.readbuffer,
|
|
1212
|
-
this.incomingbufferreadpos,
|
|
1213
|
-
firstread
|
|
1214
|
-
)
|
|
1215
|
-
destview.set(srcview)
|
|
1216
|
-
this.incomingbufferreadpos = 0
|
|
1217
|
-
}
|
|
1218
|
-
{
|
|
1219
|
-
const destview = new Uint8Array(
|
|
1220
|
-
toqueue.buffer,
|
|
1221
|
-
read + toqueue.byteOffset,
|
|
1222
|
-
toread
|
|
1223
|
-
)
|
|
1224
|
-
const srcview = new Uint8Array(
|
|
1225
|
-
this.objint.readbuffer,
|
|
1226
|
-
this.incomingbufferreadpos,
|
|
1227
|
-
toread
|
|
1228
|
-
)
|
|
1229
|
-
destview.set(srcview)
|
|
1230
|
-
read += toread
|
|
1231
|
-
this.incomingbufferreadpos =
|
|
1232
|
-
(this.incomingbufferreadpos + toread) %
|
|
1233
|
-
this.objint.readbuffer.byteLength
|
|
1234
|
-
}
|
|
1235
|
-
this.readableController.enqueue(toqueue)
|
|
1236
|
-
|
|
1237
|
-
this.incomingbufferfilled -= read
|
|
1238
|
-
this.pulledbytes += read
|
|
1239
|
-
this.objint.updateReadPos(read, this.incomingbufferreadpos)
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
/**
|
|
1244
|
-
* @param {import('./types').StreamRecvSignalEvent} args
|
|
1245
|
-
* @returns {void}
|
|
1246
|
-
*/
|
|
1247
|
-
onStreamRecvSignal(args) {
|
|
1248
|
-
log('callback', args?.nettask)
|
|
1249
|
-
log.trace('onStreamRecvSignal', args)
|
|
1250
|
-
// check if transport is closed
|
|
1251
|
-
let parentcleanup = true
|
|
1252
|
-
const parentstate = this.parentobj.state
|
|
1253
|
-
if (parentstate === 'closed' || parentstate === 'failed') {
|
|
1254
|
-
log('no parent cleanup as parent was closed or failed')
|
|
1255
|
-
parentcleanup = false
|
|
1256
|
-
}
|
|
1257
|
-
switch (args.nettask) {
|
|
1258
|
-
case 'resetStream':
|
|
1259
|
-
if (this.readable) {
|
|
1260
|
-
if (parentcleanup)
|
|
1261
|
-
this.parentobj.removeReceiveStream(
|
|
1262
|
-
this.readable,
|
|
1263
|
-
this.readableController
|
|
1264
|
-
)
|
|
1265
|
-
this.readableclosed = true
|
|
1266
|
-
this.readableController.error(
|
|
1267
|
-
new WebTransportError('Resetstream with code:' + (args.code || 0))
|
|
1268
|
-
)
|
|
1269
|
-
} else {
|
|
1270
|
-
log.error('resetStream without readable')
|
|
1271
|
-
}
|
|
1272
|
-
break
|
|
1273
|
-
|
|
1274
|
-
case 'stopSending':
|
|
1275
|
-
if (this.writable) {
|
|
1276
|
-
if (parentcleanup)
|
|
1277
|
-
this.parentobj.removeSendStream(
|
|
1278
|
-
this.writable,
|
|
1279
|
-
this.writableController
|
|
1280
|
-
)
|
|
1281
|
-
|
|
1282
|
-
this.writableclosed = true
|
|
1283
|
-
this.writableController.error(
|
|
1284
|
-
new WebTransportError('StopSending with code:' + (args.code || 0))
|
|
1285
|
-
)
|
|
1286
|
-
} else {
|
|
1287
|
-
log.error('stopSending without writable')
|
|
1288
|
-
}
|
|
1289
|
-
break
|
|
1290
|
-
default:
|
|
1291
|
-
log.error('unhandled onStreamRecvSignal')
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
if (this.pendingoperation) {
|
|
1295
|
-
const res = this.pendingres
|
|
1296
|
-
this.pendingoperation = null
|
|
1297
|
-
this.pendingres = null
|
|
1298
|
-
if (res != null) {
|
|
1299
|
-
res()
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
if (this.pendingoperationRead) {
|
|
1303
|
-
const res = this.pendingresRead
|
|
1304
|
-
this.pendingoperationRead = null
|
|
1305
|
-
this.pendingresRead = null
|
|
1306
|
-
if (res != null) {
|
|
1307
|
-
res()
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
/**
|
|
1313
|
-
* @param {StreamReadEvent} args
|
|
1314
|
-
* @returns {void}
|
|
1315
|
-
*/
|
|
1316
|
-
onStreamRead(args) {
|
|
1317
|
-
if (args.buffergrow && !this.readableclosed) {
|
|
1318
|
-
log.trace('stream read received', args.buffergrow)
|
|
1319
|
-
this.incomingbufferfilled += args.buffergrow
|
|
1320
|
-
// console.log('stream read received', args.data, Date.now())
|
|
1321
|
-
if (this.pendingoperationRead) {
|
|
1322
|
-
// this.readableController.enqueue(data)
|
|
1323
|
-
const res = this.pendingresRead
|
|
1324
|
-
this.pendingoperationRead = null
|
|
1325
|
-
this.pendingresRead = null
|
|
1326
|
-
if (res) res()
|
|
1327
|
-
}
|
|
1328
|
-
if (
|
|
1329
|
-
this.readableController.desiredSize != null &&
|
|
1330
|
-
this.readableController.desiredSize < 0
|
|
1331
|
-
)
|
|
1332
|
-
this.objint.stopReading()
|
|
1333
|
-
}
|
|
1334
|
-
if (args.fin) {
|
|
1335
|
-
if (this.incomingbufferfilled > 0) {
|
|
1336
|
-
log.trace('Warning buffer filled and we got a fin')
|
|
1337
|
-
if (this.pendingoperationRead || this.pendingresRead)
|
|
1338
|
-
throw new Error('We have pendingoperationRead and a filled buffer?')
|
|
1339
|
-
|
|
1340
|
-
this.finalDrain()
|
|
1341
|
-
}
|
|
1342
|
-
if (this.cancelres) {
|
|
1343
|
-
const res = this.cancelres
|
|
1344
|
-
this.cancelres = null
|
|
1345
|
-
res()
|
|
1346
|
-
}
|
|
1347
|
-
if (!this.readableclosed) {
|
|
1348
|
-
this.readableController.close()
|
|
1349
|
-
this.readableclosed = true
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
finalDrain() {
|
|
1355
|
-
while (this.incomingbufferfilled > 0) this.drainBuffer()
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
/**
|
|
1359
|
-
* @param {StreamWriteEvent} args
|
|
1360
|
-
*/
|
|
1361
|
-
onStreamWrite(args) {
|
|
1362
|
-
// we ignore success
|
|
1363
|
-
if (this.pendingoperation) {
|
|
1364
|
-
const res = this.pendingres
|
|
1365
|
-
this.pendingoperation = null
|
|
1366
|
-
this.pendingres = null
|
|
1367
|
-
if (res != null) {
|
|
1368
|
-
res()
|
|
1369
|
-
}
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
/**
|
|
1374
|
-
* @param {StreamNetworkFinishEvent} args
|
|
1375
|
-
*/
|
|
1376
|
-
onStreamNetworkFinish(args) {
|
|
1377
|
-
log('callback', args?.nettask)
|
|
1378
|
-
log.trace('networkfinish args', args)
|
|
1379
|
-
switch (args.nettask) {
|
|
1380
|
-
case 'stopSending':
|
|
1381
|
-
if (this.cancelres) {
|
|
1382
|
-
const res = this.cancelres
|
|
1383
|
-
this.cancelres = null
|
|
1384
|
-
res()
|
|
1385
|
-
}
|
|
1386
|
-
this.stopSendingRecv = true
|
|
1387
|
-
break
|
|
1388
|
-
case 'resetStream':
|
|
1389
|
-
if (this.abortres) {
|
|
1390
|
-
const res = this.abortres
|
|
1391
|
-
this.abortres = null
|
|
1392
|
-
res()
|
|
1393
|
-
if (this.readable)
|
|
1394
|
-
this.parentobj.removeReceiveStream(
|
|
1395
|
-
this.readable,
|
|
1396
|
-
this.readableController
|
|
1397
|
-
)
|
|
1398
|
-
if (this.writable)
|
|
1399
|
-
this.parentobj.removeSendStream(
|
|
1400
|
-
this.writable,
|
|
1401
|
-
this.writableController
|
|
1402
|
-
)
|
|
1403
|
-
this.readableclosed = true
|
|
1404
|
-
this.parentobj.removeStreamObj(this)
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
break
|
|
1408
|
-
|
|
1409
|
-
case 'streamFinal':
|
|
1410
|
-
if (this.pendingoperation) {
|
|
1411
|
-
const res = this.pendingres
|
|
1412
|
-
this.pendingoperation = null
|
|
1413
|
-
this.pendingres = null
|
|
1414
|
-
if (res != null) {
|
|
1415
|
-
res()
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
break
|
|
1419
|
-
default:
|
|
1420
|
-
log.error('onStreamNetworkFinish unknown task', args.nettask)
|
|
1421
|
-
}
|
|
1422
|
-
// we could differentiate....
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* WebTransport session events
|
|
1428
|
-
* @typedef {import('./types').WebTransportSessionEventHandler} WebTransportSessionEventHandler
|
|
1429
|
-
* @typedef {import('./types').SessionReadyEvent} SessionReadyEvent
|
|
1430
|
-
* @typedef {import('./types').SessionCloseEvent} SessionCloseEvent
|
|
1431
|
-
* @typedef {import('./types').DatagramReceivedEvent} DatagramReceivedEvent
|
|
1432
|
-
* @typedef {import('./types').DatagramSendEvent} DatagramSendEvent
|
|
1433
|
-
* @typedef {import('./types').GoawayReceivedEvent} GoawayReceivedEvent
|
|
1434
|
-
* @typedef {import('./types').DatagramStatsEvent} DatagramStatsEvent
|
|
1435
|
-
* @typedef {import('./types').SessionStatsEvent} SessionStatsEvent
|
|
1436
|
-
* @typedef {import('./types').NewStreamEvent} NewStreamEvent
|
|
1437
|
-
*
|
|
1438
|
-
* @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo
|
|
1439
|
-
* @typedef {import('./dom').WebTransportBidirectionalStream} WebTransportBidirectionalStream
|
|
1440
|
-
* @typedef {import('./dom').WebTransportSendStream} WebTransportSendStream
|
|
1441
|
-
* @typedef {import('./dom').WebTransportReceiveStream} WebTransportReceiveStream
|
|
1442
|
-
* @typedef {import('./dom').WebTransportDatagramDuplexStream} WebTransportDatagramDuplexStream
|
|
1443
|
-
* @typedef {import('./dom').WebTransportReliabilityMode} WebTransportReliabilityMode
|
|
1444
|
-
* @typedef {import('./dom').WebTransportCongestionControl} WebTransportCongestionControl
|
|
1445
|
-
* @typedef {import('./dom').WebTransportStats} WebTransportStats
|
|
1446
|
-
* @typedef {import('./dom').WebTransportDatagramStats} WebTransportDatagramStats
|
|
1447
|
-
*
|
|
1448
|
-
* @typedef {import('./types').NativeHttpWTSession} NativeHttpWTSession
|
|
1449
|
-
*
|
|
1450
|
-
* Public API
|
|
1451
|
-
* @typedef {import('./types').WebTransportSession} WebTransportSession
|
|
1452
|
-
*
|
|
1453
|
-
* @typedef {import('./server').HttpServer} HttpServer
|
|
1454
|
-
* @typedef {import('./client').HttpClient} HttpClient
|
|
1455
|
-
*
|
|
1456
|
-
* @typedef {import('stream/web').WritableStreamDefaultController} WritableStreamDefaultController
|
|
1457
|
-
*/
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
* @implements {WebTransportSessionEventHandler}
|
|
1461
|
-
* @implements {WebTransportSession}
|
|
1462
|
-
*/
|
|
1463
|
-
export class HttpWTSession {
|
|
1464
|
-
/**
|
|
1465
|
-
* @param {object} args
|
|
1466
|
-
* @param {import('./types').NativeHttpWTSession} [args.object]
|
|
1467
|
-
* @param {HttpServer | HttpClient} args.parentobj
|
|
1468
|
-
* @param {any | undefined} [args.header= undefined]
|
|
1469
|
-
*/
|
|
1470
|
-
constructor(args) {
|
|
1471
|
-
if (args.object) {
|
|
1472
|
-
this.objint = args.object
|
|
1473
|
-
this.objint.jsobj = this
|
|
1474
|
-
}
|
|
1475
|
-
this.parentobj = args.parentobj
|
|
1476
|
-
/** @type {import('./types').WebTransportSessionState} */
|
|
1477
|
-
this.state = 'connecting'
|
|
1478
|
-
|
|
1479
|
-
/** @type {((value?: any) => void) | null | undefined} */
|
|
1480
|
-
this.readyResolve = null
|
|
1481
|
-
/** @type {(() => void) | null | undefined} */
|
|
1482
|
-
this.closeHook = null
|
|
1483
|
-
/** @type {(any | null | undefined)} */
|
|
1484
|
-
this.header = args.header
|
|
1485
|
-
|
|
1486
|
-
/** @type {Promise<void>} */
|
|
1487
|
-
this.ready = new Promise((resolve, reject) => {
|
|
1488
|
-
this.readyResolve = resolve
|
|
1489
|
-
this.readyReject = reject
|
|
1490
|
-
})
|
|
1491
|
-
/** @type {WebTransportReliabilityMode} */
|
|
1492
|
-
this.reliability = 'pending'
|
|
1493
|
-
/** @type {WebTransportCongestionControl} */
|
|
1494
|
-
this.congestionControl = 'default'
|
|
1495
|
-
/** @type {Promise<WebTransportCloseInfo>} */
|
|
1496
|
-
this.closed = new Promise((resolve, reject) => {
|
|
1497
|
-
this.closedResolve = resolve
|
|
1498
|
-
this.closedReject = reject
|
|
1499
|
-
})
|
|
1500
|
-
|
|
1501
|
-
/** @type {Promise<undefined>} */
|
|
1502
|
-
this.draining = new Promise((resolve, reject) => {
|
|
1503
|
-
this.drainingResolve = resolve
|
|
1504
|
-
this.drainingReject = reject
|
|
1505
|
-
})
|
|
1506
|
-
|
|
1507
|
-
/** @type {ReadableStream<WebTransportBidirectionalStream>} */
|
|
1508
|
-
this.incomingBidirectionalStreams = new ReadableStream({
|
|
1509
|
-
/** @param {ReadableStreamDefaultController<WebTransportBidirectionalStream>} controller */
|
|
1510
|
-
start: (controller) => {
|
|
1511
|
-
this.incomBiDiController = controller
|
|
1512
|
-
}
|
|
1513
|
-
})
|
|
1514
|
-
/** @type {ReadableStream<WebTransportReceiveStream>} */
|
|
1515
|
-
this.incomingUnidirectionalStreams = new ReadableStream({
|
|
1516
|
-
/** @param {ReadableStreamDefaultController<WebTransportReceiveStream>} controller */
|
|
1517
|
-
start: (controller) => {
|
|
1518
|
-
this.incomUniDiController = controller
|
|
1519
|
-
}
|
|
1520
|
-
})
|
|
1521
|
-
|
|
1522
|
-
/** @type {Array<() => void>} */
|
|
1523
|
-
this.writeDatagramRes = []
|
|
1524
|
-
/** @type {Array<(err?: Error) => void>} */
|
|
1525
|
-
this.writeDatagramRej = []
|
|
1526
|
-
/** @type {Array<Promise<void>>} */
|
|
1527
|
-
this.writeDatagramProm = []
|
|
1528
|
-
|
|
1529
|
-
/** @type {WebTransportDatagramDuplexStream} */
|
|
1530
|
-
this.datagrams = {
|
|
1531
|
-
/** @type {ReadableStream<Uint8Array>} */
|
|
1532
|
-
readable: new ReadableStream({
|
|
1533
|
-
start: (
|
|
1534
|
-
/** @type {import("stream/web").ReadableByteStreamController} */ controller
|
|
1535
|
-
) => {
|
|
1536
|
-
this.incomDatagramController = controller
|
|
1537
|
-
},
|
|
1538
|
-
type: 'bytes'
|
|
1539
|
-
}),
|
|
1540
|
-
writable: new WritableStream({
|
|
1541
|
-
start: (controller) => {
|
|
1542
|
-
this.outgoDatagramController = controller
|
|
1543
|
-
},
|
|
1544
|
-
write: (chunk, controller) => {
|
|
1545
|
-
if (this.state === 'closed') throw new Error('Session is closed')
|
|
1546
|
-
if (chunk instanceof Uint8Array) {
|
|
1547
|
-
/** @type {Promise<void>} */
|
|
1548
|
-
const ret = new Promise((resolve, reject) => {
|
|
1549
|
-
this.writeDatagramRes.push(resolve)
|
|
1550
|
-
this.writeDatagramRej.push(reject)
|
|
1551
|
-
})
|
|
1552
|
-
this.writeDatagramProm.push(ret)
|
|
1553
|
-
log.trace('b4 datagram write', chunk)
|
|
1554
|
-
if (this.objint == null) {
|
|
1555
|
-
throw new Error('this.objint is not set')
|
|
1556
|
-
}
|
|
1557
|
-
this.objint.writeDatagram(chunk)
|
|
1558
|
-
return ret
|
|
1559
|
-
} else throw new Error('chunk is not of type Uint8Array')
|
|
1560
|
-
},
|
|
1561
|
-
close: () => {
|
|
1562
|
-
// do nothing
|
|
1563
|
-
}
|
|
1564
|
-
})
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
|
-
/** @type {Array<(stream: WebTransportBidirectionalStream) => void>} */
|
|
1568
|
-
this.resolveBiDi = []
|
|
1569
|
-
/** @type {Array<(stream: WebTransportSendStream) => void>} */
|
|
1570
|
-
this.resolveUniDi = []
|
|
1571
|
-
/** @type {Array<(err?: Error) => void>} */
|
|
1572
|
-
this.rejectBiDi = []
|
|
1573
|
-
/** @type {Array<(err?: Error) => void>} */
|
|
1574
|
-
this.rejectUniDi = []
|
|
1575
|
-
|
|
1576
|
-
/** @type {Array<(stats: WebTransportStats) => void>} */
|
|
1577
|
-
this.resolveSessionStats = []
|
|
1578
|
-
/** @type {Array<(err?: Error) => void>} */
|
|
1579
|
-
this.rejectSessionStats = []
|
|
1580
|
-
|
|
1581
|
-
/** @type {Array<(stats: WebTransportDatagramStats) => void>} */
|
|
1582
|
-
this.resolveDatagramStats = []
|
|
1583
|
-
/** @type {Array<(err?: Error) => void>} */
|
|
1584
|
-
this.rejectDatagramStats = []
|
|
1585
|
-
|
|
1586
|
-
/** @type {Set<WebTransportSendStream>} */
|
|
1587
|
-
this.sendStreams = new Set()
|
|
1588
|
-
/** @type {Set<WebTransportReceiveStream>} */
|
|
1589
|
-
this.receiveStreams = new Set()
|
|
1590
|
-
/** @type {Set<HttpWTStream>} */
|
|
1591
|
-
this.streamObjs = new Set()
|
|
1592
|
-
|
|
1593
|
-
/** @type {Set<WritableStreamDefaultController>} */
|
|
1594
|
-
this.sendStreamsController = new Set()
|
|
1595
|
-
/** @type {Set<ReadableStreamDefaultController>} */
|
|
1596
|
-
this.receiveStreamsController = new Set()
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
/**
|
|
1600
|
-
* @param {NativeHttpWTSession} object
|
|
1601
|
-
* @param {boolean} reliable
|
|
1602
|
-
*/
|
|
1603
|
-
setSessionObj(object, reliable) {
|
|
1604
|
-
if (object) {
|
|
1605
|
-
this.objint = object
|
|
1606
|
-
this.objint.jsobj = this
|
|
1607
|
-
this.reliable = !!reliable
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
getStats() {
|
|
1612
|
-
if (this.objint == null) {
|
|
1613
|
-
throw new Error('this.objint not set')
|
|
1614
|
-
}
|
|
1615
|
-
const prom = new Promise((resolve, reject) => {
|
|
1616
|
-
this.resolveSessionStats.push(resolve)
|
|
1617
|
-
this.rejectSessionStats.push(reject)
|
|
1618
|
-
})
|
|
1619
|
-
this.objint.orderSessionStats()
|
|
1620
|
-
return prom
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
/**
|
|
1624
|
-
* @param {SessionStatsEvent} evt
|
|
1625
|
-
*/
|
|
1626
|
-
onSessionStats({
|
|
1627
|
-
timestamp,
|
|
1628
|
-
expiredOutgoing = BigInt(0),
|
|
1629
|
-
lostOutgoing = BigInt(0),
|
|
1630
|
-
// non Datagram
|
|
1631
|
-
minRtt = 0,
|
|
1632
|
-
smoothedRtt = 0,
|
|
1633
|
-
rttVariation = 0,
|
|
1634
|
-
estimatedSendRateBps
|
|
1635
|
-
}) {
|
|
1636
|
-
const res = this.resolveSessionStats.pop()
|
|
1637
|
-
this.rejectSessionStats.pop()
|
|
1638
|
-
if (res)
|
|
1639
|
-
res({
|
|
1640
|
-
timestamp,
|
|
1641
|
-
bytesSent: BigInt(0),
|
|
1642
|
-
packetsSent: BigInt(0),
|
|
1643
|
-
packetsLost: BigInt(0),
|
|
1644
|
-
numOutgoingStreamsCreated: 0,
|
|
1645
|
-
numIncomingStreamsCreated: 0,
|
|
1646
|
-
bytesReceived: BigInt(0),
|
|
1647
|
-
packetsReceived: BigInt(0),
|
|
1648
|
-
smoothedRtt,
|
|
1649
|
-
rttVariation,
|
|
1650
|
-
minRtt,
|
|
1651
|
-
estimatedSendRate: estimatedSendRateBps,
|
|
1652
|
-
datagrams: {
|
|
1653
|
-
timestamp,
|
|
1654
|
-
expiredOutgoing,
|
|
1655
|
-
droppedIncoming: BigInt(0),
|
|
1656
|
-
lostOutgoing
|
|
1657
|
-
}
|
|
1658
|
-
})
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
/**
|
|
1662
|
-
* @param {DatagramStatsEvent} evt
|
|
1663
|
-
*/
|
|
1664
|
-
onDatagramStats({
|
|
1665
|
-
timestamp,
|
|
1666
|
-
expiredOutgoing = BigInt(0),
|
|
1667
|
-
lostOutgoing = BigInt(0)
|
|
1668
|
-
}) {
|
|
1669
|
-
const res = this.resolveDatagramStats.pop()
|
|
1670
|
-
this.rejectDatagramStats.pop()
|
|
1671
|
-
if (res)
|
|
1672
|
-
res({
|
|
1673
|
-
timestamp,
|
|
1674
|
-
expiredOutgoing,
|
|
1675
|
-
droppedIncoming: BigInt(0),
|
|
1676
|
-
lostOutgoing
|
|
1677
|
-
})
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
async waitForDatagramsSend() {
|
|
1681
|
-
while (this.writeDatagramProm.length > 0) {
|
|
1682
|
-
try {
|
|
1683
|
-
await Promise.allSettled(this.writeDatagramProm)
|
|
1684
|
-
} catch (error) {
|
|
1685
|
-
log.error('datagram promise failed ', error)
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
notifySessionDraining() {
|
|
1691
|
-
if (this.objint == null) {
|
|
1692
|
-
throw new Error('this.objint not set')
|
|
1693
|
-
}
|
|
1694
|
-
this.objint.notifySessionDraining()
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
/**
|
|
1698
|
-
* @param {HttpWTStream} stream
|
|
1699
|
-
*/
|
|
1700
|
-
addStreamObj(stream) {
|
|
1701
|
-
this.streamObjs.add(stream)
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
/**
|
|
1705
|
-
* @param {HttpWTStream} stream
|
|
1706
|
-
*/
|
|
1707
|
-
removeStreamObj(stream) {
|
|
1708
|
-
this.streamObjs.delete(stream)
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
/**
|
|
1712
|
-
* @param {WebTransportSendStream} stream
|
|
1713
|
-
* @param {WritableStreamDefaultController} controller
|
|
1714
|
-
*/
|
|
1715
|
-
addSendStream(stream, controller) {
|
|
1716
|
-
this.sendStreams.add(stream)
|
|
1717
|
-
this.sendStreamsController.add(controller)
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
/**
|
|
1721
|
-
* @param {WebTransportSendStream} stream
|
|
1722
|
-
* @param {WritableStreamDefaultController} controller
|
|
1723
|
-
*/
|
|
1724
|
-
removeSendStream(stream, controller) {
|
|
1725
|
-
this.sendStreams.delete(stream)
|
|
1726
|
-
this.sendStreamsController.delete(controller)
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
/**
|
|
1730
|
-
* @param {WebTransportReceiveStream } stream
|
|
1731
|
-
* @param {ReadableStreamDefaultController} controller
|
|
1732
|
-
*/
|
|
1733
|
-
addReceiveStream(stream, controller) {
|
|
1734
|
-
this.receiveStreams.add(stream)
|
|
1735
|
-
this.receiveStreamsController.add(controller)
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
/**
|
|
1739
|
-
* @param {WebTransportReceiveStream } stream
|
|
1740
|
-
* @param {ReadableStreamDefaultController} controller
|
|
1741
|
-
*/
|
|
1742
|
-
removeReceiveStream(stream, controller) {
|
|
1743
|
-
this.receiveStreams.delete(stream)
|
|
1744
|
-
this.receiveStreamsController.delete(controller)
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
/**
|
|
1748
|
-
* @returns {Promise<WebTransportBidirectionalStream>}
|
|
1749
|
-
*/
|
|
1750
|
-
createBidirectionalStream() {
|
|
1751
|
-
if (this.objint == null) {
|
|
1752
|
-
throw new Error('this.objint not set')
|
|
1753
|
-
}
|
|
1754
|
-
/** @type {Promise<WebTransportBidirectionalStream>} */
|
|
1755
|
-
const prom = new Promise((resolve, reject) => {
|
|
1756
|
-
this.resolveBiDi.push(resolve)
|
|
1757
|
-
this.rejectBiDi.push(reject)
|
|
1758
|
-
})
|
|
1759
|
-
this.objint.orderBidiStream()
|
|
1760
|
-
return prom
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
/**
|
|
1764
|
-
*@returns {Promise<WebTransportSendStream>}
|
|
1765
|
-
*/
|
|
1766
|
-
createUnidirectionalStream() {
|
|
1767
|
-
if (this.objint == null) {
|
|
1768
|
-
throw new Error('this.objint not set')
|
|
1769
|
-
}
|
|
1770
|
-
/** @type {Promise<WebTransportSendStream>} */
|
|
1771
|
-
const prom = new Promise((resolve, reject) => {
|
|
1772
|
-
this.resolveUniDi.push(resolve)
|
|
1773
|
-
this.rejectUniDi.push(reject)
|
|
1774
|
-
})
|
|
1775
|
-
this.objint.orderUnidiStream()
|
|
1776
|
-
return prom
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
/**
|
|
1780
|
-
* @param {object} [closeInfo]
|
|
1781
|
-
* @param {number} closeInfo.closeCode
|
|
1782
|
-
* @param {string} closeInfo.reason
|
|
1783
|
-
* @returns {void}
|
|
1784
|
-
*/
|
|
1785
|
-
close(closeInfo) {
|
|
1786
|
-
log('closeinfo', closeInfo)
|
|
1787
|
-
if (this.state === 'closed' || this.state === 'failed') return
|
|
1788
|
-
if (this.objint) {
|
|
1789
|
-
this.objint.close({
|
|
1790
|
-
code: closeInfo?.closeCode ?? 0,
|
|
1791
|
-
reason: closeInfo?.reason.substring(0, 1023) ?? ''
|
|
1792
|
-
})
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
onReady(/* error */) {
|
|
1797
|
-
this.state = 'connected'
|
|
1798
|
-
if (!this.reliable) this.reliability = 'supports-unreliable'
|
|
1799
|
-
else this.reliability = 'reliable-only'
|
|
1800
|
-
if (this.readyResolve) this.readyResolve()
|
|
1801
|
-
delete this.readyResolve
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
/**
|
|
1805
|
-
* @param {SessionCloseEvent} args
|
|
1806
|
-
*/
|
|
1807
|
-
onClose(args) {
|
|
1808
|
-
delete this.objint // not valid any more
|
|
1809
|
-
|
|
1810
|
-
if (this.state !== 'connected') {
|
|
1811
|
-
log.error(
|
|
1812
|
-
'session was closed before state was "connected" - it was "%s"',
|
|
1813
|
-
this.state
|
|
1814
|
-
)
|
|
1815
|
-
this.state = 'failed'
|
|
1816
|
-
|
|
1817
|
-
// make sure the event loop can still exit
|
|
1818
|
-
if (this.closeHook) {
|
|
1819
|
-
this.closeHook()
|
|
1820
|
-
delete this.closeHook
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
// closed before connected
|
|
1824
|
-
const error = new WebTransportError('Opening handshake failed.')
|
|
1825
|
-
this.readyReject(error)
|
|
1826
|
-
this.closedReject(error)
|
|
1827
|
-
return
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
log('onClose')
|
|
1831
|
-
this.streamObjs.forEach((ele) => ele.finalDrain())
|
|
1832
|
-
|
|
1833
|
-
const error = new WebTransportError('Session closed')
|
|
1834
|
-
|
|
1835
|
-
for (const rej of this.rejectBiDi) rej(error)
|
|
1836
|
-
for (const rej of this.rejectUniDi) rej(error)
|
|
1837
|
-
for (const rej of this.writeDatagramRej) rej(error)
|
|
1838
|
-
for (const rej of this.rejectSessionStats) rej(error)
|
|
1839
|
-
for (const rej of this.rejectDatagramStats) rej(error)
|
|
1840
|
-
|
|
1841
|
-
this.writeDatagramRej = []
|
|
1842
|
-
this.writeDatagramRes = []
|
|
1843
|
-
this.writeDatagramProm = []
|
|
1844
|
-
this.resolveBiDi = []
|
|
1845
|
-
this.resolveUniDi = []
|
|
1846
|
-
this.rejectBiDi = []
|
|
1847
|
-
this.rejectUniDi = []
|
|
1848
|
-
|
|
1849
|
-
this.resolveSessionStats = []
|
|
1850
|
-
this.rejectSessionStats = []
|
|
1851
|
-
this.resolveDatagramStats = []
|
|
1852
|
-
this.rejectDatagramStats = []
|
|
1853
|
-
|
|
1854
|
-
this.incomBiDiController.close()
|
|
1855
|
-
this.incomUniDiController.close()
|
|
1856
|
-
this.incomDatagramController.close()
|
|
1857
|
-
// this.outgoDatagramController.error(errorcode)
|
|
1858
|
-
this.state = 'closed'
|
|
1859
|
-
|
|
1860
|
-
const wtError = new WebTransportError(
|
|
1861
|
-
`Session closed (on process ${pid}) with code ` +
|
|
1862
|
-
args.errorcode +
|
|
1863
|
-
' and reason' +
|
|
1864
|
-
args.error
|
|
1865
|
-
)
|
|
1866
|
-
|
|
1867
|
-
this.sendStreamsController.forEach((ele) => ele.error(wtError))
|
|
1868
|
-
this.receiveStreamsController.forEach((ele) => ele.error(wtError))
|
|
1869
|
-
|
|
1870
|
-
this.streamObjs.forEach((ele) => (ele.readableclosed = true))
|
|
1871
|
-
|
|
1872
|
-
this.sendStreams.clear()
|
|
1873
|
-
this.receiveStreams.clear()
|
|
1874
|
-
this.sendStreamsController.clear()
|
|
1875
|
-
this.receiveStreamsController.clear()
|
|
1876
|
-
this.streamObjs.clear()
|
|
1877
|
-
|
|
1878
|
-
if (this.closedResolve)
|
|
1879
|
-
this.closedResolve({
|
|
1880
|
-
closeCode: args.errorcode,
|
|
1881
|
-
reason: args.error ? args.error : ''
|
|
1882
|
-
})
|
|
1883
|
-
if (this.closeHook) {
|
|
1884
|
-
this.closeHook()
|
|
1885
|
-
delete this.closeHook
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
/**
|
|
1890
|
-
* @param {NewStreamEvent} args
|
|
1891
|
-
*/
|
|
1892
|
-
onStream(args) {
|
|
1893
|
-
const strobj = new HttpWTStream({
|
|
1894
|
-
object: args.stream,
|
|
1895
|
-
parentobj: this,
|
|
1896
|
-
transport: this.parentobj,
|
|
1897
|
-
bidirectional: args.bidirectional,
|
|
1898
|
-
incoming: args.incoming
|
|
1899
|
-
})
|
|
1900
|
-
this.addStreamObj(strobj)
|
|
1901
|
-
if (args.incoming) {
|
|
1902
|
-
if (args.bidirectional) {
|
|
1903
|
-
this.incomBiDiController.enqueue(strobj)
|
|
1904
|
-
} else {
|
|
1905
|
-
this.incomUniDiController.enqueue(strobj.readable)
|
|
1906
|
-
}
|
|
1907
|
-
} else {
|
|
1908
|
-
if (args.bidirectional) {
|
|
1909
|
-
if (this.resolveBiDi.length === 0)
|
|
1910
|
-
throw new Error('Got bidirectional stream without asking for it')
|
|
1911
|
-
this.rejectBiDi.shift()
|
|
1912
|
-
const curres = this.resolveBiDi.shift()
|
|
1913
|
-
|
|
1914
|
-
if (
|
|
1915
|
-
curres != null &&
|
|
1916
|
-
strobj.readable != null &&
|
|
1917
|
-
strobj.writable != null
|
|
1918
|
-
) {
|
|
1919
|
-
curres({
|
|
1920
|
-
readable: strobj.readable,
|
|
1921
|
-
writable: strobj.writable
|
|
1922
|
-
})
|
|
1923
|
-
}
|
|
1924
|
-
} else {
|
|
1925
|
-
if (this.resolveUniDi.length === 0)
|
|
1926
|
-
throw new Error('Got unidirectional stream without asking for it')
|
|
1927
|
-
this.rejectUniDi.shift()
|
|
1928
|
-
const curres = this.resolveUniDi.shift()
|
|
1929
|
-
|
|
1930
|
-
if (curres != null && strobj.writable != null) {
|
|
1931
|
-
curres(strobj.writable)
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
/**
|
|
1938
|
-
* @param {DatagramReceivedEvent} args
|
|
1939
|
-
*/
|
|
1940
|
-
onDatagramReceived(args) {
|
|
1941
|
-
log.trace('datagram received', args.datagram)
|
|
1942
|
-
// console.log('datagram received', args.datagram, Date.now())
|
|
1943
|
-
if (this.incomDatagramController.byobRequest) {
|
|
1944
|
-
/** @type {ReadableStreamBYOBRequest} */
|
|
1945
|
-
const byob = this.incomDatagramController.byobRequest
|
|
1946
|
-
/** @type {Uint8Array} */
|
|
1947
|
-
// @ts-ignore
|
|
1948
|
-
const view = byob?.view
|
|
1949
|
-
// @ts-ignore
|
|
1950
|
-
if (!(view instanceof Uint8Array))
|
|
1951
|
-
throw new Error('byob view is not a Uint8Array')
|
|
1952
|
-
if (view.byteLength < args.datagram.byteLength) {
|
|
1953
|
-
throw new Error('supplied view is not large enough.')
|
|
1954
|
-
}
|
|
1955
|
-
const destview = new Uint8Array(
|
|
1956
|
-
view.buffer,
|
|
1957
|
-
0 + view.byteOffset,
|
|
1958
|
-
args.datagram.byteLength
|
|
1959
|
-
)
|
|
1960
|
-
destview.set(args.datagram)
|
|
1961
|
-
byob.respond(args.datagram.byteLength)
|
|
1962
|
-
}
|
|
1963
|
-
this.incomDatagramController.enqueue(new Uint8Array(args.datagram))
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
/**
|
|
1967
|
-
* @param {DatagramSendEvent} args
|
|
1968
|
-
*/
|
|
1969
|
-
onDatagramSend(args) {
|
|
1970
|
-
if (this.state === 'closed') return
|
|
1971
|
-
this.writeDatagramRej.shift()
|
|
1972
|
-
this.writeDatagramProm.shift()
|
|
1973
|
-
const res = this.writeDatagramRes.shift()
|
|
1974
|
-
|
|
1975
|
-
if (res != null) {
|
|
1976
|
-
res()
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
/**
|
|
1981
|
-
* @param {GoawayReceivedEvent} args
|
|
1982
|
-
*/
|
|
1983
|
-
onGoAwayReceived(args) {
|
|
1984
|
-
if (this.drainingResolve) this.drainingResolve(undefined)
|
|
1985
|
-
this.state = 'draining'
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
const pid = typeof process !== 'undefined' ? process.pid : 0
|
|
1990
|
-
const log = console.log
|
|
1991
|
-
|
|
1992
|
-
/**
|
|
1993
|
-
* @typedef {import('./session').HttpWTSession} HttpWTSession
|
|
1994
|
-
* @typedef {import('./types').HttpClientInit} HttpClientInit
|
|
1995
|
-
*
|
|
1996
|
-
* Http3Client events
|
|
1997
|
-
* @typedef {import('./types').HttpClientEventHandler} HttpClientEventHandler
|
|
1998
|
-
* @typedef {import('./types').ClientConnectedEvent} ClientConnectedEvent
|
|
1999
|
-
* @typedef {import('./types').ClientWebtransportSupportEvent} ClientWebtransportSupportEvent
|
|
2000
|
-
* @typedef {import('./types').HttpWTSessionVisitorEvent} HttpWTSessionVisitorEvent
|
|
2001
|
-
*/
|
|
2002
|
-
|
|
2003
|
-
/**
|
|
2004
|
-
* @implements {HttpClientEventHandler}
|
|
2005
|
-
*/
|
|
2006
|
-
export class HttpClient {
|
|
2007
|
-
/**
|
|
2008
|
-
* @param {HttpClientInit} args
|
|
2009
|
-
*/
|
|
2010
|
-
constructor(args) {
|
|
2011
|
-
/** @type {HttpClientInit| undefined} */
|
|
2012
|
-
this.args = args
|
|
2013
|
-
|
|
2014
|
-
/** @type {{ resolve: (value?: any) => void, reject: (err?: Error) => void} | null | undefined} */
|
|
2015
|
-
this.sessionProm = null
|
|
2016
|
-
|
|
2017
|
-
/** @type {Promise<void> | undefined} */
|
|
2018
|
-
this.sessionobj = new Promise((resolve, reject) => {
|
|
2019
|
-
this.sessionProm = { resolve, reject }
|
|
2020
|
-
}).catch(() => {}) // add default handler if no one cares
|
|
2021
|
-
/** @type {HttpWTSession | null | undefined} */
|
|
2022
|
-
this.sessionobjint = null
|
|
2023
|
-
this.closeHookSession = this.closeHookSession.bind(this)
|
|
2024
|
-
|
|
2025
|
-
/** @type {{ resolve: (value?: any) => void, reject: (err?: Error) => void} | null | undefined} */
|
|
2026
|
-
this.webtransportProm = null
|
|
2027
|
-
/** @type {{ resolve: (value?: any) => void, reject: (err?: Error) => void} | null | undefined} */
|
|
2028
|
-
this.quicconnectedProm = null
|
|
2029
|
-
|
|
2030
|
-
this._quicConnectTimeout = args.quicConnectTimeout ?? 8000
|
|
2031
|
-
this._webTransportConnectTimeout = args.webTransportConnectTimeout ?? 2000
|
|
2032
|
-
}
|
|
2033
|
-
|
|
2034
|
-
/**
|
|
2035
|
-
* @param {Object} args
|
|
2036
|
-
* @param {boolean} args.createTransport
|
|
2037
|
-
* @param {string} args.path
|
|
2038
|
-
*/
|
|
2039
|
-
async handleConnection({ createTransport, path }) {
|
|
2040
|
-
if (createTransport) this.createTransportInt({ path })
|
|
2041
|
-
|
|
2042
|
-
this.quicconnected = new Promise((resolve, reject) => {
|
|
2043
|
-
this.quicconnectedProm = { resolve, reject }
|
|
2044
|
-
})
|
|
2045
|
-
this.webtransport = new Promise((resolve, reject) => {
|
|
2046
|
-
this.webtransportProm = { resolve, reject }
|
|
2047
|
-
})
|
|
2048
|
-
|
|
2049
|
-
const timeout = setTimeout(() => {
|
|
2050
|
-
if (this.quicconnectedProm) {
|
|
2051
|
-
log.error('quic connection timeout')
|
|
2052
|
-
this.quicconnectedProm.reject(
|
|
2053
|
-
new WebTransportError('Opening handshake failed.')
|
|
2054
|
-
)
|
|
2055
|
-
delete this.quicconnectedProm
|
|
2056
|
-
}
|
|
2057
|
-
}, this._quicConnectTimeout)
|
|
2058
|
-
try {
|
|
2059
|
-
await this.quicconnected
|
|
2060
|
-
} finally {
|
|
2061
|
-
clearTimeout(timeout)
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
/**
|
|
2066
|
-
* @param {HttpWTSession} sessionobj
|
|
2067
|
-
* @param {string} path
|
|
2068
|
-
* @returns
|
|
2069
|
-
*/
|
|
2070
|
-
async createWTSession(sessionobj, path) {
|
|
2071
|
-
// now create Webtransport session
|
|
2072
|
-
const timeout = setTimeout(() => {
|
|
2073
|
-
if (this.webtransportProm) {
|
|
2074
|
-
log.error('webtransport connection timeout')
|
|
2075
|
-
this.webtransportProm.reject(
|
|
2076
|
-
new WebTransportError('Opening handshake failed.')
|
|
2077
|
-
)
|
|
2078
|
-
delete this.webtransportProm
|
|
2079
|
-
}
|
|
2080
|
-
}, this._webTransportConnectTimeout)
|
|
2081
|
-
await this.webtransport // wait for webtransport support
|
|
2082
|
-
clearTimeout(timeout)
|
|
2083
|
-
|
|
2084
|
-
// ok now we open the session
|
|
2085
|
-
this.sessionobjint = sessionobj
|
|
2086
|
-
this.transportInt.openWTSession(path)
|
|
2087
|
-
|
|
2088
|
-
// we wait for a new session
|
|
2089
|
-
const sessobj = await this.sessionobj
|
|
2090
|
-
|
|
2091
|
-
delete this.sessionobj
|
|
2092
|
-
|
|
2093
|
-
return sessobj
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
closeHookSession() {
|
|
2097
|
-
if (this.transportInt != null) {
|
|
2098
|
-
this.transportInt.closeClient()
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
this.stopped = true
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
/**
|
|
2105
|
-
* @param {import('./types').SessionCloseEvent} args
|
|
2106
|
-
*/
|
|
2107
|
-
onClientError(args) {
|
|
2108
|
-
if (this.sessionobjint != null) {
|
|
2109
|
-
this.sessionobjint.onClose(args)
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
/**
|
|
2114
|
-
* @param {ClientConnectedEvent} args
|
|
2115
|
-
*/
|
|
2116
|
-
onClientConnected(args) {
|
|
2117
|
-
this.transportIntSwitchToReliable = undefined
|
|
2118
|
-
if (this.quicconnectedProm) {
|
|
2119
|
-
if (args.success) this.quicconnectedProm.resolve()
|
|
2120
|
-
else
|
|
2121
|
-
this.quicconnectedProm.reject(
|
|
2122
|
-
new WebTransportError('Opening handshake failed.')
|
|
2123
|
-
)
|
|
2124
|
-
delete this.quicconnectedProm
|
|
2125
|
-
} else
|
|
2126
|
-
throw new WebTransportError('Client connected with no pending promise')
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
/**
|
|
2130
|
-
* @param {ClientWebtransportSupportEvent} args
|
|
2131
|
-
*/
|
|
2132
|
-
onClientWebTransportSupport(args) {
|
|
2133
|
-
if (this.webtransportProm) {
|
|
2134
|
-
this.webtransportProm.resolve()
|
|
2135
|
-
delete this.webtransportProm
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
/**
|
|
2140
|
-
* @param {HttpWTSessionVisitorEvent} args
|
|
2141
|
-
*/
|
|
2142
|
-
onHttpWTSessionVisitor(args) {
|
|
2143
|
-
// create Http Visitor
|
|
2144
|
-
if (args.session && this.sessionProm && this.sessionobjint) {
|
|
2145
|
-
this.sessionobjint.setSessionObj(args.session, !!args.reliable)
|
|
2146
|
-
args.session.jsobj.closeHook = this.closeHookSession
|
|
2147
|
-
delete this.sessionobjint
|
|
2148
|
-
this.sessionProm.resolve(args.session)
|
|
2149
|
-
delete this.sessionProm
|
|
2150
|
-
} else {
|
|
2151
|
-
throw new WebTransportError(
|
|
2152
|
-
'Http3WTSessionVisitor no object session or nor sessionprom'
|
|
2153
|
-
)
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
/**
|
|
2158
|
-
* @param{{path: string}} [args]
|
|
2159
|
-
**/
|
|
2160
|
-
createTransportInt(args) {
|
|
2161
|
-
const path = args?.path
|
|
2162
|
-
if (this.transportInt != null) {
|
|
2163
|
-
return
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
try {
|
|
2167
|
-
// @ts-ignore
|
|
2168
|
-
if (this.args?.forceReliable || !this.args.createUnreliableClient) {
|
|
2169
|
-
// internal option for unit tests only
|
|
2170
|
-
// @ts-ignore
|
|
2171
|
-
this.transportInt = this.args.createReliableClient(this)
|
|
2172
|
-
} else {
|
|
2173
|
-
// @ts-ignore
|
|
2174
|
-
this.transportInt = this.args.createUnreliableClient(this)
|
|
2175
|
-
if (!this.args?.requireUnreliable) {
|
|
2176
|
-
const args = this.args
|
|
2177
|
-
this.transportIntSwitchToReliable = () => {
|
|
2178
|
-
if (this.transportInt != null) {
|
|
2179
|
-
this.transportInt.closeClient()
|
|
2180
|
-
}
|
|
2181
|
-
// @ts-ignore
|
|
2182
|
-
this.transportInt = args.createReliableClient(this)
|
|
2183
|
-
this.transportInt.jsobj = this
|
|
2184
|
-
if (this.transportInt.createTransport) {
|
|
2185
|
-
this.transportInt.createTransport({ path })
|
|
2186
|
-
}
|
|
2187
|
-
this.transportIntSwitchToReliable = undefined
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
} catch (/** @type {any} */ err) {
|
|
2192
|
-
const error = new WebTransportError('Opening handshake failed.')
|
|
2193
|
-
error.stack = err.stack
|
|
2194
|
-
|
|
2195
|
-
throw error
|
|
2196
|
-
}
|
|
2197
|
-
delete this.args
|
|
2198
|
-
this.transportInt.jsobj = this
|
|
2199
|
-
if (this.transportInt.createTransport) {
|
|
2200
|
-
this.transportInt.createTransport({ path })
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
/**
|
|
2206
|
-
* @typedef {import('./dom').WebTransport} WebTransport
|
|
2207
|
-
* @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo
|
|
2208
|
-
* @typedef {import('./dom').WebTransportBidirectionalStream} WebTransportBidirectionalStream
|
|
2209
|
-
* @typedef {import('./dom').WebTransportReceiveStream} WebTransportReceiveStream
|
|
2210
|
-
* @typedef {import('./error').WebTransportError} WebTransportError
|
|
2211
|
-
*/
|
|
2212
|
-
|
|
2213
|
-
/**
|
|
2214
|
-
* @template T
|
|
2215
|
-
* @typedef {import('node:stream/web').ReadableStream<T>} ReadableStream<T>
|
|
2216
|
-
*/
|
|
2217
|
-
|
|
2218
|
-
/**
|
|
2219
|
-
* @typedef {import('./dom').WebTransport} WebTransportInterface
|
|
2220
|
-
*
|
|
2221
|
-
* @implements {WebTransportInterface}
|
|
2222
|
-
*/
|
|
2223
|
-
export class WebTransportPonyfill extends WebTransportBase {
|
|
2224
|
-
/**
|
|
2225
|
-
* @param{{client: HttpClient, sessionint: HttpWTSession, ourl: URL}} args
|
|
2226
|
-
*/
|
|
2227
|
-
startUpConnection({ client, sessionint, ourl }) {
|
|
2228
|
-
client
|
|
2229
|
-
.handleConnection({ createTransport: true, path: ourl.pathname })
|
|
2230
|
-
.then(() => client.createWTSession(sessionint, ourl.pathname))
|
|
2231
|
-
.catch((error) => {
|
|
2232
|
-
client.closeHookSession()
|
|
2233
|
-
sessionint.readyReject(error)
|
|
2234
|
-
sessionint.closedReject(error)
|
|
2235
|
-
})
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
/**
|
|
2239
|
-
* @param{import('./types.js').HttpWebTransportInit} args
|
|
2240
|
-
* @return {{sessionint: HttpWTSession, client: HttpClient}}
|
|
2241
|
-
*/
|
|
2242
|
-
createClient(args) {
|
|
2243
|
-
const client = new HttpClient({
|
|
2244
|
-
createReliableClient: (client) => {
|
|
2245
|
-
// @ts-ignore
|
|
2246
|
-
return new Http2WebTransportBrowser({ ...args })
|
|
2247
|
-
},
|
|
2248
|
-
...args
|
|
2249
|
-
})
|
|
2250
|
-
const sessionint = new HttpWTSession({
|
|
2251
|
-
/* object: args.session, */
|
|
2252
|
-
parentobj: client
|
|
2253
|
-
})
|
|
2254
|
-
return { client, sessionint }
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
|
-
export class WebTransportPolyfill {
|
|
2259
|
-
/**
|
|
2260
|
-
* @param {string} url
|
|
2261
|
-
* @param {import('./dom.js').WebTransportOptions} [args]
|
|
2262
|
-
*/
|
|
2263
|
-
constructor(url, args) {
|
|
2264
|
-
this.curtype = 'native'
|
|
2265
|
-
this.closeset = false
|
|
2266
|
-
this.allowFallback = true
|
|
2267
|
-
this.initiatedFallback = false
|
|
2268
|
-
this.args = args
|
|
2269
|
-
|
|
2270
|
-
this.closed = new Promise((resolve, reject) => {
|
|
2271
|
-
this.closeRes = resolve
|
|
2272
|
-
this.closeRej = reject
|
|
2273
|
-
})
|
|
2274
|
-
|
|
2275
|
-
this.ready = new Promise((resolve, reject) => {
|
|
2276
|
-
this.readyRes = resolve
|
|
2277
|
-
this.readyRej = reject
|
|
2278
|
-
})
|
|
2279
|
-
|
|
2280
|
-
this.draining = new Promise((resolve, reject) => {
|
|
2281
|
-
this.drainingRes = resolve
|
|
2282
|
-
this.drainingRej = reject
|
|
2283
|
-
})
|
|
2284
|
-
|
|
2285
|
-
/** @type {WebTransport|WebTransportPonyfill} */
|
|
2286
|
-
// @ts-ignore
|
|
2287
|
-
// eslint-disable-next-line no-undef
|
|
2288
|
-
this.curtransport = new WebTransport(url, args)
|
|
2289
|
-
|
|
2290
|
-
const initiateFallback = () => {
|
|
2291
|
-
this.initiatedFallback = true
|
|
2292
|
-
this.curtype = 'websocket'
|
|
2293
|
-
this.curtransport = new WebTransportPonyfill(url, args)
|
|
2294
|
-
this.curtransport.ready
|
|
2295
|
-
.then((val) => this.readyRes(val))
|
|
2296
|
-
.catch((error) => this.readyRej(error))
|
|
2297
|
-
this.curtransport.closed
|
|
2298
|
-
.then((val) => this.closeRes(val))
|
|
2299
|
-
.catch((error) => this.closeRej(error))
|
|
2300
|
-
this.curtransport.draining
|
|
2301
|
-
.then((val) => this.drainingRes(val))
|
|
2302
|
-
.catch((error) => this.drainingRej(error))
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
this.curtransport.ready
|
|
2306
|
-
.then((val) => {
|
|
2307
|
-
this.allowFallback = false
|
|
2308
|
-
this.readyRes(val)
|
|
2309
|
-
})
|
|
2310
|
-
.catch((error) => {
|
|
2311
|
-
if (this.allowFallback && !this.closeset) {
|
|
2312
|
-
if (
|
|
2313
|
-
!this.initiatedFallback &&
|
|
2314
|
-
!this.curtransport?.reliability.supportsReliableOnly // way how browser signals support for http/2, no polyfill needed in this cases
|
|
2315
|
-
) {
|
|
2316
|
-
initiateFallback()
|
|
2317
|
-
}
|
|
2318
|
-
} else {
|
|
2319
|
-
this.readyRej(error)
|
|
2320
|
-
}
|
|
2321
|
-
})
|
|
2322
|
-
this.curtransport.closed
|
|
2323
|
-
.then((val) => {
|
|
2324
|
-
if (this.curtype === 'native') this.closeRes(val)
|
|
2325
|
-
})
|
|
2326
|
-
.catch((error) => {
|
|
2327
|
-
if (this.allowFallback && !this.closeset) {
|
|
2328
|
-
if (
|
|
2329
|
-
!this.initiatedFallback &&
|
|
2330
|
-
!this.curtransport?.reliability?.supportsReliableOnly // way how browser signals support for http/2, no polyfill needed in this cases
|
|
2331
|
-
) {
|
|
2332
|
-
initiateFallback()
|
|
2333
|
-
}
|
|
2334
|
-
} else {
|
|
2335
|
-
this.closeRej(error)
|
|
2336
|
-
}
|
|
2337
|
-
})
|
|
2338
|
-
if (this.curtransport.draining) {
|
|
2339
|
-
// @ts-ignore
|
|
2340
|
-
this.curtransport.draining
|
|
2341
|
-
.then((/** @type {any} */ val) => {
|
|
2342
|
-
if (this.curtype === 'native') this.drainingRes(val)
|
|
2343
|
-
})
|
|
2344
|
-
.catch((/** @type {WebTransportError} */ error) => {
|
|
2345
|
-
if (this.curtype === 'native') this.drainingRej(error)
|
|
2346
|
-
})
|
|
2347
|
-
}
|
|
2348
|
-
/** @type {import('./dom').WebTransportDatagramDuplexStream} */
|
|
2349
|
-
// @ts-ignore
|
|
2350
|
-
this.datagrams = {}
|
|
2351
|
-
// @ts-ignore
|
|
2352
|
-
// eslint-disable-next-line no-undef
|
|
2353
|
-
this.datagrams.readable = new ReadableStream({
|
|
2354
|
-
start: async (controller) => {
|
|
2355
|
-
await this.ready
|
|
2356
|
-
this.datagramsReader = this.curtransport.datagrams.readable.getReader()
|
|
2357
|
-
},
|
|
2358
|
-
pull: async (controller) => {
|
|
2359
|
-
const { value, done } = await this.datagramsReader.read()
|
|
2360
|
-
if (value) controller.enqueue(value)
|
|
2361
|
-
if (done) controller.close()
|
|
2362
|
-
},
|
|
2363
|
-
cancel: async (reason) => {
|
|
2364
|
-
await this.datagramsReader.cancel(reason)
|
|
2365
|
-
}
|
|
2366
|
-
})
|
|
2367
|
-
// @ts-ignore
|
|
2368
|
-
// eslint-disable-next-line no-undef
|
|
2369
|
-
this.datagrams.writable = new WritableStream({
|
|
2370
|
-
start: async (controller) => {
|
|
2371
|
-
await this.ready
|
|
2372
|
-
this.datagramsWriter = this.curtransport.datagrams.writable.getWriter()
|
|
2373
|
-
},
|
|
2374
|
-
write: async (chunk, controller) => {
|
|
2375
|
-
await this.datagramsWriter.write(chunk)
|
|
2376
|
-
},
|
|
2377
|
-
abort: async (reason) => {
|
|
2378
|
-
await this.datagramsWriter.abort(reason)
|
|
2379
|
-
},
|
|
2380
|
-
close: async () => {
|
|
2381
|
-
await this.datagramsWriter.close()
|
|
2382
|
-
}
|
|
2383
|
-
})
|
|
2384
|
-
// eslint-disable-next-line no-undef
|
|
2385
|
-
this.incomingBidirectionalStreams = new ReadableStream({
|
|
2386
|
-
start: async (controller) => {
|
|
2387
|
-
await this.ready
|
|
2388
|
-
this.incomingBidirectionalStreamsReader =
|
|
2389
|
-
this.curtransport.incomingBidirectionalStreams.getReader()
|
|
2390
|
-
},
|
|
2391
|
-
pull: async (controller) => {
|
|
2392
|
-
const { value, done } =
|
|
2393
|
-
await this.incomingBidirectionalStreamsReader.read()
|
|
2394
|
-
if (value) controller.enqueue(value)
|
|
2395
|
-
if (done) controller.close()
|
|
2396
|
-
},
|
|
2397
|
-
cancel: async (reason) => {
|
|
2398
|
-
await this.incomingBidirectionalStreamsReader.cancel(reason)
|
|
2399
|
-
}
|
|
2400
|
-
})
|
|
2401
|
-
// eslint-disable-next-line no-undef
|
|
2402
|
-
this.incomingUnidirectionalStreams = new ReadableStream({
|
|
2403
|
-
start: async (controller) => {
|
|
2404
|
-
await this.ready
|
|
2405
|
-
this.incomingUnidirectionalStreamsReader =
|
|
2406
|
-
this.curtransport.incomingUnidirectionalStreams.getReader()
|
|
2407
|
-
},
|
|
2408
|
-
pull: async (controller) => {
|
|
2409
|
-
const { value, done } =
|
|
2410
|
-
await this.incomingUnidirectionalStreamsReader.read()
|
|
2411
|
-
if (value) controller.enqueue(value)
|
|
2412
|
-
if (done) controller.close()
|
|
2413
|
-
},
|
|
2414
|
-
cancel: async (reason) => {
|
|
2415
|
-
await this.incomingUnidirectionalStreamsReader.cancel(reason)
|
|
2416
|
-
}
|
|
2417
|
-
})
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
get congestionControl() {
|
|
2421
|
-
// @ts-ignore
|
|
2422
|
-
return this.curtransport?.congestionControl || undefined
|
|
2423
|
-
}
|
|
2424
|
-
|
|
2425
|
-
get reliability() {
|
|
2426
|
-
// @ts-ignore
|
|
2427
|
-
return this.curtransport?.reliability || undefined
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
get supportsReliableOnly() {
|
|
2431
|
-
return true
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
|
-
getStats() {
|
|
2435
|
-
// @ts-ignore
|
|
2436
|
-
return this.curtransport.getStats()
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
|
-
/**
|
|
2440
|
-
* @param {WebTransportCloseInfo} [closeinfo]
|
|
2441
|
-
*/
|
|
2442
|
-
close(closeinfo) {
|
|
2443
|
-
this.closeset = true
|
|
2444
|
-
this.curtransport.close(closeinfo)
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2447
|
-
async createBidirectionalStream() {
|
|
2448
|
-
await this.ready
|
|
2449
|
-
return await this.curtransport.createBidirectionalStream()
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
async createUnidirectionalStream() {
|
|
2453
|
-
await this.ready
|
|
2454
|
-
return await this.curtransport.createUnidirectionalStream()
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
// import { WebTransportPolyfill } from '../lib/index.browser'
|
|
2459
|
-
// @ts-nocheck
|
|
2460
|
-
let client
|
|
2461
|
-
|
|
2462
|
-
const afterEach = async () => {
|
|
2463
|
-
if (client != null) {
|
|
2464
|
-
client.close()
|
|
2465
|
-
client = undefined
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
class TimeoutError extends Error {
|
|
2470
|
-
/**
|
|
2471
|
-
* @param {string} message
|
|
2472
|
-
*/
|
|
2473
|
-
constructor(message) {
|
|
2474
|
-
super(message)
|
|
2475
|
-
|
|
2476
|
-
this.name = this[Symbol.toStringTag] = 'TimeoutError'
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
async function pTimeout(promise, timeout) {
|
|
2481
|
-
let ref
|
|
2482
|
-
|
|
2483
|
-
const value = await Promise.race([
|
|
2484
|
-
promise,
|
|
2485
|
-
new Promise((resolve, reject) => {
|
|
2486
|
-
ref = setTimeout(() => {
|
|
2487
|
-
reject(new TimeoutError('timeout'))
|
|
2488
|
-
}, timeout)
|
|
2489
|
-
})
|
|
2490
|
-
])
|
|
2491
|
-
|
|
2492
|
-
clearTimeout(ref)
|
|
2493
|
-
|
|
2494
|
-
return value
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
async function getReaderValue(readableStream) {
|
|
2498
|
-
const reader = readableStream.getReader()
|
|
2499
|
-
|
|
2500
|
-
try {
|
|
2501
|
-
const { done, value } = await reader.read()
|
|
2502
|
-
|
|
2503
|
-
if (done) {
|
|
2504
|
-
throw new Error('Stream ended')
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
if (!value) {
|
|
2508
|
-
throw new Error('Stream value was undefined')
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
return value
|
|
2512
|
-
} finally {
|
|
2513
|
-
reader.releaseLock()
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
async function readStream(readable, expected) {
|
|
2518
|
-
const reader = readable.getReader()
|
|
2519
|
-
|
|
2520
|
-
try {
|
|
2521
|
-
/** @type {T[]} */
|
|
2522
|
-
const output = []
|
|
2523
|
-
|
|
2524
|
-
while (true) {
|
|
2525
|
-
const { done, value } = await reader.read()
|
|
2526
|
-
|
|
2527
|
-
if (done) {
|
|
2528
|
-
break
|
|
2529
|
-
}
|
|
2530
|
-
|
|
2531
|
-
if (value != null) {
|
|
2532
|
-
output.push(value)
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
|
-
if (expected != null && output.length === expected) {
|
|
2536
|
-
break
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
return output
|
|
2541
|
-
} finally {
|
|
2542
|
-
reader.releaseLock()
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
const KNOWN_BYTES = [
|
|
2547
|
-
Uint8Array.from([0, 1, 2, 3, 4]),
|
|
2548
|
-
Uint8Array.from([5, 6, 7, 8, 9]),
|
|
2549
|
-
Uint8Array.from([10, 11, 12, 13, 14]),
|
|
2550
|
-
Uint8Array.from([15, 16, 17, 18, 19]),
|
|
2551
|
-
Uint8Array.from([20, 21, 22, 23, 24])
|
|
2552
|
-
]
|
|
2553
|
-
|
|
2554
|
-
function readCertHash(certHash) {
|
|
2555
|
-
return Uint8Array.from(`${certHash}`.split(':').map((i) => parseInt(i, 16)))
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
async function writeStream(writable, input) {
|
|
2559
|
-
const writer = writable.getWriter()
|
|
2560
|
-
|
|
2561
|
-
for (const buf of input) {
|
|
2562
|
-
await writer.ready
|
|
2563
|
-
await writer.write(buf)
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
|
-
await writer.ready
|
|
2567
|
-
await writer.releaseLock()
|
|
2568
|
-
await writable.close()
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
async function main() {
|
|
2572
|
-
{
|
|
2573
|
-
client = new WebTransportPolyfill(
|
|
2574
|
-
`${SERVER_URL}/bidirectional_client_initiated_echo`,
|
|
2575
|
-
{
|
|
2576
|
-
serverCertificateHashes: [
|
|
2577
|
-
{
|
|
2578
|
-
algorithm: 'sha-256',
|
|
2579
|
-
value: readCertHash(CERT_HASH)
|
|
2580
|
-
}
|
|
2581
|
-
]
|
|
2582
|
-
}
|
|
2583
|
-
)
|
|
2584
|
-
await client.ready
|
|
2585
|
-
|
|
2586
|
-
const stream = await client.createBidirectionalStream()
|
|
2587
|
-
await writeStream(stream.writable, KNOWN_BYTES)
|
|
2588
|
-
|
|
2589
|
-
await readStream(stream.readable, KNOWN_BYTES.length)
|
|
2590
|
-
}
|
|
2591
|
-
console.log('test 1 complete')
|
|
2592
|
-
await afterEach()
|
|
2593
|
-
{
|
|
2594
|
-
// client context - waits for the server to open a bidi stream then pipes it back to them
|
|
2595
|
-
client = new WebTransportPolyfill(
|
|
2596
|
-
`${SERVER_URL}/bidirectional_server_initiated_echo`,
|
|
2597
|
-
{
|
|
2598
|
-
serverCertificateHashes: [
|
|
2599
|
-
{
|
|
2600
|
-
algorithm: 'sha-256',
|
|
2601
|
-
value: readCertHash(CERT_HASH)
|
|
2602
|
-
}
|
|
2603
|
-
]
|
|
2604
|
-
}
|
|
2605
|
-
)
|
|
2606
|
-
await client.ready
|
|
2607
|
-
|
|
2608
|
-
const bidiStream = await getReaderValue(client.incomingBidirectionalStreams)
|
|
2609
|
-
|
|
2610
|
-
// redirect input to output
|
|
2611
|
-
await bidiStream.readable.pipeTo(bidiStream.writable)
|
|
2612
|
-
|
|
2613
|
-
// the remote will close the session
|
|
2614
|
-
await client.closed
|
|
2615
|
-
}
|
|
2616
|
-
console.log('test 2 complete')
|
|
2617
|
-
await afterEach()
|
|
2618
|
-
{
|
|
2619
|
-
// client context - connects to the server, sends some datagrams and reads the response
|
|
2620
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/datagrams_client_send`, {
|
|
2621
|
-
serverCertificateHashes: [
|
|
2622
|
-
{
|
|
2623
|
-
algorithm: 'sha-256',
|
|
2624
|
-
value: readCertHash(CERT_HASH)
|
|
2625
|
-
}
|
|
2626
|
-
]
|
|
2627
|
-
})
|
|
2628
|
-
await client.ready
|
|
2629
|
-
|
|
2630
|
-
const writer = client.datagrams.writable.getWriter()
|
|
2631
|
-
let closed = false
|
|
2632
|
-
|
|
2633
|
-
// write datagrams until the server receives one and closes the connection
|
|
2634
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
2635
|
-
Promise.resolve().then(async () => {
|
|
2636
|
-
// eslint-disable-next-line no-unmodified-loop-condition
|
|
2637
|
-
while (!closed) {
|
|
2638
|
-
try {
|
|
2639
|
-
await writer.ready
|
|
2640
|
-
await writer.write(Uint8Array.from([0, 1, 2, 3, 4]))
|
|
2641
|
-
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
2642
|
-
} catch {
|
|
2643
|
-
// the session can be closed while we are writing
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
})
|
|
2647
|
-
|
|
2648
|
-
await client.closed
|
|
2649
|
-
closed = true
|
|
2650
|
-
}
|
|
2651
|
-
console.log('test 3 complete')
|
|
2652
|
-
await afterEach()
|
|
2653
|
-
{
|
|
2654
|
-
// client context - pipes the server's datagrams back to them
|
|
2655
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/datagrams_server_send`, {
|
|
2656
|
-
serverCertificateHashes: [
|
|
2657
|
-
{
|
|
2658
|
-
algorithm: 'sha-256',
|
|
2659
|
-
value: readCertHash(CERT_HASH)
|
|
2660
|
-
}
|
|
2661
|
-
]
|
|
2662
|
-
})
|
|
2663
|
-
await client.ready
|
|
2664
|
-
|
|
2665
|
-
// datagram transport is unreliable, at least one message should make it through
|
|
2666
|
-
const expected = 1
|
|
2667
|
-
|
|
2668
|
-
await pTimeout(readStream(client.datagrams.readable, expected), 1000)
|
|
2669
|
-
}
|
|
2670
|
-
console.log('test 4 complete')
|
|
2671
|
-
await afterEach()
|
|
2672
|
-
|
|
2673
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/session_close`, {
|
|
2674
|
-
serverCertificateHashes: [
|
|
2675
|
-
{
|
|
2676
|
-
algorithm: 'sha-256',
|
|
2677
|
-
value: readCertHash(CERT_HASH)
|
|
2678
|
-
}
|
|
2679
|
-
]
|
|
2680
|
-
})
|
|
2681
|
-
await client.ready
|
|
2682
|
-
|
|
2683
|
-
await client.closed
|
|
2684
|
-
console.log('test 5 complete')
|
|
2685
|
-
|
|
2686
|
-
await afterEach()
|
|
2687
|
-
|
|
2688
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/session_close_with_reason`, {
|
|
2689
|
-
serverCertificateHashes: [
|
|
2690
|
-
{
|
|
2691
|
-
algorithm: 'sha-256',
|
|
2692
|
-
value: readCertHash(CERT_HASH)
|
|
2693
|
-
}
|
|
2694
|
-
]
|
|
2695
|
-
})
|
|
2696
|
-
await client.ready
|
|
2697
|
-
|
|
2698
|
-
await client.closed
|
|
2699
|
-
console.log('test 6 complete')
|
|
2700
|
-
await afterEach()
|
|
2701
|
-
|
|
2702
|
-
client = new WebTransportPolyfill(`https://127.0.0.1:39821`, {
|
|
2703
|
-
serverCertificateHashes: [
|
|
2704
|
-
{
|
|
2705
|
-
algorithm: 'sha-256',
|
|
2706
|
-
value: readCertHash(CERT_HASH)
|
|
2707
|
-
}
|
|
2708
|
-
],
|
|
2709
|
-
quicConnectTimeout: 100,
|
|
2710
|
-
webTransportConnectTimeout: 100
|
|
2711
|
-
})
|
|
2712
|
-
|
|
2713
|
-
await Promise.all([
|
|
2714
|
-
client.closed.catch((err) => err),
|
|
2715
|
-
client.ready.catch((err) => err)
|
|
2716
|
-
])
|
|
2717
|
-
console.log('test 7 complete')
|
|
2718
|
-
await afterEach()
|
|
2719
|
-
|
|
2720
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/non_existant`, {
|
|
2721
|
-
serverCertificateHashes: [
|
|
2722
|
-
{
|
|
2723
|
-
algorithm: 'sha-256',
|
|
2724
|
-
value: readCertHash(CERT_HASH)
|
|
2725
|
-
}
|
|
2726
|
-
]
|
|
2727
|
-
})
|
|
2728
|
-
|
|
2729
|
-
await Promise.all([
|
|
2730
|
-
client.closed.catch((err) => err),
|
|
2731
|
-
client.ready.catch((err) => err)
|
|
2732
|
-
])
|
|
2733
|
-
console.log('test 8 complete')
|
|
2734
|
-
await afterEach()
|
|
2735
|
-
|
|
2736
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/session_close`, {
|
|
2737
|
-
serverCertificateHashes: [
|
|
2738
|
-
{
|
|
2739
|
-
algorithm: 'sha-256',
|
|
2740
|
-
value: readCertHash(CERT_HASH + ':DE:AD:BE:EF')
|
|
2741
|
-
}
|
|
2742
|
-
]
|
|
2743
|
-
})
|
|
2744
|
-
|
|
2745
|
-
await Promise.all([
|
|
2746
|
-
client.closed.catch((err) => err),
|
|
2747
|
-
client.ready.catch((err) => err)
|
|
2748
|
-
])
|
|
2749
|
-
console.log('test 10 complete')
|
|
2750
|
-
await afterEach()
|
|
2751
|
-
client = new WebTransportPolyfill(`${SERVER_URL}/session_close`, {
|
|
2752
|
-
serverCertificateHashes: [
|
|
2753
|
-
{
|
|
2754
|
-
algorithm: 'sha-256',
|
|
2755
|
-
value: readCertHash('DE:AD:BE:EF:' + CERT_HASH?.substring(12))
|
|
2756
|
-
}
|
|
2757
|
-
]
|
|
2758
|
-
})
|
|
2759
|
-
|
|
2760
|
-
await Promise.all([
|
|
2761
|
-
client.closed.catch((err) => err),
|
|
2762
|
-
client.ready.catch((err) => err)
|
|
2763
|
-
])
|
|
2764
|
-
console.log('test 11 complete')
|
|
2765
|
-
await afterEach()
|
|
2766
|
-
{
|
|
2767
|
-
// client context - connects to the server, opens a bidi stream, sends some data and reads the response
|
|
2768
|
-
try {
|
|
2769
|
-
console.log('unidirectional mark1')
|
|
2770
|
-
client = new WebTransportPolyfill(
|
|
2771
|
-
`${SERVER_URL}/unidirectional_client_send`,
|
|
2772
|
-
{
|
|
2773
|
-
serverCertificateHashes: [
|
|
2774
|
-
{
|
|
2775
|
-
algorithm: 'sha-256',
|
|
2776
|
-
value: readCertHash(CERT_HASH)
|
|
2777
|
-
}
|
|
2778
|
-
]
|
|
2779
|
-
}
|
|
2780
|
-
)
|
|
2781
|
-
console.log('unidirectional mark2')
|
|
2782
|
-
await client.ready
|
|
2783
|
-
console.log('unidirectional mark3')
|
|
2784
|
-
} catch (error) {
|
|
2785
|
-
console.log('Peak unidirectional error:', error)
|
|
2786
|
-
throw error
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
const stream = await client.createUnidirectionalStream()
|
|
2790
|
-
await writeStream(stream, KNOWN_BYTES)
|
|
2791
|
-
|
|
2792
|
-
// the remote will close the session
|
|
2793
|
-
await client.closed
|
|
2794
|
-
}
|
|
2795
|
-
console.log('test 12 complete')
|
|
2796
|
-
await afterEach()
|
|
2797
|
-
{
|
|
2798
|
-
client = new WebTransportPolyfill(
|
|
2799
|
-
`${SERVER_URL}/unidirectional_server_send`,
|
|
2800
|
-
{
|
|
2801
|
-
serverCertificateHashes: [
|
|
2802
|
-
{
|
|
2803
|
-
algorithm: 'sha-256',
|
|
2804
|
-
value: readCertHash(CERT_HASH)
|
|
2805
|
-
}
|
|
2806
|
-
]
|
|
2807
|
-
}
|
|
2808
|
-
)
|
|
2809
|
-
await client.ready
|
|
2810
|
-
|
|
2811
|
-
const stream = await getReaderValue(client.incomingUnidirectionalStreams)
|
|
2812
|
-
await readStream(stream, KNOWN_BYTES.length)
|
|
2813
|
-
}
|
|
2814
|
-
console.log('test 13 complete')
|
|
2815
|
-
await afterEach()
|
|
2816
|
-
{
|
|
2817
|
-
client = new WebTransportPolyfill(
|
|
2818
|
-
`${SERVER_URL}/unidirectional_server_delay_before_read`,
|
|
2819
|
-
{
|
|
2820
|
-
serverCertificateHashes: [
|
|
2821
|
-
{
|
|
2822
|
-
algorithm: 'sha-256',
|
|
2823
|
-
value: readCertHash(CERT_HASH)
|
|
2824
|
-
}
|
|
2825
|
-
]
|
|
2826
|
-
}
|
|
2827
|
-
)
|
|
2828
|
-
await client.ready
|
|
2829
|
-
|
|
2830
|
-
const clientStream = await client.createUnidirectionalStream()
|
|
2831
|
-
|
|
2832
|
-
const writer = clientStream.getWriter()
|
|
2833
|
-
|
|
2834
|
-
for (const buf of KNOWN_BYTES) {
|
|
2835
|
-
await writer.ready
|
|
2836
|
-
await writer.write(buf)
|
|
2837
|
-
}
|
|
2838
|
-
|
|
2839
|
-
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
2840
|
-
|
|
2841
|
-
await writer.ready
|
|
2842
|
-
await writer.close()
|
|
2843
|
-
|
|
2844
|
-
// the remote will close the session cleanly if everything was ok
|
|
2845
|
-
await client.closed
|
|
2846
|
-
}
|
|
2847
|
-
console.log('test 14 complete')
|
|
2848
|
-
await afterEach()
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
main().catch((error) => {
|
|
2852
|
-
console.log('main error: ', error.toString())
|
|
2853
|
-
console.dir(error)
|
|
2854
|
-
})
|