@libp2p/webrtc 3.2.1 → 3.2.2-62a56b54

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 (67) hide show
  1. package/dist/index.min.js +13 -13
  2. package/dist/src/index.d.ts +29 -0
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/maconn.d.ts.map +1 -1
  6. package/dist/src/maconn.js +5 -2
  7. package/dist/src/maconn.js.map +1 -1
  8. package/dist/src/muxer.d.ts +10 -13
  9. package/dist/src/muxer.d.ts.map +1 -1
  10. package/dist/src/muxer.js +44 -29
  11. package/dist/src/muxer.js.map +1 -1
  12. package/dist/src/pb/message.d.ts +2 -1
  13. package/dist/src/pb/message.d.ts.map +1 -1
  14. package/dist/src/pb/message.js +2 -0
  15. package/dist/src/pb/message.js.map +1 -1
  16. package/dist/src/private-to-private/initiate-connection.d.ts +25 -0
  17. package/dist/src/private-to-private/initiate-connection.d.ts.map +1 -0
  18. package/dist/src/private-to-private/initiate-connection.js +145 -0
  19. package/dist/src/private-to-private/initiate-connection.js.map +1 -0
  20. package/dist/src/private-to-private/listener.d.ts +6 -2
  21. package/dist/src/private-to-private/listener.d.ts.map +1 -1
  22. package/dist/src/private-to-private/listener.js +6 -3
  23. package/dist/src/private-to-private/listener.js.map +1 -1
  24. package/dist/src/private-to-private/signaling-stream-handler.d.ts +10 -0
  25. package/dist/src/private-to-private/signaling-stream-handler.d.ts.map +1 -0
  26. package/dist/src/private-to-private/signaling-stream-handler.js +97 -0
  27. package/dist/src/private-to-private/signaling-stream-handler.js.map +1 -0
  28. package/dist/src/private-to-private/transport.d.ts +12 -2
  29. package/dist/src/private-to-private/transport.d.ts.map +1 -1
  30. package/dist/src/private-to-private/transport.js +67 -56
  31. package/dist/src/private-to-private/transport.js.map +1 -1
  32. package/dist/src/private-to-private/util.d.ts +6 -5
  33. package/dist/src/private-to-private/util.d.ts.map +1 -1
  34. package/dist/src/private-to-private/util.js +72 -21
  35. package/dist/src/private-to-private/util.js.map +1 -1
  36. package/dist/src/private-to-public/transport.d.ts +2 -2
  37. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  38. package/dist/src/private-to-public/transport.js +2 -2
  39. package/dist/src/private-to-public/transport.js.map +1 -1
  40. package/dist/src/stream.d.ts +39 -19
  41. package/dist/src/stream.d.ts.map +1 -1
  42. package/dist/src/stream.js +135 -39
  43. package/dist/src/stream.js.map +1 -1
  44. package/dist/src/util.d.ts +6 -0
  45. package/dist/src/util.d.ts.map +1 -1
  46. package/dist/src/util.js +46 -0
  47. package/dist/src/util.js.map +1 -1
  48. package/package.json +17 -11
  49. package/src/index.ts +34 -0
  50. package/src/maconn.ts +7 -2
  51. package/src/muxer.ts +58 -44
  52. package/src/pb/message.proto +6 -1
  53. package/src/pb/message.ts +4 -2
  54. package/src/private-to-private/initiate-connection.ts +191 -0
  55. package/src/private-to-private/listener.ts +12 -4
  56. package/src/private-to-private/signaling-stream-handler.ts +129 -0
  57. package/src/private-to-private/transport.ts +87 -59
  58. package/src/private-to-private/util.ts +89 -24
  59. package/src/private-to-public/transport.ts +4 -4
  60. package/src/stream.ts +163 -61
  61. package/src/util.ts +60 -0
  62. package/dist/src/private-to-private/handler.d.ts +0 -26
  63. package/dist/src/private-to-private/handler.d.ts.map +0 -1
  64. package/dist/src/private-to-private/handler.js +0 -137
  65. package/dist/src/private-to-private/handler.js.map +0 -1
  66. package/dist/typedoc-urls.json +0 -6
  67. package/src/private-to-private/handler.ts +0 -177
@@ -6,26 +6,36 @@ import { multiaddr, type Multiaddr } from '@multiformats/multiaddr'
6
6
  import { WebRTC } from '@multiformats/multiaddr-matcher'
7
7
  import { codes } from '../error.js'
8
8
  import { WebRTCMultiaddrConnection } from '../maconn.js'
9
- import { cleanup } from '../webrtc/index.js'
10
- import { initiateConnection, handleIncomingStream } from './handler.js'
9
+ import { DataChannelMuxerFactory } from '../muxer.js'
10
+ import { cleanup, RTCPeerConnection } from '../webrtc/index.js'
11
+ import { initiateConnection } from './initiate-connection.js'
11
12
  import { WebRTCPeerListener } from './listener.js'
12
- import type { DataChannelOpts } from '../stream.js'
13
+ import { handleIncomingStream } from './signaling-stream-handler.js'
14
+ import type { DataChannelOptions } from '../index.js'
13
15
  import type { Connection } from '@libp2p/interface/connection'
14
16
  import type { PeerId } from '@libp2p/interface/peer-id'
15
17
  import type { CounterGroup, Metrics } from '@libp2p/interface/src/metrics/index.js'
16
18
  import type { Startable } from '@libp2p/interface/startable'
17
19
  import type { IncomingStreamData, Registrar } from '@libp2p/interface-internal/registrar'
20
+ import type { ConnectionManager } from '@libp2p/interface-internal/src/connection-manager/index.js'
18
21
  import type { TransportManager } from '@libp2p/interface-internal/transport-manager'
19
22
 
20
23
  const log = logger('libp2p:webrtc:peer')
21
24
 
22
25
  const WEBRTC_TRANSPORT = '/webrtc'
23
26
  const CIRCUIT_RELAY_TRANSPORT = '/p2p-circuit'
24
- const SIGNALING_PROTO_ID = '/webrtc-signaling/0.0.1'
27
+ export const SIGNALING_PROTO_ID = '/webrtc-signaling/0.0.1'
28
+ const INBOUND_CONNECTION_TIMEOUT = 30 * 1000
25
29
 
26
30
  export interface WebRTCTransportInit {
27
31
  rtcConfiguration?: RTCConfiguration
28
- dataChannel?: Partial<DataChannelOpts>
32
+ dataChannel?: DataChannelOptions
33
+
34
+ /**
35
+ * Inbound connections must complete the upgrade within this many ms
36
+ * (default: 30s)
37
+ */
38
+ inboundConnectionTimeout?: number
29
39
  }
30
40
 
31
41
  export interface WebRTCTransportComponents {
@@ -33,6 +43,7 @@ export interface WebRTCTransportComponents {
33
43
  registrar: Registrar
34
44
  upgrader: Upgrader
35
45
  transportManager: TransportManager
46
+ connectionManager: ConnectionManager
36
47
  metrics?: Metrics
37
48
  }
38
49
 
@@ -44,11 +55,14 @@ export interface WebRTCTransportMetrics {
44
55
  export class WebRTCTransport implements Transport, Startable {
45
56
  private _started = false
46
57
  private readonly metrics?: WebRTCTransportMetrics
58
+ private readonly shutdownController: AbortController
47
59
 
48
60
  constructor (
49
61
  private readonly components: WebRTCTransportComponents,
50
62
  private readonly init: WebRTCTransportInit = {}
51
63
  ) {
64
+ this.shutdownController = new AbortController()
65
+
52
66
  if (components.metrics != null) {
53
67
  this.metrics = {
54
68
  dialerEvents: components.metrics.registerCounterGroup('libp2p_webrtc_dialer_events_total', {
@@ -83,7 +97,9 @@ export class WebRTCTransport implements Transport, Startable {
83
97
  }
84
98
 
85
99
  createListener (options: CreateListenerOptions): Listener {
86
- return new WebRTCPeerListener(this.components)
100
+ return new WebRTCPeerListener(this.components, {
101
+ shutdownController: this.shutdownController
102
+ })
87
103
  }
88
104
 
89
105
  readonly [Symbol.toStringTag] = '@libp2p/webrtc'
@@ -102,84 +118,96 @@ export class WebRTCTransport implements Transport, Startable {
102
118
  * <relay address>/p2p/<relay-peer>/p2p-circuit/webrtc/p2p/<destination-peer>
103
119
  */
104
120
  async dial (ma: Multiaddr, options: DialOptions): Promise<Connection> {
105
- log.trace('dialing address: ', ma)
106
- const { baseAddr, peerId } = splitAddr(ma)
121
+ log.trace('dialing address: %a', ma)
107
122
 
108
- if (options.signal == null) {
109
- const controller = new AbortController()
110
- options.signal = controller.signal
111
- }
123
+ const peerConnection = new RTCPeerConnection(this.init.rtcConfiguration)
124
+ const muxerFactory = new DataChannelMuxerFactory({
125
+ peerConnection,
126
+ dataChannelOptions: this.init.dataChannel
127
+ })
112
128
 
113
- this.metrics?.dialerEvents.increment({ open: true })
114
- const connection = await this.components.transportManager.dial(baseAddr, options)
115
- const signalingStream = await connection.newStream(SIGNALING_PROTO_ID, {
116
- ...options,
117
- runOnTransientConnection: true
129
+ const { remoteAddress } = await initiateConnection({
130
+ peerConnection,
131
+ multiaddr: ma,
132
+ dataChannelOptions: this.init.dataChannel,
133
+ signal: options.signal,
134
+ connectionManager: this.components.connectionManager,
135
+ transportManager: this.components.transportManager
118
136
  })
119
137
 
120
- try {
121
- const { pc, muxerFactory, remoteAddress } = await initiateConnection({
122
- stream: signalingStream,
123
- rtcConfiguration: this.init.rtcConfiguration,
124
- dataChannelOptions: this.init.dataChannel,
125
- signal: options.signal
126
- })
138
+ const webRTCConn = new WebRTCMultiaddrConnection({
139
+ peerConnection,
140
+ timeline: { open: Date.now() },
141
+ remoteAddr: remoteAddress,
142
+ metrics: this.metrics?.dialerEvents
143
+ })
127
144
 
128
- const result = await options.upgrader.upgradeOutbound(
129
- new WebRTCMultiaddrConnection({
130
- peerConnection: pc,
131
- timeline: { open: Date.now() },
132
- remoteAddr: multiaddr(remoteAddress).encapsulate(`/p2p/${peerId.toString()}`),
133
- metrics: this.metrics?.dialerEvents
134
- }),
135
- {
136
- skipProtection: true,
137
- skipEncryption: true,
138
- muxerFactory
139
- }
140
- )
141
-
142
- // close the stream if SDP has been exchanged successfully
143
- await signalingStream.close()
144
- return result
145
- } catch (err: any) {
146
- this.metrics?.dialerEvents.increment({ error: true })
147
- // reset the stream in case of any error
148
- signalingStream.abort(err)
149
- throw err
150
- } finally {
151
- // Close the signaling connection
152
- await connection.close()
153
- }
145
+ const connection = await options.upgrader.upgradeOutbound(webRTCConn, {
146
+ skipProtection: true,
147
+ skipEncryption: true,
148
+ muxerFactory
149
+ })
150
+
151
+ // close the connection on shut down
152
+ this._closeOnShutdown(peerConnection, webRTCConn)
153
+
154
+ return connection
154
155
  }
155
156
 
156
157
  async _onProtocol ({ connection, stream }: IncomingStreamData): Promise<void> {
158
+ const signal = AbortSignal.timeout(this.init.inboundConnectionTimeout ?? INBOUND_CONNECTION_TIMEOUT)
159
+ const peerConnection = new RTCPeerConnection(this.init.rtcConfiguration)
160
+ const muxerFactory = new DataChannelMuxerFactory({ peerConnection, dataChannelOptions: this.init.dataChannel })
161
+
157
162
  try {
158
- const { pc, muxerFactory, remoteAddress } = await handleIncomingStream({
159
- rtcConfiguration: this.init.rtcConfiguration,
163
+ const { remoteAddress } = await handleIncomingStream({
164
+ peerConnection,
160
165
  connection,
161
166
  stream,
162
- dataChannelOptions: this.init.dataChannel
167
+ signal
163
168
  })
164
169
 
165
- await this.components.upgrader.upgradeInbound(new WebRTCMultiaddrConnection({
166
- peerConnection: pc,
170
+ const webRTCConn = new WebRTCMultiaddrConnection({
171
+ peerConnection,
167
172
  timeline: { open: (new Date()).getTime() },
168
173
  remoteAddr: multiaddr(remoteAddress).encapsulate(`/p2p/${connection.remotePeer.toString()}`),
169
174
  metrics: this.metrics?.listenerEvents
170
- }), {
175
+ })
176
+
177
+ // close the connection on shut down
178
+ this._closeOnShutdown(peerConnection, webRTCConn)
179
+
180
+ await this.components.upgrader.upgradeInbound(webRTCConn, {
171
181
  skipEncryption: true,
172
182
  skipProtection: true,
173
183
  muxerFactory
174
184
  })
185
+
186
+ // close the stream if SDP messages have been exchanged successfully
187
+ await stream.close({
188
+ signal
189
+ })
175
190
  } catch (err: any) {
176
191
  stream.abort(err)
177
192
  throw err
178
- } finally {
179
- // Close the signaling connection
180
- await connection.close()
181
193
  }
182
194
  }
195
+
196
+ private _closeOnShutdown (pc: RTCPeerConnection, webRTCConn: WebRTCMultiaddrConnection): void {
197
+ // close the connection on shut down
198
+ const shutDownListener = (): void => {
199
+ webRTCConn.close()
200
+ .catch(err => {
201
+ log.error('could not close WebRTCMultiaddrConnection', err)
202
+ })
203
+ }
204
+
205
+ this.shutdownController.signal.addEventListener('abort', shutDownListener)
206
+
207
+ pc.addEventListener('close', () => {
208
+ this.shutdownController.signal.removeEventListener('abort', shutDownListener)
209
+ })
210
+ }
183
211
  }
184
212
 
185
213
  export function splitAddr (ma: Multiaddr): { baseAddr: Multiaddr, peerId: PeerId } {
@@ -1,49 +1,101 @@
1
+ import { CodeError } from '@libp2p/interface/errors'
1
2
  import { logger } from '@libp2p/logger'
3
+ import { abortableSource } from 'abortable-iterator'
4
+ import { anySignal } from 'any-signal'
5
+ import * as lp from 'it-length-prefixed'
6
+ import { AbortError, raceSignal } from 'race-signal'
2
7
  import { isFirefox } from '../util.js'
3
8
  import { RTCIceCandidate } from '../webrtc/index.js'
4
9
  import { Message } from './pb/message.js'
10
+ import type { Stream } from '@libp2p/interface/connection'
11
+ import type { AbortOptions, MessageStream } from 'it-protobuf-stream'
5
12
  import type { DeferredPromise } from 'p-defer'
6
13
 
7
- interface MessageStream {
8
- read: () => Promise<Message>
9
- write: (d: Message) => void | Promise<void>
14
+ const log = logger('libp2p:webrtc:peer:util')
15
+
16
+ export interface ReadCandidatesOptions extends AbortOptions {
17
+ direction: string
10
18
  }
11
19
 
12
- const log = logger('libp2p:webrtc:peer:util')
20
+ export const readCandidatesUntilConnected = async (connectedPromise: DeferredPromise<void>, pc: RTCPeerConnection, stream: MessageStream<Message, Stream>, options: ReadCandidatesOptions): Promise<void> => {
21
+ // if we connect, stop trying to read from the stream
22
+ const controller = new AbortController()
23
+ connectedPromise.promise.then(() => {
24
+ controller.abort()
25
+ }, () => {
26
+ controller.abort()
27
+ })
28
+
29
+ const signal = anySignal([
30
+ controller.signal,
31
+ options.signal
32
+ ])
33
+
34
+ const source = abortableSource(stream.unwrap().unwrap().source, signal, {
35
+ returnOnAbort: true
36
+ })
37
+
38
+ try {
39
+ // read candidates until we are connected or we reach the end of the stream
40
+ for await (const buf of lp.decode(source)) {
41
+ const message = Message.decode(buf)
13
42
 
14
- export const readCandidatesUntilConnected = async (connectedPromise: DeferredPromise<void>, pc: RTCPeerConnection, stream: MessageStream): Promise<void> => {
15
- while (true) {
16
- const readResult = await Promise.race([connectedPromise.promise, stream.read()])
17
- // check if readResult is a message
18
- if (readResult instanceof Object) {
19
- const message = readResult
20
43
  if (message.type !== Message.Type.ICE_CANDIDATE) {
21
- throw new Error('expected only ice candidates')
44
+ throw new CodeError('ICE candidate message expected', 'ERR_NOT_ICE_CANDIDATE')
22
45
  }
23
- // end of candidates has been signalled
24
- if (message.data == null || message.data === '') {
46
+
47
+ let candidateInit = JSON.parse(message.data ?? 'null')
48
+
49
+ if (candidateInit === '') {
50
+ log.trace('end-of-candidates for this generation received')
51
+ candidateInit = {
52
+ candidate: '',
53
+ sdpMid: '0',
54
+ sdpMLineIndex: 0
55
+ }
56
+ }
57
+
58
+ if (candidateInit === null) {
25
59
  log.trace('end-of-candidates received')
26
- break
60
+ candidateInit = {
61
+ candidate: null,
62
+ sdpMid: '0',
63
+ sdpMLineIndex: 0
64
+ }
27
65
  }
28
66
 
29
- log.trace('received new ICE candidate: %s', message.data)
67
+ // a null candidate means end-of-candidates
68
+ // see - https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent
69
+ const candidate = new RTCIceCandidate(candidateInit)
70
+
71
+ log.trace('%s received new ICE candidate', options.direction, candidate)
72
+
30
73
  try {
31
- await pc.addIceCandidate(new RTCIceCandidate(JSON.parse(message.data)))
74
+ await pc.addIceCandidate(candidate)
32
75
  } catch (err) {
33
- log.error('bad candidate received: ', err)
34
- throw new Error('bad candidate received')
76
+ log.error('%s bad candidate received', options.direction, err)
35
77
  }
36
- } else {
37
- // connected promise resolved
38
- break
39
78
  }
79
+ } catch (err) {
80
+ log.error('%s error parsing ICE candidate', options.direction, err)
81
+ } finally {
82
+ signal.clear()
40
83
  }
41
- await connectedPromise.promise
84
+
85
+ if (options.signal?.aborted === true) {
86
+ throw new AbortError('Aborted while reading ICE candidates', 'ERR_ICE_CANDIDATES_READ_ABORTED')
87
+ }
88
+
89
+ // read all available ICE candidates, wait for connection state change
90
+ await raceSignal(connectedPromise.promise, options.signal, {
91
+ errorMessage: 'Aborted before connected',
92
+ errorCode: 'ERR_ABORTED_BEFORE_CONNECTED'
93
+ })
42
94
  }
43
95
 
44
96
  export function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredPromise<void>): void {
45
97
  pc[isFirefox ? 'oniceconnectionstatechange' : 'onconnectionstatechange'] = (_) => {
46
- log.trace('receiver peerConnectionState state: ', pc.connectionState)
98
+ log.trace('receiver peerConnectionState state change: %s', pc.connectionState)
47
99
  switch (isFirefox ? pc.iceConnectionState : pc.connectionState) {
48
100
  case 'connected':
49
101
  promise.resolve()
@@ -51,10 +103,23 @@ export function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredProm
51
103
  case 'failed':
52
104
  case 'disconnected':
53
105
  case 'closed':
54
- promise.reject(new Error('RTCPeerConnection was closed'))
106
+ promise.reject(new CodeError('RTCPeerConnection was closed', 'ERR_CONNECTION_CLOSED_BEFORE_CONNECTED'))
55
107
  break
56
108
  default:
57
109
  break
58
110
  }
59
111
  }
60
112
  }
113
+
114
+ export function parseRemoteAddress (sdp: string): string {
115
+ // 'a=candidate:1746876089 1 udp 2113937151 0614fbad-b...ocal 54882 typ host generation 0 network-cost 999'
116
+ const candidateLine = sdp.split('\r\n').filter(line => line.startsWith('a=candidate')).pop()
117
+ const candidateParts = candidateLine?.split(' ')
118
+
119
+ if (candidateLine == null || candidateParts == null || candidateParts.length < 5) {
120
+ log('could not parse remote address from', candidateLine)
121
+ return '/webrtc'
122
+ }
123
+
124
+ return `/dnsaddr/${candidateParts[4]}/${candidateParts[2].toLowerCase()}/${candidateParts[5]}/webrtc`
125
+ }
@@ -16,7 +16,7 @@ import { RTCPeerConnection } from '../webrtc/index.js'
16
16
  import * as sdp from './sdp.js'
17
17
  import { genUfrag } from './util.js'
18
18
  import type { WebRTCDialOptions } from './options.js'
19
- import type { DataChannelOpts } from '../stream.js'
19
+ import type { DataChannelOptions } from '../index.js'
20
20
  import type { Connection } from '@libp2p/interface/connection'
21
21
  import type { CounterGroup, Metrics } from '@libp2p/interface/metrics'
22
22
  import type { PeerId } from '@libp2p/interface/peer-id'
@@ -56,7 +56,7 @@ export interface WebRTCMetrics {
56
56
  }
57
57
 
58
58
  export interface WebRTCTransportDirectInit {
59
- dataChannel?: Partial<DataChannelOpts>
59
+ dataChannel?: DataChannelOptions
60
60
  }
61
61
 
62
62
  export class WebRTCDirectTransport implements Transport {
@@ -81,7 +81,7 @@ export class WebRTCDirectTransport implements Transport {
81
81
  */
82
82
  async dial (ma: Multiaddr, options: WebRTCDialOptions): Promise<Connection> {
83
83
  const rawConn = await this._connect(ma, options)
84
- log(`dialing address - ${ma.toString()}`)
84
+ log('dialing address: %a', ma)
85
85
  return rawConn
86
86
  }
87
87
 
@@ -194,7 +194,7 @@ export class WebRTCDirectTransport implements Transport {
194
194
  // we pass in undefined for these parameters.
195
195
  const noise = Noise({ prologueBytes: fingerprintsPrologue })()
196
196
 
197
- const wrappedChannel = createStream({ channel: handshakeDataChannel, direction: 'inbound', dataChannelOptions: this.init.dataChannel })
197
+ const wrappedChannel = createStream({ channel: handshakeDataChannel, direction: 'inbound', ...(this.init.dataChannel ?? {}) })
198
198
  const wrappedDuplex = {
199
199
  ...wrappedChannel,
200
200
  sink: wrappedChannel.sink.bind(wrappedChannel),