@libp2p/webrtc 5.2.9 → 5.2.10-b9e32cc37

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 (40) hide show
  1. package/README.md +54 -0
  2. package/dist/index.min.js +153 -18
  3. package/dist/src/constants.d.ts +20 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +20 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +54 -0
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js +54 -0
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/private-to-private/util.d.ts +2 -2
  12. package/dist/src/private-to-private/util.d.ts.map +1 -1
  13. package/dist/src/private-to-public/listener.d.ts +9 -4
  14. package/dist/src/private-to-public/listener.d.ts.map +1 -1
  15. package/dist/src/private-to-public/listener.js +8 -18
  16. package/dist/src/private-to-public/listener.js.map +1 -1
  17. package/dist/src/private-to-public/transport.d.ts +68 -10
  18. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  19. package/dist/src/private-to-public/transport.js +204 -33
  20. package/dist/src/private-to-public/transport.js.map +1 -1
  21. package/dist/src/private-to-public/utils/connect.js +126 -124
  22. package/dist/src/private-to-public/utils/connect.js.map +1 -1
  23. package/dist/src/private-to-public/utils/pem.d.ts +6 -0
  24. package/dist/src/private-to-public/utils/pem.d.ts.map +1 -0
  25. package/dist/src/private-to-public/utils/pem.js +15 -0
  26. package/dist/src/private-to-public/utils/pem.js.map +1 -0
  27. package/dist/src/stream.d.ts +5 -0
  28. package/dist/src/stream.d.ts.map +1 -1
  29. package/dist/src/stream.js +14 -9
  30. package/dist/src/stream.js.map +1 -1
  31. package/package.json +12 -9
  32. package/src/constants.ts +25 -0
  33. package/src/index.ts +54 -0
  34. package/src/private-to-private/util.ts +2 -2
  35. package/src/private-to-public/listener.ts +18 -26
  36. package/src/private-to-public/transport.ts +304 -39
  37. package/src/private-to-public/utils/connect.ts +139 -135
  38. package/src/private-to-public/utils/pem.ts +18 -0
  39. package/src/stream.ts +20 -11
  40. 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 remote will initiate the noise handshake. This
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
- if (options.role === 'client') {
49
- // the client has to set the local offer before the remote answer
50
-
51
- // Create offer and munge sdp with ufrag == pwd. This allows the remote to
52
- // respond to STUN messages without performing an actual SDP exchange.
53
- // This is because it can infer the passwd field by reading the USERNAME
54
- // attribute of the STUN message.
55
- options.log.trace('client creating local offer')
56
- const offerSdp = await peerConnection.createOffer()
57
- options.log.trace('client created local offer %s', offerSdp.sdp)
58
- const mungedOfferSdp = sdp.munge(offerSdp, ufrag)
59
- options.log.trace('client setting local offer %s', mungedOfferSdp.sdp)
60
- await peerConnection.setLocalDescription(mungedOfferSdp)
61
-
62
- const answerSdp = sdp.serverAnswerFromMultiaddr(options.remoteAddr, ufrag)
63
- options.log.trace('client setting server description %s', answerSdp.sdp)
64
- await peerConnection.setRemoteDescription(answerSdp)
65
- } else {
66
- // the server has to set the remote offer before the local answer
67
- const offerSdp = sdp.clientOfferFromMultiAddr(options.remoteAddr, ufrag)
68
- options.log.trace('server setting client %s %s', offerSdp.type, offerSdp.sdp)
69
- await peerConnection.setRemoteDescription(offerSdp)
70
-
71
- // Create offer and munge sdp with ufrag == pwd. This allows the remote to
72
- // respond to STUN messages without performing an actual SDP exchange.
73
- // This is because it can infer the passwd field by reading the USERNAME
74
- // attribute of the STUN message.
75
- options.log.trace('server creating local answer')
76
- const answerSdp = await peerConnection.createAnswer()
77
- options.log.trace('server created local answer')
78
- const mungedAnswerSdp = sdp.munge(answerSdp, ufrag)
79
- options.log.trace('server setting local description %s', answerSdp.sdp)
80
- await peerConnection.setLocalDescription(mungedAnswerSdp)
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
- options.log.trace('%s wait for handshake channel to open', options.role)
84
- await raceEvent(handshakeDataChannel, 'open', options.signal)
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
- options.log.trace('%s handshake channel opened', options.role)
89
+ options.log.trace('%s handshake channel opened', options.role)
87
90
 
88
- if (options.role === 'server') {
89
- // now that the connection has been opened, add the remote's certhash to
90
- // it's multiaddr so we can complete the noise handshake
91
- const remoteFingerprint = peerConnection.remoteFingerprint()?.value ?? ''
92
- options.remoteAddr = options.remoteAddr.encapsulate(sdp.fingerprint2Ma(remoteFingerprint))
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
- // Do noise handshake.
96
- // Set the Noise Prologue to libp2p-webrtc-noise:<FINGERPRINTS> before
97
- // starting the actual Noise handshake.
98
- // <FINGERPRINTS> is the concatenation of the of the two TLS fingerprints
99
- // of A (responder) and B (initiator) in their byte representation.
100
- const localFingerprint = sdp.getFingerprintFromSdp(peerConnection.localDescription?.sdp)
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
- if (localFingerprint == null) {
103
- throw new WebRTCTransportError('Could not get fingerprint from local description sdp')
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
- options.log.trace('%s performing noise handshake', options.role)
107
- const noisePrologue = generateNoisePrologue(localFingerprint, options.remoteAddr, options.role)
108
-
109
- // Since we use the default crypto interface and do not use a static key
110
- // or early data, we pass in undefined for these parameters.
111
- const connectionEncrypter = noise({ prologueBytes: noisePrologue })(options)
112
-
113
- const wrappedChannel = createStream({
114
- channel: handshakeDataChannel,
115
- direction: 'inbound',
116
- logger: options.logger,
117
- ...(options.dataChannel ?? {})
118
- })
119
- const wrappedDuplex = {
120
- ...wrappedChannel,
121
- sink: wrappedChannel.sink.bind(wrappedChannel),
122
- source: (async function * () {
123
- for await (const list of wrappedChannel.source) {
124
- for (const buf of list) {
125
- yield buf
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
- // Creating the connection before completion of the noise
132
- // handshake ensures that the stream opening callback is set up
133
- const maConn = new WebRTCMultiaddrConnection(options, {
134
- peerConnection,
135
- remoteAddr: options.remoteAddr,
136
- timeline: {
137
- open: Date.now()
138
- },
139
- metrics: options.events
140
- })
141
-
142
- peerConnection.addEventListener(CONNECTION_STATE_CHANGE_EVENT, () => {
143
- switch (peerConnection.connectionState) {
144
- case 'failed':
145
- case 'disconnected':
146
- case 'closed':
147
- maConn.close().catch((err) => {
148
- options.log.error('error closing connection', err)
149
- maConn.abort(err)
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
- // Track opened peer connection
158
- options.events?.increment({ peer_connection: true })
159
-
160
- const muxerFactory = new DataChannelMuxerFactory(options, {
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
- options.log.trace('%s upgrade outbound', options.role)
176
- return options.upgrader.upgradeOutbound(maConn, {
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
- // For inbound connections, we are expected to start the noise handshake.
185
- // Therefore, we need to secure an outbound noise connection from the remote.
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
- // stop processing incoming messages
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
- this.log.trace('-> will send %d bytes', data.byteLength)
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('-> sending message %s', this.channel.readyState)
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('-> sent data %s', this.channel.readyState)
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: direction === 'inbound' ? (`i${channel.id}`) : `r${channel.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
  }
@@ -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
- }