@libp2p/webrtc 5.2.24-8484de8a2 → 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 (89) hide show
  1. package/README.md +20 -10
  2. package/dist/index.min.js +17 -17
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +23 -0
  5. package/dist/src/constants.d.ts.map +1 -1
  6. package/dist/src/constants.js +23 -0
  7. package/dist/src/constants.js.map +1 -1
  8. package/dist/src/index.d.ts +22 -12
  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 +135 -32
  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 -23
  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 +6 -10
  27. package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -1
  28. package/dist/src/private-to-private/transport.d.ts +2 -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 +1 -3
  33. package/dist/src/private-to-private/util.d.ts.map +1 -1
  34. package/dist/src/private-to-private/util.js +3 -15
  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.map +1 -1
  40. package/dist/src/private-to-public/transport.js +2 -3
  41. package/dist/src/private-to-public/transport.js.map +1 -1
  42. package/dist/src/private-to-public/utils/connect.d.ts +1 -1
  43. package/dist/src/private-to-public/utils/connect.d.ts.map +1 -1
  44. package/dist/src/private-to-public/utils/connect.js +14 -17
  45. package/dist/src/private-to-public/utils/connect.js.map +1 -1
  46. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +4 -4
  47. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -1
  48. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +2 -13
  49. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -1
  50. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
  51. package/dist/src/private-to-public/utils/sdp.js +13 -25
  52. package/dist/src/private-to-public/utils/sdp.js.map +1 -1
  53. package/dist/src/private-to-public/utils/stun-listener.js +1 -1
  54. package/dist/src/private-to-public/utils/stun-listener.js.map +1 -1
  55. package/dist/src/stream.d.ts +26 -13
  56. package/dist/src/stream.d.ts.map +1 -1
  57. package/dist/src/stream.js +166 -64
  58. package/dist/src/stream.js.map +1 -1
  59. package/dist/src/util.d.ts +1 -3
  60. package/dist/src/util.d.ts.map +1 -1
  61. package/dist/src/util.js +0 -19
  62. package/dist/src/util.js.map +1 -1
  63. package/dist/src/webrtc/index.d.ts +1 -1
  64. package/dist/src/webrtc/index.d.ts.map +1 -1
  65. package/dist/src/webrtc/index.js +1 -1
  66. package/dist/src/webrtc/index.js.map +1 -1
  67. package/package.json +29 -26
  68. package/src/constants.ts +28 -0
  69. package/src/index.ts +22 -12
  70. package/src/maconn.ts +101 -0
  71. package/src/muxer.ts +166 -43
  72. package/src/private-to-private/initiate-connection.ts +8 -30
  73. package/src/private-to-private/signaling-stream-handler.ts +9 -12
  74. package/src/private-to-private/transport.ts +17 -33
  75. package/src/private-to-private/util.ts +4 -21
  76. package/src/private-to-public/listener.ts +15 -22
  77. package/src/private-to-public/transport.ts +2 -3
  78. package/src/private-to-public/utils/connect.ts +15 -18
  79. package/src/private-to-public/utils/get-rtcpeerconnection.ts +4 -16
  80. package/src/private-to-public/utils/sdp.ts +13 -29
  81. package/src/private-to-public/utils/stun-listener.ts +1 -1
  82. package/src/stream.ts +194 -74
  83. package/src/util.ts +1 -22
  84. package/src/webrtc/index.ts +1 -1
  85. package/dist/src/rtcpeerconnection-to-conn.d.ts +0 -12
  86. package/dist/src/rtcpeerconnection-to-conn.d.ts.map +0 -1
  87. package/dist/src/rtcpeerconnection-to-conn.js +0 -43
  88. package/dist/src/rtcpeerconnection-to-conn.js.map +0 -1
  89. package/src/rtcpeerconnection-to-conn.ts +0 -62
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/webrtc",
3
- "version": "5.2.24-8484de8a2",
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-8484de8a2",
49
- "@libp2p/interface": "2.11.0-8484de8a2",
50
- "@libp2p/interface-internal": "2.3.19-8484de8a2",
51
- "@libp2p/keychain": "5.2.9-8484de8a2",
52
- "@libp2p/noise": "16.1.4-8484de8a2",
53
- "@libp2p/peer-id": "5.1.9-8484de8a2",
54
- "@libp2p/utils": "6.7.2-8484de8a2",
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-8484de8a2",
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.21",
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
@@ -67,6 +82,19 @@ function calculateProtobufOverhead (maxMessageSize = MAX_MESSAGE_SIZE): number {
67
82
  */
68
83
  export const PROTOBUF_OVERHEAD = calculateProtobufOverhead()
69
84
 
85
+ /**
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.
89
+ */
90
+ export const FIN_ACK_TIMEOUT = 5_000
91
+
92
+ /**
93
+ * When sending data messages, if the channel is not in the "open" state, wait
94
+ * this long for the "open" event to fire.
95
+ */
96
+ export const OPEN_TIMEOUT = 5_000
97
+
70
98
  /**
71
99
  * When closing a stream, we wait for `bufferedAmount` to become 0 before
72
100
  * closing the underlying RTCDataChannel - this controls how long we wait in ms
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
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,9 +1,11 @@
1
- import { AbstractStreamMuxer } from '@libp2p/utils'
2
- import { pEvent } from 'p-event'
3
1
  import { MUXER_PROTOCOL } from './constants.js'
4
- import { createStream, WebRTCStream } from './stream.js'
2
+ import { createStream } from './stream.js'
3
+ import { drainAndClose, nopSink, nopSource } from './util.js'
5
4
  import type { DataChannelOptions } from './index.js'
6
- 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'
7
9
 
8
10
  export interface DataChannelMuxerFactoryInit {
9
11
  /**
@@ -21,9 +23,6 @@ export interface DataChannelMuxerFactoryInit {
21
23
  */
22
24
  metrics?: CounterGroup
23
25
 
24
- /**
25
- * Options used to create data channels
26
- */
27
26
  dataChannelOptions?: DataChannelOptions
28
27
  }
29
28
 
@@ -31,6 +30,12 @@ export interface DataChannelMuxerFactoryComponents {
31
30
  logger: ComponentLogger
32
31
  }
33
32
 
33
+ interface BufferedStream {
34
+ stream: Stream
35
+ channel: RTCDataChannel
36
+ onEnd(err?: Error): void
37
+ }
38
+
34
39
  export class DataChannelMuxerFactory implements StreamMuxerFactory {
35
40
  public readonly protocol: string
36
41
 
@@ -38,28 +43,69 @@ export class DataChannelMuxerFactory implements StreamMuxerFactory {
38
43
  * WebRTC Peer Connection
39
44
  */
40
45
  private readonly peerConnection: RTCPeerConnection
46
+ private bufferedStreams: BufferedStream[] = []
41
47
  private readonly metrics?: CounterGroup
42
48
  private readonly dataChannelOptions?: DataChannelOptions
49
+ private readonly components: DataChannelMuxerFactoryComponents
50
+ private readonly log: Logger
43
51
 
44
- constructor (init: DataChannelMuxerFactoryInit) {
52
+ constructor (components: DataChannelMuxerFactoryComponents, init: DataChannelMuxerFactoryInit) {
53
+ this.components = components
45
54
  this.peerConnection = init.peerConnection
46
55
  this.metrics = init.metrics
47
56
  this.protocol = init.protocol ?? MUXER_PROTOCOL
48
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
+ }
49
93
  }
50
94
 
51
- createStreamMuxer (maConn: MultiaddrConnection): StreamMuxer {
52
- return new DataChannelMuxer(maConn, {
95
+ createStreamMuxer (init?: StreamMuxerInit): StreamMuxer {
96
+ return new DataChannelMuxer(this.components, {
97
+ ...init,
53
98
  peerConnection: this.peerConnection,
54
99
  dataChannelOptions: this.dataChannelOptions,
55
100
  metrics: this.metrics,
101
+ streams: this.bufferedStreams,
56
102
  protocol: this.protocol
57
103
  })
58
104
  }
59
105
  }
60
106
 
61
- export interface DataChannelMuxerInit extends DataChannelMuxerFactoryInit {
62
- protocol: string
107
+ export interface DataChannelMuxerInit extends DataChannelMuxerFactoryInit, StreamMuxerInit {
108
+ streams: BufferedStream[]
63
109
  }
64
110
 
65
111
  export interface DataChannelMuxerComponents {
@@ -69,18 +115,26 @@ export interface DataChannelMuxerComponents {
69
115
  /**
70
116
  * A libp2p data channel stream muxer
71
117
  */
72
- 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
73
126
  private readonly peerConnection: RTCPeerConnection
74
127
  private readonly dataChannelOptions: DataChannelOptions
128
+ private readonly metrics?: CounterGroup
129
+ private readonly logger: ComponentLogger
75
130
 
76
- constructor (maConn: MultiaddrConnection, init: DataChannelMuxerInit) {
77
- super(maConn, {
78
- ...init,
79
- name: 'muxer'
80
- })
81
-
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)
82
135
  this.peerConnection = init.peerConnection
83
136
  this.protocol = init.protocol ?? MUXER_PROTOCOL
137
+ this.metrics = init.metrics
84
138
  this.dataChannelOptions = init.dataChannelOptions ?? {}
85
139
 
86
140
  /**
@@ -90,7 +144,7 @@ export class DataChannelMuxer extends AbstractStreamMuxer<WebRTCStream> implemen
90
144
  * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/datachannel_event}
91
145
  */
92
146
  this.peerConnection.ondatachannel = ({ channel }) => {
93
- this.log.trace('incoming %s datachannel with channel id %d and status', channel.protocol, channel.id, channel.readyState)
147
+ this.log.trace('incoming datachannel with channel id %d', channel.id)
94
148
 
95
149
  // 'init' channel is only used during connection establishment
96
150
  if (channel.label === 'init') {
@@ -100,46 +154,115 @@ export class DataChannelMuxer extends AbstractStreamMuxer<WebRTCStream> implemen
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
109
170
  })
110
171
 
111
- this.onRemoteStream(stream)
172
+ this.streams.push(stream)
173
+ this.metrics?.increment({ incoming_stream: true })
174
+ init?.onIncomingStream?.(stream)
112
175
  }
113
- }
114
176
 
115
- async onCreateStream (options?: CreateStreamOptions): Promise<WebRTCStream> {
116
- // 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('wtf', {
118
- // TODO: pre-negotiate stream protocol
119
- protocol: options?.protocol
120
- })
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
+ })
194
+ })
195
+ }
196
+ }
121
197
 
122
- this.log('open channel %d for protocol %s', channel.id, options?.protocol)
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
+ }
123
211
 
124
- if (channel.readyState !== 'open') {
125
- this.log('channel %d state is "%s" and not "open", waiting for "open" event before sending data', channel.id, channel.readyState)
126
- await pEvent(channel, 'open', options)
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)
222
+ }
223
+ }
127
224
 
128
- this.log('channel %d state is now "%s", sending data', channel.id, channel.readyState)
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)
129
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 {
245
+ // The spec says the label MUST be an empty string: https://github.com/libp2p/specs/blob/master/webrtc/README.md#rtcdatachannel-label
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
250
+
251
+ this.log.trace('opened outgoing datachannel with channel id %s', id)
130
252
 
131
253
  const stream = createStream({
132
- ...options,
133
- ...this.dataChannelOptions,
134
254
  channel,
135
255
  direction: 'outbound',
136
- 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
137
262
  })
263
+ this.streams.push(stream)
264
+ this.metrics?.increment({ outgoing_stream: true })
138
265
 
139
266
  return stream
140
267
  }
141
-
142
- onData (): void {
143
-
144
- }
145
268
  }
@@ -1,5 +1,4 @@
1
- import { pbStream } from '@libp2p/utils'
2
- import { pEvent } from 'p-event'
1
+ import { pbStream } from 'it-protobuf-stream'
3
2
  import { CustomProgressEvent } from 'progress-events'
4
3
  import { SIGNALING_PROTOCOL } from '../constants.js'
5
4
  import { SDPHandshakeFailedError } from '../error.js'
@@ -10,14 +9,15 @@ import { splitAddr } from './transport.js'
10
9
  import { readCandidatesUntilConnected } from './util.js'
11
10
  import type { WebRTCDialEvents, WebRTCTransportMetrics } from './transport.js'
12
11
  import type { DataChannelOptions } from '../index.js'
13
- import type { LoggerOptions, Connection, ComponentLogger, AbortOptions } from '@libp2p/interface'
12
+ import type { LoggerOptions, Connection, ComponentLogger, IncomingStreamData } from '@libp2p/interface'
14
13
  import type { ConnectionManager, TransportManager } from '@libp2p/interface-internal'
15
14
  import type { Multiaddr } from '@multiformats/multiaddr'
16
15
  import type { ProgressOptions } from 'progress-events'
17
16
 
18
- export interface IncomingStreamOptions extends AbortOptions {
17
+ export interface IncomingStreamOpts extends IncomingStreamData {
19
18
  rtcConfiguration?: RTCConfiguration
20
19
  dataChannelOptions?: Partial<DataChannelOptions>
20
+ signal: AbortSignal
21
21
  }
22
22
 
23
23
  export interface ConnectOptions extends LoggerOptions, ProgressOptions<WebRTCDialEvents> {
@@ -67,21 +67,9 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa
67
67
 
68
68
  const messageStream = pbStream(stream).pb(Message)
69
69
  const peerConnection = new RTCPeerConnection(rtcConfiguration)
70
-
71
- // make sure C++ peer connection is garbage collected
72
- // https://github.com/murat-dogan/node-datachannel/issues/366#issuecomment-3228453155
73
- peerConnection.addEventListener('connectionstatechange', () => {
74
- switch (peerConnection.connectionState) {
75
- case 'closed':
76
- peerConnection.close()
77
- break
78
- default:
79
- break
80
- }
81
- })
82
-
83
70
  const muxerFactory = new DataChannelMuxerFactory({
84
- // @ts-expect-error https://github.com/murat-dogan/node-datachannel/pull/370
71
+ logger
72
+ }, {
85
73
  peerConnection,
86
74
  dataChannelOptions: dataChannel
87
75
  })
@@ -109,7 +97,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa
109
97
  signal
110
98
  })
111
99
  .catch(err => {
112
- log.error('error sending ICE candidate - %e', err)
100
+ log.error('error sending ICE candidate', err)
113
101
  })
114
102
  }
115
103
  peerConnection.onicecandidateerror = (event) => {
@@ -169,17 +157,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa
169
157
  onProgress
170
158
  })
171
159
 
172
- log.trace('initiator connected')
173
-
174
- if (channel.readyState !== 'open') {
175
- log.trace('wait for init channel to open')
176
- await pEvent(channel, 'open', {
177
- signal
178
- })
179
- }
180
-
181
- log.trace('closing init channel, starting status')
182
-
160
+ log.trace('initiator connected, closing init channel')
183
161
  channel.close()
184
162
 
185
163
  onProgress?.(new CustomProgressEvent('webrtc:close-signaling-stream'))