From 5f087eb17e88c54e23d17ade9d2f78bba5e2d722 Mon Sep 17 00:00:00 2001 From: devops-github-rudderstack <88187154+devops-github-rudderstack@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:30:55 +0530 Subject: [PATCH] chore(release): pull hotfix-release/3.74.0-SDK-2903 into main (#2015) * feat: lock plugins and integrations version by default (#1956) * feat: lock plugins and integrations version by default * chore: avoid publishing latest plugins and integrations to common prod location * chore: regenerate listing html for other paths * feat: get sdk url from script tag using the write key * choer: bump size limit * chore: update deploy script to copy dependencies first * chore: include url in cdn path error messages * fix: lock deps version through build * chore: add env to deploy workflow * fix: cdn paths edge case handling * chore: fix lock deps envs * chore: revert unnecessary changes * feat: lock plugins and integrations versions for custom urls * chore: fix a minor lint issue * fix: load options normalization for boolean values * test: add tests for coverage and fix issues * chore: fix size limit * refactor: rename variables and add in-line docs to improve readability * chore: avoid cache invalidation for versioned directories by default * refactor: fix a lint issue * chore(@rudderstack/analytics-js-common): release version 3.15.0 * chore(@rudderstack/analytics-js-cookies): release version 0.4.19 * chore(@rudderstack/analytics-js-plugins): release version 3.6.22 * chore(@rudderstack/analytics-js): release version 3.12.0 * chore(rudder-sdk-js): release version 2.48.44 * chore(@rudderstack/analytics-js-integrations): release version 3.12.2 * chore(@rudderstack/analytics-js-service-worker): release version 3.2.19 * chore(@rudderstack/analytics-js-sanity-suite): release version 3.2.0 * chore(monorepo): sync versions and generate release logs --------- Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> Co-authored-by: GitHub actions --- .github/workflows/deploy.yml | 164 +- .github/workflows/rollback.yml | 41 + jest/jest.setup-dom.js | 1 + package-lock.json | 20 +- package.json | 2 +- packages/analytics-js-common/CHANGELOG.md | 7 + .../analytics-js-common/CHANGELOG_LATEST.md | 6 +- .../__tests__/utilities/object.test.ts | 178 ++ packages/analytics-js-common/package.json | 2 +- packages/analytics-js-common/project.json | 6 +- .../src/types/LoadOptions.ts | 10 +- .../src/utilities/object.ts | 40 +- packages/analytics-js-cookies/CHANGELOG.md | 5 + .../analytics-js-cookies/CHANGELOG_LATEST.md | 4 +- packages/analytics-js-cookies/package.json | 2 +- packages/analytics-js-cookies/project.json | 6 +- .../analytics-js-integrations/CHANGELOG.md | 5 + .../CHANGELOG_LATEST.md | 8 +- .../analytics-js-integrations/package.json | 2 +- .../analytics-js-integrations/project.json | 6 +- packages/analytics-js-plugins/CHANGELOG.md | 6 + .../analytics-js-plugins/CHANGELOG_LATEST.md | 6 +- packages/analytics-js-plugins/package.json | 2 +- packages/analytics-js-plugins/project.json | 6 +- .../analytics-js-service-worker/CHANGELOG.md | 5 + .../CHANGELOG_LATEST.md | 9 +- .../analytics-js-service-worker/package.json | 2 +- .../analytics-js-service-worker/project.json | 6 +- packages/analytics-js/.env.example | 3 +- packages/analytics-js/.size-limit.mjs | 8 +- packages/analytics-js/CHANGELOG.md | 12 + packages/analytics-js/CHANGELOG_LATEST.md | 13 +- .../configManager/ConfigManager.test.ts | 3 +- .../components/configManager/cdnPaths.test.ts | 182 +- .../configManager/commonUtil.test.ts | 49 +- .../components/utilities/loadOptions.test.ts | 1779 +++++++++++++++++ .../StoreManager/StoreManager.test.ts | 1 + packages/analytics-js/package.json | 2 +- packages/analytics-js/project.json | 6 +- packages/analytics-js/public/index.html | 16 +- packages/analytics-js/rollup.config.mjs | 13 +- .../components/configManager/ConfigManager.ts | 1 + .../components/configManager/util/cdnPaths.ts | 89 +- .../configManager/util/commonUtil.ts | 10 + .../src/components/utilities/loadOptions.ts | 144 +- .../analytics-js/src/constants/logMessages.ts | 4 +- packages/analytics-js/src/constants/urls.ts | 8 +- .../src/state/slices/lifecycle.ts | 6 +- .../src/state/slices/loadOptions.ts | 7 +- packages/analytics-v1.1/CHANGELOG.md | 5 + packages/analytics-v1.1/CHANGELOG_LATEST.md | 4 +- packages/analytics-v1.1/package.json | 2 +- packages/analytics-v1.1/project.json | 6 +- packages/sanity-suite/CHANGELOG.md | 10 + packages/sanity-suite/package.json | 2 +- .../sanity-suite/public/v1.1/index-local.html | 1 - .../sanity-suite/public/v3/index-local.html | 1 - .../public/v3/integrations/index-local.html | 1 - .../public/v3/manualLoadCall/index-local.html | 1 - sonar-project.properties | 2 +- types/global.d.ts | 2 + 61 files changed, 2592 insertions(+), 358 deletions(-) create mode 100644 packages/analytics-js/__tests__/components/utilities/loadOptions.test.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 061b7b2ab6..ddc8183c04 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,9 +48,16 @@ permissions: contents: read # This is required for actions/checkout env: - NODE_OPTIONS: '--no-warnings' - CACHE_CONTROL_NO_STORE: '"no-store"' - CACHE_CONTROL_MAX_AGE: '"max-age=3600"' + NODE_OPTIONS: "--no-warnings" + CACHE_CONTROL_NO_STORE: "\"no-store\"" + CACHE_CONTROL_MAX_AGE: "\"max-age=3600\"" + INTEGRATIONS_ZIP_FILE: "all_integration_sdks.tar.gz" + PLUGINS_ZIP_FILE: "all_plugins.tar.gz" + INTEGRATIONS_ARTIFACTS_BASE_PATH: "packages/analytics-js-integrations/dist/cdn" + PLUGINS_ARTIFACTS_BASE_PATH: "packages/analytics-js-plugins/dist/cdn" + CORE_ARTIFACTS_BASE_PATH: "packages/analytics-js/dist/cdn" + INTEGRATIONS_HTML_FILE: "list.html" + PLUGINS_HTML_FILE: "list.html" jobs: deploy: @@ -103,6 +110,7 @@ jobs: REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ inputs.s3_dir_path }}/modern/plugins' BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} BUGSNAG_RELEASE_STAGE: ${{ inputs.bugsnag_release_stage }} + LOCK_DEPS_VERSION: ${{ inputs.environment == 'production' && 'true' || 'false' }} run: | npm run setup:ci @@ -110,10 +118,27 @@ jobs: env: BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} BUGSNAG_RELEASE_STAGE: ${{ inputs.bugsnag_release_stage }} + LOCK_DEPS_VERSION: ${{ inputs.environment == 'production' && 'true' || 'false' }} run: | npm run build:browser npm run build:browser:modern + - name: Generate zip files for integrations and plugins + run: | + # Generate a zip file of all the integrations + tmp_file="/tmp/legacy_${{ env.INTEGRATIONS_ZIP_FILE }}" + tar -czvf "$tmp_file" -C "${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/legacy/js-integrations/" . + mv "$tmp_file" "${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/legacy/js-integrations/${{ env.INTEGRATIONS_ZIP_FILE }}" + + tmp_file="/tmp/modern_${{ env.INTEGRATIONS_ZIP_FILE }}" + tar -czvf "$tmp_file" -C "${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/modern/js-integrations/" . + mv "$tmp_file" "${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/modern/js-integrations/${{ env.INTEGRATIONS_ZIP_FILE }}" + + # Generate a zip file of all the plugins + tmp_file="/tmp/${{ env.PLUGINS_ZIP_FILE }}" + tar -czvf "$tmp_file" -C ${{ env.PLUGINS_ARTIFACTS_BASE_PATH }}/modern/plugins/ . + mv "$tmp_file" "${{ env.PLUGINS_ARTIFACTS_BASE_PATH }}/modern/plugins/${{ env.PLUGINS_ZIP_FILE }}" + - name: Copy assets to S3 if: ${{ inputs.environment == 'production' }} run: | @@ -135,106 +160,75 @@ jobs: echo "CACHE_CONTROL=${{ env.CACHE_CONTROL_MAX_AGE }}" >> $GITHUB_ENV fi - - name: Copy SDK artifacts to S3 + # IMPORTANT: We're deliberately copying the artifacts to versioned directory ahead of + # of the common production paths to avoid any downtime in production + - name: Copy SDK plugins and integrations artifacts to S3 (versioned directory) + if: ${{ inputs.environment == 'production' }} run: | - core_sdk_path_prefix="packages/analytics-js/dist/cdn" - integration_sdks_path_prefix="packages/analytics-js-integrations/dist/cdn" - plugins_path_prefix="packages/analytics-js-plugins/dist/cdn" - s3_relative_path_prefix="${{ inputs.s3_dir_path }}" + s3_relative_path_prefix="${{ env.CURRENT_VERSION_VALUE }}" s3_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/$s3_relative_path_prefix" - copy_recursive_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" - copy_args="--cache-control ${{ env.CACHE_CONTROL }}" - - integration_sdks_zip_file="all_integration_sdks.tar.gz" - plugins_zip_file="all_plugins.tar.gz" - - integration_sdks_html_file="list.html" - plugins_html_file="list.html" - - # Generate a zip file of all the integrations - tmp_file="/tmp/legacy_$integration_sdks_zip_file" - tar -czvf "$tmp_file" -C "$integration_sdks_path_prefix/legacy/js-integrations/" . - mv "$tmp_file" "$integration_sdks_path_prefix/legacy/js-integrations/$integration_sdks_zip_file" - - tmp_file="/tmp/modern_$integration_sdks_zip_file" - tar -czvf "$tmp_file" -C "$integration_sdks_path_prefix/modern/js-integrations/" . - mv "$tmp_file" "$integration_sdks_path_prefix/modern/js-integrations/$integration_sdks_zip_file" - - # Generate a zip file of all the plugins - tmp_file="/tmp/$plugins_zip_file" - tar -czvf "$tmp_file" -C $plugins_path_prefix/modern/plugins/ . - mv "$tmp_file" "$plugins_path_prefix/modern/plugins/$plugins_zip_file" - - # Upload all the files to S3 - aws s3 cp $core_sdk_path_prefix/legacy/iife/ $s3_path_prefix/legacy/ $copy_recursive_args - aws s3 cp $core_sdk_path_prefix/modern/iife/ $s3_path_prefix/modern/ $copy_recursive_args - - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_recursive_args - aws s3 cp $plugins_path_prefix/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_recursive_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_recursive_args + copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL_MAX_AGE }}" # Generate the HTML file to list all the integrations - ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/legacy/js-integrations $integration_sdks_html_file $integration_sdks_path_prefix/legacy/js-integrations "Device Mode Integrations (Legacy)" $integration_sdks_zip_file + ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/legacy/js-integrations ${{ env.INTEGRATIONS_HTML_FILE }} ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/legacy/js-integrations "Device Mode Integrations (Legacy)" ${{ env.INTEGRATIONS_ZIP_FILE }} - ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/js-integrations $integration_sdks_html_file $integration_sdks_path_prefix/modern/js-integrations "Device Mode Integrations (Modern)" $integration_sdks_zip_file + ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/js-integrations ${{ env.INTEGRATIONS_HTML_FILE }} ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/modern/js-integrations "Device Mode Integrations (Modern)" ${{ env.INTEGRATIONS_ZIP_FILE }} # Generate the HTML file to list all the plugins - ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/plugins $plugins_html_file $plugins_path_prefix/modern/plugins "Plugins" $plugins_zip_file - - # Copy the HTML files to S3 - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/$integration_sdks_html_file $s3_path_prefix/legacy/js-integrations/$integration_sdks_html_file $copy_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/$integration_sdks_html_file $s3_path_prefix/modern/js-integrations/$integration_sdks_html_file $copy_args - aws s3 cp $plugins_path_prefix/modern/plugins/$plugins_html_file $s3_path_prefix/modern/plugins/$plugins_html_file $copy_args + ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/plugins ${{ env.PLUGINS_HTML_FILE }} ${{ env.PLUGINS_ARTIFACTS_BASE_PATH }}/modern/plugins "Plugins" ${{ env.PLUGINS_ZIP_FILE }} - - name: Invalidate CloudFront cache for all the SDK artifacts - run: | - invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ inputs.s3_dir_path }}/*" --query "Invalidation.Id" --output text) - - aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" + # Copy all the files to S3 + aws s3 cp ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_args + aws s3 cp ${{ env.PLUGINS_ARTIFACTS_BASE_PATH }}/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_args + aws s3 cp ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args - - name: Copy SDK artifacts to S3 (versioned directory) + - name: Copy SDK core artifacts to S3 (versioned directory) if: ${{ inputs.environment == 'production' }} run: | - core_sdk_path_prefix="packages/analytics-js/dist/cdn" - integration_sdks_path_prefix="packages/analytics-js-integrations/dist/cdn" - plugins_path_prefix="packages/analytics-js-plugins/dist/cdn" s3_relative_path_prefix="${{ env.CURRENT_VERSION_VALUE }}" s3_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/$s3_relative_path_prefix" - copy_recursive_args="--recursive --cache-control ${{ env.CACHE_CONTROL_MAX_AGE }}" - copy_args="--cache-control ${{ env.CACHE_CONTROL_MAX_AGE }}" - - integration_sdks_zip_file="all_integration_sdks.tar.gz" - plugins_zip_file="all_plugins.tar.gz" - - integration_sdks_html_file="list.html" - plugins_html_file="list.html" + copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL_MAX_AGE }}" # Copy all the files to S3 - aws s3 cp $core_sdk_path_prefix/legacy/iife/ $s3_path_prefix/legacy/ $copy_recursive_args - aws s3 cp $core_sdk_path_prefix/modern/iife/ $s3_path_prefix/modern/ $copy_recursive_args - - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_recursive_args - aws s3 cp $plugins_path_prefix/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_recursive_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_recursive_args + aws s3 cp ${{ env.CORE_ARTIFACTS_BASE_PATH }}/legacy/iife/ $s3_path_prefix/legacy/ $copy_args + aws s3 cp ${{ env.CORE_ARTIFACTS_BASE_PATH }}/modern/iife/ $s3_path_prefix/modern/ $copy_args + - name: Copy SDK plugins and integrations artifacts to S3 + # IMPORTANT: We're deliberately avoiding copying these artifacts for the production environment + # as they are already copied to versioned directory and expected to be loaded from there + if: ${{ inputs.environment != 'production' }} + run: | + s3_relative_path_prefix="${{ inputs.s3_dir_path }}" + s3_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/$s3_relative_path_prefix" + copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" + # Generate the HTML file to list all the integrations - ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/legacy/js-integrations $integration_sdks_html_file $integration_sdks_path_prefix/legacy/js-integrations "Device Mode Integrations (Legacy)" $integration_sdks_zip_file + ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/legacy/js-integrations ${{ env.INTEGRATIONS_HTML_FILE }} ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/legacy/js-integrations "Device Mode Integrations (Legacy)" ${{ env.INTEGRATIONS_ZIP_FILE }} - ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/js-integrations $integration_sdks_html_file $integration_sdks_path_prefix/modern/js-integrations "Device Mode Integrations (Modern)" $integration_sdks_zip_file + ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/js-integrations ${{ env.INTEGRATIONS_HTML_FILE }} ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/modern/js-integrations "Device Mode Integrations (Modern)" ${{ env.INTEGRATIONS_ZIP_FILE }} # Generate the HTML file to list all the plugins - ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/plugins $plugins_html_file $plugins_path_prefix/modern/plugins "Plugins" $plugins_zip_file - - # Copy all the HTML files to S3 - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/$integration_sdks_html_file $s3_path_prefix/legacy/js-integrations/$integration_sdks_html_file $copy_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/$integration_sdks_html_file $s3_path_prefix/modern/js-integrations/$integration_sdks_html_file $copy_args - aws s3 cp $plugins_path_prefix/modern/plugins/$plugins_html_file $s3_path_prefix/modern/plugins/$plugins_html_file $copy_args + ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} $s3_relative_path_prefix/modern/plugins ${{ env.PLUGINS_HTML_FILE }} ${{ env.PLUGINS_ARTIFACTS_BASE_PATH }}/modern/plugins "Plugins" ${{ env.PLUGINS_ZIP_FILE }} - - name: Invalidate CloudFront cache for all the SDK artifacts (versioned directory) - if: ${{ inputs.environment == 'production' }} + # Copy all the files to S3 + aws s3 cp ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_args + aws s3 cp ${{ env.PLUGINS_ARTIFACTS_BASE_PATH }}/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_args + aws s3 cp ${{ env.INTEGRATIONS_ARTIFACTS_BASE_PATH }}/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args + + - name: Copy SDK core artifacts to S3 run: | - invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_VALUE }}/*" --query "Invalidation.Id" --output text) + s3_relative_path_prefix="${{ inputs.s3_dir_path }}" + s3_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/$s3_relative_path_prefix" + copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" + + # Copy all the files to S3 + aws s3 cp ${{ env.CORE_ARTIFACTS_BASE_PATH }}/legacy/iife/ $s3_path_prefix/legacy/ $copy_args + aws s3 cp ${{ env.CORE_ARTIFACTS_BASE_PATH }}/modern/iife/ $s3_path_prefix/modern/ $copy_args + - name: Invalidate CloudFront cache for all the SDK artifacts + run: | + invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ inputs.s3_dir_path }}/*" --query "Invalidation.Id" --output text) + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" - name: Send message to Slack channel @@ -279,8 +273,7 @@ jobs: ] } - # Below steps are for v1.1 SDK (legacy) - + # All the below steps are for v1.1 SDK (legacy) - name: Copy legacy SDK artifacts to S3 run: | core_sdk_path_prefix="packages/analytics-v1.1/dist/cdn" @@ -313,10 +306,3 @@ jobs: aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/js-integrations/ $copy_args aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args - - - name: Invalidate CloudFront cache for all the legacy SDK artifacts (versioned directory) - if: ${{ inputs.environment == 'production' }} - run: | - invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_V1_VALUE }}/*" --query "Invalidation.Id" --output text) - - aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" diff --git a/.github/workflows/rollback.yml b/.github/workflows/rollback.yml index f1d3589e26..1c6cffeea7 100644 --- a/.github/workflows/rollback.yml +++ b/.github/workflows/rollback.yml @@ -29,3 +29,44 @@ jobs: BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} SLACK_RELEASE_CHANNEL_ID: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + + # As we rollback to a previous version, we need to invalidate the CDN cache for the previous version's directory + # The above deploy action will invalidate the cache for the core SDK artifacts, so this step is needed for dependencies (plugins and integrations) + invalidate-cdn-cache: + needs: deploy + name: Invalidate CDN cache + runs-on: [self-hosted, Linux, X64] + + steps: + - name: Install AWS CLI + uses: unfor19/install-aws-cli-action@master + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_PROD_ACCOUNT_ID }}:role/${{ secrets.AWS_PROD_S3_SYNC_ROLE }} + aws-region: us-east-1 + + - name: Checkout source code + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + + - name: Get new versions + run: | + current_version_v1=$(jq -r .version packages/analytics-v1.1/package.json) + current_version=$(jq -r .version packages/analytics-js/package.json) + echo "CURRENT_VERSION_V1_VALUE=$current_version_v1" >> $GITHUB_ENV + echo "CURRENT_VERSION_VALUE=$current_version" >> $GITHUB_ENV + + - name: Invalidate CloudFront cache for all the SDK artifacts (versioned directory) + run: | + invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_VALUE }}/*" --query "Invalidation.Id" --output text) + + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --id "$invalidation_id" + + - name: Invalidate CloudFront cache for all the legacy SDK artifacts (versioned directory) + run: | + invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_V1_VALUE }}/*" --query "Invalidation.Id" --output text) + + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --id "$invalidation_id" diff --git a/jest/jest.setup-dom.js b/jest/jest.setup-dom.js index 9452bd18cf..5b7abb8ce8 100644 --- a/jest/jest.setup-dom.js +++ b/jest/jest.setup-dom.js @@ -6,6 +6,7 @@ global.window.document.body.innerHTML = documentHTML; global.window.innerWidth = 1680; global.window.innerHeight = 1024; global.window.__BUNDLE_ALL_PLUGINS__ = false; +global.window.__LOCK_DEPS_VERSION__ = false; global.window.__IS_LEGACY_BUILD__ = false; global.window.__IS_DYNAMIC_CUSTOM_BUNDLE__ = false; global.PromiseRejectionEvent = function (reason) { diff --git a/package-lock.json b/package-lock.json index 01a1cff3e1..5d6982eca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.73.0", + "version": "3.74.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.73.0", + "version": "3.74.0", "hasInstallScript": true, "license": "Elastic-2.0", "workspaces": [ @@ -25484,7 +25484,7 @@ }, "packages/analytics-js": { "name": "@rudderstack/analytics-js", - "version": "3.11.17", + "version": "3.12.0", "license": "Elastic-2.0", "dependencies": { "@preact/signals-core": "1.8.0", @@ -25498,7 +25498,7 @@ }, "packages/analytics-js-common": { "name": "@rudderstack/analytics-js-common", - "version": "3.14.15", + "version": "3.15.0", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25515,7 +25515,7 @@ }, "packages/analytics-js-cookies": { "name": "@rudderstack/analytics-js-cookies", - "version": "0.4.18", + "version": "0.4.19", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*" @@ -25524,7 +25524,7 @@ }, "packages/analytics-js-integrations": { "name": "@rudderstack/analytics-js-integrations", - "version": "3.12.1", + "version": "3.12.2", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25544,7 +25544,7 @@ }, "packages/analytics-js-plugins": { "name": "@rudderstack/analytics-js-plugins", - "version": "3.6.21", + "version": "3.6.22", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*", @@ -25558,7 +25558,7 @@ }, "packages/analytics-js-service-worker": { "name": "@rudderstack/analytics-js-service-worker", - "version": "3.2.18", + "version": "3.2.19", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25594,7 +25594,7 @@ }, "packages/analytics-v1.1": { "name": "rudder-sdk-js", - "version": "2.48.43", + "version": "2.48.44", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*" @@ -25612,7 +25612,7 @@ }, "packages/sanity-suite": { "name": "@rudderstack/analytics-js-sanity-suite", - "version": "3.1.51", + "version": "3.2.0", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*", diff --git a/package.json b/package.json index d05d569e75..9e5cb97ad5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.73.0", + "version": "3.74.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 7257077bd8..a35e699087 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.15.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.15...@rudderstack/analytics-js-common@3.15.0) (2025-01-24) + + +### Features + +* lock plugins and integrations version by default ([#1956](https://github.com/rudderlabs/rudder-sdk-js/issues/1956)) ([45e716e](https://github.com/rudderlabs/rudder-sdk-js/commit/45e716e6df3d6e665c25aa907531adb746961d50)) + ## [3.14.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.14...@rudderstack/analytics-js-common@3.14.15) (2025-01-03) diff --git a/packages/analytics-js-common/CHANGELOG_LATEST.md b/packages/analytics-js-common/CHANGELOG_LATEST.md index 74519f5cdc..b0cd874b05 100644 --- a/packages/analytics-js-common/CHANGELOG_LATEST.md +++ b/packages/analytics-js-common/CHANGELOG_LATEST.md @@ -1,7 +1,7 @@ -## [3.14.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.14...@rudderstack/analytics-js-common@3.14.15) (2025-01-03) +## [3.15.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.15...@rudderstack/analytics-js-common@3.15.0) (2025-01-24) -### Bug Fixes +### Features -* update destination constants ([#1968](https://github.com/rudderlabs/rudder-sdk-js/issues/1968)) ([fbd3b3f](https://github.com/rudderlabs/rudder-sdk-js/commit/fbd3b3fd82441f50092326765c58bfdacd314876)) +* lock plugins and integrations version by default ([#1956](https://github.com/rudderlabs/rudder-sdk-js/issues/1956)) ([45e716e](https://github.com/rudderlabs/rudder-sdk-js/commit/45e716e6df3d6e665c25aa907531adb746961d50)) diff --git a/packages/analytics-js-common/__tests__/utilities/object.test.ts b/packages/analytics-js-common/__tests__/utilities/object.test.ts index ef342a5312..9fd417360e 100644 --- a/packages/analytics-js-common/__tests__/utilities/object.test.ts +++ b/packages/analytics-js-common/__tests__/utilities/object.test.ts @@ -7,6 +7,8 @@ import { isObjectLiteralAndNotNull, removeUndefinedValues, removeUndefinedAndNullValues, + getNormalizedBooleanValue, + getNormalizedObjectValue, } from '../../src/utilities/object'; const identifyTraitsPayloadMock = { @@ -284,4 +286,180 @@ describe('Common Utils - Object', () => { }); }); }); + + describe('getNormalizedObjectValue', () => { + describe('should return undefined if input value is not an object', () => { + it('should return undefined for non-object values', () => { + const outcome1 = getNormalizedObjectValue(undefined); + const outcome2 = getNormalizedObjectValue(null); + const outcome3 = getNormalizedObjectValue('string'); + const outcome4 = getNormalizedObjectValue(123456); + const outcome5 = getNormalizedObjectValue([]); + expect(outcome1).toEqual(undefined); + expect(outcome2).toEqual(undefined); + expect(outcome3).toEqual(undefined); + expect(outcome4).toEqual(undefined); + expect(outcome5).toEqual(undefined); + }); + + it('should return undefined for empty object', () => { + const outcome = getNormalizedObjectValue({}); + expect(outcome).toEqual(undefined); + }); + + it('should return normalized object for valid object', () => { + const nestedObj = { + someKey: 'someValue', + nested: { + key1: 'value1', + key2: undefined, + key3: null, + }, + }; + + const outcome = getNormalizedObjectValue(nestedObj); + + expect(outcome).toStrictEqual({ + someKey: 'someValue', + nested: { + key1: 'value1', + }, + }); + }); + + it('should return normalized object for object with undefined and null values recursively', () => { + const nestedObj = { + key1: 'value', + key2: undefined, + key3: { + key4: 'value', + key5: undefined, + key6: { + key7: 'value', + key8: undefined, + }, + key9: null, + }, + key10: null, + key11: { + key12: null, + }, + }; + + const outcome = getNormalizedObjectValue(nestedObj); + + expect(outcome).toStrictEqual({ + key1: 'value', + key3: { + key4: 'value', + key6: { + key7: 'value', + }, + }, + key11: {}, + }); + }); + }); + }); + + describe('getNormalizedBooleanValue', () => { + const tcData = [ + { + input: [true, undefined], + output: true, + }, + { + input: [false, undefined], + output: false, + }, + { + input: [undefined, true], + output: true, + }, + { + input: [undefined, false], + output: false, + }, + { + input: [true, false], + output: true, + }, + { + input: [false, true], + output: false, + }, + { + input: [undefined, undefined], + output: false, + }, + { + input: [{}, false], + output: false, + }, + { + input: [{}, true], + output: false, + }, + { + input: [{}, undefined], + output: false, + }, + { + input: [[], false], + output: false, + }, + { + input: [[], true], + output: false, + }, + { + input: [[], undefined], + output: false, + }, + { + input: ['string', false], + output: false, + }, + { + input: ['string', true], + output: false, + }, + { + input: ['string', undefined], + output: false, + }, + { + input: [123456, false], + output: false, + }, + { + input: [123456, true], + output: false, + }, + { + input: [123456, undefined], + output: false, + }, + { + input: [new Date(), false], + output: false, + }, + { + input: [new Date(), true], + output: false, + }, + { + input: [new Date(), undefined], + output: false, + }, + ]; + + it.each(tcData)( + 'should return $output for input $input', + ({ input, output }: { input: any; output: any }) => { + const outcome = getNormalizedBooleanValue(input[0], input[1]); + expect(outcome).toEqual(output); + }, + ); + }); }); diff --git a/packages/analytics-js-common/package.json b/packages/analytics-js-common/package.json index 06dc03bde5..d18328a520 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.14.15", + "version": "3.15.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 db7f214973..e1e489640c 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.14.15", - "title": "@rudderstack/analytics-js-common@3.14.15", - "discussion-category": "@rudderstack/analytics-js-common@3.14.15", + "tag": "@rudderstack/analytics-js-common@3.15.0", + "title": "@rudderstack/analytics-js-common@3.15.0", + "discussion-category": "@rudderstack/analytics-js-common@3.15.0", "notesFile": "./packages/analytics-js-common/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-common/src/types/LoadOptions.ts b/packages/analytics-js-common/src/types/LoadOptions.ts index ad615b2f81..536fedc5ff 100644 --- a/packages/analytics-js-common/src/types/LoadOptions.ts +++ b/packages/analytics-js-common/src/types/LoadOptions.ts @@ -144,8 +144,14 @@ export type LoadOptions = { anonymousIdOptions?: AnonymousIdOptions; setCookieDomain?: string; // defaults to current domain. sameSiteCookie?: CookieSameSite; // defaults to Lax. - lockIntegrationsVersion?: boolean; // defaults to false. - lockPluginsVersion?: boolean; // defaults to false. + /** + * @deprecated Integrations version are locked by default. We do not recommend using this option. + */ + lockIntegrationsVersion?: boolean; // defaults to true. + /** + * @deprecated Plugins version are locked by default. We do not recommend using this option. + */ + lockPluginsVersion?: boolean; // defaults to true. polyfillIfRequired?: boolean; // defaults to true. Controls whether the SDK should polyfill unsupported browser API's if they are detected as missing onLoaded?: OnLoadedCallback; uaChTrackLevel?: UaChTrackLevel; diff --git a/packages/analytics-js-common/src/utilities/object.ts b/packages/analytics-js-common/src/utilities/object.ts index 366e605a00..6c0e4d4913 100644 --- a/packages/analytics-js-common/src/utilities/object.ts +++ b/packages/analytics-js-common/src/utilities/object.ts @@ -94,17 +94,36 @@ const removeUndefinedAndNullValues = >(obj: T): T => { }; /** - * A utility to get all the values from an object - * @param obj Input object - * @returns an array of values from the input object + * Normalizes an object by removing undefined and null values. + * @param val - The value to normalize + * @returns The normalized object, or undefined if input is not a non-empty object + * @example + * getNormalizedObjectValue({ a: 1, b: null, c: undefined }) // returns { a: 1 } + * getNormalizedObjectValue({}) // returns undefined + * getNormalizedObjectValue(null) // returns undefined */ -const getObjectValues = >(obj: T): any[] => { - const result: any[] = []; - Object.keys(obj as Record).forEach(key => { - result.push((obj as Record)[key]); - }); +const getNormalizedObjectValue = (val: any): any => { + if (!isNonEmptyObject(val)) { + return undefined; + } + + return removeUndefinedAndNullValues(val); +}; + +/** + * Normalizes a value to a boolean, with support for a default value + * @param val Input value + * @param defVal Default value + * @returns Returns the normalized boolean value + * @example + * getNormalizedBooleanValue(true, false) // returns true + */ +const getNormalizedBooleanValue = (val: any, defVal: boolean | undefined): boolean | undefined => { + if (isDefined(defVal)) { + return isDefined(val) ? val === true : defVal; + } - return result; + return val === true; }; export { @@ -117,6 +136,7 @@ export { isObjectLiteralAndNotNull, removeUndefinedValues, removeUndefinedAndNullValues, - getObjectValues, isObject, + getNormalizedObjectValue, + getNormalizedBooleanValue, }; diff --git a/packages/analytics-js-cookies/CHANGELOG.md b/packages/analytics-js-cookies/CHANGELOG.md index b891ed834e..482f454742 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.19](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.18...@rudderstack/analytics-js-cookies@0.4.19) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.15.0` ## [0.4.18](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.17...@rudderstack/analytics-js-cookies@0.4.18) (2025-01-03) ### Dependency Updates diff --git a/packages/analytics-js-cookies/CHANGELOG_LATEST.md b/packages/analytics-js-cookies/CHANGELOG_LATEST.md index 7d965e8dad..c20823051c 100644 --- a/packages/analytics-js-cookies/CHANGELOG_LATEST.md +++ b/packages/analytics-js-cookies/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [0.4.18](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.17...@rudderstack/analytics-js-cookies@0.4.18) (2025-01-03) +## [0.4.19](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.18...@rudderstack/analytics-js-cookies@0.4.19) (2025-01-24) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.15` +* `@rudderstack/analytics-js-common` updated to version `3.15.0` diff --git a/packages/analytics-js-cookies/package.json b/packages/analytics-js-cookies/package.json index c32e121fb5..6abb720c13 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.18", + "version": "0.4.19", "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 838f242dba..b5c3e8183b 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.18", - "title": "@rudderstack/analytics-js-cookies@0.4.18", - "discussion-category": "@rudderstack/analytics-js-cookies@0.4.18", + "tag": "@rudderstack/analytics-js-cookies@0.4.19", + "title": "@rudderstack/analytics-js-cookies@0.4.19", + "discussion-category": "@rudderstack/analytics-js-cookies@0.4.19", "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 90a312af02..7994398296 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.12.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.12.1...@rudderstack/analytics-js-integrations@3.12.2) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.15.0` ## [3.12.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.12.0...@rudderstack/analytics-js-integrations@3.12.1) (2025-01-22) diff --git a/packages/analytics-js-integrations/CHANGELOG_LATEST.md b/packages/analytics-js-integrations/CHANGELOG_LATEST.md index 79d2c98349..4196516b19 100644 --- a/packages/analytics-js-integrations/CHANGELOG_LATEST.md +++ b/packages/analytics-js-integrations/CHANGELOG_LATEST.md @@ -1,7 +1,5 @@ -## [3.12.1](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.12.0...@rudderstack/analytics-js-integrations@3.12.1) (2025-01-22) +## [3.12.2](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.12.1...@rudderstack/analytics-js-integrations@3.12.2) (2025-01-24) +### Dependency Updates -### Bug Fixes - -* braze anonymousId tracking with alias details ([#1994](https://github.com/rudderlabs/rudder-sdk-js/issues/1994)) ([7215304](https://github.com/rudderlabs/rudder-sdk-js/commit/721530493d559ba89d2bfe1156a916a8885bb34c)) - +* `@rudderstack/analytics-js-common` updated to version `3.15.0` diff --git a/packages/analytics-js-integrations/package.json b/packages/analytics-js-integrations/package.json index 066aa63653..a95b2045d2 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.12.1", + "version": "3.12.2", "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 afab101e6a..f02ed9c9e9 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.12.1", - "title": "@rudderstack/analytics-js-integrations@3.12.1", - "discussion-category": "@rudderstack/analytics-js-integrations@3.12.1", + "tag": "@rudderstack/analytics-js-integrations@3.12.2", + "title": "@rudderstack/analytics-js-integrations@3.12.2", + "discussion-category": "@rudderstack/analytics-js-integrations@3.12.2", "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 b4b6cb208f..31e1b6e19d 100644 --- a/packages/analytics-js-plugins/CHANGELOG.md +++ b/packages/analytics-js-plugins/CHANGELOG.md @@ -2,6 +2,12 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.6.22](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.21...@rudderstack/analytics-js-plugins@3.6.22) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.15.0` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.19` ## [3.6.21](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.20...@rudderstack/analytics-js-plugins@3.6.21) (2025-01-03) ### Dependency Updates diff --git a/packages/analytics-js-plugins/CHANGELOG_LATEST.md b/packages/analytics-js-plugins/CHANGELOG_LATEST.md index 1ed51344df..67f804ae22 100644 --- a/packages/analytics-js-plugins/CHANGELOG_LATEST.md +++ b/packages/analytics-js-plugins/CHANGELOG_LATEST.md @@ -1,6 +1,6 @@ -## [3.6.21](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.20...@rudderstack/analytics-js-plugins@3.6.21) (2025-01-03) +## [3.6.22](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.21...@rudderstack/analytics-js-plugins@3.6.22) (2025-01-24) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.15` -* `@rudderstack/analytics-js-cookies` updated to version `0.4.18` +* `@rudderstack/analytics-js-common` updated to version `3.15.0` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.19` diff --git a/packages/analytics-js-plugins/package.json b/packages/analytics-js-plugins/package.json index b0af892d1c..961779d402 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.21", + "version": "3.6.22", "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 7c43bac887..0de5603b81 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.21", - "title": "@rudderstack/analytics-js-plugins@3.6.21", - "discussion-category": "@rudderstack/analytics-js-plugins@3.6.21", + "tag": "@rudderstack/analytics-js-plugins@3.6.22", + "title": "@rudderstack/analytics-js-plugins@3.6.22", + "discussion-category": "@rudderstack/analytics-js-plugins@3.6.22", "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 9a243257fb..865f74b31b 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.19](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.18...@rudderstack/analytics-js-service-worker@3.2.19) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.15.0` ## [3.2.18](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.17...@rudderstack/analytics-js-service-worker@3.2.18) (2025-01-03) ### Dependency Updates diff --git a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md index b5f01f574e..3c486d3e33 100644 --- a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md +++ b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md @@ -1,10 +1,5 @@ -## [3.2.18](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.17...@rudderstack/analytics-js-service-worker@3.2.18) (2025-01-03) +## [3.2.19](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.18...@rudderstack/analytics-js-service-worker@3.2.19) (2025-01-24) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.15` - -### Bug Fixes - -* vulnerabilities in dependencies ([#1965](https://github.com/rudderlabs/rudder-sdk-js/issues/1965)) ([61e1e6e](https://github.com/rudderlabs/rudder-sdk-js/commit/61e1e6e272d40796f56cffe118b387f22b14f620)) - +* `@rudderstack/analytics-js-common` updated to version `3.15.0` diff --git a/packages/analytics-js-service-worker/package.json b/packages/analytics-js-service-worker/package.json index ecef3e0b0f..aee3fb49f9 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.18", + "version": "3.2.19", "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 4fc492cb09..e5a42064f0 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.18", - "title": "rudderstack/analytics-js-service-worker@3.2.18", - "discussion-category": "rudderstack/analytics-js-service-worker@3.2.18", + "tag": "@rudderstack/analytics-js-service-worker@3.2.19", + "title": "rudderstack/analytics-js-service-worker@3.2.19", + "discussion-category": "rudderstack/analytics-js-service-worker@3.2.19", "notesFile": "./packages/analytics-js-service-worker/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/.env.example b/packages/analytics-js/.env.example index eb0bfe7e95..fce36073b1 100644 --- a/packages/analytics-js/.env.example +++ b/packages/analytics-js/.env.example @@ -1,7 +1,8 @@ # Variables required for the example WRITE_KEY= DATAPLANE_URL= -TEST_FILE_PATH= + +# Optional variables CONFIG_SERVER_HOST= DEST_SDK_BASE_URL= REMOTE_MODULES_BASE_PATH= diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index 9b26ac3a8a..1b1240e776 100644 --- a/packages/analytics-js/.size-limit.mjs +++ b/packages/analytics-js/.size-limit.mjs @@ -13,7 +13,7 @@ export default [ name: 'Core - Legacy - NPM (CJS)', path: 'dist/npm/legacy/cjs/index.cjs', import: '*', - limit: '49.1 KiB', + limit: '49.3 KiB', }, { name: 'Core - Legacy - NPM (UMD)', @@ -24,7 +24,7 @@ export default [ { name: 'Core - Legacy - CDN', path: 'dist/cdn/legacy/iife/rsa.min.js', - limit: '49 KiB', + limit: '49.1 KiB', }, { name: 'Core - Modern - NPM (ESM)', @@ -59,7 +59,7 @@ export default [ name: 'Core (Bundled) - Legacy - NPM (CJS)', path: 'dist/npm/legacy/bundled/cjs/index.cjs', import: '*', - limit: '49.2 KiB', + limit: '49.5 KiB', }, { name: 'Core (Bundled) - Legacy - NPM (UMD)', @@ -95,7 +95,7 @@ export default [ name: 'Core (Content Script) - Legacy - NPM (CJS)', path: 'dist/npm/legacy/content-script/cjs/index.cjs', import: '*', - limit: '48.6 KiB', + limit: '48.8 KiB', }, { name: 'Core (Content Script) - Legacy - NPM (UMD)', diff --git a/packages/analytics-js/CHANGELOG.md b/packages/analytics-js/CHANGELOG.md index 56db30cdac..018f338d2a 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.12.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.17...@rudderstack/analytics-js@3.12.0) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js-cookies` updated to version `0.4.19` +* `@rudderstack/analytics-js-common` updated to version `3.15.0` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.22` + +### Features + +* lock plugins and integrations version by default ([#1956](https://github.com/rudderlabs/rudder-sdk-js/issues/1956)) ([45e716e](https://github.com/rudderlabs/rudder-sdk-js/commit/45e716e6df3d6e665c25aa907531adb746961d50)) + ## [3.11.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.16...@rudderstack/analytics-js@3.11.17) (2025-01-03) ### Dependency Updates diff --git a/packages/analytics-js/CHANGELOG_LATEST.md b/packages/analytics-js/CHANGELOG_LATEST.md index df466bfa3c..98af017a68 100644 --- a/packages/analytics-js/CHANGELOG_LATEST.md +++ b/packages/analytics-js/CHANGELOG_LATEST.md @@ -1,7 +1,12 @@ -## [3.11.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.16...@rudderstack/analytics-js@3.11.17) (2025-01-03) +## [3.12.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.17...@rudderstack/analytics-js@3.12.0) (2025-01-24) ### Dependency Updates -* `@rudderstack/analytics-js-cookies` updated to version `0.4.18` -* `@rudderstack/analytics-js-common` updated to version `3.14.15` -* `@rudderstack/analytics-js-plugins` updated to version `3.6.21` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.19` +* `@rudderstack/analytics-js-common` updated to version `3.15.0` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.22` + +### Features + +* lock plugins and integrations version by default ([#1956](https://github.com/rudderlabs/rudder-sdk-js/issues/1956)) ([45e716e](https://github.com/rudderlabs/rudder-sdk-js/commit/45e716e6df3d6e665c25aa907531adb746961d50)) + diff --git a/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts b/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts index 9f1fc09154..37257815f4 100644 --- a/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts +++ b/packages/analytics-js/__tests__/components/configManager/ConfigManager.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/deprecation */ import { effect, signal } from '@preact/signals-core'; import { http, HttpResponse } from 'msw'; import { defaultHttpClient } from '../../../src/services/HttpClient'; @@ -8,7 +9,7 @@ import { state, resetState } from '../../../src/state'; import { getSDKUrl } from '../../../src/components/configManager/util/commonUtil'; import { server } from '../../../__fixtures__/msw.server'; import { dummySourceConfigResponse } from '../../../__fixtures__/fixtures'; -import { +import type { ConfigResponseDestinationItem, SourceConfigResponse, } from '../../../src/components/configManager/types'; diff --git a/packages/analytics-js/__tests__/components/configManager/cdnPaths.test.ts b/packages/analytics-js/__tests__/components/configManager/cdnPaths.test.ts index 1f2e912889..cf97e18fea 100644 --- a/packages/analytics-js/__tests__/components/configManager/cdnPaths.test.ts +++ b/packages/analytics-js/__tests__/components/configManager/cdnPaths.test.ts @@ -1,10 +1,10 @@ -import { CDN_INT_DIR } from '@rudderstack/analytics-js-common/constants/urls'; +import { resetState, state } from '../../../src/state'; import { getIntegrationsCDNPath, getPluginsCDNPath, } from '../../../src/components/configManager/util/cdnPaths'; -import { getSDKUrl } from '../../../src/components/configManager/util/commonUtil'; -import { DEST_SDK_BASE_URL, SDK_CDN_BASE_URL } from '../../../src/constants/urls'; + +const getSDKUrlMock = jest.fn(); jest.mock('../../../src/components/configManager/util/commonUtil.ts', () => { const originalModule = jest.requireActual( @@ -14,87 +14,138 @@ jest.mock('../../../src/components/configManager/util/commonUtil.ts', () => { return { __esModule: true, ...originalModule, - getSDKUrl: jest.fn(), + getSDKUrl: (...args: any[]) => getSDKUrlMock(...args), }; }); describe('CDN path utilities', () => { - describe('getIntegrationsCDNPath', () => { - const dummyCustomURL = 'https://www.dummy.url/integrations'; - const dummyScriptURL = 'https://www.dummy.url/fromScript/v3/modern/rsa.min.js'; - const dummyVersion = '3.x.x'; + const dummyScriptURL = 'https://www.dummy.url/fromScript/v3/modern/rsa.min.js'; + const dummyVersion = '3.x.x'; - beforeEach(() => { - getSDKUrl.mockImplementation(() => dummyScriptURL); - }); + beforeEach(() => { + getSDKUrlMock.mockImplementation(() => dummyScriptURL); - afterEach(() => { - jest.resetAllMocks(); - }); + // @ts-expect-error needed for testing + state.context.app.value.installType = 'cdn'; + }); - it('should return custom url if valid url is provided', () => { + afterEach(() => { + jest.resetAllMocks(); + + resetState(); + }); + + describe('getIntegrationsCDNPath', () => { + const dummyCustomURL = 'https://www.dummy.url/integrations/'; + + it('should return custom URL if it is valid', () => { const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, false, dummyCustomURL); - expect(integrationsCDNPath).toBe(dummyCustomURL); + expect(integrationsCDNPath).toBe('https://www.dummy.url/integrations'); }); - it('should throw error if invalid custom url is provided', () => { + it('should throw error if invalid custom URL is provided', () => { const integrationsCDNPath = () => getIntegrationsCDNPath(dummyVersion, false, '/'); expect(integrationsCDNPath).toThrow( - 'Failed to load the SDK as the base URL for integrations is not valid.', + 'Failed to load the SDK as the base URL "/" for integrations is not valid.', ); }); it('should return script src path if script src exists and integrations version is not locked', () => { - const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, false, undefined); + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, false); expect(integrationsCDNPath).toBe( 'https://www.dummy.url/fromScript/v3/modern/js-integrations', ); }); it('should return script src path with versioned folder if script src exists and integrations version is locked', () => { - const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true, undefined); + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true); expect(integrationsCDNPath).toBe( 'https://www.dummy.url/fromScript/3.x.x/modern/js-integrations', ); }); it('should return default path if no script src exists and integrations version is not locked', () => { - getSDKUrl.mockImplementation(() => undefined); + getSDKUrlMock.mockImplementation(() => undefined); - const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, false, undefined); + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, false); expect(integrationsCDNPath).toBe('https://cdn.rudderlabs.com/v3/modern/js-integrations'); }); it('should return default path with versioned folder if no script src exists and integrations version is locked', () => { - getSDKUrl.mockImplementation(() => undefined); + getSDKUrlMock.mockImplementation(() => undefined); - const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true, undefined); - expect(integrationsCDNPath).toBe(`${SDK_CDN_BASE_URL}/${dummyVersion}/modern/${CDN_INT_DIR}`); + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true); + expect(integrationsCDNPath).toBe( + `https://cdn.rudderlabs.com/${dummyVersion}/modern/js-integrations`, + ); }); - }); - describe('getPluginsCDNPath', () => { - const dummyCustomURL = 'https://www.dummy.url/plugins/'; - const dummyScriptURL = 'https://www.dummy.url/fromScript/v3/modern/rsa.min.js'; - const dummyVersion = '3.x.x'; + it('should return that is not version locked when the script source is not as per the convention', () => { + getSDKUrlMock.mockImplementation( + () => 'https://www.dummy.url/fromScript/v3/custom/rsa.min.js', + ); + + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true); + expect(integrationsCDNPath).toBe( + `https://www.dummy.url/fromScript/v3/custom/js-integrations`, + ); + }); + + it('should return the url that is not version locked when the script source is slightly off from the convention', () => { + // /v3/modern/js-integrations matches but it is in the middle of the path + getSDKUrlMock.mockImplementation( + () => 'https://www.dummy.url/fromScript/v3/modern/js-integrations/custom/rsa.min.js', + ); - beforeEach(() => { - getSDKUrl.mockImplementation(() => dummyScriptURL); + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true); + expect(integrationsCDNPath).toBe( + `https://www.dummy.url/fromScript/v3/modern/js-integrations/custom/js-integrations`, + ); }); - afterEach(() => { - jest.resetAllMocks(); + it('should lock the version on custom URL if it follows the convention', () => { + const integrationsCDNPath = getIntegrationsCDNPath( + dummyVersion, + true, + 'https://www.dummy.url/v3/modern/js-integrations', + ); + expect(integrationsCDNPath).toBe('https://www.dummy.url/3.x.x/modern/js-integrations'); }); + it('should not lock the version on custom URL if it does not follow the convention', () => { + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true, dummyCustomURL); + expect(integrationsCDNPath).toBe('https://www.dummy.url/integrations'); + }); + + it('should return the default component url in case of npm installation', () => { + // @ts-expect-error needed for testing + state.context.app.value.installType = 'npm'; + + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, false); + expect(integrationsCDNPath).toBe('https://cdn.rudderlabs.com/v3/modern/js-integrations'); + }); + + it('should lock version on default component url in case of npm installation', () => { + // @ts-expect-error needed for testing + state.context.app.value.installType = 'npm'; + + const integrationsCDNPath = getIntegrationsCDNPath(dummyVersion, true); + expect(integrationsCDNPath).toBe('https://cdn.rudderlabs.com/3.x.x/modern/js-integrations'); + }); + }); + + describe('getPluginsCDNPath', () => { + const dummyCustomURL = 'https://www.dummy.url/plugins/'; + it('should return plugins CDN URL if a valid custom URL is provided', () => { const pluginsCDNPath = getPluginsCDNPath(dummyVersion, false, dummyCustomURL); expect(pluginsCDNPath).toBe('https://www.dummy.url/plugins'); }); - it('should throw error if invalid custom url is provided', () => { + it('should throw error if invalid custom URL is provided', () => { const pluginsCDNPath = () => getPluginsCDNPath(dummyVersion, false, 'htp:/some.broken.url'); expect(pluginsCDNPath).toThrow( - 'Failed to load the SDK as the base URL for plugins is not valid.', + 'Failed to load the SDK as the base URL "htp:/some.broken.url" for plugins is not valid.', ); }); @@ -109,16 +160,67 @@ describe('CDN path utilities', () => { }); it('should return default path if no script src exists and plugins version is not locked', () => { - getSDKUrl.mockImplementation(() => undefined); + getSDKUrlMock.mockImplementation(() => undefined); - const pluginsCDNPath = getPluginsCDNPath(dummyVersion, false, undefined); + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, false); expect(pluginsCDNPath).toBe('https://cdn.rudderlabs.com/v3/modern/plugins'); }); it('should return default path if no script src exists but plugins version is locked', () => { - getSDKUrl.mockImplementation(() => undefined); + getSDKUrlMock.mockImplementation(() => undefined); + + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, true); + expect(pluginsCDNPath).toBe('https://cdn.rudderlabs.com/3.x.x/modern/plugins'); + }); - const pluginsCDNPath = getPluginsCDNPath(dummyVersion, true, undefined); + it('should return that is not version locked when the script source is not as per the convention', () => { + getSDKUrlMock.mockImplementation( + () => 'https://www.dummy.url/fromScript/v3/custom/rsa.min.js', + ); + + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, true); + expect(pluginsCDNPath).toBe(`https://www.dummy.url/fromScript/v3/custom/plugins`); + }); + + it('should return the url that is not version locked when the script source is slightly off from the convention', () => { + // /v3/modern/plugins matches but it is in the middle of the path + getSDKUrlMock.mockImplementation( + () => 'https://www.dummy.url/fromScript/v3/modern/plugins/custom/rsa.min.js', + ); + + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, true); + expect(pluginsCDNPath).toBe( + `https://www.dummy.url/fromScript/v3/modern/plugins/custom/plugins`, + ); + }); + + it('should lock the version on custom URL if it follows the convention', () => { + const pluginsCDNPath = getPluginsCDNPath( + dummyVersion, + true, + 'https://www.dummy.url/v3/modern/plugins', + ); + expect(pluginsCDNPath).toBe('https://www.dummy.url/3.x.x/modern/plugins'); + }); + + it('should not lock the version on custom URL if it does not follow the convention', () => { + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, true, dummyCustomURL); + expect(pluginsCDNPath).toBe('https://www.dummy.url/plugins'); + }); + + it('should return the default component url in case of npm installation', () => { + // @ts-expect-error needed for testing + state.context.app.value.installType = 'npm'; + + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, false); + expect(pluginsCDNPath).toBe('https://cdn.rudderlabs.com/v3/modern/plugins'); + }); + + it('should lock version on default component url in case of npm installation', () => { + // @ts-expect-error needed for testing + state.context.app.value.installType = 'npm'; + + const pluginsCDNPath = getPluginsCDNPath(dummyVersion, true); expect(pluginsCDNPath).toBe('https://cdn.rudderlabs.com/3.x.x/modern/plugins'); }); }); diff --git a/packages/analytics-js/__tests__/components/configManager/commonUtil.test.ts b/packages/analytics-js/__tests__/components/configManager/commonUtil.test.ts index ff30f79602..d3e1b27735 100644 --- a/packages/analytics-js/__tests__/components/configManager/commonUtil.test.ts +++ b/packages/analytics-js/__tests__/components/configManager/commonUtil.test.ts @@ -17,11 +17,14 @@ import { state, resetState } from '../../../src/state'; jest.mock('../../../src/components/configManager/util/validate'); -const createScriptElement = (url: string) => { +const createScriptElement = (url: string, writeKey?: string) => { const script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; script.id = 'SOME_ID'; + if (writeKey) { + script.setAttribute('data-rsa-write-key', writeKey); + } document.head.appendChild(script); }; @@ -51,6 +54,7 @@ describe('Config Manager Common Utilities', () => { beforeEach(() => { resetState(); + state.lifecycle.writeKey.value = 'writeKey'; (getDataServiceUrl as jest.Mock).mockRestore(); }); @@ -63,28 +67,35 @@ describe('Config Manager Common Utilities', () => { // expected, input [ 'https://www.dummy.url/fromScript/v3/rsa.min.js', - 'https://www.dummy.url/fromScript/v3/rsa.min.js', + ['https://www.dummy.url/fromScript/v3/rsa.min.js'], + ], + [undefined, ['https://www.dummy.url/fromScript/v3/other.min.js']], + [ + 'https://www.dummy.url/fromScript/v3/rsa.js', + ['https://www.dummy.url/fromScript/v3/rsa.js'], + ], + [undefined, ['https://www.dummy.url/fromScript/v3/rudder.min.js']], + [undefined, ['https://www.dummy.url/fromScript/v3/analytics.min.js']], + [undefined, ['https://www.dummy.url/fromScript/v3/rsa.min']], + [undefined, ['https://www.dummy.url/fromScript/v3/rsa']], + [undefined, ['https://www.dummy.url/fromScript/v3rsa.min.js']], + ['/rsa.min.js', ['/rsa.min.js']], + ['/rsa.js', ['/rsa.js']], + [undefined, ['https://www.dummy.url/fromScript/v3/rs.min.js']], + [ + 'https://www.dummy.url/fromScript/v3/rs.min.js', + ['https://www.dummy.url/fromScript/v3/rs.min.js', 'writeKey'], ], - [undefined, 'https://www.dummy.url/fromScript/v3/other.min.js'], - ['https://www.dummy.url/fromScript/v3/rsa.js', 'https://www.dummy.url/fromScript/v3/rsa.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/rudder.min.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/analytics.min.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/rsa.min'], - ['https://www.dummy.url/fromScript/v3/rsa.js', 'https://www.dummy.url/fromScript/v3/rsa.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/rsa'], - [undefined, 'https://www.dummy.url/fromScript/v3rsa.min.js'], - ['/rsa.min.js', '/rsa.min.js'], - ['/rsa.js', '/rsa.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/rs.min.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/rsamin.js'], - ['rsa.min.js', 'rsa.min.js'], - ['rsa.js', 'rsa.js'], - [undefined, 'https://www.dummy.url/fromScript/v3/rsa.min.jsx'], - [undefined, null], + [undefined, ['https://www.dummy.url/fromScript/v3/rs.min.js', 'writeKey-1']], + [undefined, ['https://www.dummy.url/fromScript/v3/rsamin.js']], + ['rsa.min.js', ['rsa.min.js']], + ['rsa.js', ['rsa.js']], + [undefined, ['https://www.dummy.url/fromScript/v3/rsa.min.jsx']], + [undefined, [null]], ]; test.each(testCases)('should return %s when the script src is %s', (expected, input) => { - createScriptElement(input as string); + createScriptElement((input as any[])[0], (input as any[])[1]); const sdkURL = getSDKUrl(); expect(sdkURL).toBe(expected); diff --git a/packages/analytics-js/__tests__/components/utilities/loadOptions.test.ts b/packages/analytics-js/__tests__/components/utilities/loadOptions.test.ts new file mode 100644 index 0000000000..33bdaacd11 --- /dev/null +++ b/packages/analytics-js/__tests__/components/utilities/loadOptions.test.ts @@ -0,0 +1,1779 @@ +/* eslint-disable compat/compat */ +import type { LoadOptions } from '@rudderstack/analytics-js-common/types/LoadOptions'; +import type { PluginName } from '@rudderstack/analytics-js-common/types/PluginsManager'; +import { normalizeLoadOptions } from '../../../src/components/utilities/loadOptions'; + +describe('load API options', () => { + const defaultLoadOptions: LoadOptions = { + logLevel: 'ERROR', + configUrl: 'https://api.rudderstack.com', + loadIntegration: true, + sessions: { + autoTrack: true, + timeout: 30 * 60 * 1000, // 30 minutes + }, + sameSiteCookie: 'Lax', + polyfillIfRequired: true, + integrations: { + All: true, + }, + useBeacon: false, + beaconQueueOptions: {}, + destinationsQueueOptions: {}, + queueOptions: {}, + lockIntegrationsVersion: false, + lockPluginsVersion: false, + uaChTrackLevel: 'none', + plugins: [], + useGlobalIntegrationsConfigInEvents: false, + bufferDataPlaneEventsUntilReady: false, + dataPlaneEventsBufferTimeout: 10 * 1000, // 10 seconds + storage: { + encryption: { + version: 'v3', + }, + migrate: true, + cookie: {}, + }, + sendAdblockPageOptions: {}, + useServerSideCookies: false, + sendAdblockPage: false, + sameDomainCookiesOnly: false, + secureCookie: false, + }; + + const defaultOptionalPluginsList: PluginName[] = [ + 'BeaconQueue', + 'Bugsnag', + 'CustomConsentManager', + 'DeviceModeDestinations', + 'DeviceModeTransformation', + 'ErrorReporting', + 'ExternalAnonymousId', + 'GoogleLinker', + 'IubendaConsentManager', + 'KetchConsentManager', + 'NativeDestinationQueue', + 'OneTrustConsentManager', + 'StorageEncryption', + 'StorageEncryptionLegacy', + 'StorageMigrator', + 'XhrQueue', + ]; + + describe('normalizeLoadOptions', () => { + describe('setCookieDomain', () => { + const testCaseData: any[] = [ + { + name: 'should ignore setCookieDomain if it is a number', + input: { setCookieDomain: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is a boolean', + input: { setCookieDomain: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is an object', + input: { setCookieDomain: { domain: 'rudderstack.com' } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is an array', + input: { setCookieDomain: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is a function', + input: { setCookieDomain: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is a symbol', + input: { setCookieDomain: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is null', + input: { setCookieDomain: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore setCookieDomain if it is undefined', + input: { setCookieDomain: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set setCookieDomain to the input (string) value', + input: { setCookieDomain: 'rudderstack.com' }, + expected: { + ...defaultLoadOptions, + setCookieDomain: 'rudderstack.com', + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('sameSiteCookie', () => { + const testCaseData: any[] = [ + { + name: 'should ignore sameSiteCookie if it is not a valid value', + input: { sameSiteCookie: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is a number', + input: { sameSiteCookie: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is a boolean', + input: { sameSiteCookie: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is an object', + input: { sameSiteCookie: { domain: 'rudderstack.com' } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is an array', + input: { sameSiteCookie: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is a function', + input: { sameSiteCookie: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is a symbol', + input: { sameSiteCookie: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is null', + input: { sameSiteCookie: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sameSiteCookie if it is undefined', + input: { sameSiteCookie: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set sameSiteCookie to the input (Strict) value', + input: { sameSiteCookie: 'Strict' }, + expected: { + ...defaultLoadOptions, + sameSiteCookie: 'Strict', + }, + }, + { + name: 'should set sameSiteCookie to the input (Lax) value', + input: { sameSiteCookie: 'Lax' }, + expected: { + ...defaultLoadOptions, + sameSiteCookie: 'Lax', + }, + }, + { + name: 'should set sameSiteCookie to the input (None) value', + input: { sameSiteCookie: 'None' }, + expected: { + ...defaultLoadOptions, + sameSiteCookie: 'None', + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('secureCookie', () => { + const testCaseData: any[] = [ + { + name: 'should consider false secureCookie if it is a string', + input: { secureCookie: 'Invalid' }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should consider false secureCookie if it is a number', + input: { secureCookie: 123 }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should consider false secureCookie if it is an object', + input: { secureCookie: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should consider false secureCookie if it is an array', + input: { secureCookie: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should consider false secureCookie if it is a function', + input: { secureCookie: () => {} }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should consider false secureCookie if it is a symbol', + input: { secureCookie: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should consider false secureCookie if it is null', + input: { secureCookie: null }, + expected: { ...defaultLoadOptions, secureCookie: false }, + }, + { + name: 'should ignore secureCookie if it is undefined', + input: { secureCookie: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set secureCookie to the input (true) value', + input: { secureCookie: true }, + expected: { + ...defaultLoadOptions, + secureCookie: true, + }, + }, + { + name: 'should set secureCookie to the input (false) value', + input: { secureCookie: false }, + expected: { + ...defaultLoadOptions, + secureCookie: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('sameDomainCookiesOnly', () => { + const testCaseData: any[] = [ + { + name: 'should consider false sameDomainCookiesOnly if it is a number', + input: { sameDomainCookiesOnly: 123 }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should consider false sameDomainCookiesOnly if it is a string', + input: { sameDomainCookiesOnly: 'Invalid' }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should consider false sameDomainCookiesOnly if it is an object', + input: { sameDomainCookiesOnly: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should consider false sameDomainCookiesOnly if it is an array', + input: { sameDomainCookiesOnly: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should consider false sameDomainCookiesOnly if it is a function', + input: { sameDomainCookiesOnly: () => {} }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should consider false sameDomainCookiesOnly if it is a symbol', + input: { sameDomainCookiesOnly: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should consider false sameDomainCookiesOnly if it is null', + input: { sameDomainCookiesOnly: null }, + expected: { ...defaultLoadOptions, sameDomainCookiesOnly: false }, + }, + { + name: 'should ignore sameDomainCookiesOnly if it is undefined', + input: { sameDomainCookiesOnly: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set sameDomainCookiesOnly to the input (true) value', + input: { sameDomainCookiesOnly: true }, + expected: { + ...defaultLoadOptions, + sameDomainCookiesOnly: true, + }, + }, + { + name: 'should set sameDomainCookiesOnly to the input (false) value', + input: { sameDomainCookiesOnly: false }, + expected: { + ...defaultLoadOptions, + sameDomainCookiesOnly: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('uaChTrackLevel', () => { + const testCaseData: any[] = [ + { + name: 'should ignore uaChTrackLevel if it is not a valid value', + input: { uaChTrackLevel: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is a number', + input: { uaChTrackLevel: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is a boolean', + input: { uaChTrackLevel: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is an object', + input: { uaChTrackLevel: { domain: 'rudderstack.com' } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is an array', + input: { uaChTrackLevel: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is a function', + input: { uaChTrackLevel: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is a symbol', + input: { uaChTrackLevel: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is null', + input: { uaChTrackLevel: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore uaChTrackLevel if it is undefined', + input: { uaChTrackLevel: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set uaChTrackLevel to the input (none) value', + input: { uaChTrackLevel: 'none' }, + expected: { + ...defaultLoadOptions, + uaChTrackLevel: 'none', + }, + }, + { + name: 'should set uaChTrackLevel to the input (default) value', + input: { uaChTrackLevel: 'default' }, + expected: { + ...defaultLoadOptions, + uaChTrackLevel: 'default', + }, + }, + { + name: 'should set uaChTrackLevel to the input (full) value', + input: { uaChTrackLevel: 'full' }, + expected: { + ...defaultLoadOptions, + uaChTrackLevel: 'full', + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('integrations', () => { + const testCaseData: any[] = [ + { + name: 'should ignore integrations if it is not an object', + input: { integrations: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is a number', + input: { integrations: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is a boolean', + input: { integrations: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is an array', + input: { integrations: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is a function', + input: { integrations: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is a symbol', + input: { integrations: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is null', + input: { integrations: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore integrations if it is undefined', + input: { integrations: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set integrations to the input value (merged with default)', + input: { integrations: { GoogleAnalytics: true } }, + expected: { + ...defaultLoadOptions, + integrations: { All: true, GoogleAnalytics: true }, + }, + }, + { + name: 'should set integrations to the input value (override values)', + input: { + integrations: { + All: false, + GoogleAnalytics: true, + Mixpanel: false, + VWO: undefined, + GA: null, + }, + }, + expected: { + ...defaultLoadOptions, + integrations: { All: false, GoogleAnalytics: true, Mixpanel: false }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('plugins', () => { + const testCaseData: any[] = [ + { + name: 'should ignore plugins if it is a string', + input: { plugins: 'Invalid' }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is a number', + input: { plugins: 123 }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is a boolean', + input: { plugins: true }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is an object', + input: { plugins: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is a string', + input: { plugins: 'rudderstack.com' }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is a function', + input: { plugins: () => {} }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is a symbol', + input: { plugins: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is null', + input: { plugins: null }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should ignore plugins if it is undefined', + input: { plugins: undefined }, + expected: { ...defaultLoadOptions, plugins: defaultOptionalPluginsList }, + }, + { + name: 'should set plugins to the input value', + input: { plugins: ['StorageMigrator', 'OneTrustConsentManager'] }, + expected: { + ...defaultLoadOptions, + plugins: ['StorageMigrator', 'OneTrustConsentManager'], + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual( + expected, + ); + }); + }); + + describe('useGlobalIntegrationsConfigInEvents', () => { + const testCaseData: any[] = [ + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is a string', + input: { useGlobalIntegrationsConfigInEvents: 'Invalid' }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is a number', + input: { useGlobalIntegrationsConfigInEvents: 123 }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is an object', + input: { useGlobalIntegrationsConfigInEvents: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is an array', + input: { useGlobalIntegrationsConfigInEvents: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is a function', + input: { useGlobalIntegrationsConfigInEvents: () => {} }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is a symbol', + input: { useGlobalIntegrationsConfigInEvents: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should consider false useGlobalIntegrationsConfigInEvents if it is null', + input: { useGlobalIntegrationsConfigInEvents: null }, + expected: { ...defaultLoadOptions, useGlobalIntegrationsConfigInEvents: false }, + }, + { + name: 'should ignore useGlobalIntegrationsConfigInEvents if it is undefined', + input: { useGlobalIntegrationsConfigInEvents: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set useGlobalIntegrationsConfigInEvents to the input (true) value', + input: { useGlobalIntegrationsConfigInEvents: true }, + expected: { + ...defaultLoadOptions, + useGlobalIntegrationsConfigInEvents: true, + }, + }, + { + name: 'should set useGlobalIntegrationsConfigInEvents to the input (false) value', + input: { useGlobalIntegrationsConfigInEvents: false }, + expected: { + ...defaultLoadOptions, + useGlobalIntegrationsConfigInEvents: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('bufferDataPlaneEventsUntilReady', () => { + const testCaseData: any[] = [ + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is a string', + input: { bufferDataPlaneEventsUntilReady: 'Invalid' }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is a number', + input: { bufferDataPlaneEventsUntilReady: 123 }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is an object', + input: { bufferDataPlaneEventsUntilReady: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is an array', + input: { bufferDataPlaneEventsUntilReady: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is a function', + input: { bufferDataPlaneEventsUntilReady: () => {} }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is a symbol', + input: { bufferDataPlaneEventsUntilReady: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should consider false bufferDataPlaneEventsUntilReady if it is null', + input: { bufferDataPlaneEventsUntilReady: null }, + expected: { ...defaultLoadOptions, bufferDataPlaneEventsUntilReady: false }, + }, + { + name: 'should ignore bufferDataPlaneEventsUntilReady if it is undefined', + input: { bufferDataPlaneEventsUntilReady: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set bufferDataPlaneEventsUntilReady to the input (true) value', + input: { bufferDataPlaneEventsUntilReady: true }, + expected: { + ...defaultLoadOptions, + bufferDataPlaneEventsUntilReady: true, + }, + }, + { + name: 'should set bufferDataPlaneEventsUntilReady to the input (false) value', + input: { bufferDataPlaneEventsUntilReady: false }, + expected: { + ...defaultLoadOptions, + bufferDataPlaneEventsUntilReady: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('sendAdblockPage', () => { + const testCaseData: any[] = [ + { + name: 'should consider false sendAdblockPage if it is a string', + input: { sendAdblockPage: 'Invalid' }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should consider false sendAdblockPage if it is a number', + input: { sendAdblockPage: 123 }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should consider false sendAdblockPage if it is an object', + input: { sendAdblockPage: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should consider false sendAdblockPage if it is an array', + input: { sendAdblockPage: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should consider false sendAdblockPage if it is a function', + input: { sendAdblockPage: () => {} }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should consider false sendAdblockPage if it is a symbol', + input: { sendAdblockPage: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should consider false sendAdblockPage if it is null', + input: { sendAdblockPage: null }, + expected: { ...defaultLoadOptions, sendAdblockPage: false }, + }, + { + name: 'should ignore sendAdblockPage if it is undefined', + input: { sendAdblockPage: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set sendAdblockPage to the input (true) value', + input: { sendAdblockPage: true }, + expected: { + ...defaultLoadOptions, + sendAdblockPage: true, + }, + }, + { + name: 'should set sendAdblockPage to the input (false) value', + input: { sendAdblockPage: false }, + expected: { + ...defaultLoadOptions, + sendAdblockPage: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('sendAdblockPageOptions', () => { + const testCaseData: any[] = [ + { + name: 'should ignore sendAdblockPageOptions if it is a number', + input: { sendAdblockPageOptions: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is a boolean', + input: { sendAdblockPageOptions: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is a string', + input: { sendAdblockPageOptions: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is an array', + input: { sendAdblockPageOptions: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is a function', + input: { sendAdblockPageOptions: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is a symbol', + input: { sendAdblockPageOptions: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is null', + input: { sendAdblockPageOptions: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore sendAdblockPageOptions if it is undefined', + input: { sendAdblockPageOptions: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set sendAdblockPageOptions to the input value', + input: { + sendAdblockPageOptions: { + endpoint: 'https://api.rudderstack.com', + undefinedProperty: undefined, + nullProperty: null, + }, + }, + expected: { + ...defaultLoadOptions, + sendAdblockPageOptions: { endpoint: 'https://api.rudderstack.com' }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('useServerSideCookies', () => { + const testCaseData: any[] = [ + { + name: 'should consider false useServerSideCookies if it is a string', + input: { useServerSideCookies: 'Invalid' }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should consider false useServerSideCookies if it is a number', + input: { useServerSideCookies: 123 }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should consider false useServerSideCookies if it is an object', + input: { useServerSideCookies: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should consider false useServerSideCookies if it is an array', + input: { useServerSideCookies: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should consider false useServerSideCookies if it is a function', + input: { useServerSideCookies: () => {} }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should consider false useServerSideCookies if it is a symbol', + input: { useServerSideCookies: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should consider false useServerSideCookies if it is null', + input: { useServerSideCookies: null }, + expected: { ...defaultLoadOptions, useServerSideCookies: false }, + }, + { + name: 'should ignore useServerSideCookies if it is undefined', + input: { useServerSideCookies: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set useServerSideCookies to the input (true) value', + input: { useServerSideCookies: true }, + expected: { + ...defaultLoadOptions, + useServerSideCookies: true, + }, + }, + { + name: 'should set useServerSideCookies to the input (false) value', + input: { useServerSideCookies: false }, + expected: { + ...defaultLoadOptions, + useServerSideCookies: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('dataServiceEndpoint', () => { + const testCaseData: any[] = [ + { + name: 'should ignore dataServiceEndpoint if it is a number', + input: { dataServiceEndpoint: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is a boolean', + input: { dataServiceEndpoint: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is an object', + input: { dataServiceEndpoint: { domain: 'rudderstack.com' } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is an array', + input: { dataServiceEndpoint: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is a function', + input: { dataServiceEndpoint: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is a symbol', + input: { dataServiceEndpoint: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is null', + input: { dataServiceEndpoint: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataServiceEndpoint if it is undefined', + input: { dataServiceEndpoint: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set dataServiceEndpoint to the input (string) value', + input: { dataServiceEndpoint: 'https://api.rudderstack.com' }, + expected: { + ...defaultLoadOptions, + dataServiceEndpoint: 'https://api.rudderstack.com', + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('loadIntegration', () => { + const testCaseData: any[] = [ + { + name: 'should consider false loadIntegration if it is a string', + input: { loadIntegration: 'Invalid' }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should consider false loadIntegration if it is a number', + input: { loadIntegration: 123 }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should consider false loadIntegration if it is an object', + input: { loadIntegration: { domain: 'rudderstack.com' } }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should consider false loadIntegration if it is an array', + input: { loadIntegration: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should consider false loadIntegration if it is a function', + input: { loadIntegration: () => {} }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should consider false loadIntegration if it is a symbol', + input: { loadIntegration: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should consider false loadIntegration if it is null', + input: { loadIntegration: null }, + expected: { ...defaultLoadOptions, loadIntegration: false }, + }, + { + name: 'should ignore loadIntegration if it is undefined', + input: { loadIntegration: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set loadIntegration to the input (true) value', + input: { loadIntegration: true }, + expected: { + ...defaultLoadOptions, + loadIntegration: true, + }, + }, + { + name: 'should set loadIntegration to the input (false) value', + input: { loadIntegration: false }, + expected: { + ...defaultLoadOptions, + loadIntegration: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('destinationsQueueOptions', () => { + const testCaseData: any[] = [ + { + name: 'should ignore destinationsQueueOptions if it is a number', + input: { destinationsQueueOptions: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is a boolean', + input: { destinationsQueueOptions: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is a string', + input: { destinationsQueueOptions: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is an array', + input: { destinationsQueueOptions: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is a function', + input: { destinationsQueueOptions: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is a symbol', + input: { destinationsQueueOptions: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is null', + input: { destinationsQueueOptions: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore destinationsQueueOptions if it is undefined', + input: { destinationsQueueOptions: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set destinationsQueueOptions to the input value', + input: { + destinationsQueueOptions: { + maxItems: 100, + maxBytes: 1024, + undefinedProperty: undefined, + nullProperty: null, + }, + }, + expected: { + ...defaultLoadOptions, + destinationsQueueOptions: { maxItems: 100, maxBytes: 1024 }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('queueOptions', () => { + const testCaseData: any[] = [ + { + name: 'should ignore queueOptions if it is a number', + input: { queueOptions: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is a boolean', + input: { queueOptions: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is a string', + input: { queueOptions: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is an array', + input: { queueOptions: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is a function', + input: { queueOptions: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is a symbol', + input: { queueOptions: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is null', + input: { queueOptions: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore queueOptions if it is undefined', + input: { queueOptions: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set queueOptions to the input value', + input: { + queueOptions: { + maxItems: 100, + maxBytes: 1024, + undefinedProperty: undefined, + nullProperty: null, + }, + }, + expected: { + ...defaultLoadOptions, + queueOptions: { maxItems: 100, maxBytes: 1024 }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('beaconQueueOptions', () => { + const testCaseData: any[] = [ + { + name: 'should ignore beaconQueueOptions if it is a number', + input: { beaconQueueOptions: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is a boolean', + input: { beaconQueueOptions: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is a string', + input: { beaconQueueOptions: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is an array', + input: { beaconQueueOptions: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is a function', + input: { beaconQueueOptions: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is a symbol', + input: { beaconQueueOptions: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is null', + input: { beaconQueueOptions: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore beaconQueueOptions if it is undefined', + input: { beaconQueueOptions: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set beaconQueueOptions to the input value', + input: { + beaconQueueOptions: { + maxItems: 100, + maxBytes: 1024, + undefinedProperty: undefined, + nullProperty: null, + }, + }, + expected: { + ...defaultLoadOptions, + beaconQueueOptions: { maxItems: 100, maxBytes: 1024 }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('lockIntegrationsVersion', () => { + const testCaseData: any[] = [ + { + name: 'should consider false lockIntegrationsVersion if it is a number', + input: { lockIntegrationsVersion: 123 }, + expected: { ...defaultLoadOptions, lockIntegrationsVersion: false }, + }, + { + name: 'should consider false lockIntegrationsVersion if it is a string', + input: { lockIntegrationsVersion: 'Invalid' }, + expected: { ...defaultLoadOptions, lockIntegrationsVersion: false }, + }, + { + name: 'should consider false lockIntegrationsVersion if it is an array', + input: { lockIntegrationsVersion: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, lockIntegrationsVersion: false }, + }, + { + name: 'should consider false lockIntegrationsVersion if it is a function', + input: { lockIntegrationsVersion: () => {} }, + expected: { ...defaultLoadOptions, lockIntegrationsVersion: false }, + }, + { + name: 'should consider false lockIntegrationsVersion if it is a symbol', + input: { lockIntegrationsVersion: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, lockIntegrationsVersion: false }, + }, + { + name: 'should consider false lockIntegrationsVersion if it is null', + input: { lockIntegrationsVersion: null }, + expected: { ...defaultLoadOptions, lockIntegrationsVersion: false }, + }, + { + name: 'should ignore lockIntegrationsVersion if it is undefined', + input: { lockIntegrationsVersion: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set lockIntegrationsVersion to the input(true) value', + input: { lockIntegrationsVersion: true }, + expected: { + ...defaultLoadOptions, + lockIntegrationsVersion: true, + }, + }, + { + name: 'should set lockIntegrationsVersion to the input(false) value', + input: { lockIntegrationsVersion: false }, + expected: { + ...defaultLoadOptions, + lockIntegrationsVersion: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('lockPluginsVersion', () => { + const testCaseData: any[] = [ + { + name: 'should consider false lockPluginsVersion if it is a number', + input: { lockPluginsVersion: 123 }, + expected: { ...defaultLoadOptions, lockPluginsVersion: false }, + }, + { + name: 'should consider false lockPluginsVersion if it is a string', + input: { lockPluginsVersion: 'Invalid' }, + expected: { ...defaultLoadOptions, lockPluginsVersion: false }, + }, + { + name: 'should consider false lockPluginsVersion if it is an array', + input: { lockPluginsVersion: ['rudderstack.com'] }, + expected: { ...defaultLoadOptions, lockPluginsVersion: false }, + }, + { + name: 'should consider false lockPluginsVersion if it is a function', + input: { lockPluginsVersion: () => {} }, + expected: { ...defaultLoadOptions, lockPluginsVersion: false }, + }, + { + name: 'should consider false lockPluginsVersion if it is a symbol', + input: { lockPluginsVersion: Symbol('rudderstack.com') }, + expected: { ...defaultLoadOptions, lockPluginsVersion: false }, + }, + { + name: 'should consider false lockPluginsVersion if it is null', + input: { lockPluginsVersion: null }, + expected: { ...defaultLoadOptions, lockPluginsVersion: false }, + }, + { + name: 'should ignore lockPluginsVersion if it is undefined', + input: { lockPluginsVersion: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set lockPluginsVersion to the input(true) value', + input: { lockPluginsVersion: true }, + expected: { + ...defaultLoadOptions, + lockPluginsVersion: true, + }, + }, + { + name: 'should set lockPluginsVersion to the input(false) value', + input: { lockPluginsVersion: false }, + expected: { + ...defaultLoadOptions, + lockPluginsVersion: false, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('dataPlaneEventsBufferTimeout', () => { + const testCaseData: any[] = [ + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is a boolean', + input: { dataPlaneEventsBufferTimeout: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is a string', + input: { dataPlaneEventsBufferTimeout: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is an array', + input: { dataPlaneEventsBufferTimeout: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is a function', + input: { dataPlaneEventsBufferTimeout: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is a symbol', + input: { dataPlaneEventsBufferTimeout: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is null', + input: { dataPlaneEventsBufferTimeout: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore dataPlaneEventsBufferTimeout if it is undefined', + input: { dataPlaneEventsBufferTimeout: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set dataPlaneEventsBufferTimeout to the input value', + input: { dataPlaneEventsBufferTimeout: 1000 }, + expected: { + ...defaultLoadOptions, + dataPlaneEventsBufferTimeout: 1000, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('preConsent', () => { + const testCaseData: any[] = [ + { + name: 'should ignore preConsent if it is a number', + input: { preConsent: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is a boolean', + input: { preConsent: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is a string', + input: { preConsent: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is an array', + input: { preConsent: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is a function', + input: { preConsent: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is a symbol', + input: { preConsent: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is null', + input: { preConsent: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore preConsent if it is undefined', + input: { preConsent: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set preConsent to the input value', + input: { preConsent: { enable: true, undefinedProperty: undefined, nullProperty: null } }, + expected: { + ...defaultLoadOptions, + preConsent: { enable: true }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + + describe('storage', () => { + const testCaseData: any[] = [ + { + name: 'should ignore storage if it is a number', + input: { storage: 123 }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is a boolean', + input: { storage: true }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is a string', + input: { storage: 'Invalid' }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is an array', + input: { storage: ['rudderstack.com'] }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is a function', + input: { storage: () => {} }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is a symbol', + input: { storage: Symbol('rudderstack.com') }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is null', + input: { storage: null }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage if it is undefined', + input: { storage: undefined }, + expected: defaultLoadOptions, + }, + { + name: 'should set storage to the input value', + input: { + storage: { type: 'cookieStorage', undefinedProperty: undefined, nullProperty: null }, + }, + expected: { + ...defaultLoadOptions, + storage: { + type: 'cookieStorage', + cookie: defaultLoadOptions.storage?.cookie, + migrate: true, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is a string', + input: { storage: { migrate: 'Invalid' } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is a number', + input: { storage: { migrate: 123 } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is a boolean', + input: { storage: { migrate: true } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: true, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is an object', + input: { storage: { migrate: { domain: 'rudderstack.com' } } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is an array', + input: { storage: { migrate: ['rudderstack.com'] } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is a function', + input: { storage: { migrate: () => {} } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is a symbol', + input: { storage: { migrate: Symbol('rudderstack.com') } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should consider false for storage.migrate if it is null', + input: { storage: { migrate: null } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should ignore storage.migrate if it is undefined', + input: { storage: { migrate: undefined } }, + expected: defaultLoadOptions, + }, + { + name: 'should set storage.migrate to the input (true) value', + input: { storage: { migrate: true } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: true, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should set storage.migrate to the input (false) value', + input: { storage: { migrate: false } }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: defaultLoadOptions.storage?.cookie, + migrate: false, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should ignore storage.cookie if it is a number', + input: { storage: { cookie: 123 } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is a boolean', + input: { storage: { cookie: true } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is a string', + input: { storage: { cookie: 'Invalid' } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is an array', + input: { storage: { cookie: ['rudderstack.com'] } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is a function', + input: { storage: { cookie: () => {} } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is a symbol', + input: { storage: { cookie: Symbol('rudderstack.com') } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is null', + input: { storage: { cookie: null } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.cookie if it is undefined', + input: { storage: { cookie: undefined } }, + expected: defaultLoadOptions, + }, + { + name: 'should set storage.cookie to the input value', + input: { + storage: { + cookie: { + domain: 'rudderstack.com', + undefinedProperty: undefined, + nullProperty: null, + }, + }, + }, + expected: { + ...defaultLoadOptions, + storage: { + cookie: { domain: 'rudderstack.com' }, + migrate: defaultLoadOptions.storage?.migrate, + encryption: defaultLoadOptions.storage?.encryption, + }, + }, + }, + { + name: 'should ignore storage.encryption if it is a number', + input: { storage: { encryption: 123 } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is a boolean', + input: { storage: { encryption: true } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is a string', + input: { storage: { encryption: 'Invalid' } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is an array', + input: { storage: { encryption: ['rudderstack.com'] } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is a function', + input: { storage: { encryption: () => {} } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is a symbol', + input: { storage: { encryption: Symbol('rudderstack.com') } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is null', + input: { storage: { encryption: null } }, + expected: defaultLoadOptions, + }, + { + name: 'should ignore storage.encryption if it is undefined', + input: { storage: { encryption: undefined } }, + expected: defaultLoadOptions, + }, + { + name: 'should set storage.encryption to the input value', + input: { + storage: { + encryption: { version: 'legacy', undefinedProperty: undefined, nullProperty: null }, + }, + }, + expected: { + ...defaultLoadOptions, + storage: { + encryption: { version: 'legacy' }, + cookie: defaultLoadOptions.storage?.cookie, + migrate: defaultLoadOptions.storage?.migrate, + }, + }, + }, + ]; + + it.each(testCaseData)('$name', ({ input, expected }) => { + expect(normalizeLoadOptions(defaultLoadOptions, input as Partial)).toEqual({ + ...expected, + plugins: defaultOptionalPluginsList, + }); + }); + }); + }); +}); diff --git a/packages/analytics-js/__tests__/services/StoreManager/StoreManager.test.ts b/packages/analytics-js/__tests__/services/StoreManager/StoreManager.test.ts index a28fffa932..3b5bb6dceb 100644 --- a/packages/analytics-js/__tests__/services/StoreManager/StoreManager.test.ts +++ b/packages/analytics-js/__tests__/services/StoreManager/StoreManager.test.ts @@ -76,6 +76,7 @@ describe('StoreManager', () => { samesite: state.loadOptions.value.sameSiteCookie, secure: state.loadOptions.value.secureCookie, domain: state.loadOptions.value.setCookieDomain, + sameDomainCookiesOnly: state.loadOptions.value.sameDomainCookiesOnly, enabled: true, }, { enabled: true }, diff --git a/packages/analytics-js/package.json b/packages/analytics-js/package.json index 6b7744cc18..763eab9c58 100644 --- a/packages/analytics-js/package.json +++ b/packages/analytics-js/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js", - "version": "3.11.17", + "version": "3.12.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 70a11d1909..9bcb915156 100644 --- a/packages/analytics-js/project.json +++ b/packages/analytics-js/project.json @@ -52,9 +52,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js@3.11.17", - "title": "@rudderstack/analytics-js@3.11.17", - "discussion-category": "@rudderstack/analytics-js@3.11.17", + "tag": "@rudderstack/analytics-js@3.12.0", + "title": "@rudderstack/analytics-js@3.12.0", + "discussion-category": "@rudderstack/analytics-js@3.12.0", "notesFile": "./packages/analytics-js/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/public/index.html b/packages/analytics-js/public/index.html index 029e693b8c..bf7d9716f1 100644 --- a/packages/analytics-js/public/index.html +++ b/packages/analytics-js/public/index.html @@ -98,10 +98,6 @@ } var loadOptions = { logLevel: 'DEBUG', - configUrl: '__CONFIG_SERVER_HOST__', - destSDKBaseURL: - '__DEST_SDK_BASE_URL__' + window.rudderAnalyticsBuildType + '/js-integrations', - pluginsSDKBaseURL: '__PLUGINS_BASE_URL__' + window.rudderAnalyticsBuildType + '/plugins', // integrations: { // All: false // }, @@ -173,6 +169,18 @@ // } }; + const envDestSDKBaseURL = '__DEST_SDK_BASE_URL__'; + if (envDestSDKBaseURL !== 'undefined') { + loadOptions.destSDKBaseURL = envDestSDKBaseURL + window.rudderAnalyticsBuildType + '/js-integrations'; + } + const envPluginsSDKBaseURL = '__PLUGINS_BASE_URL__'; + if (envPluginsSDKBaseURL !== 'undefined') { + loadOptions.pluginsSDKBaseURL = envPluginsSDKBaseURL + window.rudderAnalyticsBuildType + '/plugins'; + } + const envConfigUrl = '__CONFIG_SERVER_HOST__'; + if (envConfigUrl !== 'undefined') { + loadOptions.configUrl = envConfigUrl; + } rudderanalytics.load('__WRITE_KEY__', '__DATAPLANE_URL__', loadOptions); } } diff --git a/packages/analytics-js/rollup.config.mjs b/packages/analytics-js/rollup.config.mjs index 2a1a2e8ea7..b1537690f3 100644 --- a/packages/analytics-js/rollup.config.mjs +++ b/packages/analytics-js/rollup.config.mjs @@ -32,8 +32,11 @@ const isModuleFederatedBuild = !isDynamicCustomBuild && !isLegacyBuild; const sourceMapType = process.env.PROD_DEBUG === 'inline' ? 'inline' : process.env.PROD_DEBUG === 'true'; const cdnPath = isDynamicCustomBuild ? `dynamicCdnBundle` : `cdn`; -const remotePluginsBasePath = - process.env.REMOTE_MODULES_BASE_PATH || `http://localhost:3002/${cdnPath}/`; +let remotePluginsBasePath = + process.env.REMOTE_MODULES_BASE_PATH; +remotePluginsBasePath = remotePluginsBasePath?.endsWith('/') ? remotePluginsBasePath : `${remotePluginsBasePath}/`; +let destSDKBaseURL = process.env.DEST_SDK_BASE_URL; +destSDKBaseURL = destSDKBaseURL?.endsWith('/') ? destSDKBaseURL : `${destSDKBaseURL}/`; const outDirNpmRoot = `dist/npm`; const outDirCDNRoot = isDynamicCustomBuild ? `dist/${cdnPath}` : `dist/${cdnPath}`; let outDirNpm = `${outDirNpmRoot}${variantSubfolder}`; @@ -43,6 +46,7 @@ const modName = 'rudderanalytics'; const remotePluginsExportsFilename = `rsa-plugins`; const remotePluginsHostPromise = `Promise.resolve(window.RudderStackGlobals && window.RudderStackGlobals.app && window.RudderStackGlobals.app.pluginsCDNPath ? \`\${window.RudderStackGlobals.app.pluginsCDNPath}/${remotePluginsExportsFilename}.js\` : \`${remotePluginsBasePath}/${remotePluginsExportsFilename}.js\`)`; const moduleType = process.env.MODULE_TYPE || 'cdn'; +const lockDepsVersion = process.env.LOCK_DEPS_VERSION ?? false; const isCDNPackageBuild = moduleType === 'cdn'; let bugsnagSDKUrl = 'https://d2wy8f7a9ursnm.cloudfront.net/v6/bugsnag.min.js'; let polyfillIoUrl = 'https://polyfill-fastly.io/v3/polyfill.min.js'; @@ -190,6 +194,7 @@ export function getDefaultConfig(distName) { __IS_LEGACY_BUILD__: isLegacyBuild, __PACKAGE_VERSION__: version, __MODULE_TYPE__: moduleType, + __LOCK_DEPS_VERSION__: lockDepsVersion, __SDK_BUNDLE_FILENAME__: distName, __RS_POLYFILLIO_SDK_URL__: polyfillIoUrl, __RS_BUGSNAG_API_KEY__: process.env.BUGSNAG_API_KEY || '{{__RS_BUGSNAG_API_KEY__}}', @@ -262,8 +267,8 @@ export function getDefaultConfig(distName) { replaceVars: { __WRITE_KEY__: process.env.WRITE_KEY, __DATAPLANE_URL__: process.env.DATAPLANE_URL, - __CONFIG_SERVER_HOST__: process.env.CONFIG_SERVER_HOST || '', - __DEST_SDK_BASE_URL__: process.env.DEST_SDK_BASE_URL, + __CONFIG_SERVER_HOST__: process.env.CONFIG_SERVER_HOST, + __DEST_SDK_BASE_URL__: destSDKBaseURL, __PLUGINS_BASE_URL__: remotePluginsBasePath, __SDK_BUNDLE_FILENAME__: distName, }, diff --git a/packages/analytics-js/src/components/configManager/ConfigManager.ts b/packages/analytics-js/src/components/configManager/ConfigManager.ts index 315f2ab5a5..762b92d6ae 100644 --- a/packages/analytics-js/src/components/configManager/ConfigManager.ts +++ b/packages/analytics-js/src/components/configManager/ConfigManager.ts @@ -1,3 +1,4 @@ +/* eslint-disable sonarjs/deprecation */ /* eslint-disable class-methods-use-this */ import type { IHttpClient, diff --git a/packages/analytics-js/src/components/configManager/util/cdnPaths.ts b/packages/analytics-js/src/components/configManager/util/cdnPaths.ts index 0cc0070b18..56cf9c4cd2 100644 --- a/packages/analytics-js/src/components/configManager/util/cdnPaths.ts +++ b/packages/analytics-js/src/components/configManager/util/cdnPaths.ts @@ -3,84 +3,107 @@ import { isValidURL } from '@rudderstack/analytics-js-common/utilities/url'; import { BUILD_TYPE, CDN_ARCH_VERSION_DIR, - DEST_SDK_BASE_URL, - PLUGINS_BASE_URL, + DEFAULT_INTEGRATION_SDKS_URL, + DEFAULT_PLUGINS_URL, } from '../../../constants/urls'; import { COMPONENT_BASE_URL_ERROR } from '../../../constants/logMessages'; import { removeTrailingSlashes } from '../../utilities/url'; import { getSDKUrl } from './commonUtil'; +import { state } from '../../../state'; +/** + * A function that determines the base URL for the integrations or plugins SDK + * @param componentType The type of the component (integrations or plugins) + * @param pathSuffix The path suffix to be appended to the base URL (js-integrations or plugins) + * @param defaultComponentUrl The default URL to be used if the user has not provided a custom URL + * @param currentSdkVersion The current version of the SDK + * @param lockVersion Flag to lock the version of the component + * @param urlFromLoadOptions The URL provided by the user in the load options + * @returns The base URL for the integrations or plugins SDK + */ const getSDKComponentBaseURL = ( componentType: string, pathSuffix: string, - baseURL: string, - currentVersion: string, + defaultComponentUrl: string, + currentSdkVersion: string, lockVersion: boolean, - customURL?: string, + urlFromLoadOptions?: string, ) => { - let sdkComponentURL = ''; - - if (customURL) { - if (!isValidURL(customURL)) { - throw new Error(COMPONENT_BASE_URL_ERROR(componentType)); + let sdkComponentBaseURL; + // If the user has provided a custom URL, then validate, clean up and use it + if (urlFromLoadOptions) { + if (!isValidURL(urlFromLoadOptions)) { + throw new Error(COMPONENT_BASE_URL_ERROR(componentType, urlFromLoadOptions)); } - return removeTrailingSlashes(customURL) as string; - } + sdkComponentBaseURL = removeTrailingSlashes(urlFromLoadOptions) as string; + } else { + sdkComponentBaseURL = defaultComponentUrl; - const sdkURL = getSDKUrl(); - sdkComponentURL = sdkURL ? sdkURL.split('/').slice(0, -1).concat(pathSuffix).join('/') : baseURL; + // We can automatically determine the base URL only for CDN installations + if (state.context.app.value.installType === 'cdn') { + const sdkURL = getSDKUrl(); + + if (sdkURL) { + // Extract the base URL from the core SDK file URL + // and append the path suffix to it + sdkComponentBaseURL = sdkURL.split('/').slice(0, -1).concat(pathSuffix).join('/'); + } + } + } + // If the version needs to be locked, then replace the major version in the URL + // with the current version of the SDK if (lockVersion) { - sdkComponentURL = sdkComponentURL.replace( - `/${CDN_ARCH_VERSION_DIR}/${BUILD_TYPE}/${pathSuffix}`, - `/${currentVersion}/${BUILD_TYPE}/${pathSuffix}`, + sdkComponentBaseURL = sdkComponentBaseURL.replace( + new RegExp(`/${CDN_ARCH_VERSION_DIR}/${BUILD_TYPE}/${pathSuffix}$`), + `/${currentSdkVersion}/${BUILD_TYPE}/${pathSuffix}`, ); } - return sdkComponentURL; + return sdkComponentBaseURL; }; /** * A function that determines integration SDK loading path - * @param currentVersion - * @param lockIntegrationsVersion - * @param customIntegrationsCDNPath + * @param currentSdkVersion Current SDK version + * @param lockIntegrationsVersion Flag to lock the integrations version + * @param integrationsUrlFromLoadOptions URL to load the integrations from as provided by the user * @returns */ const getIntegrationsCDNPath = ( - currentVersion: string, + currentSdkVersion: string, lockIntegrationsVersion: boolean, - customIntegrationsCDNPath?: string, + integrationsUrlFromLoadOptions?: string, ): string => getSDKComponentBaseURL( 'integrations', CDN_INT_DIR, - DEST_SDK_BASE_URL, - currentVersion, + DEFAULT_INTEGRATION_SDKS_URL, + currentSdkVersion, lockIntegrationsVersion, - customIntegrationsCDNPath, + integrationsUrlFromLoadOptions, ); /** * A function that determines plugins SDK loading path - * @param currentVersion Current SDK version + * @param currentSdkVersion Current SDK version * @param lockPluginsVersion Flag to lock the plugins version - * @param customPluginsCDNPath URL to load the plugins from + * @param pluginsUrlFromLoadOptions URL to load the plugins from as provided by the user * @returns Final plugins CDN path */ const getPluginsCDNPath = ( - currentVersion: string, + currentSdkVersion: string, lockPluginsVersion: boolean, - customPluginsCDNPath?: string, + pluginsUrlFromLoadOptions?: string, ): string => getSDKComponentBaseURL( 'plugins', CDN_PLUGINS_DIR, - PLUGINS_BASE_URL, - currentVersion, + DEFAULT_PLUGINS_URL, + currentSdkVersion, lockPluginsVersion, - customPluginsCDNPath, + pluginsUrlFromLoadOptions, ); export { getIntegrationsCDNPath, getPluginsCDNPath }; diff --git a/packages/analytics-js/src/components/configManager/util/commonUtil.ts b/packages/analytics-js/src/components/configManager/util/commonUtil.ts index 1396639883..29e35d836f 100644 --- a/packages/analytics-js/src/components/configManager/util/commonUtil.ts +++ b/packages/analytics-js/src/components/configManager/util/commonUtil.ts @@ -56,6 +56,16 @@ import { getConsentManagementData } from '../../utilities/consent'; * @returns sdkURL */ const getSDKUrl = (): string | undefined => { + // First try the new method of getting the SDK URL + // from the script tag + const scriptTag = document.querySelector('script[data-rsa-write-key]'); + if (scriptTag && scriptTag.dataset.rsaWriteKey === state.lifecycle.writeKey.value) { + return scriptTag.src; + } + + // If the new method fails, try the old method + // TODO: We need to remove this once all the customers upgrade to the + // latest SDK loading snippet const scripts = document.getElementsByTagName('script'); const sdkFileNameRegex = /(?:^|\/)rsa(\.min)?\.js$/; diff --git a/packages/analytics-js/src/components/utilities/loadOptions.ts b/packages/analytics-js/src/components/utilities/loadOptions.ts index f70d11948f..1287d23b6b 100644 --- a/packages/analytics-js/src/components/utilities/loadOptions.ts +++ b/packages/analytics-js/src/components/utilities/loadOptions.ts @@ -1,7 +1,9 @@ +/* eslint-disable sonarjs/deprecation */ import { clone } from 'ramda'; import { + getNormalizedBooleanValue, + getNormalizedObjectValue, isNonEmptyObject, - isObjectLiteralAndNotNull, mergeDeepRight, removeUndefinedAndNullValues, } from '@rudderstack/analytics-js-common/utilities/object'; @@ -10,7 +12,7 @@ import type { UaChTrackLevel, } from '@rudderstack/analytics-js-common/types/LoadOptions'; import type { StorageOpts, CookieSameSite } from '@rudderstack/analytics-js-common/types/Storage'; -import { isDefined, isString } from '@rudderstack/analytics-js-common/utilities/checks'; +import { isString } from '@rudderstack/analytics-js-common/utilities/checks'; import { defaultOptionalPluginsList } from '../pluginsManager/defaultPluginsList'; import { isNumber } from './number'; @@ -22,109 +24,113 @@ const normalizeLoadOptions = ( const normalizedLoadOpts = clone(loadOptions); if (!isString(normalizedLoadOpts.setCookieDomain)) { - delete normalizedLoadOpts.setCookieDomain; + normalizedLoadOpts.setCookieDomain = undefined; } const cookieSameSiteValues = ['Strict', 'Lax', 'None']; if (!cookieSameSiteValues.includes(normalizedLoadOpts.sameSiteCookie as CookieSameSite)) { - delete normalizedLoadOpts.sameSiteCookie; + normalizedLoadOpts.sameSiteCookie = undefined; } - normalizedLoadOpts.secureCookie = normalizedLoadOpts.secureCookie === true; + normalizedLoadOpts.secureCookie = getNormalizedBooleanValue( + normalizedLoadOpts.secureCookie, + loadOptionsFromState.secureCookie, + ); - normalizedLoadOpts.sameDomainCookiesOnly = normalizedLoadOpts.sameDomainCookiesOnly === true; + normalizedLoadOpts.sameDomainCookiesOnly = getNormalizedBooleanValue( + normalizedLoadOpts.sameDomainCookiesOnly, + loadOptionsFromState.sameDomainCookiesOnly, + ); const uaChTrackLevels = ['none', 'default', 'full']; if (!uaChTrackLevels.includes(normalizedLoadOpts.uaChTrackLevel as UaChTrackLevel)) { - delete normalizedLoadOpts.uaChTrackLevel; + normalizedLoadOpts.uaChTrackLevel = undefined; } - if (!isNonEmptyObject(normalizedLoadOpts.integrations)) { - delete normalizedLoadOpts.integrations; - } + normalizedLoadOpts.integrations = getNormalizedObjectValue(normalizedLoadOpts.integrations); - normalizedLoadOpts.plugins = normalizedLoadOpts.plugins ?? defaultOptionalPluginsList; + if (!Array.isArray(normalizedLoadOpts.plugins)) { + normalizedLoadOpts.plugins = defaultOptionalPluginsList; + } - normalizedLoadOpts.useGlobalIntegrationsConfigInEvents = - normalizedLoadOpts.useGlobalIntegrationsConfigInEvents === true; + normalizedLoadOpts.useGlobalIntegrationsConfigInEvents = getNormalizedBooleanValue( + normalizedLoadOpts.useGlobalIntegrationsConfigInEvents, + loadOptionsFromState.useGlobalIntegrationsConfigInEvents, + ); - normalizedLoadOpts.bufferDataPlaneEventsUntilReady = - normalizedLoadOpts.bufferDataPlaneEventsUntilReady === true; + normalizedLoadOpts.bufferDataPlaneEventsUntilReady = getNormalizedBooleanValue( + normalizedLoadOpts.bufferDataPlaneEventsUntilReady, + loadOptionsFromState.bufferDataPlaneEventsUntilReady, + ); - normalizedLoadOpts.sendAdblockPage = normalizedLoadOpts.sendAdblockPage === true; + normalizedLoadOpts.sendAdblockPage = getNormalizedBooleanValue( + normalizedLoadOpts.sendAdblockPage, + loadOptionsFromState.sendAdblockPage, + ); - normalizedLoadOpts.useServerSideCookies = normalizedLoadOpts.useServerSideCookies === true; + normalizedLoadOpts.useServerSideCookies = getNormalizedBooleanValue( + normalizedLoadOpts.useServerSideCookies, + loadOptionsFromState.useServerSideCookies, + ); - if ( - normalizedLoadOpts.dataServiceEndpoint && - typeof normalizedLoadOpts.dataServiceEndpoint !== 'string' - ) { - delete normalizedLoadOpts.dataServiceEndpoint; + if (!isString(normalizedLoadOpts.dataServiceEndpoint)) { + normalizedLoadOpts.dataServiceEndpoint = undefined; } - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.sendAdblockPageOptions)) { - delete normalizedLoadOpts.sendAdblockPageOptions; - } - - if (!isDefined(normalizedLoadOpts.loadIntegration)) { - delete normalizedLoadOpts.loadIntegration; - } else { - normalizedLoadOpts.loadIntegration = normalizedLoadOpts.loadIntegration === true; - } + normalizedLoadOpts.sendAdblockPageOptions = getNormalizedObjectValue( + normalizedLoadOpts.sendAdblockPageOptions, + ); - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.storage)) { - delete normalizedLoadOpts.storage; - } else { - normalizedLoadOpts.storage = removeUndefinedAndNullValues(normalizedLoadOpts.storage); - (normalizedLoadOpts.storage as StorageOpts).migrate = - normalizedLoadOpts.storage?.migrate === true; - } + normalizedLoadOpts.loadIntegration = getNormalizedBooleanValue( + normalizedLoadOpts.loadIntegration, + loadOptionsFromState.loadIntegration, + ); - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.beaconQueueOptions)) { - delete normalizedLoadOpts.beaconQueueOptions; + if (!isNonEmptyObject(normalizedLoadOpts.storage)) { + normalizedLoadOpts.storage = undefined; } else { - normalizedLoadOpts.beaconQueueOptions = removeUndefinedAndNullValues( - normalizedLoadOpts.beaconQueueOptions, + normalizedLoadOpts.storage.migrate = getNormalizedBooleanValue( + normalizedLoadOpts.storage.migrate, + loadOptionsFromState.storage?.migrate, ); - } - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.destinationsQueueOptions)) { - delete normalizedLoadOpts.destinationsQueueOptions; - } else { - normalizedLoadOpts.destinationsQueueOptions = removeUndefinedAndNullValues( - normalizedLoadOpts.destinationsQueueOptions, + normalizedLoadOpts.storage.cookie = getNormalizedObjectValue(normalizedLoadOpts.storage.cookie); + normalizedLoadOpts.storage.encryption = getNormalizedObjectValue( + normalizedLoadOpts.storage.encryption, ); + normalizedLoadOpts.storage = removeUndefinedAndNullValues(normalizedLoadOpts.storage); } - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.queueOptions)) { - delete normalizedLoadOpts.queueOptions; - } else { - normalizedLoadOpts.queueOptions = removeUndefinedAndNullValues(normalizedLoadOpts.queueOptions); - } + normalizedLoadOpts.destinationsQueueOptions = getNormalizedObjectValue( + normalizedLoadOpts.destinationsQueueOptions, + ); + + normalizedLoadOpts.queueOptions = getNormalizedObjectValue(normalizedLoadOpts.queueOptions); - normalizedLoadOpts.lockIntegrationsVersion = normalizedLoadOpts.lockIntegrationsVersion === true; + normalizedLoadOpts.lockIntegrationsVersion = getNormalizedBooleanValue( + normalizedLoadOpts.lockIntegrationsVersion, + loadOptionsFromState.lockIntegrationsVersion, + ); - normalizedLoadOpts.lockPluginsVersion = normalizedLoadOpts.lockPluginsVersion === true; + normalizedLoadOpts.lockPluginsVersion = getNormalizedBooleanValue( + normalizedLoadOpts.lockPluginsVersion, + loadOptionsFromState.lockPluginsVersion, + ); if (!isNumber(normalizedLoadOpts.dataPlaneEventsBufferTimeout)) { - delete normalizedLoadOpts.dataPlaneEventsBufferTimeout; + normalizedLoadOpts.dataPlaneEventsBufferTimeout = undefined; } - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.storage?.cookie)) { - delete normalizedLoadOpts.storage?.cookie; - } else { - (normalizedLoadOpts.storage as StorageOpts).cookie = removeUndefinedAndNullValues( - normalizedLoadOpts.storage?.cookie, - ); - } + normalizedLoadOpts.beaconQueueOptions = getNormalizedObjectValue( + normalizedLoadOpts.beaconQueueOptions, + ); - if (!isObjectLiteralAndNotNull(normalizedLoadOpts.preConsent)) { - delete normalizedLoadOpts.preConsent; - } else { - normalizedLoadOpts.preConsent = removeUndefinedAndNullValues(normalizedLoadOpts.preConsent); - } + normalizedLoadOpts.preConsent = getNormalizedObjectValue(normalizedLoadOpts.preConsent); - const mergedLoadOptions: LoadOptions = mergeDeepRight(loadOptionsFromState, normalizedLoadOpts); + const mergedLoadOptions: LoadOptions = mergeDeepRight( + loadOptionsFromState, + removeUndefinedAndNullValues(normalizedLoadOpts), + ); return mergedLoadOptions; }; diff --git a/packages/analytics-js/src/constants/logMessages.ts b/packages/analytics-js/src/constants/logMessages.ts index 0020c5fdb4..d521f97ed4 100644 --- a/packages/analytics-js/src/constants/logMessages.ts +++ b/packages/analytics-js/src/constants/logMessages.ts @@ -19,8 +19,8 @@ const EVENT_OBJECT_GENERATION_ERROR = `Failed to generate the event object.`; const PLUGIN_EXT_POINT_MISSING_ERROR = `Failed to invoke plugin because the extension point name is missing.`; const PLUGIN_EXT_POINT_INVALID_ERROR = `Failed to invoke plugin because the extension point name is invalid.`; -const COMPONENT_BASE_URL_ERROR = (component: string): string => - `Failed to load the SDK as the base URL for ${component} is not valid.`; +const COMPONENT_BASE_URL_ERROR = (component: string, url: string): string => + `Failed to load the SDK as the base URL "${url}" for ${component} is not valid.`; // ERROR const UNSUPPORTED_CONSENT_MANAGER_ERROR = ( diff --git a/packages/analytics-js/src/constants/urls.ts b/packages/analytics-js/src/constants/urls.ts index b27ce2c7f1..89f8c8ed3d 100644 --- a/packages/analytics-js/src/constants/urls.ts +++ b/packages/analytics-js/src/constants/urls.ts @@ -4,15 +4,15 @@ import { IS_LEGACY_BUILD } from './app'; const BUILD_TYPE = IS_LEGACY_BUILD ? 'legacy' : 'modern'; const SDK_CDN_BASE_URL = 'https://cdn.rudderlabs.com'; const CDN_ARCH_VERSION_DIR = 'v3'; -const DEST_SDK_BASE_URL = `${SDK_CDN_BASE_URL}/${CDN_ARCH_VERSION_DIR}/${BUILD_TYPE}/${CDN_INT_DIR}`; -const PLUGINS_BASE_URL = `${SDK_CDN_BASE_URL}/${CDN_ARCH_VERSION_DIR}/${BUILD_TYPE}/${CDN_PLUGINS_DIR}`; +const DEFAULT_INTEGRATION_SDKS_URL = `${SDK_CDN_BASE_URL}/${CDN_ARCH_VERSION_DIR}/${BUILD_TYPE}/${CDN_INT_DIR}`; +const DEFAULT_PLUGINS_URL = `${SDK_CDN_BASE_URL}/${CDN_ARCH_VERSION_DIR}/${BUILD_TYPE}/${CDN_PLUGINS_DIR}`; const DEFAULT_CONFIG_BE_URL = 'https://api.rudderstack.com'; export { BUILD_TYPE, SDK_CDN_BASE_URL, CDN_ARCH_VERSION_DIR, - DEST_SDK_BASE_URL, - PLUGINS_BASE_URL, + DEFAULT_INTEGRATION_SDKS_URL, + DEFAULT_PLUGINS_URL, DEFAULT_CONFIG_BE_URL, }; diff --git a/packages/analytics-js/src/state/slices/lifecycle.ts b/packages/analytics-js/src/state/slices/lifecycle.ts index 4a2f69018b..1389d80db4 100644 --- a/packages/analytics-js/src/state/slices/lifecycle.ts +++ b/packages/analytics-js/src/state/slices/lifecycle.ts @@ -1,11 +1,11 @@ import { signal } from '@preact/signals-core'; import type { LifecycleState } from '@rudderstack/analytics-js-common/types/ApplicationState'; -import { DEST_SDK_BASE_URL, PLUGINS_BASE_URL } from '../../constants/urls'; +import { DEFAULT_INTEGRATION_SDKS_URL, DEFAULT_PLUGINS_URL } from '../../constants/urls'; const lifecycleState: LifecycleState = { activeDataplaneUrl: signal(undefined), - integrationsCDNPath: signal(DEST_SDK_BASE_URL), - pluginsCDNPath: signal(PLUGINS_BASE_URL), + integrationsCDNPath: signal(DEFAULT_INTEGRATION_SDKS_URL), + pluginsCDNPath: signal(DEFAULT_PLUGINS_URL), sourceConfigUrl: signal(undefined), status: signal(undefined), initialized: signal(false), diff --git a/packages/analytics-js/src/state/slices/loadOptions.ts b/packages/analytics-js/src/state/slices/loadOptions.ts index 8d97593535..df6af07ff1 100644 --- a/packages/analytics-js/src/state/slices/loadOptions.ts +++ b/packages/analytics-js/src/state/slices/loadOptions.ts @@ -25,8 +25,8 @@ const defaultLoadOptions: LoadOptions = { beaconQueueOptions: {}, destinationsQueueOptions: {}, queueOptions: {}, - lockIntegrationsVersion: false, - lockPluginsVersion: false, + lockIntegrationsVersion: __LOCK_DEPS_VERSION__, + lockPluginsVersion: __LOCK_DEPS_VERSION__, uaChTrackLevel: 'none', plugins: [], useGlobalIntegrationsConfigInEvents: false, @@ -39,6 +39,9 @@ const defaultLoadOptions: LoadOptions = { migrate: true, cookie: {}, }, + sendAdblockPage: false, + sameDomainCookiesOnly: false, + secureCookie: false, sendAdblockPageOptions: {}, useServerSideCookies: false, }; diff --git a/packages/analytics-v1.1/CHANGELOG.md b/packages/analytics-v1.1/CHANGELOG.md index f80e0a4c15..56935867c1 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.44](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.43...rudder-sdk-js@2.48.44) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.15.0` ## [2.48.43](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.42...rudder-sdk-js@2.48.43) (2025-01-03) ### Dependency Updates diff --git a/packages/analytics-v1.1/CHANGELOG_LATEST.md b/packages/analytics-v1.1/CHANGELOG_LATEST.md index bf38616a62..f36d5ee10a 100644 --- a/packages/analytics-v1.1/CHANGELOG_LATEST.md +++ b/packages/analytics-v1.1/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [2.48.43](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.42...rudder-sdk-js@2.48.43) (2025-01-03) +## [2.48.44](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.43...rudder-sdk-js@2.48.44) (2025-01-24) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.15` +* `@rudderstack/analytics-js-common` updated to version `3.15.0` diff --git a/packages/analytics-v1.1/package.json b/packages/analytics-v1.1/package.json index 5e16f790e6..5b16ba0047 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.43", + "version": "2.48.44", "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 6c747dd8c4..1041ede1a5 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.43", - "title": "rudder-sdk-js@2.48.43", - "discussion-category": "rudder-sdk-js@2.48.43", + "tag": "rudder-sdk-js@2.48.44", + "title": "rudder-sdk-js@2.48.44", + "discussion-category": "rudder-sdk-js@2.48.44", "notesFile": "./packages/analytics-v1.1/CHANGELOG_LATEST.md" } } diff --git a/packages/sanity-suite/CHANGELOG.md b/packages/sanity-suite/CHANGELOG.md index 39c012784f..a870342434 100644 --- a/packages/sanity-suite/CHANGELOG.md +++ b/packages/sanity-suite/CHANGELOG.md @@ -2,6 +2,16 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.2.0](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.51...@rudderstack/analytics-js-sanity-suite@3.2.0) (2025-01-24) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.12.0` + +### Features + +* lock plugins and integrations version by default ([#1956](https://github.com/rudderlabs/rudder-sdk-js/issues/1956)) ([45e716e](https://github.com/rudderlabs/rudder-sdk-js/commit/45e716e6df3d6e665c25aa907531adb746961d50)) + ## [3.1.51](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.50...@rudderstack/analytics-js-sanity-suite@3.1.51) (2025-01-03) ### Dependency Updates diff --git a/packages/sanity-suite/package.json b/packages/sanity-suite/package.json index 77a30be7f6..0dafe84e7f 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.51", + "version": "3.2.0", "private": true, "description": "Sanity suite for testing JS SDK package", "main": "./dist/v3/cdn/testBook.js", diff --git a/packages/sanity-suite/public/v1.1/index-local.html b/packages/sanity-suite/public/v1.1/index-local.html index a41ffd2d36..f7bf5b37ce 100644 --- a/packages/sanity-suite/public/v1.1/index-local.html +++ b/packages/sanity-suite/public/v1.1/index-local.html @@ -70,7 +70,6 @@ rudderanalytics.load('__WRITE_KEY__', '__DATAPLANE_URL__', { logLevel: 'DEBUG', configUrl: '__CONFIG_SERVER_HOST__', - lockIntegrationsVersion: true, destSDKBaseURL: '__DEST_SDK_BASE_URL__', cookieConsentManager: { oneTrust: { diff --git a/packages/sanity-suite/public/v3/index-local.html b/packages/sanity-suite/public/v3/index-local.html index 1a7a5825b9..7eae33ea51 100644 --- a/packages/sanity-suite/public/v3/index-local.html +++ b/packages/sanity-suite/public/v3/index-local.html @@ -135,7 +135,6 @@ configUrl: '__CONFIG_SERVER_HOST__', destSDKBaseURL: '__DEST_SDK_BASE_URL__', pluginsSDKBaseURL: '__PLUGINS_SDK_BASE_URL__', - lockIntegrationsVersion: true, consentManagement: { enabled: true, provider: 'oneTrust', diff --git a/packages/sanity-suite/public/v3/integrations/index-local.html b/packages/sanity-suite/public/v3/integrations/index-local.html index b09881a9c6..00a489b3e0 100644 --- a/packages/sanity-suite/public/v3/integrations/index-local.html +++ b/packages/sanity-suite/public/v3/integrations/index-local.html @@ -124,7 +124,6 @@ configUrl: '__CONFIG_SERVER_HOST__', destSDKBaseURL: '__DEST_SDK_BASE_URL__', pluginsSDKBaseURL: '__PLUGINS_SDK_BASE_URL__', - lockIntegrationsVersion: true, consentManagement: { enabled: true, provider: 'oneTrust', diff --git a/packages/sanity-suite/public/v3/manualLoadCall/index-local.html b/packages/sanity-suite/public/v3/manualLoadCall/index-local.html index 288282acb1..cc0688bff5 100644 --- a/packages/sanity-suite/public/v3/manualLoadCall/index-local.html +++ b/packages/sanity-suite/public/v3/manualLoadCall/index-local.html @@ -121,7 +121,6 @@ configUrl: '__CONFIG_SERVER_HOST__', destSDKBaseURL: '__DEST_SDK_BASE_URL__', pluginsSDKBaseURL: '__PLUGINS_SDK_BASE_URL__', - lockIntegrationsVersion: true, consentManagement: { enabled: true, provider: 'oneTrust', diff --git a/sonar-project.properties b/sonar-project.properties index d6f829fda7..e806a808b3 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.73.0 +sonar.projectVersion=3.74.0 # Meta-data for the project sonar.links.scm=https://github.com/rudderlabs/rudder-sdk-js diff --git a/types/global.d.ts b/types/global.d.ts index af82260a2f..1c3ee3f5f0 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -16,3 +16,5 @@ declare const __IS_DYNAMIC_CUSTOM_BUNDLE__: boolean; declare const __IS_LEGACY_BUILD__: boolean; declare const __BUNDLED_PLUGINS_LIST__: string | undefined; + +declare const __LOCK_DEPS_VERSION__: boolean;