@libp2p/webtransport 4.0.28 → 4.0.29

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 (47) 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 +55 -94
  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/dist/typedoc-urls.json +2 -0
  38. package/package.json +18 -9
  39. package/src/index.ts +74 -122
  40. package/src/listener.browser.ts +5 -0
  41. package/src/listener.ts +19 -0
  42. package/src/muxer.ts +95 -0
  43. package/src/stream.ts +90 -164
  44. package/src/utils/generate-certificates.browser.ts +3 -0
  45. package/src/utils/generate-certificates.ts +11 -0
  46. package/src/webtransport.browser.ts +1 -0
  47. 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,112 +303,34 @@ 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
- }
306
+ createListener (options: CreateListenerOptions): Listener {
307
+ return createListener(this.components, {
308
+ ...options,
309
+ certificates: this.config.certificates,
310
+ maxInboundStreams: this.config.maxInboundStreams
311
+ })
371
312
  }
372
313
 
373
- createListener (options: CreateListenerOptions): Listener {
374
- throw new Error('Webtransport servers are not supported in Node or the browser')
314
+ /**
315
+ * Filter check for all Multiaddrs that this transport can listen on
316
+ */
317
+ listenFilter (): Multiaddr[] {
318
+ return []
375
319
  }
376
320
 
377
321
  /**
378
- * Takes a list of `Multiaddr`s and returns only valid webtransport addresses.
322
+ * Filter check for all Multiaddrs that this transport can dial
379
323
  */
380
- filter (multiaddrs: Multiaddr[]): Multiaddr[] {
381
- return multiaddrs.filter(WebTransportMatcher.exactMatch)
324
+ dialFilter (multiaddrs: Multiaddr[]): Multiaddr[] {
325
+ return multiaddrs.filter(ma => {
326
+ if (!WebTransportMatcher.exactMatch(ma)) {
327
+ return false
328
+ }
329
+
330
+ const { url, certhashes, remotePeer } = parseMultiaddr(ma)
331
+
332
+ return url != null && remotePeer != null && certhashes.length > 0
333
+ })
382
334
  }
383
335
  }
384
336
 
@@ -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
+ }