@libp2p/webrtc 5.1.1 → 5.2.0-2b49a5f74
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 +27 -6
- package/dist/index.min.js +18 -18
- package/dist/src/index.d.ts +27 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +27 -6
- 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 +27 -6
- 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
package/src/index.ts
CHANGED
@@ -33,7 +33,6 @@
|
|
33
33
|
* import { identify } from '@libp2p/identify'
|
34
34
|
* import { webRTC } from '@libp2p/webrtc'
|
35
35
|
* import { webSockets } from '@libp2p/websockets'
|
36
|
-
* import * as filters from '@libp2p/websockets/filters'
|
37
36
|
* import { WebRTC } from '@multiformats/multiaddr-matcher'
|
38
37
|
* import delay from 'delay'
|
39
38
|
* import { pipe } from 'it-pipe'
|
@@ -47,10 +46,13 @@
|
|
47
46
|
* listen: ['/ip4/127.0.0.1/tcp/0/ws']
|
48
47
|
* },
|
49
48
|
* transports: [
|
50
|
-
* webSockets(
|
49
|
+
* webSockets()
|
51
50
|
* ],
|
52
51
|
* connectionEncrypters: [noise()],
|
53
52
|
* streamMuxers: [yamux()],
|
53
|
+
* connectionGater: {
|
54
|
+
* denyDialMultiaddr: () => false
|
55
|
+
* },
|
54
56
|
* services: {
|
55
57
|
* identify: identify(),
|
56
58
|
* relay: circuitRelayServer()
|
@@ -68,12 +70,15 @@
|
|
68
70
|
* ]
|
69
71
|
* },
|
70
72
|
* transports: [
|
71
|
-
* webSockets(
|
73
|
+
* webSockets(),
|
72
74
|
* webRTC(),
|
73
75
|
* circuitRelayTransport()
|
74
76
|
* ],
|
75
77
|
* connectionEncrypters: [noise()],
|
76
78
|
* streamMuxers: [yamux()],
|
79
|
+
* connectionGater: {
|
80
|
+
* denyDialMultiaddr: () => false
|
81
|
+
* },
|
77
82
|
* services: {
|
78
83
|
* identify: identify(),
|
79
84
|
* echo: echo()
|
@@ -105,12 +110,15 @@
|
|
105
110
|
* // direct WebRTC connection
|
106
111
|
* const dialer = await createLibp2p({
|
107
112
|
* transports: [
|
108
|
-
* webSockets(
|
113
|
+
* webSockets(),
|
109
114
|
* webRTC(),
|
110
115
|
* circuitRelayTransport()
|
111
116
|
* ],
|
112
117
|
* connectionEncrypters: [noise()],
|
113
118
|
* streamMuxers: [yamux()],
|
119
|
+
* connectionGater: {
|
120
|
+
* denyDialMultiaddr: () => false
|
121
|
+
* },
|
114
122
|
* services: {
|
115
123
|
* identify: identify(),
|
116
124
|
* echo: echo()
|
@@ -139,9 +147,22 @@
|
|
139
147
|
*
|
140
148
|
* @example WebRTC Direct
|
141
149
|
*
|
142
|
-
*
|
150
|
+
* WebRTC Direct allows a client to establish a WebRTC connection to a server
|
151
|
+
* without using a relay to first exchange SDP messages.
|
152
|
+
*
|
153
|
+
* Instead the server listens on a public UDP port and embeds its certificate
|
154
|
+
* hash in the published multiaddr. It derives the client's SDP offer based on
|
155
|
+
* the incoming IP/port of STUN messages sent to this public port.
|
156
|
+
*
|
157
|
+
* The client derives the server's SDP answer based on the information in the
|
158
|
+
* multiaddr so no SDP handshake via a third party is required.
|
159
|
+
*
|
160
|
+
* Full details of the connection protocol can be found in the [WebRTC Direct spec](https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md).
|
161
|
+
*
|
162
|
+
* Browsers cannot listen on WebRTC Direct addresses since they cannot open
|
163
|
+
* ports, but they can dial all spec-compliant servers.
|
143
164
|
*
|
144
|
-
*
|
165
|
+
* Node.js/go and rust-libp2p can listen on and dial WebRTC Direct addresses.
|
145
166
|
*
|
146
167
|
* ```TypeScript
|
147
168
|
* import { createLibp2p } from 'libp2p'
|
@@ -36,7 +36,7 @@ export class WebRTCPeerListener extends TypedEventEmitter<ListenerEvents> implem
|
|
36
36
|
getAddrs (): Multiaddr[] {
|
37
37
|
return this.transportManager
|
38
38
|
.getListeners()
|
39
|
-
.filter(l => l
|
39
|
+
.filter(l => !(l instanceof WebRTCPeerListener))
|
40
40
|
.map(l => l.getAddrs()
|
41
41
|
.filter(ma => Circuit.exactMatch(ma))
|
42
42
|
.map(ma => {
|
@@ -46,6 +46,10 @@ export class WebRTCPeerListener extends TypedEventEmitter<ListenerEvents> implem
|
|
46
46
|
.flat()
|
47
47
|
}
|
48
48
|
|
49
|
+
updateAnnounceAddrs (): void {
|
50
|
+
|
51
|
+
}
|
52
|
+
|
49
53
|
async close (): Promise<void> {
|
50
54
|
this.shutdownController.abort()
|
51
55
|
queueMicrotask(() => {
|
@@ -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
|
-
}
|