@libp2p/identify 1.0.21 → 2.0.0-4ad63bb79

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/index.ts CHANGED
@@ -3,7 +3,16 @@
3
3
  *
4
4
  * Use the `identify` function to add support for the [Identify protocol](https://github.com/libp2p/specs/blob/master/identify/README.md) to libp2p.
5
5
  *
6
- * @example
6
+ * This protocol allows network peers to discover the multiaddrs the current node listens on, and the protocols it supports.
7
+ *
8
+ * A second function, `identifyPush` is also exported to add support for [identify/push](https://github.com/libp2p/specs/blob/master/identify/README.md#identifypush).
9
+ *
10
+ * This protocol will send updates to all connected peers when the multiaddrs or protocols of the current node change.
11
+ *
12
+ * > [!TIP]
13
+ * > For maximum network compatibility you should configure both protocols
14
+ *
15
+ * @example Enabling identify
7
16
  *
8
17
  * ```typescript
9
18
  * import { createLibp2p } from 'libp2p'
@@ -16,19 +25,32 @@
16
25
  * }
17
26
  * })
18
27
  * ```
28
+ *
29
+ * @example Enabling identify push
30
+ *
31
+ * ```typescript
32
+ * import { createLibp2p } from 'libp2p'
33
+ * import { identifyPush } from '@libp2p/identify'
34
+ *
35
+ * const node = await createLibp2p({
36
+ * // ...other options
37
+ * services: {
38
+ * identifyPush: identifyPush()
39
+ * }
40
+ * })
41
+ * ```
19
42
  */
20
43
 
21
- import {
22
- MULTICODEC_IDENTIFY,
23
- MULTICODEC_IDENTIFY_PUSH
24
- } from './consts.js'
44
+ import { IdentifyPush as IdentifyPushClass } from './identify-push.js'
25
45
  import { Identify as IdentifyClass } from './identify.js'
26
46
  import type { AbortOptions, IdentifyResult, Libp2pEvents, ComponentLogger, NodeInfo, TypedEventTarget, PeerId, PeerStore, Connection } from '@libp2p/interface'
27
47
  import type { AddressManager, ConnectionManager, Registrar } from '@libp2p/interface-internal'
28
48
 
29
49
  export interface IdentifyInit {
30
50
  /**
31
- * The prefix to use for the protocol (default: 'ipfs')
51
+ * The prefix to use for the protocol
52
+ *
53
+ * @default 'ipfs'
32
54
  */
33
55
  protocolPrefix?: string
34
56
 
@@ -39,36 +61,73 @@ export interface IdentifyInit {
39
61
 
40
62
  /**
41
63
  * How long we should wait for a remote peer to send their identify response
64
+ *
65
+ * @default 5000
42
66
  */
43
67
  timeout?: number
44
68
 
45
69
  /**
46
- * Identify responses larger than this in bytes will be rejected (default: 8192)
70
+ * Identify responses larger than this in bytes will be rejected
71
+ *
72
+ * @default 8192
47
73
  */
48
- maxIdentifyMessageSize?: number
74
+ maxMessageSize?: number
49
75
 
76
+ /**
77
+ * The maximum number of inbound streams that may be open on a single
78
+ * connection for this protocol
79
+ *
80
+ * @default 1
81
+ */
50
82
  maxInboundStreams?: number
83
+
84
+ /**
85
+ * The maximum number of outbound streams that may be open on a single
86
+ * connection for this protocol
87
+ *
88
+ * @default 1
89
+ */
51
90
  maxOutboundStreams?: number
52
91
 
53
- maxPushIncomingStreams?: number
54
- maxPushOutgoingStreams?: number
92
+ /**
93
+ * The maximum number of observed addresses to send in an Identify message
94
+ */
55
95
  maxObservedAddresses?: number
56
96
 
57
97
  /**
58
- * Whether to automatically dial identify on newly opened connections (default: true)
98
+ * Whether to run on connections with data or duration limits
99
+ *
100
+ * @default true
101
+ */
102
+ runOnTransientConnection?: boolean
103
+
104
+ /**
105
+ * Whether to automatically run identify on newly opened connections
106
+ *
107
+ * @default true
59
108
  */
60
109
  runOnConnectionOpen?: boolean
110
+ }
61
111
 
112
+ export interface IdentifyPushInit extends Omit<IdentifyInit, 'runOnConnectionOpen'> {
62
113
  /**
63
- * Whether to run on connections with data or duration limits (default: true)
114
+ * Whether to automatically dial identify-push on self updates
115
+ *
116
+ * @default true
64
117
  */
65
- runOnTransientConnection?: boolean
118
+ runOnSelfUpdate?: boolean
119
+
120
+ /**
121
+ * Push to this many connections in parallel
122
+ *
123
+ * @default 32
124
+ */
125
+ concurrency?: number
66
126
  }
67
127
 
68
128
  export interface IdentifyComponents {
69
129
  peerId: PeerId
70
130
  peerStore: PeerStore
71
- connectionManager: ConnectionManager
72
131
  registrar: Registrar
73
132
  addressManager: AddressManager
74
133
  events: TypedEventTarget<Libp2pEvents>
@@ -76,12 +135,8 @@ export interface IdentifyComponents {
76
135
  nodeInfo: NodeInfo
77
136
  }
78
137
 
79
- /**
80
- * The protocols the Identify service supports
81
- */
82
- export const multicodecs = {
83
- IDENTIFY: MULTICODEC_IDENTIFY,
84
- IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
138
+ export interface IdentifyPushComponents extends IdentifyComponents {
139
+ connectionManager: ConnectionManager
85
140
  }
86
141
 
87
142
  export interface Identify {
@@ -93,10 +148,16 @@ export interface Identify {
93
148
  * you may be better off configuring a topology to be notified instead.
94
149
  */
95
150
  identify(connection: Connection, options?: AbortOptions): Promise<IdentifyResult>
151
+ }
96
152
 
153
+ export interface IdentifyPush {
97
154
  push(): Promise<void>
98
155
  }
99
156
 
100
157
  export function identify (init: IdentifyInit = {}): (components: IdentifyComponents) => Identify {
101
158
  return (components) => new IdentifyClass(components, init)
102
159
  }
160
+
161
+ export function identifyPush (init: IdentifyPushInit = {}): (components: IdentifyPushComponents) => IdentifyPush {
162
+ return (components) => new IdentifyPushClass(components, init)
163
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,273 @@
1
+ import { CodeError } from '@libp2p/interface'
2
+ import { peerIdFromKeys } from '@libp2p/peer-id'
3
+ import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
4
+ import { type Multiaddr, multiaddr } from '@multiformats/multiaddr'
5
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
6
+ import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe'
7
+ import { IDENTIFY_PROTOCOL_VERSION, MAX_IDENTIFY_MESSAGE_SIZE, MAX_PUSH_CONCURRENCY } from './consts.js'
8
+ import type { IdentifyComponents, IdentifyInit } from './index.js'
9
+ import type { Identify as IdentifyMessage } from './pb/message.js'
10
+ import type { Libp2pEvents, IdentifyResult, SignedPeerRecord, Logger, Connection, TypedEventTarget, Peer, PeerData, PeerStore, NodeInfo, Startable, PeerId, IncomingStreamData } from '@libp2p/interface'
11
+ import type { AddressManager, Registrar } from '@libp2p/interface-internal'
12
+
13
+ export const defaultValues = {
14
+ protocolPrefix: 'ipfs',
15
+ timeout: 5000,
16
+ maxInboundStreams: 1,
17
+ maxOutboundStreams: 1,
18
+ maxObservedAddresses: 10,
19
+ maxMessageSize: MAX_IDENTIFY_MESSAGE_SIZE,
20
+ runOnConnectionOpen: true,
21
+ runOnSelfUpdate: true,
22
+ runOnTransientConnection: true,
23
+ concurrency: MAX_PUSH_CONCURRENCY
24
+ }
25
+
26
+ /**
27
+ * Takes the `addr` and converts it to a Multiaddr if possible
28
+ */
29
+ export function getCleanMultiaddr (addr: Uint8Array | string | null | undefined): Multiaddr | undefined {
30
+ if (addr != null && addr.length > 0) {
31
+ try {
32
+ return multiaddr(addr)
33
+ } catch {
34
+
35
+ }
36
+ }
37
+ }
38
+
39
+ export function getAgentVersion (nodeInfo: NodeInfo, agentVersion?: string): string {
40
+ if (agentVersion != null) {
41
+ return agentVersion
42
+ }
43
+
44
+ agentVersion = `${nodeInfo.name}/${nodeInfo.version}`
45
+ // Append user agent version to default AGENT_VERSION depending on the environment
46
+ if (isNode || isElectronMain) {
47
+ agentVersion += ` UserAgent=${globalThis.process.version}`
48
+ } else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) {
49
+ agentVersion += ` UserAgent=${globalThis.navigator.userAgent}`
50
+ }
51
+
52
+ return agentVersion
53
+ }
54
+
55
+ export async function consumeIdentifyMessage (peerStore: PeerStore, events: TypedEventTarget<Libp2pEvents>, log: Logger, connection: Connection, message: IdentifyMessage): Promise<IdentifyResult> {
56
+ log('received identify from %p', connection.remotePeer)
57
+
58
+ if (message == null) {
59
+ throw new CodeError('message was null or undefined', 'ERR_INVALID_MESSAGE')
60
+ }
61
+
62
+ const peer: PeerData = {}
63
+
64
+ if (message.listenAddrs.length > 0) {
65
+ peer.addresses = message.listenAddrs.map(buf => ({
66
+ isCertified: false,
67
+ multiaddr: multiaddr(buf)
68
+ }))
69
+ }
70
+
71
+ if (message.protocols.length > 0) {
72
+ peer.protocols = message.protocols
73
+ }
74
+
75
+ if (message.publicKey != null) {
76
+ peer.publicKey = message.publicKey
77
+
78
+ const peerId = await peerIdFromKeys(message.publicKey)
79
+
80
+ if (!peerId.equals(connection.remotePeer)) {
81
+ throw new CodeError('public key did not match remote PeerId', 'ERR_INVALID_PUBLIC_KEY')
82
+ }
83
+ }
84
+
85
+ let output: SignedPeerRecord | undefined
86
+
87
+ // if the peer record has been sent, prefer the addresses in the record as they are signed by the remote peer
88
+ if (message.signedPeerRecord != null) {
89
+ log('received signedPeerRecord from %p', connection.remotePeer)
90
+
91
+ let peerRecordEnvelope = message.signedPeerRecord
92
+ const envelope = await RecordEnvelope.openAndCertify(peerRecordEnvelope, PeerRecord.DOMAIN)
93
+ let peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
94
+
95
+ // Verify peerId
96
+ if (!peerRecord.peerId.equals(envelope.peerId)) {
97
+ throw new CodeError('signing key does not match PeerId in the PeerRecord', 'ERR_INVALID_SIGNING_KEY')
98
+ }
99
+
100
+ // Make sure remote peer is the one sending the record
101
+ if (!connection.remotePeer.equals(peerRecord.peerId)) {
102
+ throw new CodeError('signing key does not match remote PeerId', 'ERR_INVALID_PEER_RECORD_KEY')
103
+ }
104
+
105
+ let existingPeer: Peer | undefined
106
+
107
+ try {
108
+ existingPeer = await peerStore.get(peerRecord.peerId)
109
+ } catch (err: any) {
110
+ if (err.code !== 'ERR_NOT_FOUND') {
111
+ throw err
112
+ }
113
+ }
114
+
115
+ if (existingPeer != null) {
116
+ // don't lose any existing metadata
117
+ peer.metadata = existingPeer.metadata
118
+
119
+ // if we have previously received a signed record for this peer, compare it to the incoming one
120
+ if (existingPeer.peerRecordEnvelope != null) {
121
+ const storedEnvelope = await RecordEnvelope.createFromProtobuf(existingPeer.peerRecordEnvelope)
122
+ const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload)
123
+
124
+ // ensure seq is greater than, or equal to, the last received
125
+ if (storedRecord.seqNumber >= peerRecord.seqNumber) {
126
+ log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber)
127
+ peerRecord = storedRecord
128
+ peerRecordEnvelope = existingPeer.peerRecordEnvelope
129
+ }
130
+ }
131
+ }
132
+
133
+ // store the signed record for next time
134
+ peer.peerRecordEnvelope = peerRecordEnvelope
135
+
136
+ // override the stored addresses with the signed multiaddrs
137
+ peer.addresses = peerRecord.multiaddrs.map(multiaddr => ({
138
+ isCertified: true,
139
+ multiaddr
140
+ }))
141
+
142
+ output = {
143
+ seq: peerRecord.seqNumber,
144
+ addresses: peerRecord.multiaddrs
145
+ }
146
+ } else {
147
+ log('%p did not send a signed peer record', connection.remotePeer)
148
+ }
149
+
150
+ log('patching %p with', connection.remotePeer, peer)
151
+ await peerStore.patch(connection.remotePeer, peer)
152
+
153
+ if (message.agentVersion != null || message.protocolVersion != null) {
154
+ const metadata: Record<string, Uint8Array> = {}
155
+
156
+ if (message.agentVersion != null) {
157
+ metadata.AgentVersion = uint8ArrayFromString(message.agentVersion)
158
+ }
159
+
160
+ if (message.protocolVersion != null) {
161
+ metadata.ProtocolVersion = uint8ArrayFromString(message.protocolVersion)
162
+ }
163
+
164
+ log('merging %p metadata', connection.remotePeer, metadata)
165
+ await peerStore.merge(connection.remotePeer, {
166
+ metadata
167
+ })
168
+ }
169
+
170
+ const result: IdentifyResult = {
171
+ peerId: connection.remotePeer,
172
+ protocolVersion: message.protocolVersion,
173
+ agentVersion: message.agentVersion,
174
+ publicKey: message.publicKey,
175
+ listenAddrs: message.listenAddrs.map(buf => multiaddr(buf)),
176
+ observedAddr: message.observedAddr == null ? undefined : multiaddr(message.observedAddr),
177
+ protocols: message.protocols,
178
+ signedPeerRecord: output,
179
+ connection
180
+ }
181
+
182
+ events.safeDispatchEvent('peer:identify', { detail: result })
183
+
184
+ return result
185
+ }
186
+
187
+ export interface AbstractIdentifyInit extends IdentifyInit {
188
+ protocol: string
189
+ log: Logger
190
+ }
191
+
192
+ export abstract class AbstractIdentify implements Startable {
193
+ public readonly host: {
194
+ protocolVersion: string
195
+ agentVersion: string
196
+ }
197
+
198
+ protected protocol: string
199
+ protected started: boolean
200
+ protected readonly timeout: number
201
+ protected readonly peerId: PeerId
202
+ protected readonly peerStore: PeerStore
203
+ protected readonly registrar: Registrar
204
+ protected readonly addressManager: AddressManager
205
+ private readonly maxInboundStreams: number
206
+ private readonly maxOutboundStreams: number
207
+ protected readonly maxMessageSize: number
208
+ protected readonly maxObservedAddresses: number
209
+ protected readonly events: TypedEventTarget<Libp2pEvents>
210
+ protected readonly runOnTransientConnection: boolean
211
+ protected readonly log: Logger
212
+
213
+ constructor (components: IdentifyComponents, init: AbstractIdentifyInit) {
214
+ this.protocol = init.protocol
215
+ this.started = false
216
+ this.peerId = components.peerId
217
+ this.peerStore = components.peerStore
218
+ this.registrar = components.registrar
219
+ this.addressManager = components.addressManager
220
+ this.events = components.events
221
+ this.log = init.log
222
+
223
+ this.timeout = init.timeout ?? defaultValues.timeout
224
+ this.maxInboundStreams = init.maxInboundStreams ?? defaultValues.maxInboundStreams
225
+ this.maxOutboundStreams = init.maxOutboundStreams ?? defaultValues.maxOutboundStreams
226
+ this.maxMessageSize = init.maxMessageSize ?? defaultValues.maxMessageSize
227
+ this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses
228
+ this.runOnTransientConnection = init.runOnTransientConnection ?? defaultValues.runOnTransientConnection
229
+
230
+ // Store self host metadata
231
+ this.host = {
232
+ protocolVersion: `${init.protocolPrefix ?? defaultValues.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`,
233
+ agentVersion: getAgentVersion(components.nodeInfo, init.agentVersion)
234
+ }
235
+ }
236
+
237
+ isStarted (): boolean {
238
+ return this.started
239
+ }
240
+
241
+ async start (): Promise<void> {
242
+ if (this.started) {
243
+ return
244
+ }
245
+
246
+ await this.peerStore.merge(this.peerId, {
247
+ metadata: {
248
+ AgentVersion: uint8ArrayFromString(this.host.agentVersion),
249
+ ProtocolVersion: uint8ArrayFromString(this.host.protocolVersion)
250
+ }
251
+ })
252
+
253
+ await this.registrar.handle(this.protocol, (data) => {
254
+ void this.handleProtocol(data).catch(err => {
255
+ this.log.error(err)
256
+ })
257
+ }, {
258
+ maxInboundStreams: this.maxInboundStreams,
259
+ maxOutboundStreams: this.maxOutboundStreams,
260
+ runOnTransientConnection: this.runOnTransientConnection
261
+ })
262
+
263
+ this.started = true
264
+ }
265
+
266
+ async stop (): Promise<void> {
267
+ await this.registrar.unhandle(this.protocol)
268
+
269
+ this.started = false
270
+ }
271
+
272
+ protected abstract handleProtocol (data: IncomingStreamData): Promise<void>
273
+ }
@@ -1,12 +0,0 @@
1
- {
2
- "Identify": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_identify.Identify.html",
3
- ".:Identify": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_identify.Identify.html",
4
- "IdentifyComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_identify.IdentifyComponents.html",
5
- ".:IdentifyComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_identify.IdentifyComponents.html",
6
- "IdentifyInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_identify.IdentifyInit.html",
7
- ".:IdentifyInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_identify.IdentifyInit.html",
8
- "multicodecs": "https://libp2p.github.io/js-libp2p/variables/_libp2p_identify.multicodecs.html",
9
- ".:multicodecs": "https://libp2p.github.io/js-libp2p/variables/_libp2p_identify.multicodecs.html",
10
- "identify": "https://libp2p.github.io/js-libp2p/functions/_libp2p_identify.identify-1.html",
11
- ".:identify": "https://libp2p.github.io/js-libp2p/functions/_libp2p_identify.identify-1.html"
12
- }