@libp2p/webrtc 0.0.0

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 (57) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +210 -0
  3. package/dist/index.min.js +54 -0
  4. package/dist/index.min.js.map +7 -0
  5. package/dist/proto_ts/message.d.ts +56 -0
  6. package/dist/proto_ts/message.d.ts.map +1 -0
  7. package/dist/proto_ts/message.js +86 -0
  8. package/dist/proto_ts/message.js.map +1 -0
  9. package/dist/src/error.d.ts +54 -0
  10. package/dist/src/error.d.ts.map +1 -0
  11. package/dist/src/error.js +105 -0
  12. package/dist/src/error.js.map +1 -0
  13. package/dist/src/index.d.ts +4 -0
  14. package/dist/src/index.d.ts.map +1 -0
  15. package/dist/src/index.js +6 -0
  16. package/dist/src/index.js.map +1 -0
  17. package/dist/src/maconn.d.ts +43 -0
  18. package/dist/src/maconn.d.ts.map +1 -0
  19. package/dist/src/maconn.js +29 -0
  20. package/dist/src/maconn.js.map +1 -0
  21. package/dist/src/muxer.d.ts +55 -0
  22. package/dist/src/muxer.d.ts.map +1 -0
  23. package/dist/src/muxer.js +92 -0
  24. package/dist/src/muxer.js.map +1 -0
  25. package/dist/src/options.d.ts +6 -0
  26. package/dist/src/options.d.ts.map +1 -0
  27. package/dist/src/options.js +2 -0
  28. package/dist/src/options.js.map +1 -0
  29. package/dist/src/sdp.d.ts +33 -0
  30. package/dist/src/sdp.d.ts.map +1 -0
  31. package/dist/src/sdp.js +118 -0
  32. package/dist/src/sdp.js.map +1 -0
  33. package/dist/src/stream.d.ts +141 -0
  34. package/dist/src/stream.d.ts.map +1 -0
  35. package/dist/src/stream.js +290 -0
  36. package/dist/src/stream.js.map +1 -0
  37. package/dist/src/transport.d.ts +60 -0
  38. package/dist/src/transport.d.ts.map +1 -0
  39. package/dist/src/transport.js +193 -0
  40. package/dist/src/transport.js.map +1 -0
  41. package/dist/src/util.d.ts +5 -0
  42. package/dist/src/util.d.ts.map +1 -0
  43. package/dist/src/util.js +5 -0
  44. package/dist/src/util.js.map +1 -0
  45. package/dist/stats.json +1 -0
  46. package/package.json +170 -0
  47. package/proto_ts/message.ts +105 -0
  48. package/src/error.ts +123 -0
  49. package/src/index.ts +6 -0
  50. package/src/maconn.ts +67 -0
  51. package/src/message.proto +22 -0
  52. package/src/muxer.ts +120 -0
  53. package/src/options.ts +4 -0
  54. package/src/sdp.ts +136 -0
  55. package/src/stream.ts +422 -0
  56. package/src/transport.ts +243 -0
  57. package/src/util.ts +5 -0
@@ -0,0 +1,243 @@
1
+ import { noise as Noise } from '@chainsafe/libp2p-noise'
2
+ import { Connection } from '@libp2p/interface-connection'
3
+ import type { PeerId } from '@libp2p/interface-peer-id'
4
+ import { CreateListenerOptions, Listener, symbol, Transport } from '@libp2p/interface-transport'
5
+ import { logger } from '@libp2p/logger'
6
+ import * as p from '@libp2p/peer-id'
7
+ import { Multiaddr } from '@multiformats/multiaddr'
8
+ import * as multihashes from 'multihashes'
9
+ import defer from 'p-defer'
10
+ import { v4 as genUuid } from 'uuid'
11
+ import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'
12
+ import { concat } from 'uint8arrays/concat'
13
+
14
+ import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument } from './error.js'
15
+ import { WebRTCMultiaddrConnection } from './maconn.js'
16
+ import { DataChannelMuxerFactory } from './muxer.js'
17
+ import { WebRTCDialOptions } from './options.js'
18
+ import * as sdp from './sdp.js'
19
+ import { WebRTCStream } from './stream.js'
20
+
21
+ const log = logger('libp2p:webrtc:transport')
22
+
23
+ /**
24
+ * The time to wait, in milliseconds, for the data channel handshake to complete
25
+ */
26
+ const HANDSHAKE_TIMEOUT_MS = 10000
27
+
28
+ /**
29
+ * Created by converting the hexadecimal protocol code to an integer.
30
+ *
31
+ * {@link https://github.com/multiformats/multiaddr/blob/master/protocols.csv}
32
+ */
33
+ export const WEBRTC_CODE: number = 280
34
+
35
+ /**
36
+ * Created by converting the hexadecimal protocol code to an integer.
37
+ *
38
+ * {@link https://github.com/multiformats/multiaddr/blob/master/protocols.csv}
39
+ */
40
+ export const CERTHASH_CODE: number = 466
41
+
42
+ /**
43
+ * The peer for this transport
44
+ */
45
+ // @TODO(ddimaria): seems like an unnessary abstraction, consider removing
46
+ export interface WebRTCTransportComponents {
47
+ peerId: PeerId
48
+ }
49
+
50
+ export class WebRTCTransport implements Transport {
51
+ /**
52
+ * The peer for this transport
53
+ */
54
+ private readonly components: WebRTCTransportComponents
55
+
56
+ constructor (components: WebRTCTransportComponents) {
57
+ this.components = components
58
+ }
59
+
60
+ /**
61
+ * Dial a given multiaddr
62
+ */
63
+ async dial (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
64
+ const rawConn = await this._connect(ma, options)
65
+ log(`dialing address - ${ma.toString()}`)
66
+ return rawConn
67
+ }
68
+
69
+ /**
70
+ * Create transport listeners no supported by browsers
71
+ */
72
+ createListener (options: CreateListenerOptions): Listener {
73
+ throw unimplemented('WebRTCTransport.createListener')
74
+ }
75
+
76
+ /**
77
+ * Takes a list of `Multiaddr`s and returns only valid addresses for the transport
78
+ */
79
+ filter (multiaddrs: Multiaddr[]): Multiaddr[] {
80
+ return multiaddrs.filter(validMa)
81
+ }
82
+
83
+ /**
84
+ * Implement toString() for WebRTCTransport
85
+ */
86
+ get [Symbol.toStringTag] (): string {
87
+ return '@libp2p/webrtc'
88
+ }
89
+
90
+ /**
91
+ * Symbol.for('@libp2p/transport')
92
+ */
93
+ get [symbol] (): true {
94
+ return true
95
+ }
96
+
97
+ /**
98
+ * Connect to a peer using a multiaddr
99
+ */
100
+ async _connect (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
101
+ const rps = ma.getPeerId()
102
+
103
+ if (rps === null) {
104
+ throw inappropriateMultiaddr("we need to have the remote's PeerId")
105
+ }
106
+
107
+ const remoteCerthash = sdp.decodeCerthash(sdp.certhash(ma))
108
+
109
+ // ECDSA is preferred over RSA here. From our testing we find that P-256 elliptic
110
+ // curve is supported by Pion, webrtc-rs, as well as Chromium (P-228 and P-384
111
+ // was not supported in Chromium). We use the same hash function as found in the
112
+ // multiaddr if it is supported.
113
+ const certificate = await RTCPeerConnection.generateCertificate({
114
+ name: 'ECDSA',
115
+ namedCurve: 'P-256',
116
+ hash: sdp.toSupportedHashFunction(remoteCerthash.name)
117
+ } as any)
118
+ const peerConnection = new RTCPeerConnection({ certificates: [certificate] })
119
+
120
+ // create data channel for running the noise handshake. Once the data channel is opened,
121
+ // the remote will initiate the noise handshake. This is used to confirm the identity of
122
+ // the peer.
123
+ const dataChannelOpenPromise = defer()
124
+ const handshakeDataChannel = peerConnection.createDataChannel('handshake', { negotiated: true, id: 0 })
125
+ const handhsakeTimeout = setTimeout(() => {
126
+ const error = `Data channel was never opened: state: ${handshakeDataChannel.readyState}`
127
+ log.error(error)
128
+ dataChannelOpenPromise.reject(dataChannelError('data', error))
129
+ }, HANDSHAKE_TIMEOUT_MS)
130
+
131
+ handshakeDataChannel.onopen = (_) => {
132
+ clearTimeout(handhsakeTimeout)
133
+ dataChannelOpenPromise.resolve()
134
+ }
135
+
136
+ // ref: https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/error_event
137
+ handshakeDataChannel.onerror = (event: Event) => {
138
+ clearTimeout(handhsakeTimeout)
139
+ const errorTarget = event.target?.toString() ?? 'not specified'
140
+ const error = `Error opening a data channel for handshaking: ${errorTarget}`
141
+ log.error(error)
142
+ dataChannelOpenPromise.reject(dataChannelError('data', error))
143
+ }
144
+
145
+ const ufrag = 'libp2p+webrtc+v1/' + genUuid().replaceAll('-', '')
146
+
147
+ // Create offer and munge sdp with ufrag = pwd. This allows the remote to
148
+ // respond to STUN messages without performing an actual SDP exchange.
149
+ // This is because it can infer the passwd field by reading the USERNAME
150
+ // attribute of the STUN message.
151
+ const offerSdp = await peerConnection.createOffer()
152
+ const mungedOfferSdp = sdp.munge(offerSdp, ufrag)
153
+ await peerConnection.setLocalDescription(mungedOfferSdp)
154
+
155
+ // construct answer sdp from multiaddr and ufrag
156
+ const answerSdp = sdp.fromMultiAddr(ma, ufrag)
157
+ await peerConnection.setRemoteDescription(answerSdp)
158
+
159
+ // wait for peerconnection.onopen to fire, or for the datachannel to open
160
+ await dataChannelOpenPromise.promise
161
+
162
+ const myPeerId = this.components.peerId
163
+ const theirPeerId = p.peerIdFromString(rps)
164
+
165
+ // Do noise handshake.
166
+ // Set the Noise Prologue to libp2p-webrtc-noise:<FINGERPRINTS> before starting the actual Noise handshake.
167
+ // <FINGERPRINTS> is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order.
168
+ const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, remoteCerthash.name, ma)
169
+
170
+ // Since we use the default crypto interface and do not use a static key or early data,
171
+ // we pass in undefined for these parameters.
172
+ const noiseInit = { staticNoiseKey: undefined, extensions: undefined, crypto: undefined, prologueBytes: fingerprintsPrologue }
173
+ const noise = Noise(noiseInit)()
174
+ const wrappedChannel = new WebRTCStream({ channel: handshakeDataChannel, stat: { direction: 'outbound', timeline: { open: 1 } } })
175
+ const wrappedDuplex = {
176
+ ...wrappedChannel,
177
+ source: {
178
+ [Symbol.asyncIterator]: async function * () {
179
+ for await (const list of wrappedChannel.source) {
180
+ yield list.subarray()
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ // Creating the connection before completion of the noise
187
+ // handshake ensures that the stream opening callback is set up
188
+ const maConn = new WebRTCMultiaddrConnection({
189
+ peerConnection,
190
+ remoteAddr: ma,
191
+ timeline: {
192
+ open: (new Date()).getTime()
193
+ }
194
+ })
195
+
196
+ const muxerFactory = new DataChannelMuxerFactory(peerConnection)
197
+
198
+ // For outbound connections, the remote is expected to start the noise handshake.
199
+ // Therefore, we need to secure an inbound noise connection from the remote.
200
+ await noise.secureInbound(myPeerId, wrappedDuplex, theirPeerId)
201
+
202
+ return await options.upgrader.upgradeOutbound(maConn, { skipProtection: true, skipEncryption: true, muxerFactory })
203
+ }
204
+
205
+ /**
206
+ * Generate a noise prologue from the peer connection's certificate.
207
+ * noise prologue = bytes('libp2p-webrtc-noise:') + noise-responder fingerprint + noise-initiator fingerprint
208
+ */
209
+ private generateNoisePrologue (pc: RTCPeerConnection, hashName: multihashes.HashName, ma: Multiaddr): Uint8Array {
210
+ if (pc.getConfiguration().certificates?.length === 0) {
211
+ throw invalidArgument('no local certificate')
212
+ }
213
+
214
+ const localCert = pc.getConfiguration().certificates?.at(0)
215
+
216
+ if (localCert === undefined || localCert.getFingerprints().length === 0) {
217
+ throw invalidArgument('no fingerprint on local certificate')
218
+ }
219
+
220
+ const localFingerprint = localCert.getFingerprints()[0]
221
+
222
+ if (localFingerprint.value === undefined) {
223
+ throw invalidArgument('no fingerprint on local certificate')
224
+ }
225
+
226
+ const localFpString = localFingerprint.value.replace(/:/g, '')
227
+ const localFpArray = uint8arrayFromString(localFpString, 'hex')
228
+ const local = multihashes.encode(localFpArray, multihashes.names[hashName])
229
+ const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma))
230
+ const prefix = uint8arrayFromString('libp2p-webrtc-noise:')
231
+
232
+ return concat([prefix, local, remote])
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Determine if a given multiaddr contains a WebRTC Code (280),
238
+ * a Certhash Code (466) and a PeerId
239
+ */
240
+ function validMa (ma: Multiaddr): boolean {
241
+ const codes = ma.protoCodes()
242
+ return codes.includes(WEBRTC_CODE) && codes.includes(CERTHASH_CODE) && ma.getPeerId() != null
243
+ }
package/src/util.ts ADDED
@@ -0,0 +1,5 @@
1
+ export const nopSource = {
2
+ async * [Symbol.asyncIterator] () {}
3
+ }
4
+
5
+ export const nopSink = async (_: any) => {}