diff --git a/package-lock.json b/package-lock.json index edd52f3db..411cc30a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.51.0", + "version": "3.52.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.51.0", + "version": "3.52.0", "hasInstallScript": true, "license": "Elastic-2.0", "workspaces": [ @@ -25284,7 +25284,7 @@ }, "packages/analytics-js": { "name": "@rudderstack/analytics-js", - "version": "3.10.2", + "version": "3.11.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.13.0", + "version": "3.14.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.4.2", + "version": "0.4.3", "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.4", + "version": "3.10.5", "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.6.2", + "version": "3.6.3", "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.2.2", + "version": "3.2.3", "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.27", + "version": "2.48.28", "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.42", + "version": "3.0.43", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*" @@ -25397,7 +25397,7 @@ }, "packages/sanity-suite": { "name": "@rudderstack/analytics-js-sanity-suite", - "version": "3.1.33", + "version": "3.1.34", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*", diff --git a/package.json b/package.json index 0e0c4466d..176c5ebd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.51.0", + "version": "3.52.0", "private": true, "description": "Monorepo for RudderStack Analytics JS SDK", "workspaces": [ diff --git a/packages/analytics-js-common/CHANGELOG.md b/packages/analytics-js-common/CHANGELOG.md index 6099859dd..a5fe28e4a 100644 --- a/packages/analytics-js-common/CHANGELOG.md +++ b/packages/analytics-js-common/CHANGELOG.md @@ -2,6 +2,13 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.14.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.13.0...@rudderstack/analytics-js-common@3.14.0) (2024-11-18) + + +### Features + +* error handle public apis ([295793a](https://github.com/rudderlabs/rudder-sdk-js/commit/295793a2cc60172b001c3fb1bc2624bb19fa8546)) + ## [3.13.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.12.1...@rudderstack/analytics-js-common@3.13.0) (2024-11-18) diff --git a/packages/analytics-js-common/CHANGELOG_LATEST.md b/packages/analytics-js-common/CHANGELOG_LATEST.md index 06ff69a74..bfc0306e5 100644 --- a/packages/analytics-js-common/CHANGELOG_LATEST.md +++ b/packages/analytics-js-common/CHANGELOG_LATEST.md @@ -1,7 +1,7 @@ -## [3.13.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.12.1...@rudderstack/analytics-js-common@3.13.0) (2024-11-18) +## [3.14.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.13.0...@rudderstack/analytics-js-common@3.14.0) (2024-11-18) ### Features -* add more utilities ([7bd0cc9](https://github.com/rudderlabs/rudder-sdk-js/commit/7bd0cc98d5de1e9c20aaee4400263da12f2943d1)) +* error handle public apis ([295793a](https://github.com/rudderlabs/rudder-sdk-js/commit/295793a2cc60172b001c3fb1bc2624bb19fa8546)) diff --git a/packages/analytics-js-common/package.json b/packages/analytics-js-common/package.json index 9818557b0..d550097c6 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.13.0", + "version": "3.14.0", "private": true, "description": "RudderStack JavaScript SDK common code", "module": "dist/npm/index.js", diff --git a/packages/analytics-js-common/project.json b/packages/analytics-js-common/project.json index d3ade0832..c2a85e226 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.13.0", - "title": "@rudderstack/analytics-js-common@3.13.0", - "discussion-category": "@rudderstack/analytics-js-common@3.13.0", + "tag": "@rudderstack/analytics-js-common@3.14.0", + "title": "@rudderstack/analytics-js-common@3.14.0", + "discussion-category": "@rudderstack/analytics-js-common@3.14.0", "notesFile": "./packages/analytics-js-common/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-common/src/utilities/eventMethodOverloads.ts b/packages/analytics-js-common/src/utilities/eventMethodOverloads.ts index 4a7f06381..f92b823ca 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-cookies/CHANGELOG.md b/packages/analytics-js-cookies/CHANGELOG.md index 1f594bf33..88d1eda0f 100644 --- a/packages/analytics-js-cookies/CHANGELOG.md +++ b/packages/analytics-js-cookies/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [0.4.3](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.2...@rudderstack/analytics-js-cookies@0.4.3) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.0` ## [0.4.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.1...@rudderstack/analytics-js-cookies@0.4.2) (2024-11-18) ### Dependency Updates diff --git a/packages/analytics-js-cookies/CHANGELOG_LATEST.md b/packages/analytics-js-cookies/CHANGELOG_LATEST.md index 075f2adf0..e02a7a347 100644 --- a/packages/analytics-js-cookies/CHANGELOG_LATEST.md +++ b/packages/analytics-js-cookies/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [0.4.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.1...@rudderstack/analytics-js-cookies@0.4.2) (2024-11-18) +## [0.4.3](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.2...@rudderstack/analytics-js-cookies@0.4.3) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.13.0` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` diff --git a/packages/analytics-js-cookies/package.json b/packages/analytics-js-cookies/package.json index 7852d9e27..fae0bbfce 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.4.2", + "version": "0.4.3", "description": "RudderStack JavaScript SDK Cookies Utilities", "main": "dist/npm/modern/cjs/index.cjs", "module": "dist/npm/modern/esm/index.mjs", diff --git a/packages/analytics-js-cookies/project.json b/packages/analytics-js-cookies/project.json index d56f884d5..c341dbe3e 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.4.2", - "title": "@rudderstack/analytics-js-cookies@0.4.2", - "discussion-category": "@rudderstack/analytics-js-cookies@0.4.2", + "tag": "@rudderstack/analytics-js-cookies@0.4.3", + "title": "@rudderstack/analytics-js-cookies@0.4.3", + "discussion-category": "@rudderstack/analytics-js-cookies@0.4.3", "notesFile": "./packages/analytics-js-cookies/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-integrations/CHANGELOG.md b/packages/analytics-js-integrations/CHANGELOG.md index ae91ccc1b..4646dd7c9 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.5](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.4...@rudderstack/analytics-js-integrations@3.10.5) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.0` ## [3.10.4](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.3...@rudderstack/analytics-js-integrations@3.10.4) (2024-11-18) ### Dependency Updates diff --git a/packages/analytics-js-integrations/CHANGELOG_LATEST.md b/packages/analytics-js-integrations/CHANGELOG_LATEST.md index fe5dbb12f..b92db7164 100644 --- a/packages/analytics-js-integrations/CHANGELOG_LATEST.md +++ b/packages/analytics-js-integrations/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.10.4](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.3...@rudderstack/analytics-js-integrations@3.10.4) (2024-11-18) +## [3.10.5](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.10.4...@rudderstack/analytics-js-integrations@3.10.5) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.13.0` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` diff --git a/packages/analytics-js-integrations/package.json b/packages/analytics-js-integrations/package.json index 343dddf47..8d3104495 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.4", + "version": "3.10.5", "private": true, "description": "RudderStack JavaScript SDK device mode integrations", "main": "dist/npm/modern/cjs/index.js", diff --git a/packages/analytics-js-integrations/project.json b/packages/analytics-js-integrations/project.json index 39634a7f0..ac2f12673 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.4", - "title": "@rudderstack/analytics-js-integrations@3.10.4", - "discussion-category": "@rudderstack/analytics-js-integrations@3.10.4", + "tag": "@rudderstack/analytics-js-integrations@3.10.5", + "title": "@rudderstack/analytics-js-integrations@3.10.5", + "discussion-category": "@rudderstack/analytics-js-integrations@3.10.5", "notesFile": "./packages/analytics-js-integrations/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-plugins/CHANGELOG.md b/packages/analytics-js-plugins/CHANGELOG.md index e9b0fa77f..cf5dfad82 100644 --- a/packages/analytics-js-plugins/CHANGELOG.md +++ b/packages/analytics-js-plugins/CHANGELOG.md @@ -2,6 +2,13 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.6.3](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.2...@rudderstack/analytics-js-plugins@3.6.3) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.0` +* `@rudderstack/analytics-js` updated to version `3.10.2` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.2` ## [3.6.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.1...@rudderstack/analytics-js-plugins@3.6.2) (2024-11-18) ### Dependency Updates diff --git a/packages/analytics-js-plugins/CHANGELOG_LATEST.md b/packages/analytics-js-plugins/CHANGELOG_LATEST.md index 81944482a..2ae1f9163 100644 --- a/packages/analytics-js-plugins/CHANGELOG_LATEST.md +++ b/packages/analytics-js-plugins/CHANGELOG_LATEST.md @@ -1,7 +1,7 @@ -## [3.6.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.1...@rudderstack/analytics-js-plugins@3.6.2) (2024-11-18) +## [3.6.3](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.2...@rudderstack/analytics-js-plugins@3.6.3) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.13.0` -* `@rudderstack/analytics-js` updated to version `3.10.1` -* `@rudderstack/analytics-js-cookies` updated to version `0.4.1` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` +* `@rudderstack/analytics-js` updated to version `3.10.2` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.2` diff --git a/packages/analytics-js-plugins/package.json b/packages/analytics-js-plugins/package.json index 817d70e15..25ab8a524 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.6.2", + "version": "3.6.3", "private": true, "description": "RudderStack JavaScript SDK plugins", "main": "dist/npm/modern/cjs/index.cjs", diff --git a/packages/analytics-js-plugins/project.json b/packages/analytics-js-plugins/project.json index 4f31c1de6..b43bdea39 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.6.2", - "title": "@rudderstack/analytics-js-plugins@3.6.2", - "discussion-category": "@rudderstack/analytics-js-plugins@3.6.2", + "tag": "@rudderstack/analytics-js-plugins@3.6.3", + "title": "@rudderstack/analytics-js-plugins@3.6.3", + "discussion-category": "@rudderstack/analytics-js-plugins@3.6.3", "notesFile": "./packages/analytics-js-plugins/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-service-worker/CHANGELOG.md b/packages/analytics-js-service-worker/CHANGELOG.md index adc2cf308..bf5f6c18d 100644 --- a/packages/analytics-js-service-worker/CHANGELOG.md +++ b/packages/analytics-js-service-worker/CHANGELOG.md @@ -2,6 +2,11 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.2.3](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.2...@rudderstack/analytics-js-service-worker@3.2.3) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.0` ## [3.2.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.1...@rudderstack/analytics-js-service-worker@3.2.2) (2024-11-18) ### Dependency Updates diff --git a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md index 1178aded4..76042ef79 100644 --- a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md +++ b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.2.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.1...@rudderstack/analytics-js-service-worker@3.2.2) (2024-11-18) +## [3.2.3](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.2...@rudderstack/analytics-js-service-worker@3.2.3) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.13.0` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` diff --git a/packages/analytics-js-service-worker/package.json b/packages/analytics-js-service-worker/package.json index 798751355..7de06eecc 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.2.2", + "version": "3.2.3", "description": "RudderStack JavaScript Service Worker SDK", "main": "dist/npm/modern/cjs/index.cjs", "module": "dist/npm/modern/esm/index.mjs", diff --git a/packages/analytics-js-service-worker/project.json b/packages/analytics-js-service-worker/project.json index b1375f0ef..15b9ca608 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.2.2", - "title": "rudderstack/analytics-js-service-worker@3.2.2", - "discussion-category": "rudderstack/analytics-js-service-worker@3.2.2", + "tag": "@rudderstack/analytics-js-service-worker@3.2.3", + "title": "rudderstack/analytics-js-service-worker@3.2.3", + "discussion-category": "rudderstack/analytics-js-service-worker@3.2.3", "notesFile": "./packages/analytics-js-service-worker/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index add092808..3153e9cd0 100644 --- a/packages/analytics-js/.size-limit.mjs +++ b/packages/analytics-js/.size-limit.mjs @@ -7,7 +7,7 @@ export default [ name: 'Core - Legacy - NPM (ESM)', path: 'dist/npm/legacy/esm/index.mjs', import: '*', - limit: '48.5 KiB', + limit: '49 KiB', }, { name: 'Core - Legacy - NPM (CJS)', @@ -19,7 +19,7 @@ export default [ name: 'Core - Legacy - NPM (UMD)', path: 'dist/npm/legacy/umd/index.js', import: '*', - limit: '48.5 KiB', + limit: '49 KiB', }, { name: 'Core - Legacy - CDN', @@ -47,13 +47,13 @@ export default [ { name: 'Core - Modern - CDN', path: 'dist/cdn/modern/iife/rsa.min.js', - limit: '25 KiB', + limit: '25.5 KiB', }, { name: 'Core (Bundled) - Legacy - NPM (ESM)', path: 'dist/npm/legacy/bundled/esm/index.mjs', import: '*', - limit: '48.5 KiB', + limit: '49 KiB', }, { name: 'Core (Bundled) - Legacy - NPM (CJS)', @@ -65,7 +65,7 @@ export default [ name: 'Core (Bundled) - Legacy - NPM (UMD)', path: 'dist/npm/legacy/bundled/umd/index.js', import: '*', - limit: '48.5 KiB', + limit: '49 KiB', }, { name: 'Core (Bundled) - Modern - NPM (ESM)', @@ -77,7 +77,7 @@ export default [ name: 'Core (Bundled) - Modern - NPM (CJS)', path: 'dist/npm/modern/bundled/cjs/index.cjs', import: '*', - limit: '40 KiB', + limit: '40.5 KiB', }, { name: 'Core (Bundled) - Modern - NPM (UMD)', @@ -89,7 +89,7 @@ export default [ name: 'Core (Content Script) - Legacy - NPM (ESM)', path: 'dist/npm/legacy/content-script/esm/index.mjs', import: '*', - limit: '48 KiB', + limit: '48.5 KiB', }, { name: 'Core (Content Script) - Legacy - NPM (CJS)', @@ -101,7 +101,7 @@ export default [ name: 'Core (Content Script) - Legacy - NPM (UMD)', path: 'dist/npm/legacy/content-script/umd/index.js', import: '*', - limit: '48 KiB', + limit: '48.5 KiB', }, { name: 'Core (Content Script) - Modern - NPM (ESM)', @@ -119,6 +119,6 @@ export default [ name: 'Core (Content Script) - Modern - NPM (UMD)', path: 'dist/npm/modern/content-script/umd/index.js', import: '*', - limit: '39 KiB', + limit: '39.5 KiB', }, ]; diff --git a/packages/analytics-js/CHANGELOG.md b/packages/analytics-js/CHANGELOG.md index 4dca8ae5f..92de03d5f 100644 --- a/packages/analytics-js/CHANGELOG.md +++ b/packages/analytics-js/CHANGELOG.md @@ -2,6 +2,18 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.11.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.10.2...@rudderstack/analytics-js@3.11.0) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js-cookies` updated to version `0.4.3` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.3` + +### Features + +* error handle public apis ([295793a](https://github.com/rudderlabs/rudder-sdk-js/commit/295793a2cc60172b001c3fb1bc2624bb19fa8546)) + ## [3.10.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.10.1...@rudderstack/analytics-js@3.10.2) (2024-11-18) ### Dependency Updates diff --git a/packages/analytics-js/CHANGELOG_LATEST.md b/packages/analytics-js/CHANGELOG_LATEST.md index 765335051..020fc5a40 100644 --- a/packages/analytics-js/CHANGELOG_LATEST.md +++ b/packages/analytics-js/CHANGELOG_LATEST.md @@ -1,7 +1,12 @@ -## [3.10.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.10.1...@rudderstack/analytics-js@3.10.2) (2024-11-18) +## [3.11.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.10.2...@rudderstack/analytics-js@3.11.0) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js-cookies` updated to version `0.4.2` -* `@rudderstack/analytics-js-common` updated to version `3.13.0` -* `@rudderstack/analytics-js-plugins` updated to version `3.6.2` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.3` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.3` + +### Features + +* error handle public apis ([295793a](https://github.com/rudderlabs/rudder-sdk-js/commit/295793a2cc60172b001c3fb1bc2624bb19fa8546)) + diff --git a/packages/analytics-js/__tests__/app/RudderAnalytics.test.ts b/packages/analytics-js/__tests__/app/RudderAnalytics.test.ts index 85afcbd90..690c96114 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/__tests__/components/configManager/ConfigManager.test.ts b/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts index df73dd4f0..9f1fc0915 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 2176388a7..ccaa68f39 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 e85709a4d..f10e2ff0f 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/package.json b/packages/analytics-js/package.json index ddef1b415..d614e798c 100644 --- a/packages/analytics-js/package.json +++ b/packages/analytics-js/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js", - "version": "3.10.2", + "version": "3.11.0", "description": "RudderStack JavaScript SDK", "main": "dist/npm/modern/cjs/index.cjs", "module": "dist/npm/modern/esm/index.mjs", diff --git a/packages/analytics-js/project.json b/packages/analytics-js/project.json index a374ce690..5296eda93 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.10.2", - "title": "@rudderstack/analytics-js@3.10.2", - "discussion-category": "@rudderstack/analytics-js@3.10.2", + "tag": "@rudderstack/analytics-js@3.11.0", + "title": "@rudderstack/analytics-js@3.11.0", + "discussion-category": "@rudderstack/analytics-js@3.11.0", "notesFile": "./packages/analytics-js/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/src/app/RudderAnalytics.ts b/packages/analytics-js/src/app/RudderAnalytics.ts index 110598b3a..8ea3ed508 100644 --- a/packages/analytics-js/src/app/RudderAnalytics.ts +++ b/packages/analytics-js/src/app/RudderAnalytics.ts @@ -18,11 +18,13 @@ import { import type { ApiCallback, ApiOptions } from '@rudderstack/analytics-js-common/types/EventApi'; import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; import { RSA } 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 { 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, @@ -33,11 +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, - WRITE_KEY_NOT_A_STRING_ERROR, -} 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,7 +112,11 @@ class RudderAnalytics implements IRudderAnalytics { * TODO: to support multiple analytics instances in the near future */ setDefaultInstanceKey(writeKey: string) { - if (writeKey) { + // 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,44 +124,59 @@ class RudderAnalytics implements IRudderAnalytics { /** * Retrieve an existing analytics instance */ - getAnalyticsInstance(writeKey?: string): IAnalytics { - const instanceId = writeKey ?? 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; + } } /** - * 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(RSA, writeKey)); - return; - } + load(writeKey: string, dataPlaneUrl: string, loadOptions?: Partial): void { + try { + if (this.analyticsInstances[writeKey]) { + return; + } - 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, loadOptions); + this.analyticsInstances[writeKey] = new Analytics(); + this.getAnalyticsInstance(writeKey)?.load( + writeKey, + dataPlaneUrl, + getSanitizedValue(loadOptions), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -210,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[], @@ -236,7 +260,7 @@ class RudderAnalytics implements IRudderAnalytics { * @param useBeacon * @param options */ - private setupPageUnloadTracking( + setupPageUnloadTracking( events: PageLifecycleEvents[], useBeacon: boolean | undefined, options: ApiOptions, @@ -298,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); + } } /** @@ -341,9 +369,13 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().page( - pageArgumentsToCallOptions(category, name, properties, options, callback), - ); + try { + this.getAnalyticsInstance()?.page( + pageArgumentsToCallOptions(category, name, properties, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -364,9 +396,13 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().track( - trackArgumentsToCallOptions(event, properties, options, callback), - ); + try { + this.getAnalyticsInstance()?.track( + trackArgumentsToCallOptions(event, properties, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -393,9 +429,13 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().identify( - identifyArgumentsToCallOptions(userId, traits, options, callback), - ); + try { + this.getAnalyticsInstance()?.identify( + identifyArgumentsToCallOptions(userId, traits, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -412,7 +452,11 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - this.getAnalyticsInstance().alias(aliasArgumentsToCallOptions(to, from, options, callback)); + try { + this.getAnalyticsInstance()?.alias(aliasArgumentsToCallOptions(to, from, options, callback)); + } catch (error: any) { + dispatchErrorEvent(error); + } } /** @@ -439,62 +483,118 @@ class RudderAnalytics implements IRudderAnalytics { options?: Nullable | ApiCallback, callback?: ApiCallback, ) { - if (arguments.length === 0) { - this.logger.error(EMPTY_GROUP_CALL_ERROR(RSA)); - return; + try { + this.getAnalyticsInstance()?.group( + groupArgumentsToCallOptions(groupId, traits, options, callback), + ); + } catch (error: any) { + dispatchErrorEvent(error); } - - this.getAnalyticsInstance().group( - groupArgumentsToCallOptions(groupId, traits, options, callback), - ); } reset(resetAnonymousId?: boolean) { - this.getAnalyticsInstance().reset(resetAnonymousId); + try { + this.getAnalyticsInstance()?.reset(getSanitizedValue(resetAnonymousId)); + } catch (error: any) { + dispatchErrorEvent(error); + } } - getAnonymousId(options?: AnonymousIdOptions) { - return this.getAnalyticsInstance().getAnonymousId(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(anonymousId, 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(token); + setAuthToken(token: string): void { + try { + this.getAnalyticsInstance()?.setAuthToken(getSanitizedValue(token)); + } catch (error: any) { + dispatchErrorEvent(error); + } } - consent(options?: ConsentOptions) { - return this.getAnalyticsInstance().consent(options); + consent(options?: ConsentOptions): void { + try { + this.getAnalyticsInstance()?.consent(getSanitizedValue(options)); + } catch (error: any) { + dispatchErrorEvent(error); + } } } diff --git a/packages/analytics-js/src/components/configManager/ConfigManager.ts b/packages/analytics-js/src/components/configManager/ConfigManager.ts index fec3eacbb..315f2ab5a 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 6090e231c..fc22c5140 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 31948370f..00f311c1c 100644 --- a/packages/analytics-js/src/components/core/Analytics.ts +++ b/packages/analytics-js/src/components/core/Analytics.ts @@ -60,10 +60,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 +105,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 ca1dfd0c0..b9c11e7fd 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/constants/logMessages.ts b/packages/analytics-js/src/constants/logMessages.ts index 8295a634e..0020c5fdb 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,12 +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.`; - const READY_CALLBACK_INVOKE_ERROR = `Failed to invoke the ready callback`; const API_CALLBACK_INVOKE_ERROR = `API Callback Invocation Failed`; @@ -300,8 +294,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, 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 405d91e5d..7d2fbc5f4 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); } } diff --git a/packages/analytics-v1.1/CHANGELOG.md b/packages/analytics-v1.1/CHANGELOG.md index 0164e14d3..a0e616ff2 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.28](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.27...rudder-sdk-js@2.48.28) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.0` ## [2.48.27](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.26...rudder-sdk-js@2.48.27) (2024-11-18) ### Dependency Updates diff --git a/packages/analytics-v1.1/CHANGELOG_LATEST.md b/packages/analytics-v1.1/CHANGELOG_LATEST.md index d2d2732e0..a1fd07033 100644 --- a/packages/analytics-v1.1/CHANGELOG_LATEST.md +++ b/packages/analytics-v1.1/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [2.48.27](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.26...rudder-sdk-js@2.48.27) (2024-11-18) +## [2.48.28](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.27...rudder-sdk-js@2.48.28) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.13.0` +* `@rudderstack/analytics-js-common` updated to version `3.14.0` diff --git a/packages/analytics-v1.1/package.json b/packages/analytics-v1.1/package.json index cfd46b717..d22a57c81 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.27", + "version": "2.48.28", "description": "RudderStack JavaScript SDK", "main": "dist/npm/index.js", "module": "dist/npm/index.es.js", diff --git a/packages/analytics-v1.1/project.json b/packages/analytics-v1.1/project.json index 3199e0ee0..7911597ab 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.27", - "title": "rudder-sdk-js@2.48.27", - "discussion-category": "rudder-sdk-js@2.48.27", + "tag": "rudder-sdk-js@2.48.28", + "title": "rudder-sdk-js@2.48.28", + "discussion-category": "rudder-sdk-js@2.48.28", "notesFile": "./packages/analytics-v1.1/CHANGELOG_LATEST.md" } } diff --git a/packages/loading-scripts/CHANGELOG.md b/packages/loading-scripts/CHANGELOG.md index 097403e18..90b7bf15b 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.43](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.42...@rudderstack/analytics-js-loading-scripts@3.0.43) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.0` ## [3.0.42](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.41...@rudderstack/analytics-js-loading-scripts@3.0.42) (2024-11-18) ### Dependency Updates diff --git a/packages/loading-scripts/CHANGELOG_LATEST.md b/packages/loading-scripts/CHANGELOG_LATEST.md index 8cc0fd520..c5a63f2f2 100644 --- a/packages/loading-scripts/CHANGELOG_LATEST.md +++ b/packages/loading-scripts/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.0.42](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.41...@rudderstack/analytics-js-loading-scripts@3.0.42) (2024-11-18) +## [3.0.43](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.42...@rudderstack/analytics-js-loading-scripts@3.0.43) (2024-11-18) ### Dependency Updates -* `@rudderstack/analytics-js` updated to version `3.10.2` +* `@rudderstack/analytics-js` updated to version `3.11.0` diff --git a/packages/loading-scripts/package.json b/packages/loading-scripts/package.json index 96aff3f41..27501eb65 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.42", + "version": "3.0.43", "private": true, "description": "Loading script for RudderStack JavaScript SDK", "main": "./src/index.js", diff --git a/packages/loading-scripts/project.json b/packages/loading-scripts/project.json index eb34ebd77..170240e8c 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.42", - "title": "@rudderstack/analytics-js-loading-scripts@3.0.42", - "discussion-category": "@rudderstack/analytics-js-loading-scripts@3.0.42", + "tag": "@rudderstack/analytics-js-loading-scripts@3.0.43", + "title": "@rudderstack/analytics-js-loading-scripts@3.0.43", + "discussion-category": "@rudderstack/analytics-js-loading-scripts@3.0.43", "notesFile": "./packages/loading-scripts/CHANGELOG_LATEST.md" } } diff --git a/packages/sanity-suite/CHANGELOG.md b/packages/sanity-suite/CHANGELOG.md index c3cfe7c4a..2648fd890 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.34](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.33...@rudderstack/analytics-js-sanity-suite@3.1.34) (2024-11-18) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.0` ## [3.1.33](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.32...@rudderstack/analytics-js-sanity-suite@3.1.33) (2024-11-18) ### Dependency Updates diff --git a/packages/sanity-suite/package.json b/packages/sanity-suite/package.json index baee28322..2e86105de 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.33", + "version": "3.1.34", "private": true, "description": "Sanity suite for testing JS SDK package", "main": "./dist/v3/cdn/testBook.js", diff --git a/sonar-project.properties b/sonar-project.properties index b6072b34a..48e22e030 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.51.0 +sonar.projectVersion=3.52.0 # Meta-data for the project sonar.links.scm=https://github.com/rudderlabs/rudder-sdk-js