@libp2p/webrtc 5.2.9 → 5.2.10-3833353bd
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 +54 -0
- package/dist/index.min.js +153 -18
- package/dist/src/constants.d.ts +20 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +20 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +54 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +54 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/private-to-private/util.d.ts +2 -2
- package/dist/src/private-to-private/util.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.d.ts +9 -4
- package/dist/src/private-to-public/listener.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.js +8 -18
- package/dist/src/private-to-public/listener.js.map +1 -1
- package/dist/src/private-to-public/transport.d.ts +68 -10
- package/dist/src/private-to-public/transport.d.ts.map +1 -1
- package/dist/src/private-to-public/transport.js +204 -33
- package/dist/src/private-to-public/transport.js.map +1 -1
- package/dist/src/private-to-public/utils/connect.js +126 -124
- package/dist/src/private-to-public/utils/connect.js.map +1 -1
- package/dist/src/private-to-public/utils/pem.d.ts +6 -0
- package/dist/src/private-to-public/utils/pem.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/pem.js +15 -0
- package/dist/src/private-to-public/utils/pem.js.map +1 -0
- package/dist/src/stream.d.ts +5 -0
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +14 -9
- package/dist/src/stream.js.map +1 -1
- package/package.json +12 -9
- package/src/constants.ts +25 -0
- package/src/index.ts +54 -0
- package/src/private-to-private/util.ts +2 -2
- package/src/private-to-public/listener.ts +18 -26
- package/src/private-to-public/transport.ts +304 -39
- package/src/private-to-public/utils/connect.ts +139 -135
- package/src/private-to-public/utils/pem.ts +18 -0
- package/src/stream.ts +20 -11
- package/dist/typedoc-urls.json +0 -14
@@ -41,161 +41,165 @@ export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: s
|
|
41
41
|
export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: string, options: ServerOptions): Promise<void>
|
42
42
|
export async function connect (peerConnection: DirectRTCPeerConnection, ufrag: string, options: ConnectOptions): Promise<any> {
|
43
43
|
// create data channel for running the noise handshake. Once the data
|
44
|
-
// channel is opened, the
|
44
|
+
// channel is opened, the listener will initiate the noise handshake. This
|
45
45
|
// is used to confirm the identity of the peer.
|
46
46
|
const handshakeDataChannel = peerConnection.createDataChannel('', { negotiated: true, id: 0 })
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
48
|
+
try {
|
49
|
+
if (options.role === 'client') {
|
50
|
+
// the client has to set the local offer before the remote answer
|
51
|
+
|
52
|
+
// Create offer and munge sdp with ufrag == pwd. This allows the remote to
|
53
|
+
// respond to STUN messages without performing an actual SDP exchange.
|
54
|
+
// This is because it can infer the passwd field by reading the USERNAME
|
55
|
+
// attribute of the STUN message.
|
56
|
+
options.log.trace('client creating local offer')
|
57
|
+
const offerSdp = await peerConnection.createOffer()
|
58
|
+
options.log.trace('client created local offer %s', offerSdp.sdp)
|
59
|
+
const mungedOfferSdp = sdp.munge(offerSdp, ufrag)
|
60
|
+
options.log.trace('client setting local offer %s', mungedOfferSdp.sdp)
|
61
|
+
await peerConnection.setLocalDescription(mungedOfferSdp)
|
62
|
+
|
63
|
+
const answerSdp = sdp.serverAnswerFromMultiaddr(options.remoteAddr, ufrag)
|
64
|
+
options.log.trace('client setting server description %s', answerSdp.sdp)
|
65
|
+
await peerConnection.setRemoteDescription(answerSdp)
|
66
|
+
} else {
|
67
|
+
// the server has to set the remote offer before the local answer
|
68
|
+
const offerSdp = sdp.clientOfferFromMultiAddr(options.remoteAddr, ufrag)
|
69
|
+
options.log.trace('server setting client %s %s', offerSdp.type, offerSdp.sdp)
|
70
|
+
await peerConnection.setRemoteDescription(offerSdp)
|
71
|
+
|
72
|
+
// Create offer and munge sdp with ufrag == pwd. This allows the remote to
|
73
|
+
// respond to STUN messages without performing an actual SDP exchange.
|
74
|
+
// This is because it can infer the passwd field by reading the USERNAME
|
75
|
+
// attribute of the STUN message.
|
76
|
+
options.log.trace('server creating local answer')
|
77
|
+
const answerSdp = await peerConnection.createAnswer()
|
78
|
+
options.log.trace('server created local answer')
|
79
|
+
const mungedAnswerSdp = sdp.munge(answerSdp, ufrag)
|
80
|
+
options.log.trace('server setting local description %s', answerSdp.sdp)
|
81
|
+
await peerConnection.setLocalDescription(mungedAnswerSdp)
|
82
|
+
}
|
82
83
|
|
83
|
-
|
84
|
-
|
84
|
+
if (handshakeDataChannel.readyState !== 'open') {
|
85
|
+
options.log.trace('%s wait for handshake channel to open, starting status %s', options.role, handshakeDataChannel.readyState)
|
86
|
+
await raceEvent(handshakeDataChannel, 'open', options.signal)
|
87
|
+
}
|
85
88
|
|
86
|
-
|
89
|
+
options.log.trace('%s handshake channel opened', options.role)
|
87
90
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
if (options.role === 'server') {
|
92
|
+
// now that the connection has been opened, add the remote's certhash to
|
93
|
+
// it's multiaddr so we can complete the noise handshake
|
94
|
+
const remoteFingerprint = peerConnection.remoteFingerprint()?.value ?? ''
|
95
|
+
options.remoteAddr = options.remoteAddr.encapsulate(sdp.fingerprint2Ma(remoteFingerprint))
|
96
|
+
}
|
94
97
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
// Do noise handshake.
|
99
|
+
// Set the Noise Prologue to libp2p-webrtc-noise:<FINGERPRINTS> before
|
100
|
+
// starting the actual Noise handshake.
|
101
|
+
// <FINGERPRINTS> is the concatenation of the of the two TLS fingerprints
|
102
|
+
// of A (responder) and B (initiator) in their byte representation.
|
103
|
+
const localFingerprint = sdp.getFingerprintFromSdp(peerConnection.localDescription?.sdp)
|
101
104
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
+
if (localFingerprint == null) {
|
106
|
+
throw new WebRTCTransportError('Could not get fingerprint from local description sdp')
|
107
|
+
}
|
108
|
+
|
109
|
+
options.log.trace('%s performing noise handshake', options.role)
|
110
|
+
const noisePrologue = generateNoisePrologue(localFingerprint, options.remoteAddr, options.role)
|
111
|
+
|
112
|
+
// Since we use the default crypto interface and do not use a static key
|
113
|
+
// or early data, we pass in undefined for these parameters.
|
114
|
+
const connectionEncrypter = noise({ prologueBytes: noisePrologue })(options)
|
105
115
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
116
|
+
const handshakeStream = createStream({
|
117
|
+
channel: handshakeDataChannel,
|
118
|
+
direction: 'outbound',
|
119
|
+
handshake: true,
|
120
|
+
logger: options.logger,
|
121
|
+
...(options.dataChannel ?? {})
|
122
|
+
})
|
123
|
+
|
124
|
+
// Creating the connection before completion of the noise
|
125
|
+
// handshake ensures that the stream opening callback is set up
|
126
|
+
const maConn = new WebRTCMultiaddrConnection(options, {
|
127
|
+
peerConnection,
|
128
|
+
remoteAddr: options.remoteAddr,
|
129
|
+
timeline: {
|
130
|
+
open: Date.now()
|
131
|
+
},
|
132
|
+
metrics: options.events
|
133
|
+
})
|
134
|
+
|
135
|
+
peerConnection.addEventListener(CONNECTION_STATE_CHANGE_EVENT, () => {
|
136
|
+
switch (peerConnection.connectionState) {
|
137
|
+
case 'failed':
|
138
|
+
case 'disconnected':
|
139
|
+
case 'closed':
|
140
|
+
maConn.close().catch((err) => {
|
141
|
+
options.log.error('error closing connection', err)
|
142
|
+
maConn.abort(err)
|
143
|
+
})
|
144
|
+
break
|
145
|
+
default:
|
146
|
+
break
|
127
147
|
}
|
128
|
-
}
|
129
|
-
|
148
|
+
})
|
149
|
+
|
150
|
+
// Track opened peer connection
|
151
|
+
options.events?.increment({ peer_connection: true })
|
152
|
+
|
153
|
+
const muxerFactory = new DataChannelMuxerFactory(options, {
|
154
|
+
peerConnection,
|
155
|
+
metrics: options.events,
|
156
|
+
dataChannelOptions: options.dataChannel
|
157
|
+
})
|
130
158
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
break
|
152
|
-
default:
|
153
|
-
break
|
159
|
+
if (options.role === 'client') {
|
160
|
+
// For outbound connections, the remote is expected to start the noise
|
161
|
+
// handshake. Therefore, we need to secure an inbound noise connection
|
162
|
+
// from the server.
|
163
|
+
options.log.trace('%s secure inbound', options.role)
|
164
|
+
await connectionEncrypter.secureInbound(handshakeStream, {
|
165
|
+
remotePeer: options.remotePeerId,
|
166
|
+
signal: options.signal
|
167
|
+
})
|
168
|
+
|
169
|
+
options.log.trace('%s closing handshake datachannel', options.role)
|
170
|
+
handshakeDataChannel.close()
|
171
|
+
|
172
|
+
options.log.trace('%s upgrade outbound', options.role)
|
173
|
+
return await options.upgrader.upgradeOutbound(maConn, {
|
174
|
+
skipProtection: true,
|
175
|
+
skipEncryption: true,
|
176
|
+
muxerFactory,
|
177
|
+
signal: options.signal
|
178
|
+
})
|
154
179
|
}
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
peerConnection,
|
162
|
-
metrics: options.events,
|
163
|
-
dataChannelOptions: options.dataChannel
|
164
|
-
})
|
165
|
-
|
166
|
-
if (options.role === 'client') {
|
167
|
-
// For outbound connections, the remote is expected to start the noise handshake.
|
168
|
-
// Therefore, we need to secure an inbound noise connection from the remote.
|
169
|
-
options.log.trace('%s secure inbound', options.role)
|
170
|
-
await connectionEncrypter.secureInbound(wrappedDuplex, {
|
180
|
+
|
181
|
+
// For inbound connections, the server is are expected to start the noise
|
182
|
+
// handshake. Therefore, we need to secure an outbound noise connection from
|
183
|
+
// the client.
|
184
|
+
options.log.trace('%s secure outbound', options.role)
|
185
|
+
const result = await connectionEncrypter.secureOutbound(handshakeStream, {
|
171
186
|
remotePeer: options.remotePeerId,
|
172
187
|
signal: options.signal
|
173
188
|
})
|
174
189
|
|
175
|
-
|
176
|
-
|
190
|
+
maConn.remoteAddr = maConn.remoteAddr.encapsulate(`/p2p/${result.remotePeer}`)
|
191
|
+
|
192
|
+
options.log.trace('%s upgrade inbound', options.role)
|
193
|
+
|
194
|
+
await options.upgrader.upgradeInbound(maConn, {
|
177
195
|
skipProtection: true,
|
178
196
|
skipEncryption: true,
|
179
197
|
muxerFactory,
|
180
198
|
signal: options.signal
|
181
199
|
})
|
182
|
-
}
|
200
|
+
} catch (err) {
|
201
|
+
handshakeDataChannel.close()
|
183
202
|
|
184
|
-
|
185
|
-
|
186
|
-
options.log.trace('%s secure outbound', options.role)
|
187
|
-
const result = await connectionEncrypter.secureOutbound(wrappedDuplex, {
|
188
|
-
remotePeer: options.remotePeerId,
|
189
|
-
signal: options.signal
|
190
|
-
})
|
191
|
-
maConn.remoteAddr = maConn.remoteAddr.encapsulate(`/p2p/${result.remotePeer}`)
|
192
|
-
|
193
|
-
options.log.trace('%s upgrade inbound', options.role)
|
194
|
-
|
195
|
-
await options.upgrader.upgradeInbound(maConn, {
|
196
|
-
skipProtection: true,
|
197
|
-
skipEncryption: true,
|
198
|
-
muxerFactory,
|
199
|
-
signal: options.signal
|
200
|
-
})
|
203
|
+
throw err
|
204
|
+
}
|
201
205
|
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { privateKeyToCryptoKeyPair } from '@libp2p/crypto/keys'
|
2
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
3
|
+
import type { PrivateKey } from '@libp2p/interface'
|
4
|
+
|
5
|
+
export function toBuffer (uint8Array: Uint8Array): Buffer {
|
6
|
+
return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength)
|
7
|
+
}
|
8
|
+
|
9
|
+
export async function formatAsPem (privateKey: PrivateKey): Promise<string> {
|
10
|
+
const keyPair = await privateKeyToCryptoKeyPair(privateKey)
|
11
|
+
const exported = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
|
12
|
+
|
13
|
+
return [
|
14
|
+
'-----BEGIN PRIVATE KEY-----',
|
15
|
+
...uint8ArrayToString(new Uint8Array(exported), 'base64pad').split(/(.{64})/).filter(Boolean),
|
16
|
+
'-----END PRIVATE KEY-----'
|
17
|
+
].join('\n')
|
18
|
+
}
|
package/src/stream.ts
CHANGED
@@ -60,7 +60,7 @@ export class WebRTCStream extends AbstractStream {
|
|
60
60
|
// override onEnd to send/receive FIN_ACK before closing the stream
|
61
61
|
const originalOnEnd = init.onEnd
|
62
62
|
init.onEnd = (err?: Error): void => {
|
63
|
-
this.log.trace('readable and writeable ends closed', this.status)
|
63
|
+
this.log.trace('readable and writeable ends closed with status "%s"', this.status)
|
64
64
|
|
65
65
|
void Promise.resolve(async () => {
|
66
66
|
if (this.timeline.abort != null || this.timeline.reset !== null) {
|
@@ -77,9 +77,8 @@ export class WebRTCStream extends AbstractStream {
|
|
77
77
|
}
|
78
78
|
})
|
79
79
|
.then(() => {
|
80
|
-
|
80
|
+
// stop processing incoming messages
|
81
81
|
this.incomingData.end()
|
82
|
-
this.channel.close()
|
83
82
|
|
84
83
|
// final cleanup
|
85
84
|
originalOnEnd?.(err)
|
@@ -87,6 +86,9 @@ export class WebRTCStream extends AbstractStream {
|
|
87
86
|
.catch(err => {
|
88
87
|
this.log.error('error ending stream', err)
|
89
88
|
})
|
89
|
+
.finally(() => {
|
90
|
+
this.channel.close()
|
91
|
+
})
|
90
92
|
}
|
91
93
|
|
92
94
|
super(init)
|
@@ -229,6 +231,7 @@ export class WebRTCStream extends AbstractStream {
|
|
229
231
|
}
|
230
232
|
|
231
233
|
try {
|
234
|
+
this.log.trace('sending message, channel state "%s"', this.channel.readyState)
|
232
235
|
// send message without copying data
|
233
236
|
this.channel.send(data.subarray())
|
234
237
|
} catch (err: any) {
|
@@ -237,8 +240,7 @@ export class WebRTCStream extends AbstractStream {
|
|
237
240
|
}
|
238
241
|
|
239
242
|
async sendData (data: Uint8ArrayList): Promise<void> {
|
240
|
-
|
241
|
-
|
243
|
+
const bytesTotal = data.byteLength
|
242
244
|
// sending messages is an async operation so use a copy of the list as it
|
243
245
|
// may be changed beneath us
|
244
246
|
data = data.sublist()
|
@@ -248,14 +250,13 @@ export class WebRTCStream extends AbstractStream {
|
|
248
250
|
const buf = data.subarray(0, toSend)
|
249
251
|
const messageBuf = Message.encode({ message: buf })
|
250
252
|
const sendBuf = lengthPrefixed.encode.single(messageBuf)
|
251
|
-
this.log.trace('
|
253
|
+
this.log.trace('sending %d/%d bytes on channel', buf.byteLength, bytesTotal)
|
252
254
|
await this._sendMessage(sendBuf)
|
253
|
-
this.log.trace('-> sent message %s', this.channel.readyState)
|
254
255
|
|
255
256
|
data.consume(toSend)
|
256
257
|
}
|
257
258
|
|
258
|
-
this.log.trace('
|
259
|
+
this.log.trace('finished sending data, channel state "%s"', this.channel.readyState)
|
259
260
|
}
|
260
261
|
|
261
262
|
async sendReset (): Promise<void> {
|
@@ -263,6 +264,8 @@ export class WebRTCStream extends AbstractStream {
|
|
263
264
|
await this._sendFlag(Message.Flag.RESET)
|
264
265
|
} catch (err) {
|
265
266
|
this.log.error('failed to send reset - %e', err)
|
267
|
+
} finally {
|
268
|
+
this.channel.close()
|
266
269
|
}
|
267
270
|
}
|
268
271
|
|
@@ -387,14 +390,20 @@ export interface WebRTCStreamOptions extends DataChannelOptions {
|
|
387
390
|
onEnd?(err?: Error | undefined): void
|
388
391
|
|
389
392
|
logger: ComponentLogger
|
393
|
+
|
394
|
+
/**
|
395
|
+
* If true the underlying datachannel is being used to perform the noise
|
396
|
+
* handshake during connection establishment
|
397
|
+
*/
|
398
|
+
handshake?: boolean
|
390
399
|
}
|
391
400
|
|
392
401
|
export function createStream (options: WebRTCStreamOptions): WebRTCStream {
|
393
|
-
const { channel, direction } = options
|
402
|
+
const { channel, direction, handshake } = options
|
394
403
|
|
395
404
|
return new WebRTCStream({
|
396
|
-
id:
|
397
|
-
log: options.logger.forComponent(`libp2p:webrtc:stream:${direction}:${channel.id}`),
|
405
|
+
id: `${channel.id}`,
|
406
|
+
log: options.logger.forComponent(`libp2p:webrtc:stream:${handshake === true ? 'handshake' : direction}:${channel.id}`),
|
398
407
|
...options
|
399
408
|
})
|
400
409
|
}
|
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
|
-
}
|