@libp2p/webrtc 5.1.1 → 5.2.0-1ab50cc0d
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 +15 -2
- package/dist/index.min.js +18 -18
- package/dist/src/index.d.ts +15 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +15 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/private-to-private/listener.d.ts +1 -0
- package/dist/src/private-to-private/listener.d.ts.map +1 -1
- package/dist/src/private-to-private/listener.js +3 -1
- package/dist/src/private-to-private/listener.js.map +1 -1
- package/dist/src/private-to-public/listener.browser.d.ts +1 -0
- package/dist/src/private-to-public/listener.browser.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.browser.js +2 -0
- package/dist/src/private-to-public/listener.browser.js.map +1 -1
- package/dist/src/private-to-public/listener.d.ts +2 -1
- package/dist/src/private-to-public/listener.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.js +104 -29
- package/dist/src/private-to-public/listener.js.map +1 -1
- package/package.json +13 -11
- package/src/index.ts +15 -2
- package/src/private-to-private/listener.ts +5 -1
- package/src/private-to-public/listener.browser.ts +4 -0
- package/src/private-to-public/listener.ts +131 -31
- package/dist/typedoc-urls.json +0 -14
@@ -1,8 +1,8 @@
|
|
1
1
|
import { networkInterfaces } from 'node:os'
|
2
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'
|
3
|
+
import { InvalidPeerIdError, TypedEventEmitter } from '@libp2p/interface'
|
4
|
+
import { multiaddr, protocols, fromStringTuples } from '@multiformats/multiaddr'
|
5
|
+
import { IP4, WebRTCDirect } from '@multiformats/multiaddr-matcher'
|
6
6
|
import { Crypto } from '@peculiar/webcrypto'
|
7
7
|
import getPort from 'get-port'
|
8
8
|
import pWaitFor from 'p-wait-for'
|
@@ -22,6 +22,8 @@ const crypto = new Crypto()
|
|
22
22
|
* The time to wait, in milliseconds, for the data channel handshake to complete
|
23
23
|
*/
|
24
24
|
const HANDSHAKE_TIMEOUT_MS = 10_000
|
25
|
+
const CODEC_WEBRTC_DIRECT = 0x0118
|
26
|
+
const CODEC_CERTHASH = 0x01d2
|
25
27
|
|
26
28
|
export interface WebRTCDirectListenerComponents {
|
27
29
|
peerId: PeerId
|
@@ -47,8 +49,18 @@ const UDP_PROTOCOL = protocols('udp')
|
|
47
49
|
const IP4_PROTOCOL = protocols('ip4')
|
48
50
|
const IP6_PROTOCOL = protocols('ip6')
|
49
51
|
|
52
|
+
interface UDPMuxServer {
|
53
|
+
server: Promise<StunServer>
|
54
|
+
isIPv4: boolean
|
55
|
+
isIPv6: boolean
|
56
|
+
port: number
|
57
|
+
owner: WebRTCDirectListener
|
58
|
+
peerId: PeerId
|
59
|
+
}
|
60
|
+
|
61
|
+
let UDP_MUX_LISTENERS: UDPMuxServer[] = []
|
62
|
+
|
50
63
|
export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> implements Listener {
|
51
|
-
private server?: StunServer
|
52
64
|
private readonly multiaddrs: Multiaddr[]
|
53
65
|
private certificate?: TransportCertificate
|
54
66
|
private readonly connections: Map<string, DirectRTCPeerConnection>
|
@@ -89,7 +101,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
89
101
|
if (host == null) {
|
90
102
|
throw new Error('IP4/6 host must be specified in webrtc-direct mulitaddr')
|
91
103
|
}
|
92
|
-
|
104
|
+
const port = parseInt(parts
|
93
105
|
.filter(([code, value]) => code === UDP_PROTOCOL.code)
|
94
106
|
.pop()?.[1] ?? '')
|
95
107
|
|
@@ -97,41 +109,90 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
97
109
|
throw new Error('UDP port must be specified in webrtc-direct mulitaddr')
|
98
110
|
}
|
99
111
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
112
|
+
// have to do this before any async work happens so starting two listeners
|
113
|
+
// for the same port concurrently (e.g. ipv4/ipv6 both port 0) results in a
|
114
|
+
// single mux listener. This is necessary because libjuice binds to all
|
115
|
+
// interfaces for a given port so we we need to key on just the port number
|
116
|
+
// not the host + the port number
|
117
|
+
let existingServer = UDP_MUX_LISTENERS.find(s => s.port === port)
|
118
|
+
|
119
|
+
// if the server has not been started yet, or the port is a wildcard port
|
120
|
+
// and there is already a wildcard port for this address family, start a new
|
121
|
+
// UDP mux server
|
122
|
+
const wildcardPorts = port === 0 && existingServer?.port === 0
|
123
|
+
const sameAddressFamily = (existingServer?.isIPv4 === true && isIPv4(host)) || (existingServer?.isIPv6 === true && isIPv6(host))
|
124
|
+
let createdMuxServer = false
|
125
|
+
|
126
|
+
if (existingServer == null || (wildcardPorts && sameAddressFamily)) {
|
127
|
+
this.log('starting UDP mux server on %s:%p', host, port)
|
128
|
+
existingServer = this.startUDPMuxServer(host, port)
|
129
|
+
UDP_MUX_LISTENERS.push(existingServer)
|
130
|
+
createdMuxServer = true
|
104
131
|
}
|
105
132
|
|
106
|
-
|
107
|
-
this
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
})
|
112
|
-
|
113
|
-
let certificate = this.certificate
|
133
|
+
if (!existingServer.peerId.equals(this.components.peerId)) {
|
134
|
+
// this would have to be another in-process peer so we are likely in a
|
135
|
+
// testing environment
|
136
|
+
throw new InvalidPeerIdError(`Another peer is already performing UDP mux on ${host}:${existingServer.port}`)
|
137
|
+
}
|
114
138
|
|
115
|
-
|
116
|
-
|
117
|
-
name: 'ECDSA',
|
118
|
-
namedCurve: 'P-256'
|
119
|
-
}, true, ['sign', 'verify'])
|
139
|
+
const server = await existingServer.server
|
140
|
+
const address = server.address()
|
120
141
|
|
121
|
-
|
122
|
-
|
123
|
-
})
|
142
|
+
if (!createdMuxServer) {
|
143
|
+
this.log('reused existing UDP mux server on %s:%p', host, address.port)
|
124
144
|
}
|
125
145
|
|
126
|
-
|
127
|
-
|
128
|
-
getNetworkAddresses(address.address, address.port, ipVersion).forEach((ma) => {
|
129
|
-
this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${certificate.certhash}`))
|
146
|
+
getNetworkAddresses(host, address.port, ipVersion).forEach((ma) => {
|
147
|
+
this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${this.certificate?.certhash}`))
|
130
148
|
})
|
131
149
|
|
132
150
|
this.safeDispatchEvent('listening')
|
133
151
|
}
|
134
152
|
|
153
|
+
private startUDPMuxServer (host: string, port: number): UDPMuxServer {
|
154
|
+
return {
|
155
|
+
peerId: this.components.peerId,
|
156
|
+
owner: this,
|
157
|
+
port,
|
158
|
+
isIPv4: isIPv4(host),
|
159
|
+
isIPv6: isIPv6(host),
|
160
|
+
server: Promise.resolve()
|
161
|
+
.then(async (): Promise<StunServer> => {
|
162
|
+
// ensure we have a certificate
|
163
|
+
if (this.certificate == null) {
|
164
|
+
this.log.trace('creating TLS certificate')
|
165
|
+
const keyPair = await crypto.subtle.generateKey({
|
166
|
+
name: 'ECDSA',
|
167
|
+
namedCurve: 'P-256'
|
168
|
+
}, true, ['sign', 'verify'])
|
169
|
+
|
170
|
+
const certificate = await generateTransportCertificate(keyPair, {
|
171
|
+
days: 365 * 10
|
172
|
+
})
|
173
|
+
|
174
|
+
if (this.certificate == null) {
|
175
|
+
this.certificate = certificate
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
if (port === 0) {
|
180
|
+
// libjuice doesn't map 0 to a random free port so we have to do it
|
181
|
+
// ourselves
|
182
|
+
this.log.trace('searching for free port')
|
183
|
+
port = await getPort()
|
184
|
+
}
|
185
|
+
|
186
|
+
return stunListener(host, port, this.log, (ufrag, remoteHost, remotePort) => {
|
187
|
+
this.incomingConnection(ufrag, remoteHost, remotePort)
|
188
|
+
.catch(err => {
|
189
|
+
this.log.error('error processing incoming STUN request', err)
|
190
|
+
})
|
191
|
+
})
|
192
|
+
})
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
135
196
|
private async incomingConnection (ufrag: string, remoteHost: string, remotePort: number): Promise<void> {
|
136
197
|
const key = `${remoteHost}:${remotePort}:${ufrag}`
|
137
198
|
let peerConnection = this.connections.get(key)
|
@@ -184,12 +245,51 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
184
245
|
return this.multiaddrs
|
185
246
|
}
|
186
247
|
|
248
|
+
updateAnnounceAddrs (multiaddrs: Multiaddr[]): void {
|
249
|
+
for (let i = 0; i < multiaddrs.length; i++) {
|
250
|
+
let ma = multiaddrs[i]
|
251
|
+
|
252
|
+
if (!WebRTCDirect.exactMatch(ma)) {
|
253
|
+
continue
|
254
|
+
}
|
255
|
+
|
256
|
+
// add the certhash if it is missing
|
257
|
+
const tuples = ma.stringTuples()
|
258
|
+
|
259
|
+
for (let j = 0; j < tuples.length; j++) {
|
260
|
+
if (tuples[j][0] !== CODEC_WEBRTC_DIRECT) {
|
261
|
+
continue
|
262
|
+
}
|
263
|
+
|
264
|
+
const certhashIndex = j + 1
|
265
|
+
|
266
|
+
if (tuples[certhashIndex] == null || tuples[certhashIndex][0] !== CODEC_CERTHASH) {
|
267
|
+
tuples.splice(certhashIndex, 0, [CODEC_CERTHASH, this.certificate?.certhash])
|
268
|
+
|
269
|
+
ma = fromStringTuples(tuples)
|
270
|
+
multiaddrs[i] = ma
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
187
276
|
async close (): Promise<void> {
|
188
277
|
for (const connection of this.connections.values()) {
|
189
278
|
connection.close()
|
190
279
|
}
|
191
280
|
|
192
|
-
|
281
|
+
// stop our UDP mux listeners
|
282
|
+
await Promise.all(
|
283
|
+
UDP_MUX_LISTENERS
|
284
|
+
.filter(listener => listener.owner === this)
|
285
|
+
.map(async listener => {
|
286
|
+
const server = await listener.server
|
287
|
+
await server.close()
|
288
|
+
})
|
289
|
+
)
|
290
|
+
|
291
|
+
// remove our stopped UDP mux listeners
|
292
|
+
UDP_MUX_LISTENERS = UDP_MUX_LISTENERS.filter(listener => listener.owner !== this)
|
193
293
|
|
194
294
|
// RTCPeerConnections will be removed from the connections map when their
|
195
295
|
// connection state changes to 'closed'/'disconnected'/'failed
|
@@ -202,7 +302,7 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
|
|
202
302
|
}
|
203
303
|
|
204
304
|
function getNetworkAddresses (host: string, port: number, version: 4 | 6): string[] {
|
205
|
-
if (host === '0.0.0.0' || host === '::
|
305
|
+
if (host === '0.0.0.0' || host === '::') {
|
206
306
|
// return all ip4 interfaces
|
207
307
|
return Object.entries(networkInterfaces())
|
208
308
|
.flatMap(([_, addresses]) => addresses)
|
package/dist/typedoc-urls.json
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"DataChannelOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.DataChannelOptions.html",
|
3
|
-
".:DataChannelOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.DataChannelOptions.html",
|
4
|
-
"TransportCertificate": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.TransportCertificate.html",
|
5
|
-
".:TransportCertificate": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.TransportCertificate.html",
|
6
|
-
"WebRTCDirectTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCDirectTransportComponents.html",
|
7
|
-
"WebRTCTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportComponents.html",
|
8
|
-
"WebRTCTransportDirectInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportDirectInit.html",
|
9
|
-
"WebRTCTransportInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportInit.html",
|
10
|
-
"webRTC": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTC.html",
|
11
|
-
".:webRTC": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTC.html",
|
12
|
-
"webRTCDirect": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTCDirect.html",
|
13
|
-
".:webRTCDirect": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTCDirect.html"
|
14
|
-
}
|