From b71c44ae61f6c35cadc6523b918e1a574e32bc23 Mon Sep 17 00:00:00 2001 From: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:27:26 +0530 Subject: [PATCH 01/15] feat: sanitize input data (#1902) * feat: sanitize input data * feat: sanitize all user inputs * fix: exclude sensitive data auth token from bugsnag metadata * chore: address ai bot review comments * refactor: move utilities to the correct location and add tests * chore: revert unnecessary changes --- .eslintrc.json | 3 +- .../__tests__/utilities/json.test.ts | 351 +++++++++++------- .../integrations/integration_cname.js | 2 +- .../src/constants/logMessages.ts | 11 +- .../src/utilities/checks.ts | 8 + .../src/utilities/errors.ts | 4 +- .../analytics-js-common/src/utilities/json.ts | 116 ++++-- .../src/utilities/object.ts | 5 +- .../__tests__/cookieUtilities.test.ts | 10 - .../src/cookiesUtilities.ts | 4 +- .../__tests__/bugsnag/utils.test.ts | 7 - .../__tests__/errorReporting/utils.test.ts | 7 - .../__tests__/utilities/queue.test.ts | 108 ------ .../__tests__/xhrQueue/utilities.test.ts | 56 --- .../src/beaconQueue/utilities.ts | 2 +- .../src/bugsnag/constants.ts | 1 + .../analytics-js-plugins/src/bugsnag/utils.ts | 4 +- .../src/errorReporting/constants.ts | 1 + .../src/errorReporting/event/event.ts | 5 +- .../src/errorReporting/utils.ts | 8 +- .../src/utilities/eventsDelivery.ts | 45 +-- .../src/xhrQueue/index.ts | 8 +- .../src/xhrQueue/utilities.ts | 19 +- .../configManager/ConfigManager.test.ts | 17 - .../components/configManager/validate.test.ts | 28 -- .../components/core/Analytics.test.ts | 75 +++- .../UserSessionManager.test.ts | 8 +- .../services/HttpClient/HttpClient.test.ts | 22 -- .../analytics-js/src/app/RudderAnalytics.ts | 79 ++-- .../components/configManager/ConfigManager.ts | 4 +- .../components/configManager/util/validate.ts | 27 +- .../src/components/core/Analytics.ts | 35 +- .../src/components/core/utilities.ts | 9 +- .../userSessionManager/UserSessionManager.ts | 47 +-- .../analytics-js/src/constants/logMessages.ts | 14 +- .../src/services/ErrorHandler/processError.ts | 4 +- .../src/services/HttpClient/HttpClient.ts | 3 +- .../HttpClient/xhr/xhrRequestHandler.ts | 6 +- .../src/services/StoreManager/Store.ts | 9 +- 39 files changed, 553 insertions(+), 619 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 6ef4c561ba..f92368f4ec 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -138,7 +138,8 @@ } ] } - ] + ], + "sonarjs/todo-tag": "warn" } }, { diff --git a/packages/analytics-js-common/__tests__/utilities/json.test.ts b/packages/analytics-js-common/__tests__/utilities/json.test.ts index 4515274032..2de316cb25 100644 --- a/packages/analytics-js-common/__tests__/utilities/json.test.ts +++ b/packages/analytics-js-common/__tests__/utilities/json.test.ts @@ -1,155 +1,250 @@ -import { clone } from 'ramda'; -import { stringifyWithoutCircular } from '../../src/utilities/json'; - -const identifyTraitsPayloadMock: Record = { - firstName: 'Dummy Name', - phone: '1234567890', - email: 'dummy@email.com', - custom_flavor: 'chocolate', - custom_date: new Date(2022, 1, 21, 0, 0, 0), - address: [ - { - label: 'office', - city: 'Brussels', - country: 'Belgium', - }, - { - label: 'home', - city: 'Kolkata', - country: 'India', - nested: { - type: 'flat', - rooms: [ - { - name: 'kitchen', - size: 'small', - }, - { - // eslint-disable-next-line sonarjs/no-duplicate-string - name: 'living room', - size: 'large', - }, - { - name: 'bedroom', - size: 'large', - }, - ], - }, - }, - { - label: 'work', - city: 'Kolkata', - country: 'India', - }, - ], - stringArray: ['string1', 'string2', 'string3'], - numberArray: [1, 2, 3], -}; +import { stringifyData, getSanitizedValue } from '../../src/utilities/json'; const circularReferenceNotice = '[Circular Reference]'; +const bigIntNotice = '[BigInt]'; describe('Common Utils - JSON', () => { - describe('stringifyWithoutCircular', () => { - it('should stringify json with circular references', () => { - const objWithCircular = clone(identifyTraitsPayloadMock); - objWithCircular.myself = objWithCircular; + describe('stringifyData', () => { + it('should stringify json excluding null values', () => { + // Define an object with null values in multiple levels along with other data types + const obj = { + key1: 'value1', + key2: null, + key3: { + key4: null, + key5: 'value5', + key10: undefined, + key6: { + key7: null, + key8: 'value8', + key9: undefined, + key11: [1, 2, null, 3], + }, + }, + }; - const json = stringifyWithoutCircular(objWithCircular); - expect(json).toContain(circularReferenceNotice); + expect(stringifyData(obj)).toBe( + '{"key1":"value1","key3":{"key5":"value5","key6":{"key8":"value8","key11":[1,2,null,3]}}}', + ); }); - it('should stringify json with circular references and exclude null values', () => { - const objWithCircular = clone(identifyTraitsPayloadMock); - objWithCircular.myself = objWithCircular; - objWithCircular.keyToExclude = null; - objWithCircular.keyToNotExclude = ''; + it('should stringify json without excluding null values', () => { + // Define an object with null values in multiple levels along with other data types + const obj = { + key1: 'value1', + key2: null, + key3: { + key4: null, + key5: 'value5', + key6: { + key7: null, + key8: 'value8', + }, + }, + }; - const json = stringifyWithoutCircular(objWithCircular, true); - expect(json).toContain(circularReferenceNotice); - expect(json).not.toContain('keyToExclude'); - expect(json).toContain('keyToNotExclude'); + expect(stringifyData(obj, false)).toBe( + '{"key1":"value1","key2":null,"key3":{"key4":null,"key5":"value5","key6":{"key7":null,"key8":"value8"}}}', + ); }); - it('should stringify json with out circular references', () => { - const objWithoutCircular = clone(identifyTraitsPayloadMock); - objWithoutCircular.myself = {}; + it('should stringify json after excluding certain keys', () => { + // Define an object with null values in multiple levels along with other data types + const obj = { + key1: 'value1', + key2: null, + key3: { + key4: null, + key5: 'value5', + key6: { + key7: null, + key8: 'value8', + }, + }, + }; - const json = stringifyWithoutCircular(objWithoutCircular); - expect(json).not.toContain(circularReferenceNotice); - }); + const keysToExclude = ['key1', 'key7']; - it('should stringify json with out circular references and reused objects', () => { - const objWithoutCircular = clone(identifyTraitsPayloadMock); - const reusableArray = [1, 2, 3]; - const reusableObject = { dummy: 'val' }; - objWithoutCircular.reused = reusableArray; - objWithoutCircular.reusedAgain = [1, 2, reusableArray]; - objWithoutCircular.reusedObj = reusableObject; - objWithoutCircular.reusedObjAgain = { reused: reusableObject }; - objWithoutCircular.reusedObjAgainWithItself = { reused: reusableObject }; - - const json = stringifyWithoutCircular(objWithoutCircular); - expect(json).not.toContain(circularReferenceNotice); + expect(stringifyData(obj, true, keysToExclude)).toBe( + '{"key3":{"key5":"value5","key6":{"key8":"value8"}}}', + ); + + expect(stringifyData(obj, false, keysToExclude)).toBe( + '{"key2":null,"key3":{"key4":null,"key5":"value5","key6":{"key8":"value8"}}}', + ); }); + }); - it('should stringify json with circular references for nested circular objects', () => { - const objWithoutCircular = clone(identifyTraitsPayloadMock); - const reusableObject = { dummy: 'val' }; - const objWithCircular = clone(reusableObject); - objWithCircular.myself = objWithCircular; - objWithoutCircular.reusedObjAgainWithItself = { reused: reusableObject }; - objWithoutCircular.objWithCircular = objWithCircular; + describe('getSanitizedValue', () => { + const mockLogger = { + warn: jest.fn(), + }; + + it('should sanitize json without excluding null and undefined values', () => { + const obj = { + a: 1, + b: null, + c: 'value', + d: undefined, + i: () => {}, + e: { + f: 2, + g: null, + h: 'value', + i: undefined, + j: { + k: 3, + l: null, + m: 'value', + n: [1, 2, 3], + o: [1, 2, 3, new Date()], + s: () => {}, + }, + }, + }; - const json = stringifyWithoutCircular(objWithoutCircular); - expect(json).toContain(circularReferenceNotice); + expect(getSanitizedValue(obj)).toEqual(obj); }); - it('should stringify json for all input types', () => { - const array = [1, 2, 3]; - const number = 1; - const string = ''; - const object = {}; - const date = new Date(2023, 1, 20, 0, 0, 0); + it('should sanitize json after replacing BigInt and circular references', () => { + const obj = { + a: BigInt(1), + b: undefined, + c: 'value', + d: { + e: BigInt(2), + f: undefined, + g: 'value', + h: { + i: BigInt(3), + j: undefined, + k: 'value', + }, + }, + }; - const arrayJson = stringifyWithoutCircular(array); - const numberJson = stringifyWithoutCircular(number); - const stringJson = stringifyWithoutCircular(string); - const objectJson = stringifyWithoutCircular(object); - const dateJson = stringifyWithoutCircular(date); - const nullJson = stringifyWithoutCircular(null); - const undefinedJson = stringifyWithoutCircular(undefined); - - expect(arrayJson).toBe('[1,2,3]'); - expect(numberJson).toBe('1'); - expect(stringJson).toBe('""'); - expect(objectJson).toBe('{}'); - expect(dateJson).toBe('"2023-02-19T18:30:00.000Z"'); - expect(nullJson).toBe('null'); - expect(undefinedJson).toBe(undefined); - }); + obj.myself = obj; + obj.d.myself2 = obj.d; + obj.d.h.myself3 = obj.d; + + expect(getSanitizedValue(obj, mockLogger)).toEqual({ + a: bigIntNotice, + c: 'value', + b: undefined, + myself: circularReferenceNotice, + d: { + e: bigIntNotice, + g: 'value', + f: undefined, + myself2: circularReferenceNotice, + h: { + i: bigIntNotice, + k: 'value', + j: undefined, + myself3: circularReferenceNotice, + }, + }, + }); + + expect(mockLogger.warn).toHaveBeenCalledTimes(6); + + expect(mockLogger.warn).toHaveBeenNthCalledWith( + 1, + 'JSON:: A bad data (like circular reference, BigInt) has been detected in the object and the property "a" has been dropped from the output.', + ); + + expect(mockLogger.warn).toHaveBeenNthCalledWith( + 2, + 'JSON:: A bad data (like circular reference, BigInt) has been detected in the object and the property "e" has been dropped from the output.', + ); - it('should stringify json after removing the exclude keys', () => { - const objWithoutCircular = clone(identifyTraitsPayloadMock); + expect(mockLogger.warn).toHaveBeenNthCalledWith( + 3, + 'JSON:: A bad data (like circular reference, BigInt) has been detected in the object and the property "i" has been dropped from the output.', + ); + + expect(mockLogger.warn).toHaveBeenNthCalledWith( + 4, + 'JSON:: A bad data (like circular reference, BigInt) has been detected in the object and the property "myself3" has been dropped from the output.', + ); - const json = stringifyWithoutCircular(objWithoutCircular, true, ['size', 'city']); - expect(json).not.toContain('size'); - expect(json).not.toContain('city'); + expect(mockLogger.warn).toHaveBeenNthCalledWith( + 5, + 'JSON:: A bad data (like circular reference, BigInt) has been detected in the object and the property "myself2" has been dropped from the output.', + ); + + expect(mockLogger.warn).toHaveBeenNthCalledWith( + 6, + 'JSON:: A bad data (like circular reference, BigInt) has been detected in the object and the property "myself" has been dropped from the output.', + ); }); - it('should return null for input containing BigInt values', () => { - const mockLogger = { - warn: jest.fn(), + it('should sanitize json even if it contains reused objects', () => { + const obj = { + a: BigInt(1), + b: undefined, + c: 'value', + d: { + e: BigInt(2), + f: undefined, + g: 'value', + h: { + i: BigInt(3), + j: undefined, + k: 'value', + }, + }, }; - const objWithBigInt = { - bigInt: BigInt(9007199254740991), - }; - const json = stringifyWithoutCircular(objWithBigInt, false, [], mockLogger); - expect(json).toBe(null); - expect(mockLogger.warn).toHaveBeenCalledWith( - 'Failed to convert the value to a JSON string.', - new TypeError('Do not know how to serialize a BigInt'), - ); + const reusableArray = [1, 2, 3]; + const reusableObject = { dummy: 'val' }; + obj.reused = reusableArray; + obj.reusedAgain = [1, 2, reusableArray]; + obj.reusedObj = reusableObject; + obj.reusedObjAgain = { reused: reusableObject }; + + obj.d.reused = reusableArray; + obj.d.h.reused = reusableObject; + obj.d.h.reusedAgain = [1, 2, reusableArray]; + + expect(getSanitizedValue(obj)).toEqual({ + a: bigIntNotice, + c: 'value', + b: undefined, + reused: [1, 2, 3], + reusedAgain: [1, 2, [1, 2, 3]], + reusedObj: { dummy: 'val' }, + reusedObjAgain: { reused: { dummy: 'val' } }, + d: { + e: bigIntNotice, + g: 'value', + f: undefined, + reused: [1, 2, 3], + h: { + i: bigIntNotice, + k: 'value', + j: undefined, + reused: { dummy: 'val' }, + reusedAgain: [1, 2, [1, 2, 3]], + }, + }, + }); + }); + + it('should sanitize all data types', () => { + const array = [1, 2, 3]; + const number = 1; + const string = ''; + const object = {}; + const date = new Date(2023, 1, 20, 0, 0, 0); + + expect(getSanitizedValue(array)).toEqual(array); + expect(getSanitizedValue(number)).toEqual(number); + expect(getSanitizedValue(string)).toEqual(string); + expect(getSanitizedValue(object)).toEqual(object); + expect(getSanitizedValue(date)).toEqual(date); + expect(getSanitizedValue(null)).toEqual(null); + expect(getSanitizedValue(undefined)).toEqual(undefined); }); }); }); diff --git a/packages/analytics-js-common/src/constants/integrations/integration_cname.js b/packages/analytics-js-common/src/constants/integrations/integration_cname.js index 57ab51ec48..1b3c6815d2 100644 --- a/packages/analytics-js-common/src/constants/integrations/integration_cname.js +++ b/packages/analytics-js-common/src/constants/integrations/integration_cname.js @@ -163,7 +163,7 @@ const commonNames = { ...Sprig, ...SpotifyPixel, ...XPixel, - ...Gainsight_PX + ...Gainsight_PX, }; export { commonNames }; diff --git a/packages/analytics-js-common/src/constants/logMessages.ts b/packages/analytics-js-common/src/constants/logMessages.ts index 91eb20f04a..fbb06c6d8a 100644 --- a/packages/analytics-js-common/src/constants/logMessages.ts +++ b/packages/analytics-js-common/src/constants/logMessages.ts @@ -9,16 +9,13 @@ const SCRIPT_LOAD_ERROR = (id: string, url: string): string => const SCRIPT_LOAD_TIMEOUT_ERROR = (id: string, url: string, timeout: number): string => `A timeout of ${timeout} ms occurred while trying to load the script with id "${id}" from URL "${url}".`; -const CIRCULAR_REFERENCE_WARNING = (context: string, key: string): string => - `${context}${LOG_CONTEXT_SEPARATOR}A circular reference has been detected in the object and the property "${key}" has been dropped from the output.`; - -const JSON_STRINGIFY_WARNING = `Failed to convert the value to a JSON string.`; +const BAD_DATA_WARNING = (context: string, key: string): string => + `${context}${LOG_CONTEXT_SEPARATOR}A bad data (like circular reference, BigInt) has been detected in the object and the property "${key}" has been dropped from the output.`; export { LOG_CONTEXT_SEPARATOR, SCRIPT_ALREADY_EXISTS_ERROR, SCRIPT_LOAD_ERROR, SCRIPT_LOAD_TIMEOUT_ERROR, - CIRCULAR_REFERENCE_WARNING, - JSON_STRINGIFY_WARNING -} + BAD_DATA_WARNING, +}; diff --git a/packages/analytics-js-common/src/utilities/checks.ts b/packages/analytics-js-common/src/utilities/checks.ts index afb4def565..fc973a34c5 100644 --- a/packages/analytics-js-common/src/utilities/checks.ts +++ b/packages/analytics-js-common/src/utilities/checks.ts @@ -35,6 +35,13 @@ const isUndefined = (value: any): value is undefined => typeof value === 'undefi */ const isNullOrUndefined = (value: any): boolean => isNull(value) || isUndefined(value); +/** + * Checks if the input is a BigInt + * @param value input value + * @returns True if the input is a BigInt + */ +const isBigInt = (value: any): value is bigint => typeof value === 'bigint'; + /** * A function to check given value is defined * @param value input value @@ -74,4 +81,5 @@ export { isDefined, isDefinedAndNotNull, isDefinedNotNullAndNotEmptyString, + isBigInt, }; diff --git a/packages/analytics-js-common/src/utilities/errors.ts b/packages/analytics-js-common/src/utilities/errors.ts index 5e806a6dce..59bf78b1ca 100644 --- a/packages/analytics-js-common/src/utilities/errors.ts +++ b/packages/analytics-js-common/src/utilities/errors.ts @@ -1,5 +1,5 @@ import { isTypeOfError } from './checks'; -import { stringifyWithoutCircular } from './json'; +import { stringifyData } from './json'; /** * Get mutated error with issue prepended to error message @@ -10,7 +10,7 @@ import { stringifyWithoutCircular } from './json'; const getMutatedError = (err: any, issue: string): Error => { let finalError = err; if (!isTypeOfError(err)) { - finalError = new Error(`${issue}: ${stringifyWithoutCircular(err as Record)}`); + finalError = new Error(`${issue}: ${stringifyData(err as Record)}`); } else { (finalError as Error).message = `${issue}: ${err.message}`; } diff --git a/packages/analytics-js-common/src/utilities/json.ts b/packages/analytics-js-common/src/utilities/json.ts index 128d9edc55..2a12b96ad0 100644 --- a/packages/analytics-js-common/src/utilities/json.ts +++ b/packages/analytics-js-common/src/utilities/json.ts @@ -1,69 +1,103 @@ +import { BAD_DATA_WARNING } from '../constants/logMessages'; import type { ILogger } from '../types/Logger'; import type { Nullable } from '../types/Nullable'; -import { isNull, isNullOrUndefined } from './checks'; -import { CIRCULAR_REFERENCE_WARNING, JSON_STRINGIFY_WARNING } from '../constants/logMessages'; - -const JSON_STRINGIFY = 'JSONStringify'; - -const getCircularReplacer = ( - excludeNull?: boolean, - excludeKeys?: string[], - logger?: ILogger, -): ((key: string, value: any) => any) => { - const ancestors: any[] = []; - - // Here we do not want to use arrow function to use "this" in function context - // eslint-disable-next-line func-names - return function (key, value): any { - if (excludeKeys?.includes(key)) { - return undefined; - } +import { isBigInt, isNull } from './checks'; +import { isObjectLiteralAndNotNull } from './object'; - if (excludeNull && isNullOrUndefined(value)) { +const JSON_UTIL = 'JSON'; + +/** + * Utility method for JSON stringify object excluding null values & circular references + * + * @param {*} value input value + * @param {boolean} excludeNull optional flag to exclude null values + * @param {string[]} excludeKeys optional array of keys to exclude + * @returns string + */ +const stringifyData = | any[] | number | string>( + value?: Nullable, + excludeNull: boolean = true, + excludeKeys: string[] = [], +): string => + JSON.stringify(value, (key: string, value: any): any => { + if ((excludeNull && isNull(value)) || excludeKeys.includes(key)) { return undefined; } + return value; + }); - if (typeof value !== 'object' || isNull(value)) { - return value; +const getReplacer = (logger?: ILogger): ((key: string, value: any) => any) => { + const ancestors: any[] = []; // Array to track ancestor objects + + // Using a regular function to use `this` for the parent context + return function replacer(key, value): any { + if (isBigInt(value)) { + logger?.warn(BAD_DATA_WARNING(JSON_UTIL, key)); + return '[BigInt]'; // Replace BigInt values } // `this` is the object that value is contained in, i.e., its direct parent. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore-next-line while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) { - ancestors.pop(); + ancestors.pop(); // Remove ancestors that are no longer part of the chain } + // Check for circular references (if the value is already in the ancestors) if (ancestors.includes(value)) { - logger?.warn(CIRCULAR_REFERENCE_WARNING(JSON_STRINGIFY, key)); + logger?.warn(BAD_DATA_WARNING(JSON_UTIL, key)); return '[Circular Reference]'; } + // Add current value to ancestors ancestors.push(value); + return value; }; }; +const traverseWithThis = (obj: any, replacer: (key: string, value: any) => any): any => { + // Create a new result object or array + const result = Array.isArray(obj) ? [] : {}; + + // Traverse object properties or array elements + // eslint-disable-next-line no-restricted-syntax + for (const key in obj) { + if (Object.hasOwnProperty.call(obj, key)) { + const value = obj[key]; + + // Recursively apply the replacer and traversal + const sanitizedValue = replacer.call(obj, key, value); + + // If the value is an object or array, continue traversal + if (isObjectLiteralAndNotNull(sanitizedValue) || Array.isArray(sanitizedValue)) { + (result as any)[key] = traverseWithThis(sanitizedValue, replacer); + } else { + (result as any)[key] = sanitizedValue; + } + } + } + + return result; +}; + /** - * Utility method for JSON stringify object excluding null values & circular references - * - * @param {*} value input - * @param {boolean} excludeNull if it should exclude nul or not - * @param {function} logger optional logger methods for warning - * @returns string + * Recursively traverses an object similar to JSON.stringify, + * sanitizing BigInts and circular references + * @param value Input object + * @param logger Logger instance + * @returns Sanitized value */ -const stringifyWithoutCircular = | any[] | number | string>( - value?: Nullable, - excludeNull?: boolean, - excludeKeys?: string[], - logger?: ILogger, -): Nullable => { - try { - return JSON.stringify(value, getCircularReplacer(excludeNull, excludeKeys, logger)); - } catch (err) { - logger?.warn(JSON_STRINGIFY_WARNING, err); - return null; +const getSanitizedValue = (value: T, logger?: ILogger): T => { + const replacer = getReplacer(logger); + + // This is needed for registering the first ancestor + const newValue = replacer.call(value, '', value); + + if (isObjectLiteralAndNotNull(value) || Array.isArray(value)) { + return traverseWithThis(value, replacer); } + return newValue; }; -export { stringifyWithoutCircular }; +export { stringifyData, getSanitizedValue }; diff --git a/packages/analytics-js-common/src/utilities/object.ts b/packages/analytics-js-common/src/utilities/object.ts index 276aac1184..366e605a00 100644 --- a/packages/analytics-js-common/src/utilities/object.ts +++ b/packages/analytics-js-common/src/utilities/object.ts @@ -9,13 +9,15 @@ const getValueByPath = (obj: Record, keyPath: string): any => { const hasValueByPath = (obj: Record, path: string): boolean => Boolean(getValueByPath(obj, path)); +const isObject = (value: any): value is object => typeof value === 'object'; + /** * Checks if the input is an object literal or built-in object type and not null * @param value Input value * @returns true if the input is an object and not null */ const isObjectAndNotNull = (value: any): value is object => - !isNull(value) && typeof value === 'object' && !Array.isArray(value); + !isNull(value) && isObject(value) && !Array.isArray(value); /** * Checks if the input is an object literal and not null @@ -116,4 +118,5 @@ export { removeUndefinedValues, removeUndefinedAndNullValues, getObjectValues, + isObject, }; diff --git a/packages/analytics-js-cookies/__tests__/cookieUtilities.test.ts b/packages/analytics-js-cookies/__tests__/cookieUtilities.test.ts index 538303972c..14691053c9 100644 --- a/packages/analytics-js-cookies/__tests__/cookieUtilities.test.ts +++ b/packages/analytics-js-cookies/__tests__/cookieUtilities.test.ts @@ -173,11 +173,6 @@ describe('Cookie Utilities', () => { ); }); - it('should return null if the input cannot be json stringified', () => { - const inputVal = { testKey: BigInt(123) }; - expect(getEncryptedValueBrowser(inputVal)).toBeNull(); - }); - it('should return encoded value if the input contains unicode characters', () => { const inputVal = { testKey: '✓' }; expect(getEncryptedValueBrowser(inputVal)).toBe('RS_ENC_v3_eyJ0ZXN0S2V5Ijoi4pyTIn0='); @@ -249,11 +244,6 @@ describe('Cookie Utilities', () => { ); }); - it('should return null if the input cannot be json stringified', () => { - const inputVal = { testKey: BigInt(123) }; - expect(getEncryptedValue(inputVal)).toBeNull(); - }); - it('should return encoded value if the input contains unicode characters', () => { const inputVal = { testKey: '✓' }; expect(getEncryptedValue(inputVal)).toBe('RS_ENC_v3_eyJ0ZXN0S2V5Ijoi4pyTIn0='); diff --git a/packages/analytics-js-cookies/src/cookiesUtilities.ts b/packages/analytics-js-cookies/src/cookiesUtilities.ts index 8d7254b1bf..68652e7e92 100644 --- a/packages/analytics-js-cookies/src/cookiesUtilities.ts +++ b/packages/analytics-js-cookies/src/cookiesUtilities.ts @@ -2,7 +2,7 @@ import { fromBase64, toBase64 } from '@rudderstack/analytics-js-common/utilities import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import { isNull, isNullOrUndefined } from '@rudderstack/analytics-js-common/utilities/checks'; import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { COOKIE_KEYS, ENCRYPTION_PREFIX_V3 } from './constants/cookies'; import { cookie } from './component-cookie'; @@ -13,7 +13,7 @@ const getEncryptedValueInternal = ( ): Nullable => { const fallbackValue = null; try { - const strValue = stringifyWithoutCircular(value, false); + const strValue = stringifyData(value, false); if (isNull(strValue)) { return null; } diff --git a/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts b/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts index 6d58890430..e83671df87 100644 --- a/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts @@ -695,13 +695,6 @@ describe('Bugsnag utilities', () => { }, ['key4', 'key6'], // excluded keys ], - [ - { - someKey: BigInt(123), - }, - undefined, - [], - ], ]; it.each(tcData)('should convert signals to JSON %#', (input, expected, excludes) => { diff --git a/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts b/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts index 1853279900..0e0ecb8dbe 100644 --- a/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts @@ -275,13 +275,6 @@ describe('Error Reporting utilities', () => { }, ['key4', 'key6'], // excluded keys ], - [ - { - someKey: BigInt(123), - }, - {}, - [], - ], ]; it.each(tcData)('should convert signals to JSON %#', (input, expected, excludes) => { diff --git a/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts b/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts index 0f60b8eac1..f98262da13 100644 --- a/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts +++ b/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts @@ -158,92 +158,6 @@ describe('Queue Plugins Utilities', () => { '{"channel":"test","type":"track","anonymousId":"test","context":{"traits":{"trait_1":"trait_1","trait_2":"trait_2"},"sessionId":1,"sessionStart":true,"ua-ch":{"test":"test"},"app":{"name":"test","version":"test","namespace":"test"},"library":{"name":"test","version":"test"},"userAgent":"test","os":{"name":"test","version":"test"},"locale":"test","screen":{"width":1,"height":1,"density":1,"innerWidth":1,"innerHeight":1}},"originalTimestamp":"test","integrations":{"All":true},"messageId":"test","previousId":"test","sentAt":"test","category":"test","groupId":"test","event":"test","userId":"test","properties":{"test":"test"}}', ); }); - - it('should return string with circular dependencies replaced with static string', () => { - const event = { - channel: 'test', - type: 'track', - anonymousId: 'test', - context: { - traits: { - trait_1: 'trait_1', - trait_2: 'trait_2', - }, - sessionId: 1, - sessionStart: true, - consentManagement: { - deniedConsentIds: ['1', '2', '3'], - }, - 'ua-ch': { - test: 'test', - }, - app: { - name: 'test', - version: 'test', - namespace: 'test', - }, - library: { - name: 'test', - version: 'test', - }, - userAgent: 'test', - os: { - name: 'test', - version: 'test', - }, - locale: 'test', - screen: { - width: 1, - height: 1, - density: 1, - innerWidth: 1, - innerHeight: 1, - }, - campaign: { - source: 'test', - medium: 'test', - name: 'test', - term: 'test', - content: 'test', - }, - }, - originalTimestamp: 'test', - integrations: { - All: true, - }, - messageId: 'test', - previousId: 'test', - sentAt: 'test', - category: 'test', - traits: { - trait_1: 'trait_11', - trait_2: 'trait_12', - }, - groupId: 'test', - event: 'test', - userId: 'test', - properties: { - test: 'test', - }, - } as RudderEvent; - - event.traits = event.context.traits; - event.context.traits.newTraits = event.traits; - - expect(getDeliveryPayload(event, mockLogger)).toContain('[Circular Reference]'); - }); - - it('should return null if the payload cannot be stringified', () => { - const event = { - channel: 'test', - type: 'track', - properties: { - someBigInt: BigInt(9007199254740991), - }, - } as unknown as RudderEvent; - - expect(getDeliveryPayload(event, mockLogger)).toBeNull(); - }); }); describe('validateEventPayloadSize', () => { @@ -301,27 +215,5 @@ describe('Queue Plugins Utilities', () => { expect(mockLogger.warn).not.toHaveBeenCalled(); }); - - it('should log a warning if the payload size could not be calculated', () => { - const event = { - channel: 'test', - type: 'track', - traits: { - trait_1: 'trait_1', - trait_2: 'trait_2', - }, - userId: 'test', - properties: { - test: 'test', - test1: BigInt(9007199254740991), - }, - } as unknown as RudderEvent; - - validateEventPayloadSize(event, mockLogger); - - expect(mockLogger.warn).toHaveBeenCalledWith( - 'QueueUtilities:: Failed to validate event payload size. Please make sure that the event payload is within the size limit and is a valid JSON object.', - ); - }); }); }); diff --git a/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts b/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts index 9b1a15bdf4..7cfb073bac 100644 --- a/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts +++ b/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts @@ -328,61 +328,5 @@ describe('xhrQueue Plugin Utilities', () => { '{"batch":[{"channel":"test","type":"track","anonymousId":"test","properties":{"test":"test"}},{"channel":"test","type":"track","anonymousId":"test","properties":{"test1":"test1","test3":{}}}],"sentAt":"2021-01-01T00:00:00.000Z"}', ); }); - - it('should return string with circular dependencies replaced with static string', () => { - const events = [ - { - channel: 'test', - type: 'track', - anonymousId: 'test', - userId: null, - properties: { - test: 'test', - test2: null, - }, - } as unknown as RudderEvent, - { - channel: 'test', - type: 'track', - anonymousId: 'test', - groupId: null, - properties: { - test1: 'test1', - test3: { - test4: null, - }, - }, - } as unknown as RudderEvent, - ]; - - events[1].properties.test5 = events[1]; - - expect(getBatchDeliveryPayload(events, currentTime, mockLogger)).toContain( - '[Circular Reference]', - ); - }); - - it('should return null if the payload cannot be stringified', () => { - const events = [ - { - channel: 'test', - type: 'track', - anonymousId: 'test', - properties: { - someBigInt: BigInt(9007199254740991), - }, - } as unknown as RudderEvent, - { - channel: 'test', - type: 'track', - anonymousId: 'test', - properties: { - test1: 'test1', - }, - } as unknown as RudderEvent, - ]; - - expect(getBatchDeliveryPayload(events, currentTime, mockLogger)).toBeNull(); - }); }); }); diff --git a/packages/analytics-js-plugins/src/beaconQueue/utilities.ts b/packages/analytics-js-plugins/src/beaconQueue/utilities.ts index 049196b2cf..2e1e83c5ef 100644 --- a/packages/analytics-js-plugins/src/beaconQueue/utilities.ts +++ b/packages/analytics-js-plugins/src/beaconQueue/utilities.ts @@ -31,7 +31,7 @@ const getBatchDeliveryPayload = ( }; try { - const blobPayload = json.stringifyWithoutCircular(data, true); + const blobPayload = json.stringifyData(data); const blobOptions: BlobPropertyBag = { type: 'text/plain' }; if (blobPayload) { diff --git a/packages/analytics-js-plugins/src/bugsnag/constants.ts b/packages/analytics-js-plugins/src/bugsnag/constants.ts index 931af3da70..5b45e385e7 100644 --- a/packages/analytics-js-plugins/src/bugsnag/constants.ts +++ b/packages/analytics-js-plugins/src/bugsnag/constants.ts @@ -31,6 +31,7 @@ const APP_STATE_EXCLUDE_KEYS = [ 'instance', // destination instance objects 'eventBuffer', // pre-load event buffer (may contain PII) 'traits', + 'authToken', ]; const BUGSNAG_PLUGIN = 'BugsnagPlugin'; diff --git a/packages/analytics-js-plugins/src/bugsnag/utils.ts b/packages/analytics-js-plugins/src/bugsnag/utils.ts index 2dfa8b8aff..8407876b4c 100644 --- a/packages/analytics-js-plugins/src/bugsnag/utils.ts +++ b/packages/analytics-js-plugins/src/bugsnag/utils.ts @@ -65,8 +65,8 @@ const isRudderSDKError = (event: BugsnagLib.Report) => { }; const getAppStateForMetadata = (state: ApplicationState): Record | undefined => { - const stateStr = json.stringifyWithoutCircular(state, false, APP_STATE_EXCLUDE_KEYS); - return stateStr !== null ? JSON.parse(stateStr) : undefined; + const stateStr = json.stringifyData(state, true, APP_STATE_EXCLUDE_KEYS); + return JSON.parse(stateStr); }; const enhanceErrorEventMutator = (state: ApplicationState, event: BugsnagLib.Report): void => { diff --git a/packages/analytics-js-plugins/src/errorReporting/constants.ts b/packages/analytics-js-plugins/src/errorReporting/constants.ts index 9765e672c6..6fa61398c0 100644 --- a/packages/analytics-js-plugins/src/errorReporting/constants.ts +++ b/packages/analytics-js-plugins/src/errorReporting/constants.ts @@ -17,6 +17,7 @@ const APP_STATE_EXCLUDE_KEYS = [ 'instance', // destination instance objects 'eventBuffer', // pre-load event buffer (may contain PII) 'traits', + 'authToken', ]; const REQUEST_TIMEOUT_MS = 10 * 1000; // 10 seconds const NOTIFIER_NAME = 'RudderStack JavaScript SDK Error Notifier'; diff --git a/packages/analytics-js-plugins/src/errorReporting/event/event.ts b/packages/analytics-js-plugins/src/errorReporting/event/event.ts index 50f09c5c95..3af8e3f73c 100644 --- a/packages/analytics-js-plugins/src/errorReporting/event/event.ts +++ b/packages/analytics-js-plugins/src/errorReporting/event/event.ts @@ -1,8 +1,7 @@ -import type { ErrorState } from '@rudderstack/analytics-js-common/types/ErrorHandler'; import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import ErrorStackParser from 'error-stack-parser'; import type { Exception, Stackframe } from '@rudderstack/analytics-js-common/types/Metrics'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import type { FrameType, IErrorFormat } from '../types'; import { hasStack, isError } from './utils'; import { ERROR_REPORTING_PLUGIN } from '../constants'; @@ -69,7 +68,7 @@ const normaliseError = (maybeError: any, component: string, logger?: ILogger) => error = maybeError; } else { logger?.warn( - `${ERROR_REPORTING_PLUGIN}:: ${component} received a non-error: ${stringifyWithoutCircular(error)}`, + `${ERROR_REPORTING_PLUGIN}:: ${component} received a non-error: ${stringifyData(error, false)}`, ); error = undefined; } diff --git a/packages/analytics-js-plugins/src/errorReporting/utils.ts b/packages/analytics-js-plugins/src/errorReporting/utils.ts index 3beeef3add..7a8fcb8c92 100644 --- a/packages/analytics-js-plugins/src/errorReporting/utils.ts +++ b/packages/analytics-js-plugins/src/errorReporting/utils.ts @@ -16,7 +16,7 @@ import type { } from '@rudderstack/analytics-js-common/types/Metrics'; import { generateUUID } from '@rudderstack/analytics-js-common/utilities/uuId'; import { METRICS_PAYLOAD_VERSION } from '@rudderstack/analytics-js-common/constants/metrics'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { ERROR_MESSAGES_TO_BE_FILTERED } from '@rudderstack/analytics-js-common/constants/errors'; import { APP_STATE_EXCLUDE_KEYS, @@ -67,8 +67,8 @@ const getReleaseStage = () => { }; const getAppStateForMetadata = (state: ApplicationState): Record => { - const stateStr = json.stringifyWithoutCircular(state, false, APP_STATE_EXCLUDE_KEYS); - return stateStr !== null ? JSON.parse(stateStr) : {}; + const stateStr = json.stringifyData(state, true, APP_STATE_EXCLUDE_KEYS); + return JSON.parse(stateStr); }; const getURLWithoutQueryString = () => { @@ -188,7 +188,7 @@ const getErrorDeliveryPayload = (payload: ErrorEventPayload, state: ApplicationS }, errors: payload, }; - return stringifyWithoutCircular(data) as string; + return stringifyData(data) as string; }; export { diff --git a/packages/analytics-js-plugins/src/utilities/eventsDelivery.ts b/packages/analytics-js-plugins/src/utilities/eventsDelivery.ts index f6bbbedaeb..ad963f2b82 100644 --- a/packages/analytics-js-plugins/src/utilities/eventsDelivery.ts +++ b/packages/analytics-js-plugins/src/utilities/eventsDelivery.ts @@ -3,7 +3,7 @@ import { clone } from 'ramda'; import { LOG_CONTEXT_SEPARATOR } from '@rudderstack/analytics-js-common/constants/logMessages'; import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { EVENT_PAYLOAD_SIZE_BYTES_LIMIT } from './constants'; import type { TransformationRequestPayload } from '../deviceModeTransformation/types'; @@ -14,9 +14,6 @@ const EVENT_PAYLOAD_SIZE_CHECK_FAIL_WARNING = ( ): string => `${context}${LOG_CONTEXT_SEPARATOR}The size of the event payload (${payloadSize} bytes) exceeds the maximum limit of ${sizeLimit} bytes. Events with large payloads may be dropped in the future. Please review your instrumentation to ensure that event payloads are within the size limit.`; -const EVENT_PAYLOAD_SIZE_VALIDATION_WARNING = (context: string): string => - `${context}${LOG_CONTEXT_SEPARATOR}Failed to validate event payload size. Please make sure that the event payload is within the size limit and is a valid JSON object.`; - const QUEUE_UTILITIES = 'QueueUtilities'; /** @@ -25,19 +22,11 @@ const QUEUE_UTILITIES = 'QueueUtilities'; * @param logger Logger instance * @returns stringified event payload. Empty string if error occurs. */ -const getDeliveryPayload = (event: RudderEvent, logger?: ILogger): Nullable => - stringifyWithoutCircular(event, true, undefined, logger); +const getDeliveryPayload = (event: RudderEvent): string => + stringifyData(event) as string; -const getDMTDeliveryPayload = ( - dmtRequestPayload: TransformationRequestPayload, - logger?: ILogger, -): Nullable => - stringifyWithoutCircular( - dmtRequestPayload, - true, - undefined, - logger, - ); +const getDMTDeliveryPayload = (dmtRequestPayload: TransformationRequestPayload): Nullable => + stringifyData(dmtRequestPayload); /** * Utility to validate final payload size before sending to server @@ -45,20 +34,16 @@ const getDMTDeliveryPayload = ( * @param logger Logger instance */ const validateEventPayloadSize = (event: RudderEvent, logger?: ILogger) => { - const payloadStr = getDeliveryPayload(event, logger); - if (payloadStr) { - const payloadSize = payloadStr.length; - if (payloadSize > EVENT_PAYLOAD_SIZE_BYTES_LIMIT) { - logger?.warn( - EVENT_PAYLOAD_SIZE_CHECK_FAIL_WARNING( - QUEUE_UTILITIES, - payloadSize, - EVENT_PAYLOAD_SIZE_BYTES_LIMIT, - ), - ); - } - } else { - logger?.warn(EVENT_PAYLOAD_SIZE_VALIDATION_WARNING(QUEUE_UTILITIES)); + const payloadStr = getDeliveryPayload(event); + const payloadSize = payloadStr.length; + if (payloadSize > EVENT_PAYLOAD_SIZE_BYTES_LIMIT) { + logger?.warn( + EVENT_PAYLOAD_SIZE_CHECK_FAIL_WARNING( + QUEUE_UTILITIES, + payloadSize, + EVENT_PAYLOAD_SIZE_BYTES_LIMIT, + ), + ); } }; diff --git a/packages/analytics-js-plugins/src/xhrQueue/index.ts b/packages/analytics-js-plugins/src/xhrQueue/index.ts index 13978bc499..c194e5a9ac 100644 --- a/packages/analytics-js-plugins/src/xhrQueue/index.ts +++ b/packages/analytics-js-plugins/src/xhrQueue/index.ts @@ -66,11 +66,7 @@ const XhrQueue = (): ExtensionPlugin => ({ maxRetryAttempts?: number, willBeRetried?: boolean, ) => { - const { data, url, headers } = getRequestInfo( - itemData as XHRRetryQueueItemData, - state, - logger, - ); + const { data, url, headers } = getRequestInfo(itemData as XHRRetryQueueItemData, state); httpClient.getAsyncData({ url, @@ -106,7 +102,7 @@ const XhrQueue = (): ExtensionPlugin => ({ const currentTime = getCurrentTimeFormatted(); const events = itemData.map((queueItemData: XHRQueueItemData) => queueItemData.event); // type casting to string as we know that the event has already been validated prior to enqueue - return (getBatchDeliveryPayload(events, currentTime, logger) as string)?.length; + return (getBatchDeliveryPayload(events, currentTime) as string)?.length; }, ); diff --git a/packages/analytics-js-plugins/src/xhrQueue/utilities.ts b/packages/analytics-js-plugins/src/xhrQueue/utilities.ts index 4a3df97a3b..9f1ecf3bf7 100644 --- a/packages/analytics-js-plugins/src/xhrQueue/utilities.ts +++ b/packages/analytics-js-plugins/src/xhrQueue/utilities.ts @@ -4,7 +4,6 @@ import type { ResponseDetails } from '@rudderstack/analytics-js-common/types/Htt import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import type { ApplicationState } from '@rudderstack/analytics-js-common/types/ApplicationState'; import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; -import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import { clone } from 'ramda'; import { getCurrentTimeFormatted } from '@rudderstack/analytics-js-common/utilities/timestamp'; import { checks, http, url, json, eventsDelivery } from '../shared-chunks/common'; @@ -12,13 +11,9 @@ import { DATA_PLANE_API_VERSION, DEFAULT_RETRY_QUEUE_OPTIONS, XHR_QUEUE_PLUGIN } import type { XHRRetryQueueItemData, XHRQueueItemData, XHRBatchPayload } from './types'; import { EVENT_DELIVERY_FAILURE_ERROR_PREFIX } from './logMessages'; -const getBatchDeliveryPayload = ( - events: RudderEvent[], - currentTime: string, - logger?: ILogger, -): Nullable => { +const getBatchDeliveryPayload = (events: RudderEvent[], currentTime: string): string => { const batchPayload: XHRBatchPayload = { batch: events, sentAt: currentTime }; - return json.stringifyWithoutCircular(batchPayload, true, undefined, logger); + return json.stringifyData(batchPayload); }; const getNormalizedQueueOptions = (queueOpts: QueueOpts): QueueOpts => @@ -66,11 +61,7 @@ const logErrorOnFailure = ( logger?.error(errMsg); }; -const getRequestInfo = ( - itemData: XHRRetryQueueItemData, - state: ApplicationState, - logger?: ILogger, -) => { +const getRequestInfo = (itemData: XHRRetryQueueItemData, state: ApplicationState) => { let data; let headers; let url: string; @@ -79,14 +70,14 @@ const getRequestInfo = ( const finalEvents = itemData.map((queueItemData: XHRQueueItemData) => eventsDelivery.getFinalEventForDeliveryMutator(queueItemData.event, currentTime), ); - data = getBatchDeliveryPayload(finalEvents, currentTime, logger); + data = getBatchDeliveryPayload(finalEvents, currentTime); headers = itemData[0] ? clone(itemData[0].headers) : {}; url = getBatchDeliveryUrl(state.lifecycle.activeDataplaneUrl.value as string); } else { const { url: eventUrl, event, headers: eventHeaders } = itemData; const finalEvent = eventsDelivery.getFinalEventForDeliveryMutator(event, currentTime); - data = eventsDelivery.getDeliveryPayload(finalEvent, logger); + data = eventsDelivery.getDeliveryPayload(finalEvent); headers = clone(eventHeaders); url = eventUrl; } diff --git a/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts b/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts index df73dd4f04..9f1fc09154 100644 --- a/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts +++ b/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts @@ -85,23 +85,6 @@ describe('ConfigManager', () => { server.close(); }); - it('should throw an error for invalid writeKey', () => { - state.lifecycle.writeKey.value = ' '; - expect(() => { - configManagerInstance.init(); - }).toThrow(errorMsg); - }); - - it('should throw error for invalid data plane url', () => { - state.lifecycle.writeKey.value = sampleWriteKey; - state.lifecycle.dataPlaneUrl.value = ' '; - expect(() => { - configManagerInstance.init(); - }).toThrow( - 'The data plane URL " " is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.', - ); - }); - it('should update lifecycle state with proper values', () => { getSDKUrl.mockImplementation(() => sampleScriptURL); diff --git a/packages/analytics-js/__tests__/components/configManager/validate.test.ts b/packages/analytics-js/__tests__/components/configManager/validate.test.ts index 2176388a7b..ccaa68f39a 100644 --- a/packages/analytics-js/__tests__/components/configManager/validate.test.ts +++ b/packages/analytics-js/__tests__/components/configManager/validate.test.ts @@ -1,38 +1,10 @@ import { - validateLoadArgs, getTopDomainUrl, getDataServiceUrl, isWebpageTopLevelDomain, } from '../../../src/components/configManager/util/validate'; describe('Config manager util - validate load arguments', () => { - const sampleWriteKey = 'dummyWriteKey'; - const sampleDataPlaneUrl = 'https://www.dummy.url'; - const errorMsg = - 'The write key " " is invalid. It must be a non-empty string. Please check that the write key is correct and try again.'; - - it('should not throw error for valid write key', () => { - expect(() => { - validateLoadArgs(sampleWriteKey); - }).not.toThrow(errorMsg); - }); - it('should not throw error for valid data plane url', () => { - expect(() => { - validateLoadArgs(sampleWriteKey, sampleDataPlaneUrl); - }).not.toThrow('Unable to load the SDK due to invalid data plane URL: " "'); - }); - it('should throw error for invalid write key', () => { - expect(() => { - validateLoadArgs(' '); - }).toThrow(errorMsg); - }); - it('should throw error for invalid data plane url', () => { - expect(() => { - validateLoadArgs(sampleWriteKey, ' '); - }).toThrow( - 'The data plane URL " " is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.', - ); - }); describe('getTopDomainUrl', () => { const testCaseData = [ ['https://sub.example.com', 'https://example.com'], diff --git a/packages/analytics-js/__tests__/components/core/Analytics.test.ts b/packages/analytics-js/__tests__/components/core/Analytics.test.ts index e85709a4d6..f10e2ff0fa 100644 --- a/packages/analytics-js/__tests__/components/core/Analytics.test.ts +++ b/packages/analytics-js/__tests__/components/core/Analytics.test.ts @@ -128,12 +128,77 @@ describe('Core - Analytics', () => { expect(setMinLogLevelSpy).toHaveBeenCalledWith('ERROR'); expect(setExposedGlobal).toHaveBeenCalledWith('state', state, dummyWriteKey); }); - it('should load the analytics script without dataPlaneUrl with the given options', () => { + + it('should not load if the write key is invalid', () => { const startLifecycleSpy = jest.spyOn(analytics, 'startLifecycle'); - analytics.load(dummyWriteKey, { logLevel: 'ERROR' }); - expect(state.lifecycle.status.value).toBe('browserCapabilitiesReady'); - expect(startLifecycleSpy).toHaveBeenCalledTimes(1); - expect(setExposedGlobal).toHaveBeenCalledWith('state', state, dummyWriteKey); + const errorSpy = jest.spyOn(analytics.logger, 'error'); + + analytics.load('', sampleDataPlaneUrl, { logLevel: 'ERROR' }); + + expect(state.lifecycle.status.value).toBeUndefined(); + expect(startLifecycleSpy).not.toHaveBeenCalled(); + + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenNthCalledWith( + 1, + 'AnalyticsCore:: The write key "" is invalid. It must be a non-empty string. Please check that the write key is correct and try again.', + ); + + // Try with different invalid write key + errorSpy.mockClear(); + analytics.load(' ', sampleDataPlaneUrl, { logLevel: 'ERROR' }); + + expect(errorSpy).toHaveBeenNthCalledWith( + 1, + 'AnalyticsCore:: The write key " " is invalid. It must be a non-empty string. Please check that the write key is correct and try again.', + ); + + // Try with different invalid write key + errorSpy.mockClear(); + analytics.load({} as any, sampleDataPlaneUrl, { logLevel: 'ERROR' }); + + expect(errorSpy).toHaveBeenNthCalledWith( + 1, + 'AnalyticsCore:: The write key "[object Object]" is invalid. It must be a non-empty string. Please check that the write key is correct and try again.', + ); + + errorSpy.mockRestore(); + }); + + it('should not load if the data plane URL is invalid', () => { + const startLifecycleSpy = jest.spyOn(analytics, 'startLifecycle'); + const errorSpy = jest.spyOn(analytics.logger, 'error'); + + analytics.load(dummyWriteKey, '', { logLevel: 'ERROR' }); + + expect(state.lifecycle.status.value).toBeUndefined(); + expect(startLifecycleSpy).not.toHaveBeenCalled(); + + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenNthCalledWith( + 1, + 'AnalyticsCore:: The data plane URL "" is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.', + ); + + // Try with different invalid data plane URL + errorSpy.mockClear(); + analytics.load(dummyWriteKey, undefined as any, { logLevel: 'ERROR' }); + + expect(errorSpy).toHaveBeenNthCalledWith( + 1, + 'AnalyticsCore:: The data plane URL "undefined" is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.', + ); + + // Try with different invalid data plane URL + errorSpy.mockClear(); + analytics.load(dummyWriteKey, 'https:///someinvalidurl', { logLevel: 'ERROR' }); + + expect(errorSpy).toHaveBeenNthCalledWith( + 1, + 'AnalyticsCore:: The data plane URL "https:///someinvalidurl" is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.', + ); + + errorSpy.mockRestore(); }); }); diff --git a/packages/analytics-js/__tests__/components/userSessionManager/UserSessionManager.test.ts b/packages/analytics-js/__tests__/components/userSessionManager/UserSessionManager.test.ts index f7b0571eaa..62e967731e 100644 --- a/packages/analytics-js/__tests__/components/userSessionManager/UserSessionManager.test.ts +++ b/packages/analytics-js/__tests__/components/userSessionManager/UserSessionManager.test.ts @@ -1,5 +1,5 @@ import type { IPluginsManager } from '@rudderstack/analytics-js-common/types/PluginsManager'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { COOKIE_KEYS } from '@rudderstack/analytics-js-cookies/constants/cookies'; import { UserSessionManager } from '../../../src/components/userSessionManager'; import { DEFAULT_USER_SESSION_VALUES } from '../../../src/components/userSessionManager/constants'; @@ -27,7 +27,7 @@ jest.mock('@rudderstack/analytics-js-common/utilities/uuId', () => ({ })); jest.mock('@rudderstack/analytics-js-common/utilities/json', () => ({ - stringifyWithoutCircular: jest.fn(d => JSON.stringify(d)), + stringifyData: jest.fn(d => JSON.stringify(d)), })); describe('User session manager', () => { @@ -1713,7 +1713,7 @@ describe('User session manager', () => { prop2: 12345678, prop3: { city: 'Kolkata', zip: '700001' }, }); - expect(stringifyWithoutCircular).toHaveBeenCalled(); + expect(stringifyData).toHaveBeenCalled(); expect(defaultLogger.error).not.toHaveBeenCalledWith( 'The server failed to set the key cookie. As a fallback, the cookies will be set client side.', ); @@ -1739,7 +1739,7 @@ describe('User session manager', () => { ); setTimeout(() => { expect(mockCookieStore.get).toHaveBeenCalledWith('key'); - expect(stringifyWithoutCircular).toHaveBeenCalled(); + expect(stringifyData).toHaveBeenCalled(); expect(defaultLogger.error).toHaveBeenCalledWith( 'The server failed to set the key cookie. As a fallback, the cookies will be set client side.', ); diff --git a/packages/analytics-js/__tests__/services/HttpClient/HttpClient.test.ts b/packages/analytics-js/__tests__/services/HttpClient/HttpClient.test.ts index 2e7349d8df..fd0bbe2af0 100644 --- a/packages/analytics-js/__tests__/services/HttpClient/HttpClient.test.ts +++ b/packages/analytics-js/__tests__/services/HttpClient/HttpClient.test.ts @@ -230,26 +230,4 @@ describe('HttpClient', () => { url: `${dummyDataplaneHost}/emptyJsonSample`, }); }); - - it('should handle if input data contains non-stringifiable values', done => { - const callback = (response: any) => { - expect(response).toBeUndefined(); - expect(defaultErrorHandler.onError).toHaveBeenCalledTimes(1); - expect(defaultErrorHandler.onError).toHaveBeenCalledWith( - new Error('Failed to prepare data for the request.'), - 'HttpClient', - ); - done(); - }; - clientInstance.getAsyncData({ - callback, - url: `${dummyDataplaneHost}/nonStringifiableDataSample`, - options: { - data: { - a: 1, - b: BigInt(1), - }, - }, - }); - }); }); diff --git a/packages/analytics-js/src/app/RudderAnalytics.ts b/packages/analytics-js/src/app/RudderAnalytics.ts index dabf2b1918..7d8de7add9 100644 --- a/packages/analytics-js/src/app/RudderAnalytics.ts +++ b/packages/analytics-js/src/app/RudderAnalytics.ts @@ -18,10 +18,11 @@ import { import type { ApiCallback, ApiOptions } from '@rudderstack/analytics-js-common/types/EventApi'; import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; import { RS_APP } from '@rudderstack/analytics-js-common/constants/loggerContexts'; -import { isString } from '@rudderstack/analytics-js-common/utilities/checks'; import type { IdentifyTraits } from '@rudderstack/analytics-js-common/types/traits'; +import { getSanitizedValue } from '@rudderstack/analytics-js-common/utilities/json'; import { generateUUID } from '@rudderstack/analytics-js-common/utilities/uuId'; import { onPageLeave } from '@rudderstack/analytics-js-common/utilities/page'; +import { isString } from '@rudderstack/analytics-js-common/utilities/checks'; import { getFormattedTimestamp } from '@rudderstack/analytics-js-common/utilities/timestamp'; import { GLOBAL_PRELOAD_BUFFER } from '../constants/app'; import { @@ -36,7 +37,6 @@ import { defaultLogger } from '../services/Logger/Logger'; import { EMPTY_GROUP_CALL_ERROR, PAGE_UNLOAD_ON_BEACON_DISABLED_WARNING, - WRITE_KEY_NOT_A_STRING_ERROR, } from '../constants/logMessages'; import { defaultErrorHandler } from '../services/ErrorHandler'; import { state } from '../state'; @@ -107,7 +107,7 @@ class RudderAnalytics implements IRudderAnalytics { * TODO: to support multiple analytics instances in the near future */ setDefaultInstanceKey(writeKey: string) { - if (writeKey) { + if (isString(writeKey) && writeKey) { this.defaultAnalyticsKey = writeKey; } } @@ -116,7 +116,10 @@ class RudderAnalytics implements IRudderAnalytics { * Retrieve an existing analytics instance */ getAnalyticsInstance(writeKey?: string): IAnalytics { - const instanceId = writeKey ?? this.defaultAnalyticsKey; + let instanceId = writeKey; + if (!isString(instanceId) || !instanceId) { + instanceId = this.defaultAnalyticsKey; + } const analyticsInstanceExists = Boolean(this.analyticsInstances[instanceId]); @@ -128,14 +131,13 @@ class RudderAnalytics implements IRudderAnalytics { } /** - * Create new analytics instance and trigger application lifecycle start + * Loads the SDK + * @param writeKey Source write key + * @param dataPlaneUrl Data plane URL + * @param loadOptions Additional options for loading the SDK + * @returns none */ load(writeKey: string, dataPlaneUrl: string, loadOptions?: Partial) { - if (!isString(writeKey)) { - this.logger.error(WRITE_KEY_NOT_A_STRING_ERROR(RS_APP, writeKey)); - return; - } - if (this.analyticsInstances[writeKey]) { return; } @@ -152,7 +154,11 @@ class RudderAnalytics implements IRudderAnalytics { setExposedGlobal(GLOBAL_PRELOAD_BUFFER, clone(preloadedEventsArray)); this.analyticsInstances[writeKey] = new Analytics(); - this.getAnalyticsInstance(writeKey).load(writeKey, dataPlaneUrl, loadOptions); + this.getAnalyticsInstance(writeKey).load( + writeKey, + dataPlaneUrl, + getSanitizedValue(loadOptions), + ); } /** @@ -342,7 +348,13 @@ class RudderAnalytics implements IRudderAnalytics { callback?: ApiCallback, ) { this.getAnalyticsInstance().page( - pageArgumentsToCallOptions(category, name, properties, options, callback), + pageArgumentsToCallOptions( + getSanitizedValue(category), + getSanitizedValue(name), + getSanitizedValue(properties), + getSanitizedValue(options), + callback, + ), ); } @@ -365,7 +377,12 @@ class RudderAnalytics implements IRudderAnalytics { callback?: ApiCallback, ) { this.getAnalyticsInstance().track( - trackArgumentsToCallOptions(event, properties, options, callback), + trackArgumentsToCallOptions( + getSanitizedValue(event), + getSanitizedValue(properties), + getSanitizedValue(options), + callback, + ), ); } @@ -394,7 +411,12 @@ class RudderAnalytics implements IRudderAnalytics { callback?: ApiCallback, ) { this.getAnalyticsInstance().identify( - identifyArgumentsToCallOptions(userId, traits, options, callback), + identifyArgumentsToCallOptions( + getSanitizedValue(userId), + getSanitizedValue(traits), + getSanitizedValue(options), + callback, + ), ); } @@ -412,7 +434,14 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().alias(aliasArgumentsToCallOptions(to, from, options, callback)); + this.getAnalyticsInstance().alias( + aliasArgumentsToCallOptions( + getSanitizedValue(to), + getSanitizedValue(from), + getSanitizedValue(options), + callback, + ), + ); } /** @@ -445,20 +474,28 @@ class RudderAnalytics implements IRudderAnalytics { } this.getAnalyticsInstance().group( - groupArgumentsToCallOptions(groupId, traits, options, callback), + groupArgumentsToCallOptions( + getSanitizedValue(groupId), + getSanitizedValue(traits), + getSanitizedValue(options), + callback, + ), ); } reset(resetAnonymousId?: boolean) { - this.getAnalyticsInstance().reset(resetAnonymousId); + this.getAnalyticsInstance().reset(getSanitizedValue(resetAnonymousId)); } getAnonymousId(options?: AnonymousIdOptions) { - return this.getAnalyticsInstance().getAnonymousId(options); + return this.getAnalyticsInstance().getAnonymousId(getSanitizedValue(options)); } setAnonymousId(anonymousId?: string, rudderAmpLinkerParam?: string) { - this.getAnalyticsInstance().setAnonymousId(anonymousId, rudderAmpLinkerParam); + this.getAnalyticsInstance().setAnonymousId( + getSanitizedValue(anonymousId), + getSanitizedValue(rudderAmpLinkerParam), + ); } getUserId() { @@ -490,11 +527,11 @@ class RudderAnalytics implements IRudderAnalytics { } setAuthToken(token: string) { - return this.getAnalyticsInstance().setAuthToken(token); + return this.getAnalyticsInstance().setAuthToken(getSanitizedValue(token)); } consent(options?: ConsentOptions) { - return this.getAnalyticsInstance().consent(options); + return this.getAnalyticsInstance().consent(getSanitizedValue(options)); } } diff --git a/packages/analytics-js/src/components/configManager/ConfigManager.ts b/packages/analytics-js/src/components/configManager/ConfigManager.ts index fec3eacbb3..315f2ab5a5 100644 --- a/packages/analytics-js/src/components/configManager/ConfigManager.ts +++ b/packages/analytics-js/src/components/configManager/ConfigManager.ts @@ -10,7 +10,7 @@ import type { Destination } from '@rudderstack/analytics-js-common/types/Destina import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import { CONFIG_MANAGER } from '@rudderstack/analytics-js-common/constants/loggerContexts'; import type { IntegrationOpts } from '@rudderstack/analytics-js-common/types/Integration'; -import { isValidSourceConfig, validateLoadArgs } from './util/validate'; +import { isValidSourceConfig } from './util/validate'; import { SOURCE_CONFIG_FETCH_ERROR, SOURCE_CONFIG_OPTION_ERROR, @@ -62,8 +62,6 @@ class ConfigManager implements IConfigManager { init() { this.attachEffects(); - validateLoadArgs(state.lifecycle.writeKey.value, state.lifecycle.dataPlaneUrl.value); - const { logLevel, configUrl, diff --git a/packages/analytics-js/src/components/configManager/util/validate.ts b/packages/analytics-js/src/components/configManager/util/validate.ts index 6090e231cd..fc22c51405 100644 --- a/packages/analytics-js/src/components/configManager/util/validate.ts +++ b/packages/analytics-js/src/components/configManager/util/validate.ts @@ -1,31 +1,9 @@ import { isObjectLiteralAndNotNull } from '@rudderstack/analytics-js-common/utilities/object'; -import { isNullOrUndefined, isString } from '@rudderstack/analytics-js-common/utilities/checks'; +import { isNullOrUndefined } from '@rudderstack/analytics-js-common/utilities/checks'; import { SUPPORTED_STORAGE_TYPES, type StorageType, } from '@rudderstack/analytics-js-common/types/Storage'; -import { isValidURL } from '@rudderstack/analytics-js-common/utilities/url'; -import { - WRITE_KEY_VALIDATION_ERROR, - DATA_PLANE_URL_VALIDATION_ERROR, -} from '../../../constants/logMessages'; - -const validateWriteKey = (writeKey?: string) => { - if (!isString(writeKey) || (writeKey as string).trim().length === 0) { - throw new Error(WRITE_KEY_VALIDATION_ERROR(writeKey)); - } -}; - -const validateDataPlaneUrl = (dataPlaneUrl?: string) => { - if (!isValidURL(dataPlaneUrl)) { - throw new Error(DATA_PLANE_URL_VALIDATION_ERROR(dataPlaneUrl)); - } -}; - -const validateLoadArgs = (writeKey?: string, dataPlaneUrl?: string) => { - validateWriteKey(writeKey); - validateDataPlaneUrl(dataPlaneUrl); -}; const isValidSourceConfig = (res: any): boolean => isObjectLiteralAndNotNull(res) && @@ -75,11 +53,8 @@ const isWebpageTopLevelDomain = (providedDomain: string): boolean => { }; export { - validateLoadArgs, isValidSourceConfig, isValidStorageType, - validateWriteKey, - validateDataPlaneUrl, getTopDomainUrl, getDataServiceUrl, isWebpageTopLevelDomain, diff --git a/packages/analytics-js/src/components/core/Analytics.ts b/packages/analytics-js/src/components/core/Analytics.ts index 6b6e231f35..ed6986769a 100644 --- a/packages/analytics-js/src/components/core/Analytics.ts +++ b/packages/analytics-js/src/components/core/Analytics.ts @@ -18,7 +18,6 @@ import type { LoadOptions, } from '@rudderstack/analytics-js-common/types/LoadOptions'; import type { ApiCallback } from '@rudderstack/analytics-js-common/types/EventApi'; -import { isObjectAndNotNull } from '@rudderstack/analytics-js-common/utilities/object'; import { ANALYTICS_CORE, READY_API, @@ -60,10 +59,15 @@ import { ADBLOCK_PAGE_PATH, CONSENT_TRACK_EVENT_NAME, } from '../../constants/app'; -import { READY_API_CALLBACK_ERROR, READY_CALLBACK_INVOKE_ERROR } from '../../constants/logMessages'; +import { + DATA_PLANE_URL_VALIDATION_ERROR, + READY_API_CALLBACK_ERROR, + READY_CALLBACK_INVOKE_ERROR, + WRITE_KEY_VALIDATION_ERROR, +} from '../../constants/logMessages'; import type { IAnalytics } from './IAnalytics'; import { getConsentManagementData, getValidPostConsentOptions } from '../utilities/consent'; -import { dispatchSDKEvent } from './utilities'; +import { dispatchSDKEvent, isDataPlaneUrlValid, isWriteKeyValid } from './utilities'; /* * Analytics class with lifecycle based on state ad user triggered events @@ -100,29 +104,26 @@ class Analytics implements IAnalytics { /** * Start application lifecycle if not already started */ - load( - writeKey: string, - dataPlaneUrl?: string | Partial, - loadOptions: Partial = {}, - ) { + load(writeKey: string, dataPlaneUrl: string, loadOptions: Partial = {}) { if (state.lifecycle.status.value) { return; } - let clonedDataPlaneUrl = clone(dataPlaneUrl); - let clonedLoadOptions = clone(loadOptions); + if (!isWriteKeyValid(writeKey)) { + this.logger.error(WRITE_KEY_VALIDATION_ERROR(ANALYTICS_CORE, writeKey)); + return; + } - // dataPlaneUrl is not provided - if (isObjectAndNotNull(dataPlaneUrl)) { - clonedLoadOptions = dataPlaneUrl; - clonedDataPlaneUrl = undefined; + if (!isDataPlaneUrlValid(dataPlaneUrl)) { + this.logger.error(DATA_PLANE_URL_VALIDATION_ERROR(ANALYTICS_CORE, dataPlaneUrl)); + return; } // Set initial state values batch(() => { - state.lifecycle.writeKey.value = writeKey; - state.lifecycle.dataPlaneUrl.value = clonedDataPlaneUrl as string | undefined; - state.loadOptions.value = normalizeLoadOptions(state.loadOptions.value, clonedLoadOptions); + state.lifecycle.writeKey.value = clone(writeKey); + state.lifecycle.dataPlaneUrl.value = clone(dataPlaneUrl); + state.loadOptions.value = normalizeLoadOptions(state.loadOptions.value, loadOptions); state.lifecycle.status.value = 'mounted'; }); diff --git a/packages/analytics-js/src/components/core/utilities.ts b/packages/analytics-js/src/components/core/utilities.ts index ca1dfd0c0b..b9c11e7fd9 100644 --- a/packages/analytics-js/src/components/core/utilities.ts +++ b/packages/analytics-js/src/components/core/utilities.ts @@ -1,3 +1,6 @@ +import { isString } from '@rudderstack/analytics-js-common/utilities/checks'; +import { isValidURL } from '@rudderstack/analytics-js-common/utilities/url'; + const dispatchSDKEvent = (event: string): void => { const customEvent = new CustomEvent(event, { detail: { analyticsInstance: (globalThis as typeof window).rudderanalytics }, @@ -9,4 +12,8 @@ const dispatchSDKEvent = (event: string): void => { (globalThis as typeof window).document.dispatchEvent(customEvent); }; -export { dispatchSDKEvent }; +const isWriteKeyValid = (writeKey: string) => isString(writeKey) && writeKey.trim().length > 0; + +const isDataPlaneUrlValid = (dataPlaneUrl: string) => isValidURL(dataPlaneUrl); + +export { dispatchSDKEvent, isWriteKeyValid, isDataPlaneUrlValid }; diff --git a/packages/analytics-js/src/components/userSessionManager/UserSessionManager.ts b/packages/analytics-js/src/components/userSessionManager/UserSessionManager.ts index 1bc46c1726..dd5fd436b7 100644 --- a/packages/analytics-js/src/components/userSessionManager/UserSessionManager.ts +++ b/packages/analytics-js/src/components/userSessionManager/UserSessionManager.ts @@ -32,7 +32,7 @@ import type { AsyncRequestCallback, IHttpClient, } from '@rudderstack/analytics-js-common/types/HttpClient'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { COOKIE_KEYS } from '@rudderstack/analytics-js-cookies/constants/cookies'; import { CLIENT_DATA_STORE_COOKIE, @@ -304,9 +304,7 @@ class UserSessionManager implements IUserSessionManager { getEncryptedCookieData(cookiesData: CookieData[], store?: IStore): EncryptedCookieData[] { const encryptedCookieData: EncryptedCookieData[] = []; cookiesData.forEach(cData => { - const encryptedValue = store?.encrypt( - stringifyWithoutCircular(cData.value, false, [], this.logger), - ); + const encryptedValue = store?.encrypt(stringifyData(cData.value, false)); if (isDefinedAndNotNull(encryptedValue)) { encryptedCookieData.push({ name: cData.name, @@ -330,21 +328,24 @@ class UserSessionManager implements IUserSessionManager { url: state.serverCookies.dataServiceUrl.value as string, options: { method: 'POST', - data: stringifyWithoutCircular({ - reqType: 'setCookies', - workspaceId: state.source.value?.workspaceId, - data: { - options: { - maxAge: state.storage.cookie.value?.maxage, - path: state.storage.cookie.value?.path, - domain: state.storage.cookie.value?.domain, - sameSite: state.storage.cookie.value?.samesite, - secure: state.storage.cookie.value?.secure, - expires: state.storage.cookie.value?.expires, + data: stringifyData( + { + reqType: 'setCookies', + workspaceId: state.source.value?.workspaceId, + data: { + options: { + maxAge: state.storage.cookie.value?.maxage, + path: state.storage.cookie.value?.path, + domain: state.storage.cookie.value?.domain, + sameSite: state.storage.cookie.value?.samesite, + secure: state.storage.cookie.value?.secure, + expires: state.storage.cookie.value?.expires, + }, + cookies: encryptedCookieData, }, - cookies: encryptedCookieData, }, - }) as string, + false, + ), sendRawData: true, withCredentials: true, }, @@ -368,8 +369,8 @@ class UserSessionManager implements IUserSessionManager { if (details?.xhr?.status === 200) { cookiesData.forEach(cData => { const cookieValue = store?.get(cData.name); - const before = stringifyWithoutCircular(cData.value, false, []); - const after = stringifyWithoutCircular(cookieValue, false, []); + const before = stringifyData(cData.value, false); + const after = stringifyData(cookieValue, false); if (after !== before) { this.logger?.error(FAILED_SETTING_COOKIE_FROM_SERVER_ERROR(cData.name)); if (cb) { @@ -468,7 +469,11 @@ class UserSessionManager implements IUserSessionManager { * 3. generateUUID: A new unique id is generated and assigned. */ setAnonymousId(anonymousId?: string, rudderAmpLinkerParam?: string) { - let finalAnonymousId: string | undefined | null = anonymousId; + let finalAnonymousId: string | undefined = anonymousId; + if (!isString(anonymousId) || !finalAnonymousId) { + finalAnonymousId = undefined; + } + if (this.isPersistenceEnabledForStorageEntry('anonymousId')) { if (!finalAnonymousId && rudderAmpLinkerParam) { const linkerPluginsResult = this.pluginsManager?.invokeSingle( @@ -668,7 +673,7 @@ class UserSessionManager implements IUserSessionManager { session.groupTraits.value = DEFAULT_USER_SESSION_VALUES.groupTraits; session.authToken.value = DEFAULT_USER_SESSION_VALUES.authToken; - if (resetAnonymousId) { + if (resetAnonymousId === true) { // This will generate a new anonymous ID this.setAnonymousId(); } diff --git a/packages/analytics-js/src/constants/logMessages.ts b/packages/analytics-js/src/constants/logMessages.ts index 8295a634e8..a28ee4b12e 100644 --- a/packages/analytics-js/src/constants/logMessages.ts +++ b/packages/analytics-js/src/constants/logMessages.ts @@ -66,14 +66,14 @@ const STORAGE_UNAVAILABILITY_ERROR_PREFIX = (context: string, storageType: Stora const SOURCE_CONFIG_FETCH_ERROR = (reason: Error | undefined): string => `Failed to fetch the source config. Reason: ${reason}`; -const WRITE_KEY_VALIDATION_ERROR = (writeKey?: string): string => - `The write key "${writeKey}" is invalid. It must be a non-empty string. Please check that the write key is correct and try again.`; +const WRITE_KEY_VALIDATION_ERROR = (context: string, writeKey: string): string => + `${context}${LOG_CONTEXT_SEPARATOR}The write key "${writeKey}" is invalid. It must be a non-empty string. Please check that the write key is correct and try again.`; -const DATA_PLANE_URL_VALIDATION_ERROR = (dataPlaneUrl: string | undefined): string => - `The data plane URL "${dataPlaneUrl}" is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.`; +const DATA_PLANE_URL_VALIDATION_ERROR = (context: string, dataPlaneUrl: string): string => + `${context}${LOG_CONTEXT_SEPARATOR}The data plane URL "${dataPlaneUrl}" is invalid. It must be a valid URL string. Please check that the data plane URL is correct and try again.`; const READY_API_CALLBACK_ERROR = (context: string): string => - `${context}${LOG_CONTEXT_SEPARATOR}The callback is not a function.`; + `${context}${LOG_CONTEXT_SEPARATOR}The provided callback is not a function.`; const XHR_DELIVERY_ERROR = ( prefix: string, @@ -193,9 +193,6 @@ const STORAGE_UNAVAILABLE_WARNING = ( ): string => `${context}${LOG_CONTEXT_SEPARATOR}The storage type "${selectedStorageType}" is not available for entry "${entry}". The SDK will initialize the entry with "${finalStorageType}" storage type instead.`; -const WRITE_KEY_NOT_A_STRING_ERROR = (context: string, writeKey: string | undefined): string => - `${context}${LOG_CONTEXT_SEPARATOR}The write key "${writeKey}" is not a string. Please check that the write key is correct and try again.`; - const EMPTY_GROUP_CALL_ERROR = (context: string): string => `${context}${LOG_CONTEXT_SEPARATOR}The group() method must be called with at least one argument.`; @@ -300,7 +297,6 @@ export { PLUGIN_EXT_POINT_MISSING_ERROR, PLUGIN_EXT_POINT_INVALID_ERROR, STORAGE_TYPE_VALIDATION_WARNING, - WRITE_KEY_NOT_A_STRING_ERROR, EMPTY_GROUP_CALL_ERROR, READY_CALLBACK_INVOKE_ERROR, API_CALLBACK_INVOKE_ERROR, diff --git a/packages/analytics-js/src/services/ErrorHandler/processError.ts b/packages/analytics-js/src/services/ErrorHandler/processError.ts index bd5588bffd..92e8db6505 100644 --- a/packages/analytics-js/src/services/ErrorHandler/processError.ts +++ b/packages/analytics-js/src/services/ErrorHandler/processError.ts @@ -1,4 +1,4 @@ -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { isString } from '@rudderstack/analytics-js-common/utilities/checks'; import type { ErrorTarget, SDKError } from '@rudderstack/analytics-js-common/types/ErrorHandler'; import { LOAD_ORIGIN } from './constant'; @@ -19,7 +19,7 @@ const processError = (error: SDKError): string => { } else { errorMessage = (error as any).message ? (error as any).message - : stringifyWithoutCircular(error as Record); + : stringifyData(error as Record); } } catch (e) { errorMessage = `Unknown error: ${(e as Error).message}`; diff --git a/packages/analytics-js/src/services/HttpClient/HttpClient.ts b/packages/analytics-js/src/services/HttpClient/HttpClient.ts index ee3f7e3ada..5aab7e3c18 100644 --- a/packages/analytics-js/src/services/HttpClient/HttpClient.ts +++ b/packages/analytics-js/src/services/HttpClient/HttpClient.ts @@ -44,7 +44,6 @@ class HttpClient implements IHttpClient { const data = await xhrRequest( createXhrRequestOptions(url, options, this.basicAuthHeader), timeout, - this.logger, ); return { data: isRawResponse ? data.response : responseTextToJson(data.response, this.onError), @@ -63,7 +62,7 @@ class HttpClient implements IHttpClient { const { callback, url, options, timeout, isRawResponse } = config; const isFireAndForget = !isFunction(callback); - xhrRequest(createXhrRequestOptions(url, options, this.basicAuthHeader), timeout, this.logger) + xhrRequest(createXhrRequestOptions(url, options, this.basicAuthHeader), timeout) .then((data: ResponseDetails) => { if (!isFireAndForget) { callback( diff --git a/packages/analytics-js/src/services/HttpClient/xhr/xhrRequestHandler.ts b/packages/analytics-js/src/services/HttpClient/xhr/xhrRequestHandler.ts index c8202d918c..182d155b86 100644 --- a/packages/analytics-js/src/services/HttpClient/xhr/xhrRequestHandler.ts +++ b/packages/analytics-js/src/services/HttpClient/xhr/xhrRequestHandler.ts @@ -1,12 +1,11 @@ /* eslint-disable prefer-promise-reject-errors */ import { mergeDeepRight } from '@rudderstack/analytics-js-common/utilities/object'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import { isNull } from '@rudderstack/analytics-js-common/utilities/checks'; import type { IXHRRequestOptions, ResponseDetails, } from '@rudderstack/analytics-js-common/types/HttpClient'; -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import { getMutatedError } from '@rudderstack/analytics-js-common/utilities/errors'; import { FAILED_REQUEST_ERR_MSG_PREFIX } from '@rudderstack/analytics-js-common/constants/errors'; import { DEFAULT_XHR_TIMEOUT_MS } from '../../../constants/timeouts'; @@ -57,14 +56,13 @@ const createXhrRequestOptions = ( const xhrRequest = ( options: IXHRRequestOptions, timeout = DEFAULT_XHR_TIMEOUT_MS, - logger?: ILogger, ): Promise => new Promise((resolve, reject) => { let payload; if (options.sendRawData === true) { payload = options.data; } else { - payload = stringifyWithoutCircular(options.data, false, [], logger); + payload = stringifyData(options.data); if (isNull(payload)) { reject({ error: new Error(XHR_PAYLOAD_PREP_ERROR), diff --git a/packages/analytics-js/src/services/StoreManager/Store.ts b/packages/analytics-js/src/services/StoreManager/Store.ts index da079a8a64..f55f0924d7 100644 --- a/packages/analytics-js/src/services/StoreManager/Store.ts +++ b/packages/analytics-js/src/services/StoreManager/Store.ts @@ -1,6 +1,6 @@ import { trim } from '@rudderstack/analytics-js-common/utilities/string'; import { isNullOrUndefined, isString } from '@rudderstack/analytics-js-common/utilities/checks'; -import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { stringifyData } from '@rudderstack/analytics-js-common/utilities/json'; import type { IStorage, IStore, IStoreConfig } from '@rudderstack/analytics-js-common/types/Store'; import type { IErrorHandler } from '@rudderstack/analytics-js-common/types/ErrorHandler'; import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; @@ -107,10 +107,7 @@ class Store implements IStore { try { // storejs that is used in localstorage engine already stringifies json - this.engine.setItem( - validKey, - this.encrypt(stringifyWithoutCircular(value, false, [], this.logger)), - ); + this.engine.setItem(validKey, this.encrypt(stringifyData(value, false))); } catch (err) { if (isStorageQuotaExceeded(err)) { this.logger?.warn(STORAGE_QUOTA_EXCEEDED_WARNING(`Store ${this.id}`)); @@ -208,7 +205,7 @@ class Store implements IStore { ? this.pluginsManager.invokeSingle(extensionPointName, value) : value; - return typeof formattedValue === 'undefined' ? value : formattedValue ?? ''; + return typeof formattedValue === 'undefined' ? value : (formattedValue ?? ''); } /** From 9c207f19f34b998cdb15b34eed3f435daff86dfd Mon Sep 17 00:00:00 2001 From: Moumita <36885121+MoumitaM@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:44:07 +0530 Subject: [PATCH 02/15] feat: add date datatype (#1906) * feat: add date datatype * chore: update allowed datatypes * chore: address review comment --- packages/analytics-js-service-worker/src/types.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/analytics-js-service-worker/src/types.ts b/packages/analytics-js-service-worker/src/types.ts index 9ced48c967..701046949a 100644 --- a/packages/analytics-js-service-worker/src/types.ts +++ b/packages/analytics-js-service-worker/src/types.ts @@ -11,8 +11,9 @@ export interface ApiObject { | boolean | undefined | ApiObject - | unknown - | (string | number | boolean | ApiObject)[]; + | Date + | null + | (string | number | boolean | ApiObject | Date | null | undefined)[]; } /** From 9fbaf819bb02320d2f8ae82a869ad2b85090ea34 Mon Sep 17 00:00:00 2001 From: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:32:14 +0530 Subject: [PATCH 03/15] feat: add error handling to all public apis (#1907) * feat: add error handling to all public apis * chore: address ai bot review comments * test: add unit tests for RudderAnalytics class * fix: buble up error to handle * fix: error handling in analytics * test: address ai bot review comments * test: add unit tests for error utilities * test: clean up test suite * chore: revert size limit changes * chore: update code owners and slack group for integrations * fix: log manually raised unhandled errors * fix: improve stack mutation logic --- .github/workflows/deploy-npm.yml | 12 +- .github/workflows/deploy-sanity-suite.yml | 4 +- .github/workflows/deploy.yml | 4 +- .github/workflows/publish-new-release.yml | 4 +- CODEOWNERS | 18 +- .../__tests__/utilities/errors.test.ts | 18 + .../src/constants/loggerContexts.ts | 4 +- .../src/types/ApiObject.ts | 2 +- .../src/types/IRudderAnalytics.ts | 4 +- .../src/utilities/errors.ts | 11 +- .../src/utilities/eventMethodOverloads.ts | 201 +++-- packages/analytics-js/.size-limit.mjs | 4 +- .../__tests__/app/RudderAnalytics.test.ts | 783 ++++++++++++++---- .../analytics-js/src/app/RudderAnalytics.ts | 351 ++++---- .../analytics-js/src/constants/logMessages.ts | 4 - .../src/services/ErrorHandler/ErrorHandler.ts | 3 + 16 files changed, 995 insertions(+), 432 deletions(-) create mode 100644 packages/analytics-js-common/__tests__/utilities/errors.test.ts diff --git a/.github/workflows/deploy-npm.yml b/.github/workflows/deploy-npm.yml index 63dfa3e0f7..f73132e335 100644 --- a/.github/workflows/deploy-npm.yml +++ b/.github/workflows/deploy-npm.yml @@ -155,7 +155,7 @@ jobs: channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} payload: | { - "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}\nCC: ", + "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}\nCC: ", "blocks": [ { "type": "header", @@ -171,7 +171,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*<${{ env.NPM_PACKAGE_URL }}|v${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}\nCC: " + "text": "*<${{ env.NPM_PACKAGE_URL }}|v${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}\nCC: " }, "accessory": { "type": "image", @@ -205,7 +205,7 @@ jobs: channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} payload: | { - "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_SW_VALUE }}>*\n${{ env.DATE }}\nCC: ", + "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_SW_VALUE }}>*\n${{ env.DATE }}\nCC: ", "blocks": [ { "type": "header", @@ -221,7 +221,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*<${{ env.NPM_PACKAGE_URL }}|v${{ env.CURRENT_VERSION_SW_VALUE }}>*\n${{ env.DATE }}\nCC: " + "text": "*<${{ env.NPM_PACKAGE_URL }}|v${{ env.CURRENT_VERSION_SW_VALUE }}>*\n${{ env.DATE }}\nCC: " }, "accessory": { "type": "image", @@ -255,7 +255,7 @@ jobs: channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} payload: | { - "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_COOKIE_UTILS_VALUE }}>*\n${{ env.DATE }}\nCC: ", + "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_COOKIE_UTILS_VALUE }}>*\n${{ env.DATE }}\nCC: ", "blocks": [ { "type": "header", @@ -271,7 +271,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*<${{ env.NPM_PACKAGE_URL }}|v${{ env.CURRENT_VERSION_COOKIE_UTILS_VALUE }}>*\n${{ env.DATE }}\nCC: " + "text": "*<${{ env.NPM_PACKAGE_URL }}|v${{ env.CURRENT_VERSION_COOKIE_UTILS_VALUE }}>*\n${{ env.DATE }}\nCC: " }, "accessory": { "type": "image", diff --git a/.github/workflows/deploy-sanity-suite.yml b/.github/workflows/deploy-sanity-suite.yml index 600ef9f0d3..d5522ed839 100644 --- a/.github/workflows/deploy-sanity-suite.yml +++ b/.github/workflows/deploy-sanity-suite.yml @@ -153,7 +153,7 @@ jobs: channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} payload: | { - "text": "*New Deployment: ${{ env.PROJECT_NAME }} - <${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: ", + "text": "*New Deployment: ${{ env.PROJECT_NAME }} - <${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: ", "blocks": [ { "type": "header", @@ -169,7 +169,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*<${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: " + "text": "*<${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: " }, "accessory": { "type": "image", diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 801fc3e96f..794a6c0e26 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -233,7 +233,7 @@ jobs: channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} payload: | { - "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: ", + "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: ", "blocks": [ { "type": "header", @@ -249,7 +249,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*<${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: " + "text": "*<${{ env.CDN_URL }}|${{ env.LINK_TEXT }}>*\n${{ env.DATE }}\nCC: " }, "accessory": { "type": "image", diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml index c03c086fb8..836fe8d05b 100644 --- a/.github/workflows/publish-new-release.yml +++ b/.github/workflows/publish-new-release.yml @@ -117,7 +117,7 @@ jobs: channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} payload: | { - "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.TAG_COMPARE_URL }}${{ env.last_monorepo_version }}...v${{ steps.extract-version.outputs.release_version }}|v${{ steps.extract-version.outputs.release_version }}>*\n${{ env.DATE }}\nCC: ", + "text": "*New Release: ${{ env.PROJECT_NAME }} - <${{ env.TAG_COMPARE_URL }}${{ env.last_monorepo_version }}...v${{ steps.extract-version.outputs.release_version }}|v${{ steps.extract-version.outputs.release_version }}>*\n${{ env.DATE }}\nCC: ", "blocks": [ { "type": "header", @@ -133,7 +133,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "*<${{ env.TAG_COMPARE_URL }}${{ env.last_monorepo_version }}...v${{ steps.extract-version.outputs.release_version }}|v${{ steps.extract-version.outputs.release_version }}>*\n${{ env.DATE }}\nCC: " + "text": "*<${{ env.TAG_COMPARE_URL }}${{ env.last_monorepo_version }}...v${{ steps.extract-version.outputs.release_version }}|v${{ steps.extract-version.outputs.release_version }}>*\n${{ env.DATE }}\nCC: " }, "accessory": { "type": "image", diff --git a/CODEOWNERS b/CODEOWNERS index ba26277d24..ec64eff795 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,12 +1,12 @@ * @rudderlabs/js-sdk -/packages/analytics-js-integrations/src/ @rudderlabs/js-sdk-dm-intg-eng -/packages/analytics-js-integrations/__fixtures__/ @rudderlabs/js-sdk-dm-intg-eng -/packages/analytics-js-integrations/__tests__/ @rudderlabs/js-sdk-dm-intg-eng -/packages/analytics-js-integrations/__mocks__/ @rudderlabs/js-sdk-dm-intg-eng -/packages/analytics-js-integrations/README.md @rudderlabs/js-sdk-dm-intg-eng -/packages/analytics-js-integrations/.size-limit.js @rudderlabs/js-sdk-dm-intg-eng +/packages/analytics-js-integrations/src/ @rudderlabs/integrations +/packages/analytics-js-integrations/__fixtures__/ @rudderlabs/integrations +/packages/analytics-js-integrations/__tests__/ @rudderlabs/integrations +/packages/analytics-js-integrations/__mocks__/ @rudderlabs/integrations +/packages/analytics-js-integrations/README.md @rudderlabs/integrations +/packages/analytics-js-integrations/.size-limit.js @rudderlabs/integrations -/packages/analytics-js-common/src/constants/integrations/ @rudderlabs/js-sdk-dm-intg-eng -/assets/integrations/AdobeAnalytics/ @rudderlabs/js-sdk-dm-intg-eng -/examples/integrations/ @rudderlabs/js-sdk-dm-intg-eng +/packages/analytics-js-common/src/constants/integrations/ @rudderlabs/integrations +/assets/integrations/AdobeAnalytics/ @rudderlabs/integrations +/examples/integrations/ @rudderlabs/integrations diff --git a/packages/analytics-js-common/__tests__/utilities/errors.test.ts b/packages/analytics-js-common/__tests__/utilities/errors.test.ts new file mode 100644 index 0000000000..d1dab56190 --- /dev/null +++ b/packages/analytics-js-common/__tests__/utilities/errors.test.ts @@ -0,0 +1,18 @@ +import { dispatchErrorEvent } from '../../src/utilities/errors'; + +describe('Errors - utilities', () => { + describe('dispatchErrorEvent', () => { + it('should dispatch an error event', () => { + const dispatchEvent = jest.fn(); + const originalDispatchEvent = globalThis.dispatchEvent; + + globalThis.dispatchEvent = dispatchEvent; + const error = new Error('Test error'); + dispatchErrorEvent(error); + expect(dispatchEvent).toHaveBeenCalledWith(new ErrorEvent('error', { error })); + + // Cleanup + globalThis.dispatchEvent = originalDispatchEvent; + }); + }); +}); diff --git a/packages/analytics-js-common/src/constants/loggerContexts.ts b/packages/analytics-js-common/src/constants/loggerContexts.ts index 2aa4f7f858..65583c9774 100644 --- a/packages/analytics-js-common/src/constants/loggerContexts.ts +++ b/packages/analytics-js-common/src/constants/loggerContexts.ts @@ -11,7 +11,7 @@ const LOAD_CONFIGURATION = 'LoadConfiguration'; const EVENT_REPOSITORY = 'EventRepository'; const EXTERNAL_SRC_LOADER = 'ExternalSrcLoader'; const HTTP_CLIENT = 'HttpClient'; -const RS_APP = 'RudderStackApplication'; +const RSA = 'RudderStackAnalytics'; const ANALYTICS_CORE = 'AnalyticsCore'; export { @@ -28,6 +28,6 @@ export { EVENT_REPOSITORY, EXTERNAL_SRC_LOADER, HTTP_CLIENT, - RS_APP, + RSA, ANALYTICS_CORE, }; diff --git a/packages/analytics-js-common/src/types/ApiObject.ts b/packages/analytics-js-common/src/types/ApiObject.ts index 1265ec70d6..a72a68bacf 100644 --- a/packages/analytics-js-common/src/types/ApiObject.ts +++ b/packages/analytics-js-common/src/types/ApiObject.ts @@ -10,6 +10,6 @@ export type ApiObject = { | ApiObject | null | Date - | (string | number | boolean | null | Date | ApiObject)[] + | (string | number | boolean | null | Date | ApiObject | undefined)[] | undefined; }; diff --git a/packages/analytics-js-common/src/types/IRudderAnalytics.ts b/packages/analytics-js-common/src/types/IRudderAnalytics.ts index c9dcdbdfbc..3663b78c54 100644 --- a/packages/analytics-js-common/src/types/IRudderAnalytics.ts +++ b/packages/analytics-js-common/src/types/IRudderAnalytics.ts @@ -85,7 +85,7 @@ export interface IRudderAnalytics { /** * Get the instance of Analytics that is set as default */ - getAnalyticsInstance(writeKey?: string): T; + getAnalyticsInstance(writeKey?: string): T | undefined; /** * Trigger load event in buffer queue if exists @@ -188,7 +188,7 @@ export interface IRudderAnalytics { /** * To fetch the current sessionId */ - getSessionId(): Nullable; + getSessionId(): Nullable | undefined; /** * To provide consent diff --git a/packages/analytics-js-common/src/utilities/errors.ts b/packages/analytics-js-common/src/utilities/errors.ts index 59bf78b1ca..29768eb791 100644 --- a/packages/analytics-js-common/src/utilities/errors.ts +++ b/packages/analytics-js-common/src/utilities/errors.ts @@ -1,6 +1,8 @@ import { isTypeOfError } from './checks'; import { stringifyData } from './json'; +const MANUAL_ERROR_IDENTIFIER = '[MANUAL ERROR]'; + /** * Get mutated error with issue prepended to error message * @param err Original error @@ -17,4 +19,11 @@ const getMutatedError = (err: any, issue: string): Error => { return finalError; }; -export { getMutatedError }; +const dispatchErrorEvent = (error: any) => { + if (isTypeOfError(error)) { + error.stack = `${error.stack ?? ''}\n${MANUAL_ERROR_IDENTIFIER}`; + } + (globalThis as typeof window).dispatchEvent(new ErrorEvent('error', { error })); +}; + +export { getMutatedError, dispatchErrorEvent, MANUAL_ERROR_IDENTIFIER }; diff --git a/packages/analytics-js-common/src/utilities/eventMethodOverloads.ts b/packages/analytics-js-common/src/utilities/eventMethodOverloads.ts index 4a7f063819..f92b823ca9 100644 --- a/packages/analytics-js-common/src/utilities/eventMethodOverloads.ts +++ b/packages/analytics-js-common/src/utilities/eventMethodOverloads.ts @@ -6,6 +6,7 @@ import { isObjectLiteralAndNotNull, mergeDeepRight } from './object'; import { isDefined, isDefinedAndNotNull, isFunction, isNull, isString } from './checks'; import { tryStringify } from './string'; import type { IdentifyTraits } from '../types/traits'; +import { getSanitizedValue } from './json'; export type PageCallOptions = { category?: string; @@ -56,64 +57,70 @@ const pageArgumentsToCallOptions = ( options?: Nullable | ApiCallback, callback?: ApiCallback, ): PageCallOptions => { + const sanitizedCategory = getSanitizedValue(category); + const sanitizedName = getSanitizedValue(name); + const sanitizedProperties = getSanitizedValue(properties); + const sanitizedOptions = getSanitizedValue(options); + const sanitizedCallback = getSanitizedValue(callback); + const payload: PageCallOptions = { - category: category as string, - name: name as string, - properties: properties as Nullable, - options: options as Nullable, + category: sanitizedCategory as string, + name: sanitizedName as string, + properties: sanitizedProperties as Nullable, + options: sanitizedOptions as Nullable, callback: undefined, }; - if (isFunction(callback)) { - payload.callback = callback; + if (isFunction(sanitizedCallback)) { + payload.callback = sanitizedCallback; } - if (isFunction(options)) { - payload.category = category as string; - payload.name = name as string; - payload.properties = properties as Nullable; + if (isFunction(sanitizedOptions)) { + payload.category = sanitizedCategory as string; + payload.name = sanitizedName as string; + payload.properties = sanitizedProperties as Nullable; payload.options = undefined; - payload.callback = options; + payload.callback = sanitizedOptions; } - if (isFunction(properties)) { - payload.category = category as string; - payload.name = name as string; + if (isFunction(sanitizedProperties)) { + payload.category = sanitizedCategory as string; + payload.name = sanitizedName as string; payload.properties = undefined; payload.options = undefined; - payload.callback = properties; + payload.callback = sanitizedProperties; } - if (isFunction(name)) { - payload.category = category as string; + if (isFunction(sanitizedName)) { + payload.category = sanitizedCategory as string; payload.name = undefined; payload.properties = undefined; payload.options = undefined; - payload.callback = name; + payload.callback = sanitizedName; } - if (isFunction(category)) { + if (isFunction(sanitizedCategory)) { payload.category = undefined; payload.name = undefined; payload.properties = undefined; payload.options = undefined; - payload.callback = category; + payload.callback = sanitizedCategory; } - if (isObjectLiteralAndNotNull(category)) { + if (isObjectLiteralAndNotNull(sanitizedCategory)) { payload.name = undefined; payload.category = undefined; - payload.properties = category as Nullable; - if (!isFunction(name)) { - payload.options = name as Nullable; + payload.properties = sanitizedCategory as Nullable; + if (!isFunction(sanitizedName)) { + payload.options = sanitizedName as Nullable; } else { payload.options = undefined; } - } else if (isObjectLiteralAndNotNull(name)) { + } else if (isObjectLiteralAndNotNull(sanitizedName)) { payload.name = undefined; - payload.properties = name as Nullable; - if (!isFunction(properties)) { - payload.options = properties as Nullable; + payload.properties = sanitizedName as Nullable; + if (!isFunction(sanitizedProperties)) { + payload.options = sanitizedProperties as Nullable; } else { payload.options = undefined; } @@ -121,9 +128,9 @@ const pageArgumentsToCallOptions = ( // if the category argument alone is provided b/w category and name, // use it as name and set category to undefined - if (isString(category) && !isString(name)) { + if (isString(sanitizedCategory) && !isString(sanitizedName)) { payload.category = undefined; - payload.name = category; + payload.name = sanitizedCategory; } // Rest of the code is just to clean up undefined values @@ -171,27 +178,32 @@ const trackArgumentsToCallOptions = ( options?: Nullable | ApiCallback, callback?: ApiCallback, ): TrackCallOptions => { + const sanitizedEvent = getSanitizedValue(event); + const sanitizedProperties = getSanitizedValue(properties); + const sanitizedOptions = getSanitizedValue(options); + const sanitizedCallback = getSanitizedValue(callback); + const payload: TrackCallOptions = { - name: event, - properties: properties as Nullable, - options: options as Nullable, + name: sanitizedEvent, + properties: sanitizedProperties as Nullable, + options: sanitizedOptions as Nullable, callback: undefined, }; - if (isFunction(callback)) { - payload.callback = callback; + if (isFunction(sanitizedCallback)) { + payload.callback = sanitizedCallback; } - if (isFunction(options)) { - payload.properties = properties as Nullable; + if (isFunction(sanitizedOptions)) { + payload.properties = sanitizedProperties as Nullable; payload.options = undefined; - payload.callback = options; + payload.callback = sanitizedOptions; } - if (isFunction(properties)) { + if (isFunction(sanitizedProperties)) { payload.properties = undefined; payload.options = undefined; - payload.callback = properties; + payload.callback = sanitizedProperties; } // Rest of the code is just to clean up undefined values @@ -217,38 +229,43 @@ const identifyArgumentsToCallOptions = ( options?: Nullable | ApiCallback, callback?: ApiCallback, ): IdentifyCallOptions => { + const sanitizedUserId = getSanitizedValue(userId); + const sanitizedTraits = getSanitizedValue(traits); + const sanitizedOptions = getSanitizedValue(options); + const sanitizedCallback = getSanitizedValue(callback); + const payload: IdentifyCallOptions = { - userId: userId as string, - traits: traits as Nullable, - options: options as Nullable, + userId: sanitizedUserId as string, + traits: sanitizedTraits as Nullable, + options: sanitizedOptions as Nullable, callback: undefined, }; - if (isFunction(callback)) { - payload.callback = callback; + if (isFunction(sanitizedCallback)) { + payload.callback = sanitizedCallback; } - if (isFunction(options)) { - payload.userId = userId as string; - payload.traits = traits as Nullable; + if (isFunction(sanitizedOptions)) { + payload.userId = sanitizedUserId as string; + payload.traits = sanitizedTraits as Nullable; payload.options = undefined; - payload.callback = options; + payload.callback = sanitizedOptions; } - if (isFunction(traits)) { - payload.userId = userId as string; + if (isFunction(sanitizedTraits)) { + payload.userId = sanitizedUserId as string; payload.traits = undefined; payload.options = undefined; - payload.callback = traits; + payload.callback = sanitizedTraits; } - if (isObjectLiteralAndNotNull(userId) || isNull(userId)) { + if (isObjectLiteralAndNotNull(sanitizedUserId) || isNull(sanitizedUserId)) { // Explicitly set null to prevent resetting the existing value // in the Analytics class payload.userId = null; - payload.traits = userId as Nullable; - if (!isFunction(traits)) { - payload.options = traits as Nullable; + payload.traits = sanitizedUserId as Nullable; + if (!isFunction(sanitizedTraits)) { + payload.options = sanitizedTraits as Nullable; } else { payload.options = undefined; } @@ -283,33 +300,38 @@ const aliasArgumentsToCallOptions = ( options?: Nullable | ApiCallback, callback?: ApiCallback, ): AliasCallOptions => { + const sanitizedTo = getSanitizedValue(to); + const sanitizedFrom = getSanitizedValue(from); + const sanitizedOptions = getSanitizedValue(options); + const sanitizedCallback = getSanitizedValue(callback); + const payload: AliasCallOptions = { - to, - from: from as string, - options: options as Nullable, + to: sanitizedTo, + from: sanitizedFrom as string, + options: sanitizedOptions as Nullable, callback: undefined, }; - if (isFunction(callback)) { - payload.callback = callback; + if (isFunction(sanitizedCallback)) { + payload.callback = sanitizedCallback; } - if (isFunction(options)) { - payload.to = to; - payload.from = from as string; + if (isFunction(sanitizedOptions)) { + payload.to = sanitizedTo; + payload.from = sanitizedFrom as string; payload.options = undefined; - payload.callback = options; + payload.callback = sanitizedOptions; } - if (isFunction(from)) { - payload.to = to; + if (isFunction(sanitizedFrom)) { + payload.to = sanitizedTo; payload.from = undefined; payload.options = undefined; - payload.callback = from; - } else if (isObjectLiteralAndNotNull(from) || isNull(from)) { - payload.to = to; + payload.callback = sanitizedFrom; + } else if (isObjectLiteralAndNotNull(sanitizedFrom) || isNull(sanitizedFrom)) { + payload.to = sanitizedTo; payload.from = undefined; - payload.options = from as Nullable; + payload.options = sanitizedFrom as Nullable; } // Rest of the code is just to clean up undefined values @@ -343,38 +365,43 @@ const groupArgumentsToCallOptions = ( options?: Nullable | ApiCallback, callback?: ApiCallback, ): GroupCallOptions => { + const sanitizedGroupId = getSanitizedValue(groupId); + const sanitizedTraits = getSanitizedValue(traits); + const sanitizedOptions = getSanitizedValue(options); + const sanitizedCallback = getSanitizedValue(callback); + const payload: GroupCallOptions = { - groupId: groupId as string, - traits: traits as Nullable, - options: options as Nullable, + groupId: sanitizedGroupId as string, + traits: sanitizedTraits as Nullable, + options: sanitizedOptions as Nullable, callback: undefined, }; - if (isFunction(callback)) { - payload.callback = callback; + if (isFunction(sanitizedCallback)) { + payload.callback = sanitizedCallback; } - if (isFunction(options)) { - payload.groupId = groupId as string; - payload.traits = traits as Nullable; + if (isFunction(sanitizedOptions)) { + payload.groupId = sanitizedGroupId as string; + payload.traits = sanitizedTraits as Nullable; payload.options = undefined; - payload.callback = options; + payload.callback = sanitizedOptions; } - if (isFunction(traits)) { - payload.groupId = groupId as string; + if (isFunction(sanitizedTraits)) { + payload.groupId = sanitizedGroupId as string; payload.traits = undefined; payload.options = undefined; - payload.callback = traits; + payload.callback = sanitizedTraits; } - if (isObjectLiteralAndNotNull(groupId) || isNull(groupId)) { + if (isObjectLiteralAndNotNull(sanitizedGroupId) || isNull(sanitizedGroupId)) { // Explicitly set null to prevent resetting the existing value // in the Analytics class payload.groupId = null; - payload.traits = groupId as Nullable; - if (!isFunction(traits)) { - payload.options = traits as Nullable; + payload.traits = sanitizedGroupId as Nullable; + if (!isFunction(sanitizedTraits)) { + payload.options = sanitizedTraits as Nullable; } else { payload.options = undefined; } diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index dc241cc4a3..b002464866 100644 --- a/packages/analytics-js/.size-limit.mjs +++ b/packages/analytics-js/.size-limit.mjs @@ -24,7 +24,7 @@ export default [ { name: 'Core - Legacy - CDN', path: 'dist/cdn/legacy/iife/rsa.min.js', - limit: '48.5 KiB', + limit: '49 KiB', }, { name: 'Core - Modern - NPM (ESM)', @@ -95,7 +95,7 @@ export default [ name: 'Core (Content Script) - Legacy - NPM (CJS)', path: 'dist/npm/legacy/content-script/cjs/index.cjs', import: '*', - limit: '48 KiB', + limit: '48.5 KiB', }, { name: 'Core (Content Script) - Legacy - NPM (UMD)', diff --git a/packages/analytics-js/__tests__/app/RudderAnalytics.test.ts b/packages/analytics-js/__tests__/app/RudderAnalytics.test.ts index 85afcbd90b..690c96114b 100644 --- a/packages/analytics-js/__tests__/app/RudderAnalytics.test.ts +++ b/packages/analytics-js/__tests__/app/RudderAnalytics.test.ts @@ -16,7 +16,6 @@ describe('Core - Rudder Analytics Facade', () => { } as LoadOptions; beforeEach(() => { - Analytics.mockClear(); analyticsInstanceMock = new Analytics() as jest.Mocked; (window as any).rudderanalytics = [ ['track'], @@ -31,11 +30,10 @@ describe('Core - Rudder Analytics Facade', () => { }); afterEach(() => { - (rudderAnalytics as any).globalSingleton = null; jest.resetAllMocks(); }); - it('should return the global singleton from "rudderanalytics" global object', done => { + it('should return the global singleton from "rudderanalytics" global object', () => { const expectedPreloadedEvents = [ ['consent', { sendPageEvent: true }], ['consent', { sendPageEvent: false }], @@ -46,7 +44,6 @@ describe('Core - Rudder Analytics Facade', () => { expect(window.RudderStackGlobals?.app?.preloadedEventsBuffer).toEqual(expectedPreloadedEvents); expect(window.rudderanalytics).toEqual(globalSingleton); - done(); }); it('should retrieve all preloaded events and set to global', () => { @@ -58,6 +55,27 @@ describe('Core - Rudder Analytics Facade', () => { ]); }); + it('should return an empty array when globalThis.rudderanalytics is not an array', () => { + const rudderAnalyticsInstance = new RudderAnalytics(); + (globalThis as typeof window).rudderanalytics = undefined; + const result = rudderAnalyticsInstance.getPreloadedEvents(); + expect(result).toEqual([]); + }); + + it('should return buffered events array when globalThis.rudderanalytics is an array', () => { + const bufferedEvents = [ + ['track'], + ['consent', { sendPageEvent: true }], + ['load', 'dummyWriteKey', 'dummyDataPlaneUrl', { option1: true }], + ['consent', { sendPageEvent: false }], + ['track'], + ]; + (window as any).rudderanalytics = bufferedEvents; + const rudderAnalyticsInstance = new RudderAnalytics(); + const result = rudderAnalyticsInstance.getPreloadedEvents(); + expect(result).toEqual(bufferedEvents); + }); + it('should return the global singleton if it exists', () => { const globalSingleton = rudderAnalytics; rudderAnalytics = new RudderAnalytics(); @@ -65,20 +83,54 @@ describe('Core - Rudder Analytics Facade', () => { expect(rudderAnalytics).toEqual(globalSingleton); }); + it('should dispatch an error event if an exception is thrown during the construction', () => { + const originalSingleton = RudderAnalytics.globalSingleton; + + RudderAnalytics.globalSingleton = null; + + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Explicitly throw an error during the construction + const nowSpy = jest.spyOn(Date, 'now').mockImplementation(() => { + throw new Error('Error in now function'); + }); + + // eslint-disable-next-line sonarjs/constructor-for-side-effects, no-new + new RudderAnalytics(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in now function'), + }), + ); + + RudderAnalytics.globalSingleton = originalSingleton; + nowSpy.mockRestore(); + + dispatchEventSpy.mockRestore(); + }); + it('should auto set the default analytics key if no analytics instances exist', () => { (rudderAnalytics as any).analyticsInstances = {}; (rudderAnalytics as any).defaultAnalyticsKey = ''; + rudderAnalytics.setDefaultInstanceKey('writeKey'); expect(rudderAnalytics.defaultAnalyticsKey).toEqual('writeKey'); }); - it('should auto set the default analytics key if analytics instances exist', () => { + it('should set the default analytics key even if analytics instances exist', () => { rudderAnalytics.setDefaultInstanceKey('writeKey2'); expect(rudderAnalytics.defaultAnalyticsKey).toEqual('writeKey2'); }); + it('should not set default analytics key if the key is not a valid string', () => { + rudderAnalytics.setDefaultInstanceKey(''); + + expect(rudderAnalytics.defaultAnalyticsKey).toEqual('writeKey'); + }); + it('should return an existing analytics instance', () => { expect(rudderAnalytics.getAnalyticsInstance('writeKey')).toStrictEqual(analyticsInstanceMock); }); @@ -106,6 +158,25 @@ describe('Core - Rudder Analytics Facade', () => { expect(rudderAnalytics.getAnalyticsInstance('writeKey')).toStrictEqual(analyticsInstance); }); + it('should return undefined and log error if an exception is thrown while getting the analytics instance', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally set the parameter to undefined to trigger an error + (rudderAnalytics as any).analyticsInstances = undefined; + + const result = rudderAnalytics.getAnalyticsInstance('writeKey2'); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new TypeError("Cannot read properties of undefined (reading 'writeKey2')"), + }), + ); + + expect(result).toBeUndefined(); + + dispatchEventSpy.mockRestore(); + }); + it('should set the default analytics key if none has been set', () => { rudderAnalytics.load('writeKey', 'data-plane-url'); @@ -116,169 +187,390 @@ describe('Core - Rudder Analytics Facade', () => { rudderAnalytics.analyticsInstances = {}; rudderAnalytics.defaultAnalyticsKey = ''; rudderAnalytics.load('writeKey', 'data-plane-url', mockLoadOptions); - const analyticsInstance = rudderAnalytics.getAnalyticsInstance('writeKey'); + const analyticsInstance = rudderAnalytics.getAnalyticsInstance('writeKey') as Analytics; const loadSpy = jest.spyOn(analyticsInstance, 'load'); expect(rudderAnalytics.analyticsInstances).toHaveProperty('writeKey', analyticsInstance); expect(loadSpy).toHaveBeenCalledWith('writeKey', 'data-plane-url', mockLoadOptions); }); + it('should dispatch an error event if an exception is thrown during the load', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally set the parameter to undefined to trigger an error + (rudderAnalytics as any).analyticsInstances = undefined; + + rudderAnalytics.defaultAnalyticsKey = ''; + rudderAnalytics.load('writeKey', 'data-plane-url', mockLoadOptions); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new TypeError("Cannot read properties of undefined (reading 'writeKey')"), + }), + ); + + dispatchEventSpy.mockRestore(); + }); + it('should process ready arguments and forwards to ready call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const readySpy = jest.spyOn(analyticsInstance, 'ready'); + const callback = () => console.log('Ready!'); + + rudderAnalytics.ready(callback); + expect(analyticsInstanceMock.ready).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('should dispatch an error event if an exception is thrown during the ready call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the ready call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); const callback = () => console.log('Ready!'); rudderAnalytics.ready(callback); - expect(readySpy).toHaveBeenCalledWith(expect.any(Function)); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + getAnalyticsInstanceSpy.mockRestore(); }); it('should process page arguments and forwards to page call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const pageSpy = jest.spyOn(analyticsInstance, 'page'); - rudderAnalytics.page('category'); - expect(pageSpy).toHaveBeenCalledWith({ + expect(analyticsInstanceMock.page).toHaveBeenCalledWith({ name: 'category', properties: { name: 'category' }, }); }); + it('should dispatch an error event if an exception is thrown during the page call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the page call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + rudderAnalytics.page('category'); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); + }); + it('should process track arguments and forwards to track call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const trackSpy = jest.spyOn(analyticsInstance, 'track'); + rudderAnalytics.track('event'); + expect(analyticsInstanceMock.track).toHaveBeenCalledWith({ name: 'event', properties: {} }); + }); + + it('should dispatch an error event if an exception is thrown during the track call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the track call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); rudderAnalytics.track('event'); - expect(trackSpy).toHaveBeenCalledWith({ name: 'event', properties: {} }); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process identify arguments and forwards to identify call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const identifySpy = jest.spyOn(analyticsInstance, 'identify'); + rudderAnalytics.identify('1234'); + expect(analyticsInstanceMock.identify).toHaveBeenCalledWith({ userId: '1234' }); + }); + + it('should dispatch an error event if an exception is thrown during the identify call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the identify call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); - rudderAnalytics.identify(1234); - expect(identifySpy).toHaveBeenCalledWith({ userId: '1234' }); + rudderAnalytics.identify('1234'); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process alias arguments and forwards to alias call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const aliasSpy = jest.spyOn(analyticsInstance, 'alias'); + rudderAnalytics.alias('abc'); + expect(analyticsInstanceMock.alias).toHaveBeenCalledWith({ to: 'abc' }); + }); + + it('should dispatch an error event if an exception is thrown during the alias call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the alias call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + rudderAnalytics.alias('abc'); - rudderAnalytics.alias('1234'); - expect(aliasSpy).toHaveBeenCalledWith({ to: '1234' }); + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process group arguments and forwards to group call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const groupSpy = jest.spyOn(analyticsInstance, 'group'); + rudderAnalytics.group('5678'); + expect(analyticsInstanceMock.group).toHaveBeenCalledWith({ groupId: '5678' }); + }); + + it('should dispatch an error event if an exception is thrown during the group call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the group call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + rudderAnalytics.group('5678'); - rudderAnalytics.group(1234); - expect(groupSpy).toHaveBeenCalledWith({ groupId: '1234' }); + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process reset arguments and forwards to reset call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const resetSpy = jest.spyOn(analyticsInstance, 'reset'); + rudderAnalytics.reset(true); + expect(analyticsInstanceMock.reset).toHaveBeenCalledWith(true); + }); + + it('should dispatch an error event if an exception is thrown during the reset call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the reset call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); rudderAnalytics.reset(true); - expect(resetSpy).toHaveBeenCalledWith(true); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process getAnonymousId arguments and forwards to getAnonymousId call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const getAnonymousIdSpy = jest.spyOn(analyticsInstance, 'getAnonymousId'); - rudderAnalytics.getAnonymousId({ autoCapture: { enabled: true, }, }); - expect(getAnonymousIdSpy).toHaveBeenCalledWith({ autoCapture: { enabled: true } }); + expect(analyticsInstanceMock.getAnonymousId).toHaveBeenCalledWith({ + autoCapture: { enabled: true }, + }); }); - it('should process setAnonymousId arguments and forwards to setAnonymousId call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const setAnonymousIdSpy = jest.spyOn(analyticsInstance, 'setAnonymousId'); + it('should return undefined and log an error if an exception is thrown during the getAnonymousId call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the getAnonymousId call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + const result = rudderAnalytics.getAnonymousId(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + expect(result).toBeUndefined(); + + getAnalyticsInstanceSpy.mockRestore(); + }); + + it('should process setAnonymousId arguments and forwards to setAnonymousId call', () => { rudderAnalytics.setAnonymousId('id', 'param'); - expect(setAnonymousIdSpy).toHaveBeenCalledWith('id', 'param'); + expect(analyticsInstanceMock.setAnonymousId).toHaveBeenCalledWith('id', 'param'); }); - it('should process getUserId arguments and forwards to getUserId call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const getUserIdSpy = jest.spyOn(analyticsInstance, 'getUserId'); + it('should dispatch an error event if an exception is thrown during the setAnonymousId call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + // Intentionally cause an error during the setAnonymousId call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + rudderAnalytics.setAnonymousId('id'); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); + }); + + it('should process getUserId arguments and forwards to getUserId call', () => { rudderAnalytics.getUserId(); - expect(getUserIdSpy).toHaveBeenCalledTimes(1); + expect(analyticsInstanceMock.getUserId).toHaveBeenCalledTimes(1); }); it('should process getUserTraits arguments and forwards to getUserTraits call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const getUserTraitsSpy = jest.spyOn(analyticsInstance, 'getUserTraits'); - rudderAnalytics.getUserTraits(); - expect(getUserTraitsSpy).toHaveBeenCalledTimes(1); + expect(analyticsInstanceMock.getUserTraits).toHaveBeenCalledTimes(1); }); it('should process getGroupId arguments and forwards to getGroupId call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const getGroupIdSpy = jest.spyOn(analyticsInstance, 'getGroupId'); - rudderAnalytics.getGroupId(); - expect(getGroupIdSpy).toHaveBeenCalledTimes(1); + expect(analyticsInstanceMock.getGroupId).toHaveBeenCalledTimes(1); }); it('should process getGroupTraits arguments and forwards to getGroupTraits call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const getGroupTraitsSpy = jest.spyOn(analyticsInstance, 'getGroupTraits'); - rudderAnalytics.getGroupTraits(); - expect(getGroupTraitsSpy).toHaveBeenCalledTimes(1); + expect(analyticsInstanceMock.getGroupTraits).toHaveBeenCalledTimes(1); }); it('should process startSession arguments and forwards to startSession call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const startSessionSpy = jest.spyOn(analyticsInstance, 'startSession'); + rudderAnalytics.startSession(1234); + expect(analyticsInstanceMock.startSession).toHaveBeenCalledWith(1234); + }); + + it('should dispatch an error event if an exception is thrown during the startSession call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the startSession call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); rudderAnalytics.startSession(1234); - expect(startSessionSpy).toHaveBeenCalledWith(1234); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process endSession arguments and forwards to endSession call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const endSessionSpy = jest.spyOn(analyticsInstance, 'endSession'); - rudderAnalytics.endSession(); - expect(endSessionSpy).toHaveBeenCalledTimes(1); + expect(analyticsInstanceMock.endSession).toHaveBeenCalledTimes(1); }); it('should process getSessionId arguments and forwards to getSessionId call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const getSessionIdSpy = jest.spyOn(analyticsInstance, 'getSessionId'); - rudderAnalytics.getSessionId(); - expect(getSessionIdSpy).toHaveBeenCalledTimes(1); + expect(analyticsInstanceMock.getSessionId).toHaveBeenCalledTimes(1); }); it('should process setAuthToken arguments and forwards to setAuthToken call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const setAuthTokenSpy = jest.spyOn(analyticsInstance, 'setAuthToken'); + rudderAnalytics.setAuthToken('token'); + expect(analyticsInstanceMock.setAuthToken).toHaveBeenCalledWith('token'); + }); + + it('should dispatch an error event if an exception is thrown during the setAuthToken call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the setAuthToken call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); rudderAnalytics.setAuthToken('token'); - expect(setAuthTokenSpy).toHaveBeenCalledWith('token'); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); it('should process consent arguments and forwards to consent call', () => { - const analyticsInstance = rudderAnalytics.getAnalyticsInstance(); - const consentSpy = jest.spyOn(analyticsInstance, 'consent'); - rudderAnalytics.consent({ consentManagement: { allowedConsentIds: ['1'], deniedConsentIds: ['2'], }, }); - expect(consentSpy).toHaveBeenCalledWith({ + expect(analyticsInstanceMock.consent).toHaveBeenCalledWith({ consentManagement: { allowedConsentIds: ['1'], deniedConsentIds: ['2'], @@ -286,128 +578,283 @@ describe('Core - Rudder Analytics Facade', () => { }); }); - it('should return an empty array when globalThis.rudderanalytics is not an array', () => { - const rudderAnalyticsInstance = new RudderAnalytics(); - (globalThis as typeof window).rudderanalytics = undefined; - const result = rudderAnalyticsInstance.getPreloadedEvents(); - expect(result).toEqual([]); + it('should dispatch an error event if an exception is thrown during the consent call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the consent call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + rudderAnalytics.consent({ + consentManagement: { + allowedConsentIds: ['1'], + deniedConsentIds: ['2'], + }, + }); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); - it('should return buffered events array when globalThis.rudderanalytics is an array', () => { - const bufferedEvents = [ - ['track'], - ['consent', { sendPageEvent: true }], - ['load', 'dummyWriteKey', 'dummyDataPlaneUrl', { option1: true }], - ['consent', { sendPageEvent: false }], - ['track'], - ]; - (window as any).rudderanalytics = bufferedEvents; - const rudderAnalyticsInstance = new RudderAnalytics(); - const result = rudderAnalyticsInstance.getPreloadedEvents(); - expect(result).toEqual(bufferedEvents); + it('should dispatch an error event if an exception is thrown during the getUserId call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the getUserId call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + const userIdVal = rudderAnalytics.getUserId(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + expect(userIdVal).toBeUndefined(); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); -}); -describe('trackPageLifecycleEvents', () => { - let rudderAnalyticsInstance: RudderAnalytics; + it('should dispatch an error event if an exception is thrown during the getUserTraits call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); - beforeEach(() => { - (window as any).rudderanalytics = []; - rudderAnalyticsInstance = new RudderAnalytics(); - rudderAnalyticsInstance.analyticsInstances = {}; + // Intentionally cause an error during the getUserTraits call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + const traitsVal = rudderAnalytics.getUserTraits(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + expect(traitsVal).toBeUndefined(); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); - afterEach(() => { - (rudderAnalyticsInstance as any).globalSingleton = null; - jest.resetAllMocks(); + it('should dispatch an error event if an exception is thrown during the getGroupId call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the getGroupId call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + const groupId = rudderAnalytics.getGroupId(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + expect(groupId).toBeUndefined(); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); - it('should not add pageLifecycleEvents in the buffer when the tracking is not enabled through load options', () => { - const bufferedEvents: PreloadedEventCall[] = []; - rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, {}); + it('should dispatch an error event if an exception is thrown during the getGroupTraits call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the getGroupTraits call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + const traitsVal = rudderAnalytics.getGroupTraits(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + expect(traitsVal).toBeUndefined(); - expect(bufferedEvents).toEqual([]); + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); - it('should inherit enabled and options properties from autoTrack load option', () => { - const bufferedEvents: PreloadedEventCall[] = []; - rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { - autoTrack: { - enabled: true, - options: { key: 'value' }, - }, - }); + it('should dispatch an error event if an exception is thrown during the endSession call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); - expect(bufferedEvents).toEqual([ - ['track', 'Page Loaded', {}, { key: 'value', originalTimestamp: expect.any(String) }], - ]); + // Intentionally cause an error during the endSession call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + rudderAnalytics.endSession(); + + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); - it('should override enabled and options properties of autoTrack if provided in load option', () => { - const bufferedEvents: PreloadedEventCall[] = []; - rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { - autoTrack: { - enabled: true, - options: { key: 'value' }, - pageLifecycle: { - enabled: false, - }, - }, - }); + it('should dispatch an error event if an exception is thrown during the getSessionId call', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + // Intentionally cause an error during the getSessionId call + const getAnalyticsInstanceSpy = jest + .spyOn(rudderAnalytics, 'getAnalyticsInstance') + .mockImplementation(() => { + throw new Error('Error in getAnalyticsInstance'); + }); + + const sessionId = rudderAnalytics.getSessionId(); - expect(bufferedEvents).toEqual([]); + expect(dispatchEventSpy).toHaveBeenCalledWith( + new ErrorEvent('error', { + error: new Error('Error in getAnalyticsInstance'), + }), + ); + + expect(sessionId).toBeUndefined(); + + dispatchEventSpy.mockRestore(); + + getAnalyticsInstanceSpy.mockRestore(); }); - it('should track Page Loaded event irrespective of useBeacon load option', () => { - const bufferedEvents: PreloadedEventCall[] = []; - rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { - useBeacon: false, - autoTrack: { - pageLifecycle: { + describe('trackPageLifecycleEvents', () => { + let rudderAnalyticsInstance: RudderAnalytics; + + beforeEach(() => { + (window as any).rudderanalytics = []; + rudderAnalyticsInstance = new RudderAnalytics(); + rudderAnalyticsInstance.analyticsInstances = {}; + }); + + afterEach(() => { + (rudderAnalyticsInstance as any).globalSingleton = null; + jest.resetAllMocks(); + }); + + it('should not add pageLifecycleEvents in the buffer when the tracking is not enabled through load options', () => { + const bufferedEvents: PreloadedEventCall[] = []; + rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, {}); + + expect(bufferedEvents).toEqual([]); + }); + + it('should inherit enabled and options properties from autoTrack load option', () => { + const bufferedEvents: PreloadedEventCall[] = []; + rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { + autoTrack: { enabled: true, + options: { key: 'value' }, }, - }, - }); + }); - expect(bufferedEvents).toEqual([ - ['track', 'Page Loaded', {}, { originalTimestamp: expect.any(String) }], - ]); - }); + expect(bufferedEvents).toEqual([ + ['track', 'Page Loaded', {}, { key: 'value', originalTimestamp: expect.any(String) }], + ]); + }); - it('should track Page Unloaded event if useBeacon is set to true and trackPageLifecycle feature is enabled', () => { - const bufferedEvents: PreloadedEventCall[] = []; - rudderAnalyticsInstance.track = jest.fn(); - rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { - useBeacon: true, - autoTrack: { - pageLifecycle: { + it('should override enabled and options properties of autoTrack if provided in load option', () => { + const bufferedEvents: PreloadedEventCall[] = []; + rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { + autoTrack: { enabled: true, + options: { key: 'value' }, + pageLifecycle: { + enabled: false, + }, }, - }, + }); + + expect(bufferedEvents).toEqual([]); }); - state.lifecycle.loaded.value = true; - const event = new Event('beforeunload'); - // Simulate the event - window.dispatchEvent(event); - - expect(rudderAnalyticsInstance.track).toHaveBeenCalledWith( - 'Page Unloaded', - { visitDuration: expect.any(Number) }, - { originalTimestamp: expect.any(String) }, - ); - }); - it('should invoke trackPageLifecycleEvents method when load API is called', () => { - rudderAnalyticsInstance.trackPageLifecycleEvents = jest.fn(); - rudderAnalyticsInstance.load('writeKey', 'data-plane-url', { - autoTrack: { - enabled: true, - }, + it('should track Page Loaded event irrespective of useBeacon load option', () => { + const bufferedEvents: PreloadedEventCall[] = []; + rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { + useBeacon: false, + autoTrack: { + pageLifecycle: { + enabled: true, + }, + }, + }); + + expect(bufferedEvents).toEqual([ + ['track', 'Page Loaded', {}, { originalTimestamp: expect.any(String) }], + ]); }); - expect(rudderAnalyticsInstance.trackPageLifecycleEvents).toHaveBeenCalledWith([], { - autoTrack: { - enabled: true, - }, + + it('should track Page Unloaded event if useBeacon is set to true and trackPageLifecycle feature is enabled', () => { + const bufferedEvents: PreloadedEventCall[] = []; + rudderAnalyticsInstance.track = jest.fn(); + rudderAnalyticsInstance.trackPageLifecycleEvents(bufferedEvents, { + useBeacon: true, + autoTrack: { + pageLifecycle: { + enabled: true, + }, + }, + }); + state.lifecycle.loaded.value = true; + const event = new Event('beforeunload'); + // Simulate the event + window.dispatchEvent(event); + + expect(rudderAnalyticsInstance.track).toHaveBeenCalledWith( + 'Page Unloaded', + { visitDuration: expect.any(Number) }, + { originalTimestamp: expect.any(String) }, + ); + }); + + it('should invoke trackPageLifecycleEvents method when load API is called', () => { + rudderAnalyticsInstance.trackPageLifecycleEvents = jest.fn(); + rudderAnalyticsInstance.load('writeKey', 'data-plane-url', { + autoTrack: { + enabled: true, + }, + }); + expect(rudderAnalyticsInstance.trackPageLifecycleEvents).toHaveBeenCalledWith([], { + autoTrack: { + enabled: true, + }, + }); }); }); }); diff --git a/packages/analytics-js/src/app/RudderAnalytics.ts b/packages/analytics-js/src/app/RudderAnalytics.ts index 7d8de7add9..8ea3ed5085 100644 --- a/packages/analytics-js/src/app/RudderAnalytics.ts +++ b/packages/analytics-js/src/app/RudderAnalytics.ts @@ -17,13 +17,14 @@ import { } from '@rudderstack/analytics-js-common/types/LoadOptions'; import type { ApiCallback, ApiOptions } from '@rudderstack/analytics-js-common/types/EventApi'; import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; -import { RS_APP } from '@rudderstack/analytics-js-common/constants/loggerContexts'; +import { RSA } from '@rudderstack/analytics-js-common/constants/loggerContexts'; import type { IdentifyTraits } from '@rudderstack/analytics-js-common/types/traits'; -import { getSanitizedValue } from '@rudderstack/analytics-js-common/utilities/json'; import { generateUUID } from '@rudderstack/analytics-js-common/utilities/uuId'; import { onPageLeave } from '@rudderstack/analytics-js-common/utilities/page'; import { isString } from '@rudderstack/analytics-js-common/utilities/checks'; import { getFormattedTimestamp } from '@rudderstack/analytics-js-common/utilities/timestamp'; +import { getSanitizedValue } from '@rudderstack/analytics-js-common/utilities/json'; +import { dispatchErrorEvent } from '@rudderstack/analytics-js-common/utilities/errors'; import { GLOBAL_PRELOAD_BUFFER } from '../constants/app'; import { getPreloadedLoadEvent, @@ -34,10 +35,7 @@ import { setExposedGlobal } from '../components/utilities/globals'; import type { IAnalytics } from '../components/core/IAnalytics'; import { Analytics } from '../components/core/Analytics'; import { defaultLogger } from '../services/Logger/Logger'; -import { - EMPTY_GROUP_CALL_ERROR, - PAGE_UNLOAD_ON_BEACON_DISABLED_WARNING, -} from '../constants/logMessages'; +import { PAGE_UNLOAD_ON_BEACON_DISABLED_WARNING } from '../constants/logMessages'; import { defaultErrorHandler } from '../services/ErrorHandler'; import { state } from '../state'; @@ -50,55 +48,62 @@ import { state } from '../state'; * consume SDK preload event buffer */ class RudderAnalytics implements IRudderAnalytics { + // START-NO-SONAR-SCAN + // eslint-disable-next-line sonarjs/public-static-readonly static globalSingleton: Nullable = null; + // END-NO-SONAR-SCAN analyticsInstances: Record = {}; defaultAnalyticsKey = ''; logger = defaultLogger; // Singleton with constructor bind methods constructor() { - if (RudderAnalytics.globalSingleton) { - // START-NO-SONAR-SCAN - // eslint-disable-next-line no-constructor-return - return RudderAnalytics.globalSingleton; - // END-NO-SONAR-SCAN + try { + if (RudderAnalytics.globalSingleton) { + // START-NO-SONAR-SCAN + // eslint-disable-next-line no-constructor-return + return RudderAnalytics.globalSingleton; + // END-NO-SONAR-SCAN + } + defaultErrorHandler.attachErrorListeners(); + + this.setDefaultInstanceKey = this.setDefaultInstanceKey.bind(this); + this.getAnalyticsInstance = this.getAnalyticsInstance.bind(this); + this.load = this.load.bind(this); + this.ready = this.ready.bind(this); + this.triggerBufferedLoadEvent = this.triggerBufferedLoadEvent.bind(this); + this.page = this.page.bind(this); + this.track = this.track.bind(this); + this.identify = this.identify.bind(this); + this.alias = this.alias.bind(this); + this.group = this.group.bind(this); + this.reset = this.reset.bind(this); + this.getAnonymousId = this.getAnonymousId.bind(this); + this.setAnonymousId = this.setAnonymousId.bind(this); + this.getUserId = this.getUserId.bind(this); + this.getUserTraits = this.getUserTraits.bind(this); + this.getGroupId = this.getGroupId.bind(this); + this.getGroupTraits = this.getGroupTraits.bind(this); + this.startSession = this.startSession.bind(this); + this.endSession = this.endSession.bind(this); + this.getSessionId = this.getSessionId.bind(this); + this.setAuthToken = this.setAuthToken.bind(this); + this.consent = this.consent.bind(this); + + RudderAnalytics.globalSingleton = this; + + state.autoTrack.pageLifecycle.visitId.value = generateUUID(); + state.autoTrack.pageLifecycle.pageLoadedTimestamp.value = Date.now(); + + // start loading if a load event was buffered or wait for explicit load call + this.triggerBufferedLoadEvent(); + + // Assign to global "rudderanalytics" object after processing the preload buffer (if any exists) + // for CDN bundling IIFE exports covers this but for npm ESM and CJS bundling has to be done explicitly + (globalThis as typeof window).rudderanalytics = this; + } catch (error: any) { + dispatchErrorEvent(error); } - defaultErrorHandler.attachErrorListeners(); - - this.setDefaultInstanceKey = this.setDefaultInstanceKey.bind(this); - this.getAnalyticsInstance = this.getAnalyticsInstance.bind(this); - this.load = this.load.bind(this); - this.ready = this.ready.bind(this); - this.triggerBufferedLoadEvent = this.triggerBufferedLoadEvent.bind(this); - this.page = this.page.bind(this); - this.track = this.track.bind(this); - this.identify = this.identify.bind(this); - this.alias = this.alias.bind(this); - this.group = this.group.bind(this); - this.reset = this.reset.bind(this); - this.getAnonymousId = this.getAnonymousId.bind(this); - this.setAnonymousId = this.setAnonymousId.bind(this); - this.getUserId = this.getUserId.bind(this); - this.getUserTraits = this.getUserTraits.bind(this); - this.getGroupId = this.getGroupId.bind(this); - this.getGroupTraits = this.getGroupTraits.bind(this); - this.startSession = this.startSession.bind(this); - this.endSession = this.endSession.bind(this); - this.getSessionId = this.getSessionId.bind(this); - this.setAuthToken = this.setAuthToken.bind(this); - this.consent = this.consent.bind(this); - - RudderAnalytics.globalSingleton = this; - - state.autoTrack.pageLifecycle.visitId.value = generateUUID(); - state.autoTrack.pageLifecycle.pageLoadedTimestamp.value = Date.now(); - - // start loading if a load event was buffered or wait for explicit load call - this.triggerBufferedLoadEvent(); - - // Assign to global "rudderanalytics" object after processing the preload buffer (if any exists) - // for CDN bundling IIFE exports covers this but for npm ESM and CJS bundling has to be done explicitly - (globalThis as typeof window).rudderanalytics = this; } /** @@ -107,6 +112,10 @@ class RudderAnalytics implements IRudderAnalytics { * TODO: to support multiple analytics instances in the near future */ setDefaultInstanceKey(writeKey: string) { + // IMP: Add try-catch block to handle any unhandled errors + // similar to other public methods + // if the implementation of this method goes beyond + // this simple implementation if (isString(writeKey) && writeKey) { this.defaultAnalyticsKey = writeKey; } @@ -115,19 +124,24 @@ class RudderAnalytics implements IRudderAnalytics { /** * Retrieve an existing analytics instance */ - getAnalyticsInstance(writeKey?: string): IAnalytics { - let instanceId = writeKey; - if (!isString(instanceId) || !instanceId) { - instanceId = this.defaultAnalyticsKey; - } + getAnalyticsInstance(writeKey?: string): IAnalytics | undefined { + try { + let instanceId = writeKey; + if (!isString(instanceId) || !instanceId) { + instanceId = this.defaultAnalyticsKey; + } - const analyticsInstanceExists = Boolean(this.analyticsInstances[instanceId]); + const analyticsInstanceExists = Boolean(this.analyticsInstances[instanceId]); - if (!analyticsInstanceExists) { - this.analyticsInstances[instanceId] = new Analytics(); - } + if (!analyticsInstanceExists) { + this.analyticsInstances[instanceId] = new Analytics(); + } - return this.analyticsInstances[instanceId] as IAnalytics; + return this.analyticsInstances[instanceId] as IAnalytics; + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } /** @@ -137,28 +151,32 @@ class RudderAnalytics implements IRudderAnalytics { * @param loadOptions Additional options for loading the SDK * @returns none */ - load(writeKey: string, dataPlaneUrl: string, loadOptions?: Partial) { - if (this.analyticsInstances[writeKey]) { - return; - } + load(writeKey: string, dataPlaneUrl: string, loadOptions?: Partial): void { + try { + if (this.analyticsInstances[writeKey]) { + return; + } - this.setDefaultInstanceKey(writeKey); - const preloadedEventsArray = this.getPreloadedEvents(); + this.setDefaultInstanceKey(writeKey); + const preloadedEventsArray = this.getPreloadedEvents(); - // Track page loaded lifecycle event if enabled - this.trackPageLifecycleEvents(preloadedEventsArray, loadOptions); + // Track page loaded lifecycle event if enabled + this.trackPageLifecycleEvents(preloadedEventsArray, loadOptions); - // The array will be mutated in the below method - promotePreloadedConsentEventsToTop(preloadedEventsArray); + // The array will be mutated in the below method + promotePreloadedConsentEventsToTop(preloadedEventsArray); - setExposedGlobal(GLOBAL_PRELOAD_BUFFER, clone(preloadedEventsArray)); + setExposedGlobal(GLOBAL_PRELOAD_BUFFER, clone(preloadedEventsArray)); - this.analyticsInstances[writeKey] = new Analytics(); - this.getAnalyticsInstance(writeKey).load( - writeKey, - dataPlaneUrl, - getSanitizedValue(loadOptions), - ); + this.analyticsInstances[writeKey] = new Analytics(); + this.getAnalyticsInstance(writeKey)?.load( + writeKey, + dataPlaneUrl, + getSanitizedValue(loadOptions), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -216,7 +234,7 @@ class RudderAnalytics implements IRudderAnalytics { * @param preloadedEventsArray */ // eslint-disable-next-line class-methods-use-this - private trackPageLoadedEvent( + trackPageLoadedEvent( events: PageLifecycleEvents[], options: ApiOptions, preloadedEventsArray: PreloadedEventCall[], @@ -242,7 +260,7 @@ class RudderAnalytics implements IRudderAnalytics { * @param useBeacon * @param options */ - private setupPageUnloadTracking( + setupPageUnloadTracking( events: PageLifecycleEvents[], useBeacon: boolean | undefined, options: ApiOptions, @@ -270,7 +288,7 @@ class RudderAnalytics implements IRudderAnalytics { }); } else { // throw warning if beacon is disabled - this.logger.warn(PAGE_UNLOAD_ON_BEACON_DISABLED_WARNING(RS_APP)); + this.logger.warn(PAGE_UNLOAD_ON_BEACON_DISABLED_WARNING(RSA)); } } } @@ -304,7 +322,11 @@ class RudderAnalytics implements IRudderAnalytics { * Get ready callback arguments and forward to ready call */ ready(callback: ApiCallback) { - this.getAnalyticsInstance().ready(callback); + try { + this.getAnalyticsInstance()?.ready(getSanitizedValue(callback)); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -347,15 +369,13 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().page( - pageArgumentsToCallOptions( - getSanitizedValue(category), - getSanitizedValue(name), - getSanitizedValue(properties), - getSanitizedValue(options), - callback, - ), - ); + try { + this.getAnalyticsInstance()?.page( + pageArgumentsToCallOptions(category, name, properties, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -376,14 +396,13 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().track( - trackArgumentsToCallOptions( - getSanitizedValue(event), - getSanitizedValue(properties), - getSanitizedValue(options), - callback, - ), - ); + try { + this.getAnalyticsInstance()?.track( + trackArgumentsToCallOptions(event, properties, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -410,14 +429,13 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().identify( - identifyArgumentsToCallOptions( - getSanitizedValue(userId), - getSanitizedValue(traits), - getSanitizedValue(options), - callback, - ), - ); + try { + this.getAnalyticsInstance()?.identify( + identifyArgumentsToCallOptions(userId, traits, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -434,14 +452,11 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().alias( - aliasArgumentsToCallOptions( - getSanitizedValue(to), - getSanitizedValue(from), - getSanitizedValue(options), - callback, - ), - ); + try { + this.getAnalyticsInstance()?.alias(aliasArgumentsToCallOptions(to, from, options, callback)); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -468,70 +483,118 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - if (arguments.length === 0) { - this.logger.error(EMPTY_GROUP_CALL_ERROR(RS_APP)); - return; + try { + this.getAnalyticsInstance()?.group( + groupArgumentsToCallOptions(groupId, traits, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); } - - this.getAnalyticsInstance().group( - groupArgumentsToCallOptions( - getSanitizedValue(groupId), - getSanitizedValue(traits), - getSanitizedValue(options), - callback, - ), - ); } reset(resetAnonymousId?: boolean) { - this.getAnalyticsInstance().reset(getSanitizedValue(resetAnonymousId)); + try { + this.getAnalyticsInstance()?.reset(getSanitizedValue(resetAnonymousId)); + } catch (error: any) { + dispatchErrorEvent(error); + } } - getAnonymousId(options?: AnonymousIdOptions) { - return this.getAnalyticsInstance().getAnonymousId(getSanitizedValue(options)); + getAnonymousId(options?: AnonymousIdOptions): string | undefined { + try { + return this.getAnalyticsInstance()?.getAnonymousId(getSanitizedValue(options)); + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } - setAnonymousId(anonymousId?: string, rudderAmpLinkerParam?: string) { - this.getAnalyticsInstance().setAnonymousId( - getSanitizedValue(anonymousId), - getSanitizedValue(rudderAmpLinkerParam), - ); + setAnonymousId(anonymousId?: string, rudderAmpLinkerParam?: string): void { + try { + this.getAnalyticsInstance()?.setAnonymousId( + getSanitizedValue(anonymousId), + getSanitizedValue(rudderAmpLinkerParam), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } getUserId() { - return this.getAnalyticsInstance().getUserId(); + try { + return this.getAnalyticsInstance()?.getUserId(); + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } getUserTraits() { - return this.getAnalyticsInstance().getUserTraits(); + try { + return this.getAnalyticsInstance()?.getUserTraits(); + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } getGroupId() { - return this.getAnalyticsInstance().getGroupId(); + try { + return this.getAnalyticsInstance()?.getGroupId(); + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } getGroupTraits() { - return this.getAnalyticsInstance().getGroupTraits(); + try { + return this.getAnalyticsInstance()?.getGroupTraits(); + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } - startSession(sessionId?: number) { - return this.getAnalyticsInstance().startSession(sessionId); + startSession(sessionId?: number): void { + try { + this.getAnalyticsInstance()?.startSession(getSanitizedValue(sessionId)); + } catch (error: any) { + dispatchErrorEvent(error); + } } - endSession() { - return this.getAnalyticsInstance().endSession(); + endSession(): void { + try { + this.getAnalyticsInstance()?.endSession(); + } catch (error: any) { + dispatchErrorEvent(error); + } } getSessionId() { - return this.getAnalyticsInstance().getSessionId(); + try { + return this.getAnalyticsInstance()?.getSessionId(); + } catch (error: any) { + dispatchErrorEvent(error); + return undefined; + } } - setAuthToken(token: string) { - return this.getAnalyticsInstance().setAuthToken(getSanitizedValue(token)); + setAuthToken(token: string): void { + try { + this.getAnalyticsInstance()?.setAuthToken(getSanitizedValue(token)); + } catch (error: any) { + dispatchErrorEvent(error); + } } - consent(options?: ConsentOptions) { - return this.getAnalyticsInstance().consent(getSanitizedValue(options)); + consent(options?: ConsentOptions): void { + try { + this.getAnalyticsInstance()?.consent(getSanitizedValue(options)); + } catch (error: any) { + dispatchErrorEvent(error); + } } } diff --git a/packages/analytics-js/src/constants/logMessages.ts b/packages/analytics-js/src/constants/logMessages.ts index a28ee4b12e..0020c5fdb4 100644 --- a/packages/analytics-js/src/constants/logMessages.ts +++ b/packages/analytics-js/src/constants/logMessages.ts @@ -193,9 +193,6 @@ const STORAGE_UNAVAILABLE_WARNING = ( ): string => `${context}${LOG_CONTEXT_SEPARATOR}The storage type "${selectedStorageType}" is not available for entry "${entry}". The SDK will initialize the entry with "${finalStorageType}" storage type instead.`; -const EMPTY_GROUP_CALL_ERROR = (context: string): string => - `${context}${LOG_CONTEXT_SEPARATOR}The group() method must be called with at least one argument.`; - const READY_CALLBACK_INVOKE_ERROR = `Failed to invoke the ready callback`; const API_CALLBACK_INVOKE_ERROR = `API Callback Invocation Failed`; @@ -297,7 +294,6 @@ export { PLUGIN_EXT_POINT_MISSING_ERROR, PLUGIN_EXT_POINT_INVALID_ERROR, STORAGE_TYPE_VALIDATION_WARNING, - EMPTY_GROUP_CALL_ERROR, READY_CALLBACK_INVOKE_ERROR, API_CALLBACK_INVOKE_ERROR, INVALID_CONFIG_URL_WARNING, diff --git a/packages/analytics-js/src/services/ErrorHandler/ErrorHandler.ts b/packages/analytics-js/src/services/ErrorHandler/ErrorHandler.ts index 405d91e5d8..7d2fbc5f4c 100644 --- a/packages/analytics-js/src/services/ErrorHandler/ErrorHandler.ts +++ b/packages/analytics-js/src/services/ErrorHandler/ErrorHandler.ts @@ -14,6 +14,7 @@ import { LOG_CONTEXT_SEPARATOR } from '@rudderstack/analytics-js-common/constant import { BufferQueue } from '@rudderstack/analytics-js-common/services/BufferQueue/BufferQueue'; import type { IHttpClient } from '@rudderstack/analytics-js-common/types/HttpClient'; import type { IExternalSrcLoader } from '@rudderstack/analytics-js-common/services/ExternalSrcLoader/types'; +import { MANUAL_ERROR_IDENTIFIER } from '@rudderstack/analytics-js-common/utilities/errors'; import { NOTIFY_FAILURE_ERROR, REPORTING_PLUGIN_INIT_FAILURE_ERROR, @@ -168,6 +169,8 @@ class ErrorHandler implements IErrorHandler { } else { throw normalizedError; } + } else if ((error as any).error?.stack?.includes(MANUAL_ERROR_IDENTIFIER)) { + this.logger?.error('An unknown error occurred:', (error as ErrorEvent).error?.message); } } From 3f877eeebedaae8b0c9bf605bcc4c3e68c0ef3cb Mon Sep 17 00:00:00 2001 From: Moumita <36885121+MoumitaM@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:57:41 +0530 Subject: [PATCH 04/15] chore: add event name in breadcrumb (#1912) * chore: add event name in breadcrumb * chore: minor formatting in breadcrumb string Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> * chore: size limit updated --------- Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> --- packages/analytics-js/.size-limit.mjs | 4 ++-- packages/analytics-js/src/components/core/Analytics.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index b002464866..3ac516c9af 100644 --- a/packages/analytics-js/.size-limit.mjs +++ b/packages/analytics-js/.size-limit.mjs @@ -83,7 +83,7 @@ export default [ name: 'Core (Bundled) - Modern - NPM (UMD)', path: 'dist/npm/modern/bundled/umd/index.js', import: '*', - limit: '39.5 KiB', + limit: '40 KiB', }, { name: 'Core (Content Script) - Legacy - NPM (ESM)', @@ -107,7 +107,7 @@ export default [ name: 'Core (Content Script) - Modern - NPM (ESM)', path: 'dist/npm/modern/content-script/esm/index.mjs', import: '*', - limit: '39 KiB', + limit: '39.5 KiB', }, { name: 'Core (Content Script) - Modern - NPM (CJS)', diff --git a/packages/analytics-js/src/components/core/Analytics.ts b/packages/analytics-js/src/components/core/Analytics.ts index ed6986769a..8421dda6cf 100644 --- a/packages/analytics-js/src/components/core/Analytics.ts +++ b/packages/analytics-js/src/components/core/Analytics.ts @@ -534,7 +534,7 @@ class Analytics implements IAnalytics { return; } - this.errorHandler.leaveBreadcrumb(`New ${type} event`); + this.errorHandler.leaveBreadcrumb(`New ${type} event - ${payload.name}`); state.metrics.triggered.value += 1; this.eventManager?.addEvent({ From fcc3e8f9e4dd74c9f5c3f4a2f587c47360a2046c Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:09 +0000 Subject: [PATCH 05/15] chore(@rudderstack/analytics-js-common): release version 3.12.0 --- packages/analytics-js-common/CHANGELOG.md | 8 ++++++++ packages/analytics-js-common/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/analytics-js-common/CHANGELOG.md b/packages/analytics-js-common/CHANGELOG.md index c8ccb8596e..255cd712b8 100644 --- a/packages/analytics-js-common/CHANGELOG.md +++ b/packages/analytics-js-common/CHANGELOG.md @@ -2,6 +2,14 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.12.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.11.1...@rudderstack/analytics-js-common@3.12.0) (2024-11-08) + + +### Features + +* add error handling to all public apis ([#1907](https://github.com/rudderlabs/rudder-sdk-js/issues/1907)) ([9fbaf81](https://github.com/rudderlabs/rudder-sdk-js/commit/9fbaf819bb02320d2f8ae82a869ad2b85090ea34)) +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) + ## [3.11.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.11.0...@rudderstack/analytics-js-common@3.11.1) (2024-11-07) diff --git a/packages/analytics-js-common/package.json b/packages/analytics-js-common/package.json index c0ea5f3a9f..a6c9d2c342 100644 --- a/packages/analytics-js-common/package.json +++ b/packages/analytics-js-common/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-common", - "version": "3.11.1", + "version": "3.12.0", "private": true, "description": "RudderStack JavaScript SDK common code", "module": "dist/npm/index.js", From 798910a81545fb6201c37750215240d4c7082ffc Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:10 +0000 Subject: [PATCH 06/15] chore(@rudderstack/analytics-js-plugins): release version 3.6.0 --- packages/analytics-js-plugins/CHANGELOG.md | 12 ++++++++++++ packages/analytics-js-plugins/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/analytics-js-plugins/CHANGELOG.md b/packages/analytics-js-plugins/CHANGELOG.md index 258caa7656..c922241688 100644 --- a/packages/analytics-js-plugins/CHANGELOG.md +++ b/packages/analytics-js-plugins/CHANGELOG.md @@ -2,6 +2,18 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.6.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.5.2...@rudderstack/analytics-js-plugins@3.6.0) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.12.0` +* `@rudderstack/analytics-js` updated to version `3.9.1` +* `@rudderstack/analytics-js-cookies` updated to version `0.3.14` + +### Features + +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) + ## [3.5.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.5.1...@rudderstack/analytics-js-plugins@3.5.2) (2024-11-07) ### Dependency Updates diff --git a/packages/analytics-js-plugins/package.json b/packages/analytics-js-plugins/package.json index e480a7b346..dc70743708 100644 --- a/packages/analytics-js-plugins/package.json +++ b/packages/analytics-js-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-plugins", - "version": "3.5.2", + "version": "3.6.0", "private": true, "description": "RudderStack JavaScript SDK plugins", "main": "dist/npm/modern/cjs/index.cjs", From 7b9040911a708c8612ad50f09b21f420d30bd960 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:11 +0000 Subject: [PATCH 07/15] chore(@rudderstack/analytics-js-cookies): release version 0.4.0 --- packages/analytics-js-cookies/CHANGELOG.md | 10 ++++++++++ packages/analytics-js-cookies/package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/analytics-js-cookies/CHANGELOG.md b/packages/analytics-js-cookies/CHANGELOG.md index ae4d3b7619..252ebfa370 100644 --- a/packages/analytics-js-cookies/CHANGELOG.md +++ b/packages/analytics-js-cookies/CHANGELOG.md @@ -2,6 +2,16 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [0.4.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.3.14...@rudderstack/analytics-js-cookies@0.4.0) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.12.0` + +### Features + +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) + ## [0.3.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.3.13...@rudderstack/analytics-js-cookies@0.3.14) (2024-11-07) ### Dependency Updates diff --git a/packages/analytics-js-cookies/package.json b/packages/analytics-js-cookies/package.json index 78fb3f35a4..b1c8d27d33 100644 --- a/packages/analytics-js-cookies/package.json +++ b/packages/analytics-js-cookies/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-cookies", - "version": "0.3.14", + "version": "0.4.0", "description": "RudderStack JavaScript SDK Cookies Utilities", "main": "dist/npm/modern/cjs/index.cjs", "module": "dist/npm/modern/esm/index.mjs", From c1778538f9ae4339e2e200c8359a5d43ff9abcbd Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:12 +0000 Subject: [PATCH 08/15] chore(@rudderstack/analytics-js): release version 3.10.0 --- packages/analytics-js/CHANGELOG.md | 13 +++++++++++++ packages/analytics-js/package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/analytics-js/CHANGELOG.md b/packages/analytics-js/CHANGELOG.md index df604344cc..02d271834d 100644 --- a/packages/analytics-js/CHANGELOG.md +++ b/packages/analytics-js/CHANGELOG.md @@ -2,6 +2,19 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.10.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.9.1...@rudderstack/analytics-js@3.10.0) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js-cookies` updated to version `0.4.0` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.0` + +### Features + +* add error handling to all public apis ([#1907](https://github.com/rudderlabs/rudder-sdk-js/issues/1907)) ([9fbaf81](https://github.com/rudderlabs/rudder-sdk-js/commit/9fbaf819bb02320d2f8ae82a869ad2b85090ea34)) +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) + ## [3.9.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.9.0...@rudderstack/analytics-js@3.9.1) (2024-11-07) ### Dependency Updates diff --git a/packages/analytics-js/package.json b/packages/analytics-js/package.json index ee39e18af5..37d34835b9 100644 --- a/packages/analytics-js/package.json +++ b/packages/analytics-js/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js", - "version": "3.9.1", + "version": "3.10.0", "description": "RudderStack JavaScript SDK", "main": "dist/npm/modern/cjs/index.cjs", "module": "dist/npm/modern/esm/index.mjs", From 424526f86712abfd434f4d09c96ab35bac5cd2d8 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:13 +0000 Subject: [PATCH 09/15] chore(rudder-sdk-js): release version 2.48.25 --- packages/analytics-v1.1/CHANGELOG.md | 5 +++++ packages/analytics-v1.1/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/analytics-v1.1/CHANGELOG.md b/packages/analytics-v1.1/CHANGELOG.md index f6b8340d89..0cb748a782 100644 --- a/packages/analytics-v1.1/CHANGELOG.md +++ b/packages/analytics-v1.1/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [2.48.25](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.24...rudder-sdk-js@2.48.25) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.12.0` ## [2.48.24](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.23...rudder-sdk-js@2.48.24) (2024-11-07) ### Dependency Updates diff --git a/packages/analytics-v1.1/package.json b/packages/analytics-v1.1/package.json index 8a5fe0e0a7..32675296dc 100644 --- a/packages/analytics-v1.1/package.json +++ b/packages/analytics-v1.1/package.json @@ -1,6 +1,6 @@ { "name": "rudder-sdk-js", - "version": "2.48.24", + "version": "2.48.25", "description": "RudderStack JavaScript SDK", "main": "dist/npm/index.js", "module": "dist/npm/index.es.js", From 831ca11f965b2091e44d78a0d977ff63e3d93d0d Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:14 +0000 Subject: [PATCH 10/15] chore(@rudderstack/analytics-js-loading-scripts): release version 3.0.40 --- packages/loading-scripts/CHANGELOG.md | 5 +++++ packages/loading-scripts/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/loading-scripts/CHANGELOG.md b/packages/loading-scripts/CHANGELOG.md index da8574332f..584a10c007 100644 --- a/packages/loading-scripts/CHANGELOG.md +++ b/packages/loading-scripts/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.0.40](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.39...@rudderstack/analytics-js-loading-scripts@3.0.40) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.10.0` ## [3.0.39](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.38...@rudderstack/analytics-js-loading-scripts@3.0.39) (2024-11-07) ### Dependency Updates diff --git a/packages/loading-scripts/package.json b/packages/loading-scripts/package.json index fa4a6fb59c..0c7a60de70 100644 --- a/packages/loading-scripts/package.json +++ b/packages/loading-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-loading-scripts", - "version": "3.0.39", + "version": "3.0.40", "private": true, "description": "Loading script for RudderStack JavaScript SDK", "main": "./src/index.js", From f11960eeaf65770a1bd431512fd7e75269e04891 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:15 +0000 Subject: [PATCH 11/15] chore(@rudderstack/analytics-js-integrations): release version 3.10.2 --- packages/analytics-js-integrations/CHANGELOG.md | 5 +++++ packages/analytics-js-integrations/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/analytics-js-integrations/CHANGELOG.md b/packages/analytics-js-integrations/CHANGELOG.md index ffd0e304b4..0f5308a396 100644 --- a/packages/analytics-js-integrations/CHANGELOG.md +++ b/packages/analytics-js-integrations/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.10.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.1...@rudderstack/analytics-js-integrations@3.10.2) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.12.0` ## [3.10.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.0...@rudderstack/analytics-js-integrations@3.10.1) (2024-11-07) ### Dependency Updates diff --git a/packages/analytics-js-integrations/package.json b/packages/analytics-js-integrations/package.json index ebbeb803a1..da29bb3f04 100644 --- a/packages/analytics-js-integrations/package.json +++ b/packages/analytics-js-integrations/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-integrations", - "version": "3.10.1", + "version": "3.10.2", "private": true, "description": "RudderStack JavaScript SDK device mode integrations", "main": "dist/npm/modern/cjs/index.js", From cddbc1e5303a2d1a76877d7d1a2557705aa128bd Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:16 +0000 Subject: [PATCH 12/15] chore(@rudderstack/analytics-js-service-worker): release version 3.2.0 --- packages/analytics-js-service-worker/CHANGELOG.md | 10 ++++++++++ packages/analytics-js-service-worker/package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/analytics-js-service-worker/CHANGELOG.md b/packages/analytics-js-service-worker/CHANGELOG.md index d99a728f71..698140e14c 100644 --- a/packages/analytics-js-service-worker/CHANGELOG.md +++ b/packages/analytics-js-service-worker/CHANGELOG.md @@ -2,6 +2,16 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.2.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.1.17...@rudderstack/analytics-js-service-worker@3.2.0) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.12.0` + +### Features + +* add date datatype ([#1906](https://github.com/rudderlabs/rudder-sdk-js/issues/1906)) ([9c207f1](https://github.com/rudderlabs/rudder-sdk-js/commit/9c207f19f34b998cdb15b34eed3f435daff86dfd)) + ## [3.1.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.1.16...@rudderstack/analytics-js-service-worker@3.1.17) (2024-11-07) ### Dependency Updates diff --git a/packages/analytics-js-service-worker/package.json b/packages/analytics-js-service-worker/package.json index 994680c4f4..a30cb02c02 100644 --- a/packages/analytics-js-service-worker/package.json +++ b/packages/analytics-js-service-worker/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-service-worker", - "version": "3.1.17", + "version": "3.2.0", "description": "RudderStack JavaScript Service Worker SDK", "main": "dist/npm/modern/cjs/index.cjs", "module": "dist/npm/modern/esm/index.mjs", From 8926f622791bf4126d17a60aea9b8e520df89803 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:17 +0000 Subject: [PATCH 13/15] chore(@rudderstack/analytics-js-sanity-suite): release version 3.1.31 --- packages/sanity-suite/CHANGELOG.md | 5 +++++ packages/sanity-suite/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/sanity-suite/CHANGELOG.md b/packages/sanity-suite/CHANGELOG.md index 05dd62e49d..ad2fed5cc4 100644 --- a/packages/sanity-suite/CHANGELOG.md +++ b/packages/sanity-suite/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.1.31](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.30...@rudderstack/analytics-js-sanity-suite@3.1.31) (2024-11-08) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.10.0` ## [3.1.30](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.29...@rudderstack/analytics-js-sanity-suite@3.1.30) (2024-11-07) ### Dependency Updates diff --git a/packages/sanity-suite/package.json b/packages/sanity-suite/package.json index 1badf6bb2e..58436da9fd 100644 --- a/packages/sanity-suite/package.json +++ b/packages/sanity-suite/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-sanity-suite", - "version": "3.1.30", + "version": "3.1.31", "private": true, "description": "Sanity suite for testing JS SDK package", "main": "./dist/v3/cdn/testBook.js", From 9df5934287f7e5d5cf6b5fcc2f3aaf56b1719237 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 8 Nov 2024 09:30:28 +0000 Subject: [PATCH 14/15] chore(monorepo): sync versions and generate release logs --- package-lock.json | 22 +++++++++---------- package.json | 2 +- .../analytics-js-common/CHANGELOG_LATEST.md | 8 +++---- packages/analytics-js-common/project.json | 6 ++--- .../analytics-js-cookies/CHANGELOG_LATEST.md | 9 ++++++-- packages/analytics-js-cookies/project.json | 6 ++--- .../CHANGELOG_LATEST.md | 4 ++-- .../analytics-js-integrations/project.json | 6 ++--- .../analytics-js-plugins/CHANGELOG_LATEST.md | 13 +++++++---- packages/analytics-js-plugins/project.json | 6 ++--- .../CHANGELOG_LATEST.md | 9 ++++++-- .../analytics-js-service-worker/project.json | 6 ++--- packages/analytics-js/CHANGELOG_LATEST.md | 13 ++++++----- packages/analytics-js/project.json | 6 ++--- packages/analytics-v1.1/CHANGELOG_LATEST.md | 4 ++-- packages/analytics-v1.1/project.json | 6 ++--- packages/loading-scripts/CHANGELOG_LATEST.md | 4 ++-- packages/loading-scripts/project.json | 6 ++--- sonar-project.properties | 2 +- 19 files changed, 77 insertions(+), 61 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27c584065a..93f11ecb6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.47.0", + "version": "3.48.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.47.0", + "version": "3.48.0", "hasInstallScript": true, "license": "Elastic-2.0", "workspaces": [ @@ -25284,7 +25284,7 @@ }, "packages/analytics-js": { "name": "@rudderstack/analytics-js", - "version": "3.9.1", + "version": "3.10.0", "license": "Elastic-2.0", "dependencies": { "@preact/signals-core": "1.8.0", @@ -25298,7 +25298,7 @@ }, "packages/analytics-js-common": { "name": "@rudderstack/analytics-js-common", - "version": "3.11.1", + "version": "3.12.0", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25315,7 +25315,7 @@ }, "packages/analytics-js-cookies": { "name": "@rudderstack/analytics-js-cookies", - "version": "0.3.14", + "version": "0.4.0", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*" @@ -25324,7 +25324,7 @@ }, "packages/analytics-js-integrations": { "name": "@rudderstack/analytics-js-integrations", - "version": "3.10.1", + "version": "3.10.2", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25344,7 +25344,7 @@ }, "packages/analytics-js-plugins": { "name": "@rudderstack/analytics-js-plugins", - "version": "3.5.2", + "version": "3.6.0", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*", @@ -25358,7 +25358,7 @@ }, "packages/analytics-js-service-worker": { "name": "@rudderstack/analytics-js-service-worker", - "version": "3.1.17", + "version": "3.2.0", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25379,7 +25379,7 @@ }, "packages/analytics-v1.1": { "name": "rudder-sdk-js", - "version": "2.48.24", + "version": "2.48.25", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*" @@ -25388,7 +25388,7 @@ }, "packages/loading-scripts": { "name": "@rudderstack/analytics-js-loading-scripts", - "version": "3.0.39", + "version": "3.0.40", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*" @@ -25397,7 +25397,7 @@ }, "packages/sanity-suite": { "name": "@rudderstack/analytics-js-sanity-suite", - "version": "3.1.30", + "version": "3.1.31", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*", diff --git a/package.json b/package.json index 41a70053fe..fdb773def6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.47.0", + "version": "3.48.0", "private": true, "description": "Monorepo for RudderStack Analytics JS SDK", "workspaces": [ diff --git a/packages/analytics-js-common/CHANGELOG_LATEST.md b/packages/analytics-js-common/CHANGELOG_LATEST.md index d5d40c36c5..de7a92db4d 100644 --- a/packages/analytics-js-common/CHANGELOG_LATEST.md +++ b/packages/analytics-js-common/CHANGELOG_LATEST.md @@ -1,8 +1,8 @@ -## [3.11.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.11.0...@rudderstack/analytics-js-common@3.11.1) (2024-11-07) +## [3.12.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.11.1...@rudderstack/analytics-js-common@3.12.0) (2024-11-08) -### Bug Fixes +### Features -* fixing display name issues ([#1909](https://github.com/rudderlabs/rudder-sdk-js/issues/1909)) ([633d887](https://github.com/rudderlabs/rudder-sdk-js/commit/633d8873a2e3c660573681608acf8ba5c431be3d)) -* move page visit id to context ([#1904](https://github.com/rudderlabs/rudder-sdk-js/issues/1904)) ([76bbd16](https://github.com/rudderlabs/rudder-sdk-js/commit/76bbd16bd764baa00df2995fa9fb287800fd68d3)) +* add error handling to all public apis ([#1907](https://github.com/rudderlabs/rudder-sdk-js/issues/1907)) ([9fbaf81](https://github.com/rudderlabs/rudder-sdk-js/commit/9fbaf819bb02320d2f8ae82a869ad2b85090ea34)) +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) diff --git a/packages/analytics-js-common/project.json b/packages/analytics-js-common/project.json index 7178851a67..d689a5332f 100644 --- a/packages/analytics-js-common/project.json +++ b/packages/analytics-js-common/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-common@3.11.1", - "title": "@rudderstack/analytics-js-common@3.11.1", - "discussion-category": "@rudderstack/analytics-js-common@3.11.1", + "tag": "@rudderstack/analytics-js-common@3.12.0", + "title": "@rudderstack/analytics-js-common@3.12.0", + "discussion-category": "@rudderstack/analytics-js-common@3.12.0", "notesFile": "./packages/analytics-js-common/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-cookies/CHANGELOG_LATEST.md b/packages/analytics-js-cookies/CHANGELOG_LATEST.md index 66b86942ad..7b79aaf136 100644 --- a/packages/analytics-js-cookies/CHANGELOG_LATEST.md +++ b/packages/analytics-js-cookies/CHANGELOG_LATEST.md @@ -1,5 +1,10 @@ -## [0.3.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.3.13...@rudderstack/analytics-js-cookies@0.3.14) (2024-11-07) +## [0.4.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.3.14...@rudderstack/analytics-js-cookies@0.4.0) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.11.1` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` + +### Features + +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) + diff --git a/packages/analytics-js-cookies/project.json b/packages/analytics-js-cookies/project.json index 8281d2a1b1..6689cd1d0f 100644 --- a/packages/analytics-js-cookies/project.json +++ b/packages/analytics-js-cookies/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-cookies@0.3.14", - "title": "@rudderstack/analytics-js-cookies@0.3.14", - "discussion-category": "@rudderstack/analytics-js-cookies@0.3.14", + "tag": "@rudderstack/analytics-js-cookies@0.4.0", + "title": "@rudderstack/analytics-js-cookies@0.4.0", + "discussion-category": "@rudderstack/analytics-js-cookies@0.4.0", "notesFile": "./packages/analytics-js-cookies/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-integrations/CHANGELOG_LATEST.md b/packages/analytics-js-integrations/CHANGELOG_LATEST.md index 88088e4307..cbff7b802b 100644 --- a/packages/analytics-js-integrations/CHANGELOG_LATEST.md +++ b/packages/analytics-js-integrations/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.10.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.0...@rudderstack/analytics-js-integrations@3.10.1) (2024-11-07) +## [3.10.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.1...@rudderstack/analytics-js-integrations@3.10.2) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.11.1` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` diff --git a/packages/analytics-js-integrations/project.json b/packages/analytics-js-integrations/project.json index e8b8b8c939..1d80bce24a 100644 --- a/packages/analytics-js-integrations/project.json +++ b/packages/analytics-js-integrations/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-integrations@3.10.1", - "title": "@rudderstack/analytics-js-integrations@3.10.1", - "discussion-category": "@rudderstack/analytics-js-integrations@3.10.1", + "tag": "@rudderstack/analytics-js-integrations@3.10.2", + "title": "@rudderstack/analytics-js-integrations@3.10.2", + "discussion-category": "@rudderstack/analytics-js-integrations@3.10.2", "notesFile": "./packages/analytics-js-integrations/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-plugins/CHANGELOG_LATEST.md b/packages/analytics-js-plugins/CHANGELOG_LATEST.md index 2653e51e37..b24633e7e7 100644 --- a/packages/analytics-js-plugins/CHANGELOG_LATEST.md +++ b/packages/analytics-js-plugins/CHANGELOG_LATEST.md @@ -1,7 +1,12 @@ -## [3.5.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.5.1...@rudderstack/analytics-js-plugins@3.5.2) (2024-11-07) +## [3.6.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.5.2...@rudderstack/analytics-js-plugins@3.6.0) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.11.1` -* `@rudderstack/analytics-js` updated to version `3.9.0` -* `@rudderstack/analytics-js-cookies` updated to version `0.3.13` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` +* `@rudderstack/analytics-js` updated to version `3.9.1` +* `@rudderstack/analytics-js-cookies` updated to version `0.3.14` + +### Features + +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) + diff --git a/packages/analytics-js-plugins/project.json b/packages/analytics-js-plugins/project.json index d349b7f0bc..41ea9633e2 100644 --- a/packages/analytics-js-plugins/project.json +++ b/packages/analytics-js-plugins/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-plugins@3.5.2", - "title": "@rudderstack/analytics-js-plugins@3.5.2", - "discussion-category": "@rudderstack/analytics-js-plugins@3.5.2", + "tag": "@rudderstack/analytics-js-plugins@3.6.0", + "title": "@rudderstack/analytics-js-plugins@3.6.0", + "discussion-category": "@rudderstack/analytics-js-plugins@3.6.0", "notesFile": "./packages/analytics-js-plugins/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md index 336853fd2e..2a7228524e 100644 --- a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md +++ b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md @@ -1,5 +1,10 @@ -## [3.1.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.1.16...@rudderstack/analytics-js-service-worker@3.1.17) (2024-11-07) +## [3.2.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.1.17...@rudderstack/analytics-js-service-worker@3.2.0) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.11.1` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` + +### Features + +* add date datatype ([#1906](https://github.com/rudderlabs/rudder-sdk-js/issues/1906)) ([9c207f1](https://github.com/rudderlabs/rudder-sdk-js/commit/9c207f19f34b998cdb15b34eed3f435daff86dfd)) + diff --git a/packages/analytics-js-service-worker/project.json b/packages/analytics-js-service-worker/project.json index 5393921faf..6705ea047c 100644 --- a/packages/analytics-js-service-worker/project.json +++ b/packages/analytics-js-service-worker/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-service-worker@3.1.17", - "title": "rudderstack/analytics-js-service-worker@3.1.17", - "discussion-category": "rudderstack/analytics-js-service-worker@3.1.17", + "tag": "@rudderstack/analytics-js-service-worker@3.2.0", + "title": "rudderstack/analytics-js-service-worker@3.2.0", + "discussion-category": "rudderstack/analytics-js-service-worker@3.2.0", "notesFile": "./packages/analytics-js-service-worker/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/CHANGELOG_LATEST.md b/packages/analytics-js/CHANGELOG_LATEST.md index eff8ba14a6..ec0763b201 100644 --- a/packages/analytics-js/CHANGELOG_LATEST.md +++ b/packages/analytics-js/CHANGELOG_LATEST.md @@ -1,12 +1,13 @@ -## [3.9.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.9.0...@rudderstack/analytics-js@3.9.1) (2024-11-07) +## [3.10.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.9.1...@rudderstack/analytics-js@3.10.0) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js-cookies` updated to version `0.3.14` -* `@rudderstack/analytics-js-common` updated to version `3.11.1` -* `@rudderstack/analytics-js-plugins` updated to version `3.5.2` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.0` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.0` -### Bug Fixes +### Features -* move page visit id to context ([#1904](https://github.com/rudderlabs/rudder-sdk-js/issues/1904)) ([76bbd16](https://github.com/rudderlabs/rudder-sdk-js/commit/76bbd16bd764baa00df2995fa9fb287800fd68d3)) +* add error handling to all public apis ([#1907](https://github.com/rudderlabs/rudder-sdk-js/issues/1907)) ([9fbaf81](https://github.com/rudderlabs/rudder-sdk-js/commit/9fbaf819bb02320d2f8ae82a869ad2b85090ea34)) +* sanitize input data ([#1902](https://github.com/rudderlabs/rudder-sdk-js/issues/1902)) ([b71c44a](https://github.com/rudderlabs/rudder-sdk-js/commit/b71c44ae61f6c35cadc6523b918e1a574e32bc23)) diff --git a/packages/analytics-js/project.json b/packages/analytics-js/project.json index a856b8d8d8..a6e4979a2f 100644 --- a/packages/analytics-js/project.json +++ b/packages/analytics-js/project.json @@ -59,9 +59,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js@3.9.1", - "title": "@rudderstack/analytics-js@3.9.1", - "discussion-category": "@rudderstack/analytics-js@3.9.1", + "tag": "@rudderstack/analytics-js@3.10.0", + "title": "@rudderstack/analytics-js@3.10.0", + "discussion-category": "@rudderstack/analytics-js@3.10.0", "notesFile": "./packages/analytics-js/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-v1.1/CHANGELOG_LATEST.md b/packages/analytics-v1.1/CHANGELOG_LATEST.md index 7394093475..8e6deaebc6 100644 --- a/packages/analytics-v1.1/CHANGELOG_LATEST.md +++ b/packages/analytics-v1.1/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [2.48.24](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.23...rudder-sdk-js@2.48.24) (2024-11-07) +## [2.48.25](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.24...rudder-sdk-js@2.48.25) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.11.1` +* `@rudderstack/analytics-js-common` updated to version `3.12.0` diff --git a/packages/analytics-v1.1/project.json b/packages/analytics-v1.1/project.json index 503ac0805f..137f23fc2f 100644 --- a/packages/analytics-v1.1/project.json +++ b/packages/analytics-v1.1/project.json @@ -59,9 +59,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "rudder-sdk-js@2.48.24", - "title": "rudder-sdk-js@2.48.24", - "discussion-category": "rudder-sdk-js@2.48.24", + "tag": "rudder-sdk-js@2.48.25", + "title": "rudder-sdk-js@2.48.25", + "discussion-category": "rudder-sdk-js@2.48.25", "notesFile": "./packages/analytics-v1.1/CHANGELOG_LATEST.md" } } diff --git a/packages/loading-scripts/CHANGELOG_LATEST.md b/packages/loading-scripts/CHANGELOG_LATEST.md index f4a3301cd6..0e2e672b9d 100644 --- a/packages/loading-scripts/CHANGELOG_LATEST.md +++ b/packages/loading-scripts/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.0.39](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.38...@rudderstack/analytics-js-loading-scripts@3.0.39) (2024-11-07) +## [3.0.40](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.39...@rudderstack/analytics-js-loading-scripts@3.0.40) (2024-11-08) ### Dependency Updates -* `@rudderstack/analytics-js` updated to version `3.9.1` +* `@rudderstack/analytics-js` updated to version `3.10.0` diff --git a/packages/loading-scripts/project.json b/packages/loading-scripts/project.json index 89c1fa1694..4c749e8d3d 100644 --- a/packages/loading-scripts/project.json +++ b/packages/loading-scripts/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-loading-scripts@3.0.39", - "title": "@rudderstack/analytics-js-loading-scripts@3.0.39", - "discussion-category": "@rudderstack/analytics-js-loading-scripts@3.0.39", + "tag": "@rudderstack/analytics-js-loading-scripts@3.0.40", + "title": "@rudderstack/analytics-js-loading-scripts@3.0.40", + "discussion-category": "@rudderstack/analytics-js-loading-scripts@3.0.40", "notesFile": "./packages/loading-scripts/CHANGELOG_LATEST.md" } } diff --git a/sonar-project.properties b/sonar-project.properties index 1b40b7f58a..22cc127746 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,7 +6,7 @@ sonar.qualitygate.wait=false sonar.projectKey=rudderlabs_rudder-sdk-js sonar.organization=rudderlabs sonar.projectName=rudder-sdk-js -sonar.projectVersion=3.47.0 +sonar.projectVersion=3.48.0 # Meta-data for the project sonar.links.scm=https://github.com/rudderlabs/rudder-sdk-js From 4cfe72f7cea867dfd59c6125fe4a0eebcfaa1188 Mon Sep 17 00:00:00 2001 From: Sai Kumar Battinoju Date: Mon, 11 Nov 2024 10:48:15 +0530 Subject: [PATCH 15/15] chore: fix size limit --- packages/analytics-js/.size-limit.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index 3ac516c9af..add092808c 100644 --- a/packages/analytics-js/.size-limit.mjs +++ b/packages/analytics-js/.size-limit.mjs @@ -71,7 +71,7 @@ export default [ name: 'Core (Bundled) - Modern - NPM (ESM)', path: 'dist/npm/modern/bundled/esm/index.mjs', import: '*', - limit: '39.5 KiB', + limit: '40 KiB', }, { name: 'Core (Bundled) - Modern - NPM (CJS)',