@libp2p/webrtc 5.0.27 → 5.1.0-23685db5e

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 (120) hide show
  1. package/README.md +16 -21
  2. package/dist/index.min.js +31 -12
  3. package/dist/src/constants.d.ts +2 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +2 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +33 -21
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js +16 -21
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/maconn.d.ts +1 -0
  12. package/dist/src/maconn.d.ts.map +1 -1
  13. package/dist/src/maconn.js +4 -3
  14. package/dist/src/maconn.js.map +1 -1
  15. package/dist/src/muxer.d.ts +1 -0
  16. package/dist/src/muxer.d.ts.map +1 -1
  17. package/dist/src/muxer.js +2 -2
  18. package/dist/src/muxer.js.map +1 -1
  19. package/dist/src/private-to-private/util.d.ts +1 -0
  20. package/dist/src/private-to-private/util.d.ts.map +1 -1
  21. package/dist/src/private-to-private/util.js.map +1 -1
  22. package/dist/src/private-to-public/listener.browser.d.ts +17 -0
  23. package/dist/src/private-to-public/listener.browser.d.ts.map +1 -0
  24. package/dist/src/private-to-public/listener.browser.js +13 -0
  25. package/dist/src/private-to-public/listener.browser.js.map +1 -0
  26. package/dist/src/private-to-public/listener.d.ts +37 -0
  27. package/dist/src/private-to-public/listener.d.ts.map +1 -0
  28. package/dist/src/private-to-public/listener.js +175 -0
  29. package/dist/src/private-to-public/listener.js.map +1 -0
  30. package/dist/src/private-to-public/pb/message.d.ts.map +1 -0
  31. package/dist/src/private-to-public/pb/message.js.map +1 -0
  32. package/dist/src/private-to-public/transport.d.ts +10 -11
  33. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  34. package/dist/src/private-to-public/transport.js +28 -155
  35. package/dist/src/private-to-public/transport.js.map +1 -1
  36. package/dist/src/private-to-public/utils/connect.d.ts +27 -0
  37. package/dist/src/private-to-public/utils/connect.d.ts.map +1 -0
  38. package/dist/src/private-to-public/utils/connect.js +142 -0
  39. package/dist/src/private-to-public/utils/connect.js.map +1 -0
  40. package/dist/src/private-to-public/utils/generate-certificates.browser.d.ts +2 -0
  41. package/dist/src/private-to-public/utils/generate-certificates.browser.d.ts.map +1 -0
  42. package/dist/src/private-to-public/utils/generate-certificates.browser.js +4 -0
  43. package/dist/src/private-to-public/utils/generate-certificates.browser.js.map +1 -0
  44. package/dist/src/private-to-public/utils/generate-certificates.d.ts +8 -0
  45. package/dist/src/private-to-public/utils/generate-certificates.d.ts.map +1 -0
  46. package/dist/src/private-to-public/utils/generate-certificates.js +39 -0
  47. package/dist/src/private-to-public/utils/generate-certificates.js.map +1 -0
  48. package/dist/src/private-to-public/utils/generate-noise-prologue.d.ts +7 -0
  49. package/dist/src/private-to-public/utils/generate-noise-prologue.d.ts.map +1 -0
  50. package/dist/src/private-to-public/utils/generate-noise-prologue.js +22 -0
  51. package/dist/src/private-to-public/utils/generate-noise-prologue.js.map +1 -0
  52. package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.d.ts +2 -0
  53. package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.d.ts.map +1 -0
  54. package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.js +20 -0
  55. package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.js.map +1 -0
  56. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +19 -0
  57. package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -0
  58. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +86 -0
  59. package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -0
  60. package/dist/src/private-to-public/utils/sdp.d.ts +36 -0
  61. package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -0
  62. package/dist/src/private-to-public/{sdp.js → utils/sdp.js} +72 -57
  63. package/dist/src/private-to-public/utils/sdp.js.map +1 -0
  64. package/dist/src/private-to-public/utils/stun-listener.d.ts +15 -0
  65. package/dist/src/private-to-public/utils/stun-listener.d.ts.map +1 -0
  66. package/dist/src/private-to-public/utils/stun-listener.js +79 -0
  67. package/dist/src/private-to-public/utils/stun-listener.js.map +1 -0
  68. package/dist/src/stream.d.ts +2 -0
  69. package/dist/src/stream.d.ts.map +1 -1
  70. package/dist/src/stream.js +56 -12
  71. package/dist/src/stream.js.map +1 -1
  72. package/dist/src/util.d.ts +4 -0
  73. package/dist/src/util.d.ts.map +1 -1
  74. package/dist/src/util.js +7 -1
  75. package/dist/src/util.js.map +1 -1
  76. package/dist/src/webrtc/index.d.ts +2 -1
  77. package/dist/src/webrtc/index.d.ts.map +1 -1
  78. package/dist/src/webrtc/index.js +1 -1
  79. package/dist/src/webrtc/index.js.map +1 -1
  80. package/package.json +22 -11
  81. package/src/constants.ts +4 -0
  82. package/src/index.ts +35 -21
  83. package/src/maconn.ts +5 -3
  84. package/src/muxer.ts +3 -2
  85. package/src/private-to-private/util.ts +1 -0
  86. package/src/private-to-public/listener.browser.ts +28 -0
  87. package/src/private-to-public/listener.ts +233 -0
  88. package/src/private-to-public/transport.ts +39 -182
  89. package/src/private-to-public/utils/connect.ts +192 -0
  90. package/src/private-to-public/utils/generate-certificates.browser.ts +3 -0
  91. package/src/private-to-public/utils/generate-certificates.ts +51 -0
  92. package/src/private-to-public/utils/generate-noise-prologue.ts +26 -0
  93. package/src/private-to-public/utils/get-rtcpeerconnection.browser.ts +22 -0
  94. package/src/private-to-public/utils/get-rtcpeerconnection.ts +108 -0
  95. package/src/private-to-public/utils/sdp.ts +174 -0
  96. package/src/private-to-public/utils/stun-listener.ts +104 -0
  97. package/src/stream.ts +68 -15
  98. package/src/util.ts +11 -1
  99. package/src/webrtc/index.ts +2 -1
  100. package/dist/src/pb/message.d.ts.map +0 -1
  101. package/dist/src/pb/message.js.map +0 -1
  102. package/dist/src/private-to-public/options.d.ts +0 -6
  103. package/dist/src/private-to-public/options.d.ts.map +0 -1
  104. package/dist/src/private-to-public/options.js +0 -2
  105. package/dist/src/private-to-public/options.js.map +0 -1
  106. package/dist/src/private-to-public/sdp.d.ts +0 -31
  107. package/dist/src/private-to-public/sdp.d.ts.map +0 -1
  108. package/dist/src/private-to-public/sdp.js.map +0 -1
  109. package/dist/src/private-to-public/util.d.ts +0 -2
  110. package/dist/src/private-to-public/util.d.ts.map +0 -1
  111. package/dist/src/private-to-public/util.js +0 -3
  112. package/dist/src/private-to-public/util.js.map +0 -1
  113. package/dist/typedoc-urls.json +0 -12
  114. package/src/private-to-public/options.ts +0 -4
  115. package/src/private-to-public/sdp.ts +0 -159
  116. package/src/private-to-public/util.ts +0 -2
  117. /package/dist/src/{pb → private-to-public/pb}/message.d.ts +0 -0
  118. /package/dist/src/{pb → private-to-public/pb}/message.js +0 -0
  119. /package/src/{pb → private-to-public/pb}/message.proto +0 -0
  120. /package/src/{pb → private-to-public/pb}/message.ts +0 -0
@@ -0,0 +1,233 @@
1
+ import { networkInterfaces } from 'node:os'
2
+ import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
3
+ import { TypedEventEmitter } from '@libp2p/interface'
4
+ import { multiaddr, protocols } from '@multiformats/multiaddr'
5
+ import { IP4 } from '@multiformats/multiaddr-matcher'
6
+ import { Crypto } from '@peculiar/webcrypto'
7
+ import getPort from 'get-port'
8
+ import pWaitFor from 'p-wait-for'
9
+ import { connect } from './utils/connect.js'
10
+ import { generateTransportCertificate } from './utils/generate-certificates.js'
11
+ import { createDialerRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
12
+ import { stunListener } from './utils/stun-listener.js'
13
+ import type { DataChannelOptions, TransportCertificate } from '../index.js'
14
+ import type { DirectRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
15
+ import type { StunServer } from './utils/stun-listener.js'
16
+ import type { PeerId, ListenerEvents, Listener, Upgrader, ComponentLogger, Logger, CounterGroup, Metrics, PrivateKey } from '@libp2p/interface'
17
+ import type { Multiaddr } from '@multiformats/multiaddr'
18
+
19
+ const crypto = new Crypto()
20
+
21
+ /**
22
+ * The time to wait, in milliseconds, for the data channel handshake to complete
23
+ */
24
+ const HANDSHAKE_TIMEOUT_MS = 10_000
25
+
26
+ export interface WebRTCDirectListenerComponents {
27
+ peerId: PeerId
28
+ privateKey: PrivateKey
29
+ logger: ComponentLogger
30
+ metrics?: Metrics
31
+ }
32
+
33
+ export interface WebRTCDirectListenerInit {
34
+ upgrader: Upgrader
35
+ certificates?: TransportCertificate[]
36
+ maxInboundStreams?: number
37
+ dataChannel?: DataChannelOptions
38
+ rtcConfiguration?: RTCConfiguration | (() => RTCConfiguration | Promise<RTCConfiguration>)
39
+ useLibjuice?: boolean
40
+ }
41
+
42
+ export interface WebRTCListenerMetrics {
43
+ listenerEvents: CounterGroup
44
+ }
45
+
46
+ const UDP_PROTOCOL = protocols('udp')
47
+ const IP4_PROTOCOL = protocols('ip4')
48
+ const IP6_PROTOCOL = protocols('ip6')
49
+
50
+ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> implements Listener {
51
+ private server?: StunServer
52
+ private readonly multiaddrs: Multiaddr[]
53
+ private certificate?: TransportCertificate
54
+ private readonly connections: Map<string, DirectRTCPeerConnection>
55
+ private readonly log: Logger
56
+ private readonly init: WebRTCDirectListenerInit
57
+ private readonly components: WebRTCDirectListenerComponents
58
+ private readonly metrics?: WebRTCListenerMetrics
59
+
60
+ constructor (components: WebRTCDirectListenerComponents, init: WebRTCDirectListenerInit) {
61
+ super()
62
+
63
+ this.init = init
64
+ this.components = components
65
+ this.multiaddrs = []
66
+ this.connections = new Map()
67
+ this.log = components.logger.forComponent('libp2p:webrtc-direct:listener')
68
+ this.certificate = init.certificates?.[0]
69
+
70
+ if (components.metrics != null) {
71
+ this.metrics = {
72
+ listenerEvents: components.metrics.registerCounterGroup('libp2p_webrtc-direct_listener_events_total', {
73
+ label: 'event',
74
+ help: 'Total count of WebRTC-direct listen events by type'
75
+ })
76
+ }
77
+ }
78
+ }
79
+
80
+ async listen (ma: Multiaddr): Promise<void> {
81
+ const parts = ma.stringTuples()
82
+ const ipVersion = IP4.matches(ma) ? 4 : 6
83
+ const host = parts
84
+ .filter(([code]) => code === IP4_PROTOCOL.code)
85
+ .pop()?.[1] ?? parts
86
+ .filter(([code]) => code === IP6_PROTOCOL.code)
87
+ .pop()?.[1]
88
+
89
+ if (host == null) {
90
+ throw new Error('IP4/6 host must be specified in webrtc-direct mulitaddr')
91
+ }
92
+ let port = parseInt(parts
93
+ .filter(([code, value]) => code === UDP_PROTOCOL.code)
94
+ .pop()?.[1] ?? '')
95
+
96
+ if (isNaN(port)) {
97
+ throw new Error('UDP port must be specified in webrtc-direct mulitaddr')
98
+ }
99
+
100
+ if (port === 0 && this.init.useLibjuice !== false) {
101
+ // libjuice doesn't map 0 to a random free port so we have to do it
102
+ // ourselves
103
+ port = await getPort()
104
+ }
105
+
106
+ this.server = await stunListener(host, port, ipVersion, this.log, (ufrag, remoteHost, remotePort) => {
107
+ this.incomingConnection(ufrag, remoteHost, remotePort)
108
+ .catch(err => {
109
+ this.log.error('error processing incoming STUN request', err)
110
+ })
111
+ }, {
112
+ useLibjuice: this.init.useLibjuice
113
+ })
114
+
115
+ let certificate = this.certificate
116
+
117
+ if (certificate == null) {
118
+ const keyPair = await crypto.subtle.generateKey({
119
+ name: 'ECDSA',
120
+ namedCurve: 'P-256'
121
+ }, true, ['sign', 'verify'])
122
+
123
+ certificate = this.certificate = await generateTransportCertificate(keyPair, {
124
+ days: 365
125
+ })
126
+ }
127
+
128
+ const address = this.server.address()
129
+
130
+ getNetworkAddresses(address.address, address.port, ipVersion).forEach((ma) => {
131
+ this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${certificate.certhash}`))
132
+ })
133
+
134
+ this.safeDispatchEvent('listening')
135
+ }
136
+
137
+ private async incomingConnection (ufrag: string, remoteHost: string, remotePort: number): Promise<void> {
138
+ const key = `${remoteHost}:${remotePort}:${ufrag}`
139
+ let peerConnection = this.connections.get(key)
140
+
141
+ if (peerConnection != null) {
142
+ this.log.trace('already got peer connection for %s', key)
143
+ return
144
+ }
145
+
146
+ this.log('create peer connection for %s', key)
147
+
148
+ // https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md#browser-to-public-server
149
+ peerConnection = await createDialerRTCPeerConnection('server', ufrag, this.init.rtcConfiguration, this.certificate)
150
+
151
+ this.connections.set(key, peerConnection)
152
+
153
+ peerConnection.addEventListener('connectionstatechange', () => {
154
+ switch (peerConnection.connectionState) {
155
+ case 'failed':
156
+ case 'disconnected':
157
+ case 'closed':
158
+ this.connections.delete(key)
159
+ break
160
+ default:
161
+ break
162
+ }
163
+ })
164
+
165
+ try {
166
+ await connect(peerConnection, ufrag, {
167
+ role: 'server',
168
+ log: this.log,
169
+ logger: this.components.logger,
170
+ metrics: this.components.metrics,
171
+ events: this.metrics?.listenerEvents,
172
+ signal: AbortSignal.timeout(HANDSHAKE_TIMEOUT_MS),
173
+ remoteAddr: multiaddr(`/ip${isIPv4(remoteHost) ? 4 : 6}/${remoteHost}/udp/${remotePort}`),
174
+ dataChannel: this.init.dataChannel,
175
+ upgrader: this.init.upgrader,
176
+ peerId: this.components.peerId,
177
+ privateKey: this.components.privateKey
178
+ })
179
+ } catch (err) {
180
+ peerConnection.close()
181
+ throw err
182
+ }
183
+ }
184
+
185
+ getAddrs (): Multiaddr[] {
186
+ return this.multiaddrs
187
+ }
188
+
189
+ async close (): Promise<void> {
190
+ for (const connection of this.connections.values()) {
191
+ connection.close()
192
+ }
193
+
194
+ await this.server?.close()
195
+
196
+ // RTCPeerConnections will be removed from the connections map when their
197
+ // connection state changes to 'closed'/'disconnected'/'failed
198
+ await pWaitFor(() => {
199
+ return this.connections.size === 0
200
+ })
201
+
202
+ this.safeDispatchEvent('close')
203
+ }
204
+ }
205
+
206
+ function getNetworkAddresses (host: string, port: number, version: 4 | 6): string[] {
207
+ if (host === '0.0.0.0' || host === '::1') {
208
+ // return all ip4 interfaces
209
+ return Object.entries(networkInterfaces())
210
+ .flatMap(([_, addresses]) => addresses)
211
+ .map(address => address?.address)
212
+ .filter(address => {
213
+ if (address == null) {
214
+ return false
215
+ }
216
+
217
+ if (version === 4) {
218
+ return isIPv4(address)
219
+ }
220
+
221
+ if (version === 6) {
222
+ return isIPv6(address)
223
+ }
224
+
225
+ return false
226
+ })
227
+ .map(address => `/ip${version}/${address}/udp/${port}`)
228
+ }
229
+
230
+ return [
231
+ `/ip${version}/${host}/udp/${port}`
232
+ ]
233
+ }
@@ -1,22 +1,16 @@
1
- import { noise } from '@chainsafe/libp2p-noise'
2
- import { transportSymbol, serviceCapabilities, InvalidParametersError } from '@libp2p/interface'
1
+ import { serviceCapabilities, transportSymbol } from '@libp2p/interface'
3
2
  import { peerIdFromString } from '@libp2p/peer-id'
4
3
  import { protocols } from '@multiformats/multiaddr'
5
4
  import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
6
- import * as Digest from 'multiformats/hashes/digest'
7
- import { concat } from 'uint8arrays/concat'
8
- import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'
9
- import { DataChannelError, UnimplementedError } from '../error.js'
10
- import { WebRTCMultiaddrConnection } from '../maconn.js'
11
- import { DataChannelMuxerFactory } from '../muxer.js'
12
- import { createStream } from '../stream.js'
13
- import { getRtcConfiguration, isFirefox } from '../util.js'
14
- import { RTCPeerConnection } from '../webrtc/index.js'
15
- import * as sdp from './sdp.js'
16
- import { genUfrag } from './util.js'
17
- import type { WebRTCDialOptions } from './options.js'
18
- import type { DataChannelOptions } from '../index.js'
19
- import type { CreateListenerOptions, Transport, Listener, ComponentLogger, Logger, Connection, CounterGroup, Metrics, PeerId, PrivateKey } from '@libp2p/interface'
5
+ import { raceSignal } from 'race-signal'
6
+ import { genUfrag } from '../util.js'
7
+ import { WebRTCDirectListener } from './listener.js'
8
+ import { connect } from './utils/connect.js'
9
+ import { createDialerRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
10
+ import type { DataChannelOptions, TransportCertificate } from '../index.js'
11
+ import type { WebRTCDialEvents } from '../private-to-private/transport.js'
12
+ import type { CreateListenerOptions, Transport, Listener, ComponentLogger, Logger, Connection, CounterGroup, Metrics, PeerId, DialTransportOptions, PrivateKey } from '@libp2p/interface'
13
+ import type { TransportManager } from '@libp2p/interface-internal'
20
14
  import type { Multiaddr } from '@multiformats/multiaddr'
21
15
 
22
16
  /**
@@ -46,6 +40,7 @@ export interface WebRTCDirectTransportComponents {
46
40
  privateKey: PrivateKey
47
41
  metrics?: Metrics
48
42
  logger: ComponentLogger
43
+ transportManager: TransportManager
49
44
  }
50
45
 
51
46
  export interface WebRTCMetrics {
@@ -55,6 +50,8 @@ export interface WebRTCMetrics {
55
50
  export interface WebRTCTransportDirectInit {
56
51
  rtcConfiguration?: RTCConfiguration | (() => RTCConfiguration | Promise<RTCConfiguration>)
57
52
  dataChannel?: DataChannelOptions
53
+ certificates?: TransportCertificate[]
54
+ useLibjuice?: boolean
58
55
  }
59
56
 
60
57
  export class WebRTCDirectTransport implements Transport {
@@ -62,10 +59,12 @@ export class WebRTCDirectTransport implements Transport {
62
59
  private readonly metrics?: WebRTCMetrics
63
60
  private readonly components: WebRTCDirectTransportComponents
64
61
  private readonly init: WebRTCTransportDirectInit
62
+
65
63
  constructor (components: WebRTCDirectTransportComponents, init: WebRTCTransportDirectInit = {}) {
66
64
  this.log = components.logger.forComponent('libp2p:webrtc-direct')
67
65
  this.components = components
68
66
  this.init = init
67
+
69
68
  if (components.metrics != null) {
70
69
  this.metrics = {
71
70
  dialerEvents: components.metrics.registerCounterGroup('libp2p_webrtc-direct_dialer_events_total', {
@@ -87,7 +86,8 @@ export class WebRTCDirectTransport implements Transport {
87
86
  /**
88
87
  * Dial a given multiaddr
89
88
  */
90
- async dial (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
89
+ async dial (ma: Multiaddr, options: DialTransportOptions<WebRTCDialEvents>): Promise<Connection> {
90
+ options?.signal?.throwIfAborted()
91
91
  const rawConn = await this._connect(ma, options)
92
92
  this.log('dialing address: %a', ma)
93
93
  return rawConn
@@ -97,7 +97,10 @@ export class WebRTCDirectTransport implements Transport {
97
97
  * Create transport listeners no supported by browsers
98
98
  */
99
99
  createListener (options: CreateListenerOptions): Listener {
100
- throw new UnimplementedError('WebRTCTransport.createListener')
100
+ return new WebRTCDirectListener(this.components, {
101
+ ...this.init,
102
+ ...options
103
+ })
101
104
  }
102
105
 
103
106
  /**
@@ -117,182 +120,36 @@ export class WebRTCDirectTransport implements Transport {
117
120
  /**
118
121
  * Connect to a peer using a multiaddr
119
122
  */
120
- async _connect (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
121
- const controller = new AbortController()
122
- const signal = controller.signal
123
-
124
- let remotePeer: PeerId | undefined
123
+ async _connect (ma: Multiaddr, options: DialTransportOptions<WebRTCDialEvents>): Promise<Connection> {
124
+ let theirPeerId: PeerId | undefined
125
125
  const remotePeerString = ma.getPeerId()
126
126
  if (remotePeerString != null) {
127
- remotePeer = peerIdFromString(remotePeerString)
127
+ theirPeerId = peerIdFromString(remotePeerString)
128
128
  }
129
129
 
130
- const remoteCerthash = sdp.decodeCerthash(sdp.certhash(ma))
130
+ const ufrag = genUfrag()
131
131
 
132
- // ECDSA is preferred over RSA here. From our testing we find that P-256 elliptic
133
- // curve is supported by Pion, webrtc-rs, as well as Chromium (P-228 and P-384
134
- // was not supported in Chromium). We use the same hash function as found in the
135
- // multiaddr if it is supported.
136
- const certificate = await RTCPeerConnection.generateCertificate({
137
- name: 'ECDSA',
138
- namedCurve: 'P-256',
139
- hash: sdp.toSupportedHashFunction(remoteCerthash.code)
140
- } as any)
141
-
142
- const peerConnection = new RTCPeerConnection({
143
- ...(await getRtcConfiguration(this.init.rtcConfiguration)),
144
- certificates: [certificate]
145
- })
132
+ // https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md#browser-to-public-server
133
+ const peerConnection = await createDialerRTCPeerConnection('client', ufrag, typeof this.init.rtcConfiguration === 'function' ? await this.init.rtcConfiguration() : this.init.rtcConfiguration ?? {})
146
134
 
147
135
  try {
148
- // create data channel for running the noise handshake. Once the data channel is opened,
149
- // the remote will initiate the noise handshake. This is used to confirm the identity of
150
- // the peer.
151
- const dataChannelOpenPromise = new Promise<RTCDataChannel>((resolve, reject) => {
152
- const handshakeDataChannel = peerConnection.createDataChannel('', { negotiated: true, id: 0 })
153
- const handshakeTimeout = setTimeout(() => {
154
- const error = `Data channel was never opened: state: ${handshakeDataChannel.readyState}`
155
- this.log.error(error)
156
- this.metrics?.dialerEvents.increment({ open_error: true })
157
- reject(new DataChannelError('data', error))
158
- }, HANDSHAKE_TIMEOUT_MS)
159
-
160
- handshakeDataChannel.onopen = (_) => {
161
- clearTimeout(handshakeTimeout)
162
- resolve(handshakeDataChannel)
163
- }
164
-
165
- // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/error_event
166
- handshakeDataChannel.onerror = (event: Event) => {
167
- clearTimeout(handshakeTimeout)
168
- const errorTarget = event.target?.toString() ?? 'not specified'
169
- const error = `Error opening a data channel for handshaking: ${errorTarget}`
170
- this.log.error(error)
171
- // NOTE: We use unknown error here but this could potentially be considered a reset by some standards.
172
- this.metrics?.dialerEvents.increment({ unknown_error: true })
173
- reject(new DataChannelError('data', error))
174
- }
175
- })
176
-
177
- const ufrag = 'libp2p+webrtc+v1/' + genUfrag(32)
178
-
179
- // Create offer and munge sdp with ufrag == pwd. This allows the remote to
180
- // respond to STUN messages without performing an actual SDP exchange.
181
- // This is because it can infer the passwd field by reading the USERNAME
182
- // attribute of the STUN message.
183
- const offerSdp = await peerConnection.createOffer()
184
- const mungedOfferSdp = sdp.munge(offerSdp, ufrag)
185
- await peerConnection.setLocalDescription(mungedOfferSdp)
186
-
187
- // construct answer sdp from multiaddr and ufrag
188
- const answerSdp = sdp.fromMultiAddr(ma, ufrag)
189
- await peerConnection.setRemoteDescription(answerSdp)
190
-
191
- // wait for peerconnection.onopen to fire, or for the datachannel to open
192
- const handshakeDataChannel = await dataChannelOpenPromise
193
-
194
- // Do noise handshake.
195
- // Set the Noise Prologue to libp2p-webrtc-noise:<FINGERPRINTS> before starting the actual Noise handshake.
196
- // <FINGERPRINTS> is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order.
197
- const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.code, ma)
198
-
199
- // Since we use the default crypto interface and do not use a static key or early data,
200
- // we pass in undefined for these parameters.
201
- const connectionEncrypter = noise({ prologueBytes: fingerprintsPrologue })(this.components)
202
-
203
- const wrappedChannel = createStream({
204
- channel: handshakeDataChannel,
205
- direction: 'inbound',
136
+ return await raceSignal(connect(peerConnection, ufrag, {
137
+ role: 'client',
138
+ log: this.log,
206
139
  logger: this.components.logger,
207
- ...(this.init.dataChannel ?? {})
208
- })
209
- const wrappedDuplex = {
210
- ...wrappedChannel,
211
- sink: wrappedChannel.sink.bind(wrappedChannel),
212
- source: (async function * () {
213
- for await (const list of wrappedChannel.source) {
214
- for (const buf of list) {
215
- yield buf
216
- }
217
- }
218
- }())
219
- }
220
-
221
- // Creating the connection before completion of the noise
222
- // handshake ensures that the stream opening callback is set up
223
- const maConn = new WebRTCMultiaddrConnection(this.components, {
224
- peerConnection,
140
+ metrics: this.components.metrics,
141
+ events: this.metrics?.dialerEvents,
142
+ signal: options.signal ?? AbortSignal.timeout(HANDSHAKE_TIMEOUT_MS),
225
143
  remoteAddr: ma,
226
- timeline: {
227
- open: Date.now()
228
- },
229
- metrics: this.metrics?.dialerEvents
230
- })
231
-
232
- const eventListeningName = isFirefox ? 'iceconnectionstatechange' : 'connectionstatechange'
233
-
234
- peerConnection.addEventListener(eventListeningName, () => {
235
- switch (peerConnection.connectionState) {
236
- case 'failed':
237
- case 'disconnected':
238
- case 'closed':
239
- maConn.close().catch((err) => {
240
- this.log.error('error closing connection', err)
241
- }).finally(() => {
242
- // Remove the event listener once the connection is closed
243
- controller.abort()
244
- })
245
- break
246
- default:
247
- break
248
- }
249
- }, { signal })
250
-
251
- // Track opened peer connection
252
- this.metrics?.dialerEvents.increment({ peer_connection: true })
253
-
254
- const muxerFactory = new DataChannelMuxerFactory(this.components, {
255
- peerConnection,
256
- metrics: this.metrics?.dialerEvents,
257
- dataChannelOptions: this.init.dataChannel
258
- })
259
-
260
- // For outbound connections, the remote is expected to start the noise handshake.
261
- // Therefore, we need to secure an inbound noise connection from the remote.
262
- await connectionEncrypter.secureInbound(wrappedDuplex, {
263
- signal,
264
- remotePeer
265
- })
266
-
267
- return await options.upgrader.upgradeOutbound(maConn, { skipProtection: true, skipEncryption: true, muxerFactory })
144
+ dataChannel: this.init.dataChannel,
145
+ upgrader: options.upgrader,
146
+ peerId: this.components.peerId,
147
+ remotePeerId: theirPeerId,
148
+ privateKey: this.components.privateKey
149
+ }), options.signal)
268
150
  } catch (err) {
269
151
  peerConnection.close()
270
152
  throw err
271
153
  }
272
154
  }
273
-
274
- /**
275
- * Generate a noise prologue from the peer connection's certificate.
276
- * noise prologue = bytes('libp2p-webrtc-noise:') + noise-responder fingerprint + noise-initiator fingerprint
277
- */
278
- private generateNoisePrologue (pc: RTCPeerConnection, hashCode: number, ma: Multiaddr): Uint8Array {
279
- if (pc.getConfiguration().certificates?.length === 0) {
280
- throw new InvalidParametersError('no local certificate')
281
- }
282
-
283
- const localFingerprint = sdp.getLocalFingerprint(pc, {
284
- log: this.log
285
- })
286
- if (localFingerprint == null) {
287
- throw new InvalidParametersError('no local fingerprint found')
288
- }
289
-
290
- const localFpString = localFingerprint.trim().toLowerCase().replaceAll(':', '')
291
- const localFpArray = uint8arrayFromString(localFpString, 'hex')
292
- const local = Digest.create(hashCode, localFpArray)
293
- const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma))
294
- const prefix = uint8arrayFromString('libp2p-webrtc-noise:')
295
-
296
- return concat([prefix, local.bytes, remote])
297
- }
298
155
  }