diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index eb8de3e863..ad1166ba91 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,5 @@ export { DefaultPubSubTopic } from "./lib/constants"; +export { DefaultUserAgent } from "./lib/waku"; export * as proto_message from "./proto/message"; export * as proto_topic_only_message from "./proto/topic_only_message"; diff --git a/packages/core/src/lib/waku.ts b/packages/core/src/lib/waku.ts index 7192d2a59d..b5690d8889 100644 --- a/packages/core/src/lib/waku.ts +++ b/packages/core/src/lib/waku.ts @@ -16,6 +16,7 @@ import { StoreCodec, StoreComponents } from "./waku_store"; export const DefaultPingKeepAliveValueSecs = 0; export const DefaultRelayKeepAliveValueSecs = 5 * 60; +export const DefaultUserAgent = "js-waku"; const log = debug("waku:waku"); @@ -34,6 +35,11 @@ export interface WakuOptions { * @default {@link DefaultRelayKeepAliveValueSecs} */ relayKeepAlive?: number; + /** + * Set the user agent string to be used in identification of the node. + * @default {@link DefaultUserAgent} + */ + userAgent?: string; } export class WakuNode implements Waku { diff --git a/packages/create/src/index.ts b/packages/create/src/index.ts index d7f33bb321..63e09865ac 100644 --- a/packages/create/src/index.ts +++ b/packages/create/src/index.ts @@ -13,6 +13,7 @@ import { wakuRelay, wakuStore, } from "@waku/core"; +import { DefaultUserAgent } from "@waku/core"; import { getPredefinedBootstrapNodes } from "@waku/core/lib/predefined_bootstrap_nodes"; import type { Relay, WakuFull, WakuLight, WakuPrivacy } from "@waku/interfaces"; import type { Libp2p } from "libp2p"; @@ -72,7 +73,11 @@ export async function createLightNode( Object.assign(libp2pOptions, { peerDiscovery }); } - const libp2p = await defaultLibp2p(undefined, libp2pOptions); + const libp2p = await defaultLibp2p( + undefined, + libp2pOptions, + options?.userAgent + ); const store = wakuStore(options); const lightPush = wakuLightPush(options); @@ -101,7 +106,11 @@ export async function createPrivacyNode( Object.assign(libp2pOptions, { peerDiscovery }); } - const libp2p = await defaultLibp2p(wakuRelay(options), libp2pOptions); + const libp2p = await defaultLibp2p( + wakuRelay(options), + libp2pOptions, + options?.userAgent + ); return new WakuNode(options ?? {}, libp2p) as WakuPrivacy; } @@ -129,7 +138,11 @@ export async function createFullNode( Object.assign(libp2pOptions, { peerDiscovery }); } - const libp2p = await defaultLibp2p(wakuRelay(options), libp2pOptions); + const libp2p = await defaultLibp2p( + wakuRelay(options), + libp2pOptions, + options?.userAgent + ); const store = wakuStore(options); const lightPush = wakuLightPush(options); @@ -152,14 +165,20 @@ export function defaultPeerDiscovery(): ( export async function defaultLibp2p( wakuRelay?: (components: Components) => Relay, - options?: Partial + options?: Partial, + userAgent?: string ): Promise { const libp2pOpts = Object.assign( { transports: [webSockets({ filter: filterAll })], streamMuxers: [mplex()], connectionEncryption: [noise()], - }, + identify: { + host: { + agentVersion: userAgent ?? DefaultUserAgent, + }, + }, + } as Libp2pOptions, wakuRelay ? { pubsub: wakuRelay } : {}, options ?? {} ); diff --git a/packages/tests/tests/waku.node.spec.ts b/packages/tests/tests/waku.node.spec.ts index 640106ea16..f56c8a8c27 100644 --- a/packages/tests/tests/waku.node.spec.ts +++ b/packages/tests/tests/waku.node.spec.ts @@ -1,6 +1,7 @@ import { bootstrap } from "@libp2p/bootstrap"; import type { PeerId } from "@libp2p/interface-peer-id"; import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; +import { DefaultUserAgent } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; import { createLightNode, createPrivacyNode } from "@waku/create"; import type { @@ -186,3 +187,49 @@ describe("Decryption Keys", () => { expect(receivedMsg.timestamp?.valueOf()).to.eq(messageTimestamp.valueOf()); }); }); + +describe("User Agent", () => { + let waku1: Waku; + let waku2: Waku; + + afterEach(async function () { + !!waku1 && waku1.stop().catch((e) => console.log("Waku failed to stop", e)); + !!waku2 && waku2.stop().catch((e) => console.log("Waku failed to stop", e)); + }); + + it("Sets default value correctly", async function () { + this.timeout(20_000); + + const waku1UserAgent = "test-user-agent"; + + [waku1, waku2] = await Promise.all([ + createPrivacyNode({ + staticNoiseKey: NOISE_KEY_1, + userAgent: waku1UserAgent, + }).then((waku) => waku.start().then(() => waku)), + createPrivacyNode({ + staticNoiseKey: NOISE_KEY_2, + libp2p: { addresses: { listen: ["/ip4/0.0.0.0/tcp/0/ws"] } }, + }).then((waku) => waku.start().then(() => waku)), + ]); + + await waku1.libp2p.peerStore.addressBook.set( + waku2.libp2p.peerId, + waku2.libp2p.getMultiaddrs() + ); + await waku1.dial(waku2.libp2p.peerId); + await waitForRemotePeer(waku1); + + const [waku1PeerInfo, waku2PeerInfo] = await Promise.all([ + waku2.libp2p.peerStore.metadataBook.get(waku1.libp2p.peerId), + waku1.libp2p.peerStore.metadataBook.get(waku2.libp2p.peerId), + ]); + + expect(bytesToUtf8(waku1PeerInfo.get("AgentVersion")!)).to.eq( + waku1UserAgent + ); + expect(bytesToUtf8(waku2PeerInfo.get("AgentVersion")!)).to.eq( + DefaultUserAgent + ); + }); +});