@libp2p/webtransport 4.0.28-c82432312 → 4.0.28-de3f7aeaf

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 (46) hide show
  1. package/dist/index.min.js +3 -3
  2. package/dist/src/index.d.ts +12 -1
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/index.js +40 -91
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/listener.browser.d.ts +3 -0
  7. package/dist/src/listener.browser.d.ts.map +1 -0
  8. package/dist/src/listener.browser.js +4 -0
  9. package/dist/src/listener.browser.js.map +1 -0
  10. package/dist/src/listener.d.ts +15 -0
  11. package/dist/src/listener.d.ts.map +1 -0
  12. package/dist/src/listener.js +4 -0
  13. package/dist/src/listener.js.map +1 -0
  14. package/dist/src/muxer.d.ts +7 -0
  15. package/dist/src/muxer.d.ts.map +1 -0
  16. package/dist/src/muxer.js +70 -0
  17. package/dist/src/muxer.js.map +1 -0
  18. package/dist/src/stream.d.ts.map +1 -1
  19. package/dist/src/stream.js +79 -151
  20. package/dist/src/stream.js.map +1 -1
  21. package/dist/src/utils/generate-certificates.browser.d.ts +2 -0
  22. package/dist/src/utils/generate-certificates.browser.d.ts.map +1 -0
  23. package/dist/src/utils/generate-certificates.browser.js +4 -0
  24. package/dist/src/utils/generate-certificates.browser.js.map +1 -0
  25. package/dist/src/utils/generate-certificates.d.ts +8 -0
  26. package/dist/src/utils/generate-certificates.d.ts.map +1 -0
  27. package/dist/src/utils/generate-certificates.js +4 -0
  28. package/dist/src/utils/generate-certificates.js.map +1 -0
  29. package/dist/src/webtransport.browser.d.ts +2 -0
  30. package/dist/src/webtransport.browser.d.ts.map +1 -0
  31. package/dist/src/webtransport.browser.js +2 -0
  32. package/dist/src/webtransport.browser.js.map +1 -0
  33. package/dist/src/webtransport.d.ts +9 -0
  34. package/dist/src/webtransport.d.ts.map +1 -0
  35. package/dist/src/webtransport.js +15 -0
  36. package/dist/src/webtransport.js.map +1 -0
  37. package/package.json +18 -9
  38. package/src/index.ts +57 -120
  39. package/src/listener.browser.ts +5 -0
  40. package/src/listener.ts +19 -0
  41. package/src/muxer.ts +95 -0
  42. package/src/stream.ts +90 -164
  43. package/src/utils/generate-certificates.browser.ts +3 -0
  44. package/src/utils/generate-certificates.ts +11 -0
  45. package/src/webtransport.browser.ts +1 -0
  46. package/src/webtransport.ts +17 -0
package/src/index.ts CHANGED
@@ -30,23 +30,38 @@
30
30
  */
31
31
 
32
32
  import { noise } from '@chainsafe/libp2p-noise'
33
- import { type Transport, transportSymbol, type CreateListenerOptions, type DialOptions, type Listener, type ComponentLogger, type Logger, type Connection, type MultiaddrConnection, type Stream, type CounterGroup, type Metrics, type PeerId, type StreamMuxerFactory, type StreamMuxerInit, type StreamMuxer } from '@libp2p/interface'
34
- import { type Multiaddr, type AbortOptions } from '@multiformats/multiaddr'
33
+ import { AbortError, CodeError, transportSymbol } from '@libp2p/interface'
35
34
  import { WebTransport as WebTransportMatcher } from '@multiformats/multiaddr-matcher'
36
- import { webtransportBiDiStreamToStream } from './stream.js'
35
+ import { raceSignal } from 'race-signal'
36
+ import createListener from './listener.js'
37
+ import { webtransportMuxer } from './muxer.js'
37
38
  import { inertDuplex } from './utils/inert-duplex.js'
38
39
  import { isSubset } from './utils/is-subset.js'
39
40
  import { parseMultiaddr } from './utils/parse-multiaddr.js'
41
+ import WebTransport from './webtransport.js'
42
+ import type { Transport, CreateListenerOptions, DialOptions, Listener, ComponentLogger, Logger, Connection, MultiaddrConnection, CounterGroup, Metrics, PeerId } from '@libp2p/interface'
43
+ import type { Multiaddr } from '@multiformats/multiaddr'
40
44
  import type { Source } from 'it-stream-types'
41
45
  import type { MultihashDigest } from 'multiformats/hashes/interface'
42
46
  import type { Uint8ArrayList } from 'uint8arraylist'
43
47
 
48
+ /**
49
+ * PEM format server certificate and private key
50
+ */
51
+ export interface WebTransportCertificate {
52
+ privateKey: string
53
+ pem: string
54
+ hash: MultihashDigest<number>
55
+ secret: string
56
+ }
57
+
44
58
  interface WebTransportSessionCleanup {
45
59
  (metric: string): void
46
60
  }
47
61
 
48
62
  export interface WebTransportInit {
49
63
  maxInboundStreams?: number
64
+ certificates?: WebTransportCertificate[]
50
65
  }
51
66
 
52
67
  export interface WebTransportComponents {
@@ -69,7 +84,9 @@ class WebTransportTransport implements Transport {
69
84
  this.log = components.logger.forComponent('libp2p:webtransport')
70
85
  this.components = components
71
86
  this.config = {
72
- maxInboundStreams: init.maxInboundStreams ?? 1000
87
+ ...init,
88
+ maxInboundStreams: init.maxInboundStreams ?? 1000,
89
+ certificates: init.certificates ?? []
73
90
  }
74
91
 
75
92
  if (components.metrics != null) {
@@ -87,12 +104,14 @@ class WebTransportTransport implements Transport {
87
104
  readonly [transportSymbol] = true
88
105
 
89
106
  async dial (ma: Multiaddr, options: DialOptions): Promise<Connection> {
90
- options?.signal?.throwIfAborted()
107
+ if (options?.signal?.aborted === true) {
108
+ throw new AbortError()
109
+ }
91
110
 
92
111
  this.log('dialing %s', ma)
93
112
  const localPeer = this.components.peerId
94
113
  if (localPeer === undefined) {
95
- throw new Error('Need a local peerid')
114
+ throw new CodeError('Need a local peerid', 'ERR_INVALID_PARAMETERS')
96
115
  }
97
116
 
98
117
  options = options ?? {}
@@ -100,11 +119,11 @@ class WebTransportTransport implements Transport {
100
119
  const { url, certhashes, remotePeer } = parseMultiaddr(ma)
101
120
 
102
121
  if (remotePeer == null) {
103
- throw new Error('Need a target peerid')
122
+ throw new CodeError('Need a target peerid', 'ERR_INVALID_PARAMETERS')
104
123
  }
105
124
 
106
125
  if (certhashes.length === 0) {
107
- throw new Error('Expected multiaddr to contain certhashes')
126
+ throw new CodeError('Expected multiaddr to contain certhashes', 'ERR_INVALID_PARAMETERS')
108
127
  }
109
128
 
110
129
  let abortListener: (() => void) | undefined
@@ -159,10 +178,12 @@ class WebTransportTransport implements Transport {
159
178
  once: true
160
179
  })
161
180
 
181
+ this.log('wait for session to be ready')
162
182
  await Promise.race([
163
183
  wt.closed,
164
184
  wt.ready
165
185
  ])
186
+ this.log('session became ready')
166
187
 
167
188
  ready = true
168
189
  this.metrics?.dialerEvents.increment({ ready: true })
@@ -175,15 +196,17 @@ class WebTransportTransport implements Transport {
175
196
  cleanUpWTSession('remote_close')
176
197
  })
177
198
 
178
- if (!await this.authenticateWebTransport(wt, localPeer, remotePeer, certhashes)) {
179
- throw new Error('Failed to authenticate webtransport')
199
+ authenticated = await raceSignal(this.authenticateWebTransport(wt, localPeer, remotePeer, certhashes), options.signal)
200
+
201
+ if (!authenticated) {
202
+ throw new CodeError('Failed to authenticate webtransport', 'ERR_AUTHENTICATION_FAILED')
180
203
  }
181
204
 
182
205
  this.metrics?.dialerEvents.increment({ open: true })
183
206
 
184
207
  maConn = {
185
208
  close: async () => {
186
- this.log('Closing webtransport')
209
+ this.log('closing webtransport')
187
210
  cleanUpWTSession('close')
188
211
  },
189
212
  abort: (err: Error) => {
@@ -199,9 +222,11 @@ class WebTransportTransport implements Transport {
199
222
  ...inertDuplex()
200
223
  }
201
224
 
202
- authenticated = true
203
-
204
- return await options.upgrader.upgradeOutbound(maConn, { skipEncryption: true, muxerFactory: this.webtransportMuxer(wt), skipProtection: true })
225
+ return await options.upgrader.upgradeOutbound(maConn, {
226
+ skipEncryption: true,
227
+ muxerFactory: webtransportMuxer(wt, wt.incomingBidirectionalStreams.getReader(), this.components.logger, this.config),
228
+ skipProtection: true
229
+ })
205
230
  } catch (err: any) {
206
231
  this.log.error('caught wt session err', err)
207
232
 
@@ -221,11 +246,14 @@ class WebTransportTransport implements Transport {
221
246
  }
222
247
  }
223
248
 
224
- async authenticateWebTransport (wt: InstanceType<typeof WebTransport>, localPeer: PeerId, remotePeer: PeerId, certhashes: Array<MultihashDigest<number>>): Promise<boolean> {
249
+ async authenticateWebTransport (wt: WebTransport, localPeer: PeerId, remotePeer: PeerId, certhashes: Array<MultihashDigest<number>>, signal?: AbortSignal): Promise<boolean> {
250
+ if (signal?.aborted === true) {
251
+ throw new AbortError()
252
+ }
253
+
225
254
  const stream = await wt.createBidirectionalStream()
226
255
  const writer = stream.writable.getWriter()
227
256
  const reader = stream.readable.getReader()
228
- await writer.ready
229
257
 
230
258
  const duplex = {
231
259
  source: (async function * () {
@@ -241,13 +269,15 @@ class WebTransportTransport implements Transport {
241
269
  }
242
270
  }
243
271
  })(),
244
- sink: async function (source: Source<Uint8Array | Uint8ArrayList>) {
272
+ sink: async (source: Source<Uint8Array | Uint8ArrayList>) => {
245
273
  for await (const chunk of source) {
246
- if (chunk instanceof Uint8Array) {
247
- await writer.write(chunk)
248
- } else {
249
- await writer.write(chunk.subarray())
250
- }
274
+ await raceSignal(writer.ready, signal)
275
+
276
+ const buf = chunk instanceof Uint8Array ? chunk : chunk.subarray()
277
+
278
+ writer.write(buf).catch(err => {
279
+ this.log.error('could not write chunk during authentication of WebTransport stream', err)
280
+ })
251
281
  }
252
282
  }
253
283
  }
@@ -273,105 +303,12 @@ class WebTransportTransport implements Transport {
273
303
  return true
274
304
  }
275
305
 
276
- webtransportMuxer (wt: WebTransport): StreamMuxerFactory {
277
- let streamIDCounter = 0
278
- const config = this.config
279
- const self = this
280
- return {
281
- protocol: 'webtransport',
282
- createStreamMuxer: (init?: StreamMuxerInit): StreamMuxer => {
283
- // !TODO handle abort signal when WebTransport supports this.
284
-
285
- if (typeof init === 'function') {
286
- // The api docs say that init may be a function
287
- init = { onIncomingStream: init }
288
- }
289
-
290
- const activeStreams: Stream[] = [];
291
-
292
- (async function () {
293
- //! TODO unclear how to add backpressure here?
294
-
295
- const reader = wt.incomingBidirectionalStreams.getReader()
296
- while (true) {
297
- const { done, value: wtStream } = await reader.read()
298
-
299
- if (done) {
300
- break
301
- }
302
-
303
- if (activeStreams.length >= config.maxInboundStreams) {
304
- // We've reached our limit, close this stream.
305
- wtStream.writable.close().catch((err: Error) => {
306
- self.log.error(`Failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`)
307
- })
308
- wtStream.readable.cancel().catch((err: Error) => {
309
- self.log.error(`Failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`)
310
- })
311
- } else {
312
- const stream = await webtransportBiDiStreamToStream(
313
- wtStream,
314
- String(streamIDCounter++),
315
- 'inbound',
316
- activeStreams,
317
- init?.onStreamEnd,
318
- self.components.logger
319
- )
320
- activeStreams.push(stream)
321
- init?.onIncomingStream?.(stream)
322
- }
323
- }
324
- })().catch(() => {
325
- this.log.error('WebTransport failed to receive incoming stream')
326
- })
327
-
328
- const muxer: StreamMuxer = {
329
- protocol: 'webtransport',
330
- streams: activeStreams,
331
- newStream: async (name?: string): Promise<Stream> => {
332
- const wtStream = await wt.createBidirectionalStream()
333
-
334
- const stream = await webtransportBiDiStreamToStream(
335
- wtStream,
336
- String(streamIDCounter++),
337
- init?.direction ?? 'outbound',
338
- activeStreams,
339
- init?.onStreamEnd,
340
- self.components.logger
341
- )
342
- activeStreams.push(stream)
343
-
344
- return stream
345
- },
346
-
347
- /**
348
- * Close or abort all tracked streams and stop the muxer
349
- */
350
- close: async (options?: AbortOptions) => {
351
- this.log('Closing webtransport muxer')
352
-
353
- await Promise.all(
354
- activeStreams.map(async s => s.close(options))
355
- )
356
- },
357
- abort: (err: Error) => {
358
- this.log('Aborting webtransport muxer with err:', err)
359
-
360
- for (const stream of activeStreams) {
361
- stream.abort(err)
362
- }
363
- },
364
- // This stream muxer is webtransport native. Therefore it doesn't plug in with any other duplex.
365
- ...inertDuplex()
366
- }
367
-
368
- return muxer
369
- }
370
- }
371
- }
372
-
373
306
  createListener (options: CreateListenerOptions): Listener {
374
- throw new Error('Webtransport servers are not supported in Node or the browser')
307
+ return createListener(this.components, {
308
+ ...options,
309
+ certificates: this.config.certificates,
310
+ maxInboundStreams: this.config.maxInboundStreams
311
+ })
375
312
  }
376
313
 
377
314
  /**
@@ -0,0 +1,5 @@
1
+ import type { CreateListenerOptions, Listener } from '@libp2p/interface'
2
+
3
+ export default function createListener (options: CreateListenerOptions): Listener {
4
+ throw new Error('Not implemented')
5
+ }
@@ -0,0 +1,19 @@
1
+ import type { WebTransportCertificate } from './index.js'
2
+ import type { Connection, Upgrader, Listener, CreateListenerOptions, PeerId, ComponentLogger, Metrics } from '@libp2p/interface'
3
+
4
+ export interface WebTransportListenerComponents {
5
+ peerId: PeerId
6
+ logger: ComponentLogger
7
+ metrics?: Metrics
8
+ }
9
+
10
+ export interface WebTransportListenerInit extends CreateListenerOptions {
11
+ handler?(conn: Connection): void
12
+ upgrader: Upgrader
13
+ certificates?: WebTransportCertificate[]
14
+ maxInboundStreams?: number
15
+ }
16
+
17
+ export default function createListener (components: WebTransportListenerComponents, options: WebTransportListenerInit): Listener {
18
+ throw new Error('Only supported in browsers')
19
+ }
package/src/muxer.ts ADDED
@@ -0,0 +1,95 @@
1
+ import { webtransportBiDiStreamToStream } from './stream.js'
2
+ import { inertDuplex } from './utils/inert-duplex.js'
3
+ import type WebTransport from './webtransport.js'
4
+ import type { ComponentLogger, Stream, StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface'
5
+
6
+ export interface WebTransportMuxerInit {
7
+ maxInboundStreams: number
8
+ }
9
+
10
+ export function webtransportMuxer (wt: Pick<WebTransport, 'close' | 'createBidirectionalStream'>, reader: ReadableStreamDefaultReader<WebTransportBidirectionalStream>, logger: ComponentLogger, config: WebTransportMuxerInit): StreamMuxerFactory {
11
+ let streamIDCounter = 0
12
+ const log = logger.forComponent('libp2p:webtransport:muxer')
13
+
14
+ return {
15
+ protocol: 'webtransport',
16
+ createStreamMuxer: (init?: StreamMuxerInit): StreamMuxer => {
17
+ // !TODO handle abort signal when WebTransport supports this.
18
+
19
+ if (typeof init === 'function') {
20
+ // The api docs say that init may be a function
21
+ init = { onIncomingStream: init }
22
+ }
23
+
24
+ const activeStreams: Stream[] = []
25
+
26
+ void Promise.resolve().then(async () => {
27
+ //! TODO unclear how to add backpressure here?
28
+ while (true) {
29
+ const { done, value: wtStream } = await reader.read()
30
+
31
+ if (done) {
32
+ break
33
+ }
34
+
35
+ if (activeStreams.length >= config.maxInboundStreams) {
36
+ log(`too many inbound streams open - ${activeStreams.length}/${config.maxInboundStreams}, closing new incoming stream`)
37
+ // We've reached our limit, close this stream.
38
+ wtStream.writable.close().catch((err: Error) => {
39
+ log.error(`failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`)
40
+ })
41
+ wtStream.readable.cancel().catch((err: Error) => {
42
+ log.error(`failed to close inbound stream that crossed our maxInboundStream limit: ${err.message}`)
43
+ })
44
+ } else {
45
+ const stream = await webtransportBiDiStreamToStream(
46
+ wtStream,
47
+ String(streamIDCounter++),
48
+ 'inbound',
49
+ activeStreams,
50
+ init?.onStreamEnd,
51
+ logger
52
+ )
53
+ activeStreams.push(stream)
54
+ init?.onIncomingStream?.(stream)
55
+ }
56
+ }
57
+ })
58
+
59
+ const muxer: StreamMuxer = {
60
+ protocol: 'webtransport',
61
+ streams: activeStreams,
62
+ newStream: async (name?: string): Promise<Stream> => {
63
+ log('new outgoing stream', name)
64
+
65
+ const wtStream = await wt.createBidirectionalStream()
66
+ const stream = await webtransportBiDiStreamToStream(wtStream, String(streamIDCounter++), init?.direction ?? 'outbound', activeStreams, init?.onStreamEnd, logger)
67
+ activeStreams.push(stream)
68
+
69
+ return stream
70
+ },
71
+
72
+ /**
73
+ * Close all tracked streams and stop the muxer
74
+ */
75
+ close: async () => {
76
+ log('closing webtransport muxer gracefully')
77
+ wt.close()
78
+ },
79
+
80
+ /**
81
+ * Abort all tracked streams and stop the muxer
82
+ */
83
+ abort: (err: Error) => {
84
+ log('closing webtransport muxer with err:', err)
85
+ wt.close()
86
+ },
87
+
88
+ // This stream muxer is webtransport native. Therefore it doesn't plug in with any other duplex.
89
+ ...inertDuplex()
90
+ }
91
+
92
+ return muxer
93
+ }
94
+ }
95
+ }
package/src/stream.ts CHANGED
@@ -1,184 +1,110 @@
1
+ import { AbstractStream, type AbstractStreamInit } from '@libp2p/utils/abstract-stream'
2
+ import { raceSignal } from 'race-signal'
1
3
  import { Uint8ArrayList } from 'uint8arraylist'
2
4
  import type { AbortOptions, ComponentLogger, Direction, Stream } from '@libp2p/interface'
3
- import type { Source } from 'it-stream-types'
4
5
 
5
- export async function webtransportBiDiStreamToStream (bidiStream: WebTransportBidirectionalStream, streamId: string, direction: Direction, activeStreams: Stream[], onStreamEnd: undefined | ((s: Stream) => void), logger: ComponentLogger): Promise<Stream> {
6
- const log = logger.forComponent(`libp2p:webtransport:stream:${direction}:${streamId}`)
7
- const writer = bidiStream.writable.getWriter()
8
- const reader = bidiStream.readable.getReader()
9
- await writer.ready
10
-
11
- function cleanupStreamFromActiveStreams (): void {
12
- const index = activeStreams.findIndex(s => s === stream)
13
- if (index !== -1) {
14
- activeStreams.splice(index, 1)
15
- stream.timeline.close = Date.now()
16
- onStreamEnd?.(stream)
17
- }
18
- }
6
+ interface WebTransportStreamInit extends AbstractStreamInit {
7
+ bidiStream: WebTransportBidirectionalStream
8
+ }
19
9
 
20
- let writerClosed = false
21
- let readerClosed = false;
22
- (async function () {
23
- const err: Error | undefined = await writer.closed.catch((err: Error) => err)
24
- if (err != null) {
25
- const msg = err.message
26
- if (!(msg.includes('aborted by the remote server') || msg.includes('STOP_SENDING'))) {
27
- log.error(`WebTransport writer closed unexpectedly: streamId=${streamId} err=${err.message}`)
28
- }
29
- }
30
- writerClosed = true
31
- if (writerClosed && readerClosed) {
32
- cleanupStreamFromActiveStreams()
33
- }
34
- })().catch(() => {
35
- log.error('WebTransport failed to cleanup closed stream')
36
- });
37
-
38
- (async function () {
39
- const err: Error | undefined = await reader.closed.catch((err: Error) => err)
40
- if (err != null) {
41
- log.error(`WebTransport reader closed unexpectedly: streamId=${streamId} err=${err.message}`)
42
- }
43
- readerClosed = true
44
- if (writerClosed && readerClosed) {
45
- cleanupStreamFromActiveStreams()
46
- }
47
- })().catch(() => {
48
- log.error('WebTransport failed to cleanup closed stream')
49
- })
10
+ class WebTransportStream extends AbstractStream {
11
+ private readonly writer: WritableStreamDefaultWriter<Uint8Array>
12
+ private readonly reader: ReadableStreamDefaultReader<Uint8Array>
50
13
 
51
- let sinkSunk = false
52
- const stream: Stream = {
53
- id: streamId,
54
- status: 'open',
55
- writeStatus: 'ready',
56
- readStatus: 'ready',
57
- abort (err: Error) {
58
- if (!writerClosed) {
59
- writer.abort(err)
60
- .catch(err => {
61
- log.error('could not abort stream', err)
62
- })
63
- writerClosed = true
64
- }
65
- readerClosed = true
66
-
67
- this.status = 'aborted'
68
- this.writeStatus = 'closed'
69
- this.readStatus = 'closed'
70
-
71
- this.timeline.reset =
72
- this.timeline.close =
73
- this.timeline.closeRead =
74
- this.timeline.closeWrite = Date.now()
75
-
76
- cleanupStreamFromActiveStreams()
77
- },
78
- async close (options?: AbortOptions) {
79
- this.status = 'closing'
80
-
81
- await Promise.all([
82
- stream.closeRead(options),
83
- stream.closeWrite(options)
84
- ])
85
-
86
- cleanupStreamFromActiveStreams()
87
-
88
- this.status = 'closed'
89
- this.timeline.close = Date.now()
90
- },
91
-
92
- async closeRead (options?: AbortOptions) {
93
- if (!readerClosed) {
94
- this.readStatus = 'closing'
95
-
96
- try {
97
- await reader.cancel()
98
- } catch (err: any) {
99
- if (err.toString().includes('RESET_STREAM') === true) {
100
- writerClosed = true
101
- }
102
- }
14
+ constructor (init: WebTransportStreamInit) {
15
+ super(init)
103
16
 
104
- this.timeline.closeRead = Date.now()
105
- this.readStatus = 'closed'
17
+ this.writer = init.bidiStream.writable.getWriter()
18
+ this.reader = init.bidiStream.readable.getReader()
106
19
 
107
- readerClosed = true
108
- }
20
+ Promise.resolve().then(async () => {
21
+ while (true) {
22
+ const result = await this.reader.read()
109
23
 
110
- if (writerClosed) {
111
- cleanupStreamFromActiveStreams()
112
- }
113
- },
24
+ if (result.done) {
25
+ init.log('remote closed write')
26
+ return
27
+ }
114
28
 
115
- async closeWrite (options?: AbortOptions) {
116
- if (!writerClosed) {
117
- writerClosed = true
29
+ if (result.value != null) {
30
+ this.sourcePush(new Uint8ArrayList(result.value))
31
+ }
32
+ }
33
+ })
34
+ .catch(err => {
35
+ init.log.error('error reading from stream', err)
36
+ this.abort(err)
37
+ })
38
+ .finally(() => {
39
+ this.remoteCloseWrite()
40
+ })
41
+
42
+ void this.writer.closed
43
+ .then(() => {
44
+ init.log('writer closed')
45
+ })
46
+ .catch((err) => {
47
+ init.log('writer close promise rejected', err)
48
+ })
49
+ .finally(() => {
50
+ this.remoteCloseRead()
51
+ })
52
+ }
118
53
 
119
- this.writeStatus = 'closing'
54
+ sendNewStream (options?: AbortOptions | undefined): void {
55
+ // this is a no-op
56
+ }
120
57
 
121
- try {
122
- await writer.close()
123
- } catch (err: any) {
124
- if (err.toString().includes('RESET_STREAM') === true) {
125
- readerClosed = true
126
- }
127
- }
58
+ async sendData (buf: Uint8ArrayList, options?: AbortOptions): Promise<void> {
59
+ for await (const chunk of buf) {
60
+ this.log('sendData waiting for writer to be ready')
61
+ await raceSignal(this.writer.ready, options?.signal)
62
+
63
+ // the streams spec recommends not waiting for data to be sent
64
+ // https://streams.spec.whatwg.org/#example-manual-write-dont-await
65
+ this.writer.write(chunk)
66
+ .catch(err => {
67
+ this.log.error('error sending stream data', err)
68
+ })
69
+ }
70
+ }
128
71
 
129
- this.timeline.closeWrite = Date.now()
130
- this.writeStatus = 'closed'
131
- }
72
+ async sendReset (options?: AbortOptions): Promise<void> {
73
+ this.log('sendReset aborting writer')
74
+ await raceSignal(this.writer.abort(), options?.signal)
75
+ this.log('sendReset aborted writer')
76
+ }
132
77
 
133
- if (readerClosed) {
134
- cleanupStreamFromActiveStreams()
135
- }
136
- },
137
- direction,
138
- timeline: { open: Date.now() },
139
- metadata: {},
140
- source: (async function * () {
141
- while (true) {
142
- const val = await reader.read()
143
- if (val.done) {
144
- readerClosed = true
145
- if (writerClosed) {
146
- cleanupStreamFromActiveStreams()
147
- }
148
- return
149
- }
78
+ async sendCloseWrite (options?: AbortOptions): Promise<void> {
79
+ this.log('sendCloseWrite closing writer')
80
+ await raceSignal(this.writer.close(), options?.signal)
81
+ this.log('sendCloseWrite closed writer')
82
+ }
150
83
 
151
- yield new Uint8ArrayList(val.value)
152
- }
153
- })(),
154
- sink: async function (source: Source<Uint8Array | Uint8ArrayList>) {
155
- if (sinkSunk) {
156
- throw new Error('sink already called on stream')
157
- }
158
- sinkSunk = true
159
- try {
160
- this.writeStatus = 'writing'
161
-
162
- for await (const chunks of source) {
163
- if (chunks instanceof Uint8Array) {
164
- await writer.write(chunks)
165
- } else {
166
- for (const buf of chunks) {
167
- await writer.write(buf)
168
- }
169
- }
170
- }
84
+ async sendCloseRead (options?: AbortOptions): Promise<void> {
85
+ this.log('sendCloseRead cancelling reader')
86
+ await raceSignal(this.reader.cancel(), options?.signal)
87
+ this.log('sendCloseRead cancelled reader')
88
+ }
89
+ }
171
90
 
172
- this.writeStatus = 'done'
173
- } finally {
174
- this.timeline.closeWrite = Date.now()
175
- this.writeStatus = 'closed'
91
+ export async function webtransportBiDiStreamToStream (bidiStream: WebTransportBidirectionalStream, streamId: string, direction: Direction, activeStreams: Stream[], onStreamEnd: undefined | ((s: Stream) => void), logger: ComponentLogger): Promise<Stream> {
92
+ const log = logger.forComponent(`libp2p:webtransport:stream:${direction}:${streamId}`)
176
93
 
177
- await stream.closeWrite()
94
+ const stream = new WebTransportStream({
95
+ bidiStream,
96
+ id: streamId,
97
+ direction,
98
+ log,
99
+ onEnd: () => {
100
+ const index = activeStreams.findIndex(s => s === stream)
101
+ if (index !== -1) {
102
+ activeStreams.splice(index, 1)
178
103
  }
179
- },
180
- log
181
- }
104
+
105
+ onStreamEnd?.(stream)
106
+ }
107
+ })
182
108
 
183
109
  return stream
184
110
  }
@@ -0,0 +1,3 @@
1
+ export async function generateWebTransportCertificates (): Promise<any> {
2
+ throw new Error('Not implemented')
3
+ }