@fails-components/webtransport 0.0.9 → 0.1.0-macarmbuild.6

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.
Files changed (90) hide show
  1. package/CMakeLists.txt +110 -73
  2. package/build.js +32 -8
  3. package/dist/lib/client.d.ts +74 -0
  4. package/dist/lib/client.d.ts.map +1 -0
  5. package/dist/lib/dom.d.ts +70 -0
  6. package/dist/lib/dom.d.ts.map +1 -0
  7. package/dist/lib/event-loop.d.ts +26 -0
  8. package/dist/lib/event-loop.d.ts.map +1 -0
  9. package/dist/lib/index.d.ts +79 -0
  10. package/dist/lib/index.d.ts.map +1 -0
  11. package/dist/lib/native.d.ts +2 -0
  12. package/dist/lib/native.d.ts.map +1 -0
  13. package/dist/lib/server.d.ts +75 -0
  14. package/dist/lib/server.d.ts.map +1 -0
  15. package/dist/lib/session.d.ts +221 -0
  16. package/dist/lib/session.d.ts.map +1 -0
  17. package/dist/lib/stream.d.ts +116 -0
  18. package/dist/lib/stream.d.ts.map +1 -0
  19. package/dist/lib/transport.d.ts +25 -0
  20. package/dist/lib/transport.d.ts.map +1 -0
  21. package/dist/lib/types.d.ts +157 -0
  22. package/dist/lib/types.d.ts.map +1 -0
  23. package/dist/lib/utils.d.ts +6 -0
  24. package/dist/lib/utils.d.ts.map +1 -0
  25. package/dist/lib/webtransport.d.ts +35 -0
  26. package/dist/lib/webtransport.d.ts.map +1 -0
  27. package/dist/test/bidirectional-streams.spec.d.ts +5 -0
  28. package/dist/test/bidirectional-streams.spec.d.ts.map +1 -0
  29. package/dist/test/datagrams.spec.d.ts +5 -0
  30. package/dist/test/datagrams.spec.d.ts.map +1 -0
  31. package/dist/test/echoclient.d.ts +2 -0
  32. package/dist/test/echoclient.d.ts.map +1 -0
  33. package/dist/test/echoserver.d.ts +2 -0
  34. package/dist/test/echoserver.d.ts.map +1 -0
  35. package/dist/test/event-loop.spec.d.ts +2 -0
  36. package/dist/test/event-loop.spec.d.ts.map +1 -0
  37. package/dist/test/fixtures/certificate.d.ts +21 -0
  38. package/dist/test/fixtures/certificate.d.ts.map +1 -0
  39. package/dist/test/fixtures/chai.d.ts +2 -0
  40. package/dist/test/fixtures/chai.d.ts.map +1 -0
  41. package/dist/test/fixtures/read-stream.d.ts +10 -0
  42. package/dist/test/fixtures/read-stream.d.ts.map +1 -0
  43. package/dist/test/fixtures/reader-value.d.ts +7 -0
  44. package/dist/test/fixtures/reader-value.d.ts.map +1 -0
  45. package/dist/test/fixtures/server.d.ts +10 -0
  46. package/dist/test/fixtures/server.d.ts.map +1 -0
  47. package/dist/test/fixtures/write-stream.d.ts +10 -0
  48. package/dist/test/fixtures/write-stream.d.ts.map +1 -0
  49. package/dist/test/test.d.ts +2 -0
  50. package/dist/test/test.d.ts.map +1 -0
  51. package/dist/test/testsuite.d.ts +25 -0
  52. package/dist/test/testsuite.d.ts.map +1 -0
  53. package/dist/test/unidirectional-streams.spec.d.ts +5 -0
  54. package/dist/test/unidirectional-streams.spec.d.ts.map +1 -0
  55. package/lib/client.js +163 -0
  56. package/lib/dom.ts +95 -0
  57. package/lib/event-loop.js +79 -0
  58. package/lib/index.js +33 -0
  59. package/lib/native.js +25 -0
  60. package/lib/server.js +165 -0
  61. package/lib/session.js +422 -0
  62. package/lib/stream.js +325 -0
  63. package/lib/transport.js +42 -0
  64. package/lib/types.ts +181 -0
  65. package/lib/utils.js +22 -0
  66. package/lib/webtransport.js +114 -0
  67. package/package.json +18 -6
  68. package/readme.md +28 -5
  69. package/src/http3client.h +0 -1
  70. package/src/http3eventloop.cc +52 -0
  71. package/src/http3eventloop.h +22 -1
  72. package/src/http3server.cc +4 -1
  73. package/src/http3server.h +8 -0
  74. package/src/http3serversession.h +1 -1
  75. package/test/bidirectional-streams.spec.js +138 -0
  76. package/test/datagrams.spec.js +132 -0
  77. package/test/echoclient.js +10 -6
  78. package/test/echoserver.js +6 -8
  79. package/test/event-loop.spec.js +24 -0
  80. package/test/{certificate.js → fixtures/certificate.js} +31 -15
  81. package/test/fixtures/chai.js +6 -0
  82. package/test/fixtures/read-stream.js +36 -0
  83. package/test/fixtures/reader-value.js +24 -0
  84. package/test/fixtures/server.js +58 -0
  85. package/test/fixtures/write-stream.js +17 -0
  86. package/test/test.js +29 -3
  87. package/test/testsuite.js +22 -0
  88. package/test/unidirectional-streams.spec.js +128 -0
  89. package/tsconfig.json +40 -0
  90. package/src/webtransport.js +0 -870
@@ -1,870 +0,0 @@
1
- /* eslint-disable no-prototype-builtins */
2
- // Copyright (c) 2022 Marten Richter or other contributers (see commit). All rights reserved.
3
- // Use of this source code is governed by a BSD-style license that can be
4
- // found in the LICENSE file.
5
-
6
- import { existsSync } from 'fs'
7
- import { createRequire } from 'module'
8
- import { ReadableStream, WritableStream } from 'node:stream/web'
9
- import * as path from 'path'
10
- import * as url from 'url'
11
- import { arch, platform } from 'node:process'
12
-
13
- const binplatform = platform + '_' + arch
14
-
15
- const require = createRequire(import.meta.url)
16
- const dirname = url.fileURLToPath(new URL('.', import.meta.url))
17
- let buildpath = '../build_' + binplatform
18
- if (!existsSync(path.join(dirname, buildpath))) buildpath = '../build' // use precompiled only if own compilation does not exis
19
-
20
- let wtpath = buildpath + '/Release/webtransport.node'
21
- if (
22
- process.env.NODE_ENV !== 'production' &&
23
- existsSync(path.join(dirname, buildpath + '/Debug/webtransport.node'))
24
- ) {
25
- wtpath = buildpath + '/Debug/webtransport.node'
26
- }
27
- console.log('load webtransport binary:', wtpath)
28
-
29
- const wtrouter = require(wtpath)
30
-
31
- class Http3WTStream {
32
- constructor(args) {
33
- this.objint = args.object
34
- this.objint.jsobj = this
35
- this.parentobj = args.parentobj
36
- this.transport = args.transport
37
- this.bidirectional = args.bidirectional
38
- this.incoming = args.incoming
39
- this.closed = false
40
-
41
- this.pendingoperation = null
42
- this.pendingres = null
43
-
44
- if (this.bidirectional || this.incoming) {
45
- this.readable = new ReadableStream(
46
- {
47
- start: (controller) => {
48
- this.readableController = controller
49
- this.parentobj.addReceiveStream(this.readable, controller)
50
- this.objint.startReading()
51
- },
52
- pull: (controller) => {
53
- if (this.readableclosed) {
54
- return Promise.resolve()
55
- }
56
- this.objint.startReading()
57
- },
58
- cancel: (reason) => {
59
- const promise = new Promise((resolve, reject) => {
60
- this.cancelres = resolve
61
- })
62
- let code = 0
63
- if (reason && reason.code) {
64
- if (reason.code < 0) code = 0
65
- else if (reason.code > 255) code = 255
66
- else code = reason.code
67
- }
68
- this.readableclosed = true
69
- this.objint.stopSending(code)
70
- return promise
71
- }
72
- },
73
- { highWaterMark: 4 }
74
- )
75
- }
76
- if (this.bidirectional || !this.incoming) {
77
- this.writable = new WritableStream(
78
- {
79
- start: (controller) => {
80
- this.writableController = controller
81
- this.parentobj.addSendStream(this.writable, controller)
82
- },
83
- write: (chunk, controller) => {
84
- if (this.writableclosed) {
85
- return Promise.resolve()
86
- }
87
- if (chunk instanceof Uint8Array) {
88
- this.pendingoperation = new Promise((resolve, reject) => {
89
- this.pendingres = resolve
90
- })
91
- const dataprom = this.parentobj.waitForDatagramsSend()
92
- dataprom.finally(() => {
93
- this.objint.writeChunk(chunk)
94
- })
95
- return this.pendingoperation
96
- } else {
97
- console.log('chunk info:', chunk)
98
- throw new Error('chunk is not of instanceof Uint8Array ')
99
- }
100
- },
101
- close: (controller) => {
102
- if (this.writableclosed) {
103
- return Promise.resolve()
104
- }
105
- this.objint.streamFinal()
106
- this.pendingoperation = new Promise((resolve, reject) => {
107
- this.pendingres = resolve
108
- })
109
- return this.pendingoperation
110
- },
111
- abort: (reason) => {
112
- if (this.writableclosed) {
113
- return new Promise((resolve, reject) => {
114
- resolve()
115
- })
116
- }
117
- let code = 0
118
- if (reason && reason.code) {
119
- if (reason.code < 0) code = 0
120
- else if (reason.code > 255) code = 255
121
- else code = reason.code
122
- }
123
- const promise = new Promise((resolve, reject) => {
124
- this.abortres = resolve
125
- })
126
- this.objint.resetStream(code)
127
- return promise
128
- }
129
- },
130
- { highWaterMark: 4 }
131
- )
132
- }
133
- }
134
-
135
- onStreamRecvSignal(args) {
136
- // console.log('onStreamRecvSignal', args)
137
- // check if transport is closed
138
- const parentstate = this.parentobj.state
139
- if (parentstate === 'closed' || parentstate === 'failed') return
140
- switch (args.nettask) {
141
- case 'resetStream':
142
- if (this.readable) {
143
- this.parentobj.removeReceiveStream(
144
- this.readable,
145
- this.readableController
146
- )
147
- this.readableclosed = true
148
- this.readableController.error(args.code || 0)
149
- } else console.log('stopSending wihtout readable')
150
- break
151
-
152
- case 'stopSending':
153
- if (this.writable) {
154
- this.parentobj.removeSendStream(
155
- this.writable,
156
- this.writableController
157
- )
158
-
159
- this.writableclosed = true
160
- this.writableController.error(args.code || 0)
161
- } else console.log('stopSending wihtout writable')
162
- break
163
- default:
164
- console.log('unhandled onStreamRecvSignal')
165
- }
166
-
167
- if (this.pendingoperation) {
168
- const res = this.pendingres
169
- this.pendingoperation = null
170
- this.pendingres = null
171
- res()
172
- }
173
- }
174
-
175
- onStreamRead(args) {
176
- if (args.data && args.data.length && !this.readableclosed) {
177
- // console.log('stream read received', args.data, Date.now())
178
- this.readableController.enqueue(args.data)
179
- if (this.readableController.desiredSize < 0) this.objint.stopReading()
180
- }
181
- if (args.fin) {
182
- if (this.cancelres) {
183
- const res = this.cancelres
184
- this.cancelres = null
185
- res()
186
- }
187
- if (!this.readableclosed) {
188
- this.readableController.close()
189
- this.readableclosed = true
190
- }
191
- }
192
- }
193
-
194
- onStreamWrite(args) {
195
- // we ignore success
196
- if (this.pendingoperation) {
197
- const res = this.pendingres
198
- this.pendingoperation = null
199
- this.pendingres = null
200
- res()
201
- }
202
- }
203
-
204
- onStreamReset(args) {
205
- if (this.abortres) {
206
- this.abortres()
207
- if (this.readable)
208
- this.parentobj.removeReceiveStream(
209
- this.readable,
210
- this.readableController
211
- )
212
- if (this.writable)
213
- this.parentobj.removeSendStream(this.writable, this.writableController)
214
- this.readableclosed = true
215
- this.parentobj.removeStreamObj(this)
216
- }
217
- }
218
-
219
- onStreamNetworkFinish(args) {
220
- // console.log('networkfinish args', args)
221
- switch (args.nettask) {
222
- case 'stopSending':
223
- if (this.cancelres) {
224
- const res = this.cancelres
225
- this.cancelres = null
226
- res()
227
- }
228
- this.stopSendingRecv = true
229
- break
230
- case 'resetStream':
231
- if (this.abortres) {
232
- const res = this.abortres
233
- this.abortres = null
234
- res()
235
- }
236
-
237
- break
238
-
239
- case 'streamFinal':
240
- if (this.pendingoperation) {
241
- const res = this.pendingres
242
- this.pendingoperation = null
243
- this.pendingres = null
244
- res()
245
- }
246
- break
247
- default:
248
- console.log('onStreamNetworkFinish unknown task')
249
- }
250
- // we could differentiate....
251
- }
252
-
253
- static callback(args) {
254
- // console.log('Stream callback called', args)
255
- if (!args || !args.object || !args.object.jsobj)
256
- throw new Error('Stream callback without jsobj')
257
- const visitor = args.object.jsobj
258
- if (args.purpose) {
259
- switch (args.purpose) {
260
- case 'StreamRecvSignal':
261
- visitor.onStreamRecvSignal(args)
262
- break
263
- case 'StreamRead':
264
- if (visitor && args.hasOwnProperty('data')) {
265
- visitor.onStreamRead(args)
266
- } else {
267
- console.log('Stream callback called', visitor, args)
268
- throw new Error('Malformed StreamRead')
269
- }
270
- break
271
- case 'StreamWrite':
272
- visitor.onStreamWrite(args)
273
- break
274
- case 'StreamReset':
275
- visitor.onStreamReset(args)
276
- break
277
- case 'StreamNetworkFinish':
278
- visitor.onStreamNetworkFinish(args)
279
-
280
- break
281
- default: {
282
- throw new Error('unknown purpose Streamcb')
283
- }
284
- }
285
- } else throw new Error('no purpose Streamcb')
286
- }
287
- }
288
-
289
- class Http3WTSession {
290
- constructor(args) {
291
- if (args.object) {
292
- this.objint = args.object
293
- this.objint.jsobj = this
294
- }
295
- this.parentobj = args.parentobj
296
- this.state = 'connected'
297
-
298
- this.ready = new Promise((resolve, reject) => {
299
- this.readyResolve = resolve
300
- this.readyReject = reject
301
- }).catch(() => {}) // add default handler if no one cares
302
- this.closed = new Promise((resolve, reject) => {
303
- this.closedResolve = resolve
304
- this.closedReject = reject
305
- }).catch(() => {}) // add default handler if no one cares
306
-
307
- this.incomingBidirectionalStreams = new ReadableStream({
308
- start: (controller) => {
309
- this.incomBiDiController = controller
310
- }
311
- })
312
-
313
- this.incomingUnidirectionalStreams = new ReadableStream({
314
- start: (controller) => {
315
- this.incomUniDiController = controller
316
- }
317
- })
318
-
319
- this.datagrams = {}
320
- this.datagrams.readable = new ReadableStream({
321
- start: (controller) => {
322
- this.incomDatagramController = controller
323
- }
324
- })
325
- this.writeDatagramRes = []
326
- this.writeDatagramRej = []
327
- this.writeDatagramProm = []
328
- this.datagrams.writable = new WritableStream({
329
- start: (controller) => {
330
- this.outgoDatagramController = controller
331
- },
332
- write: (chunk, controller) => {
333
- if (this.state === 'closed') throw new Error('Session is closed')
334
- if (chunk instanceof Uint8Array) {
335
- const ret = new Promise((resolve, reject) => {
336
- this.writeDatagramRes.push(resolve)
337
- this.writeDatagramRej.push(reject)
338
- })
339
- this.writeDatagramProm.push(ret)
340
- // console.log('b4 datagram write', chunk, Date.now())
341
- this.objint.writeDatagram(chunk)
342
- return ret
343
- } else throw new Error('chunk is not of type Uint8Array')
344
- },
345
- close: (controller) => {
346
- // do nothing
347
- }
348
- })
349
-
350
- this.resolveBiDi = []
351
- this.resolveUniDi = []
352
- this.rejectBiDi = []
353
- this.rejectUniDi = []
354
-
355
- this.sendStreams = new Set()
356
- this.receiveStreams = new Set()
357
- this.streamObjs = new Set()
358
-
359
- this.sendStreamsController = new Set()
360
- this.receiveStreamsController = new Set()
361
- }
362
-
363
- setSessionObj(object) {
364
- if (object) {
365
- this.objint = object
366
- this.objint.jsobj = this
367
- }
368
- }
369
-
370
- async waitForDatagramsSend() {
371
- while (this.writeDatagramProm.length > 0) {
372
- try {
373
- await Promise.allSettled(this.writeDatagramProm)
374
- } catch (error) {
375
- console.log('datagram promise failed ', error)
376
- }
377
- }
378
- }
379
-
380
- addStreamObj(stream) {
381
- this.streamObjs.add(stream)
382
- }
383
-
384
- removeStreamObj(stream) {
385
- this.streamObjs.delete(stream)
386
- }
387
-
388
- addSendStream(stream, controller) {
389
- this.sendStreams.add(stream)
390
- this.sendStreamsController.add(controller)
391
- }
392
-
393
- removeSendStream(stream, controller) {
394
- this.sendStreams.delete(stream)
395
- this.sendStreamsController.delete(controller)
396
- }
397
-
398
- addReceiveStream(stream, controller) {
399
- this.receiveStreams.add(stream)
400
- this.receiveStreamsController.add(controller)
401
- }
402
-
403
- removeReceiveStream(stream, controller) {
404
- this.receiveStreams.delete(stream)
405
- this.receiveStreamsController.delete(controller)
406
- }
407
-
408
- createBidirectionalStream() {
409
- const prom = new Promise((resolve, reject) => {
410
- this.resolveBiDi.push(resolve)
411
- this.rejectBiDi.push(reject)
412
- })
413
- this.objint.orderBidiStream()
414
- return prom
415
- }
416
-
417
- createUnidirectionalStream() {
418
- const prom = new Promise((resolve, reject) => {
419
- this.resolveUniDi.push(resolve)
420
- this.rejectUniDi.push(reject)
421
- })
422
- this.objint.orderUnidiStream()
423
- return prom
424
- }
425
-
426
- close(closeInfo) {
427
- // console.log('closeinfo', closeInfo)
428
- if (this.state === 'closed' || this.state === 'failed') return
429
- if (this.objint) {
430
- this.objint.close({
431
- code: closeInfo.closeCode,
432
- reason: closeInfo.reason.substring(0, 1023)
433
- })
434
- }
435
- }
436
-
437
- onReady(/* error */) {
438
- if (this.readyResolve) this.readyResolve()
439
- delete this.readyResolve
440
- }
441
-
442
- onClose(errorcode, error) {
443
- delete this.objint // not valid any more
444
- // console.log('onClose')
445
- for (const rej of this.rejectBiDi) rej()
446
- for (const rej of this.rejectUniDi) rej()
447
- for (const rej of this.writeDatagramRej) rej()
448
- this.writeDatagramRej = []
449
- this.writeDatagramRes = []
450
- this.writeDatagramProm = []
451
- this.resolveBiDi = []
452
- this.resolveUniDi = []
453
- this.rejectBiDi = []
454
- this.rejectUniDi = []
455
-
456
- this.incomBiDiController.close()
457
- this.incomUniDiController.close()
458
- this.incomDatagramController.close()
459
- // this.outgoDatagramController.error(errorcode)
460
- this.state = 'closed'
461
-
462
- this.sendStreamsController.forEach((ele) => ele.error(errorcode))
463
- this.receiveStreamsController.forEach((ele) => ele.error(errorcode))
464
- this.streamObjs.forEach((ele) => (ele.readableclosed = true))
465
-
466
- this.sendStreams.clear()
467
- this.receiveStreams.clear()
468
- this.sendStreamsController.clear()
469
- this.receiveStreamsController.clear()
470
- this.streamObjs.clear()
471
-
472
- if (this.closedResolve) this.closedResolve(errorcode)
473
- if (this.closeHook) {
474
- this.closeHook()
475
- delete this.closeHook
476
- }
477
- }
478
-
479
- onStream(args) {
480
- const strobj = new Http3WTStream({
481
- object: args.stream,
482
- parentobj: this,
483
- transport: this.parentobj,
484
- bidirectional: args.bidirectional,
485
- incoming: args.incoming
486
- })
487
- this.addStreamObj(strobj)
488
- if (args.incoming) {
489
- if (args.bidirectional) {
490
- this.incomBiDiController.enqueue(strobj)
491
- } else {
492
- this.incomUniDiController.enqueue(strobj.readable)
493
- }
494
- } else {
495
- if (args.bidirectional) {
496
- if (this.resolveBiDi.length === 0)
497
- throw new Error('Got bidirectional stream without asking for it')
498
- this.rejectBiDi.shift()
499
- const curres = this.resolveBiDi.shift()
500
- curres(strobj)
501
- } else {
502
- if (this.resolveUniDi.length === 0)
503
- throw new Error('Got unidirectional stream without asking for it')
504
- this.rejectUniDi.shift()
505
- const curres = this.resolveUniDi.shift()
506
- curres(strobj.writable)
507
- }
508
- }
509
- }
510
-
511
- onDatagramReceived(args) {
512
- // console.log('datagram received', args.datagram, Date.now())
513
- this.incomDatagramController.enqueue(args.datagram)
514
- }
515
-
516
- onDatagramSend(args) {
517
- if (this.state === 'closed') return
518
- this.writeDatagramRej.shift()
519
- this.writeDatagramProm.shift()
520
- const res = this.writeDatagramRes.shift()
521
- res()
522
- }
523
-
524
- static callback(args) {
525
- // console.log('Session callback called', args)
526
- if (!args || !args.object || !args.object.jsobj)
527
- throw new Error('Session callback without jsobj')
528
- const visitor = args.object.jsobj
529
- if (args.purpose) {
530
- switch (args.purpose) {
531
- case 'SessionReady':
532
- visitor.onReady()
533
- break
534
- case 'SessionClose':
535
- visitor.onClose(args.errorcode, args.error)
536
- break
537
- case 'DatagramReceived':
538
- if (visitor && args.hasOwnProperty('datagram'))
539
- visitor.onDatagramReceived(args)
540
- break
541
- case 'DatagramSend':
542
- if (visitor) visitor.onDatagramSend(args)
543
- break
544
- case 'Http3WTStreamVisitor':
545
- if (
546
- visitor &&
547
- args.hasOwnProperty('bidirectional') &&
548
- args.hasOwnProperty('incoming')
549
- ) {
550
- visitor.onStream(args)
551
- } else throw new Error('Malformed Http3WTStreamVisitor')
552
- break
553
- default:
554
- throw new Error('unknown purpose Sessioncb')
555
- }
556
- } else throw new Error('no purpose Sessioncb')
557
- }
558
- }
559
-
560
- class Http3WebTransport {
561
- constructor(args, purpose) {
562
- const eventloop = Http3EventLoop.getGlobalEventLoop(this).eventloopInt
563
-
564
- if (purpose === 'server')
565
- this.transportInt = new wtrouter.Http3WebTransportServer(args, eventloop)
566
- else if (purpose === 'client')
567
- this.transportInt = new wtrouter.Http3WebTransportClient(args, eventloop)
568
- else throw new Error('unknown purpose')
569
- this.transportInt.jsobj = this
570
-
571
- this.sessions = {}
572
- }
573
-
574
- static transportCallback(args) {
575
- // console.log('incoming callback transport', args)
576
- if (!args || !args.object || !args.object.jsobj)
577
- throw new Error('Transport callback without jsobj')
578
- const visitor = args.object.jsobj
579
- if (args.purpose) {
580
- if (visitor.customCallback) {
581
- visitor.customCallback(args)
582
- } else {
583
- throw new Error('unknown purpose')
584
- }
585
- }
586
- }
587
- }
588
-
589
- export class Http3Server extends Http3WebTransport {
590
- constructor(args) {
591
- super(args, 'server')
592
- this.sessionStreams = {}
593
- this.sessionController = {}
594
- }
595
-
596
- startServer() {
597
- this.transportInt.startServer()
598
- }
599
-
600
- stopServer() {
601
- this.transportInt.stopServer()
602
- for (const i in this.sessionController) {
603
- this.sessionController[i].close() // inform the controller, that we are closing
604
- delete this.sessionController[i]
605
- }
606
- this.stopped = true
607
- }
608
-
609
- sessionStream(path) {
610
- if (path in this.sessionStreams) {
611
- return this.sessionsStreams[path]
612
- }
613
- this.sessionStreams[path] = new ReadableStream({
614
- start: async (controller) => {
615
- this.sessionController[path] = controller
616
- }
617
- })
618
- this.transportInt.addPath(path)
619
- return this.sessionStreams[path]
620
- }
621
-
622
- customCallback(args) {
623
- // console.log('incoming callback server', args)
624
- if (args.purpose) {
625
- switch (args.purpose) {
626
- case 'Http3WTSessionVisitor':
627
- // create Http3 Visitor
628
- if (args.object) {
629
- const sesobj = new Http3WTSession({
630
- object: args.session,
631
- parentobj: this
632
- })
633
- if (this.sessionController[args.path])
634
- this.sessionController[args.path].enqueue(sesobj)
635
- } else throw new Error('Http3WTSessionVisitor')
636
-
637
- break
638
-
639
- default: {
640
- throw new Error('unknown purpose')
641
- }
642
- }
643
- }
644
- }
645
- }
646
-
647
- class Http3Client extends Http3WebTransport {
648
- constructor(args) {
649
- super(args, 'client')
650
-
651
- this.sessionobj = new Promise((resolve, reject) => {
652
- this.sessionProm = { resolve, reject }
653
- }).catch(() => {}) // add default handler if no one cares
654
- this.sessionobjint = null
655
- this.closeHookSession = this.closeHookSession.bind(this)
656
-
657
- this.handleConnection()
658
- }
659
-
660
- async handleConnection() {
661
- this.quicconnected = new Promise((resolve, reject) => {
662
- this.quicconnectedProm = { resolve, reject }
663
- }).catch(() => {}) // add default handler if no one cares
664
-
665
- this.webtransport = new Promise((resolve, reject) => {
666
- this.webtransportProm = { resolve, reject }
667
- }).catch(() => {}) // add default handler if no one cares
668
-
669
- try {
670
- await this.quicconnected
671
- // now create Webtransport session
672
- setTimeout(() => {
673
- if (this.webtransportProm) {
674
- this.webtransportProm.reject(
675
- new Error('Timeout webtransport support')
676
- )
677
- delete this.webtransportProm
678
- }
679
- }, 2000)
680
- } catch (error) {
681
- console.log('Connecting failed for client:' + error)
682
- }
683
- }
684
-
685
- async createWTSession(sessionobj, path) {
686
- // TODO
687
- try {
688
- await this.webtransport // wait for webtransport support
689
- // ok now we open the session
690
- this.sessionobjint = sessionobj
691
- this.transportInt.openWTSession(path)
692
- console.log('wait for session')
693
- // we wait for a new session
694
- const sessobj = await this.sessionobj
695
-
696
- delete this.sessionobj
697
-
698
- return sessobj
699
- } catch (error) {
700
- throw new Error('createWTSession failed ' + error)
701
- }
702
- }
703
-
704
- closeHookSession() {
705
- this.transportInt.closeClient()
706
- this.stopped = true
707
- }
708
-
709
- customCallback(args) {
710
- // console.log('incoming callback custom client', args)
711
- if (args.purpose) {
712
- switch (args.purpose) {
713
- case 'ClientConnected':
714
- if (this.quicconnectedProm) {
715
- if (args.success) this.quicconnectedProm.resolve()
716
- else
717
- this.quicconnectedProm.reject(
718
- new Error('Connecting quic client failed')
719
- )
720
- } else throw new Error('Client connected with no pending promise')
721
- break
722
- case 'ClientWebtransportSupport':
723
- if (this.webtransportProm) {
724
- this.webtransportProm.resolve()
725
- delete this.webtransportProm
726
- }
727
- break
728
- case 'Http3WTSessionVisitor':
729
- // create Http3 Visitor
730
- if (args.session && this.sessionProm && this.sessionobjint) {
731
- this.sessionobjint.setSessionObj(args.session)
732
- args.session.jsobj.closeHook = this.closeHookSession
733
- delete this.sessionobjint
734
- this.sessionProm.resolve(args.session)
735
- delete this.sessionProm
736
- } else
737
- throw new Error(
738
- 'Http3WTSessionVisitor no object session or nor sessionprom'
739
- )
740
- break
741
-
742
- default: {
743
- throw new Error('unknown purpose')
744
- }
745
- }
746
- }
747
- }
748
- }
749
-
750
- export class WebTransport {
751
- constructor(url, args) {
752
- if (!url) throw new Error('no URL supplied')
753
- const ourl = (this.urlint = new URL(url))
754
-
755
- if (ourl.protocol !== 'https:')
756
- return new Error('URL is not supported for webtransport')
757
- const hostname = ourl.hostname
758
- let port = ourl.port
759
- if (port === '') port = 443
760
-
761
- this.client = new Http3Client({ hostname, port, ...args })
762
-
763
- this.sessionint = new Http3WTSession({
764
- /* object: args.session, */
765
- parentobj: this.client
766
- })
767
-
768
- this.ready = this.sessionint.ready
769
- this.closed = this.sessionint.closed
770
-
771
- this.datagrams = this.sessionint.datagrams
772
-
773
- this.incomingBidirectionalStreams =
774
- this.sessionint.incomingBidirectionalStreams
775
-
776
- this.incomingUnidirectionalStreams =
777
- this.sessionint.incomingUnidirectionalStreams
778
-
779
- this.establishSession()
780
- }
781
-
782
- async establishSession() {
783
- try {
784
- await this.client.quicconnected
785
- await this.client.createWTSession(this.sessionint, this.urlint.pathname)
786
- } catch (error) {
787
- this.sessionint.readyReject(
788
- new Error('Establishing session failed ' + error)
789
- )
790
- console.log('Establishing session failed ' + error)
791
- }
792
- }
793
-
794
- close(closeinfo) {
795
- return this.sessionint.close(closeinfo)
796
- }
797
-
798
- createBidirectionalStream() {
799
- return this.sessionint.createBidirectionalStream()
800
- }
801
-
802
- createUnidirectionalStream() {
803
- return this.sessionint.createUnidirectionalStream()
804
- }
805
- }
806
-
807
- class Http3EventLoop {
808
- static globalLoop = null
809
- constructor(args) {
810
- this.eventloopInt = new wtrouter.Http3EventLoop({
811
- transportCallback: Http3WebTransport.transportCallback,
812
- streamCallback: Http3WTStream.callback,
813
- sessionCallback: Http3WTSession.callback,
814
- eventloopCallback: Http3EventLoop.callback
815
- })
816
- this.eventloopInt.jsobj = this
817
-
818
- this.refObjects = new Set()
819
- this.loopGuardian = this.loopGuardian.bind(this)
820
- }
821
-
822
- startEventLoop() {
823
- console.log('start GlobalEventLoop')
824
- this.eventloopInt.startEventLoop()
825
- this.loopGuardianTimer = setInterval(this.loopGuardian, 5000)
826
- }
827
-
828
- shutdownEventLoop() {
829
- console.log('shutdown GlobalEventLoop')
830
- Http3EventLoop.globalLoop = null
831
- clearInterval(this.loopGuardianTimer)
832
- this.eventloopInt.shutDownEventLoop()
833
- }
834
-
835
- loopGuardian() {
836
- for (const item of this.refObjects) {
837
- if (typeof item.deref() === 'undefined' || item.deref()?.stopped)
838
- this.refObjects.delete(item)
839
- }
840
- if (this.refObjects.size === 0) {
841
- const now = Date.now()
842
- if (!this.refObjectsEmptyTime) this.refObjectsEmptyTime = now
843
- else if (now - this.refObjectsEmptyTime > 20 * 1000)
844
- this.shutdownEventLoop()
845
- } else if (this.refObjectsEmptyTime) delete this.refObjectsEmptyTime
846
- }
847
-
848
- static callback() {
849
- console.log('final eventloop callback called')
850
- }
851
-
852
- static createGlobalEventLoop() {
853
- if (!Http3EventLoop.globalLoop) {
854
- Http3EventLoop.globalLoop = new Http3EventLoop()
855
- Http3EventLoop.globalLoop.startEventLoop()
856
- console.log('createGlobalEventLoop')
857
- }
858
- }
859
-
860
- static getGlobalEventLoop(object) {
861
- if (!object) throw new Error('getGlobalEventLoop without reference object')
862
- if (!Http3EventLoop.globalLoop) Http3EventLoop.createGlobalEventLoop()
863
- Http3EventLoop.globalLoop.refObjects.add(new WeakRef(object))
864
- return Http3EventLoop.globalLoop
865
- }
866
- }
867
-
868
- export function testcheck() {
869
- return !Http3EventLoop.globalLoop
870
- }