diff --git a/package-lock.json b/package-lock.json index 31c0ab6..a279c2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-network-providers", - "version": "2.6.0", + "version": "2.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-network-providers", - "version": "2.6.0", + "version": "2.7.0", "license": "MIT", "dependencies": { "bech32": "1.1.4", diff --git a/package.json b/package.json index c9337a1..74f9f07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-network-providers", - "version": "2.6.0", + "version": "2.7.0", "lockfileVersion": 2, "requires": true, "author": "MultiversX", diff --git a/src/apiNetworkProvider.ts b/src/apiNetworkProvider.ts index 086980c..170b2fb 100644 --- a/src/apiNetworkProvider.ts +++ b/src/apiNetworkProvider.ts @@ -1,4 +1,4 @@ -import axios, { AxiosRequestConfig } from "axios"; +import axios from "axios"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig, defaultPagination } from "./config"; import { ContractQueryRequest } from "./contractQueryRequest"; @@ -16,17 +16,29 @@ import { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwor import { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } from "./tokens"; import { TransactionOnNetwork, prepareTransactionForBroadcasting } from "./transactions"; import { TransactionStatus } from "./transactionStatus"; +import { extendUserAgent } from "./userAgent"; +import { NetworkProviderConfig } from "./networkProviderConfig"; +import { BaseUserAgent } from "./constants"; // TODO: Find & remove duplicate code between "ProxyNetworkProvider" and "ApiNetworkProvider". export class ApiNetworkProvider implements INetworkProvider { private url: string; - private config: AxiosRequestConfig; + private config: NetworkProviderConfig; private backingProxyNetworkProvider; + private userAgentPrefix = `${BaseUserAgent}/api` - constructor(url: string, config?: AxiosRequestConfig) { + constructor(url: string, config?: NetworkProviderConfig) { this.url = url; + let proxyConfig = this.getProxyConfig(config); this.config = { ...defaultAxiosConfig, ...config }; - this.backingProxyNetworkProvider = new ProxyNetworkProvider(url, config); + this.backingProxyNetworkProvider = new ProxyNetworkProvider(url, proxyConfig); + extendUserAgent(this.userAgentPrefix, this.config); + } + + private getProxyConfig(config: NetworkProviderConfig | undefined) { + let proxyConfig = JSON.parse(JSON.stringify(config)); + proxyConfig = { ...defaultAxiosConfig, ...proxyConfig }; + return proxyConfig; } async getNetworkConfig(): Promise { diff --git a/src/constants.ts b/src/constants.ts index 932f959..ea134f2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,3 +3,5 @@ import { Address } from "./primitives"; export const MaxUint64AsBigNumber = new BigNumber("18446744073709551615"); export const EsdtContractAddress = new Address("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"); +export const BaseUserAgent = "multiversx-sdk" +export const UnknownClientName = "unknown" diff --git a/src/networkProviderConfig.ts b/src/networkProviderConfig.ts new file mode 100644 index 0000000..b66bffb --- /dev/null +++ b/src/networkProviderConfig.ts @@ -0,0 +1,5 @@ +import { AxiosRequestConfig } from 'axios'; + +export interface NetworkProviderConfig extends AxiosRequestConfig { + clientName?: string; +} diff --git a/src/providers.dev.net.spec.ts b/src/providers.dev.net.spec.ts index f4f4399..890c0e5 100644 --- a/src/providers.dev.net.spec.ts +++ b/src/providers.dev.net.spec.ts @@ -1,4 +1,4 @@ -import { assert } from "chai"; +import { assert, expect } from "chai"; import { ApiNetworkProvider } from "./apiNetworkProvider"; import { INetworkProvider, ITransactionNext } from "./interface"; import { Address } from "./primitives"; @@ -7,6 +7,7 @@ import { MockQuery } from "./testscommon/dummyQuery"; import { NonFungibleTokenOfAccountOnNetwork } from "./tokens"; import { TransactionEventData } from "./transactionEvents"; import { TransactionOnNetwork } from "./transactions"; +import { AxiosHeaders } from "axios"; describe("test network providers on devnet: Proxy and API", function () { let alice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); @@ -14,8 +15,8 @@ describe("test network providers on devnet: Proxy and API", function () { let dan = new Address("erd1kyaqzaprcdnv4luvanah0gfxzzsnpaygsy6pytrexll2urtd05ts9vegu7"); const MAX_NUMBER_OF_ITEMS_BY_DEFAULT = 20; - let apiProvider: INetworkProvider = new ApiNetworkProvider("https://devnet-api.multiversx.com", { timeout: 10000 }); - let proxyProvider: INetworkProvider = new ProxyNetworkProvider("https://devnet-gateway.multiversx.com", { timeout: 10000 }); + let apiProvider: INetworkProvider = new ApiNetworkProvider("https://devnet-api.multiversx.com", { timeout: 10000, clientName: 'test' }); + let proxyProvider: INetworkProvider = new ProxyNetworkProvider("https://devnet-gateway.multiversx.com", { timeout: 10000, clientName: 'test' }); it("should have same response for getNetworkConfig()", async function () { let apiResponse = await apiProvider.getNetworkConfig(); @@ -24,6 +25,39 @@ describe("test network providers on devnet: Proxy and API", function () { assert.deepEqual(apiResponse, proxyResponse); }); + it("should add userAgent unknown for clientName when no clientName passed", async function () { + const expectedApiUserAgent = "multiversx-sdk/api/unknown" + const expectedProxyUserAgent = "multiversx-sdk/proxy/unknown" + + let localApiProvider: any = new ApiNetworkProvider("https://devnet-api.multiversx.com", { timeout: 10000 }); + let localProxyProvider: any = new ProxyNetworkProvider("https://devnet-gateway.multiversx.com", { timeout: 10000 }); + + assert.equal(localApiProvider.config.headers.getUserAgent(), expectedApiUserAgent); + assert.equal(localProxyProvider.config.headers.getUserAgent(), expectedProxyUserAgent); + }); + + it("should set userAgent with specified clientName ", async function () { + const expectedApiUserAgent = "multiversx-sdk/api/test" + const expectedProxyUserAgent = "multiversx-sdk/proxy/test" + + let localApiProvider: any = new ApiNetworkProvider("https://devnet-api.multiversx.com", { timeout: 10000, clientName: 'test' }); + let localProxyProvider: any = new ProxyNetworkProvider("https://devnet-gateway.multiversx.com", { timeout: 10000, clientName: 'test' }); + + assert.equal(localApiProvider.config.headers.getUserAgent(), expectedApiUserAgent); + assert.equal(localProxyProvider.config.headers.getUserAgent(), expectedProxyUserAgent); + }); + + it("should keep the set userAgent and add the sdk to it", async function () { + const expectedApiUserAgent = "Client-info multiversx-sdk/api/test" + const expectedProxyUserAgent = "Client-info multiversx-sdk/proxy/test" + + let localApiProvider: any = new ApiNetworkProvider("https://devnet-api.multiversx.com", { timeout: 10000, headers: new AxiosHeaders({ "User-Agent": "Client-info" }), clientName: 'test' }); + let localProxyProvider: any = new ProxyNetworkProvider("https://devnet-gateway.multiversx.com", { timeout: 10000, headers: new AxiosHeaders({ "User-Agent": "Client-info" }), clientName: 'test' }); + + assert.equal(localApiProvider.config.headers.getUserAgent(), expectedApiUserAgent); + assert.equal(localProxyProvider.config.headers.getUserAgent(), expectedProxyUserAgent); + }); + it("should have same response for getNetworkStatus()", async function () { let apiResponse = await apiProvider.getNetworkStatus(); let proxyResponse = await proxyProvider.getNetworkStatus(); diff --git a/src/proxyNetworkProvider.ts b/src/proxyNetworkProvider.ts index a3d8d2d..c58e4bf 100644 --- a/src/proxyNetworkProvider.ts +++ b/src/proxyNetworkProvider.ts @@ -1,7 +1,7 @@ -import axios, { AxiosRequestConfig } from "axios"; +import axios from "axios"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig } from "./config"; -import { EsdtContractAddress } from "./constants"; +import { EsdtContractAddress, BaseUserAgent } from "./constants"; import { ContractQueryRequest } from "./contractQueryRequest"; import { ContractQueryResponse } from "./contractQueryResponse"; import { ErrContractQuery, ErrNetworkProvider } from "./errors"; @@ -14,15 +14,19 @@ import { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwor import { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } from "./tokens"; import { TransactionOnNetwork, prepareTransactionForBroadcasting } from "./transactions"; import { TransactionStatus } from "./transactionStatus"; +import { extendUserAgent } from "./userAgent"; +import { NetworkProviderConfig } from "./networkProviderConfig"; // TODO: Find & remove duplicate code between "ProxyNetworkProvider" and "ApiNetworkProvider". export class ProxyNetworkProvider implements INetworkProvider { private url: string; - private config: AxiosRequestConfig; + private config: NetworkProviderConfig; + private userAgentPrefix = `${BaseUserAgent}/proxy` - constructor(url: string, config?: AxiosRequestConfig) { + constructor(url: string, config?: NetworkProviderConfig) { this.url = url; this.config = { ...defaultAxiosConfig, ...config }; + extendUserAgent(this.userAgentPrefix, this.config); } async getNetworkConfig(): Promise { diff --git a/src/userAgent.ts b/src/userAgent.ts new file mode 100644 index 0000000..e102f11 --- /dev/null +++ b/src/userAgent.ts @@ -0,0 +1,19 @@ +import { AxiosHeaders } from "axios"; +import { NetworkProviderConfig } from "./networkProviderConfig"; +import { UnknownClientName } from "./constants"; + +export function extendUserAgent(userAgentPrefix: string, config: NetworkProviderConfig) { + if (!config.headers) { + config.headers = new AxiosHeaders({}) + }; + if (!config.clientName) { + console.log("Can you please provide the client name of the application that uses the SDK? It will be used for metrics.") + } + const headers = AxiosHeaders.from(config.headers as AxiosHeaders).normalize(true); + const resolvedClientName = config.clientName || UnknownClientName; + + const currentUserAgent = headers.hasUserAgent() ? headers.getUserAgent() : ''; + const newUserAgent = currentUserAgent ? `${currentUserAgent} ${userAgentPrefix}/${resolvedClientName}` : `${userAgentPrefix}/${resolvedClientName}`; + + headers.setUserAgent(newUserAgent, true); +}