From 170c1ff82fcd9979b062dd00098cbd0260cbe967 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Tue, 24 Mar 2026 13:38:48 +0000 Subject: [PATCH 1/7] feat: add opt-in system attributes to publish payload Add includeSystemAttributes context option that, when enabled, automatically includes sdk_name, sdk_version, application, environment, and app_version (if > 0) as attributes in every publish payload. --- README.md | 22 +++++ src/__tests__/context.test.js | 179 ++++++++++++++++++++++++++++++++++ src/client.ts | 12 +++ src/context.ts | 33 +++++-- src/version.ts | 1 + 5 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 src/version.ts diff --git a/README.md b/README.md index 05e021b..b6ead13 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,28 @@ context.attributes({ }); ``` +#### Including system attributes +You can opt-in to automatically include system attributes (SDK name, SDK version, application, environment, and application version) in every publish payload. These are sent as context attributes and can be useful for debugging and filtering in the Web Console. + +To enable this, set the `includeSystemAttributes` option to `true` when creating the context: +```javascript +const context = sdk.createContext(request, { + includeSystemAttributes: true, +}); +``` + +When enabled, the following attributes are automatically prepended to the publish payload: + +| Attribute | Description | +|:--- |---| +| `sdk_name` | The SDK agent name (e.g. `"absmartly-javascript-sdk"`) | +| `sdk_version` | The SDK version (e.g. `"1.13.4"`) | +| `application` | The application name from the SDK configuration | +| `environment` | The environment from the SDK configuration | +| `app_version` | The application version, only included if greater than `0` | + +These system attributes are prepended before any user-defined attributes. + #### Selecting a treatment ```javascript if (context.treament("exp_test_experiment") == 0) { diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js index 0dc3f89..8406b2c 100644 --- a/src/__tests__/context.test.js +++ b/src/__tests__/context.test.js @@ -473,6 +473,16 @@ describe("Context", () => { sdk.getClient.mockReturnValue(client); sdk.getEventLogger.mockReturnValue(SDK.defaultEventLogger); + client.getAgent.mockReturnValue("absmartly-javascript-sdk"); + client.getApplication.mockReturnValue({ name: "website", version: 0 }); + client.getEnvironment.mockReturnValue("production"); + + const systemAttributes = [ + { name: "sdk_version", value: expect.any(String), setAt: expect.any(Number) }, + { name: "application", value: "website", setAt: expect.any(Number) }, + { name: "environment", value: "production", setAt: expect.any(Number) }, + ]; + const contextOptions = { publishDelay: -1, refreshPeriod: 0, @@ -3922,4 +3932,173 @@ describe("Context", () => { expect(value).toEqual("string"); }); }); + + describe("includeSystemAttributes", () => { + it("should not include system attributes by default", (done) => { + const defaultOptions = { + publishDelay: -1, + refreshPeriod: 0, + }; + + const context = new Context(sdk, defaultOptions, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toBeUndefined(); + + done(); + }); + }); + + it("should include system attributes when includeSystemAttributes is true", (done) => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toBeDefined(); + expect(request.attributes.length).toBeGreaterThanOrEqual(4); + + const sdkNameAttr = request.attributes.find((a) => a.name === "sdk_name"); + const sdkVersionAttr = request.attributes.find((a) => a.name === "sdk_version"); + const applicationAttr = request.attributes.find((a) => a.name === "application"); + const environmentAttr = request.attributes.find((a) => a.name === "environment"); + + expect(sdkNameAttr).toBeDefined(); + expect(sdkNameAttr.value).toEqual("absmartly-javascript-sdk"); + expect(sdkNameAttr.setAt).toEqual(expect.any(Number)); + + expect(sdkVersionAttr).toBeDefined(); + expect(sdkVersionAttr.value).toEqual(expect.any(String)); + expect(sdkVersionAttr.setAt).toEqual(expect.any(Number)); + + expect(applicationAttr).toBeDefined(); + expect(applicationAttr.value).toEqual("website"); + expect(applicationAttr.setAt).toEqual(expect.any(Number)); + + expect(environmentAttr).toBeDefined(); + expect(environmentAttr.value).toEqual("production"); + expect(environmentAttr.setAt).toEqual(expect.any(Number)); + + done(); + }); + }); + + it("should prepend system attributes before user attributes", (done) => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.attribute("custom_attr", "custom_value"); + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + expect(request.attributes[0].name).toEqual("sdk_name"); + expect(request.attributes[1].name).toEqual("sdk_version"); + expect(request.attributes[2].name).toEqual("application"); + expect(request.attributes[3].name).toEqual("environment"); + expect(request.attributes[4].name).toEqual("custom_attr"); + expect(request.attributes[4].value).toEqual("custom_value"); + + done(); + }); + }); + + it("should include app_version when application version is set", (done) => { + client.getApplication.mockReturnValue({ name: "website", version: 3 }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); + expect(appVersionAttr).toBeDefined(); + expect(appVersionAttr.value).toEqual(3); + + client.getApplication.mockReturnValue({ name: "website", version: 0 }); + done(); + }); + }); + + it("should not include app_version when application version is 0", (done) => { + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); + expect(appVersionAttr).toBeUndefined(); + + done(); + }); + }); + + it("should only include user attributes when includeSystemAttributes is not set", (done) => { + const defaultOptions = { + publishDelay: -1, + refreshPeriod: 0, + }; + + const context = new Context(sdk, defaultOptions, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.attribute("custom_attr", "custom_value"); + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + expect(request.attributes).toEqual([ + { name: "custom_attr", value: "custom_value", setAt: expect.any(Number) }, + ]); + + done(); + }); + }); + }); }); diff --git a/src/client.ts b/src/client.ts index 6afdf66..3a48d61 100644 --- a/src/client.ts +++ b/src/client.ts @@ -287,6 +287,18 @@ export default class Client { }); } + getAgent() { + return this._opts.agent; + } + + getApplication() { + return this._opts.application; + } + + getEnvironment() { + return this._opts.environment; + } + getUnauthed(options: ClientRequestOptions) { return this.request({ ...options, diff --git a/src/context.ts b/src/context.ts index 424a0b4..71e6fae 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,4 +1,4 @@ -import { arrayEqualsShallow, hashUnit, isObject, isPromise } from "./utils"; +import { arrayEqualsShallow, getApplicationName, getApplicationVersion, hashUnit, isObject, isPromise } from "./utils"; import { VariantAssigner } from "./assigner"; import { AudienceMatcher } from "./matcher"; import { insertUniqueSorted } from "./algorithm"; @@ -6,6 +6,7 @@ import SDK, { EventLogger, EventName } from "./sdk"; import { ContextPublisher, PublishParams } from "./publisher"; import { ContextDataProvider } from "./provider"; import { ClientRequestOptions } from "./client"; +import { SDK_VERSION } from "./version"; type JSONPrimitive = string | number | boolean | null; type JSONObject = { [key: string]: JSONValue }; @@ -115,6 +116,7 @@ export type ContextOptions = { eventLogger?: EventLogger; refreshPeriod: number; publishDelay: number; + includeSystemAttributes?: boolean; }; export type ContextData = { @@ -830,12 +832,29 @@ export default class Context { })); } - if (this._attrs.length > 0) { - request.attributes = this._attrs.map((x) => ({ - name: x.name, - value: x.value, - setAt: x.setAt, - })); + const allAttributes: Attribute[] = []; + + if (this._opts.includeSystemAttributes === true) { + const client = this._sdk.getClient(); + const now = Date.now(); + const appVersion = getApplicationVersion(client.getApplication()); + allAttributes.push( + { name: "sdk_name", value: client.getAgent(), setAt: now }, + { name: "sdk_version", value: SDK_VERSION, setAt: now }, + { name: "application", value: getApplicationName(client.getApplication()), setAt: now }, + { name: "environment", value: client.getEnvironment(), setAt: now } + ); + if (appVersion > 0) { + allAttributes.push({ name: "app_version", value: appVersion, setAt: now }); + } + } + + for (const x of this._attrs) { + allAttributes.push({ name: x.name, value: x.value, setAt: x.setAt }); + } + + if (allAttributes.length > 0) { + request.attributes = allAttributes; } this._publisher diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..9bcd66f --- /dev/null +++ b/src/version.ts @@ -0,0 +1 @@ +export const SDK_VERSION = "1.13.4"; From 0c1622e89e93904d668d58b5212fa1e250e65811 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Tue, 24 Mar 2026 13:48:43 +0000 Subject: [PATCH 2/7] feat: always include sdkVersion in publish payload Add sdkVersion as a top-level field in the publish payload body, sent with every publish request regardless of includeSystemAttributes. --- src/__tests__/context.test.js | 26 ++++++++++++++++++++++++++ src/client.ts | 1 + src/context.ts | 1 + src/publisher.ts | 1 + 4 files changed, 29 insertions(+) diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js index 8406b2c..41de5f2 100644 --- a/src/__tests__/context.test.js +++ b/src/__tests__/context.test.js @@ -5,6 +5,7 @@ import { hashUnit } from "../utils"; import clone from "rfdc/default"; import { ContextPublisher } from "../publisher"; import { ContextDataProvider } from "../provider"; +import { SDK_VERSION } from "../version"; jest.mock("../client"); jest.mock("../sdk"); @@ -755,6 +756,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -868,6 +870,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1400,6 +1403,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1562,6 +1566,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 0, @@ -1603,6 +1608,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, attributes: [ { name: "age", @@ -1650,6 +1656,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1690,6 +1697,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1751,6 +1759,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2102,6 +2111,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2325,6 +2335,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, attributes: [ { name: "age", @@ -2372,6 +2383,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2412,6 +2424,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2606,6 +2619,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, goals: [ { achievedAt: 1611141535729, @@ -2764,6 +2778,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, goals: [ { name: "goal1", @@ -2849,6 +2864,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2970,6 +2986,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, goals: [ { name: "goal1", @@ -3023,6 +3040,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, goals: [ { achievedAt: 1611141535729, @@ -3083,6 +3101,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -3160,6 +3179,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, goals: [ { name: "goal2", @@ -3342,6 +3362,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -3392,6 +3413,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -3591,6 +3613,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -3652,6 +3675,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 2, @@ -3699,6 +3723,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 3, @@ -3768,6 +3793,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, diff --git a/src/client.ts b/src/client.ts index 3a48d61..09ad50d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -111,6 +111,7 @@ export default class Client { units: params.units, hashed: params.hashed, publishedAt: params.publishedAt || Date.now(), + sdkVersion: params.sdkVersion, }; if (Array.isArray(params.goals) && params.goals.length > 0) { diff --git a/src/context.ts b/src/context.ts index 71e6fae..d5036f7 100644 --- a/src/context.ts +++ b/src/context.ts @@ -806,6 +806,7 @@ export default class Context { uid: this._unitHash(entry[0]), })), hashed: true, + sdkVersion: SDK_VERSION, }; if (this._goals.length > 0) { diff --git a/src/publisher.ts b/src/publisher.ts index 8e0cc64..8375169 100644 --- a/src/publisher.ts +++ b/src/publisher.ts @@ -6,6 +6,7 @@ export type PublishParams = { units: Unit[]; publishedAt: number; hashed: boolean; + sdkVersion?: string; attributes?: Attribute[]; goals?: Goal[]; exposures?: Exposure[]; From 74cfcf419b03b70e9af1a5beeb8cff5a50745dc2 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Tue, 24 Mar 2026 14:33:55 +0000 Subject: [PATCH 3/7] docs: document application object format with version --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index b6ead13..3449f9c 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,16 @@ const sdk = new absmartly.SDK({ }); ``` +The `application` option can also be an object with `name` and `version` to track which version of your application is generating events: +```javascript +const sdk = new absmartly.SDK({ + endpoint: 'https://sandbox.absmartly.io/v1', + apiKey: process.env.ABSMARTLY_API_KEY, + environment: process.env.NODE_ENV, + application: { name: 'website', version: 3 }, +}); +``` + #### Creating a new Context with raw promises ```javascript // define a new context request From 95c9ed6bfe185d1b12338eddc570ab021db33ae5 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Tue, 24 Mar 2026 15:46:23 +0000 Subject: [PATCH 4/7] fix: address PR review comments - Fix "opt-in" grammar to "opt in" (verb form) - Clarify "publish request payload" wording - Remove unused systemAttributes test constant - Use mockReturnValueOnce to prevent mock leakage - Source SDK_VERSION from package.json instead of hardcoding --- README.md | 4 ++-- src/__tests__/context.test.js | 9 +-------- src/version.ts | 4 +++- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3449f9c..31b2ccf 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ context.attributes({ ``` #### Including system attributes -You can opt-in to automatically include system attributes (SDK name, SDK version, application, environment, and application version) in every publish payload. These are sent as context attributes and can be useful for debugging and filtering in the Web Console. +You can opt in to automatically include system attributes (SDK name, SDK version, application, environment, and application version) in every publish payload. These are sent as context attributes and can be useful for debugging and filtering in the Web Console. To enable this, set the `includeSystemAttributes` option to `true` when creating the context: ```javascript @@ -157,7 +157,7 @@ const context = sdk.createContext(request, { }); ``` -When enabled, the following attributes are automatically prepended to the publish payload: +When enabled, the following attributes are automatically prepended to the publish request payload: | Attribute | Description | |:--- |---| diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js index 41de5f2..e33d504 100644 --- a/src/__tests__/context.test.js +++ b/src/__tests__/context.test.js @@ -478,12 +478,6 @@ describe("Context", () => { client.getApplication.mockReturnValue({ name: "website", version: 0 }); client.getEnvironment.mockReturnValue("production"); - const systemAttributes = [ - { name: "sdk_version", value: expect.any(String), setAt: expect.any(Number) }, - { name: "application", value: "website", setAt: expect.any(Number) }, - { name: "environment", value: "production", setAt: expect.any(Number) }, - ]; - const contextOptions = { publishDelay: -1, refreshPeriod: 0, @@ -4054,7 +4048,7 @@ describe("Context", () => { }); it("should include app_version when application version is set", (done) => { - client.getApplication.mockReturnValue({ name: "website", version: 3 }); + client.getApplication.mockReturnValueOnce({ name: "website", version: 3 }); const optionsWithSystemAttrs = { publishDelay: -1, @@ -4075,7 +4069,6 @@ describe("Context", () => { expect(appVersionAttr).toBeDefined(); expect(appVersionAttr.value).toEqual(3); - client.getApplication.mockReturnValue({ name: "website", version: 0 }); done(); }); }); diff --git a/src/version.ts b/src/version.ts index 9bcd66f..541897e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1,3 @@ -export const SDK_VERSION = "1.13.4"; +import pkg from "../package.json"; + +export const SDK_VERSION: string = pkg.version; From 0968f4632de15cce363f0417b224c3e9d1e77f38 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Tue, 24 Mar 2026 15:54:23 +0000 Subject: [PATCH 5/7] refactor: address deep PR review findings - Make sdkVersion required in PublishParams (matches actual usage) - Add explicit return types to Client getters with post-construction types - Extract _buildAttributes() private method from _flush() - Wrap request construction in try-catch to prevent silent data loss - Fix sdkVersion indentation alignment in test assertions - Add client tests: sdkVersion in publish body, getter methods - Strictly assert sdk_version attribute value equals SDK_VERSION - Remove unused getApplicationName/getApplicationVersion imports --- src/__tests__/client.test.js | 50 +++++++++++ src/__tests__/context.test.js | 40 ++++----- src/client.ts | 10 +-- src/context.ts | 151 ++++++++++++++++++---------------- src/publisher.ts | 2 +- 5 files changed, 158 insertions(+), 95 deletions(-) diff --git a/src/__tests__/client.test.js b/src/__tests__/client.test.js index 5d0d3b6..52dd8bd 100644 --- a/src/__tests__/client.test.js +++ b/src/__tests__/client.test.js @@ -4,6 +4,7 @@ import fetch from "../fetch"; // eslint-disable-next-line no-shadow import { AbortController } from "../abort"; import { AbortError, RetryError, TimeoutError } from "../errors"; //eslint-disable-line no-shadow +import { SDK_VERSION } from "../version"; jest.mock("../fetch"); @@ -830,6 +831,7 @@ describe("Client", () => { .publish({ units, publishedAt, + sdkVersion: SDK_VERSION, goals, exposures, attributes, @@ -850,6 +852,7 @@ describe("Client", () => { body: JSON.stringify({ units, publishedAt, + sdkVersion: SDK_VERSION, goals, exposures, attributes, @@ -972,6 +975,53 @@ describe("Client", () => { }); }); + it("publish() should include sdkVersion in body", (done) => { + fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); + + const client = new Client(clientOptions); + + client + .publish({ + units, + publishedAt, + sdkVersion: "1.2.3", + goals: [], + exposures: [], + }) + .then(() => { + const body = JSON.parse(fetch.mock.calls[0][1].body); + expect(body.sdkVersion).toEqual("1.2.3"); + + done(); + }); + }); + + it("getAgent() should return the agent", () => { + const client = new Client(clientOptions); + expect(client.getAgent()).toEqual(agent); + }); + + it("getAgent() should return default agent when not specified", () => { + const { agent: _, ...optionsWithoutAgent } = clientOptions; + const client = new Client(optionsWithoutAgent); + expect(client.getAgent()).toEqual("javascript-client"); + }); + + it("getApplication() should return normalized application object", () => { + const client = new Client(clientOptions); + expect(client.getApplication()).toEqual({ name: "test_app", version: 1000000 }); + }); + + it("getApplication() should normalize string application to object", () => { + const client = new Client({ ...clientOptions, application: "website" }); + expect(client.getApplication()).toEqual({ name: "website", version: 0 }); + }); + + it("getEnvironment() should return the environment", () => { + const client = new Client(clientOptions); + expect(client.getEnvironment()).toEqual(environment); + }); + it("publish() should not have the keepalive flag if specified", (done) => { fetch.mockResolvedValueOnce(responseMock(200, "OK", defaultMockResponse)); diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js index e33d504..e673d97 100644 --- a/src/__tests__/context.test.js +++ b/src/__tests__/context.test.js @@ -1397,7 +1397,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1560,7 +1560,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 0, @@ -1602,7 +1602,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, attributes: [ { name: "age", @@ -1650,7 +1650,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1691,7 +1691,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -1753,7 +1753,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2105,7 +2105,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2329,7 +2329,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, attributes: [ { name: "age", @@ -2377,7 +2377,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2418,7 +2418,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2613,7 +2613,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, goals: [ { achievedAt: 1611141535729, @@ -2772,7 +2772,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, goals: [ { name: "goal1", @@ -2858,7 +2858,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -2980,7 +2980,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, goals: [ { name: "goal1", @@ -3034,7 +3034,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, goals: [ { achievedAt: 1611141535729, @@ -3356,7 +3356,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -3407,7 +3407,7 @@ describe("Context", () => { publishedAt: 1611141535829, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 1, @@ -3669,7 +3669,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 2, @@ -3717,7 +3717,7 @@ describe("Context", () => { publishedAt: 1611141535729, units: publishUnits, hashed: true, - sdkVersion: SDK_VERSION, + sdkVersion: SDK_VERSION, exposures: [ { id: 3, @@ -4004,7 +4004,7 @@ describe("Context", () => { expect(sdkNameAttr.setAt).toEqual(expect.any(Number)); expect(sdkVersionAttr).toBeDefined(); - expect(sdkVersionAttr.value).toEqual(expect.any(String)); + expect(sdkVersionAttr.value).toEqual(SDK_VERSION); expect(sdkVersionAttr.setAt).toEqual(expect.any(Number)); expect(applicationAttr).toBeDefined(); diff --git a/src/client.ts b/src/client.ts index 09ad50d..a5c0acb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -288,15 +288,15 @@ export default class Client { }); } - getAgent() { - return this._opts.agent; + getAgent(): string { + return this._opts.agent as string; } - getApplication() { - return this._opts.application; + getApplication(): { name: string; version: number } { + return this._opts.application as { name: string; version: number }; } - getEnvironment() { + getEnvironment(): string { return this._opts.environment; } diff --git a/src/context.ts b/src/context.ts index d5036f7..682a9ab 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,4 +1,4 @@ -import { arrayEqualsShallow, getApplicationName, getApplicationVersion, hashUnit, isObject, isPromise } from "./utils"; +import { arrayEqualsShallow, hashUnit, isObject, isPromise } from "./utils"; import { VariantAssigner } from "./assigner"; import { AudienceMatcher } from "./matcher"; import { insertUniqueSorted } from "./algorithm"; @@ -787,6 +787,31 @@ export default class Context { } } + private _buildAttributes(): Attribute[] { + const allAttributes: Attribute[] = []; + + if (this._opts.includeSystemAttributes === true) { + const client = this._sdk.getClient(); + const app = client.getApplication(); + const now = Date.now(); + allAttributes.push( + { name: "sdk_name", value: client.getAgent(), setAt: now }, + { name: "sdk_version", value: SDK_VERSION, setAt: now }, + { name: "application", value: app.name, setAt: now }, + { name: "environment", value: client.getEnvironment(), setAt: now } + ); + if (app.version > 0) { + allAttributes.push({ name: "app_version", value: app.version, setAt: now }); + } + } + + for (const x of this._attrs) { + allAttributes.push({ name: x.name, value: x.value, setAt: x.setAt }); + } + + return allAttributes; + } + private _flush(callback?: (error?: Error) => void, requestOptions?: ClientRequestOptions) { if (this._publishTimeout !== undefined) { clearTimeout(this._publishTimeout); @@ -799,81 +824,69 @@ export default class Context { } } else { if (!this._failed) { - const request: PublishParams = { - publishedAt: Date.now(), - units: Object.entries(this._units).map((entry) => ({ - type: entry[0], - uid: this._unitHash(entry[0]), - })), - hashed: true, - sdkVersion: SDK_VERSION, - }; - - if (this._goals.length > 0) { - request.goals = this._goals.map((x) => ({ - name: x.name, - achievedAt: x.achievedAt, - properties: x.properties, - })); - } - - if (this._exposures.length > 0) { - request.exposures = this._exposures.map((x) => ({ - id: x.id, - name: x.name, - unit: x.unit, - exposedAt: x.exposedAt, - variant: x.variant, - assigned: x.assigned, - eligible: x.eligible, - overridden: x.overridden, - fullOn: x.fullOn, - custom: x.custom, - audienceMismatch: x.audienceMismatch, - })); - } + try { + const request: PublishParams = { + publishedAt: Date.now(), + units: Object.entries(this._units).map((entry) => ({ + type: entry[0], + uid: this._unitHash(entry[0]), + })), + hashed: true, + sdkVersion: SDK_VERSION, + }; + + if (this._goals.length > 0) { + request.goals = this._goals.map((x) => ({ + name: x.name, + achievedAt: x.achievedAt, + properties: x.properties, + })); + } - const allAttributes: Attribute[] = []; - - if (this._opts.includeSystemAttributes === true) { - const client = this._sdk.getClient(); - const now = Date.now(); - const appVersion = getApplicationVersion(client.getApplication()); - allAttributes.push( - { name: "sdk_name", value: client.getAgent(), setAt: now }, - { name: "sdk_version", value: SDK_VERSION, setAt: now }, - { name: "application", value: getApplicationName(client.getApplication()), setAt: now }, - { name: "environment", value: client.getEnvironment(), setAt: now } - ); - if (appVersion > 0) { - allAttributes.push({ name: "app_version", value: appVersion, setAt: now }); + if (this._exposures.length > 0) { + request.exposures = this._exposures.map((x) => ({ + id: x.id, + name: x.name, + unit: x.unit, + exposedAt: x.exposedAt, + variant: x.variant, + assigned: x.assigned, + eligible: x.eligible, + overridden: x.overridden, + fullOn: x.fullOn, + custom: x.custom, + audienceMismatch: x.audienceMismatch, + })); } - } - for (const x of this._attrs) { - allAttributes.push({ name: x.name, value: x.value, setAt: x.setAt }); - } + const allAttributes = this._buildAttributes(); + if (allAttributes.length > 0) { + request.attributes = allAttributes; + } - if (allAttributes.length > 0) { - request.attributes = allAttributes; - } + this._publisher + .publish(request, this._sdk, this, requestOptions) + .then(() => { + this._logEvent("publish", request); - this._publisher - .publish(request, this._sdk, this, requestOptions) - .then(() => { - this._logEvent("publish", request); + if (typeof callback === "function") { + callback(); + } + }) + .catch((e: Error) => { + this._logError(e); - if (typeof callback === "function") { - callback(); - } - }) - .catch((e: Error) => { - this._logError(e); + if (typeof callback === "function") { + callback(e); + } + }); + } catch (e) { + this._logError(e as Error); - if (typeof callback === "function") { - callback(e); - } - }); + if (typeof callback === "function") { + callback(e as Error); + } + } } else { if (typeof callback === "function") { callback(); diff --git a/src/publisher.ts b/src/publisher.ts index 8375169..79c5f14 100644 --- a/src/publisher.ts +++ b/src/publisher.ts @@ -6,7 +6,7 @@ export type PublishParams = { units: Unit[]; publishedAt: number; hashed: boolean; - sdkVersion?: string; + sdkVersion: string; attributes?: Attribute[]; goals?: Goal[]; exposures?: Exposure[]; From 1ea73820a0981549805661265cea20ae4bb1bad2 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Tue, 24 Mar 2026 19:55:06 +0000 Subject: [PATCH 6/7] refactor: address human review feedback - Replace package.json import in version.ts with build-time generation via scripts/generate-version.js to avoid bundling package.json - Support string | number for application version (semver support) - Update ClientOptions, utils, Client getters, and context system attributes to handle string versions - Add tests for semver string version in both client and context - Update README and docs with semver version examples --- README.md | 4 +-- package.json | 5 ++-- scripts/generate-version.js | 7 +++++ src/__tests__/client.test.js | 9 ++++++ src/__tests__/context.test.js | 55 +++++++++++++++++++++++++++++++++++ src/client.ts | 6 ++-- src/context.ts | 2 +- src/utils.ts | 4 +-- src/version.ts | 4 +-- 9 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 scripts/generate-version.js diff --git a/README.md b/README.md index 31b2ccf..be4a960 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,13 @@ const sdk = new absmartly.SDK({ }); ``` -The `application` option can also be an object with `name` and `version` to track which version of your application is generating events: +The `application` option can also be an object with `name` and `version` to track which version of your application is generating events. The version can be a number or a semver string: ```javascript const sdk = new absmartly.SDK({ endpoint: 'https://sandbox.absmartly.io/v1', apiKey: process.env.ABSMARTLY_API_KEY, environment: process.env.NODE_ENV, - application: { name: 'website', version: 3 }, + application: { name: 'website', version: '1.2.3' }, }); ``` diff --git a/package.json b/package.json index 07ea94e..380e9ad 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,13 @@ "build-browser": "TARGET=browser webpack --progress --config webpack.config.js && TARGET=browser NODE_ENV=production webpack --progress --config webpack.config.js", "build-cjs": "TARGET=cjs babel js --delete-dir-on-start --ignore 'browser.js' -d lib", "build-es": "TARGET=es babel js --delete-dir-on-start --ignore 'browser.js' -d es", - "build": "npm run -s format && npm run -s lint && npm run -s compile && npm run -s test && npm run -s build-es && npm run -s build-cjs && npm run -s build-browser", + "build": "npm run -s format && npm run -s lint && npm run -s generate-version && npm run -s compile && npm run -s test && npm run -s build-es && npm run -s build-cjs && npm run -s build-browser", "lint": "eslint -f stylish 'src/**/*.{js,mjs,jsx,ts,mts,tsx}'", "format": "prettier --write '**/*.{js,mjs,jsx,json,ts,mts,tsx}'", "test": "jest --coverage", "prepack": "npm run -s build", - "compile": "tsc" + "compile": "tsc", + "generate-version": "node scripts/generate-version.js" }, "dependencies": { "node-fetch": "^2.6.7", diff --git a/scripts/generate-version.js b/scripts/generate-version.js new file mode 100644 index 0000000..8824f3c --- /dev/null +++ b/scripts/generate-version.js @@ -0,0 +1,7 @@ +const fs = require("fs"); +const path = require("path"); + +const pkg = require(path.resolve(__dirname, "../package.json")); +const versionFile = path.resolve(__dirname, "../src/version.ts"); + +fs.writeFileSync(versionFile, `export const SDK_VERSION = "${pkg.version}";\n`); diff --git a/src/__tests__/client.test.js b/src/__tests__/client.test.js index 52dd8bd..c262a51 100644 --- a/src/__tests__/client.test.js +++ b/src/__tests__/client.test.js @@ -875,6 +875,7 @@ describe("Client", () => { .publish({ units, publishedAt, + sdkVersion: SDK_VERSION, goals: [], exposures: [], }) @@ -894,6 +895,7 @@ describe("Client", () => { body: JSON.stringify({ units, publishedAt, + sdkVersion: SDK_VERSION, }), signal: expect.any(Object), }); @@ -913,6 +915,7 @@ describe("Client", () => { client .publish({ units, + sdkVersion: SDK_VERSION, goals: [], exposures: [], }) @@ -932,6 +935,7 @@ describe("Client", () => { body: JSON.stringify({ units, publishedAt: publishedAt + 100, + sdkVersion: SDK_VERSION, }), signal: expect.any(Object), }); @@ -1017,6 +1021,11 @@ describe("Client", () => { expect(client.getApplication()).toEqual({ name: "website", version: 0 }); }); + it("getApplication() should accept semver string version", () => { + const client = new Client({ ...clientOptions, application: { name: "website", version: "1.2.3" } }); + expect(client.getApplication()).toEqual({ name: "website", version: "1.2.3" }); + }); + it("getEnvironment() should return the environment", () => { const client = new Client(clientOptions); expect(client.getEnvironment()).toEqual(environment); diff --git a/src/__tests__/context.test.js b/src/__tests__/context.test.js index e673d97..bac768c 100644 --- a/src/__tests__/context.test.js +++ b/src/__tests__/context.test.js @@ -4096,6 +4096,61 @@ describe("Context", () => { }); }); + it("should include app_version when application version is a semver string", (done) => { + client.getApplication.mockReturnValueOnce({ name: "website", version: "1.2.3" }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); + expect(appVersionAttr).toBeDefined(); + expect(appVersionAttr.value).toEqual("1.2.3"); + + done(); + }); + }); + + it("should include app_version with application as plain string", (done) => { + client.getApplication.mockReturnValueOnce({ name: "website", version: 0 }); + + const optionsWithSystemAttrs = { + publishDelay: -1, + refreshPeriod: 0, + includeSystemAttributes: true, + }; + + const context = new Context(sdk, optionsWithSystemAttrs, contextParams, getContextResponse); + publisher.publish.mockReturnValue(Promise.resolve()); + + context.treatment("exp_test_ab"); + + context.publish().then(() => { + const call = publisher.publish.mock.calls[0]; + const request = call[0]; + + const applicationAttr = request.attributes.find((a) => a.name === "application"); + expect(applicationAttr).toBeDefined(); + expect(applicationAttr.value).toEqual("website"); + + const appVersionAttr = request.attributes.find((a) => a.name === "app_version"); + expect(appVersionAttr).toBeUndefined(); + + done(); + }); + }); + it("should only include user attributes when includeSystemAttributes is not set", (done) => { const defaultOptions = { publishDelay: -1, diff --git a/src/client.ts b/src/client.ts index a5c0acb..da2f6d0 100644 --- a/src/client.ts +++ b/src/client.ts @@ -30,7 +30,7 @@ export type ClientRequestOptions = { export type ClientOptions = { agent?: string; apiKey: string; - application: string | { name: string; version: number }; + application: string | { name: string; version: number | string }; endpoint: string; environment: string; retries?: number; @@ -292,8 +292,8 @@ export default class Client { return this._opts.agent as string; } - getApplication(): { name: string; version: number } { - return this._opts.application as { name: string; version: number }; + getApplication(): { name: string; version: number | string } { + return this._opts.application as { name: string; version: number | string }; } getEnvironment(): string { diff --git a/src/context.ts b/src/context.ts index 682a9ab..c862b3d 100644 --- a/src/context.ts +++ b/src/context.ts @@ -800,7 +800,7 @@ export default class Context { { name: "application", value: app.name, setAt: now }, { name: "environment", value: client.getEnvironment(), setAt: now } ); - if (app.version > 0) { + if ((typeof app.version === "string" && app.version.length > 0) || (typeof app.version === "number" && app.version > 0)) { allAttributes.push({ name: "app_version", value: app.version, setAt: now }); } } diff --git a/src/utils.ts b/src/utils.ts index b2eaf42..529d486 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,9 @@ import { md5 } from "./md5"; -export const getApplicationName = (app: string | { name: string; version: number }): string => +export const getApplicationName = (app: string | { name: string; version: number | string }): string => typeof app !== "string" ? app.name : app; -export const getApplicationVersion = (app: string | { name: string; version: number }): number => +export const getApplicationVersion = (app: string | { name: string; version: number | string }): number | string => typeof app !== "string" ? app.version : 0; function isBrowser() { diff --git a/src/version.ts b/src/version.ts index 541897e..9bcd66f 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,3 +1 @@ -import pkg from "../package.json"; - -export const SDK_VERSION: string = pkg.version; +export const SDK_VERSION = "1.13.4"; From 219e4a6ecbd04bb7ea8c6a0ee0b40164c7a9fad7 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Wed, 25 Mar 2026 13:51:50 +0000 Subject: [PATCH 7/7] refactor: use NormalizedClientOptions to eliminate type casts - Introduce NormalizedClientOptions where application is always an object after constructor validation/normalization - Remove 'as' casts from Client getters since internal type is correct - Remove getApplicationName/getApplicationVersion usage from client (application is guaranteed to be an object) - Replace 1==1 getAgent test with meaningful custom agent test --- src/__tests__/client.test.js | 10 ++++---- src/client.ts | 46 +++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/__tests__/client.test.js b/src/__tests__/client.test.js index c262a51..7834ff4 100644 --- a/src/__tests__/client.test.js +++ b/src/__tests__/client.test.js @@ -1000,17 +1000,17 @@ describe("Client", () => { }); }); - it("getAgent() should return the agent", () => { - const client = new Client(clientOptions); - expect(client.getAgent()).toEqual(agent); - }); - it("getAgent() should return default agent when not specified", () => { const { agent: _, ...optionsWithoutAgent } = clientOptions; const client = new Client(optionsWithoutAgent); expect(client.getAgent()).toEqual("javascript-client"); }); + it("getAgent() should return custom agent when specified", () => { + const client = new Client({ ...clientOptions, agent: "custom-sdk" }); + expect(client.getAgent()).toEqual("custom-sdk"); + }); + it("getApplication() should return normalized application object", () => { const client = new Client(clientOptions); expect(client.getApplication()).toEqual({ name: "test_app", version: 1000000 }); diff --git a/src/client.ts b/src/client.ts index da2f6d0..3f934bb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4,7 +4,6 @@ import { AbortController } from "./abort"; // eslint-disable-next-line no-shadow import { AbortError, RetryError, TimeoutError } from "./errors"; -import { getApplicationName, getApplicationVersion } from "./utils"; import { AbortSignal as ABsmartlyAbortSignal } from "./abort-controller-shim"; import { ContextOptions, ContextParams } from "./context"; import { PublishParams } from "./publisher"; @@ -27,10 +26,12 @@ export type ClientRequestOptions = { timeout?: number; }; +export type ApplicationObject = { name: string; version: number | string }; + export type ClientOptions = { agent?: string; apiKey: string; - application: string | { name: string; version: number | string }; + application: string | ApplicationObject; endpoint: string; environment: string; retries?: number; @@ -38,18 +39,18 @@ export type ClientOptions = { keepalive?: boolean; }; +type NormalizedClientOptions = Omit, "application"> & { + application: ApplicationObject; +}; + export default class Client { - private readonly _opts: ClientOptions; + private readonly _opts: NormalizedClientOptions; private readonly _delay: number; constructor(opts: ClientOptions) { - this._opts = Object.assign( + const merged: Record = Object.assign( { agent: "javascript-client", - apiKey: undefined, - application: undefined, - endpoint: undefined, - environment: undefined, retries: 5, timeout: 3000, keepalive: true, @@ -57,12 +58,12 @@ export default class Client { opts ); - for (const key of ["agent", "application", "apiKey", "endpoint", "environment"] as const) { - if (key in this._opts && this._opts[key] !== undefined) { - const value = this._opts[key]; - if (typeof value !== "string" || value.length === 0) { + for (const key of ["agent", "application", "apiKey", "endpoint", "environment"]) { + if (key in merged && merged[key] !== undefined) { + const value = merged[key]; + if (typeof value !== "string" || (value as string).length === 0) { if (key === "application") { - if (value !== null && typeof value === "object" && "name" in value) { + if (value !== null && typeof value === "object" && "name" in (value as object)) { continue; } } @@ -73,13 +74,14 @@ export default class Client { } } - if (typeof this._opts.application === "string") { - this._opts.application = { - name: this._opts.application, + if (typeof merged.application === "string") { + merged.application = { + name: merged.application, version: 0, }; } + this._opts = merged as unknown as NormalizedClientOptions; this._delay = 50; } @@ -88,7 +90,7 @@ export default class Client { ...options, path: "/context", query: { - application: getApplicationName(this._opts.application), + application: this._opts.application.name, environment: this._opts.environment, }, }); @@ -161,8 +163,8 @@ export default class Client { "X-API-Key": this._opts.apiKey, "X-Agent": this._opts.agent, "X-Environment": this._opts.environment, - "X-Application": getApplicationName(this._opts.application), - "X-Application-Version": getApplicationVersion(this._opts.application), + "X-Application": this._opts.application.name, + "X-Application-Version": this._opts.application.version, }; } @@ -289,11 +291,11 @@ export default class Client { } getAgent(): string { - return this._opts.agent as string; + return this._opts.agent; } - getApplication(): { name: string; version: number | string } { - return this._opts.application as { name: string; version: number | string }; + getApplication(): ApplicationObject { + return this._opts.application; } getEnvironment(): string {