@libp2p/identify 0.0.0-05b52d69c

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.
@@ -0,0 +1,559 @@
1
+ /* eslint-disable complexity */
2
+
3
+ import { CodeError, ERR_NOT_FOUND } from '@libp2p/interface/errors'
4
+ import { setMaxListeners } from '@libp2p/interface/events'
5
+ import { peerIdFromKeys } from '@libp2p/peer-id'
6
+ import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
7
+ import { type Multiaddr, multiaddr, protocols } from '@multiformats/multiaddr'
8
+ import { IP_OR_DOMAIN } from '@multiformats/multiaddr-matcher'
9
+ import { pbStream } from 'it-protobuf-stream'
10
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
11
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
12
+ import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe'
13
+ import {
14
+ IDENTIFY_PROTOCOL_VERSION,
15
+ MULTICODEC_IDENTIFY_PROTOCOL_NAME,
16
+ MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME,
17
+ MULTICODEC_IDENTIFY_PROTOCOL_VERSION,
18
+ MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION
19
+ } from './consts.js'
20
+ import { Identify as IdentifyMessage } from './pb/message.js'
21
+ import type { Identify as IdentifyInterface, IdentifyComponents, IdentifyInit } from './index.js'
22
+ import type { Libp2pEvents, IdentifyResult, SignedPeerRecord, AbortOptions, Logger } from '@libp2p/interface'
23
+ import type { Connection, Stream } from '@libp2p/interface/connection'
24
+ import type { TypedEventTarget } from '@libp2p/interface/events'
25
+ import type { PeerId } from '@libp2p/interface/peer-id'
26
+ import type { Peer, PeerData, PeerStore } from '@libp2p/interface/peer-store'
27
+ import type { Startable } from '@libp2p/interface/startable'
28
+ import type { AddressManager } from '@libp2p/interface-internal/address-manager'
29
+ import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
30
+ import type { IncomingStreamData, Registrar } from '@libp2p/interface-internal/registrar'
31
+
32
+ // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52
33
+ const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8
34
+
35
+ const defaultValues = {
36
+ protocolPrefix: 'ipfs',
37
+ // https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48
38
+ timeout: 60000,
39
+ maxInboundStreams: 1,
40
+ maxOutboundStreams: 1,
41
+ maxPushIncomingStreams: 1,
42
+ maxPushOutgoingStreams: 1,
43
+ maxObservedAddresses: 10,
44
+ maxIdentifyMessageSize: 8192,
45
+ runOnConnectionOpen: true,
46
+ runOnTransientConnection: true
47
+ }
48
+
49
+ export class Identify implements Startable, IdentifyInterface {
50
+ private readonly identifyProtocolStr: string
51
+ private readonly identifyPushProtocolStr: string
52
+ public readonly host: {
53
+ protocolVersion: string
54
+ agentVersion: string
55
+ }
56
+
57
+ private started: boolean
58
+ private readonly timeout: number
59
+ private readonly peerId: PeerId
60
+ private readonly peerStore: PeerStore
61
+ private readonly registrar: Registrar
62
+ private readonly connectionManager: ConnectionManager
63
+ private readonly addressManager: AddressManager
64
+ private readonly maxInboundStreams: number
65
+ private readonly maxOutboundStreams: number
66
+ private readonly maxPushIncomingStreams: number
67
+ private readonly maxPushOutgoingStreams: number
68
+ private readonly maxIdentifyMessageSize: number
69
+ private readonly maxObservedAddresses: number
70
+ private readonly events: TypedEventTarget<Libp2pEvents>
71
+ private readonly runOnTransientConnection: boolean
72
+ private readonly log: Logger
73
+
74
+ constructor (components: IdentifyComponents, init: IdentifyInit = {}) {
75
+ this.started = false
76
+ this.peerId = components.peerId
77
+ this.peerStore = components.peerStore
78
+ this.registrar = components.registrar
79
+ this.addressManager = components.addressManager
80
+ this.connectionManager = components.connectionManager
81
+ this.events = components.events
82
+ this.log = components.logger.forComponent('libp2p:identify')
83
+
84
+ this.identifyProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PROTOCOL_VERSION}`
85
+ this.identifyPushProtocolStr = `/${init.protocolPrefix ?? defaultValues.protocolPrefix}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_NAME}/${MULTICODEC_IDENTIFY_PUSH_PROTOCOL_VERSION}`
86
+ this.timeout = init.timeout ?? defaultValues.timeout
87
+ this.maxInboundStreams = init.maxInboundStreams ?? defaultValues.maxInboundStreams
88
+ this.maxOutboundStreams = init.maxOutboundStreams ?? defaultValues.maxOutboundStreams
89
+ this.maxPushIncomingStreams = init.maxPushIncomingStreams ?? defaultValues.maxPushIncomingStreams
90
+ this.maxPushOutgoingStreams = init.maxPushOutgoingStreams ?? defaultValues.maxPushOutgoingStreams
91
+ this.maxIdentifyMessageSize = init.maxIdentifyMessageSize ?? defaultValues.maxIdentifyMessageSize
92
+ this.maxObservedAddresses = init.maxObservedAddresses ?? defaultValues.maxObservedAddresses
93
+ this.runOnTransientConnection = init.runOnTransientConnection ?? defaultValues.runOnTransientConnection
94
+
95
+ // Store self host metadata
96
+ this.host = {
97
+ protocolVersion: `${init.protocolPrefix ?? defaultValues.protocolPrefix}/${IDENTIFY_PROTOCOL_VERSION}`,
98
+ agentVersion: init.agentVersion ?? `${components.nodeInfo.name}/${components.nodeInfo.version}`
99
+ }
100
+
101
+ if (init.runOnConnectionOpen ?? defaultValues.runOnConnectionOpen) {
102
+ // When a new connection happens, trigger identify
103
+ components.events.addEventListener('connection:open', (evt) => {
104
+ const connection = evt.detail
105
+ this.identify(connection).catch(err => { this.log.error('error during identify trigged by connection:open', err) })
106
+ })
107
+ }
108
+
109
+ // When self peer record changes, trigger identify-push
110
+ components.events.addEventListener('self:peer:update', (evt) => {
111
+ void this.push().catch(err => { this.log.error(err) })
112
+ })
113
+
114
+ // Append user agent version to default AGENT_VERSION depending on the environment
115
+ if (this.host.agentVersion === `${components.nodeInfo.name}/${components.nodeInfo.version}`) {
116
+ if (isNode || isElectronMain) {
117
+ this.host.agentVersion += ` UserAgent=${globalThis.process.version}`
118
+ } else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) {
119
+ this.host.agentVersion += ` UserAgent=${globalThis.navigator.userAgent}`
120
+ }
121
+ }
122
+ }
123
+
124
+ isStarted (): boolean {
125
+ return this.started
126
+ }
127
+
128
+ async start (): Promise<void> {
129
+ if (this.started) {
130
+ return
131
+ }
132
+
133
+ await this.peerStore.merge(this.peerId, {
134
+ metadata: {
135
+ AgentVersion: uint8ArrayFromString(this.host.agentVersion),
136
+ ProtocolVersion: uint8ArrayFromString(this.host.protocolVersion)
137
+ }
138
+ })
139
+
140
+ await this.registrar.handle(this.identifyProtocolStr, (data) => {
141
+ void this._handleIdentify(data).catch(err => {
142
+ this.log.error(err)
143
+ })
144
+ }, {
145
+ maxInboundStreams: this.maxInboundStreams,
146
+ maxOutboundStreams: this.maxOutboundStreams,
147
+ runOnTransientConnection: this.runOnTransientConnection
148
+ })
149
+ await this.registrar.handle(this.identifyPushProtocolStr, (data) => {
150
+ void this._handlePush(data).catch(err => {
151
+ this.log.error(err)
152
+ })
153
+ }, {
154
+ maxInboundStreams: this.maxPushIncomingStreams,
155
+ maxOutboundStreams: this.maxPushOutgoingStreams,
156
+ runOnTransientConnection: this.runOnTransientConnection
157
+ })
158
+
159
+ this.started = true
160
+ }
161
+
162
+ async stop (): Promise<void> {
163
+ await this.registrar.unhandle(this.identifyProtocolStr)
164
+ await this.registrar.unhandle(this.identifyPushProtocolStr)
165
+
166
+ this.started = false
167
+ }
168
+
169
+ /**
170
+ * Send an Identify Push update to the list of connections
171
+ */
172
+ async pushToConnections (connections: Connection[]): Promise<void> {
173
+ const listenAddresses = this.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code))
174
+ const peerRecord = new PeerRecord({
175
+ peerId: this.peerId,
176
+ multiaddrs: listenAddresses
177
+ })
178
+ const signedPeerRecord = await RecordEnvelope.seal(peerRecord, this.peerId)
179
+ const supportedProtocols = this.registrar.getProtocols()
180
+ const peer = await this.peerStore.get(this.peerId)
181
+ const agentVersion = uint8ArrayToString(peer.metadata.get('AgentVersion') ?? uint8ArrayFromString(this.host.agentVersion))
182
+ const protocolVersion = uint8ArrayToString(peer.metadata.get('ProtocolVersion') ?? uint8ArrayFromString(this.host.protocolVersion))
183
+
184
+ const pushes = connections.map(async connection => {
185
+ let stream: Stream | undefined
186
+ const signal = AbortSignal.timeout(this.timeout)
187
+
188
+ setMaxListeners(Infinity, signal)
189
+
190
+ try {
191
+ stream = await connection.newStream(this.identifyPushProtocolStr, {
192
+ signal,
193
+ runOnTransientConnection: this.runOnTransientConnection
194
+ })
195
+
196
+ const pb = pbStream(stream, {
197
+ maxDataLength: this.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
198
+ }).pb(IdentifyMessage)
199
+
200
+ await pb.write({
201
+ listenAddrs: listenAddresses.map(ma => ma.bytes),
202
+ signedPeerRecord: signedPeerRecord.marshal(),
203
+ protocols: supportedProtocols,
204
+ agentVersion,
205
+ protocolVersion
206
+ }, {
207
+ signal
208
+ })
209
+
210
+ await stream.close({
211
+ signal
212
+ })
213
+ } catch (err: any) {
214
+ // Just log errors
215
+ this.log.error('could not push identify update to peer', err)
216
+ stream?.abort(err)
217
+ }
218
+ })
219
+
220
+ await Promise.all(pushes)
221
+ }
222
+
223
+ /**
224
+ * Calls `push` on all peer connections
225
+ */
226
+ async push (): Promise<void> {
227
+ // Do not try to push if we are not running
228
+ if (!this.isStarted()) {
229
+ return
230
+ }
231
+
232
+ const connections: Connection[] = []
233
+
234
+ await Promise.all(
235
+ this.connectionManager.getConnections().map(async conn => {
236
+ try {
237
+ const peer = await this.peerStore.get(conn.remotePeer)
238
+
239
+ if (!peer.protocols.includes(this.identifyPushProtocolStr)) {
240
+ return
241
+ }
242
+
243
+ connections.push(conn)
244
+ } catch (err: any) {
245
+ if (err.code !== ERR_NOT_FOUND) {
246
+ throw err
247
+ }
248
+ }
249
+ })
250
+ )
251
+
252
+ await this.pushToConnections(connections)
253
+ }
254
+
255
+ async _identify (connection: Connection, options: AbortOptions = {}): Promise<IdentifyMessage> {
256
+ let stream: Stream | undefined
257
+
258
+ if (options.signal == null) {
259
+ const signal = AbortSignal.timeout(this.timeout)
260
+ setMaxListeners(Infinity, signal)
261
+
262
+ options = {
263
+ ...options,
264
+ signal
265
+ }
266
+ }
267
+
268
+ try {
269
+ stream = await connection.newStream(this.identifyProtocolStr, {
270
+ ...options,
271
+ runOnTransientConnection: this.runOnTransientConnection
272
+ })
273
+
274
+ const pb = pbStream(stream, {
275
+ maxDataLength: this.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
276
+ }).pb(IdentifyMessage)
277
+
278
+ const message = await pb.read(options)
279
+
280
+ await stream.close(options)
281
+
282
+ return message
283
+ } catch (err: any) {
284
+ this.log.error('error while reading identify message', err)
285
+ stream?.abort(err)
286
+ throw err
287
+ }
288
+ }
289
+
290
+ async identify (connection: Connection, options: AbortOptions = {}): Promise<IdentifyResult> {
291
+ const message = await this._identify(connection, options)
292
+ const {
293
+ publicKey,
294
+ protocols,
295
+ observedAddr
296
+ } = message
297
+
298
+ if (publicKey == null) {
299
+ throw new CodeError('public key was missing from identify message', 'ERR_MISSING_PUBLIC_KEY')
300
+ }
301
+
302
+ const id = await peerIdFromKeys(publicKey)
303
+
304
+ if (!connection.remotePeer.equals(id)) {
305
+ throw new CodeError('identified peer does not match the expected peer', 'ERR_INVALID_PEER')
306
+ }
307
+
308
+ if (this.peerId.equals(id)) {
309
+ throw new CodeError('identified peer is our own peer id?', 'ERR_INVALID_PEER')
310
+ }
311
+
312
+ // Get the observedAddr if there is one
313
+ const cleanObservedAddr = getCleanMultiaddr(observedAddr)
314
+
315
+ this.log('identify completed for peer %p and protocols %o', id, protocols)
316
+ this.log('our observed address is %a', cleanObservedAddr)
317
+
318
+ if (cleanObservedAddr != null &&
319
+ this.addressManager.getObservedAddrs().length < (this.maxObservedAddresses ?? Infinity)) {
320
+ this.log('storing our observed address %a', cleanObservedAddr)
321
+ this.addressManager.addObservedAddr(cleanObservedAddr)
322
+ }
323
+
324
+ return this.#consumeIdentifyMessage(connection, message)
325
+ }
326
+
327
+ /**
328
+ * Sends the `Identify` response with the Signed Peer Record
329
+ * to the requesting peer over the given `connection`
330
+ */
331
+ async _handleIdentify (data: IncomingStreamData): Promise<void> {
332
+ const { connection, stream } = data
333
+
334
+ const signal = AbortSignal.timeout(this.timeout)
335
+
336
+ setMaxListeners(Infinity, signal)
337
+
338
+ try {
339
+ const publicKey = this.peerId.publicKey ?? new Uint8Array(0)
340
+ const peerData = await this.peerStore.get(this.peerId)
341
+ const multiaddrs = this.addressManager.getAddresses().map(ma => ma.decapsulateCode(protocols('p2p').code))
342
+ let signedPeerRecord = peerData.peerRecordEnvelope
343
+
344
+ if (multiaddrs.length > 0 && signedPeerRecord == null) {
345
+ const peerRecord = new PeerRecord({
346
+ peerId: this.peerId,
347
+ multiaddrs
348
+ })
349
+
350
+ const envelope = await RecordEnvelope.seal(peerRecord, this.peerId)
351
+ signedPeerRecord = envelope.marshal().subarray()
352
+ }
353
+
354
+ let observedAddr: Uint8Array | undefined = connection.remoteAddr.bytes
355
+
356
+ if (!IP_OR_DOMAIN.matches(connection.remoteAddr)) {
357
+ observedAddr = undefined
358
+ }
359
+
360
+ const pb = pbStream(stream).pb(IdentifyMessage)
361
+
362
+ await pb.write({
363
+ protocolVersion: this.host.protocolVersion,
364
+ agentVersion: this.host.agentVersion,
365
+ publicKey,
366
+ listenAddrs: multiaddrs.map(addr => addr.bytes),
367
+ signedPeerRecord,
368
+ observedAddr,
369
+ protocols: peerData.protocols
370
+ }, {
371
+ signal
372
+ })
373
+
374
+ await stream.close({
375
+ signal
376
+ })
377
+ } catch (err: any) {
378
+ this.log.error('could not respond to identify request', err)
379
+ stream.abort(err)
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Reads the Identify Push message from the given `connection`
385
+ */
386
+ async _handlePush (data: IncomingStreamData): Promise<void> {
387
+ const { connection, stream } = data
388
+
389
+ try {
390
+ if (this.peerId.equals(connection.remotePeer)) {
391
+ throw new Error('received push from ourselves?')
392
+ }
393
+
394
+ const options = {
395
+ signal: AbortSignal.timeout(this.timeout)
396
+ }
397
+
398
+ const pb = pbStream(stream, {
399
+ maxDataLength: this.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
400
+ }).pb(IdentifyMessage)
401
+
402
+ const message = await pb.read(options)
403
+ await stream.close(options)
404
+
405
+ await this.#consumeIdentifyMessage(connection, message)
406
+ } catch (err: any) {
407
+ this.log.error('received invalid message', err)
408
+ stream.abort(err)
409
+ return
410
+ }
411
+
412
+ this.log('handled push from %p', connection.remotePeer)
413
+ }
414
+
415
+ async #consumeIdentifyMessage (connection: Connection, message: IdentifyMessage): Promise<IdentifyResult> {
416
+ this.log('received identify from %p', connection.remotePeer)
417
+
418
+ if (message == null) {
419
+ throw new CodeError('message was null or undefined', 'ERR_INVALID_MESSAGE')
420
+ }
421
+
422
+ const peer: PeerData = {}
423
+
424
+ if (message.listenAddrs.length > 0) {
425
+ peer.addresses = message.listenAddrs.map(buf => ({
426
+ isCertified: false,
427
+ multiaddr: multiaddr(buf)
428
+ }))
429
+ }
430
+
431
+ if (message.protocols.length > 0) {
432
+ peer.protocols = message.protocols
433
+ }
434
+
435
+ if (message.publicKey != null) {
436
+ peer.publicKey = message.publicKey
437
+
438
+ const peerId = await peerIdFromKeys(message.publicKey)
439
+
440
+ if (!peerId.equals(connection.remotePeer)) {
441
+ throw new CodeError('public key did not match remote PeerId', 'ERR_INVALID_PUBLIC_KEY')
442
+ }
443
+ }
444
+
445
+ let output: SignedPeerRecord | undefined
446
+
447
+ // if the peer record has been sent, prefer the addresses in the record as they are signed by the remote peer
448
+ if (message.signedPeerRecord != null) {
449
+ this.log('received signedPeerRecord from %p', connection.remotePeer)
450
+
451
+ let peerRecordEnvelope = message.signedPeerRecord
452
+ const envelope = await RecordEnvelope.openAndCertify(peerRecordEnvelope, PeerRecord.DOMAIN)
453
+ let peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
454
+
455
+ // Verify peerId
456
+ if (!peerRecord.peerId.equals(envelope.peerId)) {
457
+ throw new CodeError('signing key does not match PeerId in the PeerRecord', 'ERR_INVALID_SIGNING_KEY')
458
+ }
459
+
460
+ // Make sure remote peer is the one sending the record
461
+ if (!connection.remotePeer.equals(peerRecord.peerId)) {
462
+ throw new CodeError('signing key does not match remote PeerId', 'ERR_INVALID_PEER_RECORD_KEY')
463
+ }
464
+
465
+ let existingPeer: Peer | undefined
466
+
467
+ try {
468
+ existingPeer = await this.peerStore.get(peerRecord.peerId)
469
+ } catch (err: any) {
470
+ if (err.code !== 'ERR_NOT_FOUND') {
471
+ throw err
472
+ }
473
+ }
474
+
475
+ if (existingPeer != null) {
476
+ // don't lose any existing metadata
477
+ peer.metadata = existingPeer.metadata
478
+
479
+ // if we have previously received a signed record for this peer, compare it to the incoming one
480
+ if (existingPeer.peerRecordEnvelope != null) {
481
+ const storedEnvelope = await RecordEnvelope.createFromProtobuf(existingPeer.peerRecordEnvelope)
482
+ const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload)
483
+
484
+ // ensure seq is greater than, or equal to, the last received
485
+ if (storedRecord.seqNumber >= peerRecord.seqNumber) {
486
+ this.log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber)
487
+ peerRecord = storedRecord
488
+ peerRecordEnvelope = existingPeer.peerRecordEnvelope
489
+ }
490
+ }
491
+ }
492
+
493
+ // store the signed record for next time
494
+ peer.peerRecordEnvelope = peerRecordEnvelope
495
+
496
+ // override the stored addresses with the signed multiaddrs
497
+ peer.addresses = peerRecord.multiaddrs.map(multiaddr => ({
498
+ isCertified: true,
499
+ multiaddr
500
+ }))
501
+
502
+ output = {
503
+ seq: peerRecord.seqNumber,
504
+ addresses: peerRecord.multiaddrs
505
+ }
506
+ } else {
507
+ this.log('%p did not send a signed peer record', connection.remotePeer)
508
+ }
509
+
510
+ this.log('patching %p with', connection.remotePeer, peer)
511
+ await this.peerStore.patch(connection.remotePeer, peer)
512
+
513
+ if (message.agentVersion != null || message.protocolVersion != null) {
514
+ const metadata: Record<string, Uint8Array> = {}
515
+
516
+ if (message.agentVersion != null) {
517
+ metadata.AgentVersion = uint8ArrayFromString(message.agentVersion)
518
+ }
519
+
520
+ if (message.protocolVersion != null) {
521
+ metadata.ProtocolVersion = uint8ArrayFromString(message.protocolVersion)
522
+ }
523
+
524
+ this.log('merging %p metadata', connection.remotePeer, metadata)
525
+ await this.peerStore.merge(connection.remotePeer, {
526
+ metadata
527
+ })
528
+ }
529
+
530
+ const result: IdentifyResult = {
531
+ peerId: connection.remotePeer,
532
+ protocolVersion: message.protocolVersion,
533
+ agentVersion: message.agentVersion,
534
+ publicKey: message.publicKey,
535
+ listenAddrs: message.listenAddrs.map(buf => multiaddr(buf)),
536
+ observedAddr: message.observedAddr == null ? undefined : multiaddr(message.observedAddr),
537
+ protocols: message.protocols,
538
+ signedPeerRecord: output,
539
+ connection
540
+ }
541
+
542
+ this.events.safeDispatchEvent('peer:identify', { detail: result })
543
+
544
+ return result
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Takes the `addr` and converts it to a Multiaddr if possible
550
+ */
551
+ function getCleanMultiaddr (addr: Uint8Array | string | null | undefined): Multiaddr | undefined {
552
+ if (addr != null && addr.length > 0) {
553
+ try {
554
+ return multiaddr(addr)
555
+ } catch {
556
+
557
+ }
558
+ }
559
+ }
package/src/index.ts ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
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
+ *
6
+ * @example
7
+ *
8
+ * ```typescript
9
+ * import { createLibp2p } from 'libp2p'
10
+ * import { identify } from '@libp2p/identify'
11
+ *
12
+ * const node = await createLibp2p({
13
+ * // ...other options
14
+ * services: {
15
+ * identify: identify()
16
+ * }
17
+ * })
18
+ * ```
19
+ */
20
+
21
+ import {
22
+ MULTICODEC_IDENTIFY,
23
+ MULTICODEC_IDENTIFY_PUSH
24
+ } from './consts.js'
25
+ import { Identify as IdentifyClass } from './identify.js'
26
+ import type { AbortOptions, IdentifyResult, Libp2pEvents, ComponentLogger, NodeInfo } from '@libp2p/interface'
27
+ import type { TypedEventTarget } from '@libp2p/interface/events'
28
+ import type { PeerId } from '@libp2p/interface/peer-id'
29
+ import type { PeerStore } from '@libp2p/interface/peer-store'
30
+ import type { Connection } from '@libp2p/interface/src/connection/index.js'
31
+ import type { AddressManager } from '@libp2p/interface-internal/address-manager'
32
+ import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
33
+ import type { Registrar } from '@libp2p/interface-internal/registrar'
34
+
35
+ export interface IdentifyInit {
36
+ /**
37
+ * The prefix to use for the protocol (default: 'ipfs')
38
+ */
39
+ protocolPrefix?: string
40
+
41
+ /**
42
+ * What details we should send as part of an identify message
43
+ */
44
+ agentVersion?: string
45
+
46
+ /**
47
+ * How long we should wait for a remote peer to send their identify response
48
+ */
49
+ timeout?: number
50
+
51
+ /**
52
+ * Identify responses larger than this in bytes will be rejected (default: 8192)
53
+ */
54
+ maxIdentifyMessageSize?: number
55
+
56
+ maxInboundStreams?: number
57
+ maxOutboundStreams?: number
58
+
59
+ maxPushIncomingStreams?: number
60
+ maxPushOutgoingStreams?: number
61
+ maxObservedAddresses?: number
62
+
63
+ /**
64
+ * Whether to automatically dial identify on newly opened connections (default: true)
65
+ */
66
+ runOnConnectionOpen?: boolean
67
+
68
+ /**
69
+ * Whether to run on connections with data or duration limits (default: true)
70
+ */
71
+ runOnTransientConnection?: boolean
72
+ }
73
+
74
+ export interface IdentifyComponents {
75
+ peerId: PeerId
76
+ peerStore: PeerStore
77
+ connectionManager: ConnectionManager
78
+ registrar: Registrar
79
+ addressManager: AddressManager
80
+ events: TypedEventTarget<Libp2pEvents>
81
+ logger: ComponentLogger
82
+ nodeInfo: NodeInfo
83
+ }
84
+
85
+ /**
86
+ * The protocols the Identify service supports
87
+ */
88
+ export const multicodecs = {
89
+ IDENTIFY: MULTICODEC_IDENTIFY,
90
+ IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
91
+ }
92
+
93
+ export interface Identify {
94
+ /**
95
+ * due to the default limits on inbound/outbound streams for this protocol,
96
+ * invoking this method when runOnConnectionOpen is true can lead to unpredictable results
97
+ * as streams may be closed by the local or the remote node.
98
+ * Please use with caution. If you find yourself needing to call this method to discover other peers that support your protocol,
99
+ * you may be better off configuring a topology to be notified instead.
100
+ */
101
+ identify(connection: Connection, options?: AbortOptions): Promise<IdentifyResult>
102
+
103
+ push(): Promise<void>
104
+ }
105
+
106
+ export function identify (init: IdentifyInit = {}): (components: IdentifyComponents) => Identify {
107
+ return (components) => new IdentifyClass(components, init)
108
+ }
@@ -0,0 +1,30 @@
1
+ syntax = "proto3";
2
+
3
+ message Identify {
4
+ // protocolVersion determines compatibility between peers
5
+ optional string protocolVersion = 5; // e.g. ipfs/1.0.0
6
+
7
+ // agentVersion is like a UserAgent string in browsers, or client version in bittorrent
8
+ // includes the client name and client.
9
+ optional string agentVersion = 6; // e.g. go-ipfs/0.1.0
10
+
11
+ // publicKey is this node's public key (which also gives its node.ID)
12
+ // - may not need to be sent, as secure channel implies it has been sent.
13
+ // - then again, if we change / disable secure channel, may still want it.
14
+ optional bytes publicKey = 1;
15
+
16
+ // listenAddrs are the multiaddrs the sender node listens for open connections on
17
+ repeated bytes listenAddrs = 2;
18
+
19
+ // oservedAddr is the multiaddr of the remote endpoint that the sender node perceives
20
+ // this is useful information to convey to the other side, as it helps the remote endpoint
21
+ // determine whether its connection to the local peer goes through NAT.
22
+ optional bytes observedAddr = 4;
23
+
24
+ repeated string protocols = 3;
25
+
26
+ // signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,
27
+ // signed by the sending node. It contains the same addresses as the listenAddrs field, but
28
+ // in a form that lets us share authenticated addrs with other peers.
29
+ optional bytes signedPeerRecord = 8;
30
+ }