@libp2p/webrtc 5.2.24-a02cb0461 → 5.2.24-e8398d97e

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 (56) 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/transport.d.ts +0 -7
  21. package/dist/src/private-to-private/transport.d.ts.map +1 -1
  22. package/dist/src/private-to-private/transport.js.map +1 -1
  23. package/dist/src/private-to-private/util.d.ts +0 -1
  24. package/dist/src/private-to-private/util.d.ts.map +1 -1
  25. package/dist/src/private-to-private/util.js +11 -11
  26. package/dist/src/private-to-private/util.js.map +1 -1
  27. package/dist/src/private-to-public/listener.d.ts.map +1 -1
  28. package/dist/src/private-to-public/listener.js +20 -14
  29. package/dist/src/private-to-public/listener.js.map +1 -1
  30. package/dist/src/private-to-public/transport.d.ts +0 -8
  31. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  32. package/dist/src/private-to-public/transport.js +2 -1
  33. package/dist/src/private-to-public/transport.js.map +1 -1
  34. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -1
  35. package/dist/src/private-to-public/utils/sdp.js +17 -10
  36. package/dist/src/private-to-public/utils/sdp.js.map +1 -1
  37. package/dist/src/rtcpeerconnection-to-conn.d.ts.map +1 -1
  38. package/dist/src/rtcpeerconnection-to-conn.js +3 -0
  39. package/dist/src/rtcpeerconnection-to-conn.js.map +1 -1
  40. package/dist/src/stream.d.ts +3 -2
  41. package/dist/src/stream.d.ts.map +1 -1
  42. package/dist/src/stream.js +100 -68
  43. package/dist/src/stream.js.map +1 -1
  44. package/package.json +12 -12
  45. package/src/constants.ts +5 -10
  46. package/src/index.ts +9 -0
  47. package/src/muxer.ts +6 -13
  48. package/src/private-to-private/initiate-connection.ts +20 -4
  49. package/src/private-to-private/signaling-stream-handler.ts +12 -2
  50. package/src/private-to-private/transport.ts +0 -8
  51. package/src/private-to-private/util.ts +12 -12
  52. package/src/private-to-public/listener.ts +21 -14
  53. package/src/private-to-public/transport.ts +2 -11
  54. package/src/private-to-public/utils/sdp.ts +21 -10
  55. package/src/rtcpeerconnection-to-conn.ts +4 -0
  56. package/src/stream.ts +115 -79
package/src/constants.ts CHANGED
@@ -26,16 +26,6 @@ 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
-
39
29
  /**
40
30
  * How much can be buffered to the DataChannel at once
41
31
  */
@@ -83,6 +73,11 @@ export const PROTOBUF_OVERHEAD = calculateProtobufOverhead()
83
73
  */
84
74
  export const DATA_CHANNEL_DRAIN_TIMEOUT = 30_000
85
75
 
76
+ /**
77
+ * Wait for the remote to acknowledge our FIN for this long
78
+ */
79
+ export const DEFAULT_FIN_ACK_TIMEOUT = 10_000
80
+
86
81
  /**
87
82
  * Set as the 'negotiated' muxer protocol name
88
83
  */
package/src/index.ts CHANGED
@@ -308,6 +308,15 @@ export interface DataChannelOptions {
308
308
  * @default 5_000
309
309
  */
310
310
  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
311
320
  }
312
321
 
313
322
  /**
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()
@@ -31,14 +31,6 @@ export interface WebRTCTransportInit {
31
31
  * Any options here will be applied to any RTCDataChannels that are opened.
32
32
  */
33
33
  dataChannel?: DataChannelOptions
34
-
35
- /**
36
- * Inbound connections must complete the upgrade within this many ms
37
- *
38
- * @default 30_000
39
- * @deprecated configure `connectionManager.inboundUpgradeTimeout` instead
40
- */
41
- inboundConnectionTimeout?: number
42
34
  }
43
35
 
44
36
  export interface WebRTCTransportComponents {
@@ -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'
@@ -50,11 +51,6 @@ export interface WebRTCTransportDirectInit {
50
51
  */
51
52
  dataChannel?: DataChannelOptions
52
53
 
53
- /**
54
- * @deprecated use `certificate` instead - this option will be removed in a future release
55
- */
56
- certificates?: TransportCertificate[]
57
-
58
54
  /**
59
55
  * Use an existing TLS certificate to secure incoming connections or supply
60
56
  * settings to generate one.
@@ -67,11 +63,6 @@ export interface WebRTCTransportDirectInit {
67
63
  */
68
64
  certificate?: TransportCertificate
69
65
 
70
- /**
71
- * @deprecated this setting is ignored and will be removed in a future release
72
- */
73
- useLibjuice?: boolean
74
-
75
66
  /**
76
67
  * The key the certificate is stored in the datastore under
77
68
  *
@@ -164,7 +155,7 @@ export class WebRTCDirectTransport implements Transport, Startable {
164
155
  options.signal.throwIfAborted()
165
156
 
166
157
  let theirPeerId: PeerId | undefined
167
- const remotePeerString = ma.getPeerId()
158
+ const remotePeerString = ma.getComponents().findLast(c => c.code === CODE_P2P)?.value
168
159
  if (remotePeerString != null) {
169
160
  theirPeerId = peerIdFromString(remotePeerString)
170
161
  }
@@ -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
  }