diff --git a/package-lock.json b/package-lock.json index 78f133ea..a8f503d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "apify-client", - "version": "2.9.7", + "version": "2.9.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apify-client", - "version": "2.9.7", + "version": "2.9.8", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.25.0", diff --git a/package.json b/package.json index 028b8b60..0dc41f04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apify-client", - "version": "2.9.7", + "version": "2.9.8", "description": "Apify API client for JavaScript", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/src/apify_client.ts b/src/apify_client.ts index 1df2927c..b62a45c4 100644 --- a/src/apify_client.ts +++ b/src/apify_client.ts @@ -53,6 +53,7 @@ export class ApifyClient { requestInterceptors: ow.optional.array, timeoutSecs: ow.optional.number, token: ow.optional.string, + userAgentSuffix: ow.optional.any(ow.string, ow.array.ofType(ow.string)), })); const { @@ -77,6 +78,7 @@ export class ApifyClient { timeoutSecs, logger: this.logger, token: this.token, + userAgentSuffix: options.userAgentSuffix, }); } @@ -341,4 +343,5 @@ export interface ApifyClientOptions { /** @default 360 */ timeoutSecs?: number; token?: string; + userAgentSuffix?: string | string[]; } diff --git a/src/http_client.ts b/src/http_client.ts index 4177f9c2..e399bb2c 100644 --- a/src/http_client.ts +++ b/src/http_client.ts @@ -6,11 +6,11 @@ import KeepAliveAgent from 'agentkeepalive'; import retry, { RetryFunction } from 'async-retry'; import axios, { AxiosError, + AxiosHeaders, AxiosInstance, AxiosRequestConfig, - InternalAxiosRequestConfig, AxiosResponse, - AxiosHeaders, + InternalAxiosRequestConfig, } from 'axios'; import { ApifyApiError } from './apify_api_error'; @@ -21,12 +21,7 @@ import { responseInterceptors, } from './interceptors'; import { Statistics } from './statistics'; -import { - isNode, - getVersionData, - cast, - isStream, -} from './utils'; +import { asArray, cast, getVersionData, isNode, isStream } from './utils'; const { version } = getVersionData(); @@ -116,7 +111,12 @@ export class HttpClient { if (isNode()) { // Works only in Node. Cannot be set in browser const isAtHome = !!process.env[APIFY_ENV_VARS.IS_AT_HOME]; - const userAgent = `ApifyClient/${version} (${os.type()}; Node/${process.version}); isAtHome/${isAtHome}`; + let userAgent = `ApifyClient/${version} (${os.type()}; Node/${process.version}); isAtHome/${isAtHome}`; + + if (options.userAgentSuffix) { + userAgent += `; ${asArray(options.userAgentSuffix).join('; ')}`; + } + this.axios.defaults.headers['User-Agent'] = userAgent; } @@ -288,4 +288,6 @@ export interface HttpClientOptions { logger: Log; token?: string; workflowKey?: string; + /** @internal */ + userAgentSuffix?: string | string[]; } diff --git a/src/utils.ts b/src/utils.ts index 22ce45ae..6d74397a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -238,6 +238,14 @@ export function cast(input: unknown): T { return input as T; } +export function asArray(value: T | T[]): T[] { + if (Array.isArray(value)) { + return value; + } + + return [value]; +} + export type Dictionary = Record; export type DistributiveOptional = T extends any ? Omit & Partial> : never; diff --git a/test/http_client.test.js b/test/http_client.test.js index 6a88eb18..b4a757a1 100644 --- a/test/http_client.test.js +++ b/test/http_client.test.js @@ -27,10 +27,7 @@ describe('HttpClient', () => { baseUrl, timeoutSecs: 1, maxRetries: 0, - requestInterceptors: [(config) => { - config.headers = {}; - return config; - }], + userAgentSuffix: ['SDK/3.1.1', 'Crawlee/3.11.5'], }); }); afterEach(async () => { @@ -41,20 +38,13 @@ describe('HttpClient', () => { const context = { delayMillis: 3000 }; const resourceId = Buffer.from(JSON.stringify(context)).toString('hex'); - expect.assertions(2); - try { - await client.actor(resourceId).get(); - } catch (err) { - expect(err.message).toMatch('timeout of 1000ms exceeded'); - } + await expect(client.actor(resourceId).get()).rejects.toThrow('timeout of 1000ms exceeded'); + const ua = mockServer.getLastRequest().headers['user-agent']; + expect(ua).toMatch(/ApifyClient\/\d+\.\d+\.\d+/); + expect(ua).toMatch('isAtHome/false; SDK/3.1.1; Crawlee/3.11.5'); - try { - const r = await page.evaluate((rId) => client.task(rId).get(), resourceId); - expect(r).toBeDefined(); - } catch (err) { - expect(err).toBeInstanceOf(Error); - // this is failing after axios upgrade, the error is returned with a wrong name and message - // expect(err.message).toMatch('timeout of 1000ms exceeded'); - } + await expect(page.evaluate((rId) => client.task(rId).get(), resourceId)).rejects.toThrow(); + // this is failing after axios upgrade, the error is returned with a wrong name and message + // expect(err.message).toMatch('timeout of 1000ms exceeded'); }); });