Skip to content

Commit 96f14e4

Browse files
authored
fix: include platform in user agent (#2942)
Adds a `userAgent` property to `NodeInfo` that includes the platform the node is running on with detection for node, browsers, electron, deno, bun and react-native. Also allows importing the user agent function for reuse elsewhere.
1 parent 34b3c14 commit 96f14e4

File tree

20 files changed

+98
-30
lines changed

20 files changed

+98
-30
lines changed

packages/auto-tls/src/auto-tls.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import process from 'node:process'
21
import { ClientAuth } from '@libp2p/http-fetch/auth'
32
import { serviceCapabilities, serviceDependencies, setMaxListeners, start, stop } from '@libp2p/interface'
43
import { debounce } from '@libp2p/utils/debounce'
@@ -54,7 +53,6 @@ export class AutoTLS implements AutoTLSInterface {
5453
private readonly domain
5554
private readonly domainMapper: DomainMapper
5655
private readonly autoConfirmAddress: boolean
57-
private readonly userAgent: string
5856

5957
constructor (components: AutoTLSComponents, init: AutoTLSInit = {}) {
6058
this.log = components.logger.forComponent('libp2p:auto-tls')
@@ -79,8 +77,7 @@ export class AutoTLS implements AutoTLSInterface {
7977
const base36EncodedPeer = base36.encode(this.components.peerId.toCID().bytes)
8078
this.domain = `${base36EncodedPeer}.${this.forgeDomain}`
8179
this.email = `${base36EncodedPeer}@${this.forgeDomain}`
82-
this.userAgent = init.userAgent ?? `${this.components.nodeInfo.name}/${this.components.nodeInfo.version} ${process.release.name}/${process.version.replaceAll('v', '')}`
83-
acme.axios.defaults.headers.common['User-Agent'] = this.userAgent
80+
acme.axios.defaults.headers.common['User-Agent'] = this.components.nodeInfo.userAgent
8481

8582
this.domainMapper = new DomainMapper(components, {
8683
...init,
@@ -345,7 +342,7 @@ export class AutoTLS implements AutoTLSInterface {
345342
method: 'POST',
346343
headers: {
347344
'Content-Type': 'application/json',
348-
'User-Agent': this.userAgent
345+
'User-Agent': this.components.nodeInfo.userAgent
349346
},
350347
body: JSON.stringify({
351348
Value: keyAuthorization,

packages/auto-tls/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export interface AutoTLSInit {
187187
* The User-Agent header sent during HTTP requests
188188
*
189189
* @default "js-libp2p/${version} node/${version}"
190+
* @deprecated Use `nodeInfo.userAgent` in the main libp2p config instead
190191
*/
191192
userAgent?: string
192193
}

packages/auto-tls/test/index.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ describe('auto-tls', () => {
5050
datastore: new MemoryDatastore(),
5151
nodeInfo: {
5252
name: 'name',
53-
version: 'version'
53+
version: 'version',
54+
userAgent: 'userAgent'
5455
}
5556
}
5657

packages/interface/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,11 @@ export interface NodeInfo {
723723
* The implementation version
724724
*/
725725
version: string
726+
727+
/**
728+
* A string that contains information about the implementation and runtime
729+
*/
730+
userAgent: string
726731
}
727732

728733
/**

packages/libp2p/.aegir.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@
22
export default {
33
build: {
44
bundlesizeMax: '95KB'
5+
},
6+
dependencyCheck: {
7+
ignore: [
8+
'react-native'
9+
]
510
}
611
}

packages/libp2p/package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
"types": "./dist/src/index.d.ts",
5353
"import": "./dist/src/index.js"
5454
},
55+
"./user-agent": {
56+
"types": "./dist/src/user-agent.d.ts",
57+
"import": "./dist/src/user-agent.js"
58+
},
5559
"./version": {
5660
"types": "./dist/src/version.d.ts",
5761
"import": "./dist/src/version.js"
@@ -128,15 +132,18 @@
128132
"p-wait-for": "^5.0.2",
129133
"sinon": "^19.0.2",
130134
"sinon-ts": "^2.0.0",
131-
"uint8arraylist": "^2.4.8"
135+
"uint8arraylist": "^2.4.8",
136+
"wherearewe": "^2.0.1"
132137
},
133138
"browser": {
134139
"./dist/src/connection-manager/constants.js": "./dist/src/connection-manager/constants.browser.js",
135-
"./dist/src/config/connection-gater.js": "./dist/src/config/connection-gater.browser.js"
140+
"./dist/src/config/connection-gater.js": "./dist/src/config/connection-gater.browser.js",
141+
"./dist/src/user-agent.js": "./dist/src/user-agent.browser.js"
136142
},
137143
"react-native": {
138144
"./dist/src/connection-manager/constants.js": "./dist/src/connection-manager/constants.browser.js",
139-
"./dist/src/config/connection-gater.js": "./dist/src/config/connection-gater.browser.js"
145+
"./dist/src/config/connection-gater.js": "./dist/src/config/connection-gater.browser.js",
146+
"./dist/src/user-agent.js": "./dist/src/user-agent.react-native.js"
140147
},
141148
"sideEffects": false
142149
}

packages/libp2p/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export interface Libp2pInit<T extends ServiceMap = ServiceMap> {
5050
/**
5151
* Metadata about the node - implementation name, version number, etc
5252
*/
53-
nodeInfo?: NodeInfo
53+
nodeInfo?: Partial<NodeInfo>
5454

5555
/**
5656
* Addresses for transport listening and to advertise to the network

packages/libp2p/src/libp2p.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RandomWalk } from './random-walk.js'
1919
import { DefaultRegistrar } from './registrar.js'
2020
import { DefaultTransportManager } from './transport-manager.js'
2121
import { DefaultUpgrader } from './upgrader.js'
22+
import { userAgent } from './user-agent.js'
2223
import * as pkg from './version.js'
2324
import type { Components } from './components.js'
2425
import type { Libp2p as Libp2pInterface, Libp2pInit } from './index.js'
@@ -64,13 +65,18 @@ export class Libp2p<T extends ServiceMap = ServiceMap> extends TypedEventEmitter
6465
this.log = this.logger.forComponent('libp2p')
6566
// @ts-expect-error {} may not be of type T
6667
this.services = {}
68+
69+
const nodeInfoName = init.nodeInfo?.name ?? pkg.name
70+
const nodeInfoVersion = init.nodeInfo?.version ?? pkg.name
71+
6772
// @ts-expect-error defaultComponents is missing component types added later
6873
const components = this.components = defaultComponents({
6974
peerId: init.peerId,
7075
privateKey: init.privateKey,
71-
nodeInfo: init.nodeInfo ?? {
72-
name: pkg.name,
73-
version: pkg.version
76+
nodeInfo: {
77+
name: nodeInfoName,
78+
version: nodeInfoVersion,
79+
userAgent: init.nodeInfo?.userAgent ?? userAgent(nodeInfoName, nodeInfoVersion)
7480
},
7581
logger: this.logger,
7682
events,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as pkg from './version.js'
2+
3+
export function userAgent (name?: string, version?: string): string {
4+
return `${name ?? pkg.name}/${version ?? pkg.version} browser/${globalThis.navigator.userAgent}`
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Platform } from 'react-native'
2+
import * as pkg from './version.js'
3+
4+
export function userAgent (name?: string, version?: string): string {
5+
return `${name ?? pkg.name}/${version ?? pkg.version} react-native/${Platform.OS}-${`${Platform.Version}`.replaceAll('v', '')}`
6+
}

packages/libp2p/src/user-agent.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import process from 'node:process'
2+
import * as pkg from './version.js'
3+
4+
export function userAgent (name?: string, version?: string): string {
5+
let platform = 'node'
6+
let platformVersion = process.versions.node
7+
8+
if (process.versions.deno != null) {
9+
platform = 'deno'
10+
platformVersion = process.versions.deno
11+
}
12+
13+
if (process.versions.bun != null) {
14+
platform = 'bun'
15+
platformVersion = process.versions.bun
16+
}
17+
18+
if (process.versions.electron != null) {
19+
platform = 'electron'
20+
platformVersion = process.versions.electron
21+
}
22+
23+
return `${name ?? pkg.name}/${version ?? pkg.version} ${platform}/${platformVersion.replaceAll('v', '')}`
24+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect } from 'aegir/chai'
2+
import { isNode, isElectronMain, isBrowser, isWebWorker } from 'wherearewe'
3+
import { userAgent } from '../../src/user-agent.js'
4+
5+
describe('user-agent', () => {
6+
it('should include runtime in user agent', () => {
7+
if (isNode) {
8+
expect(userAgent()).to.include('node/')
9+
} else if (isElectronMain) {
10+
expect(userAgent()).to.include('electron/')
11+
} else if (isBrowser || isWebWorker) {
12+
expect(userAgent()).to.include('browser/')
13+
}
14+
})
15+
})

packages/libp2p/typedoc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"entryPoints": [
33
"./src/index.ts",
4-
"./src/version.ts"
4+
"./src/version.ts",
5+
"./src/user-agent.ts"
56
]
67
}

packages/metrics-opentelemetry/test/index.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ describe('opentelemetry-metrics', () => {
66
const metrics = openTelemetryMetrics()({
77
nodeInfo: {
88
name: 'test',
9-
version: '1.0.0'
9+
version: '1.0.0',
10+
userAgent: 'test/1.0.0 node/1.0.0'
1011
}
1112
})
1213

packages/protocol-identify/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
"it-protobuf-stream": "^1.1.5",
6666
"protons-runtime": "^5.5.0",
6767
"uint8arraylist": "^2.4.8",
68-
"uint8arrays": "^5.1.0",
69-
"wherearewe": "^2.0.1"
68+
"uint8arrays": "^5.1.0"
7069
},
7170
"devDependencies": {
7271
"@libp2p/logger": "^5.1.7",

packages/protocol-identify/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export interface IdentifyInit {
5656

5757
/**
5858
* What details we should send as part of an identify message
59+
*
60+
* @deprecated Use `nodeInfo.userAgent` in the main libp2p config instead
5961
*/
6062
agentVersion?: string
6163

packages/protocol-identify/src/utils.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { peerIdFromCID, peerIdFromPublicKey } from '@libp2p/peer-id'
44
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
55
import { type Multiaddr, multiaddr } from '@multiformats/multiaddr'
66
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7-
import { isNode, isBrowser, isWebWorker, isElectronMain, isElectronRenderer, isReactNative } from 'wherearewe'
87
import { IDENTIFY_PROTOCOL_VERSION, MAX_IDENTIFY_MESSAGE_SIZE, MAX_PUSH_CONCURRENCY } from './consts.js'
98
import type { IdentifyComponents, IdentifyInit } from './index.js'
109
import type { Identify as IdentifyMessage } from './pb/message.js'
@@ -42,15 +41,7 @@ export function getAgentVersion (nodeInfo: NodeInfo, agentVersion?: string): str
4241
return agentVersion
4342
}
4443

45-
agentVersion = `${nodeInfo.name}/${nodeInfo.version}`
46-
// Append user agent version to default AGENT_VERSION depending on the environment
47-
if (isNode || isElectronMain) {
48-
agentVersion += ` UserAgent=${globalThis.process.version}`
49-
} else if (isBrowser || isWebWorker || isElectronRenderer || isReactNative) {
50-
agentVersion += ` UserAgent=${globalThis.navigator.userAgent}`
51-
}
52-
53-
return agentVersion
44+
return nodeInfo.userAgent
5445
}
5546

5647
export async function consumeIdentifyMessage (peerStore: PeerStore, events: TypedEventTarget<Libp2pEvents>, log: Logger, connection: Connection, message: IdentifyMessage): Promise<IdentifyResult> {

packages/protocol-identify/test/index.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ describe('identify', () => {
3939
logger: defaultLogger(),
4040
nodeInfo: {
4141
name: 'test',
42-
version: '1.0.0'
42+
version: '1.0.0',
43+
userAgent: 'test'
4344
}
4445
}
4546
})

packages/protocol-identify/test/push.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ describe('identify (push)', () => {
3232
logger: defaultLogger(),
3333
nodeInfo: {
3434
name: 'test',
35-
version: '1.0.0'
35+
version: '1.0.0',
36+
userAgent: 'test'
3637
}
3738
}
3839
})

packages/upnp-nat/test/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('UPnP NAT (TCP)', () => {
2929
async function createNatManager (natManagerOptions: UPnPNATInit = {}): Promise<{ natManager: any, components: StubbedUPnPNATComponents }> {
3030
const components: StubbedUPnPNATComponents = {
3131
peerId: peerIdFromPrivateKey(await generateKeyPair('Ed25519')),
32-
nodeInfo: { name: 'test', version: 'test' },
32+
nodeInfo: { name: 'test', version: 'test', userAgent: 'test' },
3333
logger: defaultLogger(),
3434
addressManager: stubInterface<AddressManager>(),
3535
events: new TypedEventEmitter()

0 commit comments

Comments
 (0)