@libp2p/webrtc 5.2.24-6059227cb → 5.2.24-87bc8d4fb

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/README.md +20 -10
  2. package/dist/index.min.js +18 -18
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +23 -4
  5. package/dist/src/constants.d.ts.map +1 -1
  6. package/dist/src/constants.js +23 -4
  7. package/dist/src/constants.js.map +1 -1
  8. package/dist/src/index.d.ts +22 -20
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +22 -12
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/maconn.d.ts +58 -0
  13. package/dist/src/maconn.d.ts.map +1 -0
  14. package/dist/src/maconn.js +56 -0
  15. package/dist/src/maconn.js.map +1 -0
  16. package/dist/src/muxer.d.ts +46 -14
  17. package/dist/src/muxer.d.ts.map +1 -1
  18. package/dist/src/muxer.js +138 -30
  19. package/dist/src/muxer.js.map +1 -1
  20. package/dist/src/private-to-private/initiate-connection.d.ts +3 -2
  21. package/dist/src/private-to-private/initiate-connection.d.ts.map +1 -1
  22. package/dist/src/private-to-private/initiate-connection.js +5 -37
  23. package/dist/src/private-to-private/initiate-connection.js.map +1 -1
  24. package/dist/src/private-to-private/signaling-stream-handler.d.ts +4 -4
  25. package/dist/src/private-to-private/signaling-stream-handler.d.ts.map +1 -1
  26. package/dist/src/private-to-private/signaling-stream-handler.js +7 -19
  27. package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -1
  28. package/dist/src/private-to-private/transport.d.ts +9 -2
  29. package/dist/src/private-to-private/transport.d.ts.map +1 -1
  30. package/dist/src/private-to-private/transport.js +15 -30
  31. package/dist/src/private-to-private/transport.js.map +1 -1
  32. package/dist/src/private-to-private/util.d.ts +2 -3
  33. package/dist/src/private-to-private/util.d.ts.map +1 -1
  34. package/dist/src/private-to-private/util.js +14 -26
  35. package/dist/src/private-to-private/util.js.map +1 -1
  36. package/dist/src/private-to-public/listener.d.ts.map +1 -1
  37. package/dist/src/private-to-public/listener.js +15 -21
  38. package/dist/src/private-to-public/listener.js.map +1 -1
  39. package/dist/src/private-to-public/transport.d.ts +8 -0
  40. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  41. package/dist/src/private-to-public/transport.js +2 -3
  42. package/dist/src/private-to-public/transport.js.map +1 -1
  43. package/dist/src/private-to-public/utils/connect.d.ts +1 -1
  44. package/dist/src/private-to-public/utils/connect.d.ts.map +1 -1
  45. package/dist/src/private-to-public/utils/connect.js +14 -17
  46. package/dist/src/private-to-public/utils/connect.js.map +1 -1
  47. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +4 -4
  48. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -1
  49. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +2 -13
  50. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -1
  51. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
  52. package/dist/src/private-to-public/utils/sdp.js +13 -25
  53. package/dist/src/private-to-public/utils/sdp.js.map +1 -1
  54. package/dist/src/private-to-public/utils/stun-listener.js +1 -1
  55. package/dist/src/private-to-public/utils/stun-listener.js.map +1 -1
  56. package/dist/src/stream.d.ts +26 -14
  57. package/dist/src/stream.d.ts.map +1 -1
  58. package/dist/src/stream.js +204 -134
  59. package/dist/src/stream.js.map +1 -1
  60. package/dist/src/util.d.ts +1 -3
  61. package/dist/src/util.d.ts.map +1 -1
  62. package/dist/src/util.js +0 -19
  63. package/dist/src/util.js.map +1 -1
  64. package/dist/src/webrtc/index.d.ts +1 -1
  65. package/dist/src/webrtc/index.d.ts.map +1 -1
  66. package/dist/src/webrtc/index.js +1 -1
  67. package/dist/src/webrtc/index.js.map +1 -1
  68. package/package.json +29 -26
  69. package/src/constants.ts +28 -5
  70. package/src/index.ts +22 -21
  71. package/src/maconn.ts +101 -0
  72. package/src/muxer.ts +169 -39
  73. package/src/private-to-private/initiate-connection.ts +8 -46
  74. package/src/private-to-private/signaling-stream-handler.ts +10 -23
  75. package/src/private-to-private/transport.ts +25 -33
  76. package/src/private-to-private/util.ts +16 -33
  77. package/src/private-to-public/listener.ts +15 -22
  78. package/src/private-to-public/transport.ts +12 -3
  79. package/src/private-to-public/utils/connect.ts +15 -18
  80. package/src/private-to-public/utils/get-rtcpeerconnection.ts +4 -16
  81. package/src/private-to-public/utils/sdp.ts +13 -29
  82. package/src/private-to-public/utils/stun-listener.ts +1 -1
  83. package/src/stream.ts +237 -153
  84. package/src/util.ts +1 -22
  85. package/src/webrtc/index.ts +1 -1
  86. package/dist/src/rtcpeerconnection-to-conn.d.ts +0 -12
  87. package/dist/src/rtcpeerconnection-to-conn.d.ts.map +0 -1
  88. package/dist/src/rtcpeerconnection-to-conn.js +0 -46
  89. package/dist/src/rtcpeerconnection-to-conn.js.map +0 -1
  90. package/src/rtcpeerconnection-to-conn.ts +0 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/webrtc",
3
- "version": "5.2.24-6059227cb",
3
+ "version": "5.2.24-87bc8d4fb",
4
4
  "description": "A libp2p transport using WebRTC connections",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/transport-webrtc#readme",
@@ -45,49 +45,52 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@chainsafe/is-ip": "^2.1.0",
48
- "@libp2p/crypto": "5.1.8-6059227cb",
49
- "@libp2p/interface": "2.11.0-6059227cb",
50
- "@libp2p/interface-internal": "2.3.19-6059227cb",
51
- "@libp2p/keychain": "5.2.9-6059227cb",
52
- "@libp2p/noise": "16.1.4-6059227cb",
53
- "@libp2p/peer-id": "5.1.9-6059227cb",
54
- "@libp2p/utils": "6.7.2-6059227cb",
55
- "@multiformats/multiaddr": "^13.0.1",
56
- "@multiformats/multiaddr-matcher": "^3.0.1",
48
+ "@chainsafe/libp2p-noise": "^16.1.3",
49
+ "@ipshipyard/node-datachannel": "^0.26.6",
50
+ "@libp2p/crypto": "5.1.8-87bc8d4fb",
51
+ "@libp2p/interface": "2.11.0-87bc8d4fb",
52
+ "@libp2p/interface-internal": "2.3.19-87bc8d4fb",
53
+ "@libp2p/keychain": "5.2.9-87bc8d4fb",
54
+ "@libp2p/peer-id": "5.1.9-87bc8d4fb",
55
+ "@libp2p/utils": "6.7.2-87bc8d4fb",
56
+ "@multiformats/multiaddr": "^12.4.4",
57
+ "@multiformats/multiaddr-matcher": "^2.0.0",
57
58
  "@peculiar/webcrypto": "^1.5.0",
58
- "@peculiar/x509": "^1.13.0",
59
+ "@peculiar/x509": "^1.12.3",
60
+ "any-signal": "^4.1.1",
59
61
  "detect-browser": "^5.3.0",
60
62
  "get-port": "^7.1.0",
61
- "interface-datastore": "^8.3.2",
63
+ "interface-datastore": "^8.3.1",
62
64
  "it-length-prefixed": "^10.0.1",
63
- "it-protobuf-stream": "^2.0.3",
65
+ "it-protobuf-stream": "^2.0.2",
64
66
  "it-pushable": "^3.2.3",
65
67
  "it-stream-types": "^2.0.2",
66
68
  "main-event": "^1.0.1",
67
- "multiformats": "^13.4.0",
68
- "node-datachannel": "^0.29.0",
69
+ "multiformats": "^13.3.6",
69
70
  "p-defer": "^4.0.1",
70
- "p-event": "^6.0.1",
71
71
  "p-timeout": "^6.1.4",
72
72
  "p-wait-for": "^5.0.2",
73
73
  "progress-events": "^1.0.1",
74
- "protons-runtime": "^5.6.0",
75
- "race-signal": "^2.0.0",
76
- "react-native-webrtc": "^124.0.6",
74
+ "protons-runtime": "^5.5.0",
75
+ "race-event": "^1.3.0",
76
+ "race-signal": "^1.1.3",
77
+ "react-native-webrtc": "^124.0.5",
77
78
  "uint8-varint": "^2.0.4",
78
79
  "uint8arraylist": "^2.4.8",
79
80
  "uint8arrays": "^5.1.0"
80
81
  },
81
82
  "devDependencies": {
82
- "@libp2p/logger": "5.2.0-6059227cb",
83
+ "@libp2p/interface-compliance-tests": "6.5.0-87bc8d4fb",
84
+ "@libp2p/logger": "5.2.0-87bc8d4fb",
83
85
  "@types/sinon": "^17.0.4",
84
- "aegir": "^47.0.22",
85
- "any-signal": "^4.1.1",
86
- "datastore-core": "^10.0.4",
86
+ "aegir": "^47.0.14",
87
+ "datastore-core": "^10.0.2",
87
88
  "delay": "^6.0.0",
88
- "p-retry": "^7.0.0",
89
- "protons": "^7.7.0",
90
- "sinon": "^21.0.0",
89
+ "it-length": "^3.0.8",
90
+ "it-pair": "^2.0.6",
91
+ "p-retry": "^6.2.1",
92
+ "protons": "^7.6.1",
93
+ "sinon": "^20.0.0",
91
94
  "sinon-ts": "^2.0.0",
92
95
  "wherearewe": "^2.0.1"
93
96
  },
package/src/constants.ts CHANGED
@@ -26,11 +26,26 @@ export const UFRAG_ALPHABET = Array.from('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
26
26
  */
27
27
  export const UFRAG_PREFIX = 'libp2p+webrtc+v1/'
28
28
 
29
+ /**
30
+ * The multicodec code for webrtc-direct tuples
31
+ */
32
+ export const CODEC_WEBRTC_DIRECT = 0x0118
33
+
34
+ /**
35
+ * The multicodec code for certhash tuples
36
+ */
37
+ export const CODEC_CERTHASH = 0x01d2
38
+
29
39
  /**
30
40
  * How much can be buffered to the DataChannel at once
31
41
  */
32
42
  export const MAX_BUFFERED_AMOUNT = 2 * 1024 * 1024
33
43
 
44
+ /**
45
+ * How long time we wait for the 'bufferedamountlow' event to be emitted
46
+ */
47
+ export const BUFFERED_AMOUNT_LOW_TIMEOUT = 30 * 1000
48
+
34
49
  /**
35
50
  * Max message size that can be sent to the DataChannel. In browsers this is
36
51
  * 256KiB but go-libp2p and rust-libp2p only support 16KiB at the time of
@@ -68,15 +83,23 @@ function calculateProtobufOverhead (maxMessageSize = MAX_MESSAGE_SIZE): number {
68
83
  export const PROTOBUF_OVERHEAD = calculateProtobufOverhead()
69
84
 
70
85
  /**
71
- * When closing a stream, we wait for `bufferedAmount` to become 0 before
72
- * closing the underlying RTCDataChannel - this controls how long we wait in ms
86
+ * When closing streams we send a FIN then wait for the remote to
87
+ * reply with a FIN_ACK. If that does not happen within this timeout
88
+ * we close the stream anyway.
73
89
  */
74
- export const DATA_CHANNEL_DRAIN_TIMEOUT = 30_000
90
+ export const FIN_ACK_TIMEOUT = 5_000
75
91
 
76
92
  /**
77
- * Wait for the remote to acknowledge our FIN for this long
93
+ * When sending data messages, if the channel is not in the "open" state, wait
94
+ * this long for the "open" event to fire.
78
95
  */
79
- export const DEFAULT_FIN_ACK_TIMEOUT = 10_000
96
+ export const OPEN_TIMEOUT = 5_000
97
+
98
+ /**
99
+ * When closing a stream, we wait for `bufferedAmount` to become 0 before
100
+ * closing the underlying RTCDataChannel - this controls how long we wait in ms
101
+ */
102
+ export const DATA_CHANNEL_DRAIN_TIMEOUT = 30_000
80
103
 
81
104
  /**
82
105
  * Set as the 'negotiated' muxer protocol name
package/src/index.ts CHANGED
@@ -26,8 +26,8 @@
26
26
  * WebRTC requires use of a relay to connect two nodes. The listener first discovers a relay server and makes a reservation, then the dialer can connect via the relayed address.
27
27
  *
28
28
  * ```TypeScript
29
- * import { noise } from '@libp2p/noise'
30
- * import { yamux } from '@libp2p/yamux'
29
+ * import { noise } from '@chainsafe/libp2p-noise'
30
+ * import { yamux } from '@chainsafe/libp2p-yamux'
31
31
  * import { echo } from '@libp2p/echo'
32
32
  * import { circuitRelayTransport, circuitRelayServer } from '@libp2p/circuit-relay-v2'
33
33
  * import { identify } from '@libp2p/identify'
@@ -35,6 +35,7 @@
35
35
  * import { webSockets } from '@libp2p/websockets'
36
36
  * import { WebRTC } from '@multiformats/multiaddr-matcher'
37
37
  * import delay from 'delay'
38
+ * import { pipe } from 'it-pipe'
38
39
  * import { createLibp2p } from 'libp2p'
39
40
  * import type { Multiaddr } from '@multiformats/multiaddr'
40
41
  *
@@ -133,11 +134,15 @@
133
134
  * await relay.stop()
134
135
  *
135
136
  * // send/receive some data from the remote peer via a direct connection
136
- * stream.send(new TextEncoder().encode('hello world'))
137
- *
138
- * stream.addEventListener('message', (evt) => {
139
- * console.info(new TextDecoder().decode(evt.data.subarray()))
140
- * })
137
+ * await pipe(
138
+ * [new TextEncoder().encode('hello world')],
139
+ * stream,
140
+ * async source => {
141
+ * for await (const buf of source) {
142
+ * console.info(new TextDecoder().decode(buf.subarray()))
143
+ * }
144
+ * }
145
+ * )
141
146
  * ```
142
147
  *
143
148
  * @example WebRTC Direct
@@ -162,6 +167,7 @@
162
167
  * ```TypeScript
163
168
  * import { createLibp2p } from 'libp2p'
164
169
  * import { multiaddr } from '@multiformats/multiaddr'
170
+ * import { pipe } from 'it-pipe'
165
171
  * import { fromString, toString } from 'uint8arrays'
166
172
  * import { webRTCDirect } from '@libp2p/webrtc'
167
173
  *
@@ -190,11 +196,15 @@
190
196
  * signal: AbortSignal.timeout(10_000)
191
197
  * })
192
198
  *
193
- * stream.send(new TextEncoder().encode('hello world'))
194
- *
195
- * stream.addEventListener('message', (evt) => {
196
- * console.info(new TextDecoder().decode(evt.data.subarray()))
197
- * })
199
+ * await pipe(
200
+ * [fromString(`Hello js-libp2p-webrtc\n`)],
201
+ * stream,
202
+ * async function (source) {
203
+ * for await (const buf of source) {
204
+ * console.info(toString(buf.subarray()))
205
+ * }
206
+ * }
207
+ * )
198
208
  * ```
199
209
  *
200
210
  * ## WebRTC Direct certificate hashes
@@ -308,15 +318,6 @@ export interface DataChannelOptions {
308
318
  * @default 5_000
309
319
  */
310
320
  openTimeout?: number
311
-
312
- /**
313
- * Due to bugs in WebRTC implementations it's necessary for the remote end of
314
- * the connection to acknowledge the FIN message we send during stream
315
- * closing. A stream will wait for this many ms.
316
- *
317
- * @default 10_000
318
- */
319
- finAckTimeout?: number
320
321
  }
321
322
 
322
323
  /**
package/src/maconn.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { nopSink, nopSource } from './util.js'
2
+ import type { RTCPeerConnection } from './webrtc/index.js'
3
+ import type { ComponentLogger, Logger, MultiaddrConnection, MultiaddrConnectionTimeline, CounterGroup } from '@libp2p/interface'
4
+ import type { AbortOptions, Multiaddr } from '@multiformats/multiaddr'
5
+ import type { Source, Sink } from 'it-stream-types'
6
+ import type { Uint8ArrayList } from 'uint8arraylist'
7
+
8
+ interface WebRTCMultiaddrConnectionInit {
9
+ /**
10
+ * WebRTC Peer Connection
11
+ */
12
+ peerConnection: RTCPeerConnection
13
+
14
+ /**
15
+ * The multiaddr address used to communicate with the remote peer
16
+ */
17
+ remoteAddr: Multiaddr
18
+
19
+ /**
20
+ * Holds the relevant events timestamps of the connection
21
+ */
22
+ timeline: MultiaddrConnectionTimeline
23
+
24
+ /**
25
+ * Optional metrics counter group for this connection
26
+ */
27
+ metrics?: CounterGroup
28
+ }
29
+
30
+ export interface WebRTCMultiaddrConnectionComponents {
31
+ logger: ComponentLogger
32
+ }
33
+
34
+ export class WebRTCMultiaddrConnection implements MultiaddrConnection {
35
+ readonly log: Logger
36
+
37
+ /**
38
+ * WebRTC Peer Connection
39
+ */
40
+ readonly peerConnection: RTCPeerConnection
41
+
42
+ /**
43
+ * The multiaddr address used to communicate with the remote peer
44
+ */
45
+ remoteAddr: Multiaddr
46
+
47
+ /**
48
+ * Holds the life cycle times of the connection
49
+ */
50
+ timeline: MultiaddrConnectionTimeline
51
+
52
+ /**
53
+ * Optional metrics counter group for this connection
54
+ */
55
+ metrics?: CounterGroup
56
+
57
+ /**
58
+ * The stream source, a no-op as the transport natively supports multiplexing
59
+ */
60
+ source: AsyncGenerator<Uint8Array, any, unknown> = nopSource()
61
+
62
+ /**
63
+ * The stream destination, a no-op as the transport natively supports multiplexing
64
+ */
65
+ sink: Sink<Source<Uint8Array | Uint8ArrayList>, Promise<void>> = nopSink
66
+
67
+ constructor (components: WebRTCMultiaddrConnectionComponents, init: WebRTCMultiaddrConnectionInit) {
68
+ this.log = components.logger.forComponent('libp2p:webrtc:maconn')
69
+ this.remoteAddr = init.remoteAddr
70
+ this.timeline = init.timeline
71
+ this.peerConnection = init.peerConnection
72
+
73
+ const peerConnection = this.peerConnection
74
+ const initialState = peerConnection.connectionState
75
+
76
+ this.peerConnection.onconnectionstatechange = () => {
77
+ this.log.trace('peer connection state change', peerConnection.connectionState, 'initial state', initialState)
78
+
79
+ if (peerConnection.connectionState === 'disconnected' || peerConnection.connectionState === 'failed' || peerConnection.connectionState === 'closed') {
80
+ // nothing else to do but close the connection
81
+ this.timeline.close = Date.now()
82
+ }
83
+ }
84
+ }
85
+
86
+ async close (options?: AbortOptions): Promise<void> {
87
+ this.log.trace('closing connection')
88
+
89
+ this.peerConnection.close()
90
+ this.timeline.close = Date.now()
91
+ this.metrics?.increment({ close: true })
92
+ }
93
+
94
+ abort (err: Error): void {
95
+ this.log.error('closing connection due to error', err)
96
+
97
+ this.peerConnection.close()
98
+ this.timeline.close = Date.now()
99
+ this.metrics?.increment({ abort: true })
100
+ }
101
+ }
package/src/muxer.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { AbstractStreamMuxer } from '@libp2p/utils'
2
1
  import { MUXER_PROTOCOL } from './constants.js'
3
- import { createStream, WebRTCStream } from './stream.js'
2
+ import { createStream } from './stream.js'
3
+ import { drainAndClose, nopSink, nopSource } from './util.js'
4
4
  import type { DataChannelOptions } from './index.js'
5
- import type { ComponentLogger, CounterGroup, StreamMuxer, StreamMuxerFactory, CreateStreamOptions, MultiaddrConnection } from '@libp2p/interface'
5
+ import type { ComponentLogger, Logger, Stream, CounterGroup, StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface'
6
+ import type { AbortOptions } from '@multiformats/multiaddr'
7
+ import type { Source, Sink } from 'it-stream-types'
8
+ import type { Uint8ArrayList } from 'uint8arraylist'
6
9
 
7
10
  export interface DataChannelMuxerFactoryInit {
8
11
  /**
@@ -20,9 +23,6 @@ export interface DataChannelMuxerFactoryInit {
20
23
  */
21
24
  metrics?: CounterGroup
22
25
 
23
- /**
24
- * Options used to create data channels
25
- */
26
26
  dataChannelOptions?: DataChannelOptions
27
27
  }
28
28
 
@@ -30,6 +30,12 @@ export interface DataChannelMuxerFactoryComponents {
30
30
  logger: ComponentLogger
31
31
  }
32
32
 
33
+ interface BufferedStream {
34
+ stream: Stream
35
+ channel: RTCDataChannel
36
+ onEnd(err?: Error): void
37
+ }
38
+
33
39
  export class DataChannelMuxerFactory implements StreamMuxerFactory {
34
40
  public readonly protocol: string
35
41
 
@@ -37,28 +43,69 @@ export class DataChannelMuxerFactory implements StreamMuxerFactory {
37
43
  * WebRTC Peer Connection
38
44
  */
39
45
  private readonly peerConnection: RTCPeerConnection
46
+ private bufferedStreams: BufferedStream[] = []
40
47
  private readonly metrics?: CounterGroup
41
48
  private readonly dataChannelOptions?: DataChannelOptions
49
+ private readonly components: DataChannelMuxerFactoryComponents
50
+ private readonly log: Logger
42
51
 
43
- constructor (init: DataChannelMuxerFactoryInit) {
52
+ constructor (components: DataChannelMuxerFactoryComponents, init: DataChannelMuxerFactoryInit) {
53
+ this.components = components
44
54
  this.peerConnection = init.peerConnection
45
55
  this.metrics = init.metrics
46
56
  this.protocol = init.protocol ?? MUXER_PROTOCOL
47
57
  this.dataChannelOptions = init.dataChannelOptions ?? {}
58
+ this.log = components.logger.forComponent('libp2p:webrtc:muxerfactory')
59
+
60
+ // store any data channels opened before upgrade has been completed
61
+ this.peerConnection.ondatachannel = ({ channel }) => {
62
+ this.log.trace('incoming early datachannel with channel id %d and label "%s"', channel.id)
63
+
64
+ // 'init' channel is only used during connection establishment
65
+ if (channel.label === 'init') {
66
+ this.log.trace('closing early init channel')
67
+ channel.close()
68
+
69
+ return
70
+ }
71
+
72
+ // @ts-expect-error fields are set below
73
+ const bufferedStream: BufferedStream = {}
74
+
75
+ const stream = createStream({
76
+ channel,
77
+ direction: 'inbound',
78
+ onEnd: (err) => {
79
+ bufferedStream.onEnd(err)
80
+ },
81
+ log: this.log,
82
+ ...this.dataChannelOptions
83
+ })
84
+
85
+ bufferedStream.stream = stream
86
+ bufferedStream.channel = channel
87
+ bufferedStream.onEnd = () => {
88
+ this.bufferedStreams = this.bufferedStreams.filter(s => s.stream.id !== stream.id)
89
+ }
90
+
91
+ this.bufferedStreams.push(bufferedStream)
92
+ }
48
93
  }
49
94
 
50
- createStreamMuxer (maConn: MultiaddrConnection): StreamMuxer {
51
- return new DataChannelMuxer(maConn, {
95
+ createStreamMuxer (init?: StreamMuxerInit): StreamMuxer {
96
+ return new DataChannelMuxer(this.components, {
97
+ ...init,
52
98
  peerConnection: this.peerConnection,
53
99
  dataChannelOptions: this.dataChannelOptions,
54
100
  metrics: this.metrics,
101
+ streams: this.bufferedStreams,
55
102
  protocol: this.protocol
56
103
  })
57
104
  }
58
105
  }
59
106
 
60
- export interface DataChannelMuxerInit extends DataChannelMuxerFactoryInit {
61
- protocol: string
107
+ export interface DataChannelMuxerInit extends DataChannelMuxerFactoryInit, StreamMuxerInit {
108
+ streams: BufferedStream[]
62
109
  }
63
110
 
64
111
  export interface DataChannelMuxerComponents {
@@ -68,18 +115,26 @@ export interface DataChannelMuxerComponents {
68
115
  /**
69
116
  * A libp2p data channel stream muxer
70
117
  */
71
- export class DataChannelMuxer extends AbstractStreamMuxer<WebRTCStream> implements StreamMuxer<WebRTCStream> {
118
+ export class DataChannelMuxer implements StreamMuxer {
119
+ /**
120
+ * Array of streams in the data channel
121
+ */
122
+ public streams: Stream[]
123
+ public protocol: string
124
+
125
+ private readonly log: Logger
72
126
  private readonly peerConnection: RTCPeerConnection
73
127
  private readonly dataChannelOptions: DataChannelOptions
128
+ private readonly metrics?: CounterGroup
129
+ private readonly logger: ComponentLogger
74
130
 
75
- constructor (maConn: MultiaddrConnection, init: DataChannelMuxerInit) {
76
- super(maConn, {
77
- ...init,
78
- name: 'muxer'
79
- })
80
-
131
+ constructor (components: DataChannelMuxerComponents, readonly init: DataChannelMuxerInit) {
132
+ this.log = init.log?.newScope('muxer') ?? components.logger.forComponent('libp2p:webrtc:muxer')
133
+ this.logger = components.logger
134
+ this.streams = init.streams.map(s => s.stream)
81
135
  this.peerConnection = init.peerConnection
82
136
  this.protocol = init.protocol ?? MUXER_PROTOCOL
137
+ this.metrics = init.metrics
83
138
  this.dataChannelOptions = init.dataChannelOptions ?? {}
84
139
 
85
140
  /**
@@ -89,50 +144,125 @@ export class DataChannelMuxer extends AbstractStreamMuxer<WebRTCStream> implemen
89
144
  * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/datachannel_event}
90
145
  */
91
146
  this.peerConnection.ondatachannel = ({ channel }) => {
92
- this.log.trace('incoming %s datachannel with channel id %d, protocol %s and status %s', channel.protocol, channel.id, channel.protocol, channel.readyState)
147
+ this.log.trace('incoming datachannel with channel id %d', channel.id)
93
148
 
94
- // 'init' channel is only used during connection establishment, it is
95
- // closed by the initiator
149
+ // 'init' channel is only used during connection establishment
96
150
  if (channel.label === 'init') {
97
- this.log.trace('closing init channel %d', channel.id)
151
+ this.log.trace('closing init channel')
98
152
  channel.close()
99
153
 
100
154
  return
101
155
  }
102
156
 
157
+ // lib-datachannel throws if `.getId` is called on a closed channel so
158
+ // memoize it
159
+ const id = channel.id
160
+
103
161
  const stream = createStream({
104
- ...this.streamOptions,
105
- ...this.dataChannelOptions,
106
162
  channel,
107
163
  direction: 'inbound',
108
- log: this.log
164
+ onEnd: () => {
165
+ this.#onStreamEnd(stream, channel)
166
+ this.log('incoming channel %s ended', id)
167
+ },
168
+ log: this.log,
169
+ ...this.dataChannelOptions
170
+ })
171
+
172
+ this.streams.push(stream)
173
+ this.metrics?.increment({ incoming_stream: true })
174
+ init?.onIncomingStream?.(stream)
175
+ }
176
+
177
+ // the DataChannelMuxer constructor is called during set up of the
178
+ // connection by the upgrader.
179
+ //
180
+ // If we invoke `init.onIncomingStream` immediately, the connection object
181
+ // will not be set up yet so add a tiny delay before letting the
182
+ // connection know about early streams
183
+ if (this.init.streams.length > 0) {
184
+ queueMicrotask(() => {
185
+ this.init.streams.forEach(bufferedStream => {
186
+ bufferedStream.onEnd = () => {
187
+ this.log('incoming early channel %s ended with state %s', bufferedStream.channel.id, bufferedStream.channel.readyState)
188
+ this.#onStreamEnd(bufferedStream.stream, bufferedStream.channel)
189
+ }
190
+
191
+ this.metrics?.increment({ incoming_stream: true })
192
+ this.init?.onIncomingStream?.(bufferedStream.stream)
193
+ })
109
194
  })
195
+ }
196
+ }
197
+
198
+ #onStreamEnd (stream: Stream, channel: RTCDataChannel): void {
199
+ this.log.trace('stream %s %s %s onEnd', stream.direction, stream.id, stream.protocol)
200
+ drainAndClose(
201
+ channel,
202
+ `${stream.direction} ${stream.id} ${stream.protocol}`,
203
+ this.dataChannelOptions.drainTimeout, {
204
+ log: this.log
205
+ }
206
+ )
207
+ this.streams = this.streams.filter(s => s.id !== stream.id)
208
+ this.metrics?.increment({ stream_end: true })
209
+ this.init?.onStreamEnd?.(stream)
210
+ }
110
211
 
111
- this.onRemoteStream(stream)
212
+ /**
213
+ * Gracefully close all tracked streams and stop the muxer
214
+ */
215
+ async close (options?: AbortOptions): Promise<void> {
216
+ try {
217
+ await Promise.all(
218
+ this.streams.map(async stream => stream.close(options))
219
+ )
220
+ } catch (err: any) {
221
+ this.abort(err)
112
222
  }
113
223
  }
114
224
 
115
- async onCreateStream (options?: CreateStreamOptions): Promise<WebRTCStream> {
225
+ /**
226
+ * Abort all tracked streams and stop the muxer
227
+ */
228
+ abort (err: Error): void {
229
+ for (const stream of this.streams) {
230
+ stream.abort(err)
231
+ }
232
+ }
233
+
234
+ /**
235
+ * The stream source, a no-op as the transport natively supports multiplexing
236
+ */
237
+ source: AsyncGenerator<Uint8Array, any, unknown> = nopSource()
238
+
239
+ /**
240
+ * The stream destination, a no-op as the transport natively supports multiplexing
241
+ */
242
+ sink: Sink<Source<Uint8Array | Uint8ArrayList>, Promise<void>> = nopSink
243
+
244
+ newStream (): Stream {
116
245
  // The spec says the label MUST be an empty string: https://github.com/libp2p/specs/blob/master/webrtc/README.md#rtcdatachannel-label
117
- const channel = this.peerConnection.createDataChannel('', {
118
- // TODO: pre-negotiate stream protocol
119
- // protocol: options?.protocol
120
- })
246
+ const channel = this.peerConnection.createDataChannel('')
247
+ // lib-datachannel throws if `.getId` is called on a closed channel so
248
+ // memoize it
249
+ const id = channel.id
121
250
 
122
- this.log('open channel %d for protocol %s', channel.id, options?.protocol)
251
+ this.log.trace('opened outgoing datachannel with channel id %s', id)
123
252
 
124
253
  const stream = createStream({
125
- ...options,
126
- ...this.dataChannelOptions,
127
254
  channel,
128
255
  direction: 'outbound',
129
- log: this.log
256
+ onEnd: () => {
257
+ this.#onStreamEnd(stream, channel)
258
+ this.log('outgoing channel %s ended', id)
259
+ },
260
+ log: this.log,
261
+ ...this.dataChannelOptions
130
262
  })
263
+ this.streams.push(stream)
264
+ this.metrics?.increment({ outgoing_stream: true })
131
265
 
132
266
  return stream
133
267
  }
134
-
135
- onData (): void {
136
-
137
- }
138
268
  }