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

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/lib/index.js ADDED
@@ -0,0 +1,32 @@
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 { Http3EventLoop } from './event-loop.js'
7
+
8
+ /**
9
+ * // Spec
10
+ * @typedef {import('./dom').WebTransportDatagramStats} WebTransportDatagramStats
11
+ * @typedef {import('./dom').WebTransportStats} WebTransportStats
12
+ * @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo
13
+ * @typedef {import('./dom').WebTransportDatagramDuplexStream} WebTransportDatagramDuplexStream
14
+ * @typedef {import('./dom').WebTransportBidirectionalStream} WebTransportBidirectionalStream
15
+ * @typedef {import('./dom').WebTransportSendStreamStats} WebTransportSendStreamStats
16
+ * @typedef {import('./dom').WebTransportSendStream} WebTransportSendStream
17
+ * @typedef {import('./dom').WebTransportReceiveStreamStats} WebTransportReceiveStreamStats
18
+ * @typedef {import('./dom').WebTransportReceiveStream} WebTransportReceiveStream
19
+ * @typedef {import('./dom').WebTransportHash} WebTransportHash
20
+ * @typedef {import('./dom').WebTransportOptions} WebTransportOptions
21
+ * @typedef {import('./dom').WebTransportReliabilityMode} WebTransportReliabilityMode
22
+ *
23
+ * Public API
24
+ * @typedef {import('./types').WebTransportSession} WebTransportSession
25
+ */
26
+
27
+ export function testcheck() {
28
+ return !Http3EventLoop.globalLoop
29
+ }
30
+
31
+ export { Http3Server } from './server.js'
32
+ export { WebTransport } from './webtransport.js'
package/lib/native.js ADDED
@@ -0,0 +1,25 @@
1
+ import { existsSync } from 'fs'
2
+ import { createRequire } from 'module'
3
+ import * as path from 'path'
4
+ import * as url from 'url'
5
+ import { arch, platform } from 'node:process'
6
+
7
+ const binplatform = platform + '_' + arch
8
+ const require = createRequire(import.meta.url)
9
+ const dirname = url.fileURLToPath(new URL('.', import.meta.url))
10
+ let buildpath = '../build_' + binplatform
11
+
12
+ if (!existsSync(path.join(dirname, buildpath))) buildpath = '../build' // use precompiled only if own compilation does not exist
13
+
14
+ let wtpath = buildpath + '/Release/webtransport.node'
15
+
16
+ if (
17
+ process.env.NODE_ENV !== 'production' &&
18
+ existsSync(path.join(dirname, buildpath + '/Debug/webtransport.node'))
19
+ ) {
20
+ wtpath = buildpath + '/Debug/webtransport.node'
21
+ }
22
+
23
+ console.log('load webtransport binary:', wtpath)
24
+
25
+ export const wtrouter = require(wtpath)
package/lib/server.js ADDED
@@ -0,0 +1,164 @@
1
+ import { Http3WebTransport } from './transport.js'
2
+ import { ReadableStream } from 'node:stream/web'
3
+ import { Http3WTSession } from './session.js'
4
+ import { isIPv4 } from 'net'
5
+ // @ts-ignore
6
+ import { defer } from './utils.js'
7
+
8
+ /**
9
+ * @typedef {import('./types').WebTransportSession} WebTransportSession
10
+ * @typedef {import('./types').NativeHttp3WTSession} NativeHttp3WTSession
11
+ * @typedef {import('./types').Http3ServerEventHandler} Http3ServerEventHandler
12
+ * @typedef {import('./types').Http3WTServerSessionVisitorEvent} Http3WTServerSessionVisitorEvent
13
+ * @typedef {import('./types').ServerStatusEvent} ServerStatusEvent
14
+ */
15
+
16
+ /**
17
+ * @implements {Http3ServerEventHandler}
18
+ */
19
+ export class Http3Server extends Http3WebTransport {
20
+ /**
21
+ *
22
+ * @param {*} args
23
+ */
24
+ constructor(args) {
25
+ super(args, 'server')
26
+
27
+ /** @type {Record<string, ReadableStream>} */
28
+ this.sessionStreams = {}
29
+
30
+ /** @type {Record<string, ReadableStreamController<any>>} */
31
+ this.sessionController = {}
32
+
33
+ this.port = null
34
+ this.host = null
35
+
36
+ this._ready = defer()
37
+ this.ready = this._ready.promise
38
+
39
+ this._closed = defer()
40
+ this.closed = this._closed.promise
41
+ }
42
+
43
+ startServer() {
44
+ this.transportInt.startServer()
45
+ }
46
+
47
+ stopServer() {
48
+ this.transportInt.stopServer()
49
+ for (const i in this.sessionController) {
50
+ this.sessionController[i].close() // inform the controller, that we are closing
51
+ delete this.sessionController[i]
52
+ }
53
+ this.stopped = true
54
+ }
55
+
56
+ /**
57
+ * @returns {{ port: number, host: string, family: 'IPv4' | 'IPv6' } | null}
58
+ */
59
+ address() {
60
+ if (this.port == null || this.host == null) {
61
+ console.info('returning null')
62
+ return null
63
+ }
64
+
65
+ return {
66
+ port: this.port,
67
+ host: this.host,
68
+ family: isIPv4(this.host) ? 'IPv4' : 'IPv6'
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @param {string} path
74
+ * @returns {ReadableStream<WebTransportSession>}
75
+ */
76
+ sessionStream(path) {
77
+ if (path in this.sessionStreams) {
78
+ return this.sessionStreams[path]
79
+ }
80
+ this.sessionStreams[path] = new ReadableStream({
81
+ start: async (controller) => {
82
+ this.sessionController[path] = controller
83
+ }
84
+ })
85
+ this.transportInt.addPath(path)
86
+ return this.sessionStreams[path]
87
+ }
88
+
89
+ /**
90
+ * @param {Http3WTServerSessionVisitorEvent} args
91
+ */
92
+ onHttp3WTSessionVisitor(args) {
93
+ // create Http3 Visitor
94
+ if (args.object) {
95
+ const sesobj = new Http3WTSession({
96
+ object: args.session,
97
+ parentobj: this
98
+ })
99
+ if (this.sessionController[args.path])
100
+ this.sessionController[args.path].enqueue(sesobj)
101
+ } else throw new Error('Http3WTSessionVisitor')
102
+ }
103
+
104
+ /**
105
+ */
106
+ onServerError() {
107
+ this._ready.reject()
108
+ }
109
+
110
+ /**
111
+ */
112
+ onServerListening() {
113
+ this._ready.resolve()
114
+ }
115
+
116
+ /**
117
+ */
118
+ onServerClose() {
119
+ this._closed.resolve()
120
+ }
121
+
122
+ /**
123
+ * @param {ServerStatusEvent} evt
124
+ */
125
+ onServerStatus(evt) {
126
+ if (evt.host) this.host = evt.host
127
+ if (evt.port) this.port = evt.port
128
+
129
+ switch (evt.status) {
130
+ case 'close':
131
+ this.onServerClose()
132
+ break
133
+ case 'listening':
134
+ this.onServerListening()
135
+ break
136
+ case 'error':
137
+ this.onServerListening()
138
+ break
139
+ default: {
140
+ throw new Error('unknown status')
141
+ }
142
+ }
143
+ }
144
+
145
+ /**
146
+ * @param {Http3WTServerSessionVisitorEvent | ServerStatusEvent} args
147
+ */
148
+ customCallback(args) {
149
+ // console.log('incoming callback server', args)
150
+ if (args.purpose) {
151
+ switch (args.purpose) {
152
+ case 'Http3WTSessionVisitor':
153
+ this.onHttp3WTSessionVisitor(args)
154
+ break
155
+ case 'ServerStatus':
156
+ this.onServerStatus(args)
157
+ break
158
+ default: {
159
+ throw new Error('unknown purpose')
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
package/lib/session.js ADDED
@@ -0,0 +1,422 @@
1
+ import { ReadableStream, WritableStream } from 'node:stream/web'
2
+ import { Http3WTStream } from './stream.js'
3
+
4
+ /**
5
+ * WebTransport session events
6
+ * @typedef {import('./types').WebTransportSessionEventHandler} WebTransportSessionEventHandler
7
+ * @typedef {import('./types').SessionReadyEvent} SessionReadyEvent
8
+ * @typedef {import('./types').SessionCloseEvent} SessionCloseEvent
9
+ * @typedef {import('./types').DatagramReceivedEvent} DatagramReceivedEvent
10
+ * @typedef {import('./types').DatagramSendEvent} DatagramSendEvent
11
+ * @typedef {import('./types').NewStreamEvent} NewStreamEvent
12
+ *
13
+ * @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo
14
+ * @typedef {import('./dom').WebTransportBidirectionalStream} WebTransportBidirectionalStream
15
+ * @typedef {import('./dom').WebTransportSendStream} WebTransportSendStream
16
+ * @typedef {import('./dom').WebTransportDatagramDuplexStream} WebTransportDatagramDuplexStream
17
+ *
18
+ * @typedef {import('./types').NativeHttp3WTSession} NativeHttp3WTSession
19
+ *
20
+ * Public API
21
+ * @typedef {import('./types').WebTransportSession} WebTransportSession
22
+ *
23
+ * @typedef {import('./server').Http3Server} Http3Server
24
+ * @typedef {import('./client').Http3Client} Http3Client
25
+ *
26
+ * @typedef {import('stream/web').WritableStreamDefaultController} WritableStreamDefaultController
27
+ */
28
+
29
+ /**
30
+ * @implements {WebTransportSessionEventHandler}
31
+ * @implements {WebTransportSession}
32
+ */
33
+ export class Http3WTSession {
34
+ /**
35
+ * @param {object} args
36
+ * @param {import('./types').NativeHttp3WTSession} [args.object]
37
+ * @param {Http3Server | Http3Client} args.parentobj
38
+ */
39
+ constructor(args) {
40
+ if (args.object) {
41
+ this.objint = args.object
42
+ this.objint.jsobj = this
43
+ }
44
+ this.parentobj = args.parentobj
45
+ /** @type {import('./types').WebTransportSessionState} */
46
+ this.state = 'connected'
47
+
48
+ /** @type {((value?: any) => void) | null | undefined} */
49
+ this.readyResolve = null
50
+ /** @type {(() => void) | null | undefined} */
51
+ this.closeHook = null
52
+
53
+ /** @type {Promise<void>} */
54
+ this.ready = new Promise((resolve, reject) => {
55
+ this.readyResolve = resolve
56
+ this.readyReject = reject
57
+ })
58
+ /** @type {Promise<WebTransportCloseInfo>} */
59
+ this.closed = new Promise((resolve, reject) => {
60
+ this.closedResolve = resolve
61
+ this.closedReject = reject
62
+ })
63
+
64
+ this.incomingBidirectionalStreams = new ReadableStream({
65
+ start: (controller) => {
66
+ this.incomBiDiController = controller
67
+ }
68
+ })
69
+
70
+ this.incomingUnidirectionalStreams = new ReadableStream({
71
+ start: (controller) => {
72
+ this.incomUniDiController = controller
73
+ }
74
+ })
75
+
76
+ /** @type {Array<() => void>} */
77
+ this.writeDatagramRes = []
78
+ /** @type {Array<() => void>} */
79
+ this.writeDatagramRej = []
80
+ /** @type {Array<Promise<void>>} */
81
+ this.writeDatagramProm = []
82
+
83
+ /** @type {WebTransportDatagramDuplexStream} */
84
+ this.datagrams = {
85
+ readable: new ReadableStream({
86
+ start: (controller) => {
87
+ this.incomDatagramController = controller
88
+ }
89
+ }),
90
+ writable: new WritableStream({
91
+ start: (controller) => {
92
+ this.outgoDatagramController = controller
93
+ },
94
+ write: (chunk, controller) => {
95
+ if (this.state === 'closed') throw new Error('Session is closed')
96
+ if (chunk instanceof Uint8Array) {
97
+ /** @type {Promise<void>} */
98
+ const ret = new Promise((resolve, reject) => {
99
+ this.writeDatagramRes.push(resolve)
100
+ this.writeDatagramRej.push(reject)
101
+ })
102
+ this.writeDatagramProm.push(ret)
103
+ // console.log('b4 datagram write', chunk, Date.now())
104
+ if (this.objint == null) {
105
+ throw new Error('this.objint is not set')
106
+ }
107
+ this.objint.writeDatagram(chunk)
108
+ return ret
109
+ } else throw new Error('chunk is not of type Uint8Array')
110
+ },
111
+ close: () => {
112
+ // do nothing
113
+ }
114
+ })
115
+ }
116
+
117
+ /** @type {Array<(stream: WebTransportBidirectionalStream) => void>} */
118
+ this.resolveBiDi = []
119
+ /** @type {Array<(stream: WebTransportSendStream) => void>} */
120
+ this.resolveUniDi = []
121
+ /** @type {Array<(err?: Error) => void>} */
122
+ this.rejectBiDi = []
123
+ /** @type {Array<(err?: Error) => void>} */
124
+ this.rejectUniDi = []
125
+
126
+ this.sendStreams = new Set()
127
+ this.receiveStreams = new Set()
128
+ /** @type {Set<Http3WTStream>} */
129
+ this.streamObjs = new Set()
130
+
131
+ /** @type {Set<WritableStreamDefaultController>} */
132
+ this.sendStreamsController = new Set()
133
+ /** @type {Set<ReadableStreamDefaultController>} */
134
+ this.receiveStreamsController = new Set()
135
+ }
136
+
137
+ /**
138
+ * @param {NativeHttp3WTSession} object
139
+ */
140
+ setSessionObj(object) {
141
+ if (object) {
142
+ this.objint = object
143
+ this.objint.jsobj = this
144
+ }
145
+ }
146
+
147
+ async waitForDatagramsSend() {
148
+ while (this.writeDatagramProm.length > 0) {
149
+ try {
150
+ await Promise.allSettled(this.writeDatagramProm)
151
+ } catch (error) {
152
+ console.log('datagram promise failed ', error)
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @param {Http3WTStream} stream
159
+ */
160
+ addStreamObj(stream) {
161
+ this.streamObjs.add(stream)
162
+ }
163
+
164
+ /**
165
+ * @param {Http3WTStream} stream
166
+ */
167
+ removeStreamObj(stream) {
168
+ this.streamObjs.delete(stream)
169
+ }
170
+
171
+ /**
172
+ * @param {WritableStream} stream
173
+ * @param {WritableStreamDefaultController} controller
174
+ */
175
+ addSendStream(stream, controller) {
176
+ this.sendStreams.add(stream)
177
+ this.sendStreamsController.add(controller)
178
+ }
179
+
180
+ /**
181
+ * @param {WritableStream} stream
182
+ * @param {WritableStreamDefaultController} controller
183
+ */
184
+ removeSendStream(stream, controller) {
185
+ this.sendStreams.delete(stream)
186
+ this.sendStreamsController.delete(controller)
187
+ }
188
+
189
+ /**
190
+ * @param {ReadableStream} stream
191
+ * @param {ReadableStreamDefaultController} controller
192
+ */
193
+ addReceiveStream(stream, controller) {
194
+ this.receiveStreams.add(stream)
195
+ this.receiveStreamsController.add(controller)
196
+ }
197
+
198
+ /**
199
+ * @param {ReadableStream} stream
200
+ * @param {ReadableStreamDefaultController} controller
201
+ */
202
+ removeReceiveStream(stream, controller) {
203
+ this.receiveStreams.delete(stream)
204
+ this.receiveStreamsController.delete(controller)
205
+ }
206
+
207
+ /**
208
+ * @returns {Promise<WebTransportBidirectionalStream>}
209
+ */
210
+ createBidirectionalStream() {
211
+ if (this.objint == null) {
212
+ throw new Error('this.objint not set')
213
+ }
214
+ /** @type {Promise<WebTransportBidirectionalStream>} */
215
+ const prom = new Promise((resolve, reject) => {
216
+ this.resolveBiDi.push(resolve)
217
+ this.rejectBiDi.push(reject)
218
+ })
219
+ this.objint.orderBidiStream()
220
+ return prom
221
+ }
222
+
223
+ /**
224
+ *@returns {Promise<WebTransportSendStream>}
225
+ */
226
+ createUnidirectionalStream() {
227
+ if (this.objint == null) {
228
+ throw new Error('this.objint not set')
229
+ }
230
+ /** @type {Promise<WebTransportSendStream>} */
231
+ const prom = new Promise((resolve, reject) => {
232
+ this.resolveUniDi.push(resolve)
233
+ this.rejectUniDi.push(reject)
234
+ })
235
+ this.objint.orderUnidiStream()
236
+ return prom
237
+ }
238
+
239
+ /**
240
+ * @param {object} [closeInfo]
241
+ * @param {number} closeInfo.closeCode
242
+ * @param {string} closeInfo.reason
243
+ * @returns {void}
244
+ */
245
+ close(closeInfo) {
246
+ // console.log('closeinfo', closeInfo)
247
+ if (this.state === 'closed' || this.state === 'failed') return
248
+ if (this.objint) {
249
+ this.objint.close({
250
+ code: closeInfo?.closeCode ?? 0,
251
+ reason: closeInfo?.reason.substring(0, 1023) ?? ''
252
+ })
253
+ }
254
+ }
255
+
256
+ onReady(/* error */) {
257
+ if (this.readyResolve) this.readyResolve()
258
+ delete this.readyResolve
259
+ }
260
+
261
+ /**
262
+ * @param {SessionCloseEvent} args
263
+ */
264
+ onClose(args) {
265
+ delete this.objint // not valid any more
266
+ // console.log('onClose')
267
+ for (const rej of this.rejectBiDi) rej()
268
+ for (const rej of this.rejectUniDi) rej()
269
+ for (const rej of this.writeDatagramRej) rej()
270
+ this.writeDatagramRej = []
271
+ this.writeDatagramRes = []
272
+ this.writeDatagramProm = []
273
+ this.resolveBiDi = []
274
+ this.resolveUniDi = []
275
+ this.rejectBiDi = []
276
+ this.rejectUniDi = []
277
+
278
+ this.incomBiDiController.close()
279
+ this.incomUniDiController.close()
280
+ this.incomDatagramController.close()
281
+ // this.outgoDatagramController.error(errorcode)
282
+ this.state = 'closed'
283
+
284
+ this.sendStreamsController.forEach((ele) => ele.error(args.errorcode))
285
+ this.receiveStreamsController.forEach((ele) => ele.error(args.errorcode))
286
+ this.streamObjs.forEach((ele) => (ele.readableclosed = true))
287
+
288
+ this.sendStreams.clear()
289
+ this.receiveStreams.clear()
290
+ this.sendStreamsController.clear()
291
+ this.receiveStreamsController.clear()
292
+ this.streamObjs.clear()
293
+
294
+ if (this.closedResolve)
295
+ this.closedResolve({ closeCode: args.errorcode, reason: 'closed' })
296
+ if (this.closeHook) {
297
+ this.closeHook()
298
+ delete this.closeHook
299
+ }
300
+ }
301
+
302
+ /**
303
+ * @param {NewStreamEvent} args
304
+ */
305
+ onStream(args) {
306
+ const strobj = new Http3WTStream({
307
+ object: args.stream,
308
+ parentobj: this,
309
+ transport: this.parentobj,
310
+ bidirectional: args.bidirectional,
311
+ incoming: args.incoming
312
+ })
313
+ this.addStreamObj(strobj)
314
+ if (args.incoming) {
315
+ if (args.bidirectional) {
316
+ this.incomBiDiController.enqueue(strobj)
317
+ } else {
318
+ this.incomUniDiController.enqueue(strobj.readable)
319
+ }
320
+ } else {
321
+ if (args.bidirectional) {
322
+ if (this.resolveBiDi.length === 0)
323
+ throw new Error('Got bidirectional stream without asking for it')
324
+ this.rejectBiDi.shift()
325
+ const curres = this.resolveBiDi.shift()
326
+
327
+ if (
328
+ curres != null &&
329
+ strobj.readable != null &&
330
+ strobj.writable != null
331
+ ) {
332
+ curres({
333
+ readable: strobj.readable,
334
+ writable: strobj.writable
335
+ })
336
+ }
337
+ } else {
338
+ if (this.resolveUniDi.length === 0)
339
+ throw new Error('Got unidirectional stream without asking for it')
340
+ this.rejectUniDi.shift()
341
+ const curres = this.resolveUniDi.shift()
342
+
343
+ if (curres != null && strobj.writable != null) {
344
+ /** @type {WebTransportSendStream} */
345
+ // @ts-expect-error `getStats` property is missing from WritableStream
346
+ // we add it on the next line
347
+ const sendStream = strobj.writable
348
+ sendStream.getStats = () => {
349
+ return Promise.resolve({
350
+ timestamp: 0,
351
+ bytesWritten: 0n,
352
+ bytesSent: 0n,
353
+ bytesAcknowledged: 0n
354
+ })
355
+ }
356
+
357
+ curres(sendStream)
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ * @param {DatagramReceivedEvent} args
365
+ */
366
+ onDatagramReceived(args) {
367
+ // console.log('datagram received', args.datagram, Date.now())
368
+ this.incomDatagramController.enqueue(args.datagram)
369
+ }
370
+
371
+ /**
372
+ * @param {DatagramSendEvent} args
373
+ */
374
+ onDatagramSend(args) {
375
+ if (this.state === 'closed') return
376
+ this.writeDatagramRej.shift()
377
+ this.writeDatagramProm.shift()
378
+ const res = this.writeDatagramRes.shift()
379
+
380
+ if (res != null) {
381
+ res()
382
+ }
383
+ }
384
+
385
+ /**
386
+ * @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | NewStreamEvent} args
387
+ */
388
+ static callback(args) {
389
+ // console.log('Session callback called', args)
390
+ if (!args || !args.object || !args.object.jsobj)
391
+ throw new Error('Session callback without jsobj')
392
+ const visitor = args.object.jsobj
393
+ if (args.purpose) {
394
+ switch (args.purpose) {
395
+ case 'SessionReady':
396
+ visitor.onReady(args)
397
+ break
398
+ case 'SessionClose':
399
+ visitor.onClose(args)
400
+ break
401
+ case 'DatagramReceived':
402
+ if (visitor && Object.prototype.hasOwnProperty.call(args, 'datagram'))
403
+ visitor.onDatagramReceived(args)
404
+ break
405
+ case 'DatagramSend':
406
+ if (visitor) visitor.onDatagramSend(args)
407
+ break
408
+ case 'Http3WTStreamVisitor':
409
+ if (
410
+ visitor &&
411
+ Object.prototype.hasOwnProperty.call(args, 'bidirectional') &&
412
+ Object.prototype.hasOwnProperty.call(args, 'incoming')
413
+ ) {
414
+ visitor.onStream(args)
415
+ } else throw new Error('Malformed Http3WTStreamVisitor')
416
+ break
417
+ default:
418
+ throw new Error('unknown purpose Sessioncb')
419
+ }
420
+ } else throw new Error('no purpose Sessioncb')
421
+ }
422
+ }