@libp2p/webrtc 5.0.26-d8f003e6e → 5.0.27-2e35b6055
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.
- package/README.md +16 -21
- package/dist/index.min.js +31 -12
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +33 -21
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +16 -21
- package/dist/src/index.js.map +1 -1
- package/dist/src/maconn.d.ts +1 -0
- package/dist/src/maconn.d.ts.map +1 -1
- package/dist/src/maconn.js +4 -3
- package/dist/src/maconn.js.map +1 -1
- package/dist/src/muxer.d.ts +1 -0
- package/dist/src/muxer.d.ts.map +1 -1
- package/dist/src/muxer.js +2 -2
- package/dist/src/muxer.js.map +1 -1
- package/dist/src/private-to-private/util.d.ts +1 -0
- package/dist/src/private-to-private/util.d.ts.map +1 -1
- package/dist/src/private-to-private/util.js.map +1 -1
- package/dist/src/private-to-public/listener.browser.d.ts +17 -0
- package/dist/src/private-to-public/listener.browser.d.ts.map +1 -0
- package/dist/src/private-to-public/listener.browser.js +13 -0
- package/dist/src/private-to-public/listener.browser.js.map +1 -0
- package/dist/src/private-to-public/listener.d.ts +37 -0
- package/dist/src/private-to-public/listener.d.ts.map +1 -0
- package/dist/src/private-to-public/listener.js +175 -0
- package/dist/src/private-to-public/listener.js.map +1 -0
- package/dist/src/private-to-public/pb/message.d.ts.map +1 -0
- package/dist/src/private-to-public/pb/message.js.map +1 -0
- package/dist/src/private-to-public/transport.d.ts +10 -11
- package/dist/src/private-to-public/transport.d.ts.map +1 -1
- package/dist/src/private-to-public/transport.js +28 -155
- package/dist/src/private-to-public/transport.js.map +1 -1
- package/dist/src/private-to-public/utils/connect.d.ts +27 -0
- package/dist/src/private-to-public/utils/connect.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/connect.js +142 -0
- package/dist/src/private-to-public/utils/connect.js.map +1 -0
- package/dist/src/private-to-public/utils/generate-certificates.browser.d.ts +2 -0
- package/dist/src/private-to-public/utils/generate-certificates.browser.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/generate-certificates.browser.js +4 -0
- package/dist/src/private-to-public/utils/generate-certificates.browser.js.map +1 -0
- package/dist/src/private-to-public/utils/generate-certificates.d.ts +8 -0
- package/dist/src/private-to-public/utils/generate-certificates.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/generate-certificates.js +39 -0
- package/dist/src/private-to-public/utils/generate-certificates.js.map +1 -0
- package/dist/src/private-to-public/utils/generate-noise-prologue.d.ts +7 -0
- package/dist/src/private-to-public/utils/generate-noise-prologue.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/generate-noise-prologue.js +22 -0
- package/dist/src/private-to-public/utils/generate-noise-prologue.js.map +1 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.d.ts +2 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.js +20 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.browser.js.map +1 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts +19 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.js +86 -0
- package/dist/src/private-to-public/utils/get-rtcpeerconnection.js.map +1 -0
- package/dist/src/private-to-public/utils/sdp.d.ts +36 -0
- package/dist/src/private-to-public/utils/sdp.d.ts.map +1 -0
- package/dist/src/private-to-public/{sdp.js → utils/sdp.js} +72 -57
- package/dist/src/private-to-public/utils/sdp.js.map +1 -0
- package/dist/src/private-to-public/utils/stun-listener.d.ts +15 -0
- package/dist/src/private-to-public/utils/stun-listener.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/stun-listener.js +79 -0
- package/dist/src/private-to-public/utils/stun-listener.js.map +1 -0
- package/dist/src/stream.d.ts +2 -0
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +56 -12
- package/dist/src/stream.js.map +1 -1
- package/dist/src/util.d.ts +4 -0
- package/dist/src/util.d.ts.map +1 -1
- package/dist/src/util.js +7 -1
- package/dist/src/util.js.map +1 -1
- package/dist/src/webrtc/index.d.ts +2 -1
- package/dist/src/webrtc/index.d.ts.map +1 -1
- package/dist/src/webrtc/index.js +1 -1
- package/dist/src/webrtc/index.js.map +1 -1
- package/package.json +22 -11
- package/src/constants.ts +4 -0
- package/src/index.ts +35 -21
- package/src/maconn.ts +5 -3
- package/src/muxer.ts +3 -2
- package/src/private-to-private/util.ts +1 -0
- package/src/private-to-public/listener.browser.ts +28 -0
- package/src/private-to-public/listener.ts +233 -0
- package/src/private-to-public/transport.ts +39 -182
- package/src/private-to-public/utils/connect.ts +192 -0
- package/src/private-to-public/utils/generate-certificates.browser.ts +3 -0
- package/src/private-to-public/utils/generate-certificates.ts +51 -0
- package/src/private-to-public/utils/generate-noise-prologue.ts +26 -0
- package/src/private-to-public/utils/get-rtcpeerconnection.browser.ts +22 -0
- package/src/private-to-public/utils/get-rtcpeerconnection.ts +108 -0
- package/src/private-to-public/utils/sdp.ts +174 -0
- package/src/private-to-public/utils/stun-listener.ts +104 -0
- package/src/stream.ts +68 -15
- package/src/util.ts +11 -1
- package/src/webrtc/index.ts +2 -1
- package/dist/src/pb/message.d.ts.map +0 -1
- package/dist/src/pb/message.js.map +0 -1
- package/dist/src/private-to-public/options.d.ts +0 -6
- package/dist/src/private-to-public/options.d.ts.map +0 -1
- package/dist/src/private-to-public/options.js +0 -2
- package/dist/src/private-to-public/options.js.map +0 -1
- package/dist/src/private-to-public/sdp.d.ts +0 -31
- package/dist/src/private-to-public/sdp.d.ts.map +0 -1
- package/dist/src/private-to-public/sdp.js.map +0 -1
- package/dist/src/private-to-public/util.d.ts +0 -2
- package/dist/src/private-to-public/util.d.ts.map +0 -1
- package/dist/src/private-to-public/util.js +0 -3
- package/dist/src/private-to-public/util.js.map +0 -1
- package/src/private-to-public/options.ts +0 -4
- package/src/private-to-public/sdp.ts +0 -159
- package/src/private-to-public/util.ts +0 -2
- /package/dist/src/{pb → private-to-public/pb}/message.d.ts +0 -0
- /package/dist/src/{pb → private-to-public/pb}/message.js +0 -0
- /package/src/{pb → private-to-public/pb}/message.proto +0 -0
- /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 {
|
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
|
7
|
-
import {
|
8
|
-
import {
|
9
|
-
import {
|
10
|
-
import {
|
11
|
-
import {
|
12
|
-
import {
|
13
|
-
import {
|
14
|
-
import {
|
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:
|
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
|
-
|
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:
|
121
|
-
|
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
|
-
|
127
|
+
theirPeerId = peerIdFromString(remotePeerString)
|
128
128
|
}
|
129
129
|
|
130
|
-
const
|
130
|
+
const ufrag = genUfrag()
|
131
131
|
|
132
|
-
//
|
133
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
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
|
-
|
208
|
-
|
209
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
}
|