@libp2p/webrtc 1.2.0 → 2.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.
package/src/maconn.ts CHANGED
@@ -64,6 +64,12 @@ export class WebRTCMultiaddrConnection implements MultiaddrConnection {
64
64
  this.remoteAddr = init.remoteAddr
65
65
  this.timeline = init.timeline
66
66
  this.peerConnection = init.peerConnection
67
+
68
+ this.peerConnection.onconnectionstatechange = () => {
69
+ if (this.peerConnection.connectionState === 'closed' || this.peerConnection.connectionState === 'disconnected' || this.peerConnection.connectionState === 'failed') {
70
+ this.timeline.close = Date.now()
71
+ }
72
+ }
67
73
  }
68
74
 
69
75
  async close (err?: Error | undefined): Promise<void> {
@@ -15,7 +15,7 @@ const log = logger('libp2p:webrtc:peer')
15
15
 
16
16
  export type IncomingStreamOpts = { rtcConfiguration?: RTCConfiguration } & IncomingStreamData
17
17
 
18
- export async function handleIncomingStream ({ rtcConfiguration, stream: rawStream }: IncomingStreamOpts): Promise<[RTCPeerConnection, StreamMuxerFactory]> {
18
+ export async function handleIncomingStream ({ rtcConfiguration, stream: rawStream }: IncomingStreamOpts): Promise<{ pc: RTCPeerConnection, muxerFactory: StreamMuxerFactory, remoteAddress: string }> {
19
19
  const signal = AbortSignal.timeout(DEFAULT_TIMEOUT)
20
20
  const stream = pbStream(abortableDuplex(rawStream, signal)).pb(pb.Message)
21
21
  const pc = new RTCPeerConnection(rtcConfiguration)
@@ -76,7 +76,10 @@ export async function handleIncomingStream ({ rtcConfiguration, stream: rawStrea
76
76
 
77
77
  // wait until candidates are connected
78
78
  await readCandidatesUntilConnected(connectedPromise, pc, stream)
79
- return [pc, muxerFactory]
79
+
80
+ const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '')
81
+
82
+ return { pc, muxerFactory, remoteAddress }
80
83
  }
81
84
 
82
85
  export interface ConnectOptions {
@@ -85,9 +88,8 @@ export interface ConnectOptions {
85
88
  rtcConfiguration?: RTCConfiguration
86
89
  }
87
90
 
88
- export async function initiateConnection ({ rtcConfiguration, signal, stream: rawStream }: ConnectOptions): Promise<[RTCPeerConnection, StreamMuxerFactory]> {
91
+ export async function initiateConnection ({ rtcConfiguration, signal, stream: rawStream }: ConnectOptions): Promise<{ pc: RTCPeerConnection, muxerFactory: StreamMuxerFactory, remoteAddress: string }> {
89
92
  const stream = pbStream(abortableDuplex(rawStream, signal)).pb(pb.Message)
90
-
91
93
  // setup peer connection
92
94
  const pc = new RTCPeerConnection(rtcConfiguration)
93
95
  const muxerFactory = new DataChannelMuxerFactory(pc)
@@ -133,5 +135,21 @@ export async function initiateConnection ({ rtcConfiguration, signal, stream: ra
133
135
 
134
136
  await readCandidatesUntilConnected(connectedPromise, pc, stream)
135
137
  channel.close()
136
- return [pc, muxerFactory]
138
+
139
+ const remoteAddress = parseRemoteAddress(pc.currentRemoteDescription?.sdp ?? '')
140
+
141
+ return { pc, muxerFactory, remoteAddress }
142
+ }
143
+
144
+ function parseRemoteAddress (sdp: string): string {
145
+ // 'a=candidate:1746876089 1 udp 2113937151 0614fbad-b...ocal 54882 typ host generation 0 network-cost 999'
146
+ const candidateLine = sdp.split('\r\n').filter(line => line.startsWith('a=candidate')).pop()
147
+ const candidateParts = candidateLine?.split(' ')
148
+
149
+ if (candidateLine == null || candidateParts == null || candidateParts.length < 5) {
150
+ log('could not parse remote address from', candidateLine)
151
+ return '/webrtc'
152
+ }
153
+
154
+ return `/dnsaddr/${candidateParts[4]}/${candidateParts[2]}/${candidateParts[3]}/webrtc`
137
155
  }
@@ -1,44 +1,55 @@
1
1
  import { EventEmitter } from '@libp2p/interfaces/events'
2
- import { multiaddr, type Multiaddr } from '@multiformats/multiaddr'
3
- import { inappropriateMultiaddr } from '../error.js'
4
- import { TRANSPORT } from './transport.js'
2
+ import type { Libp2pEvents } from '@libp2p/interface-libp2p'
5
3
  import type { PeerId } from '@libp2p/interface-peer-id'
6
- import type { ListenerEvents, TransportManager, Upgrader, Listener } from '@libp2p/interface-transport'
4
+ import type { ListenerEvents, Listener } from '@libp2p/interface-transport'
5
+ import type { Multiaddr } from '@multiformats/multiaddr'
7
6
 
8
7
  export interface ListenerOptions {
9
8
  peerId: PeerId
10
- upgrader: Upgrader
11
- transportManager: TransportManager
9
+ events: EventEmitter<Libp2pEvents>
12
10
  }
13
11
 
14
12
  export class WebRTCPeerListener extends EventEmitter<ListenerEvents> implements Listener {
15
- constructor (
16
- private readonly opts: ListenerOptions
17
- ) {
13
+ private readonly peerId: PeerId
14
+ private listeners: Listener[] = []
15
+
16
+ constructor (opts: ListenerOptions) {
18
17
  super()
19
- }
20
18
 
21
- private getBaseAddress (ma: Multiaddr): Multiaddr {
22
- const addrs = ma.toString().split(TRANSPORT)
23
- if (addrs.length < 2) {
24
- throw inappropriateMultiaddr('base address not found')
25
- }
26
- return multiaddr(addrs[0])
19
+ this.peerId = opts.peerId
20
+
21
+ opts.events.addEventListener('transport:listening', (event) => {
22
+ const listener = event.detail
23
+
24
+ if (listener === this || this.listeners.includes(listener)) {
25
+ return
26
+ }
27
+
28
+ this.listeners.push(listener)
29
+ })
30
+
31
+ opts.events.addEventListener('transport:close', (event) => {
32
+ const listener = event.detail
33
+
34
+ this.listeners = this.listeners.filter(l => l !== listener)
35
+ })
27
36
  }
28
37
 
29
- private listeningAddrs: Multiaddr[] = []
30
38
  async listen (ma: Multiaddr): Promise<void> {
31
- const baseAddr = this.getBaseAddress(ma)
32
- const tpt = this.opts.transportManager.transportForMultiaddr(baseAddr)
33
- const listener = tpt?.createListener({ ...this.opts })
34
- await listener?.listen(baseAddr)
35
- const listeningAddr = ma.encapsulate(`/p2p/${this.opts.peerId.toString()}`)
36
- this.listeningAddrs.push(listeningAddr)
37
- listener?.addEventListener('close', () => {
38
- this.listeningAddrs = this.listeningAddrs.filter(a => a !== listeningAddr)
39
- })
39
+ this.safeDispatchEvent('listening', {})
40
40
  }
41
41
 
42
- getAddrs (): Multiaddr[] { return this.listeningAddrs }
43
- async close (): Promise<void> { }
42
+ getAddrs (): Multiaddr[] {
43
+ return this.listeners
44
+ .map(l => l.getAddrs()
45
+ .map(ma => {
46
+ return ma.encapsulate(`/webrtc/p2p/${this.peerId}`)
47
+ })
48
+ )
49
+ .flat()
50
+ }
51
+
52
+ async close (): Promise<void> {
53
+ this.safeDispatchEvent('close', {})
54
+ }
44
55
  }
@@ -1,4 +1,4 @@
1
- import { type CreateListenerOptions, type DialOptions, type Listener, symbol, type Transport, type TransportManager, type Upgrader } from '@libp2p/interface-transport'
1
+ import { type CreateListenerOptions, type DialOptions, type Listener, symbol, type Transport, type Upgrader, type TransportManager } from '@libp2p/interface-transport'
2
2
  import { CodeError } from '@libp2p/interfaces/errors'
3
3
  import { logger } from '@libp2p/logger'
4
4
  import { peerIdFromString } from '@libp2p/peer-id'
@@ -8,16 +8,18 @@ import { WebRTCMultiaddrConnection } from '../maconn.js'
8
8
  import { initiateConnection, handleIncomingStream } from './handler.js'
9
9
  import { WebRTCPeerListener } from './listener.js'
10
10
  import type { Connection } from '@libp2p/interface-connection'
11
+ import type { Libp2pEvents } from '@libp2p/interface-libp2p'
11
12
  import type { PeerId } from '@libp2p/interface-peer-id'
12
- import type { PeerStore } from '@libp2p/interface-peer-store'
13
13
  import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar'
14
+ import type { EventEmitter } from '@libp2p/interfaces/events'
14
15
  import type { Startable } from '@libp2p/interfaces/startable'
15
16
 
16
17
  const log = logger('libp2p:webrtc:peer')
17
18
 
18
- export const TRANSPORT = '/webrtc'
19
- export const SIGNALING_PROTO_ID = '/webrtc-signaling/0.0.1'
20
- export const CODE = protocols('webrtc').code
19
+ const WEBRTC_TRANSPORT = '/webrtc'
20
+ const CIRCUIT_RELAY_TRANSPORT = '/p2p-circuit'
21
+ const SIGNALING_PROTO_ID = '/webrtc-signaling/0.0.1'
22
+ const WEBRTC_CODE = protocols('webrtc').code
21
23
 
22
24
  export interface WebRTCTransportInit {
23
25
  rtcConfiguration?: RTCConfiguration
@@ -28,7 +30,7 @@ export interface WebRTCTransportComponents {
28
30
  registrar: Registrar
29
31
  upgrader: Upgrader
30
32
  transportManager: TransportManager
31
- peerStore: PeerStore
33
+ events: EventEmitter<Libp2pEvents>
32
34
  }
33
35
 
34
36
  export class WebRTCTransport implements Transport, Startable {
@@ -67,22 +69,27 @@ export class WebRTCTransport implements Transport, Startable {
67
69
  filter (multiaddrs: Multiaddr[]): Multiaddr[] {
68
70
  return multiaddrs.filter((ma) => {
69
71
  const codes = ma.protoCodes()
70
- return codes.includes(CODE)
72
+ return codes.includes(WEBRTC_CODE)
71
73
  })
72
74
  }
73
75
 
74
76
  private splitAddr (ma: Multiaddr): { baseAddr: Multiaddr, peerId: PeerId } {
75
- const addrs = ma.toString().split(`${TRANSPORT}/`)
77
+ const addrs = ma.toString().split(WEBRTC_TRANSPORT)
76
78
  if (addrs.length !== 2) {
77
- throw new CodeError('invalid multiaddr', codes.ERR_INVALID_MULTIADDR)
79
+ throw new CodeError('webrtc protocol was not present in multiaddr', codes.ERR_INVALID_MULTIADDR)
80
+ }
81
+
82
+ if (!addrs[0].includes(CIRCUIT_RELAY_TRANSPORT)) {
83
+ throw new CodeError('p2p-circuit protocol was not present in multiaddr', codes.ERR_INVALID_MULTIADDR)
78
84
  }
85
+
79
86
  // look for remote peerId
80
87
  let remoteAddr = multiaddr(addrs[0])
81
- const destination = multiaddr('/' + addrs[1])
88
+ const destination = multiaddr(addrs[1])
82
89
 
83
90
  const destinationIdString = destination.getPeerId()
84
91
  if (destinationIdString == null) {
85
- throw new CodeError('bad destination', codes.ERR_INVALID_MULTIADDR)
92
+ throw new CodeError('destination peer id was missing', codes.ERR_INVALID_MULTIADDR)
86
93
  }
87
94
 
88
95
  const lastProtoInRemote = remoteAddr.protos().pop()
@@ -112,22 +119,21 @@ export class WebRTCTransport implements Transport, Startable {
112
119
  options.signal = controller.signal
113
120
  }
114
121
 
115
- const connection = await this.components.transportManager.dial(baseAddr)
116
-
117
- const rawStream = await connection.newStream([SIGNALING_PROTO_ID], options)
122
+ const connection = await this.components.transportManager.dial(baseAddr, options)
123
+ const signalingStream = await connection.newStream([SIGNALING_PROTO_ID], options)
118
124
 
119
125
  try {
120
- const [pc, muxerFactory] = await initiateConnection({
121
- stream: rawStream,
126
+ const { pc, muxerFactory, remoteAddress } = await initiateConnection({
127
+ stream: signalingStream,
122
128
  rtcConfiguration: this.init.rtcConfiguration,
123
129
  signal: options.signal
124
130
  })
125
- const webrtcMultiaddr = baseAddr.encapsulate(`${TRANSPORT}/p2p/${peerId.toString()}`)
131
+
126
132
  const result = await options.upgrader.upgradeOutbound(
127
133
  new WebRTCMultiaddrConnection({
128
134
  peerConnection: pc,
129
135
  timeline: { open: Date.now() },
130
- remoteAddr: webrtcMultiaddr
136
+ remoteAddr: multiaddr(remoteAddress).encapsulate(`/p2p/${peerId.toString()}`)
131
137
  }),
132
138
  {
133
139
  skipProtection: true,
@@ -137,28 +143,27 @@ export class WebRTCTransport implements Transport, Startable {
137
143
  )
138
144
 
139
145
  // close the stream if SDP has been exchanged successfully
140
- rawStream.close()
146
+ signalingStream.close()
141
147
  return result
142
148
  } catch (err) {
143
149
  // reset the stream in case of any error
144
- rawStream.reset()
150
+ signalingStream.reset()
145
151
  throw err
146
152
  }
147
153
  }
148
154
 
149
155
  async _onProtocol ({ connection, stream }: IncomingStreamData): Promise<void> {
150
156
  try {
151
- const [pc, muxerFactory] = await handleIncomingStream({
157
+ const { pc, muxerFactory, remoteAddress } = await handleIncomingStream({
152
158
  rtcConfiguration: this.init.rtcConfiguration,
153
159
  connection,
154
160
  stream
155
161
  })
156
- const remotePeerId = connection.remoteAddr.getPeerId()
157
- const webrtcMultiaddr = connection.remoteAddr.encapsulate(`${TRANSPORT}/p2p/${remotePeerId}`)
162
+
158
163
  await this.components.upgrader.upgradeInbound(new WebRTCMultiaddrConnection({
159
164
  peerConnection: pc,
160
165
  timeline: { open: (new Date()).getTime() },
161
- remoteAddr: webrtcMultiaddr
166
+ remoteAddr: multiaddr(remoteAddress).encapsulate(`/p2p/${connection.remotePeer.toString()}`)
162
167
  }), {
163
168
  skipEncryption: true,
164
169
  skipProtection: true,
@@ -53,7 +53,7 @@ export function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredProm
53
53
  case 'failed':
54
54
  case 'disconnected':
55
55
  case 'closed':
56
- promise.reject()
56
+ promise.reject(new Error('RTCPeerConnection was closed'))
57
57
  break
58
58
  default:
59
59
  break
package/src/stream.ts CHANGED
@@ -268,7 +268,7 @@ export class WebRTCStream implements Stream {
268
268
  // surface data from the `Message.message` field through a source.
269
269
  this._src = pipe(
270
270
  this._innersrc,
271
- lengthPrefixed.decode(),
271
+ (source) => lengthPrefixed.decode(source),
272
272
  (source) => (async function * () {
273
273
  for await (const buf of source) {
274
274
  const message = self.processIncomingProtobuf(buf.subarray())
package/src/transport.ts CHANGED
@@ -189,15 +189,13 @@ export class WebRTCDirectTransport implements Transport {
189
189
  const wrappedDuplex = {
190
190
  ...wrappedChannel,
191
191
  sink: wrappedChannel.sink.bind(wrappedChannel),
192
- source: {
193
- [Symbol.asyncIterator]: async function * () {
194
- for await (const list of wrappedChannel.source) {
195
- for (const buf of list) {
196
- yield buf
197
- }
192
+ source: (async function * () {
193
+ for await (const list of wrappedChannel.source) {
194
+ for (const buf of list) {
195
+ yield buf
198
196
  }
199
197
  }
200
- }
198
+ }())
201
199
  }
202
200
 
203
201
  // Creating the connection before completion of the noise