@libp2p/webrtc 5.2.24-a02cb0461 → 5.2.24-da78fa851

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 (51) hide show
  1. package/dist/index.min.js +17 -17
  2. package/dist/index.min.js.map +4 -4
  3. package/dist/src/constants.d.ts +4 -8
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +4 -8
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +8 -0
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/muxer.d.ts.map +1 -1
  11. package/dist/src/muxer.js +7 -12
  12. package/dist/src/muxer.js.map +1 -1
  13. package/dist/src/private-to-private/initiate-connection.d.ts +2 -2
  14. package/dist/src/private-to-private/initiate-connection.d.ts.map +1 -1
  15. package/dist/src/private-to-private/initiate-connection.js +15 -1
  16. package/dist/src/private-to-private/initiate-connection.js.map +1 -1
  17. package/dist/src/private-to-private/signaling-stream-handler.d.ts.map +1 -1
  18. package/dist/src/private-to-private/signaling-stream-handler.js +10 -2
  19. package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -1
  20. package/dist/src/private-to-private/util.d.ts +0 -1
  21. package/dist/src/private-to-private/util.d.ts.map +1 -1
  22. package/dist/src/private-to-private/util.js +11 -11
  23. package/dist/src/private-to-private/util.js.map +1 -1
  24. package/dist/src/private-to-public/listener.d.ts.map +1 -1
  25. package/dist/src/private-to-public/listener.js +20 -14
  26. package/dist/src/private-to-public/listener.js.map +1 -1
  27. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  28. package/dist/src/private-to-public/transport.js +2 -1
  29. package/dist/src/private-to-public/transport.js.map +1 -1
  30. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
  31. package/dist/src/private-to-public/utils/sdp.js +17 -10
  32. package/dist/src/private-to-public/utils/sdp.js.map +1 -1
  33. package/dist/src/rtcpeerconnection-to-conn.d.ts.map +1 -1
  34. package/dist/src/rtcpeerconnection-to-conn.js +3 -0
  35. package/dist/src/rtcpeerconnection-to-conn.js.map +1 -1
  36. package/dist/src/stream.d.ts +3 -2
  37. package/dist/src/stream.d.ts.map +1 -1
  38. package/dist/src/stream.js +100 -68
  39. package/dist/src/stream.js.map +1 -1
  40. package/package.json +12 -12
  41. package/src/constants.ts +5 -10
  42. package/src/index.ts +9 -0
  43. package/src/muxer.ts +6 -13
  44. package/src/private-to-private/initiate-connection.ts +20 -4
  45. package/src/private-to-private/signaling-stream-handler.ts +12 -2
  46. package/src/private-to-private/util.ts +12 -12
  47. package/src/private-to-public/listener.ts +21 -14
  48. package/src/private-to-public/transport.ts +2 -1
  49. package/src/private-to-public/utils/sdp.ts +21 -10
  50. package/src/rtcpeerconnection-to-conn.ts +4 -0
  51. package/src/stream.ts +115 -79
package/src/muxer.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { AbstractStreamMuxer } from '@libp2p/utils'
2
- import { pEvent } from 'p-event'
3
2
  import { MUXER_PROTOCOL } from './constants.js'
4
3
  import { createStream, WebRTCStream } from './stream.js'
5
4
  import type { DataChannelOptions } from './index.js'
@@ -90,11 +89,12 @@ export class DataChannelMuxer extends AbstractStreamMuxer<WebRTCStream> implemen
90
89
  * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/datachannel_event}
91
90
  */
92
91
  this.peerConnection.ondatachannel = ({ channel }) => {
93
- this.log.trace('incoming %s datachannel with channel id %d and status', channel.protocol, channel.id, channel.readyState)
92
+ this.log.trace('incoming %s datachannel with channel id %d, protocol %s and status %s', channel.protocol, channel.id, channel.protocol, channel.readyState)
94
93
 
95
- // 'init' channel is only used during connection establishment
94
+ // 'init' channel is only used during connection establishment, it is
95
+ // closed by the initiator
96
96
  if (channel.label === 'init') {
97
- this.log.trace('closing init channel')
97
+ this.log.trace('closing init channel %d', channel.id)
98
98
  channel.close()
99
99
 
100
100
  return
@@ -114,20 +114,13 @@ export class DataChannelMuxer extends AbstractStreamMuxer<WebRTCStream> implemen
114
114
 
115
115
  async onCreateStream (options?: CreateStreamOptions): Promise<WebRTCStream> {
116
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', {
117
+ const channel = this.peerConnection.createDataChannel('', {
118
118
  // TODO: pre-negotiate stream protocol
119
- protocol: options?.protocol
119
+ // protocol: options?.protocol
120
120
  })
121
121
 
122
122
  this.log('open channel %d for protocol %s', channel.id, options?.protocol)
123
123
 
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)
127
-
128
- this.log('channel %d state is now "%s", sending data', channel.id, channel.readyState)
129
- }
130
-
131
124
  const stream = createStream({
132
125
  ...options,
133
126
  ...this.dataChannelOptions,
@@ -10,9 +10,9 @@ import { splitAddr } from './transport.js'
10
10
  import { readCandidatesUntilConnected } from './util.js'
11
11
  import type { WebRTCDialEvents, WebRTCTransportMetrics } from './transport.js'
12
12
  import type { DataChannelOptions } from '../index.js'
13
- import type { LoggerOptions, Connection, ComponentLogger } from '@libp2p/interface'
13
+ import type { LoggerOptions, Connection, ComponentLogger, AbortOptions } from '@libp2p/interface'
14
14
  import type { ConnectionManager, TransportManager } from '@libp2p/interface-internal'
15
- import type { AbortOptions, Multiaddr } from '@multiformats/multiaddr'
15
+ import type { Multiaddr } from '@multiformats/multiaddr'
16
16
  import type { ProgressOptions } from 'progress-events'
17
17
 
18
18
  export interface IncomingStreamOptions extends AbortOptions {
@@ -94,10 +94,20 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa
94
94
 
95
95
  // setup callback to write ICE candidates to the remote peer
96
96
  peerConnection.onicecandidate = ({ candidate }) => {
97
+ if (peerConnection.connectionState === 'connected') {
98
+ log.trace('ignore new ice candidate as peer connection is already connected')
99
+ return
100
+ }
101
+
97
102
  // a null candidate means end-of-candidates, an empty string candidate
98
103
  // means end-of-candidates for this generation, otherwise this should
99
104
  // be a valid candidate object
100
105
  // see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
106
+ if (candidate == null || candidate?.candidate === '') {
107
+ log.trace('initiator detected end of ICE candidates')
108
+ return
109
+ }
110
+
101
111
  const data = JSON.stringify(candidate?.toJSON() ?? null)
102
112
 
103
113
  log.trace('initiator sending ICE candidate %o', candidate)
@@ -178,10 +188,16 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa
178
188
  })
179
189
  }
180
190
 
181
- log.trace('closing init channel, starting status')
182
-
191
+ log.trace('closing init channel')
183
192
  channel.close()
184
193
 
194
+ // wait for init channel to close before proceeding, otherwise the channel
195
+ // id can be reused before both sides have seen the channel close
196
+ log.trace('waiting for init channel to close')
197
+ await pEvent(channel, 'close', {
198
+ signal
199
+ })
200
+
185
201
  onProgress?.(new CustomProgressEvent('webrtc:close-signaling-stream'))
186
202
 
187
203
  log.trace('closing signaling channel')
@@ -3,7 +3,7 @@ import { multiaddr } from '@multiformats/multiaddr'
3
3
  import { SDPHandshakeFailedError } from '../error.js'
4
4
  import { RTCSessionDescription } from '../webrtc/index.js'
5
5
  import { Message } from './pb/message.js'
6
- import { getConnectionState, getRemotePeer, readCandidatesUntilConnected } from './util.js'
6
+ import { getRemotePeer, readCandidatesUntilConnected } from './util.js'
7
7
  import type { RTCPeerConnection } from '../webrtc/index.js'
8
8
  import type { AbortOptions, Connection, Logger, PeerId, Stream } from '@libp2p/interface'
9
9
  import type { Multiaddr } from '@multiformats/multiaddr'
@@ -21,10 +21,20 @@ export async function handleIncomingStream (stream: Stream, connection: Connecti
21
21
  try {
22
22
  // candidate callbacks
23
23
  peerConnection.onicecandidate = ({ candidate }) => {
24
+ if (peerConnection.connectionState === 'connected') {
25
+ log.trace('ignore new ice candidate as peer connection is already connected')
26
+ return
27
+ }
28
+
24
29
  // a null candidate means end-of-candidates, an empty string candidate
25
30
  // means end-of-candidates for this generation, otherwise this should
26
31
  // be a valid candidate object
27
32
  // see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
33
+ if (candidate == null || candidate?.candidate === '') {
34
+ log.trace('recipient detected end of ICE candidates')
35
+ return
36
+ }
37
+
28
38
  const data = JSON.stringify(candidate?.toJSON() ?? null)
29
39
 
30
40
  log.trace('recipient sending ICE candidate %s', data)
@@ -90,7 +100,7 @@ export async function handleIncomingStream (stream: Stream, connection: Connecti
90
100
  log
91
101
  })
92
102
  } catch (err: any) {
93
- if (getConnectionState(peerConnection) !== 'connected') {
103
+ if (peerConnection.connectionState !== 'connected') {
94
104
  log.error('error while handling signaling stream from peer %a', connection.remoteAddr, err)
95
105
 
96
106
  peerConnection.close()
@@ -1,7 +1,6 @@
1
1
  import { ConnectionFailedError, InvalidMessageError, InvalidMultiaddrError } from '@libp2p/interface'
2
2
  import { peerIdFromString } from '@libp2p/peer-id'
3
3
  import { CustomProgressEvent } from 'progress-events'
4
- import { isFirefox } from '../util.js'
5
4
  import { RTCIceCandidate } from '../webrtc/index.js'
6
5
  import { Message } from './pb/message.js'
7
6
  import type { WebRTCDialEvents } from './transport.js'
@@ -28,7 +27,7 @@ export const readCandidatesUntilConnected = async (pc: RTCPeerConnection, stream
28
27
  connectedPromise.promise,
29
28
  stream.read({
30
29
  signal: options.signal
31
- }).catch(() => {})
30
+ })
32
31
  ])
33
32
 
34
33
  // stream ended or we became connected
@@ -63,32 +62,33 @@ export const readCandidatesUntilConnected = async (pc: RTCPeerConnection, stream
63
62
  options.onProgress?.(new CustomProgressEvent<string>('webrtc:add-ice-candidate', candidate.candidate))
64
63
  await pc.addIceCandidate(candidate)
65
64
  } catch (err) {
66
- options.log.error('%s bad candidate received', options.direction, candidateInit, err)
65
+ options.log.error('%s bad candidate received %o - %e', options.direction, candidateInit, err)
67
66
  }
68
67
  }
69
68
  } catch (err) {
70
- options.log.error('%s error parsing ICE candidate', options.direction, err)
69
+ options.log.error('%s error parsing ICE candidate - %e', options.direction, err)
71
70
 
72
- if (options.signal?.aborted === true && getConnectionState(pc) !== 'connected') {
71
+ if (options.signal?.aborted === true && pc.connectionState !== 'connected') {
73
72
  throw err
74
73
  }
75
74
  }
76
75
  }
77
76
 
78
- export function getConnectionState (pc: RTCPeerConnection): string {
79
- return isFirefox ? pc.iceConnectionState : pc.connectionState
80
- }
81
-
82
77
  function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredPromise<void>): void {
83
- pc[isFirefox ? 'oniceconnectionstatechange' : 'onconnectionstatechange'] = (_) => {
84
- switch (getConnectionState(pc)) {
78
+ if (pc.connectionState === 'connected') {
79
+ promise.resolve()
80
+ return
81
+ }
82
+
83
+ pc.onconnectionstatechange = (_) => {
84
+ switch (pc.connectionState) {
85
85
  case 'connected':
86
86
  promise.resolve()
87
87
  break
88
88
  case 'failed':
89
89
  case 'disconnected':
90
90
  case 'closed':
91
- promise.reject(new ConnectionFailedError('RTCPeerConnection was closed'))
91
+ promise.reject(new ConnectionFailedError(`RTCPeerConnection connection state became "${pc.connectionState}"`))
92
92
  break
93
93
  default:
94
94
  break
@@ -1,12 +1,11 @@
1
1
  import { isIPv4 } from '@chainsafe/is-ip'
2
2
  import { InvalidParametersError } from '@libp2p/interface'
3
- import { getThinWaistAddresses } from '@libp2p/utils'
4
- import { multiaddr, fromStringTuples } from '@multiformats/multiaddr'
3
+ import { getNetConfig, getThinWaistAddresses } from '@libp2p/utils'
4
+ import { CODE_CERTHASH, CODE_WEBRTC_DIRECT, multiaddr } from '@multiformats/multiaddr'
5
5
  import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
6
6
  import getPort from 'get-port'
7
7
  import { TypedEventEmitter, setMaxListeners } from 'main-event'
8
8
  import pWaitFor from 'p-wait-for'
9
- import { CODEC_CERTHASH, CODEC_WEBRTC_DIRECT } from '../constants.js'
10
9
  import { connect } from './utils/connect.js'
11
10
  import { createDialerRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
12
11
  import { stunListener } from './utils/stun-listener.js'
@@ -94,7 +93,11 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
94
93
  }
95
94
 
96
95
  async listen (ma: Multiaddr): Promise<void> {
97
- const { host, port, family } = ma.toOptions()
96
+ const { host, port, type, protocol } = getNetConfig(ma)
97
+
98
+ if (port == null || protocol !== 'udp' || (type !== 'ip4' && type !== 'ip6')) {
99
+ throw new InvalidParametersError(`Multiaddr ${ma} was not an IPv4 or IPv6 address or was missing a UDP port`)
100
+ }
98
101
 
99
102
  let udpMuxServer: UDPMuxServer | undefined
100
103
 
@@ -106,7 +109,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
106
109
  udpMuxServer = UDP_MUX_LISTENERS.find(s => s.port === port)
107
110
 
108
111
  // make sure the port is free for the given family
109
- if (udpMuxServer != null && ((udpMuxServer.isIPv4 && family === 4) || (udpMuxServer.isIPv6 && family === 6))) {
112
+ if (udpMuxServer != null && ((udpMuxServer.isIPv4 && type === 'ip4') || (udpMuxServer.isIPv6 && type === 'ip6'))) {
110
113
  throw new InvalidParametersError(`There is already a listener for ${host}:${port}`)
111
114
  }
112
115
 
@@ -119,13 +122,13 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
119
122
  // start the mux server if we don't have one already
120
123
  if (udpMuxServer == null) {
121
124
  this.log('starting UDP mux server on %s:%p', host, port)
122
- udpMuxServer = this.startUDPMuxServer(host, port, family)
125
+ udpMuxServer = this.startUDPMuxServer(host, port, type === 'ip4' ? 4 : 6)
123
126
  UDP_MUX_LISTENERS.push(udpMuxServer)
124
127
  }
125
128
 
126
- if (family === 4) {
129
+ if (type === 'ip4') {
127
130
  udpMuxServer.isIPv4 = true
128
- } else if (family === 6) {
131
+ } else if (type === 'ip6') {
129
132
  udpMuxServer.isIPv6 = true
130
133
  }
131
134
 
@@ -238,19 +241,23 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
238
241
  }
239
242
 
240
243
  // add the certhash if it is missing
241
- const tuples = ma.stringTuples()
244
+ const components = ma.getComponents()
242
245
 
243
- for (let j = 0; j < tuples.length; j++) {
244
- if (tuples[j][0] !== CODEC_WEBRTC_DIRECT) {
246
+ for (let j = 0; j < components.length; j++) {
247
+ if (components[j].code !== CODE_WEBRTC_DIRECT) {
245
248
  continue
246
249
  }
247
250
 
248
251
  const certhashIndex = j + 1
249
252
 
250
- if (tuples[certhashIndex] == null || tuples[certhashIndex][0] !== CODEC_CERTHASH) {
251
- tuples.splice(certhashIndex, 0, [CODEC_CERTHASH, this.certificate?.certhash])
253
+ if (components[certhashIndex] == null || components[certhashIndex].code !== CODE_CERTHASH) {
254
+ components.splice(certhashIndex, 0, {
255
+ code: CODE_CERTHASH,
256
+ name: 'certhash',
257
+ value: this.certificate?.certhash
258
+ })
252
259
 
253
- ma = fromStringTuples(tuples)
260
+ ma = multiaddr(components)
254
261
  multiaddrs[i] = ma
255
262
  }
256
263
  }
@@ -1,6 +1,7 @@
1
1
  import { generateKeyPair, privateKeyToCryptoKeyPair } from '@libp2p/crypto/keys'
2
2
  import { InvalidParametersError, NotFoundError, NotStartedError, serviceCapabilities, transportSymbol } from '@libp2p/interface'
3
3
  import { peerIdFromString } from '@libp2p/peer-id'
4
+ import { CODE_P2P } from '@multiformats/multiaddr'
4
5
  import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
5
6
  import { BasicConstraintsExtension, X509Certificate, X509CertificateGenerator } from '@peculiar/x509'
6
7
  import { Key } from 'interface-datastore'
@@ -164,7 +165,7 @@ export class WebRTCDirectTransport implements Transport, Startable {
164
165
  options.signal.throwIfAborted()
165
166
 
166
167
  let theirPeerId: PeerId | undefined
167
- const remotePeerString = ma.getPeerId()
168
+ const remotePeerString = ma.getComponents().findLast(c => c.code === CODE_P2P)?.value
168
169
  if (remotePeerString != null) {
169
170
  theirPeerId = peerIdFromString(remotePeerString)
170
171
  }
@@ -1,10 +1,11 @@
1
1
  import { InvalidParametersError } from '@libp2p/interface'
2
- import { multiaddr } from '@multiformats/multiaddr'
2
+ import { getNetConfig } from '@libp2p/utils'
3
+ import { CODE_CERTHASH, multiaddr } from '@multiformats/multiaddr'
3
4
  import { base64url } from 'multiformats/bases/base64'
4
5
  import { bases, digest } from 'multiformats/basics'
5
6
  import * as Digest from 'multiformats/hashes/digest'
6
7
  import { sha256 } from 'multiformats/hashes/sha2'
7
- import { CODEC_CERTHASH, MAX_MESSAGE_SIZE } from '../../constants.js'
8
+ import { MAX_MESSAGE_SIZE } from '../../constants.js'
8
9
  import { InvalidFingerprintError, UnsupportedHashAlgorithmError } from '../../error.js'
9
10
  import type { Multiaddr } from '@multiformats/multiaddr'
10
11
  import type { MultihashDigest } from 'multiformats/hashes/interface'
@@ -27,8 +28,8 @@ export function getFingerprintFromSdp (sdp: string | undefined): string | undefi
27
28
 
28
29
  // Extract the certhash from a multiaddr
29
30
  export function certhash (ma: Multiaddr): string {
30
- const tups = ma.stringTuples()
31
- const certhash = tups.filter((tup) => tup[0] === CODEC_CERTHASH).map((tup) => tup[1])[0]
31
+ const components = ma.getComponents()
32
+ const certhash = components.find(c => c.code === CODE_CERTHASH)?.value
32
33
 
33
34
  if (certhash === undefined || certhash === '') {
34
35
  throw new InvalidParametersError(`Couldn't find a certhash component of multiaddr: ${ma.toString()}`)
@@ -100,15 +101,20 @@ export function toSupportedHashFunction (code: number): 'sha-1' | 'sha-256' | 's
100
101
  * ice-lite mode and DTLS active mode.
101
102
  */
102
103
  export function serverAnswerFromMultiaddr (ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit {
103
- const { host, port, family } = ma.toOptions()
104
+ const { host, port, type } = getNetConfig(ma)
105
+
106
+ if (type !== 'ip4' && type !== 'ip6') {
107
+ throw new InvalidParametersError(`Multiaddr ${ma} was not an IPv4 or IPv6 address`)
108
+ }
109
+
104
110
  const fingerprint = ma2Fingerprint(ma)
105
111
  const sdp = `v=0
106
- o=- 0 0 IN IP${family} ${host}
112
+ o=- 0 0 IN IP${type === 'ip4' ? 4 : 6} ${host}
107
113
  s=-
108
114
  t=0 0
109
115
  a=ice-lite
110
116
  m=application ${port} UDP/DTLS/SCTP webrtc-datachannel
111
- c=IN IP${family} ${host}
117
+ c=IN IP${type === 'ip4' ? 4 : 6} ${host}
112
118
  a=mid:0
113
119
  a=ice-options:ice2
114
120
  a=ice-ufrag:${ufrag}
@@ -131,11 +137,16 @@ a=end-of-candidates
131
137
  * Create an offer SDP message from a multiaddr
132
138
  */
133
139
  export function clientOfferFromMultiAddr (ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit {
134
- const { host, port, family } = ma.toOptions()
140
+ const { host, port, type } = getNetConfig(ma)
141
+
142
+ if (type !== 'ip4' && type !== 'ip6') {
143
+ throw new InvalidParametersError(`Multiaddr ${ma} was not an IPv4 or IPv6 address`)
144
+ }
145
+
135
146
  const sdp = `v=0
136
- o=- 0 0 IN IP${family} ${host}
147
+ o=- 0 0 IN IP${type === 'ip4' ? 4 : 6} ${host}
137
148
  s=-
138
- c=IN IP${family} ${host}
149
+ c=IN IP${type === 'ip4' ? 4 : 6} ${host}
139
150
  t=0 0
140
151
  a=ice-options:ice2,trickle
141
152
  m=application ${port} UDP/DTLS/SCTP webrtc-datachannel
@@ -24,6 +24,10 @@ class RTCPeerConnectionMultiaddrConnection extends AbstractMultiaddrConnection {
24
24
  if (this.peerConnection.connectionState === 'disconnected' || this.peerConnection.connectionState === 'failed' || this.peerConnection.connectionState === 'closed') {
25
25
  // nothing else to do but close the connection
26
26
  this.onTransportClosed()
27
+
28
+ // only necessary with node-datachannel
29
+ // https://github.com/murat-dogan/node-datachannel/issues/366#issuecomment-3228453155
30
+ this.peerConnection.close()
27
31
  }
28
32
  }
29
33
  }