diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9de37704bc..8eb81e257f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -49,7 +49,8 @@ permissions: env: NODE_OPTIONS: "--no-warnings" - CACHE_CONTROL: "\"no-store\"" # "\"max-age=3600\"" + CACHE_CONTROL_NO_STORE: "\"no-store\"" + CACHE_CONTROL_MAX_AGE: "\"max-age=3600\"" jobs: deploy: @@ -77,12 +78,12 @@ jobs: echo "Checkout SHA: $sha" echo "SHA=$sha" >> $GITHUB_OUTPUT - - name: Checkout + - name: Checkout source code uses: actions/checkout@v4 with: ref: ${{ steps.getSHA.outputs.SHA }} - - name: Get new version number + - 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) @@ -96,7 +97,7 @@ jobs: node-version-file: '.nvmrc' cache: 'npm' - - name: Install dependencies + - name: Setup workspace env: HUSKY: 0 REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ inputs.s3_dir_path }}/modern/plugins' @@ -105,7 +106,7 @@ jobs: run: | npm run setup:ci - - name: Build release artifacts + - name: Build artifacts env: BUGSNAG_API_KEY: ${{ secrets.BUGSNAG_API_KEY }} BUGSNAG_RELEASE_STAGE: ${{ inputs.bugsnag_release_stage }} @@ -113,23 +114,36 @@ jobs: # npm run build:browser npm run build:browser:modern -- --projects=@rudderstack/analytics-js-plugins,@rudderstack/analytics-js - - name: Sync Adobe Analytics assets to S3 + - name: Copy assets to S3 if: ${{ inputs.environment == 'production' }} run: | - aws s3 cp assets/integrations/AdobeAnalytics/ s3://${{ secrets.AWS_S3_BUCKET_NAME }}/adobe-analytics-js --recursive --cache-control ${{ env.CACHE_CONTROL }} + aws s3 cp assets/integrations/AdobeAnalytics/ s3://${{ secrets.AWS_S3_BUCKET_NAME }}/adobe-analytics-js --recursive --cache-control ${{ env.CACHE_CONTROL_NO_STORE }} - - name: Create Cloudfront invalidation + - name: Invalidate CloudFront cache for assets if: ${{ inputs.environment == 'production' }} run: | - aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/adobe-analytics-js*" + invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/adobe-analytics-js*" --query "Invalidation.Id" --output text) - - name: Sync files to S3 + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" + + - name: Determine cache policy based on environment + id: cache_policy + run: | + if [ "${{ inputs.environment }}" == "production" ]; then + echo "CACHE_CONTROL=${{ env.CACHE_CONTROL_NO_STORE }}" >> $GITHUB_ENV + else + echo "CACHE_CONTROL=${{ env.CACHE_CONTROL_MAX_AGE }}" >> $GITHUB_ENV + fi + + - name: Copy SDK artifacts to S3 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_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/${{ inputs.s3_dir_path }}" - copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" + s3_relative_path_prefix="${{ inputs.s3_dir_path }}" + 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" @@ -152,72 +166,76 @@ jobs: # 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_args - # aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_args + # 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 $core_sdk_path_prefix/modern/iife/ $s3_path_prefix/modern/ $copy_args - aws s3 cp $plugins_path_prefix/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_args - # aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_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 - # # Generate the HTML file to list all the integrations - # ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} ${{ inputs.s3_dir_path }}/legacy/js-integrations $integration_sdks_html_file $integration_sdks_path_prefix/legacy/js-integrations "Device Mode Integrations" $integration_sdks_zip_file + # 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 }} ${{ inputs.s3_dir_path }}/modern/js-integrations $integration_sdks_html_file $integration_sdks_path_prefix/modern/js-integrations "Device Mode Integrations" $integration_sdks_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 - # # Generate the HTML file to list all the plugins - # ./scripts/list-sdk-components.sh ${{ secrets.AWS_S3_BUCKET_NAME }} ${{ inputs.s3_dir_path }}/modern/plugins $plugins_html_file $plugins_path_prefix/modern/plugins "Plugins" $plugins_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 --cache-control ${{ env.CACHE_CONTROL }} - # aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/$integration_sdks_html_file $s3_path_prefix/modern/js-integrations/$integration_sdks_html_file --cache-control ${{ env.CACHE_CONTROL }} - # aws s3 cp $plugins_path_prefix/modern/plugins/$plugins_html_file $s3_path_prefix/modern/plugins/$plugins_html_file --cache-control ${{ env.CACHE_CONTROL }} + # 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 - - name: Create Cloudfront invalidation + - name: Invalidate CloudFront cache for all the SDK artifacts run: | - AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ inputs.s3_dir_path }}/*" + 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) - - name: Sync files to S3 versioned directory + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" + + - name: Copy SDK 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_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}" - copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" + 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 }}" - aws s3 cp $core_sdk_path_prefix/legacy/iife/ $s3_path_prefix/legacy/ $copy_args - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_args + integration_sdks_zip_file="all_integration_sdks.tar.gz" + plugins_zip_file="all_plugins.tar.gz" - aws s3 cp $core_sdk_path_prefix/modern/iife/ $s3_path_prefix/modern/ $copy_args - aws s3 cp $plugins_path_prefix/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args + integration_sdks_html_file="list.html" + plugins_html_file="list.html" - - name: Create Cloudfront invalidation - if: ${{ inputs.environment == 'production' }} - run: | - AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_VALUE }}/*" + # 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 - # TODO: The '/latest' directory is unused. Might be removed in future. - - name: Sync files to S3 latest - 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_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/latest" - copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" + 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 $core_sdk_path_prefix/legacy/iife/ $s3_path_prefix/legacy/ $copy_args - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/legacy/js-integrations/ $copy_args + # 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 - aws s3 cp $core_sdk_path_prefix/modern/iife/ $s3_path_prefix/modern/ $copy_args - aws s3 cp $plugins_path_prefix/modern/plugins/ $s3_path_prefix/modern/plugins/ $copy_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args + ./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 + + # 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 - - name: Create Cloudfront invalidation + - name: Invalidate CloudFront cache for all the SDK artifacts (versioned directory) if: ${{ inputs.environment == 'production' }} run: | - AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/latest*" + 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) + + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" - name: Send message to Slack channel id: slack @@ -263,7 +281,7 @@ jobs: # Below steps are for v1.1 SDK (legacy) - - name: Sync files to S3 v1.1 directory + - name: Copy legacy SDK artifacts to S3 if: ${{ inputs.environment == 'production' }} run: | core_sdk_path_prefix="packages/analytics-v1.1/dist/cdn" @@ -272,50 +290,35 @@ jobs: copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" aws s3 cp $core_sdk_path_prefix/legacy/ $s3_path_prefix/ $copy_args - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/js-integrations/ $copy_args - aws s3 cp $core_sdk_path_prefix/modern/ $s3_path_prefix/modern/ $copy_args + + 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: Create Cloudfront invalidation + - name: Invalidate CloudFront cache for all the legacy SDK artifacts if: ${{ inputs.environment == 'production' }} run: | - AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ inputs.s3_dir_path_legacy }}*" + invalidation_id=$(AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ inputs.s3_dir_path_legacy }}/*" --query "Invalidation.Id" --output text) + + aws cloudfront wait invalidation-completed --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --id "$invalidation_id" - - name: Sync files to S3 v1.1 versioned directory + - name: Copy legacy SDK artifacts to S3 (versioned directory) if: ${{ inputs.environment == 'production' }} run: | core_sdk_path_prefix="packages/analytics-v1.1/dist/cdn" integration_sdks_path_prefix="packages/analytics-js-integrations/dist/cdn" s3_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}" - copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" + copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL_MAX_AGE }}" aws s3 cp $core_sdk_path_prefix/legacy/ $s3_path_prefix/ $copy_args - aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/js-integrations/ $copy_args - aws s3 cp $core_sdk_path_prefix/modern/ $s3_path_prefix/modern/ $copy_args - aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args - - - name: Create Cloudfront invalidation - if: ${{ inputs.environment == 'production' }} - run: | - AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_V1_VALUE }}*" - - - name: Sync files to S3 latest (v1.1) - if: ${{ inputs.environment == 'production' }} - run: | - core_sdk_path_prefix="packages/analytics-v1.1/dist/cdn" - integration_sdks_path_prefix="packages/analytics-js-integrations/dist/cdn" - s3_path_prefix="s3://${{ secrets.AWS_S3_BUCKET_NAME }}/latest" - copy_args="--recursive --cache-control ${{ env.CACHE_CONTROL }}" - - aws s3 cp $core_sdk_path_prefix/legacy/ $s3_path_prefix/ $copy_args + aws s3 cp $integration_sdks_path_prefix/legacy/js-integrations/ $s3_path_prefix/js-integrations/ $copy_args - - aws s3 cp $core_sdk_path_prefix/modern/ $s3_path_prefix/modern/ $copy_args aws s3 cp $integration_sdks_path_prefix/modern/js-integrations/ $s3_path_prefix/modern/js-integrations/ $copy_args - - name: Create Cloudfront invalidation + - name: Invalidate CloudFront cache for all the legacy SDK artifacts (versioned directory) if: ${{ inputs.environment == 'production' }} run: | - AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_CF_DISTRIBUTION_ID }} --paths "/latest*" + 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/draft-new-release.yml b/.github/workflows/draft-new-release.yml index 0d63cd39a8..e1146582a5 100644 --- a/.github/workflows/draft-new-release.yml +++ b/.github/workflows/draft-new-release.yml @@ -11,10 +11,17 @@ env: NODE_OPTIONS: '--no-warnings' jobs: + validate-actor: + # Only allow to draft a new release from develop and hotfix branches + if: github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/') + uses: ./.github/workflows/validate-actor.yml + secrets: + PAT: ${{ secrets.PAT }} + draft-new-release: + needs: validate-actor name: Draft a new release runs-on: [self-hosted, Linux, X64] - if: github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/hotfix/') steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/rollback.yml b/.github/workflows/rollback.yml index 6bc29c88c1..f1d3589e26 100644 --- a/.github/workflows/rollback.yml +++ b/.github/workflows/rollback.yml @@ -4,9 +4,16 @@ on: workflow_dispatch: jobs: + validate-actor: + # Only allow to be deployed from tags and main branch + if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' + uses: ./.github/workflows/validate-actor.yml + secrets: + PAT: ${{ secrets.PAT }} + deploy: + needs: validate-actor name: Rollback production deployment - if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' uses: ./.github/workflows/deploy.yml with: environment: 'production' diff --git a/.github/workflows/update-cache-policy.yml b/.github/workflows/update-cache-policy.yml new file mode 100644 index 0000000000..26a1856ed3 --- /dev/null +++ b/.github/workflows/update-cache-policy.yml @@ -0,0 +1,68 @@ +name: Update cache control policy + +on: + workflow_dispatch: + inputs: + policy_type: + type: choice + description: Select the cache control policy type + required: true + options: + - no-store + - max-age=3600 + +permissions: + id-token: write # allows the JWT to be requested from GitHub's OIDC provider + contents: read # This is required for actions/checkout + +jobs: + validate-actor: + # Only allow to be deployed from tags and main branch + if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' + uses: ./.github/workflows/validate-actor.yml + secrets: + PAT: ${{ secrets.PAT }} + + update-cache-policy: + needs: validate-actor + name: Update cache control policy for SDK artifacts + 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: Determine the cache control policy + id: determine_policy + run: | + echo "cache_control_policy=${{ github.event.inputs.policy_type || inputs.policy_type }}" >> $GITHUB_ENV + + - name: Update cache control policy + run: | + # Get the number of CPU cores in the runner and leave one core free + num_cores=$(nproc --ignore=1 || echo 1) # Default to 1 if nproc is unavailable + # Use a factor to set the parallel jobs (e.g., number of cores or slightly lower) + parallel_jobs=$((num_cores * 2)) + echo "Detected $num_cores cores. Using $parallel_jobs parallel jobs." + + prefixes=("adobe-analytics-js" "v3" "v1.1") + + for prefix in "${prefixes[@]}"; do + echo "Processing prefix: $prefix" + + aws s3api list-objects --bucket ${{ secrets.AWS_PROD_S3_BUCKET_NAME }} --prefix "$prefix" --query "Contents[].Key" --output text | tr '\t' '\n' | \ + parallel --retries 10 -j "$parallel_jobs" "aws s3api copy-object \ + --bucket ${{ secrets.AWS_PROD_S3_BUCKET_NAME }} \ + --copy-source ${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/{} \ + --key {} \ + --metadata-directive REPLACE \ + --cache-control '${{ env.cache_control_policy }}'" + done + + diff --git a/.github/workflows/validate-actor.yml b/.github/workflows/validate-actor.yml new file mode 100644 index 0000000000..15981efc51 --- /dev/null +++ b/.github/workflows/validate-actor.yml @@ -0,0 +1,30 @@ +name: Validate Actor + +on: + workflow_call: + secrets: + PAT: + required: true + +jobs: + validate-actor: + runs-on: [self-hosted, Linux, X64] + steps: + - name: Validate if actor is allowed to trigger the workflow + env: + ORG_NAME: rudderlabs + TEAM_NAME: js-sdk + run: | + actor=${{ github.actor || github.triggering_actor }} + response=$(curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.PAT }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/orgs/${{ env.ORG_NAME }}/teams/${{ env.TEAM_NAME }}/memberships/$actor) + + if echo "$response" | grep -q '"state": "active"'; then + echo "$actor is a member of $TEAM_NAME team" + else + echo "$actor is NOT a member of $TEAM_NAME team" + exit 1 + fi diff --git a/package-lock.json b/package-lock.json index 649b274990..618586904e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.66.0", + "version": "3.70.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.66.0", + "version": "3.70.0", "hasInstallScript": true, "license": "Elastic-2.0", "workspaces": [ @@ -2153,12 +2153,13 @@ } }, "node_modules/@bundled-es-modules/cookie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", - "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", "dev": true, + "license": "ISC", "dependencies": { - "cookie": "^0.5.0" + "cookie": "^0.7.2" } }, "node_modules/@bundled-es-modules/statuses": { @@ -10285,10 +10286,11 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10503,10 +10505,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -25284,7 +25287,7 @@ }, "packages/analytics-js": { "name": "@rudderstack/analytics-js", - "version": "3.11.14", + "version": "3.11.16", "license": "Elastic-2.0", "dependencies": { "@preact/signals-core": "1.8.0", @@ -25298,7 +25301,7 @@ }, "packages/analytics-js-common": { "name": "@rudderstack/analytics-js-common", - "version": "3.14.12", + "version": "3.14.14", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25315,7 +25318,7 @@ }, "packages/analytics-js-cookies": { "name": "@rudderstack/analytics-js-cookies", - "version": "0.4.15", + "version": "0.4.17", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*" @@ -25324,7 +25327,7 @@ }, "packages/analytics-js-integrations": { "name": "@rudderstack/analytics-js-integrations", - "version": "3.11.11", + "version": "3.11.14", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25344,7 +25347,7 @@ }, "packages/analytics-js-plugins": { "name": "@rudderstack/analytics-js-plugins", - "version": "3.6.17", + "version": "3.6.20", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*", @@ -25358,7 +25361,7 @@ }, "packages/analytics-js-service-worker": { "name": "@rudderstack/analytics-js-service-worker", - "version": "3.2.15", + "version": "3.2.17", "license": "Elastic-2.0", "dependencies": { "@lukeed/uuid": "2.0.1", @@ -25379,7 +25382,7 @@ }, "packages/analytics-v1.1": { "name": "rudder-sdk-js", - "version": "2.48.40", + "version": "2.48.42", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js-common": "*" @@ -25388,7 +25391,7 @@ }, "packages/loading-scripts": { "name": "@rudderstack/analytics-js-loading-scripts", - "version": "3.0.57", + "version": "3.0.59", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*" @@ -25397,7 +25400,7 @@ }, "packages/sanity-suite": { "name": "@rudderstack/analytics-js-sanity-suite", - "version": "3.1.48", + "version": "3.1.50", "license": "Elastic-2.0", "dependencies": { "@rudderstack/analytics-js": "*", diff --git a/package.json b/package.json index 49b3d9c9a2..2e28b833d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-monorepo", - "version": "3.66.0", + "version": "3.70.0", "private": true, "description": "Monorepo for RudderStack Analytics JS SDK", "workspaces": [ diff --git a/packages/analytics-js-common/.size-limit.js b/packages/analytics-js-common/.size-limit.js index 0df666c22f..c6168f0bf8 100644 --- a/packages/analytics-js-common/.size-limit.js +++ b/packages/analytics-js-common/.size-limit.js @@ -6,6 +6,6 @@ module.exports = [ { name: 'Common - No bundling', path: 'dist/npm/**/*.js', - limit: '16.5 KiB', + limit: '17.2 KiB', }, ]; diff --git a/packages/analytics-js-common/CHANGELOG.md b/packages/analytics-js-common/CHANGELOG.md index 21b3a716c8..4ae08c68a5 100644 --- a/packages/analytics-js-common/CHANGELOG.md +++ b/packages/analytics-js-common/CHANGELOG.md @@ -2,6 +2,20 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.14.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.13...@rudderstack/analytics-js-common@3.14.14) (2024-12-17) + + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) + +## [3.14.13](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.12...@rudderstack/analytics-js-common@3.14.13) (2024-12-06) + + +### Bug Fixes + +* integration constants file type ([#1958](https://github.com/rudderlabs/rudder-sdk-js/issues/1958)) ([e0f6ff2](https://github.com/rudderlabs/rudder-sdk-js/commit/e0f6ff28f3b02d56e862e01d308653e2178eec43)) + ## [3.14.12](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.11...@rudderstack/analytics-js-common@3.14.12) (2024-11-22) diff --git a/packages/analytics-js-common/CHANGELOG_LATEST.md b/packages/analytics-js-common/CHANGELOG_LATEST.md index 4fb12d613c..883bb17a67 100644 --- a/packages/analytics-js-common/CHANGELOG_LATEST.md +++ b/packages/analytics-js-common/CHANGELOG_LATEST.md @@ -1,7 +1,7 @@ -## [3.14.12](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.11...@rudderstack/analytics-js-common@3.14.12) (2024-11-22) +## [3.14.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-common@3.14.13...@rudderstack/analytics-js-common@3.14.14) (2024-12-17) ### Bug Fixes -* revert temp utility ([2f60cae](https://github.com/rudderlabs/rudder-sdk-js/commit/2f60caeea0dc9944bf9434d5981952c8e85eef38)) +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) diff --git a/packages/analytics-js-common/__mocks__/.gitkeep b/packages/analytics-js-common/__mocks__/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/analytics-js-common/__mocks__/BufferQueue.ts b/packages/analytics-js-common/__mocks__/BufferQueue.ts new file mode 100644 index 0000000000..1b1fa142e7 --- /dev/null +++ b/packages/analytics-js-common/__mocks__/BufferQueue.ts @@ -0,0 +1,10 @@ +class BufferQueue { + items: T[] = []; + enqueue = jest.fn(); + dequeue = jest.fn(); + isEmpty = jest.fn(); + size = jest.fn(); + clear = jest.fn(); +} + +export { BufferQueue }; diff --git a/packages/analytics-js-common/__mocks__/ErrorHandler.ts b/packages/analytics-js-common/__mocks__/ErrorHandler.ts new file mode 100644 index 0000000000..4f5f91ac61 --- /dev/null +++ b/packages/analytics-js-common/__mocks__/ErrorHandler.ts @@ -0,0 +1,16 @@ +import type { IErrorHandler, PreLoadErrorData } from '../src/types/ErrorHandler'; +import { BufferQueue } from './BufferQueue'; + +// Mock all the methods of the ErrorHandler class +class ErrorHandler implements IErrorHandler { + onError = jest.fn(); + leaveBreadcrumb = jest.fn(); + notifyError = jest.fn(); + init = jest.fn(); + attachErrorListeners = jest.fn(); + errorBuffer = new BufferQueue(); +} + +const defaultErrorHandler = new ErrorHandler(); + +export { ErrorHandler, defaultErrorHandler }; diff --git a/packages/analytics-js-common/__mocks__/Logger.ts b/packages/analytics-js-common/__mocks__/Logger.ts new file mode 100644 index 0000000000..030653737e --- /dev/null +++ b/packages/analytics-js-common/__mocks__/Logger.ts @@ -0,0 +1,18 @@ +import type { ILogger } from '../src/types/Logger'; + +class Logger implements ILogger { + warn = jest.fn(); + log = jest.fn(); + error = jest.fn(); + info = jest.fn(); + debug = jest.fn(); + minLogLevel = 0; + scope = 'test scope'; + setMinLogLevel = jest.fn(); + setScope = jest.fn(); + logProvider = console; +} + +const defaultLogger = new Logger(); + +export { Logger, defaultLogger }; diff --git a/packages/analytics-js-common/__mocks__/Storage.ts b/packages/analytics-js-common/__mocks__/Storage.ts new file mode 100644 index 0000000000..ea3229762b --- /dev/null +++ b/packages/analytics-js-common/__mocks__/Storage.ts @@ -0,0 +1,116 @@ +import { cookie } from '../src/component-cookie'; +import type { Nullable } from '../src/types/Nullable'; +import type { IStorage } from '../src/types/Store'; +import store from 'storejs'; + +class LocalStorage implements IStorage { + keys = () => { + return store.keys(); + }; + isEnabled = true; + getItem = (key: string) => { + return store.get(key) ?? null; + }; + setItem = (key: string, value: any) => { + store.set(key, value); + }; + removeItem = (key: string) => store.remove(key); + clear = () => { + store.clear(); + }; + length = store.len(); + key = (idx: number): Nullable => { + return this.keys()[idx] ?? null; + }; +} + +/** + * A storage utility to retain values in memory via Storage interface + */ +class InMemoryStorage implements IStorage { + isEnabled = true; + length = 0; + data: Record = {}; + + setItem(key: string, value: any): any { + this.data[key] = value; + this.length = Object.keys(this.data).length; + return value; + } + + getItem(key: string): any { + if (key in this.data) { + return this.data[key]; + } + return null; + } + + removeItem(key: string) { + if (key in this.data) { + delete this.data[key]; + } + this.length = Object.keys(this.data).length; + return null; + } + + clear() { + this.data = {}; + this.length = 0; + } + + key(index: number): Nullable { + const curKeys = this.keys(); + return curKeys[index] ?? null; + } + + keys(): string[] { + return Object.keys(this.data); + } +} + +class CookieStorage implements IStorage { + keys = () => { + return Object.keys(cookie()); + }; + + isEnabled = true; + + getItem = (key: string) => { + const value = cookie(key); + return value ?? null; + }; + + setItem = (key: string, value: any) => { + cookie(key, value); + this.length = Object.keys(cookie()).length; + }; + + removeItem = (key: string) => { + const result = this.setItem(key, null); + this.length = Object.keys(cookie()).length; + return result; + }; + + clear = () => { + // Not implemented + }; + + length = Object.keys(cookie()).length; + + key = (idx: number): Nullable => { + const curKeys = this.keys(); + return curKeys[idx] ?? null; + }; +} + +const defaultCookieStorage = new CookieStorage(); + +export { CookieStorage, defaultCookieStorage }; + +const defaultInMemoryStorage = new InMemoryStorage(); + +export { InMemoryStorage, defaultInMemoryStorage }; + +const defaultLocalStorage = new LocalStorage(); + +export { LocalStorage, defaultLocalStorage }; diff --git a/packages/analytics-js-common/__mocks__/Store.ts b/packages/analytics-js-common/__mocks__/Store.ts new file mode 100644 index 0000000000..d5ee148bf9 --- /dev/null +++ b/packages/analytics-js-common/__mocks__/Store.ts @@ -0,0 +1,56 @@ +import type { IStore, IStoreConfig } from '../src/types/Store'; +import { defaultInMemoryStorage, defaultLocalStorage } from './Storage'; + +// Mock all the methods of the Store class + +class Store implements IStore { + constructor(config: IStoreConfig, engine?: any) { + this.id = config.id; + this.name = config.name; + this.isEncrypted = config.isEncrypted ?? false; + this.validKeys = config.validKeys ?? {}; + this.engine = engine ?? defaultLocalStorage; + this.originalEngine = this.engine; + } + id = 'test'; + name = 'test'; + isEncrypted = false; + validKeys: Record; + engine = defaultLocalStorage; + originalEngine = defaultLocalStorage; + createValidKey = (key: string) => { + return [this.name, this.id, key].join('.'); + }; + swapQueueStoreToInMemoryEngine = () => { + this.engine.keys().forEach((key: string) => { + const value = this.engine.getItem(key); + defaultInMemoryStorage.setItem(key, value); + }); + + this.engine = defaultInMemoryStorage; + }; + set = (key: string, value: any) => { + const validKey = this.createValidKey(key); + this.engine.setItem(validKey, value); + }; + get = (key: string) => { + const validKey = this.createValidKey(key); + return this.engine.getItem(validKey); + }; + remove = (key: string) => { + const validKey = this.createValidKey(key); + this.engine.removeItem(validKey); + }; + clear = () => { + this.engine.clear(); + }; + onError = jest.fn(); + crypto = jest.fn(); + encrypt = jest.fn(); + decrypt = jest.fn(); + getOriginalEngine = () => this.originalEngine; +} + +const defaultStore = new Store({ id: 'test', name: 'test' }); + +export { Store, defaultStore }; diff --git a/packages/analytics-js-common/__mocks__/StoreManager.ts b/packages/analytics-js-common/__mocks__/StoreManager.ts new file mode 100644 index 0000000000..da6498f147 --- /dev/null +++ b/packages/analytics-js-common/__mocks__/StoreManager.ts @@ -0,0 +1,32 @@ +import type { IStoreConfig, IStoreManager } from '../src/types/Store'; +import { defaultCookieStorage, defaultInMemoryStorage, defaultLocalStorage } from './Storage'; +import { defaultStore, Store } from './Store'; + +// Mock all the methods of the StoreManager class + +class StoreManager implements IStoreManager { + init = jest.fn(); + setStore = (config: IStoreConfig) => { + let storageEngine; + switch (config.type) { + case 'localStorage': + storageEngine = defaultLocalStorage; + break; + case 'cookieStorage': + storageEngine = defaultCookieStorage; + break; + case 'memoryStorage': + default: + storageEngine = defaultInMemoryStorage; + break; + } + + return new Store(config, storageEngine); + }; + getStore = jest.fn(() => defaultStore); + initializeStorageState = jest.fn(); +} + +const defaultStoreManager = new StoreManager(); + +export { StoreManager, defaultStoreManager }; diff --git a/packages/analytics-js-cookies/__tests__/component-cookie/index.test.ts b/packages/analytics-js-common/__tests__/component-cookie/index.test.ts similarity index 88% rename from packages/analytics-js-cookies/__tests__/component-cookie/index.test.ts rename to packages/analytics-js-common/__tests__/component-cookie/index.test.ts index 01ad344a3b..74000b8638 100644 --- a/packages/analytics-js-cookies/__tests__/component-cookie/index.test.ts +++ b/packages/analytics-js-common/__tests__/component-cookie/index.test.ts @@ -1,11 +1,5 @@ -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import { cookie } from '../../src/component-cookie'; - -class MockLogger implements ILogger { - error = jest.fn(); -} - -const mockLoggerInstance = new MockLogger(); +import { defaultLogger } from '../../__mocks__/Logger'; beforeEach(() => { const allCookies = cookie(); @@ -38,9 +32,9 @@ describe('cookies', () => { cookie('bad', '%'); cookie('bad', null); - cookie('bad', '\ud83d', undefined, mockLoggerInstance); - expect(mockLoggerInstance.error).toHaveBeenCalledTimes(1); - expect(mockLoggerInstance.error).toHaveBeenNthCalledWith( + cookie('bad', '\ud83d', undefined, defaultLogger); + expect(defaultLogger.error).toHaveBeenCalledTimes(1); + expect(defaultLogger.error).toHaveBeenNthCalledWith( 1, 'Failed to encode the cookie data.', expect.any(Error), @@ -102,6 +96,7 @@ describe('cookie()', () => { it('should return all cookies if the first argument is not a valid string', () => { cookie('name', 'loki'); cookie('species', 'ferret'); + // @ts-expect-error testing invalid input const obj = cookie(false); expect(2).toEqual(Object.keys(obj).length); diff --git a/packages/analytics-js-common/package.json b/packages/analytics-js-common/package.json index aed4f1588f..74117d65cf 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.12", + "version": "3.14.14", "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 760fd1a074..cd3ac0e7f4 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.12", - "title": "@rudderstack/analytics-js-common@3.14.12", - "discussion-category": "@rudderstack/analytics-js-common@3.14.12", + "tag": "@rudderstack/analytics-js-common@3.14.14", + "title": "@rudderstack/analytics-js-common@3.14.14", + "discussion-category": "@rudderstack/analytics-js-common@3.14.14", "notesFile": "./packages/analytics-js-common/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-cookies/src/component-cookie/README.md b/packages/analytics-js-common/src/component-cookie/README.md similarity index 100% rename from packages/analytics-js-cookies/src/component-cookie/README.md rename to packages/analytics-js-common/src/component-cookie/README.md diff --git a/packages/analytics-js-cookies/src/component-cookie/index.ts b/packages/analytics-js-common/src/component-cookie/index.ts similarity index 89% rename from packages/analytics-js-cookies/src/component-cookie/index.ts rename to packages/analytics-js-common/src/component-cookie/index.ts index e4dcaf1a85..70388e6263 100644 --- a/packages/analytics-js-cookies/src/component-cookie/index.ts +++ b/packages/analytics-js-common/src/component-cookie/index.ts @@ -1,7 +1,7 @@ -import { isNull } from '@rudderstack/analytics-js-common/utilities/checks'; -import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; -import type { CookieOptions } from '@rudderstack/analytics-js-common/types/Storage'; -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; +import { isNull } from '../utilities/checks'; +import type { Nullable } from '../types/Nullable'; +import type { CookieOptions } from '../types/Storage'; +import type { ILogger } from '../types/Logger'; import { COOKIE_DATA_ENCODING_ERROR } from '../constants/logMessages'; /** @@ -119,7 +119,7 @@ const cookie = function ( value?: Nullable, options?: CookieOptions, logger?: ILogger, -): void | any { +): any { switch (arguments.length) { case 4: case 3: diff --git a/packages/analytics-js-common/src/constants/integrations/ActiveCampaign/constants.js b/packages/analytics-js-common/src/constants/integrations/ActiveCampaign/constants.ts similarity index 100% rename from packages/analytics-js-common/src/constants/integrations/ActiveCampaign/constants.js rename to packages/analytics-js-common/src/constants/integrations/ActiveCampaign/constants.ts diff --git a/packages/analytics-js-common/src/constants/integrations/XPixel/constants.js b/packages/analytics-js-common/src/constants/integrations/XPixel/constants.ts similarity index 98% rename from packages/analytics-js-common/src/constants/integrations/XPixel/constants.js rename to packages/analytics-js-common/src/constants/integrations/XPixel/constants.ts index 6f76bf94c1..7a630dc8b8 100644 --- a/packages/analytics-js-common/src/constants/integrations/XPixel/constants.js +++ b/packages/analytics-js-common/src/constants/integrations/XPixel/constants.ts @@ -5,7 +5,6 @@ const DISPLAY_NAME = 'XPixel'; const DISPLAY_NAME_TO_DIR_NAME_MAP = { [DISPLAY_NAME]: DIR_NAME }; const CNameMapping = { [NAME]: NAME, - XPIXEL: NAME, XPixel: NAME, Xpixel: NAME, xpixel: NAME, diff --git a/packages/analytics-js-common/src/constants/logMessages.ts b/packages/analytics-js-common/src/constants/logMessages.ts index 83ef4fa826..02bab212be 100644 --- a/packages/analytics-js-common/src/constants/logMessages.ts +++ b/packages/analytics-js-common/src/constants/logMessages.ts @@ -17,6 +17,8 @@ const JSON_STRINGIFY_WARNING = `Failed to convert the value to a JSON string.`; const BAD_DATA_WARNING = (context: string, key: string): string => `${context}${LOG_CONTEXT_SEPARATOR}A bad data (like circular reference, BigInt) has been detected in the object and the property "${key}" has been dropped from the output.`; +const COOKIE_DATA_ENCODING_ERROR = `Failed to encode the cookie data.`; + export { LOG_CONTEXT_SEPARATOR, SCRIPT_ALREADY_EXISTS_ERROR, @@ -25,4 +27,5 @@ export { CIRCULAR_REFERENCE_WARNING, JSON_STRINGIFY_WARNING, BAD_DATA_WARNING, + COOKIE_DATA_ENCODING_ERROR, }; diff --git a/packages/analytics-js-common/src/types/PluginEngine.ts b/packages/analytics-js-common/src/types/PluginEngine.ts index 7b12957380..4c85a2a123 100644 --- a/packages/analytics-js-common/src/types/PluginEngine.ts +++ b/packages/analytics-js-common/src/types/PluginEngine.ts @@ -17,7 +17,7 @@ export interface ExtensionPlugin { | string | (() => void) | ExtensionPoint - | ((...args: any[]) => unknown | void) + | ((...args: any[]) => unknown) | string[] | undefined; } diff --git a/packages/analytics-js-cookies/CHANGELOG.md b/packages/analytics-js-cookies/CHANGELOG.md index 41eac7482c..c99cfe1289 100644 --- a/packages/analytics-js-cookies/CHANGELOG.md +++ b/packages/analytics-js-cookies/CHANGELOG.md @@ -2,6 +2,21 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [0.4.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.16...@rudderstack/analytics-js-cookies@0.4.17) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.14` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) + +## [0.4.16](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.15...@rudderstack/analytics-js-cookies@0.4.16) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.13` ## [0.4.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.14...@rudderstack/analytics-js-cookies@0.4.15) (2024-11-22) ### Dependency Updates diff --git a/packages/analytics-js-cookies/CHANGELOG_LATEST.md b/packages/analytics-js-cookies/CHANGELOG_LATEST.md index c2e1cbf59c..e0bf1f8752 100644 --- a/packages/analytics-js-cookies/CHANGELOG_LATEST.md +++ b/packages/analytics-js-cookies/CHANGELOG_LATEST.md @@ -1,5 +1,10 @@ -## [0.4.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.14...@rudderstack/analytics-js-cookies@0.4.15) (2024-11-22) +## [0.4.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-cookies@0.4.16...@rudderstack/analytics-js-cookies@0.4.17) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.12` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) + diff --git a/packages/analytics-js-cookies/package.json b/packages/analytics-js-cookies/package.json index 09682ed28d..da84dd7c07 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.15", + "version": "0.4.17", "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 c873efd38c..dc1a14b3b2 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.15", - "title": "@rudderstack/analytics-js-cookies@0.4.15", - "discussion-category": "@rudderstack/analytics-js-cookies@0.4.15", + "tag": "@rudderstack/analytics-js-cookies@0.4.17", + "title": "@rudderstack/analytics-js-cookies@0.4.17", + "discussion-category": "@rudderstack/analytics-js-cookies@0.4.17", "notesFile": "./packages/analytics-js-cookies/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-cookies/src/cookiesUtilities.ts b/packages/analytics-js-cookies/src/cookiesUtilities.ts index 8d7254b1bf..11471b956d 100644 --- a/packages/analytics-js-cookies/src/cookiesUtilities.ts +++ b/packages/analytics-js-cookies/src/cookiesUtilities.ts @@ -3,8 +3,8 @@ import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import { isNull, isNullOrUndefined } from '@rudderstack/analytics-js-common/utilities/checks'; import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; import { stringifyWithoutCircular } from '@rudderstack/analytics-js-common/utilities/json'; +import { cookie } from '@rudderstack/analytics-js-common/component-cookie'; import { COOKIE_KEYS, ENCRYPTION_PREFIX_V3 } from './constants/cookies'; -import { cookie } from './component-cookie'; const getEncryptedValueInternal = ( value: string | ApiObject, diff --git a/packages/analytics-js-integrations/CHANGELOG.md b/packages/analytics-js-integrations/CHANGELOG.md index af6a54dfef..7d36db02c0 100644 --- a/packages/analytics-js-integrations/CHANGELOG.md +++ b/packages/analytics-js-integrations/CHANGELOG.md @@ -2,6 +2,33 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.11.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.11.13...@rudderstack/analytics-js-integrations@3.11.14) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.14` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) + +## [3.11.13](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.11.12...@rudderstack/analytics-js-integrations@3.11.13) (2024-12-09) + + +### Bug Fixes + +* revert ms-clarity identify promise handling ([aa92760](https://github.com/rudderlabs/rudder-sdk-js/commit/aa92760087c22c61c4b6e703a2759cfc46f5576b)) + +## [3.11.12](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.11.11...@rudderstack/analytics-js-integrations@3.11.12) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.13` + +### Bug Fixes + +* microsoft clarity identify error handling ([#1948](https://github.com/rudderlabs/rudder-sdk-js/issues/1948)) ([33ac767](https://github.com/rudderlabs/rudder-sdk-js/commit/33ac7677c8717ac3ce45d34b7efae720b95b1432)) + ## [3.11.11](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.11.10...@rudderstack/analytics-js-integrations@3.11.11) (2024-11-22) ### Dependency Updates diff --git a/packages/analytics-js-integrations/CHANGELOG_LATEST.md b/packages/analytics-js-integrations/CHANGELOG_LATEST.md index 2cc8d1e662..6289a08500 100644 --- a/packages/analytics-js-integrations/CHANGELOG_LATEST.md +++ b/packages/analytics-js-integrations/CHANGELOG_LATEST.md @@ -1,5 +1,10 @@ -## [3.11.11](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.11.10...@rudderstack/analytics-js-integrations@3.11.11) (2024-11-22) +## [3.11.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-integrations@3.11.13...@rudderstack/analytics-js-integrations@3.11.14) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.12` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) + diff --git a/packages/analytics-js-integrations/__tests__/integrations/Podsights/utils.test.js b/packages/analytics-js-integrations/__tests__/integrations/Podsights/utils.test.js index fe0cd68e42..efceb36c20 100644 --- a/packages/analytics-js-integrations/__tests__/integrations/Podsights/utils.test.js +++ b/packages/analytics-js-integrations/__tests__/integrations/Podsights/utils.test.js @@ -1,4 +1,4 @@ -import { LINE_ITEMS_CONFIG } from '@rudderstack/analytics-js-common/src/constants/integrations/CommonIntegrationsConstant/constants'; +import { LINE_ITEMS_CONFIG } from '@rudderstack/analytics-js-common/constants/integrations/CommonIntegrationsConstant/constants'; import { payloadBuilder, payloadBuilderInList } from '../../../src/integrations/Podsights/utils'; describe('payloadBuilder', () => { diff --git a/packages/analytics-js-integrations/package.json b/packages/analytics-js-integrations/package.json index a7cfbc78b2..78b6cb4783 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.11.11", + "version": "3.11.14", "private": true, "description": "RudderStack JavaScript SDK device mode integrations", "main": "dist/npm/modern/cjs/index.js", @@ -74,12 +74,11 @@ "serve": "npx serve ./dist -p 3005 --cors", "package": "exit 0", "release": "exit 0", - "build:integration": "rollup -c --environment PROD_DEBUG,ENV:prod,UGLIFY,INTG_NAME:AdobeAnalytics", - "build:integration:dev": "rollup -c --environment PROD_DEBUG,INTG_NAME:AdobeAnalytics", + "build:integration": "rollup -c --environment PROD_DEBUG,ENV:prod,UGLIFY", + "build:integration:modern": "BROWSERSLIST_ENV=modern rollup -c --environment PROD_DEBUG,ENV:prod,UGLIFY", "build:integration:bundle-size": "VISUALIZER=true npm run build:integration", "build:integration:cli": "rollup -c --environment VERSION:$npm_package_version,PROD_DEBUG,ENV:prod,UGLIFY,INTG_NAME:$npm_config_intg", "build:integration:bundle-size:cli": "rollup -c --environment VERSION:$npm_package_version,VISUALIZER:true,PROD_DEBUG,ENV:prod,UGLIFY,INTG_NAME:$npm_config_intg", - "build:integration:dev:cli": "rollup -c --environment PROD_DEBUG,INTG_NAME:$npm_config_intg", "build:integration:all": "node -r esm ./scripts/integrationBuildScript.js", "build:integration:all:modern": "BROWSERSLIST_ENV=modern npm run build:integration:all", "build:integration:all:bundle-size": "VISUALIZER=true npm run build:integration:all", diff --git a/packages/analytics-js-integrations/project.json b/packages/analytics-js-integrations/project.json index b675c16697..15a65d0257 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.11.11", - "title": "@rudderstack/analytics-js-integrations@3.11.11", - "discussion-category": "@rudderstack/analytics-js-integrations@3.11.11", + "tag": "@rudderstack/analytics-js-integrations@3.11.14", + "title": "@rudderstack/analytics-js-integrations@3.11.14", + "discussion-category": "@rudderstack/analytics-js-integrations@3.11.14", "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 c9390e509a..9a4a17473c 100644 --- a/packages/analytics-js-plugins/CHANGELOG.md +++ b/packages/analytics-js-plugins/CHANGELOG.md @@ -2,6 +2,30 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.6.20](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.19...@rudderstack/analytics-js-plugins@3.6.20) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.14` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.17` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) +* separator and make changes in bugsnag plugin ([b69347c](https://github.com/rudderlabs/rudder-sdk-js/commit/b69347cd9bbf3a395b2f557f8219287900ceca5a)) + +## [3.6.19](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.18...@rudderstack/analytics-js-plugins@3.6.19) (2024-12-13) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.15` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.16` +## [3.6.18](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.17...@rudderstack/analytics-js-plugins@3.6.18) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.13` +* `@rudderstack/analytics-js` updated to version `3.11.14` ## [3.6.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.16...@rudderstack/analytics-js-plugins@3.6.17) (2024-11-30) ### Dependency Updates diff --git a/packages/analytics-js-plugins/CHANGELOG_LATEST.md b/packages/analytics-js-plugins/CHANGELOG_LATEST.md index f162ea230f..6abd863f11 100644 --- a/packages/analytics-js-plugins/CHANGELOG_LATEST.md +++ b/packages/analytics-js-plugins/CHANGELOG_LATEST.md @@ -1,6 +1,12 @@ -## [3.6.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.16...@rudderstack/analytics-js-plugins@3.6.17) (2024-11-30) +## [3.6.20](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-plugins@3.6.19...@rudderstack/analytics-js-plugins@3.6.20) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js` updated to version `3.11.13` -* `@rudderstack/analytics-js-cookies` updated to version `0.4.15` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.17` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) +* separator and make changes in bugsnag plugin ([b69347c](https://github.com/rudderlabs/rudder-sdk-js/commit/b69347cd9bbf3a395b2f557f8219287900ceca5a)) + diff --git a/packages/analytics-js-plugins/__fixtures__/fixtures.ts b/packages/analytics-js-plugins/__fixtures__/fixtures.ts index cf149a7c1e..4f1a256543 100644 --- a/packages/analytics-js-plugins/__fixtures__/fixtures.ts +++ b/packages/analytics-js-plugins/__fixtures__/fixtures.ts @@ -1,3 +1,5 @@ +import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; + const sdkName = 'RudderLabs JavaScript SDK'; const rudderEventPage = { properties: { @@ -77,7 +79,7 @@ const rudderEventPage = { userId: 'customUserID', anonymousId: '1901c08d-d505-41cd-81a6-060457fad648', event: null, -}; +} as unknown as RudderEvent; const errorMessage = 'Invalid request payload'; diff --git a/packages/analytics-js-plugins/__fixtures__/msw.handlers.js b/packages/analytics-js-plugins/__fixtures__/msw.handlers.js index 292c3e773c..130000bd57 100644 --- a/packages/analytics-js-plugins/__fixtures__/msw.handlers.js +++ b/packages/analytics-js-plugins/__fixtures__/msw.handlers.js @@ -40,7 +40,7 @@ const handlers = [ status: 500, }); }), - http.get(`https://asdf.com/bugsnag.min.js`, () => { + http.get(`__RS_BUGSNAG_SDK_URL__`, () => { return new HttpResponse(errorMessage, { status: 404, }); diff --git a/packages/analytics-js-plugins/__mocks__/.gitkeep b/packages/analytics-js-plugins/__mocks__/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/analytics-js-plugins/__mocks__/HttpClient.ts b/packages/analytics-js-plugins/__mocks__/HttpClient.ts new file mode 100644 index 0000000000..46a62d81f8 --- /dev/null +++ b/packages/analytics-js-plugins/__mocks__/HttpClient.ts @@ -0,0 +1,17 @@ +import type { IErrorHandler } from '@rudderstack/analytics-js-common/types/ErrorHandler'; +import type { IHttpClient } from '@rudderstack/analytics-js-common/types/HttpClient'; +import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; + +class HttpClient implements IHttpClient { + errorHandler?: IErrorHandler; + logger?: ILogger; + hasErrorHandler = false; + getData = jest.fn(); + getAsyncData = jest.fn(); + setAuthHeader = jest.fn(); + resetAuthHeader = jest.fn(); +} + +const defaultHttpClient = new HttpClient(); + +export { HttpClient, defaultHttpClient }; diff --git a/packages/analytics-js-plugins/__mocks__/PluginEngine.ts b/packages/analytics-js-plugins/__mocks__/PluginEngine.ts new file mode 100644 index 0000000000..7132eedcdb --- /dev/null +++ b/packages/analytics-js-plugins/__mocks__/PluginEngine.ts @@ -0,0 +1,20 @@ +import type { IPluginEngine } from '@rudderstack/analytics-js-common/types/PluginEngine'; + +class PluginEngine implements IPluginEngine { + // mock all the properties and methods of the PluginEngine class + plugins: any[] = []; + byName: any = {}; + cache: any = {}; + config: any = { throws: true }; + register = jest.fn(); + unregister = jest.fn(); + getPlugin = jest.fn(); + getPlugins = jest.fn(); + invoke = jest.fn(); + invokeSingle = jest.fn(); + invokeMultiple = jest.fn(); +} + +const defaultPluginEngine = new PluginEngine(); + +export { PluginEngine, defaultPluginEngine }; diff --git a/packages/analytics-js-plugins/__mocks__/PluginsManager.ts b/packages/analytics-js-plugins/__mocks__/PluginsManager.ts new file mode 100644 index 0000000000..9229ae2170 --- /dev/null +++ b/packages/analytics-js-plugins/__mocks__/PluginsManager.ts @@ -0,0 +1,18 @@ +import type { IPluginEngine } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import type { IPluginsManager } from '@rudderstack/analytics-js-common/types/PluginsManager'; +import { defaultPluginEngine } from './PluginEngine'; + +class PluginsManager implements IPluginsManager { + // mock all the properties and methods of the PluginsManager class + engine: IPluginEngine = defaultPluginEngine; + init = jest.fn(); + attachEffects = jest.fn(); + setActivePlugins = jest.fn(); + invokeMultiple = jest.fn(); + invokeSingle = jest.fn(); + register = jest.fn(); +} + +const defaultPluginsManager = new PluginsManager(); + +export { PluginsManager, defaultPluginsManager }; diff --git a/packages/analytics-js-plugins/__mocks__/state.ts b/packages/analytics-js-plugins/__mocks__/state.ts new file mode 100644 index 0000000000..a53a58ebbe --- /dev/null +++ b/packages/analytics-js-plugins/__mocks__/state.ts @@ -0,0 +1,208 @@ +import { signal } from '@preact/signals-core'; +import type { ApplicationState } from '@rudderstack/analytics-js-common/types/ApplicationState'; +import type { PluginName } from '@rudderstack/analytics-js-common/types/PluginsManager'; +import { clone } from 'ramda'; + +const defaultStateValues: ApplicationState = { + capabilities: { + isOnline: signal(true), + storage: { + isLocalStorageAvailable: signal(false), + isCookieStorageAvailable: signal(false), + isSessionStorageAvailable: signal(false), + }, + isBeaconAvailable: signal(false), + isLegacyDOM: signal(false), + isUaCHAvailable: signal(false), + isCryptoAvailable: signal(false), + isIE11: signal(false), + isAdBlocked: signal(false), + }, + consents: { + enabled: signal(false), + initialized: signal(false), + data: signal({}), + activeConsentManagerPluginName: signal(undefined), + preConsent: signal({ enabled: false }), + postConsent: signal({}), + resolutionStrategy: signal('and'), + provider: signal(undefined), + metadata: signal(undefined), + }, + context: { + app: signal({ + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: 'dev-snapshot', + installType: 'cdn', + }), + traits: signal(null), + library: signal({ + name: 'RudderLabs JavaScript SDK', + version: 'dev-snapshot', + snippetVersion: (globalThis as typeof window).RudderSnippetVersion, + }), + userAgent: signal(''), + device: signal(null), + network: signal(null), + os: signal({ + name: '', + version: '', + }), + locale: signal(null), + screen: signal({ + density: 0, + width: 0, + height: 0, + innerWidth: 0, + innerHeight: 0, + }), + 'ua-ch': signal(undefined), + timezone: signal(undefined), + }, + eventBuffer: { + toBeProcessedArray: signal([]), + readyCallbacksArray: signal([]), + }, + lifecycle: { + activeDataplaneUrl: signal(undefined), + integrationsCDNPath: signal(undefined), + pluginsCDNPath: signal(undefined), + sourceConfigUrl: signal(undefined), + status: signal(undefined), + initialized: signal(false), + logLevel: signal('ERROR'), + loaded: signal(false), + readyCallbacks: signal([]), + writeKey: signal(undefined), + dataPlaneUrl: signal(undefined), + }, + loadOptions: signal({ + logLevel: 'ERROR', + configUrl: 'https://api.rudderstack.com', + loadIntegration: true, + sessions: { + autoTrack: true, + timeout: 30 * 60 * 1000, + }, + sameSiteCookie: 'Lax', + polyfillIfRequired: true, + integrations: { All: true }, + useBeacon: false, + beaconQueueOptions: {}, + destinationsQueueOptions: {}, + queueOptions: {}, + lockIntegrationsVersion: false, + lockPluginsVersion: false, + uaChTrackLevel: 'none', + plugins: [], + useGlobalIntegrationsConfigInEvents: false, + bufferDataPlaneEventsUntilReady: false, + dataPlaneEventsBufferTimeout: 1000, + storage: { + encryption: { + version: 'v3', + }, + migrate: true, + cookie: {}, + }, + sendAdblockPageOptions: {}, + useServerSideCookies: false, + }), + metrics: { + retries: signal(0), + dropped: signal(0), + sent: signal(0), + queued: signal(0), + triggered: signal(0), + metricsServiceUrl: signal(undefined), + }, + nativeDestinations: { + configuredDestinations: signal([]), + activeDestinations: signal([]), + loadOnlyIntegrations: signal({}), + failedDestinations: signal([]), + loadIntegration: signal(true), + initializedDestinations: signal([]), + clientDestinationsReady: signal(false), + integrationsConfig: signal({}), + }, + plugins: { + ready: signal(false), + loadedPlugins: signal([]), + failedPlugins: signal([]), + pluginsToLoadFromConfig: signal([]), + activePlugins: signal([]), + totalPluginsToLoad: signal(0), + }, + reporting: { + isErrorReportingEnabled: signal(false), + isMetricsReportingEnabled: signal(false), + isErrorReportingPluginLoaded: signal(false), + breadcrumbs: signal([]), + }, + session: { + userId: signal(''), + userTraits: signal({}), + anonymousId: signal(''), + groupId: signal(''), + groupTraits: signal({}), + initialReferrer: signal(''), + initialReferringDomain: signal(''), + sessionInfo: signal({}), + authToken: signal(''), + }, + source: signal({ + id: 'dummy-source-id', + workspaceId: 'dummy-workspace-id', + }), + storage: { + encryptionPluginName: signal(undefined), + migrate: signal(false), + type: signal(undefined), + cookie: signal(undefined), + entries: signal({}), + trulyAnonymousTracking: signal(false), + }, + serverCookies: { + isEnabledServerSideCookies: signal(false), + dataServiceUrl: signal(undefined), + }, + dataPlaneEvents: { + eventsQueuePluginName: signal(undefined), + deliveryEnabled: signal(true), // Delivery should always happen + }, + autoTrack: { + enabled: signal(false), + pageLifecycle: { + enabled: signal(false), + visitId: signal(undefined), + pageLoadedTimestamp: signal(undefined), + }, + }, +}; + +const state: ApplicationState = { + ...clone(defaultStateValues), +}; + +const resetState = () => { + state.capabilities = clone(defaultStateValues.capabilities); + state.consents = clone(defaultStateValues.consents); + state.context = clone(defaultStateValues.context); + state.eventBuffer = clone(defaultStateValues.eventBuffer); + state.lifecycle = clone(defaultStateValues.lifecycle); + state.loadOptions = clone(defaultStateValues.loadOptions); + state.metrics = clone(defaultStateValues.metrics); + state.nativeDestinations = clone(defaultStateValues.nativeDestinations); + state.plugins = clone(defaultStateValues.plugins); + state.reporting = clone(defaultStateValues.reporting); + state.session = clone(defaultStateValues.session); + state.source = clone(defaultStateValues.source); + state.storage = clone(defaultStateValues.storage); + state.serverCookies = clone(defaultStateValues.serverCookies); + state.dataPlaneEvents = clone(defaultStateValues.dataPlaneEvents); + state.autoTrack = clone(defaultStateValues.autoTrack); +}; + +export { state, resetState }; diff --git a/packages/analytics-js-plugins/__tests__/bugsnag/index.test.ts b/packages/analytics-js-plugins/__tests__/bugsnag/index.test.ts index 4c6674bc45..1064d9ea29 100644 --- a/packages/analytics-js-plugins/__tests__/bugsnag/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/bugsnag/index.test.ts @@ -1,33 +1,10 @@ -import { signal } from '@preact/signals-core'; -import { clone } from 'ramda'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; import { Bugsnag } from '../../src/bugsnag'; import * as bugsnagConstants from '../../src/bugsnag/constants'; +import { resetState, state } from '../../__mocks__/state'; describe('Plugin - Bugsnag', () => { - const originalState = { - plugins: { - loadedPlugins: signal([]), - }, - lifecycle: { - writeKey: signal('dummy-write-key'), - }, - source: signal({ - id: 'test-source-id', - }), - context: { - app: signal({ - name: 'test-app', - namespace: 'test-namespace', - version: '1.0.0', - installType: 'npm', - }), - }, - }; - - let state: any; - const origApiKey = bugsnagConstants.API_KEY; - const origMaxSDKWait = bugsnagConstants.MAX_WAIT_FOR_SDK_LOAD_MS; const mountBugsnagSDK = () => { (window as any).bugsnag = jest.fn(() => ({ @@ -39,21 +16,28 @@ describe('Plugin - Bugsnag', () => { }; beforeEach(() => { - state = clone(originalState); + resetState(); delete (window as any).bugsnag; - bugsnagConstants.API_KEY = origApiKey; - bugsnagConstants.MAX_WAIT_FOR_SDK_LOAD_MS = origMaxSDKWait; + + Object.defineProperty(bugsnagConstants, 'API_KEY', { + value: origApiKey, + writable: true, + }); }); it('should add Bugsnag plugin in the loaded plugin list', () => { - Bugsnag().initialize(state); + Bugsnag().initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('Bugsnag')).toBe(true); }); it('should reject the promise if the Api Key is not valid', async () => { - bugsnagConstants.API_KEY = '{{ dummy api key }}'; + Object.defineProperty(bugsnagConstants, 'API_KEY', { + value: '{{ dummy api key }}', + writable: true, + }); - const pluginInitPromise = Bugsnag().errorReportingProvider.init(); + // eslint-disable-next-line @typescript-eslint/dot-notation + const pluginInitPromise = (Bugsnag().errorReportingProvider as ExtensionPoint)?.init?.(); await expect(pluginInitPromise).rejects.toThrow( 'The Bugsnag API key ({{ dummy api key }}) is invalid or not provided.', @@ -61,29 +45,45 @@ describe('Plugin - Bugsnag', () => { }); it('should reject the promise if the Bugsnag client could not be initialized', async () => { - bugsnagConstants.MAX_WAIT_FOR_SDK_LOAD_MS = 1000; + jest.useFakeTimers(); + jest.setSystemTime(0); const mockExtSrcLoader = { loadJSFile: jest.fn(() => Promise.resolve()), }; - const pluginInitPromise = Bugsnag().errorReportingProvider.init(state, mockExtSrcLoader); + const pluginInitPromise = (Bugsnag().errorReportingProvider as ExtensionPoint)?.init?.( + state, + mockExtSrcLoader, + ); + + // Advance timers to trigger the timeout + jest.advanceTimersByTime(10000); await expect(pluginInitPromise).rejects.toThrow( - 'A timeout 1000 ms occurred while trying to load the Bugsnag SDK.', + 'A timeout 10000 ms occurred while trying to load the Bugsnag SDK.', ); + + jest.useRealTimers(); }); it('should initialize the Bugsnag SDK and return the client instance', async () => { - setTimeout(() => { - mountBugsnagSDK(); - }, 500); + jest.useFakeTimers(); + jest.setSystemTime(0); const mockExtSrcLoader = { loadJSFile: jest.fn(() => Promise.resolve()), }; - const pluginInitPromise = Bugsnag().errorReportingProvider.init(state, mockExtSrcLoader); + const pluginInitPromise = (Bugsnag().errorReportingProvider as ExtensionPoint)?.init?.( + state, + mockExtSrcLoader, + ); + + jest.advanceTimersByTime(1); + mountBugsnagSDK(); + + jest.runAllTimers(); await expect(pluginInitPromise).resolves.toBeDefined(); }); @@ -93,7 +93,7 @@ describe('Plugin - Bugsnag', () => { const mockError = new Error('Test Error'); - Bugsnag().errorReportingProvider.notify(bsClient, mockError); + (Bugsnag().errorReportingProvider as ExtensionPoint)?.notify?.(bsClient, mockError); expect(bsClient.notify).toHaveBeenCalledWith(mockError); }); @@ -103,7 +103,7 @@ describe('Plugin - Bugsnag', () => { const mockMessage = 'Test Breadcrumb'; - Bugsnag().errorReportingProvider.breadcrumb(bsClient, mockMessage); + (Bugsnag().errorReportingProvider as ExtensionPoint)?.breadcrumb?.(bsClient, mockMessage); expect(bsClient.leaveBreadcrumb).toHaveBeenCalledWith(mockMessage); }); diff --git a/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts b/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts index 6d58890430..855073690f 100644 --- a/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/bugsnag/utils.test.ts @@ -1,10 +1,7 @@ -/* eslint-disable max-classes-per-file */ import { signal } from '@preact/signals-core'; import { ExternalSrcLoader } from '@rudderstack/analytics-js-common/services/ExternalSrcLoader'; -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; -import type { IErrorHandler } from '@rudderstack/analytics-js-common/types/ErrorHandler'; -import * as timeouts from '@rudderstack/analytics-js-common/src/constants/timeouts'; -import type { ApplicationState } from '@rudderstack/analytics-js-common/types/ApplicationState'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; import * as bugsnagConstants from '../../src/bugsnag/constants'; import { isApiKeyValid, @@ -20,54 +17,171 @@ import { } from '../../src/bugsnag/utils'; import { server } from '../../__fixtures__/msw.server'; import type { BugsnagLib } from '../../src/types/plugins'; - -let state: ApplicationState; +import { resetState, state } from '../../__mocks__/state'; beforeEach(() => { window.RudderSnippetVersion = '3.0.0'; - state = { - context: { - app: signal({ - name: 'test-app', - namespace: 'test-namespace', - version: '1.0.0', - installType: 'npm', - }), - }, - source: signal({ - id: 'dummy-source-id', - }), - lifecycle: { - writeKey: signal('dummy-write-key'), - }, - }; + resetState(); + state.lifecycle.writeKey.value = 'dummy-write-key'; }); afterEach(() => { window.RudderSnippetVersion = undefined; }); -describe('Bugsnag utilities', () => { - class MockLogger implements ILogger { - warn = jest.fn(); - log = jest.fn(); - error = jest.fn(); - info = jest.fn(); - debug = jest.fn(); - minLogLevel = 0; - scope = 'test scope'; - setMinLogLevel = jest.fn(); - setScope = jest.fn(); - logProvider = console; - } - - class MockErrorHandler implements IErrorHandler { - init = jest.fn(); - onError = jest.fn(); - leaveBreadcrumb = jest.fn(); - notifyError = jest.fn(); - } +const DEFAULT_STATE_DATA = { + autoTrack: { + enabled: false, + pageLifecycle: { + enabled: false, + }, + }, + capabilities: { + isAdBlocked: false, + isBeaconAvailable: false, + isCryptoAvailable: false, + isIE11: false, + isLegacyDOM: false, + isOnline: true, + isUaCHAvailable: false, + storage: { + isCookieStorageAvailable: false, + isLocalStorageAvailable: false, + isSessionStorageAvailable: false, + }, + }, + consents: { + data: {}, + enabled: false, + initialized: false, + postConsent: {}, + preConsent: { + enabled: false, + }, + resolutionStrategy: 'and', + }, + context: { + app: { + installType: 'cdn', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: 'dev-snapshot', + }, + device: null, + library: { + name: 'RudderLabs JavaScript SDK', + version: 'dev-snapshot', + }, + locale: null, + network: null, + os: { + name: '', + version: '', + }, + screen: { + density: 0, + height: 0, + innerHeight: 0, + innerWidth: 0, + width: 0, + }, + userAgent: '', + }, + dataPlaneEvents: { + deliveryEnabled: true, + }, + lifecycle: { + initialized: false, + loaded: false, + logLevel: 'ERROR', + readyCallbacks: [], + writeKey: 'dummy-write-key', + }, + loadOptions: { + beaconQueueOptions: {}, + bufferDataPlaneEventsUntilReady: false, + configUrl: 'https://api.rudderstack.com', + dataPlaneEventsBufferTimeout: 1000, + destinationsQueueOptions: {}, + integrations: { + All: true, + }, + loadIntegration: true, + lockIntegrationsVersion: false, + lockPluginsVersion: false, + logLevel: 'ERROR', + plugins: [], + polyfillIfRequired: true, + queueOptions: {}, + sameSiteCookie: 'Lax', + sendAdblockPageOptions: {}, + sessions: { + autoTrack: true, + timeout: 1800000, + }, + storage: { + cookie: {}, + encryption: { + version: 'v3', + }, + migrate: true, + }, + uaChTrackLevel: 'none', + useBeacon: false, + useGlobalIntegrationsConfigInEvents: false, + useServerSideCookies: false, + }, + metrics: { + dropped: 0, + queued: 0, + retries: 0, + sent: 0, + triggered: 0, + }, + nativeDestinations: { + activeDestinations: [], + clientDestinationsReady: false, + configuredDestinations: [], + failedDestinations: [], + initializedDestinations: [], + integrationsConfig: {}, + loadIntegration: true, + loadOnlyIntegrations: {}, + }, + plugins: { + activePlugins: [], + failedPlugins: [], + loadedPlugins: [], + pluginsToLoadFromConfig: [], + ready: false, + totalPluginsToLoad: 0, + }, + reporting: { + breadcrumbs: [], + isErrorReportingEnabled: false, + isErrorReportingPluginLoaded: false, + isMetricsReportingEnabled: false, + }, + serverCookies: { + isEnabledServerSideCookies: false, + }, + session: { + initialReferrer: '', + initialReferringDomain: '', + sessionInfo: {}, + }, + source: { + id: 'dummy-source-id', + workspaceId: 'dummy-workspace-id', + }, + storage: { + entries: {}, + migrate: false, + trulyAnonymousTracking: false, + }, +}; +describe('Bugsnag utilities', () => { describe('isApiKeyValid', () => { it('should return true for a valid API key', () => { const apiKey = '1234567890abcdef'; @@ -104,8 +218,6 @@ describe('Bugsnag utilities', () => { describe('getReleaseStage', () => { let windowSpy: any; - let documentSpy: any; - let navigatorSpy: any; let locationSpy: any; beforeEach(() => { @@ -166,7 +278,7 @@ describe('Bugsnag utilities', () => { }); describe('isRudderSDKError', () => { - const testCaseData = [ + const testCaseData: any[][] = [ ['https://invalid-domain.com/rsa.min.js', true], ['https://invalid-domain.com/rss.min.js', false], ['https://invalid-domain.com/rsa-plugins-Beacon.min.js', true], @@ -184,7 +296,7 @@ describe('Bugsnag utilities', () => { it.each(testCaseData)( 'if script src is "%s" then it should return the value as "%s" ', - (scriptSrc, expectedValue) => { + (scriptSrc: any, expectedValue: boolean) => { // Bugsnag error event object structure const event = { stacktrace: [ @@ -192,7 +304,7 @@ describe('Bugsnag utilities', () => { file: scriptSrc, }, ], - }; + } as unknown as BugsnagLib.Report; expect(isRudderSDKError(event)).toBe(expectedValue); }, @@ -202,40 +314,26 @@ describe('Bugsnag utilities', () => { describe('enhanceErrorEventMutator', () => { it('should return the enhanced error event object', () => { const event = { - metadata: {}, + metaData: {}, stacktrace: [ { file: 'https://invalid-domain.com/rsa.min.js', }, ], - updateMetaData(key, value) { - this.metadata[key] = value; + updateMetaData(key: string, value: any) { + // @ts-expect-error ignore for testing + this.metaData[key] = value; }, errorMessage: 'test error message', - }; + } as unknown as BugsnagLib.Report; enhanceErrorEventMutator(state, event); - expect(event.metadata).toEqual({ + expect(event.metaData).toEqual({ source: { snippetVersion: '3.0.0', }, - state: { - source: { - id: 'dummy-source-id', - }, - lifecycle: { - writeKey: 'dummy-write-key', - }, - context: { - app: { - name: 'test-app', - namespace: 'test-namespace', - version: '1.0.0', - installType: 'npm', - }, - }, - }, + state: DEFAULT_STATE_DATA, }); expect(event.context).toBe('test error message'); @@ -244,40 +342,26 @@ describe('Bugsnag utilities', () => { it('should return the enhanced error event object if the error is for script loads', () => { const event = { - metadata: {}, + metaData: {}, stacktrace: [ { file: 'https://invalid-domain.com/rsa.min.js', }, ], - updateMetaData(key, value) { - this.metadata[key] = value; + updateMetaData(key: string, value: any) { + // @ts-expect-error ignore for testing + this.metaData[key] = value; }, errorMessage: 'error in script loading "https://invalid-domain.com/rsa.min.js"', - }; + } as unknown as BugsnagLib.Report; - enhanceErrorEventMutator(state, event, 'dummyMetadataVal'); + enhanceErrorEventMutator(state, event); - expect(event.metadata).toEqual({ + expect(event.metaData).toEqual({ source: { snippetVersion: '3.0.0', }, - state: { - source: { - id: 'dummy-source-id', - }, - lifecycle: { - writeKey: 'dummy-write-key', - }, - context: { - app: { - name: 'test-app', - namespace: 'test-namespace', - version: '1.0.0', - installType: 'npm', - }, - }, - }, + state: DEFAULT_STATE_DATA, }); expect(event.context).toBe('Script load failures'); @@ -286,16 +370,20 @@ describe('Bugsnag utilities', () => { }); describe('initBugsnagClient', () => { - const origSdkMaxWait = bugsnagConstants.MAX_WAIT_FOR_SDK_LOAD_MS; - const mountBugsnagSDK = () => { (window as any).bugsnag = jest.fn(() => ({ notifier: { version: '6.0.0' } })); }; + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(0); + }); + afterEach(() => { delete (window as any).bugsnag; - bugsnagConstants.MAX_WAIT_FOR_SDK_LOAD_MS = origSdkMaxWait; - state = undefined; + resetState(); + + jest.useRealTimers(); }); it('should resolve the promise immediately if the bugsnag SDK is already loaded', async () => { @@ -309,14 +397,20 @@ describe('Bugsnag utilities', () => { }); it('should resolve the promise after some time when the bugsnag SDK is loaded', async () => { - setTimeout(() => { - mountBugsnagSDK(); - }, 1000); - const bsClientPromise: Promise = new Promise((resolve, reject) => { initBugsnagClient(state, resolve, reject); }); + state.session.sessionInfo.value = { id: 123 }; + state.autoTrack.pageLifecycle.visitId.value = 'test-visit-id'; + + // Advance time and mount the Bugsnag SDK + jest.advanceTimersByTime(1); + mountBugsnagSDK(); + + // Run all timers to trigger the promise resolution + jest.runAllTimers(); + const bsClient: BugsnagLib.Client = await bsClientPromise; expect(bsClient).toBeDefined(); // returns a mocked Bugsnag client @@ -325,11 +419,11 @@ describe('Bugsnag utilities', () => { expect((window as any).bugsnag).toHaveBeenCalledTimes(2); expect((window as any).bugsnag).toHaveBeenNthCalledWith(2, { apiKey: '__RS_BUGSNAG_API_KEY__', - appVersion: '1.0.0', + appVersion: 'dev-snapshot', metaData: { SDK: { name: 'JS', - installType: 'npm', + installType: 'cdn', }, }, autoCaptureSessions: false, @@ -338,7 +432,7 @@ describe('Bugsnag utilities', () => { maxBreadcrumbs: 40, releaseStage: 'development', user: { - id: 'dummy-source-id', + id: 'dummy-source-id..123..test-visit-id', }, networkBreadcrumbsEnabled: false, beforeSend: expect.any(Function), @@ -347,28 +441,34 @@ describe('Bugsnag utilities', () => { }); it('should return bugsnag client with write key as user id if source id is not available', async () => { + // @ts-expect-error source id is not defined for the test case state.source.value = { id: undefined }; state.lifecycle.writeKey = signal('dummy-write-key'); - - setTimeout(() => { - mountBugsnagSDK(); - }, 1000); + state.session.sessionInfo.value = { id: 123 }; + state.autoTrack.pageLifecycle.visitId.value = 'test-visit-id'; const bsClientPromise: Promise = new Promise((resolve, reject) => { initBugsnagClient(state, resolve, reject); }); + // Advance time and mount the Bugsnag SDK + jest.advanceTimersByTime(1); + mountBugsnagSDK(); + + // Run all timers to trigger the promise resolution + jest.runAllTimers(); + await bsClientPromise; // First call is the version check expect((window as any).bugsnag).toHaveBeenCalledTimes(2); expect((window as any).bugsnag).toHaveBeenNthCalledWith(2, { apiKey: '__RS_BUGSNAG_API_KEY__', - appVersion: '1.0.0', + appVersion: 'dev-snapshot', metaData: { SDK: { name: 'JS', - installType: 'npm', + installType: 'cdn', }, }, autoCaptureSessions: false, @@ -377,7 +477,7 @@ describe('Bugsnag utilities', () => { maxBreadcrumbs: 40, releaseStage: 'development', user: { - id: 'dummy-write-key', + id: 'dummy-write-key..123..test-visit-id', }, networkBreadcrumbsEnabled: false, beforeSend: expect.any(Function), @@ -386,19 +486,24 @@ describe('Bugsnag utilities', () => { }); it('should reject the promise if the Bugsnag SDK is not loaded', async () => { - bugsnagConstants.MAX_WAIT_FOR_SDK_LOAD_MS = 1000; - const bsClientPromise = new Promise((resolve, reject) => { initBugsnagClient(state, resolve, reject); }); + // Advance time to trigger timeout + jest.advanceTimersByTime(10000); // 10 seconds + await expect(bsClientPromise).rejects.toThrow( - 'A timeout 1000 ms occurred while trying to load the Bugsnag SDK.', + 'A timeout 10000 ms occurred while trying to load the Bugsnag SDK.', ); }); }); describe('loadBugsnagSDK', () => { + let insertBeforeSpy: any; + + const extSrcLoader = new ExternalSrcLoader(defaultErrorHandler, defaultLogger); + beforeAll(() => { server.listen(); }); @@ -406,17 +511,12 @@ describe('Bugsnag utilities', () => { afterAll(() => { server.close(); }); - let insertBeforeSpy: any; - - const mockLogger = new MockLogger(); - const mockErrorHandler = new MockErrorHandler(); - const extSrcLoader = new ExternalSrcLoader(mockErrorHandler, mockLogger); - - const origBugsnagUrl = bugsnagConstants.BUGSNAG_CDN_URL; - const origExtSrcLoadTimeout = timeouts.DEFAULT_EXT_SRC_LOAD_TIMEOUT_MS; beforeEach(() => { insertBeforeSpy = jest.spyOn(document.head, 'insertBefore'); + + jest.useFakeTimers(); + jest.setSystemTime(0); }); afterEach(() => { @@ -426,14 +526,14 @@ describe('Bugsnag utilities', () => { } delete (window as any).Bugsnag; delete (window as any).bugsnag; - bugsnagConstants.BUGSNAG_CDN_URL = origBugsnagUrl; - timeouts.DEFAULT_EXT_SRC_LOAD_TIMEOUT_MS = origExtSrcLoadTimeout; + + jest.useRealTimers(); }); it('should not load Bugsnag SDK if it (<=v6) is already loaded', () => { (window as any).bugsnag = jest.fn(() => ({ notifier: { version: '6.0.0' } })); - loadBugsnagSDK(); + loadBugsnagSDK(extSrcLoader, defaultLogger); expect(insertBeforeSpy).not.toHaveBeenCalled(); }); @@ -441,37 +541,36 @@ describe('Bugsnag utilities', () => { it('should not load Bugsnag SDK if it (>v6) is already loaded', () => { (window as any).Bugsnag = { _client: { _notifier: { version: '7.0.0' } } }; - loadBugsnagSDK(); + loadBugsnagSDK(extSrcLoader, defaultLogger); expect(insertBeforeSpy).not.toHaveBeenCalled(); }); - it('should attempt to load Bugsnag SDK if not already loaded', done => { - loadBugsnagSDK(extSrcLoader, undefined); + it('should attempt to load Bugsnag SDK if not already loaded', () => { + loadBugsnagSDK(extSrcLoader); - setTimeout(() => { - expect(insertBeforeSpy).toHaveBeenCalled(); - done(); - }, 500); + // Run all timers to trigger the script load + jest.runAllTimers(); + + expect(insertBeforeSpy).toHaveBeenCalled(); }); it('should invoke error handler and log error if Bugsnag SDK could not be loaded', done => { - timeouts.DEFAULT_EXT_SRC_LOAD_TIMEOUT_MS = 1000; // 1 second - bugsnagConstants.BUGSNAG_CDN_URL = 'https://asdf.com/bugsnag.min.js'; - loadBugsnagSDK(extSrcLoader, mockLogger); + loadBugsnagSDK(extSrcLoader, defaultLogger); - setTimeout(() => { - expect(mockErrorHandler.onError).toHaveBeenCalledWith( + // Advance the timer to trigger the script load and result in error + jest.advanceTimersByTimeAsync(1).then(() => { + expect(defaultErrorHandler.onError).toHaveBeenCalledWith( new Error( - `Failed to load the script with the id "rs-bugsnag" from URL "https://asdf.com/bugsnag.min.js".`, + `Failed to load the script with the id "rs-bugsnag" from URL "__RS_BUGSNAG_SDK_URL__".`, ), 'ExternalSrcLoader', ); - expect(mockLogger.error).toHaveBeenCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( `BugsnagPlugin:: Failed to load the Bugsnag SDK.`, ); done(); - }, 2000); + }); }); }); @@ -487,7 +586,7 @@ describe('Bugsnag utilities', () => { file: 'https://invalid-domain.com/not-rsa.min.js', }, ], - }; + } as unknown as BugsnagLib.Report; const onErrorFn = onError(state); @@ -512,22 +611,7 @@ describe('Bugsnag utilities', () => { expect(error.updateMetaData).toHaveBeenNthCalledWith(1, 'source', { snippetVersion: '3.0.0', }); - expect(error.updateMetaData).toHaveBeenNthCalledWith(2, 'state', { - source: { - id: 'dummy-source-id', - }, - lifecycle: { - writeKey: 'dummy-write-key', - }, - context: { - app: { - name: 'test-app', - namespace: 'test-namespace', - version: '1.0.0', - installType: 'npm', - }, - }, - }); + expect(error.updateMetaData).toHaveBeenNthCalledWith(2, 'state', DEFAULT_STATE_DATA); expect(error.severity).toBe('error'); expect(error.context).toBe('Script load failures'); }); @@ -549,8 +633,6 @@ describe('Bugsnag utilities', () => { }); it('should log error and return false if the error could not be filtered', () => { - const mockLogger = new MockLogger(); - const error = { stacktrace: [ { @@ -565,10 +647,12 @@ describe('Bugsnag utilities', () => { }), } as any; - const onErrorFn = onError(state, mockLogger); + const onErrorFn = onError(state, defaultLogger); expect(onErrorFn(error)).toBe(false); - expect(mockLogger.error).toHaveBeenCalledWith('BugsnagPlugin:: Failed to filter the error.'); + expect(defaultLogger.error).toHaveBeenCalledWith( + 'BugsnagPlugin:: Failed to filter the error.', + ); }); }); @@ -576,12 +660,15 @@ describe('Bugsnag utilities', () => { const origAppStateExcludes = bugsnagConstants.APP_STATE_EXCLUDE_KEYS; beforeEach(() => { - bugsnagConstants.APP_STATE_EXCLUDE_KEYS = origAppStateExcludes; + Object.defineProperty(bugsnagConstants, 'APP_STATE_EXCLUDE_KEYS', { + value: origAppStateExcludes, + writable: true, + }); }); // Here we are just exploring different combinations of data where // the signals could be buried inside objects, arrays, nested objects, etc. - const tcData = [ + const tcData: any[][] = [ [ { name: 'test', @@ -697,6 +784,9 @@ describe('Bugsnag utilities', () => { ], [ { + // We're intentionally adding BigInt values + // here to test if they are converted to strings + // eslint-disable-next-line compat/compat someKey: BigInt(123), }, undefined, @@ -705,7 +795,11 @@ describe('Bugsnag utilities', () => { ]; it.each(tcData)('should convert signals to JSON %#', (input, expected, excludes) => { - bugsnagConstants.APP_STATE_EXCLUDE_KEYS = excludes; + Object.defineProperty(bugsnagConstants, 'APP_STATE_EXCLUDE_KEYS', { + value: excludes, + writable: true, + }); + expect(getAppStateForMetadata(input)).toEqual(expected); }); }); diff --git a/packages/analytics-js-plugins/__tests__/customConsentManager/index.test.ts b/packages/analytics-js-plugins/__tests__/customConsentManager/index.test.ts index 340e9b2d81..8856df7154 100644 --- a/packages/analytics-js-plugins/__tests__/customConsentManager/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/customConsentManager/index.test.ts @@ -1,4 +1,7 @@ -import { state, resetState } from '@rudderstack/analytics-js/state'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import { resetState, state } from '../../__mocks__/state'; import { CustomConsentManager } from '../../src/customConsentManager'; describe('Plugin - CustomConsentManager', () => { @@ -6,33 +9,25 @@ describe('Plugin - CustomConsentManager', () => { resetState(); }); - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - log: jest.fn(), - }; - - const mockErrorHandler = { - onError: jest.fn(), - }; - it('should add CustomConsentManager plugin in the loaded plugin list', () => { - CustomConsentManager().initialize(state); + CustomConsentManager().initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('CustomConsentManager')).toBe(true); }); it('ensure default extension points are defined', () => { - expect(CustomConsentManager().consentManager.init()).toBeUndefined(); - expect(CustomConsentManager().consentManager.updateConsentsInfo()).toBeUndefined(); + expect((CustomConsentManager().consentManager as ExtensionPoint)?.init?.()).toBeUndefined(); + expect( + (CustomConsentManager().consentManager as ExtensionPoint)?.updateConsentsInfo?.(), + ).toBeUndefined(); }); it('should return true if the consent manager is not initialized', () => { expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, undefined, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -42,11 +37,11 @@ describe('Plugin - CustomConsentManager', () => { state.consents.provider.value = 'custom'; const destConfig = {}; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -75,11 +70,11 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -93,11 +88,11 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -111,11 +106,11 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -145,11 +140,11 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -179,11 +174,11 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -213,11 +208,11 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(false); }); @@ -226,7 +221,8 @@ describe('Plugin - CustomConsentManager', () => { state.consents.initialized.value = true; state.consents.provider.value = 'custom'; state.consents.data.value = { - allowedConsentIds: null, // This will throw an exception + // @ts-expect-error Intentionally setting to null to throw an exception + allowedConsentIds: null, }; const destConfig = { @@ -247,15 +243,15 @@ describe('Plugin - CustomConsentManager', () => { }; expect( - CustomConsentManager().consentManager.isDestinationConsented( + (CustomConsentManager().consentManager as ExtensionPoint)?.isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); - expect(mockErrorHandler.onError).toBeCalledWith( + expect(defaultErrorHandler.onError).toHaveBeenCalledWith( new TypeError("Cannot read properties of null (reading 'includes')"), 'CustomConsentManagerPlugin', 'Failed to determine the consent status for the destination. Please check the destination configuration and try again.', diff --git a/packages/analytics-js-plugins/__tests__/deviceModeDestinations/utils.test.ts b/packages/analytics-js-plugins/__tests__/deviceModeDestinations/utils.test.ts index 0e2c0a9315..1b356a10d4 100644 --- a/packages/analytics-js-plugins/__tests__/deviceModeDestinations/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/deviceModeDestinations/utils.test.ts @@ -1,5 +1,5 @@ +/* eslint-disable class-methods-use-this */ // eslint-disable-next-line max-classes-per-file -import { signal } from '@preact/signals-core'; import type { Destination } from '@rudderstack/analytics-js-common/types/Destination'; import { wait, @@ -7,15 +7,31 @@ import { createDestinationInstance, isDestinationSDKMounted, } from '../../src/deviceModeDestinations/utils'; -import * as dmdConstants from '../../src/deviceModeDestinations/constants'; +import type { DeviceModeDestinationsAnalyticsInstance } from '../../src/deviceModeDestinations/types'; +import type { LogLevel } from '../../src/types/plugins'; +import { resetState, state } from '../../__mocks__/state'; describe('deviceModeDestinations utils', () => { describe('wait', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(0); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + it('should return a promise that resolves after the specified time', async () => { const time = 1000; const startTime = Date.now(); - await wait(time); + const waitPromise = wait(time); + + // Advance the timers by the specified time + jest.runAllTimers(); + + await waitPromise; const endTime = Date.now(); @@ -26,7 +42,12 @@ describe('deviceModeDestinations utils', () => { const time = 0; const startTime = Date.now(); - await wait(time); + const waitPromise = wait(time); + + // Advance the timers by the specified time + jest.runAllTimers(); + + await waitPromise; const endTime = Date.now(); @@ -37,7 +58,12 @@ describe('deviceModeDestinations utils', () => { const time = -1000; const startTime = Date.now(); - await wait(time); + const waitPromise = wait(time); + + // Advance the timers by the next tick + jest.runAllTimers(); + + await waitPromise; const endTime = Date.now(); @@ -48,7 +74,13 @@ describe('deviceModeDestinations utils', () => { const time = '2 seconds'; const startTime = Date.now(); - await wait(time); + // @ts-expect-error intentionally passing a string + const waitPromise = wait(time); + + // Advance the timers by the next tick + jest.runAllTimers(); + + await waitPromise; const endTime = Date.now(); @@ -57,40 +89,44 @@ describe('deviceModeDestinations utils', () => { }); describe('isDestinationReady', () => { - const originalInitializedCheckTimeout = dmdConstants.READY_CHECK_TIMEOUT_MS; - const originalInitializedPollInterval = dmdConstants.LOAD_CHECK_POLL_INTERVAL; + let isLoadedResponse = false; const destination = { instance: { - isLoaded: () => false, + isLoaded: () => isLoadedResponse, }, userFriendlyId: 'GA4___1234567890', }; beforeEach(() => { - // temporarily manipulate the timeout and interval constants to speed up the test - dmdConstants.READY_CHECK_TIMEOUT_MS = 200; - dmdConstants.LOAD_CHECK_POLL_INTERVAL = 100; + jest.useFakeTimers(); + jest.setSystemTime(0); }); afterEach(() => { - dmdConstants.READY_CHECK_TIMEOUT_MS = originalInitializedCheckTimeout; - dmdConstants.LOAD_CHECK_POLL_INTERVAL = originalInitializedPollInterval; - destination.instance.isLoaded = () => false; + jest.useRealTimers(); + isLoadedResponse = false; }); it('should return a promise that gets resolved when the destination is ready immediately', async () => { - destination.instance.isLoaded = () => true; + isLoadedResponse = true; const isReadyPromise = isDestinationReady(destination as Destination); + + // Fast-forward the timers + jest.runAllTimers(); + await expect(isReadyPromise).resolves.toEqual(true); }); it('should return a promise that gets resolved when the destination is ready after some time', async () => { - const isReadyPromise = isDestinationReady(destination as Destination); + setTimeout(() => { + isLoadedResponse = true; + }, 1000); - await wait(100); + const isReadyPromise = isDestinationReady(destination as Destination); - destination.instance.isLoaded = () => true; + // Fast-forward the timers + jest.advanceTimersByTime(1000); await expect(isReadyPromise).resolves.toEqual(true); }); @@ -98,16 +134,19 @@ describe('deviceModeDestinations utils', () => { it('should return a promise that gets rejected when the destination is not ready after the timeout', async () => { const isReadyPromise = isDestinationReady(destination as Destination); + // Fast-forward the timers to cause a timeout + jest.advanceTimersByTime(11000); + await expect(isReadyPromise).rejects.toThrow( new Error( - `A timeout of 200 ms occurred while trying to check the ready status for "${destination.userFriendlyId}" destination.`, + `A timeout of 11000 ms occurred while trying to check the ready status for "${destination.userFriendlyId}" destination.`, ), ); }); }); describe('createDestinationInstance', () => { - class mockAnalytics { + class MockAnalytics implements DeviceModeDestinationsAnalyticsInstance { page = () => {}; track = () => {}; identify = () => {}; @@ -118,24 +157,27 @@ describe('deviceModeDestinations utils', () => { getUserTraits = () => ({ trait1: 'value1' }); getGroupId = () => 'groupId'; getGroupTraits = () => ({ trait2: 'value2' }); - getSessionId = () => 'sessionId'; + getSessionId = () => 123; + loadIntegration = true; + logLevel = 'DEBUG' as LogLevel; + loadOnlyIntegrations = { All: true }; } // create two mock instances to later choose based on the write key - const mockAnalyticsInstance_writeKey1 = new mockAnalytics(); - const mockAnalyticsInstance_writeKey2 = new mockAnalytics(); - - class mockRudderAnalytics { - getAnalyticsInstance = writeKey => { - const instancesMap = { - '1234567890': mockAnalyticsInstance_writeKey1, - '12345678910': mockAnalyticsInstance_writeKey2, + const mockAnalyticsInstanceWriteKey1 = new MockAnalytics(); + const mockAnalyticsInstanceWriteKey2 = new MockAnalytics(); + + class MockRudderAnalytics { + getAnalyticsInstance = (writeKey: string) => { + const instancesMap: Record = { + '1234567890': mockAnalyticsInstanceWriteKey1, + '12345678910': mockAnalyticsInstanceWriteKey2, }; return instancesMap[writeKey]; }; } - const mockRudderAnalyticsInstance = new mockRudderAnalytics(); + const mockRudderAnalyticsInstance = new MockRudderAnalytics(); // put destination SDK code on the window object const destSDKIdentifier = 'GA4_RS'; @@ -148,7 +190,7 @@ describe('deviceModeDestinations utils', () => { [sdkTypeName]: class { config: any; analytics: any; - constructor(config, analytics) { + constructor(config: any, analytics: any) { this.config = config; this.analytics = analytics; } @@ -162,19 +204,7 @@ describe('deviceModeDestinations utils', () => { }); it('should return an instance of the destination', () => { - const state = { - nativeDestinations: { - loadIntegration: signal(true), - loadOnlyIntegrations: signal({ All: true }), - }, - lifecycle: { - logLevel: signal('DEBUG'), - writeKey: signal('12345678910'), // mockAnalyticsInstance_writeKey2 - }, - consents: { - postConsent: signal({}), - }, - }; + state.lifecycle.writeKey.value = '12345678910'; // write key 2 const destination = { config: { @@ -182,7 +212,7 @@ describe('deviceModeDestinations utils', () => { }, areTransformationsConnected: false, id: 'GA4___5678', - }; + } as unknown as Destination; const destinationInstance = createDestinationInstance( destSDKIdentifier, @@ -195,8 +225,8 @@ describe('deviceModeDestinations utils', () => { expect(destinationInstance.config).toEqual(destination.config); expect(destinationInstance.analytics).toEqual({ loadIntegration: true, - loadOnlyIntegrations: { All: true }, - logLevel: 'DEBUG', + loadOnlyIntegrations: {}, + logLevel: 'ERROR', alias: expect.any(Function), group: expect.any(Function), identify: expect.any(Function), @@ -215,13 +245,15 @@ describe('deviceModeDestinations utils', () => { expect(destinationInstance.analytics.getUserTraits()).toEqual({ trait1: 'value1' }); expect(destinationInstance.analytics.getGroupId()).toEqual('groupId'); expect(destinationInstance.analytics.getGroupTraits()).toEqual({ trait2: 'value2' }); - expect(destinationInstance.analytics.getSessionId()).toEqual('sessionId'); + expect(destinationInstance.analytics.getSessionId()).toEqual(123); // Making sure that the call gets forwarded to the correct instance - const pageCallSpy = jest.spyOn(mockAnalyticsInstance_writeKey2, 'page'); + const pageCallSpy = jest.spyOn(mockAnalyticsInstanceWriteKey2, 'page'); destinationInstance.analytics.page(); - expect(mockAnalyticsInstance_writeKey2.page).toHaveBeenCalled(); + expect(mockAnalyticsInstanceWriteKey2.page).toHaveBeenCalled(); pageCallSpy.mockRestore(); + + resetState(); }); }); @@ -235,21 +267,6 @@ describe('deviceModeDestinations utils', () => { delete (window as any)[destSDKIdentifier]; }); - class MockLogger implements ILogger { - warn = jest.fn(); - log = jest.fn(); - error = jest.fn(); - info = jest.fn(); - debug = jest.fn(); - minLogLevel = 0; - scope = 'test scope'; - setMinLogLevel = jest.fn(); - setScope = jest.fn(); - logProvider = console; - } - - const mockLogger = new MockLogger(); - it('should return false if the destination SDK is not evaluated', () => { expect(isDestinationSDKMounted(destSDKIdentifier, sdkTypeName)).toEqual(false); }); @@ -265,6 +282,7 @@ describe('deviceModeDestinations utils', () => { it('should return true if the destination SDK is a constructable type', () => { (window as any)[destSDKIdentifier] = { [sdkTypeName]: class { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor, sonarjs/no-useless-constructor, sonarjs/no-empty-function, @typescript-eslint/no-empty-function constructor() {} }, }; diff --git a/packages/analytics-js-plugins/__tests__/deviceModeTransformation/.gitkeep b/packages/analytics-js-plugins/__tests__/deviceModeTransformation/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/analytics-js-plugins/__tests__/deviceModeTransformation/index.test.ts b/packages/analytics-js-plugins/__tests__/deviceModeTransformation/index.test.ts index 544957d15f..80b8c43a21 100644 --- a/packages/analytics-js-plugins/__tests__/deviceModeTransformation/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/deviceModeTransformation/index.test.ts @@ -1,14 +1,12 @@ /* eslint-disable no-plusplus */ import { batch } from '@preact/signals-core'; -import { HttpClient } from '@rudderstack/analytics-js/services/HttpClient'; -import { state } from '@rudderstack/analytics-js/state'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { defaultErrorHandler } from '@rudderstack/analytics-js/services/ErrorHandler'; -import { defaultLogger } from '@rudderstack/analytics-js/services/Logger'; -import { StoreManager } from '@rudderstack/analytics-js/services/StoreManager'; import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; -import type { IHttpClient } from '@rudderstack/analytics-js-common/types/HttpClient'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import * as utils from '../../src/deviceModeTransformation/utilities'; +import { DeviceModeTransformation } from '../../src/deviceModeTransformation'; import { dummyDataplaneHost, dummyWriteKey, @@ -16,8 +14,11 @@ import { dmtSuccessResponse, } from '../../__fixtures__/fixtures'; import { server } from '../../__fixtures__/msw.server'; -import * as utils from '@rudderstack/analytics-js-plugins/deviceModeTransformation/utilities'; -import { DeviceModeTransformation } from '@rudderstack/analytics-js-plugins/deviceModeTransformation'; +import { resetState, state } from '../../__mocks__/state'; +import { defaultHttpClient } from '../../__mocks__/HttpClient'; +import { defaultPluginsManager } from '../../__mocks__/PluginsManager'; +import type { RetryQueue } from '../../src/utilities/retryQueue/RetryQueue'; +import type { QueueItem, QueueItemData } from '../../src/types/plugins'; jest.mock('@rudderstack/analytics-js-common/utilities/uuId', () => ({ ...jest.requireActual('@rudderstack/analytics-js-common/utilities/uuId'), @@ -25,16 +26,9 @@ jest.mock('@rudderstack/analytics-js-common/utilities/uuId', () => ({ })); describe('Device mode transformation plugin', () => { - const defaultPluginsManager = new PluginsManager( - defaultPluginEngine, - defaultErrorHandler, - defaultLogger, - ); - - const defaultStoreManager = new StoreManager(defaultPluginsManager); - beforeAll(() => { server.listen(); + resetState(); batch(() => { state.lifecycle.writeKey.value = dummyWriteKey; state.lifecycle.activeDataplaneUrl.value = dummyDataplaneHost; @@ -42,8 +36,6 @@ describe('Device mode transformation plugin', () => { }); }); - const httpClient = new HttpClient(); - afterAll(() => { server.close(); }); @@ -82,32 +74,32 @@ describe('Device mode transformation plugin', () => { }); it('should return a queue object on init', () => { - const queue = DeviceModeTransformation().transformEvent?.init( + const queue = (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.init?.( state, defaultPluginsManager, - httpClient, + defaultHttpClient, defaultStoreManager, defaultErrorHandler, defaultLogger, - ); + ) as RetryQueue; expect(queue).toBeDefined(); expect(queue.name).toBe('rudder_dummy-write-key'); }); it('should add item in queue on enqueue', () => { - const queue = DeviceModeTransformation().transformEvent?.init( + const queue = (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.init?.( state, defaultPluginsManager, - httpClient, + defaultHttpClient, defaultStoreManager, defaultErrorHandler, defaultLogger, - ); + ) as RetryQueue; const addItemSpy = jest.spyOn(queue, 'addItem'); - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -117,11 +109,16 @@ describe('Device mode transformation plugin', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; - DeviceModeTransformation().transformEvent?.enqueue(state, queue, event, destinations); + (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.enqueue?.( + state, + queue, + event, + destinations, + ); - expect(addItemSpy).toBeCalledWith({ + expect(addItemSpy).toHaveBeenCalledWith({ token: authToken, destinationIds, event, @@ -131,20 +128,19 @@ describe('Device mode transformation plugin', () => { }); it('should process queue item on start', () => { - const mockHttpClient = { - getAsyncData: ({ callback }) => { - callback(true); - }, - setAuthHeader: jest.fn(), - }; - const queue = DeviceModeTransformation().transformEvent?.init( + // Mock the implementation of getAsyncData to return a successful response + defaultHttpClient.getAsyncData.mockImplementation(({ callback }) => { + callback(true); + }); + + const queue = (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.init?.( state, defaultPluginsManager, - mockHttpClient, + defaultHttpClient, defaultStoreManager, - ); + ) as RetryQueue; - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -154,17 +150,22 @@ describe('Device mode transformation plugin', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; const queueProcessCbSpy = jest.spyOn(queue, 'processQueueCb'); - DeviceModeTransformation().transformEvent?.enqueue(state, queue, event, destinations); + (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.enqueue?.( + state, + queue, + event, + destinations, + ); // Explicitly start the queue to process the item // In actual implementation, this is done based on the state signals queue.start(); - expect(queueProcessCbSpy).toBeCalledWith( + expect(queueProcessCbSpy).toHaveBeenCalledWith( { token: authToken, destinationIds, @@ -177,33 +178,33 @@ describe('Device mode transformation plugin', () => { ); // Item is successfully processed and removed from queue - expect(queue.getStorageEntry('queue').length).toBe(0); + expect((queue.getStorageEntry('queue') as QueueItem[]).length).toBe(0); queueProcessCbSpy.mockRestore(); + defaultHttpClient.getAsyncData.mockRestore(); }); - it('SendTransformedEventToDestinations function is called in case of successful transformation', () => { - const mockHttpClient: IHttpClient = { - getAsyncData: ({ callback }) => { - callback?.(JSON.stringify(dmtSuccessResponse), { xhr: { status: 200 } }); - }, - setAuthHeader: jest.fn(), - }; + it('should process transformed events in case of successful transformation', () => { + // Mock the implementation of getAsyncData to return a successful response + defaultHttpClient.getAsyncData.mockImplementation(({ callback }) => { + callback(JSON.stringify(dmtSuccessResponse), { xhr: { status: 200 } }); + }); + const mockSendTransformedEventToDestinations = jest.spyOn( utils, 'sendTransformedEventToDestinations', ); - const queue = DeviceModeTransformation().transformEvent?.init( + const queue = (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.init?.( state, defaultPluginsManager, - mockHttpClient, + defaultHttpClient, defaultStoreManager, defaultErrorHandler, defaultLogger, - ); + ) as RetryQueue; - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -213,12 +214,17 @@ describe('Device mode transformation plugin', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; queue.start(); - DeviceModeTransformation().transformEvent?.enqueue(state, queue, event, destinations); + (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.enqueue?.( + state, + queue, + event, + destinations, + ); - expect(mockSendTransformedEventToDestinations).toBeCalledTimes(1); + expect(mockSendTransformedEventToDestinations).toHaveBeenCalledTimes(1); expect(mockSendTransformedEventToDestinations).toHaveBeenCalledWith( state, defaultPluginsManager, @@ -229,30 +235,31 @@ describe('Device mode transformation plugin', () => { defaultErrorHandler, defaultLogger, ); + mockSendTransformedEventToDestinations.mockRestore(); + defaultHttpClient.getAsyncData.mockRestore(); }); - it('SendTransformedEventToDestinations function should not be called in case of unsuccessful transformation', () => { - const mockHttpClient: IHttpClient = { - getAsyncData: ({ callback }) => { - callback?.(false, { error: 'some error', xhr: { status: 502 } }); - }, - setAuthHeader: jest.fn(), - }; + it('should not process transformed events in case of unsuccessful transformation', () => { + // Mock the implementation of getAsyncData to return a retryable error response + defaultHttpClient.getAsyncData.mockImplementation(({ callback }) => { + callback(false, { error: 'some error', xhr: { status: 502 } }); + }); + const mockSendTransformedEventToDestinations = jest.spyOn( utils, 'sendTransformedEventToDestinations', ); - const queue = DeviceModeTransformation().transformEvent?.init( + const queue = (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.init?.( state, defaultPluginsManager, - mockHttpClient, + defaultHttpClient, defaultStoreManager, defaultErrorHandler, defaultLogger, - ); + ) as RetryQueue; - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -262,12 +269,17 @@ describe('Device mode transformation plugin', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; queue.start(); - DeviceModeTransformation().transformEvent?.enqueue(state, queue, event, destinations); + (DeviceModeTransformation()?.transformEvent as ExtensionPoint)?.enqueue?.( + state, + queue, + event, + destinations, + ); - expect(mockSendTransformedEventToDestinations).not.toBeCalled(); + expect(mockSendTransformedEventToDestinations).not.toHaveBeenCalled(); // The element is requeued expect(queue.getStorageEntry('queue')).toStrictEqual([ { diff --git a/packages/analytics-js-plugins/__tests__/errorReporting/index.test.ts b/packages/analytics-js-plugins/__tests__/errorReporting/index.test.ts index f1cab14dca..f0bed5e533 100644 --- a/packages/analytics-js-plugins/__tests__/errorReporting/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/errorReporting/index.test.ts @@ -1,6 +1,8 @@ import { signal } from '@preact/signals-core'; import { clone } from 'ramda'; import type { IHttpClient } from '@rudderstack/analytics-js-common/types/HttpClient'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; import { ErrorReporting } from '../../src/errorReporting'; describe('Plugin - ErrorReporting', () => { @@ -24,6 +26,14 @@ describe('Plugin - ErrorReporting', () => { id: 'test-source-id', config: {}, }), + session: { + sessionInfo: signal({ id: 'test-session-id' }), + }, + autoTrack: { + pageLifecycle: { + visitId: signal('test-visit-id'), + }, + }, }; let state: any; @@ -35,9 +45,6 @@ describe('Plugin - ErrorReporting', () => { const mockExtSrcLoader = { loadJSFile: jest.fn(() => Promise.resolve()), }; - const mockLogger = { - error: jest.fn(), - }; const mockErrReportingProviderClient = { notify: jest.fn(), leaveBreadcrumb: jest.fn(), @@ -49,47 +56,58 @@ describe('Plugin - ErrorReporting', () => { }); it('should add ErrorReporting plugin in the loaded plugin list', () => { - ErrorReporting().initialize(state); + ErrorReporting()?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('ErrorReporting')).toBe(true); expect(state.reporting.isErrorReportingPluginLoaded.value).toBe(true); expect(state.reporting.breadcrumbs.value[0].name).toBe('Error Reporting Plugin Loaded'); }); it('should not invoke error reporting provider plugin on init if request is coming from latest core SDK', () => { - ErrorReporting().errorReporting.init({}, mockPluginEngine, mockExtSrcLoader, mockLogger, true); + (ErrorReporting()?.errorReporting as ExtensionPoint)?.init?.( + {}, + mockPluginEngine, + mockExtSrcLoader, + defaultLogger, + true, + ); expect(mockPluginEngine.invokeSingle).not.toHaveBeenCalled(); }); it('should not invoke error reporting provider plugin on init if sourceConfig do not have required parameters', () => { - ErrorReporting().errorReporting.init( + (ErrorReporting()?.errorReporting as ExtensionPoint)?.init?.( state, mockPluginEngine, mockExtSrcLoader, - mockLogger, + defaultLogger, true, ); expect(mockPluginEngine.invokeSingle).not.toHaveBeenCalled(); }); it('should not invoke error reporting provider plugin on init if request is coming from old core SDK', () => { - ErrorReporting().errorReporting.init(state, mockPluginEngine, mockExtSrcLoader, mockLogger); + (ErrorReporting()?.errorReporting as ExtensionPoint)?.init?.( + state, + mockPluginEngine, + mockExtSrcLoader, + defaultLogger, + ); expect(mockPluginEngine.invokeSingle).toHaveBeenCalledTimes(1); expect(mockPluginEngine.invokeSingle).toHaveBeenCalledWith( 'errorReportingProvider.init', state, mockExtSrcLoader, - mockLogger, + defaultLogger, ); }); it('should invoke the error reporting provider plugin on notify if httpClient is not provided', () => { const dummyError = new Error('dummy error'); - ErrorReporting().errorReporting.notify( + (ErrorReporting()?.errorReporting as ExtensionPoint)?.notify?.( mockPluginEngine, mockErrReportingProviderClient, dummyError, state, - mockLogger, + defaultLogger, ); expect(mockPluginEngine.invokeSingle).toHaveBeenCalledTimes(1); expect(mockPluginEngine.invokeSingle).toHaveBeenCalledWith( @@ -97,7 +115,7 @@ describe('Plugin - ErrorReporting', () => { mockErrReportingProviderClient, dummyError, state, - mockLogger, + defaultLogger, ); }); @@ -119,7 +137,7 @@ describe('Plugin - ErrorReporting', () => { value: `The request failed due to timeout at Analytics.page (http://localhost:3001/cdn/modern/iife/rsa.js:1610:3) at RudderAnalytics.page (http://localhost:3001/cdn/modern/iife/rsa.js:1666:84)`, }, }); - ErrorReporting().errorReporting.notify( + (ErrorReporting()?.errorReporting as ExtensionPoint)?.notify?.( {}, undefined, normalizedError, @@ -154,7 +172,7 @@ describe('Plugin - ErrorReporting', () => { value: `ReferenceError: testUndefinedFn is not defined at Analytics.page (http://localhost:3001/cdn/modern/iife/rsa.js:1610:3) at RudderAnalytics.page (http://localhost:3001/cdn/modern/iife/rsa.js:1666:84)`, }, }); - ErrorReporting().errorReporting.notify( + (ErrorReporting()?.errorReporting as ExtensionPoint)?.notify?.( {}, undefined, normalizedError, @@ -183,7 +201,7 @@ describe('Plugin - ErrorReporting', () => { value: `ReferenceError: testUndefinedFn is not defined at Abcd.page (http://localhost:3001/cdn/modern/iife/abc.js:1610:3) at xyz.page (http://localhost:3001/cdn/modern/iife/abc.js:1666:84)`, }, }); - ErrorReporting().errorReporting.notify( + (ErrorReporting()?.errorReporting as ExtensionPoint)?.notify?.( {}, undefined, normalizedError, @@ -202,18 +220,24 @@ describe('Plugin - ErrorReporting', () => { it('should add a new breadcrumb', () => { const breadcrumbLength = state.reporting.breadcrumbs.value.length; - ErrorReporting().errorReporting.breadcrumb({}, undefined, 'dummy breadcrumb', undefined, state); + (ErrorReporting()?.errorReporting as ExtensionPoint)?.breadcrumb?.( + {}, + undefined, + 'dummy breadcrumb', + undefined, + state, + ); expect(state.reporting.breadcrumbs.value.length).toBe(breadcrumbLength + 1); expect(mockPluginEngine.invokeSingle).not.toHaveBeenCalled(); }); it('should invoke the error reporting provider plugin on new breadcrumb if state is not provided', () => { - ErrorReporting().errorReporting.breadcrumb( + (ErrorReporting()?.errorReporting as ExtensionPoint)?.breadcrumb?.( mockPluginEngine, mockErrReportingProviderClient, 'dummy breadcrumb', - mockLogger, + defaultLogger, ); expect(mockPluginEngine.invokeSingle).toHaveBeenCalledTimes(1); @@ -221,7 +245,7 @@ describe('Plugin - ErrorReporting', () => { 'errorReportingProvider.breadcrumb', mockErrReportingProviderClient, 'dummy breadcrumb', - mockLogger, + defaultLogger, ); }); }); diff --git a/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts b/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts index 1853279900..59c883c6fa 100644 --- a/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/errorReporting/utils.test.ts @@ -1,5 +1,7 @@ /* eslint-disable max-classes-per-file */ import { signal } from '@preact/signals-core'; +import type { ErrorEventPayload } from '@rudderstack/analytics-js-common/types/Metrics'; +import { mergeDeepRight } from '@rudderstack/analytics-js-common/utilities/object'; import { ErrorFormat } from '../../src/errorReporting/event/event'; import * as errorReportingConstants from '../../src/errorReporting/constants'; import { @@ -14,11 +16,162 @@ import { getConfigForPayloadCreation, isAllowedToBeNotified, } from '../../src/errorReporting/utils'; +import { state } from '../../__mocks__/state'; jest.mock('@rudderstack/analytics-js-common/utilities/uuId', () => ({ generateUUID: jest.fn().mockReturnValue('test_uuid'), })); +const DEFAULT_STATE_DATA = { + autoTrack: { + enabled: false, + pageLifecycle: { + enabled: false, + }, + }, + capabilities: { + isAdBlocked: false, + isBeaconAvailable: false, + isCryptoAvailable: false, + isIE11: false, + isLegacyDOM: false, + isOnline: true, + isUaCHAvailable: false, + storage: { + isCookieStorageAvailable: false, + isLocalStorageAvailable: false, + isSessionStorageAvailable: false, + }, + }, + consents: { + data: {}, + enabled: false, + initialized: false, + postConsent: {}, + preConsent: { + enabled: false, + }, + resolutionStrategy: 'and', + }, + context: { + app: { + installType: 'cdn', + name: 'RudderLabs JavaScript SDK', + namespace: 'com.rudderlabs.javascript', + version: 'dev-snapshot', + }, + device: null, + library: { + name: 'RudderLabs JavaScript SDK', + version: 'dev-snapshot', + }, + locale: null, + network: null, + os: { + name: '', + version: '', + }, + screen: { + density: 0, + height: 0, + innerHeight: 0, + innerWidth: 0, + width: 0, + }, + userAgent: '', + }, + dataPlaneEvents: { + deliveryEnabled: true, + }, + lifecycle: { + initialized: false, + loaded: false, + logLevel: 'ERROR', + readyCallbacks: [], + }, + loadOptions: { + beaconQueueOptions: {}, + bufferDataPlaneEventsUntilReady: false, + configUrl: 'https://api.rudderstack.com', + dataPlaneEventsBufferTimeout: 1000, + destinationsQueueOptions: {}, + integrations: { + All: true, + }, + loadIntegration: true, + lockIntegrationsVersion: false, + lockPluginsVersion: false, + logLevel: 'ERROR', + plugins: [], + polyfillIfRequired: true, + queueOptions: {}, + sameSiteCookie: 'Lax', + sendAdblockPageOptions: {}, + sessions: { + autoTrack: true, + timeout: 1800000, + }, + storage: { + cookie: {}, + encryption: { + version: 'v3', + }, + migrate: true, + }, + uaChTrackLevel: 'none', + useBeacon: false, + useGlobalIntegrationsConfigInEvents: false, + useServerSideCookies: false, + }, + metrics: { + dropped: 0, + queued: 0, + retries: 0, + sent: 0, + triggered: 0, + }, + nativeDestinations: { + activeDestinations: [], + clientDestinationsReady: false, + configuredDestinations: [], + failedDestinations: [], + initializedDestinations: [], + integrationsConfig: {}, + loadIntegration: true, + loadOnlyIntegrations: {}, + }, + plugins: { + activePlugins: [], + failedPlugins: [], + loadedPlugins: [], + pluginsToLoadFromConfig: [], + ready: false, + totalPluginsToLoad: 0, + }, + reporting: { + breadcrumbs: [], + isErrorReportingEnabled: false, + isErrorReportingPluginLoaded: false, + isMetricsReportingEnabled: false, + }, + serverCookies: { + isEnabledServerSideCookies: false, + }, + session: { + initialReferrer: '', + initialReferringDomain: '', + }, + source: { + id: 'dummy-source-id', + workspaceId: 'dummy-workspace-id', + }, + storage: { + entries: {}, + migrate: false, + trulyAnonymousTracking: false, + }, +}; + describe('Error Reporting utilities', () => { describe('createNewBreadcrumb', () => { it('should create and return a breadcrumb', () => { @@ -84,7 +237,7 @@ describe('Error Reporting utilities', () => { }); describe('isRudderSDKError', () => { - const testCaseData = [ + const testCaseData: any[] = [ ['https://invalid-domain.com/rsa.min.js', true], ['https://invalid-domain.com/rss.min.js', false], ['https://invalid-domain.com/rsa-plugins-Beacon.min.js', true], @@ -103,7 +256,7 @@ describe('Error Reporting utilities', () => { it.each(testCaseData)( 'if script src is "%s" then it should return the value as "%s" ', - (scriptSrc, expectedValue) => { + (scriptSrc: string, expectedValue: boolean) => { // Bugsnag error event object structure const event = { stacktrace: [ @@ -155,13 +308,16 @@ describe('Error Reporting utilities', () => { describe('getAppStateForMetadata', () => { const origAppStateExcludes = errorReportingConstants.APP_STATE_EXCLUDE_KEYS; - beforeEach(() => { - errorReportingConstants.APP_STATE_EXCLUDE_KEYS = origAppStateExcludes; + afterEach(() => { + Object.defineProperty(errorReportingConstants, 'APP_STATE_EXCLUDE_KEYS', { + value: origAppStateExcludes, + writable: true, + }); }); // Here we are just exploring different combinations of data where // the signals could be buried inside objects, arrays, nested objects, etc. - const tcData = [ + const tcData: any[][] = [ [ { name: 'test', @@ -277,6 +433,7 @@ describe('Error Reporting utilities', () => { ], [ { + // eslint-disable-next-line compat/compat someKey: BigInt(123), }, {}, @@ -285,13 +442,20 @@ describe('Error Reporting utilities', () => { ]; it.each(tcData)('should convert signals to JSON %#', (input, expected, excludes) => { - errorReportingConstants.APP_STATE_EXCLUDE_KEYS = excludes; + Object.defineProperty(errorReportingConstants, 'APP_STATE_EXCLUDE_KEYS', { + value: excludes, + writable: true, + }); + expect(getAppStateForMetadata(input)).toEqual(expected); }); }); describe('getBugsnagErrorEvent', () => { it('should return enhanced error event payload', () => { + state.session.sessionInfo.value = { id: 123 }; + state.autoTrack.pageLifecycle.visitId.value = 'test-visit-id'; + const newError = new Error(); const normalizedError = Object.create(newError, { message: { value: 'ReferenceError: testUndefinedFn is not defined' }, @@ -304,29 +468,15 @@ describe('Error Reporting utilities', () => { unhandled: false, severityReason: { type: 'handledException' }, }; - const errorPayload = ErrorFormat.create(normalizedError, 'notify()'); + const errorPayload = ErrorFormat.create(normalizedError, 'notify()') as ErrorFormat; - const appState = { - context: { - locale: signal('en-GB'), - userAgent: signal('sample user agent'), - app: signal({ version: 'sample_version', installType: 'sample_installType' }), - }, - lifecycle: { - writeKey: signal('sample-write-key'), - }, - reporting: { - breadcrumbs: signal([]), - }, - source: signal({ id: 'sample_source_id' }), - }; (window as any).RudderSnippetVersion = 'sample_snippet_version'; - const enhancedError = getBugsnagErrorEvent(errorPayload, errorState, appState); + const enhancedError = getBugsnagErrorEvent(errorPayload, errorState, state); console.log(JSON.stringify(enhancedError)); const expectedOutcome = { notifier: { name: 'RudderStack JavaScript SDK Error Notifier', - version: 'sample_version', + version: 'dev-snapshot', url: 'https://github.com/rudderlabs/rudder-sdk-js', }, events: [ @@ -355,12 +505,11 @@ describe('Error Reporting utilities', () => { type: 'handledException', }, app: { - version: 'sample_version', + version: 'dev-snapshot', releaseStage: 'development', }, device: { - locale: 'en-GB', - userAgent: 'sample user agent', + userAgent: '', time: expect.any(Date), }, request: { @@ -372,33 +521,24 @@ describe('Error Reporting utilities', () => { metaData: { sdk: { name: 'JS', - installType: 'sample_installType', + installType: 'cdn', }, - state: { - context: { - userAgent: 'sample user agent', - locale: 'en-GB', - app: { - version: 'sample_version', - installType: 'sample_installType', + state: mergeDeepRight(DEFAULT_STATE_DATA, { + autoTrack: { + pageLifecycle: { + visitId: 'test-visit-id', }, }, - lifecycle: { - writeKey: 'sample-write-key', - }, - reporting: { - breadcrumbs: [], + session: { + sessionInfo: { id: 123 }, }, - source: { - id: 'sample_source_id', - }, - }, + }), source: { snippetVersion: 'sample_snippet_version', }, }, user: { - id: 'sample_source_id', + id: 'dummy-source-id..123..test-visit-id', }, }, ], @@ -409,14 +549,6 @@ describe('Error Reporting utilities', () => { describe('getErrorDeliveryPayload', () => { it('should return error delivery payload', () => { - const appState = { - lifecycle: { - writeKey: signal('sample-write-key'), - }, - context: { - app: signal({ version: 'sample_version', installType: 'sample_installType' }), - }, - }; const enhancedErrorPayload = { notifier: { name: 'Rudderstack JavaScript SDK Error Notifier', @@ -449,12 +581,11 @@ describe('Error Reporting utilities', () => { type: 'handledException', }, app: { - version: 'sample_version', + version: 'dev-snapshot', releaseStage: 'development', }, device: { - locale: 'en-GB', - userAgent: 'sample user agent', + userAgent: '', time: expect.any(Date), }, request: { @@ -466,18 +597,11 @@ describe('Error Reporting utilities', () => { metaData: { sdk: { name: 'JS', - installType: 'sample_installType', + installType: 'cdn', }, - state: { - context: { - userAgent: 'sample user agent', - locale: 'en-GB', - app: 'sample_version', - }, - lifecycle: { - writeKey: 'sample-write-key', - }, - breadcrumbs: [], + state: DEFAULT_STATE_DATA, + source: { + snippetVersion: 'sample_snippet_version', }, }, user: { @@ -485,18 +609,17 @@ describe('Error Reporting utilities', () => { }, }, ], - }; + } as unknown as ErrorEventPayload; - const deliveryPayload = getErrorDeliveryPayload(enhancedErrorPayload, appState); + const deliveryPayload = getErrorDeliveryPayload(enhancedErrorPayload, state); expect(deliveryPayload).toEqual( JSON.stringify({ version: '1', message_id: 'test_uuid', source: { name: 'js', - sdk_version: 'sample_version', - write_key: 'sample-write-key', - install_type: 'sample_installType', + sdk_version: 'dev-snapshot', + install_type: 'cdn', }, errors: enhancedErrorPayload, }), @@ -515,7 +638,11 @@ describe('Error Reporting utilities', () => { }); it('should return the config for payload creation in case of unhandledPromiseRejection', () => { - const error = new PromiseRejectionEvent('test error'); + // eslint-disable-next-line compat/compat + const error = new PromiseRejectionEvent('test error', { + promise: Promise.resolve(), + reason: 'test error', + }); const config = getConfigForPayloadCreation(error, 'unhandledPromiseRejection'); expect(config).toEqual({ component: 'unhandledrejection handler', diff --git a/packages/analytics-js-plugins/__tests__/iubendaConsentManager/index.test.ts b/packages/analytics-js-plugins/__tests__/iubendaConsentManager/index.test.ts index 536cdf9430..7c90642a0a 100644 --- a/packages/analytics-js-plugins/__tests__/iubendaConsentManager/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/iubendaConsentManager/index.test.ts @@ -1,13 +1,15 @@ -import { state, resetState } from '@rudderstack/analytics-js/state'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { StoreManager } from '@rudderstack/analytics-js/services/StoreManager/StoreManager'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import { resetState, state } from '../../__mocks__/state'; import { IubendaConsentManager } from '../../src/iubendaConsentManager'; import { IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME } from '../../src/iubendaConsentManager/constants'; describe('Plugin - IubendaConsentManager', () => { beforeEach(() => { resetState(); + // eslint-disable-next-line no-underscore-dangle (window as any)._iub = { cs: { consent: {} } }; (window as any).getIubendaUserConsentedPurposes = undefined; (window as any).getIubendaUserDeniedPurposes = undefined; @@ -19,24 +21,14 @@ describe('Plugin - IubendaConsentManager', () => { }); }); - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - log: jest.fn(), - }; - - const mockErrorHandler = { - onError: jest.fn(), - }; - it('should add IubendaConsentManager plugin in the loaded plugin list', () => { - IubendaConsentManager().initialize(state); + IubendaConsentManager()?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('IubendaConsentManager')).toBe(true); }); it('should initialize the plugin if iubenda consent data is already available on the window object', () => { // Initialize the plugin - IubendaConsentManager().consentManager.init(state, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); expect((window as any).getIubendaUserConsentedPurposes).toEqual(expect.any(Function)); expect((window as any).getIubendaUserDeniedPurposes).toEqual(expect.any(Function)); @@ -45,27 +37,32 @@ describe('Plugin - IubendaConsentManager', () => { it('should update state with consents data from iubenda window resources', () => { // Mock the iubenda data on the window object + // eslint-disable-next-line no-underscore-dangle (window as any)._iub.cs.consent = { - timestamp: "2024-10-1T01:57:25.825Z", - version: "1.67.1", + timestamp: '2024-10-1T01:57:25.825Z', + version: '1.67.1', purposes: { '1': true, '2': false, '3': false, '4': true, - '5': true + '5': true, }, id: 252372, cons: { - rand: "92f72a" - } + rand: '92f72a', + }, }; // Initialize the plugin - IubendaConsentManager().consentManager.init(state, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Update the state with the consent data - IubendaConsentManager().consentManager.updateConsentsInfo(state, undefined, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).updateConsentsInfo?.( + state, + undefined, + defaultLogger, + ); expect(state.consents.initialized.value).toBe(true); expect(state.consents.data.value).toStrictEqual({ @@ -73,17 +70,13 @@ describe('Plugin - IubendaConsentManager', () => { deniedConsentIds: ['2', '3'], }); - expect((window as any).getIubendaUserConsentedPurposes()).toStrictEqual([ - '1', - '4', - '5', - ]); + expect((window as any).getIubendaUserConsentedPurposes()).toStrictEqual(['1', '4', '5']); expect((window as any).getIubendaUserDeniedPurposes()).toStrictEqual(['2', '3']); }); it('should return undefined values when the window callbacks are invoked and there is no data in the state', () => { // Initialize the plugin - IubendaConsentManager().consentManager.init(state, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); expect((window as any).getIubendaUserConsentedPurposes()).toStrictEqual(undefined); expect((window as any).getIubendaUserDeniedPurposes()).toStrictEqual(undefined); @@ -91,24 +84,25 @@ describe('Plugin - IubendaConsentManager', () => { it('should define a callback function on window to update consent data', () => { // Mock the iubenda data on the window object + // eslint-disable-next-line no-underscore-dangle (window as any)._iub.cs.consent = { - timestamp: "2024-10-1T01:57:25.825Z", - version: "1.67.1", + timestamp: '2024-10-1T01:57:25.825Z', + version: '1.67.1', purposes: { '1': true, '2': false, '3': false, '4': true, - '5': true + '5': true, }, id: 252372, cons: { - rand: "92f72a" - } + rand: '92f72a', + }, }; // Initialize the plugin - IubendaConsentManager().consentManager.init(state, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Call the callback function (window as any).updateIubendaConsent({ @@ -116,7 +110,7 @@ describe('Plugin - IubendaConsentManager', () => { '2': true, '3': false, '4': true, - '5': false + '5': false, }); expect(state.consents.data.value).toStrictEqual({ @@ -130,33 +124,34 @@ describe('Plugin - IubendaConsentManager', () => { it('should get consent data from iubenda cookies if iubenda consent data is not available on the window object', () => { const iubendaRawConsentData = { - timestamp: "2024-10-1T01:57:25.825Z", - version: "1.67.1", + timestamp: '2024-10-1T01:57:25.825Z', + version: '1.67.1', purposes: { '1': true, '2': false, '3': false, '4': true, - '5': true + '5': true, }, id: 252372, cons: { - rand: "92f72a" - } + rand: '92f72a', + }, }; const iubendaConsentString = JSON.stringify(iubendaRawConsentData); // Mock the iubenda cookies document.cookie = `${IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME}=${encodeURIComponent(iubendaConsentString)};`; - const pluginsManager = new PluginsManager(defaultPluginEngine, undefined, mockLogger); - const storeManager = new StoreManager(pluginsManager, undefined, mockLogger); - // Initialize the plugin - IubendaConsentManager().consentManager.init(state, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Update the state with the consent data - IubendaConsentManager().consentManager.updateConsentsInfo(state, storeManager, mockLogger); + (IubendaConsentManager()?.consentManager as ExtensionPoint).updateConsentsInfo?.( + state, + defaultStoreManager, + defaultLogger, + ); expect(state.consents.initialized.value).toBe(true); expect(state.consents.data.value).toStrictEqual({ @@ -164,21 +159,17 @@ describe('Plugin - IubendaConsentManager', () => { deniedConsentIds: ['2', '3'], }); - expect((window as any).getIubendaUserConsentedPurposes()).toStrictEqual([ - '1', - '4', - '5', - ]); + expect((window as any).getIubendaUserConsentedPurposes()).toStrictEqual(['1', '4', '5']); expect((window as any).getIubendaUserDeniedPurposes()).toStrictEqual(['2', '3']); }); it('should return true if the consent manager is not initialized', () => { expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, undefined, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -197,11 +188,11 @@ describe('Plugin - IubendaConsentManager', () => { }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -218,18 +209,19 @@ describe('Plugin - IubendaConsentManager', () => { blacklistedEvents: [], whitelistedEvents: [], eventFilteringOption: 'disable', - consentManagement: [{ - provider: 'iubenda', - }], - + consentManagement: [ + { + provider: 'iubenda', + }, + ], }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -246,21 +238,21 @@ describe('Plugin - IubendaConsentManager', () => { blacklistedEvents: [], whitelistedEvents: [], eventFilteringOption: 'disable', - consentManagement:[ + consentManagement: [ { provider: 'iubenda', consents: [{ consent: '1' }, { consent: '2' }], - resolutionStrategy: 'or' - } + resolutionStrategy: 'or', + }, ], }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -277,21 +269,21 @@ describe('Plugin - IubendaConsentManager', () => { blacklistedEvents: [], whitelistedEvents: [], eventFilteringOption: 'disable', - consentManagement:[ + consentManagement: [ { provider: 'iubenda', consents: [{ consent: '2' }, { consent: '4' }], - resolutionStrategy: 'and' - } + resolutionStrategy: 'and', + }, ], }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(false); }); @@ -299,6 +291,7 @@ describe('Plugin - IubendaConsentManager', () => { it('should return true and log an error if any exception is thrown while checking if the destination is consented', () => { state.consents.initialized.value = true; state.consents.data.value = { + // @ts-expect-error Intentionally setting null to test the error handling allowedConsentIds: null, // This will throw an exception deniedConsentIds: ['2', '4'], }; @@ -308,24 +301,23 @@ describe('Plugin - IubendaConsentManager', () => { blacklistedEvents: [], whitelistedEvents: [], eventFilteringOption: 'disable', - consentManagement:[ + consentManagement: [ { provider: 'iubenda', consents: [{ consent: '2' }, { consent: '4' }], - resolutionStrategy: 'and' - } + resolutionStrategy: 'and', + }, ], }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, - + defaultErrorHandler, + defaultLogger, ), ).toBe(true); - expect(mockErrorHandler.onError).toHaveBeenCalledWith( + expect(defaultErrorHandler.onError).toHaveBeenCalledWith( new TypeError("Cannot read properties of null (reading 'includes')"), 'IubendaConsentManagerPlugin', 'Failed to determine the consent status for the destination. Please check the destination configuration and try again.', @@ -365,12 +357,9 @@ describe('Plugin - IubendaConsentManager', () => { }, ], }; - const isDestinationConsented = IubendaConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + IubendaConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(false); }); @@ -396,12 +385,9 @@ describe('Plugin - IubendaConsentManager', () => { }, ], }; - const isDestinationConsented = IubendaConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + IubendaConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -438,12 +424,9 @@ describe('Plugin - IubendaConsentManager', () => { }, ], }; - const isDestinationConsented = IubendaConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + IubendaConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -470,11 +453,11 @@ describe('Plugin - IubendaConsentManager', () => { ], }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -482,7 +465,7 @@ describe('Plugin - IubendaConsentManager', () => { it('should return appropriate value when the resolution strategy not set', () => { state.consents.initialized.value = true; state.consents.provider.value = 'iubenda'; - state.consents.resolutionStrategy.value = null; + state.consents.resolutionStrategy.value = undefined; state.consents.data.value = { allowedConsentIds: ['C0001', 'C0002', 'C0003'], }; @@ -502,11 +485,11 @@ describe('Plugin - IubendaConsentManager', () => { ], }; expect( - IubendaConsentManager().consentManager.isDestinationConsented( + (IubendaConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); diff --git a/packages/analytics-js-plugins/__tests__/iubendaConsentManager/utils.test.ts b/packages/analytics-js-plugins/__tests__/iubendaConsentManager/utils.test.ts index 69318eb680..5f221c7958 100644 --- a/packages/analytics-js-plugins/__tests__/iubendaConsentManager/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/iubendaConsentManager/utils.test.ts @@ -1,21 +1,15 @@ -import { state, resetState } from '@rudderstack/analytics-js/state'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { StoreManager } from '@rudderstack/analytics-js/services/StoreManager/StoreManager'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import type { IStoreManager } from '@rudderstack/analytics-js-common/types/Store'; +import { resetState, state } from '../../__mocks__/state'; import { updateConsentStateFromData, getConsentData, getIubendaConsentData, } from '../../src/iubendaConsentManager/utils'; -import { IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME} from '../../src/iubendaConsentManager/constants'; +import { IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME } from '../../src/iubendaConsentManager/constants'; describe('IubendaConsentManager - Utils', () => { - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - log: jest.fn(), - }; - beforeEach(() => { resetState(); @@ -76,32 +70,29 @@ describe('IubendaConsentManager - Utils', () => { }); describe('getIubendaConsentData', () => { - const pluginsManager = new PluginsManager(defaultPluginEngine, undefined, mockLogger); - const storeManager = new StoreManager(pluginsManager, undefined, mockLogger); - it('should get the iubenda consent data from cookies', () => { // Mock the iubenda data in the cookies const iubendaRawConsentData = { - timestamp: "2024-10-1T01:57:25.825Z", - version: "1.67.1", + timestamp: '2024-10-1T01:57:25.825Z', + version: '1.67.1', purposes: { '1': true, '2': false, '3': true, '4': false, - '5': true + '5': true, }, id: 252372, cons: { - rand: "92f72a" - } + rand: '92f72a', + }, }; const iubendaConsentString = JSON.stringify(iubendaRawConsentData); // Mock the iubenda cookies document.cookie = `${IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME}=${window.encodeURIComponent(iubendaConsentString)};`; - const iubendaConsentData = getIubendaConsentData(storeManager, mockLogger); + const iubendaConsentData = getIubendaConsentData(defaultStoreManager, defaultLogger); expect(iubendaConsentData).toStrictEqual({ '1': true, @@ -118,26 +109,25 @@ describe('IubendaConsentManager - Utils', () => { setStore: () => ({ engine: null, }), - }; + } as unknown as IStoreManager; - const iubendaConsentData = getIubendaConsentData(mockStoreManager, mockLogger); + const iubendaConsentData = getIubendaConsentData(mockStoreManager, defaultLogger); expect(iubendaConsentData).toBeUndefined(); }); it('should return undefined if iubenda consent cookie is not present', () => { - const iubendaConsentData = getIubendaConsentData(storeManager, mockLogger); + const iubendaConsentData = getIubendaConsentData(defaultStoreManager, defaultLogger); expect(iubendaConsentData).toBeUndefined(); }); - it('should return undefined if iubenda consent data inside the cookie is null', () => { // Mock the iubenda cookie // The value is inside is null document.cookie = `${IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME}=null;`; - const iubendaConsentData = getIubendaConsentData(storeManager, mockLogger); + const iubendaConsentData = getIubendaConsentData(defaultStoreManager, defaultLogger); expect(iubendaConsentData).toBeUndefined(); }); @@ -147,7 +137,7 @@ describe('IubendaConsentManager - Utils', () => { // The value is inside is empty string document.cookie = `${IUBENDA_CONSENT_EXAMPLE_COOKIE_NAME}=%22%22;`; - const iubendaConsentData = getIubendaConsentData(storeManager, mockLogger); + const iubendaConsentData = getIubendaConsentData(defaultStoreManager, defaultLogger); expect(iubendaConsentData).toBeUndefined(); }); diff --git a/packages/analytics-js-plugins/__tests__/ketchConsentManager/index.test.ts b/packages/analytics-js-plugins/__tests__/ketchConsentManager/index.test.ts index 3404beb04f..b02cd1e16d 100644 --- a/packages/analytics-js-plugins/__tests__/ketchConsentManager/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/ketchConsentManager/index.test.ts @@ -1,7 +1,8 @@ -import { state, resetState } from '@rudderstack/analytics-js/state'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { StoreManager } from '@rudderstack/analytics-js/services/StoreManager/StoreManager'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import { resetState, state } from '../../__mocks__/state'; import { KetchConsentManager } from '../../src/ketchConsentManager'; describe('Plugin - KetchConsentManager', () => { @@ -18,24 +19,14 @@ describe('Plugin - KetchConsentManager', () => { }); }); - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - log: jest.fn(), - }; - - const mockErrorHandler = { - onError: jest.fn(), - }; - it('should add KetchConsentManager plugin in the loaded plugin list', () => { - KetchConsentManager().initialize(state); + KetchConsentManager()?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('KetchConsentManager')).toBe(true); }); it('should initialize the plugin if ketch consent data is already available on the window object', () => { // Initialize the plugin - KetchConsentManager().consentManager.init(state, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); expect((window as any).getKetchUserConsentedPurposes).toEqual(expect.any(Function)); expect((window as any).getKetchUserDeniedPurposes).toEqual(expect.any(Function)); @@ -53,10 +44,14 @@ describe('Plugin - KetchConsentManager', () => { }; // Initialize the plugin - KetchConsentManager().consentManager.init(state, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Update the state with the consent data - KetchConsentManager().consentManager.updateConsentsInfo(state, undefined, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).updateConsentsInfo?.( + state, + undefined, + defaultLogger, + ); expect(state.consents.initialized.value).toBe(true); expect(state.consents.data.value).toStrictEqual({ @@ -74,7 +69,7 @@ describe('Plugin - KetchConsentManager', () => { it('should return undefined values when the window callbacks are invoked and there is no data in the state', () => { // Initialize the plugin - KetchConsentManager().consentManager.init(state, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); expect((window as any).getKetchUserConsentedPurposes()).toStrictEqual(undefined); expect((window as any).getKetchUserDeniedPurposes()).toStrictEqual(undefined); @@ -91,7 +86,7 @@ describe('Plugin - KetchConsentManager', () => { }; // Initialize the plugin - KetchConsentManager().consentManager.init(state, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Call the callback function (window as any).updateKetchConsent({ @@ -138,14 +133,15 @@ describe('Plugin - KetchConsentManager', () => { // Mock the ketch cookies document.cookie = `_ketch_consent_v1_=${window.btoa(ketchConsentString)};`; - const pluginsManager = new PluginsManager(defaultPluginEngine, undefined, mockLogger); - const storeManager = new StoreManager(pluginsManager, undefined, mockLogger); - // Initialize the plugin - KetchConsentManager().consentManager.init(state, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Update the state with the consent data - KetchConsentManager().consentManager.updateConsentsInfo(state, storeManager, mockLogger); + (KetchConsentManager()?.consentManager as ExtensionPoint).updateConsentsInfo?.( + state, + defaultStoreManager, + defaultLogger, + ); expect(state.consents.initialized.value).toBe(true); expect(state.consents.data.value).toStrictEqual({ @@ -163,11 +159,11 @@ describe('Plugin - KetchConsentManager', () => { it('should return true if the consent manager is not initialized', () => { expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, undefined, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -186,11 +182,11 @@ describe('Plugin - KetchConsentManager', () => { }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -210,11 +206,11 @@ describe('Plugin - KetchConsentManager', () => { }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -241,11 +237,11 @@ describe('Plugin - KetchConsentManager', () => { }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -272,11 +268,11 @@ describe('Plugin - KetchConsentManager', () => { }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(false); }); @@ -284,6 +280,7 @@ describe('Plugin - KetchConsentManager', () => { it('should return true and log an error if any exception is thrown while checking if the destination is consented', () => { state.consents.initialized.value = true; state.consents.data.value = { + // @ts-expect-error Intentionally set to null to throw an exception allowedConsentIds: null, // This will throw an exception deniedConsentIds: ['purpose2', 'purpose4'], }; @@ -303,14 +300,14 @@ describe('Plugin - KetchConsentManager', () => { }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); - expect(mockErrorHandler.onError).toHaveBeenCalledWith( + expect(defaultErrorHandler.onError).toHaveBeenCalledWith( new TypeError("Cannot read properties of null (reading 'includes')"), 'KetchConsentManagerPlugin', 'Failed to determine the consent status for the destination. Please check the destination configuration and try again.', @@ -350,12 +347,9 @@ describe('Plugin - KetchConsentManager', () => { }, ], }; - const isDestinationConsented = KetchConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + KetchConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(false); }); @@ -381,12 +375,9 @@ describe('Plugin - KetchConsentManager', () => { }, ], }; - const isDestinationConsented = KetchConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + KetchConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -423,12 +414,9 @@ describe('Plugin - KetchConsentManager', () => { }, ], }; - const isDestinationConsented = KetchConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + KetchConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -455,11 +443,11 @@ describe('Plugin - KetchConsentManager', () => { ], }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); @@ -467,7 +455,7 @@ describe('Plugin - KetchConsentManager', () => { it('should return appropriate value when the resolution strategy not set', () => { state.consents.initialized.value = true; state.consents.provider.value = 'ketch'; - state.consents.resolutionStrategy.value = null; + state.consents.resolutionStrategy.value = undefined; state.consents.data.value = { allowedConsentIds: ['C0001', 'C0002', 'C0003'], }; @@ -487,11 +475,11 @@ describe('Plugin - KetchConsentManager', () => { ], }; expect( - KetchConsentManager().consentManager.isDestinationConsented( + (KetchConsentManager()?.consentManager as ExtensionPoint).isDestinationConsented?.( state, destConfig, - mockErrorHandler, - mockLogger, + defaultErrorHandler, + defaultLogger, ), ).toBe(true); }); diff --git a/packages/analytics-js-plugins/__tests__/ketchConsentManager/utils.test.ts b/packages/analytics-js-plugins/__tests__/ketchConsentManager/utils.test.ts index 6843d29753..af3eb2a7c9 100644 --- a/packages/analytics-js-plugins/__tests__/ketchConsentManager/utils.test.ts +++ b/packages/analytics-js-plugins/__tests__/ketchConsentManager/utils.test.ts @@ -1,20 +1,14 @@ -import { state, resetState } from '@rudderstack/analytics-js/state'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { StoreManager } from '@rudderstack/analytics-js/services/StoreManager/StoreManager'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import { resetState, state } from '../../__mocks__/state'; import { updateConsentStateFromData, getConsentData, getKetchConsentData, } from '../../src/ketchConsentManager/utils'; +import { defaultPluginsManager } from '../../__mocks__/PluginsManager'; describe('KetchConsentManager - Utils', () => { - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - log: jest.fn(), - }; - beforeEach(() => { resetState(); @@ -75,9 +69,6 @@ describe('KetchConsentManager - Utils', () => { }); describe('getKetchConsentData', () => { - const pluginsManager = new PluginsManager(defaultPluginEngine, undefined, mockLogger); - const storeManager = new StoreManager(pluginsManager, undefined, mockLogger); - it('should get the ketch consent data from cookies', () => { // Mock the ketch data in the cookies const ketchRawConsentData = { @@ -102,7 +93,7 @@ describe('KetchConsentManager - Utils', () => { // Mock the ketch cookies document.cookie = `_ketch_consent_v1_=${window.btoa(ketchConsentString)};`; - const ketchConsentData = getKetchConsentData(storeManager, mockLogger); + const ketchConsentData = getKetchConsentData(defaultStoreManager, defaultLogger); expect(ketchConsentData).toStrictEqual({ purpose1: true, @@ -121,17 +112,17 @@ describe('KetchConsentManager - Utils', () => { }), }; - const ketchConsentData = getKetchConsentData(mockStoreManager, mockLogger); + const ketchConsentData = getKetchConsentData(mockStoreManager, defaultLogger); expect(ketchConsentData).toBeUndefined(); - expect(mockLogger.error).toHaveBeenCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'KetchConsentManagerPlugin:: Failed to read the consent cookie.', new TypeError("Cannot read properties of null (reading 'getItem')"), ); }); it('should return undefined if ketch consent cookie is not present', () => { - const ketchConsentData = getKetchConsentData(storeManager, mockLogger); + const ketchConsentData = getKetchConsentData(defaultStoreManager, defaultLogger); expect(ketchConsentData).toBeUndefined(); }); @@ -141,10 +132,10 @@ describe('KetchConsentManager - Utils', () => { // The value is not Base64 encoded document.cookie = `_ketch_consent_v1_=abc;`; - const ketchConsentData = getKetchConsentData(storeManager, mockLogger); + const ketchConsentData = getKetchConsentData(defaultStoreManager, defaultLogger); expect(ketchConsentData).toBeUndefined(); - expect(mockLogger.error).toHaveBeenCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'KetchConsentManagerPlugin:: Failed to parse the consent cookie.', new SyntaxError('Unexpected token \'i\', "i�" is not valid JSON'), ); @@ -155,10 +146,10 @@ describe('KetchConsentManager - Utils', () => { // The value is not JSON stringified document.cookie = `_ketch_consent_v1_=YWJjZGU=;`; - const ketchConsentData = getKetchConsentData(storeManager, mockLogger); + const ketchConsentData = getKetchConsentData(defaultStoreManager, defaultLogger); expect(ketchConsentData).toBeUndefined(); - expect(mockLogger.error).toHaveBeenCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'KetchConsentManagerPlugin:: Failed to parse the consent cookie.', new SyntaxError('Unexpected token \'a\', "abcde" is not valid JSON'), ); @@ -169,7 +160,7 @@ describe('KetchConsentManager - Utils', () => { // The value is inside is null document.cookie = `_ketch_consent_v1_=bnVsbA==;`; - const ketchConsentData = getKetchConsentData(storeManager, mockLogger); + const ketchConsentData = getKetchConsentData(defaultStoreManager, defaultLogger); expect(ketchConsentData).toBeUndefined(); }); @@ -179,7 +170,7 @@ describe('KetchConsentManager - Utils', () => { // The value is inside is empty string document.cookie = `_ketch_consent_v1_=IiI=;`; - const ketchConsentData = getKetchConsentData(storeManager, mockLogger); + const ketchConsentData = getKetchConsentData(defaultStoreManager, defaultLogger); expect(ketchConsentData).toBeUndefined(); }); diff --git a/packages/analytics-js-plugins/__tests__/nativeDestinationQueue/utilities.test.ts b/packages/analytics-js-plugins/__tests__/nativeDestinationQueue/utilities.test.ts index 3710e18f69..10f64648c7 100644 --- a/packages/analytics-js-plugins/__tests__/nativeDestinationQueue/utilities.test.ts +++ b/packages/analytics-js-plugins/__tests__/nativeDestinationQueue/utilities.test.ts @@ -1,9 +1,10 @@ import { clone } from 'ramda'; +import type { Destination } from '@rudderstack/analytics-js-common/types/Destination'; import { isEventDenyListed } from '../../src/nativeDestinationQueue/utilities'; describe('nativeDestinationQueue Plugin - utilities', () => { describe('isEventDenyListed', () => { - const mockDestination = { + const destination = { id: 'sample-destination-id', displayName: 'Destination Display Name', userFriendlyId: 'ID_sample-destination-id', @@ -21,13 +22,13 @@ describe('nativeDestinationQueue Plugin - utilities', () => { analytics: {}, isLoaded: () => true, }, - }; + } as unknown as Destination; it('should return false if the event type is not track', () => { - const outcome1 = isEventDenyListed('identify', undefined, mockDestination); - const outcome2 = isEventDenyListed('page', undefined, mockDestination); - const outcome3 = isEventDenyListed('group', undefined, mockDestination); - const outcome4 = isEventDenyListed('alias', undefined, mockDestination); + const outcome1 = isEventDenyListed('identify', null, destination); + const outcome2 = isEventDenyListed('page', null, destination); + const outcome3 = isEventDenyListed('group', null, destination); + const outcome4 = isEventDenyListed('alias', null, destination); expect(outcome1).toBeFalsy(); expect(outcome2).toBeFalsy(); @@ -36,62 +37,66 @@ describe('nativeDestinationQueue Plugin - utilities', () => { }); it('should return false if deny list is selected and track event does not have event name property', () => { - const mockDest = clone(mockDestination); + const mockDest = clone(destination); mockDest.config.eventFilteringOption = 'blacklistedEvents'; - const outcome1 = isEventDenyListed('track', undefined, mockDest); + const outcome1 = isEventDenyListed('track', null, mockDest); expect(outcome1).toBeFalsy(); }); it('should return false if deny list is selected and track event name is not string', () => { - const mockDest = clone(mockDestination); + const mockDest = clone(destination); mockDest.config.eventFilteringOption = 'blacklistedEvents'; + // @ts-expect-error eventName is not a string const outcome1 = isEventDenyListed('track', true, mockDest); + // @ts-expect-error eventName is not a string const outcome2 = isEventDenyListed('track', 12345, mockDest); expect(outcome1).toBeFalsy(); expect(outcome2).toBeFalsy(); }); it('should return true if deny list is selected and track event name matches with denylist event name', () => { - const mockDest = clone(mockDestination); + const mockDest = clone(destination); mockDest.config.eventFilteringOption = 'blacklistedEvents'; const outcome1 = isEventDenyListed('track', 'sample track event 2', mockDest); expect(outcome1).toBeTruthy(); }); it('should return false if deny list is selected and track event name does not matches with denylist event name', () => { - const mockDest = clone(mockDestination); + const mockDest = clone(destination); mockDest.config.eventFilteringOption = 'blacklistedEvents'; const outcome1 = isEventDenyListed('track', 'sample track event 1234', mockDest); expect(outcome1).toBeFalsy(); }); it('should return false if deny list is selected and track event name is in different case than with denylist event name', () => { - const mockDest = clone(mockDestination); + const mockDest = clone(destination); mockDest.config.eventFilteringOption = 'blacklistedEvents'; const outcome1 = isEventDenyListed('track', 'Sample track event 2', mockDest); expect(outcome1).toBeFalsy(); }); it('should return true if allow list is selected and track event does not have event name property', () => { - const outcome1 = isEventDenyListed('track', undefined, mockDestination); + const outcome1 = isEventDenyListed('track', null, destination); expect(outcome1).toBeTruthy(); }); it('should return true if allow list is selected and track event name is not string', () => { - const outcome1 = isEventDenyListed('track', true, mockDestination); - const outcome2 = isEventDenyListed('track', 12345, mockDestination); + // @ts-expect-error eventName is not a string + const outcome1 = isEventDenyListed('track', true, destination); + // @ts-expect-error eventName is not a string + const outcome2 = isEventDenyListed('track', 12345, destination); expect(outcome1).toBeTruthy(); expect(outcome2).toBeTruthy(); }); it('should return false if allow list is selected and track event name matches with allowlist event name', () => { - const outcome1 = isEventDenyListed('track', 'sample track event 1', mockDestination); + const outcome1 = isEventDenyListed('track', 'sample track event 1', destination); expect(outcome1).toBeFalsy(); }); it('should return true if allow list is selected and track event name does not matches with allowlist event name', () => { - const outcome1 = isEventDenyListed('track', 'sample track event 1234', mockDestination); + const outcome1 = isEventDenyListed('track', 'sample track event 1234', destination); expect(outcome1).toBeTruthy(); }); it('should return true if allow list is selected and track event name is in different case than with with allowlist event name', () => { - const outcome1 = isEventDenyListed('track', 'Sample track event 1', mockDestination); + const outcome1 = isEventDenyListed('track', 'Sample track event 1', destination); expect(outcome1).toBeTruthy(); }); }); diff --git a/packages/analytics-js-plugins/__tests__/oneTrustConsentManager/index.test.ts b/packages/analytics-js-plugins/__tests__/oneTrustConsentManager/index.test.ts index 45efa9c923..30cab22e91 100644 --- a/packages/analytics-js-plugins/__tests__/oneTrustConsentManager/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/oneTrustConsentManager/index.test.ts @@ -1,4 +1,7 @@ -import { state, resetState } from '@rudderstack/analytics-js/state'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import { resetState, state } from '../../__mocks__/state'; import { OneTrustConsentManager } from '../../src/oneTrustConsentManager'; describe('Plugin - OneTrustConsentManager', () => { @@ -8,18 +11,8 @@ describe('Plugin - OneTrustConsentManager', () => { delete (window as any).OnetrustActiveGroups; }); - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - log: jest.fn(), - }; - - const mockErrorHandler = { - onError: jest.fn(), - }; - it('should add OneTrustConsentManager plugin in the loaded plugin list', () => { - OneTrustConsentManager().initialize(state); + OneTrustConsentManager()?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('OneTrustConsentManager')).toBe(true); }); @@ -40,10 +33,14 @@ describe('Plugin - OneTrustConsentManager', () => { (window as any).OnetrustActiveGroups = ',C0001,C0003,'; // Initialize the plugin - OneTrustConsentManager().consentManager.init(state, mockLogger); + (OneTrustConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Update the consent info from state - OneTrustConsentManager().consentManager.updateConsentsInfo(state, undefined, mockLogger); + (OneTrustConsentManager()?.consentManager as ExtensionPoint).updateConsentsInfo?.( + state, + undefined, + defaultLogger, + ); expect(state.consents.initialized.value).toBe(true); expect(state.consents.data.value).toStrictEqual({ @@ -54,13 +51,17 @@ describe('Plugin - OneTrustConsentManager', () => { it('should not successfully update consents data the plugin if OneTrust SDK is not loaded', () => { // Initialize the plugin - OneTrustConsentManager().consentManager.init(state, mockLogger); + (OneTrustConsentManager()?.consentManager as ExtensionPoint).init?.(state, defaultLogger); // Update the consent info from state - OneTrustConsentManager().consentManager.updateConsentsInfo(state, undefined, mockLogger); + (OneTrustConsentManager()?.consentManager as ExtensionPoint).updateConsentsInfo?.( + state, + undefined, + defaultLogger, + ); expect(state.consents.initialized.value).toStrictEqual(false); - expect(mockLogger.error).toHaveBeenCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( `OneTrustConsentManagerPlugin:: Failed to access OneTrust SDK resources. Please ensure that the OneTrust SDK is loaded successfully before RudderStack SDK.`, ); }); @@ -89,12 +90,9 @@ describe('Plugin - OneTrustConsentManager', () => { key: 'value', }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -116,12 +114,9 @@ describe('Plugin - OneTrustConsentManager', () => { key: 'value', }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -137,12 +132,9 @@ describe('Plugin - OneTrustConsentManager', () => { key: 'value', }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -165,12 +157,9 @@ describe('Plugin - OneTrustConsentManager', () => { ], key: 'value', }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(false); }); @@ -187,14 +176,11 @@ describe('Plugin - OneTrustConsentManager', () => { key: 'value', }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); - expect(mockErrorHandler.onError).toHaveBeenCalledWith( + expect(defaultErrorHandler.onError).toHaveBeenCalledWith( new TypeError('oneTrustCookieCategories.map is not a function'), 'OneTrustConsentManagerPlugin', 'Failed to determine the consent status for the destination. Please check the destination configuration and try again.', @@ -234,12 +220,9 @@ describe('Plugin - OneTrustConsentManager', () => { }, ], }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(false); }); @@ -265,12 +248,9 @@ describe('Plugin - OneTrustConsentManager', () => { }, ], }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -307,12 +287,9 @@ describe('Plugin - OneTrustConsentManager', () => { }, ], }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -349,12 +326,9 @@ describe('Plugin - OneTrustConsentManager', () => { }, ], }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); @@ -384,12 +358,9 @@ describe('Plugin - OneTrustConsentManager', () => { }, ], }; - const isDestinationConsented = OneTrustConsentManager().consentManager.isDestinationConsented( - state, - destConfig, - mockErrorHandler, - mockLogger, - ); + const isDestinationConsented = ( + OneTrustConsentManager()?.consentManager as ExtensionPoint + ).isDestinationConsented?.(state, destConfig, defaultErrorHandler, defaultLogger); expect(isDestinationConsented).toBe(true); }); }); diff --git a/packages/analytics-js-plugins/__tests__/storageEncryption/index.test.ts b/packages/analytics-js-plugins/__tests__/storageEncryption/index.test.ts index 877dc058a2..8f8f5ec515 100644 --- a/packages/analytics-js-plugins/__tests__/storageEncryption/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/storageEncryption/index.test.ts @@ -1,37 +1,32 @@ -import { signal } from '@preact/signals-core'; -import { clone } from 'ramda'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; import { StorageEncryption } from '../../src/storageEncryption'; +import { resetState, state } from '../../__mocks__/state'; describe('Plugin - Storage Encryption', () => { - const originalState = { - plugins: { - loadedPlugins: signal([]), - }, - }; const storageEncryption = StorageEncryption(); - let state: any; - beforeEach(() => { - state = clone(originalState); + resetState(); }); it('should add plugin in the loaded plugin list', () => { - storageEncryption.initialize(state); + storageEncryption?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('StorageEncryption')).toBe(true); }); it('should encrypt the data', () => { - const encryptedData = storageEncryption.storage?.encrypt('test-data'); + const encryptedData = (storageEncryption.storage as ExtensionPoint).encrypt?.('test-data'); expect(encryptedData).toBe('RS_ENC_v3_dGVzdC1kYXRh'); }); it('should decrypt encrypted data', () => { - const decryptedData = storageEncryption.storage?.decrypt('RS_ENC_v3_dGVzdC1kYXRh'); + const decryptedData = (storageEncryption.storage as ExtensionPoint).decrypt?.( + 'RS_ENC_v3_dGVzdC1kYXRh', + ); expect(decryptedData).toBe('test-data'); }); it('should return same data if it is not a supported encryption format', () => { - const decryptedData = storageEncryption.storage?.decrypt('test-data'); + const decryptedData = (storageEncryption.storage as ExtensionPoint).decrypt?.('test-data'); expect(decryptedData).toBe('test-data'); }); }); diff --git a/packages/analytics-js-plugins/__tests__/storageEncryptionLegacy/index.test.ts b/packages/analytics-js-plugins/__tests__/storageEncryptionLegacy/index.test.ts index bef4d7e5f2..82d2e07dad 100644 --- a/packages/analytics-js-plugins/__tests__/storageEncryptionLegacy/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/storageEncryptionLegacy/index.test.ts @@ -1,41 +1,41 @@ -import { signal } from '@preact/signals-core'; -import { clone } from 'ramda'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; import { StorageEncryptionLegacy } from '../../src/storageEncryptionLegacy'; +import { resetState, state } from '../../__mocks__/state'; describe('Plugin - Storage Encryption Legacy', () => { - const originalState = { - plugins: { - loadedPlugins: signal([]), - }, - }; const storageEncryptionLegacy = StorageEncryptionLegacy(); - let state: any; beforeEach(() => { - state = clone(originalState); + resetState(); }); it('should add plugin in the loaded plugin list', () => { - storageEncryptionLegacy.initialize(state); + storageEncryptionLegacy?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('StorageEncryptionLegacy')).toBe(true); }); it('should encrypt the data', () => { const srcData = '"1wefk7M3Y1D6EDX4ZpIE00LpKAE"'; - const encryptedData = storageEncryptionLegacy.storage?.encrypt(srcData); + const encryptedData = (storageEncryptionLegacy.storage as ExtensionPoint).encrypt?.( + srcData, + ) as string; expect(encryptedData.startsWith('RudderEncrypt:')).toBe(true); - expect(storageEncryptionLegacy.storage?.decrypt(encryptedData)).toBe(srcData); + expect((storageEncryptionLegacy.storage as ExtensionPoint).decrypt?.(encryptedData)).toBe( + srcData, + ); }); it('should decrypt encrypted data', () => { - const decryptedData = storageEncryptionLegacy.storage?.decrypt( + const decryptedData = (storageEncryptionLegacy.storage as ExtensionPoint).decrypt?.( 'RudderEncrypt:U2FsdGVkX1+5q5jikppUASe8AdIH6O2iORyF41sYXgxzIGiX9whSeVxxww0OK5h/', ); expect(decryptedData).toBe('"1wefk7M3Y1D6EDX4ZpIE00LpKAE"'); }); it('should return same data if it is not a supported encryption format', () => { - const decryptedData = storageEncryptionLegacy.storage?.decrypt('test-data'); + const decryptedData = (storageEncryptionLegacy.storage as ExtensionPoint).decrypt?.( + 'test-data', + ); expect(decryptedData).toBe('test-data'); }); }); diff --git a/packages/analytics-js-plugins/__tests__/storageMigrator/.gitkeep b/packages/analytics-js-plugins/__tests__/storageMigrator/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/analytics-js-plugins/__tests__/storageMigrator/index.test.ts b/packages/analytics-js-plugins/__tests__/storageMigrator/index.test.ts index 55a7a016df..899adbf465 100644 --- a/packages/analytics-js-plugins/__tests__/storageMigrator/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/storageMigrator/index.test.ts @@ -1,144 +1,144 @@ -import { signal } from '@preact/signals-core'; -import { clone } from 'ramda'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import { defaultLocalStorage } from '@rudderstack/analytics-js-common/__mocks__/Storage'; import { StorageMigrator } from '../../src/storageMigrator'; +import { resetState, state } from '../../__mocks__/state'; describe('Plugin - Storage Migrator', () => { - const originalState = { - plugins: { - loadedPlugins: signal([]), - }, - }; const storageMigrator = StorageMigrator(); - let state: any; beforeEach(() => { - state = clone(originalState); - }); - - let storedVal; - - const storageEngine = { - getItem: (key: string) => storedVal, - }; - - const mockLogger = { - error: jest.fn(), - }; - - const mockErrorHandler = { - onError: jest.fn(), - }; - - beforeEach(() => { - storedVal = undefined; + resetState(); }); it('should add plugin in the loaded plugin list', () => { - storageMigrator.initialize(state); + storageMigrator?.initialize?.(state); expect(state.plugins.loadedPlugins.value.includes('StorageMigrator')).toBe(true); }); it('should migrate legacy encrypted data', () => { - storedVal = 'RudderEncrypt:U2FsdGVkX1+5q5jikppUASe8AdIH6O2iORyF41sYXgxzIGiX9whSeVxxww0OK5h/'; - const migratedVal = storageMigrator.storage?.migrate( - null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem( + 'key', + 'RudderEncrypt:U2FsdGVkX1+5q5jikppUASe8AdIH6O2iORyF41sYXgxzIGiX9whSeVxxww0OK5h/', + ); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe('1wefk7M3Y1D6EDX4ZpIE00LpKAE'); }); it('should migrate v3 encrypted data', () => { - storedVal = 'RS_ENC_v3_InRlc3QtZGF0YSI='; - const migratedVal = storageMigrator.storage?.migrate( - null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem('key', 'RS_ENC_v3_InRlc3QtZGF0YSI='); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe('test-data'); }); it('should return null if the stored value is undefined', () => { - storedVal = undefined; - const migratedVal = storageMigrator.storage?.migrate( - null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem('key', undefined); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); }); it('should return null if the stored value is null', () => { - storedVal = null; - const migratedVal = storageMigrator.storage?.migrate( - null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem('key', null); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); }); it('should return null if the legacy decrypted value is undefined', () => { - storedVal = 'RudderEncrypt:U2FsdGVkX195kUN9L968e0E/eu8CtnDHWt6ALf6bm8E='; - const migratedVal = storageMigrator.storage?.migrate( - null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem( + 'key', + 'RudderEncrypt:U2FsdGVkX195kUN9L968e0E/eu8CtnDHWt6ALf6bm8E=', + ); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); }); it('should return null if the legacy decrypted value is null', () => { - storedVal = 'RudderEncrypt:U2FsdGVkX1+SMQ+LKcuk7w/nQ9DEjgU9EUmmBgb/Pfo='; - const migratedVal = storageMigrator.storage?.migrate( + defaultLocalStorage.setItem( + 'key', + 'RudderEncrypt:U2FsdGVkX1+SMQ+LKcuk7w/nQ9DEjgU9EUmmBgb/Pfo=', + ); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); }); it('should return null if the v3 decrypted value is undefined', () => { - storedVal = 'RS_ENC_v3_dW5kZWZpbmVk'; - const migratedVal = storageMigrator.storage?.migrate( - null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem('key', 'RS_ENC_v3_dW5kZWZpbmVk'); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); }); it('should return null if the v3 decrypted value is null', () => { - storedVal = 'RS_ENC_v3_bnVsbA=='; - const migratedVal = storageMigrator.storage?.migrate( + defaultLocalStorage.setItem('key', 'RS_ENC_v3_bnVsbA=='); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( null, - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); }); it('should return null and log error if the stored actual value is not JSON parsable', () => { - storedVal = 'RudderEncrypt:U2FsdGVkX1+leaJ/SuyfirUYffyQelWPnCTB93FBo4Y='; - const migratedVal = storageMigrator.storage?.migrate( - 'someKey', - storageEngine, - mockErrorHandler, - mockLogger, + defaultLocalStorage.setItem( + 'key', + 'RudderEncrypt:U2FsdGVkX1+leaJ/SuyfirUYffyQelWPnCTB93FBo4Y=', + ); + + const migratedVal = (storageMigrator.storage as ExtensionPoint).migrate?.( + 'key', + defaultLocalStorage, + defaultErrorHandler, + defaultLogger, ); expect(migratedVal).toBe(null); - expect(mockErrorHandler.onError).toHaveBeenCalledWith( + expect(defaultErrorHandler.onError).toHaveBeenCalledWith( new SyntaxError('Unexpected token \'h\', "hello" is not valid JSON'), 'StorageMigratorPlugin', - 'Failed to retrieve or parse data for someKey from storage.', + 'Failed to retrieve or parse data for key from storage.', ); }); }); diff --git a/packages/analytics-js-plugins/__tests__/utilities/destination.test.ts b/packages/analytics-js-plugins/__tests__/utilities/destination.test.ts index 57d7e40f19..3feee0c3b7 100644 --- a/packages/analytics-js-plugins/__tests__/utilities/destination.test.ts +++ b/packages/analytics-js-plugins/__tests__/utilities/destination.test.ts @@ -1,16 +1,18 @@ +import type { Destination } from '@rudderstack/analytics-js-common/types/Destination'; +import type { IntegrationOpts } from '@rudderstack/analytics-js-common/types/Integration'; import { filterDestinations } from '../../src/utilities/destination'; describe('Destination Utilities', () => { describe('filterDestinations', () => { - const destinations = [ + const destinations: Destination[] = [ { name: 'GA4', displayName: 'Google Analytics 4 (GA4)', - }, + } as unknown as Destination, { name: 'BRAZE', displayName: 'Braze', - }, + } as unknown as Destination, ]; it('return value should not contain destinations that are specified as false in the load options', () => { @@ -71,8 +73,9 @@ describe('Destination Utilities', () => { 'Google Analytics 4 (GA4)': { customKey: 'customValue', }, + // Intentionally, set a truthy value for Braze Braze: [1, 2, 3], - }; + } as unknown as IntegrationOpts; const filteredDestinations = filterDestinations(loadOptions, destinations); @@ -80,26 +83,27 @@ describe('Destination Utilities', () => { }); it('should not return destinations whose values are specified as falsy in the load options', () => { - const configDestinations = [ + const configDestinations: Destination[] = [ { name: 'GA4', displayName: 'Google Analytics 4 (GA4)', - }, + } as unknown as Destination, { name: 'BRAZE', displayName: 'Braze', - }, + } as unknown as Destination, { name: 'AM', displayName: 'Amplitude', - }, + } as unknown as Destination, ]; const loadOptions = { All: true, 'Google Analytics 4 (GA4)': '', + // Intentionally, set a falsy value for Braze Braze: 0, - }; + } as unknown as IntegrationOpts; const filteredDestinations = filterDestinations(loadOptions, configDestinations); diff --git a/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts b/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts index 0f60b8eac1..44dca615f7 100644 --- a/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts +++ b/packages/analytics-js-plugins/__tests__/utilities/queue.test.ts @@ -1,25 +1,8 @@ -import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; -import { - getDeliveryPayload, - validateEventPayloadSize, -} from '@rudderstack/analytics-js-plugins/utilities/eventsDelivery'; -import * as utilConstants from '@rudderstack/analytics-js-plugins/utilities/constants'; - -class MockLogger implements ILogger { - warn = jest.fn(); - log = jest.fn(); - error = jest.fn(); - info = jest.fn(); - debug = jest.fn(); - minLogLevel = 0; - scope = 'test scope'; - setMinLogLevel = jest.fn(); - setScope = jest.fn(); - logProvider = console; -} - -const mockLogger = new MockLogger(); +import type { RudderContext, RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; +import { getDeliveryPayload, validateEventPayloadSize } from '../../src/utilities/eventsDelivery'; +import * as utilConstants from '../../src/utilities/constants'; describe('Queue Plugins Utilities', () => { describe('getDeliveryPayload', () => { @@ -89,7 +72,7 @@ describe('Queue Plugins Utilities', () => { properties: { test: 'test', }, - }; + } as unknown as RudderEvent; expect(getDeliveryPayload(event)).toEqual( '{"channel":"test","type":"track","anonymousId":"test","context":{"traits":{"trait_1":"trait_1","trait_2":"trait_2"},"sessionId":1,"sessionStart":true,"consentManagement":{"deniedConsentIds":["1","2","3"]},"ua-ch":{"test":"test"},"app":{"name":"test","version":"test","namespace":"test"},"library":{"name":"test","version":"test"},"userAgent":"test","os":{"name":"test","version":"test"},"locale":"test","screen":{"width":1,"height":1,"density":1,"innerWidth":1,"innerHeight":1},"campaign":{"source":"test","medium":"test","name":"test","term":"test","content":"test"}},"originalTimestamp":"test","integrations":{"All":true},"messageId":"test","previousId":"test","sentAt":"test","category":"test","traits":{"trait_1":"trait_11","trait_2":"trait_12"},"groupId":"test","event":"test","userId":"test","properties":{"test":"test"}}', @@ -152,7 +135,7 @@ describe('Queue Plugins Utilities', () => { test: 'test', test2: null, }, - }; + } as unknown as RudderEvent; expect(getDeliveryPayload(event)).toEqual( '{"channel":"test","type":"track","anonymousId":"test","context":{"traits":{"trait_1":"trait_1","trait_2":"trait_2"},"sessionId":1,"sessionStart":true,"ua-ch":{"test":"test"},"app":{"name":"test","version":"test","namespace":"test"},"library":{"name":"test","version":"test"},"userAgent":"test","os":{"name":"test","version":"test"},"locale":"test","screen":{"width":1,"height":1,"density":1,"innerWidth":1,"innerHeight":1}},"originalTimestamp":"test","integrations":{"All":true},"messageId":"test","previousId":"test","sentAt":"test","category":"test","groupId":"test","event":"test","userId":"test","properties":{"test":"test"}}', @@ -225,12 +208,12 @@ describe('Queue Plugins Utilities', () => { properties: { test: 'test', }, - } as RudderEvent; + } as unknown as RudderEvent; event.traits = event.context.traits; - event.context.traits.newTraits = event.traits; + ((event.context as RudderContext).traits as ApiObject).newTraits = event.traits; - expect(getDeliveryPayload(event, mockLogger)).toContain('[Circular Reference]'); + expect(getDeliveryPayload(event, defaultLogger)).toContain('[Circular Reference]'); }); it('should return null if the payload cannot be stringified', () => { @@ -238,11 +221,12 @@ describe('Queue Plugins Utilities', () => { channel: 'test', type: 'track', properties: { + // eslint-disable-next-line compat/compat someBigInt: BigInt(9007199254740991), }, } as unknown as RudderEvent; - expect(getDeliveryPayload(event, mockLogger)).toBeNull(); + expect(getDeliveryPayload(event, defaultLogger)).toBeNull(); }); }); @@ -250,11 +234,17 @@ describe('Queue Plugins Utilities', () => { const originalMaxEventPayloadSize = utilConstants.EVENT_PAYLOAD_SIZE_BYTES_LIMIT; beforeEach(() => { - utilConstants.EVENT_PAYLOAD_SIZE_BYTES_LIMIT = 50; + Object.defineProperty(utilConstants, 'EVENT_PAYLOAD_SIZE_BYTES_LIMIT', { + value: 50, + writable: true, + }); }); afterEach(() => { - utilConstants.EVENT_PAYLOAD_SIZE_BYTES_LIMIT = originalMaxEventPayloadSize; + Object.defineProperty(utilConstants, 'EVENT_PAYLOAD_SIZE_BYTES_LIMIT', { + value: originalMaxEventPayloadSize, + writable: false, + }); }); it('should log a warning if the payload size is greater than the max limit', () => { @@ -269,10 +259,10 @@ describe('Queue Plugins Utilities', () => { properties: { test: 'test', }, - }; - validateEventPayloadSize(event, mockLogger); + } as unknown as RudderEvent; + validateEventPayloadSize(event, defaultLogger); - expect(mockLogger.warn).toHaveBeenCalledWith( + expect(defaultLogger.warn).toHaveBeenCalledWith( 'QueueUtilities:: The size of the event payload (129 bytes) exceeds the maximum limit of 50 bytes. Events with large payloads may be dropped in the future. Please review your instrumentation to ensure that event payloads are within the size limit.', ); }); @@ -281,11 +271,11 @@ describe('Queue Plugins Utilities', () => { const event = { channel: 'test', type: 'track', - }; + } as unknown as RudderEvent; - validateEventPayloadSize(event, mockLogger); + validateEventPayloadSize(event, defaultLogger); - expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(defaultLogger.warn).not.toHaveBeenCalled(); }); it('should not log a warning if the payload size is equal to the max limit', () => { @@ -295,11 +285,11 @@ describe('Queue Plugins Utilities', () => { type: 'track', ab: 'd', g: 'j', - }; + } as unknown as RudderEvent; - validateEventPayloadSize(event, mockLogger); + validateEventPayloadSize(event, defaultLogger); - expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(defaultLogger.warn).not.toHaveBeenCalled(); }); it('should log a warning if the payload size could not be calculated', () => { @@ -313,13 +303,14 @@ describe('Queue Plugins Utilities', () => { userId: 'test', properties: { test: 'test', + // eslint-disable-next-line compat/compat test1: BigInt(9007199254740991), }, } as unknown as RudderEvent; - validateEventPayloadSize(event, mockLogger); + validateEventPayloadSize(event, defaultLogger); - expect(mockLogger.warn).toHaveBeenCalledWith( + expect(defaultLogger.warn).toHaveBeenCalledWith( 'QueueUtilities:: Failed to validate event payload size. Please make sure that the event payload is within the size limit and is a valid JSON object.', ); }); diff --git a/packages/analytics-js-plugins/__tests__/utilities/retryQueue/RetryQueue.test.ts b/packages/analytics-js-plugins/__tests__/utilities/retryQueue/RetryQueue.test.ts index fba64dc8e4..8787c17838 100644 --- a/packages/analytics-js-plugins/__tests__/utilities/retryQueue/RetryQueue.test.ts +++ b/packages/analytics-js-plugins/__tests__/utilities/retryQueue/RetryQueue.test.ts @@ -1,13 +1,11 @@ /* eslint-disable import/no-extraneous-dependencies */ import { QueueStatuses } from '@rudderstack/analytics-js-common/constants/QueueStatuses'; -import { getStorageEngine } from '@rudderstack/analytics-js/services/StoreManager/storages'; -import { Store, StoreManager } from '@rudderstack/analytics-js/services/StoreManager'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { defaultErrorHandler } from '@rudderstack/analytics-js/services/ErrorHandler'; -import { defaultLogger } from '@rudderstack/analytics-js/services/Logger'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; +import { defaultLocalStorage } from '@rudderstack/analytics-js-common/__mocks__/Storage'; +import { Store } from '@rudderstack/analytics-js-common/__mocks__/Store'; import { Schedule } from '../../../src/utilities/retryQueue/Schedule'; import { RetryQueue } from '../../../src/utilities/retryQueue/RetryQueue'; +import type { QueueItem, QueueItemData } from '../../../src/types/plugins'; const size = (queue: RetryQueue): { queue: number; inProgress: number } => ({ queue: queue.store.get(QueueStatuses.QUEUE).length, @@ -16,23 +14,14 @@ const size = (queue: RetryQueue): { queue: number; inProgress: number } => ({ describe('Queue', () => { let queue: RetryQueue; - // let clock: InstalledClock; let schedule: Schedule; - const engine = getStorageEngine('localStorage'); - const defaultPluginsManager = new PluginsManager( - defaultPluginEngine, - defaultErrorHandler, - defaultLogger, - ); - - const defaultStoreManager = new StoreManager(defaultPluginsManager); beforeAll(() => { jest.useFakeTimers(); }); beforeEach(() => { - engine.clear(); + defaultLocalStorage.clear(); schedule = new Schedule(); schedule.now = () => +new window.Date(); @@ -241,7 +230,11 @@ describe('Queue', () => { queue.processQueueCb = mockProcessItemCb; queue.start(); - queue.requeue({ item: 'b', attemptNumber: 0, type: 'Single' }); + queue.requeue({ + item: 'b', + attemptNumber: 0, + type: 'Single', + } as unknown as QueueItem); queue.addItem('a'); expect(queue.processQueueCb).toHaveBeenCalledTimes(1); @@ -257,7 +250,7 @@ describe('Queue', () => { }); it('should respect shouldRetry', () => { - queue.shouldRetry = (_, attemptNumber) => !(attemptNumber > 2); + queue.shouldRetry = (_, attemptNumber) => attemptNumber <= 2; const mockProcessItemCb = jest.fn((_, cb) => cb(new Error('no'))); @@ -266,17 +259,29 @@ describe('Queue', () => { queue.start(); // over maxattempts - queue.requeue({ item: 'b', attemptNumber: 2, type: 'Single' }); + queue.requeue({ + item: 'b', + attemptNumber: 2, + type: 'Single', + } as unknown as QueueItem); jest.advanceTimersByTime(queue.getDelay(3)); expect(queue.processQueueCb).toHaveBeenCalledTimes(0); mockProcessItemCb.mockReset(); - queue.requeue({ item: ['a', 'b'], attemptNumber: 1, type: 'Batch' }); + queue.requeue({ + item: ['a', 'b'], + attemptNumber: 1, + type: 'Batch', + } as unknown as QueueItem); jest.advanceTimersByTime(queue.getDelay(2)); expect(queue.processQueueCb).toHaveBeenCalledTimes(1); mockProcessItemCb.mockReset(); - queue.requeue({ item: 'b', attemptNumber: 2, type: 'Single' }); + queue.requeue({ + item: 'b', + attemptNumber: 2, + type: 'Single', + } as unknown as QueueItem); jest.advanceTimersByTime(queue.getDelay(1)); expect(queue.processQueueCb).toHaveBeenCalledTimes(0); }); @@ -305,10 +310,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, 0); // fake timers starts at time 0 - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, 0); // fake timers starts at time 0 + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, @@ -384,10 +389,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id-1': { item: 'a', time: 0, @@ -470,10 +475,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, 0); // fake timers starts at time 0 - foundQueue.set(foundQueue.validKeys.BATCH_QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, 0); // fake timers starts at time 0 + foundQueue.set(foundQueue.validKeys.BATCH_QUEUE as string, [ { item: 'a', time: 0, @@ -514,10 +519,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, 0); // fake timers starts at time 0 - foundQueue.set(foundQueue.validKeys.BATCH_QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, 0); // fake timers starts at time 0 + foundQueue.set(foundQueue.validKeys.BATCH_QUEUE as string, [ { item: 'a', time: 0, @@ -565,10 +570,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, @@ -603,10 +608,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id-0': { item: 'a', time: 0, @@ -641,10 +646,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.BATCH_QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.BATCH_QUEUE as string, [ { item: 'a', time: 0, @@ -679,10 +684,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id-0': { item: 'a', time: 0, @@ -697,7 +702,7 @@ describe('Queue', () => { }, }); - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, @@ -712,7 +717,7 @@ describe('Queue', () => { }, ]); - foundQueue.set(foundQueue.validKeys.BATCH_QUEUE, [ + foundQueue.set(foundQueue.validKeys.BATCH_QUEUE as string, [ { item: 'c', time: 0, @@ -749,10 +754,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id-0': { item: 'a', time: 0, @@ -765,7 +770,7 @@ describe('Queue', () => { }, }); - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, @@ -798,24 +803,24 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, attemptNumber: 0, }, ]); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id': { item: 'b', time: 1, attemptNumber: 0, }, }); - foundQueue.set(foundQueue.validKeys.BATCH_QUEUE, { + foundQueue.set(foundQueue.validKeys.BATCH_QUEUE as string, { 'task-id2': { item: 'c', time: 1, @@ -850,10 +855,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, 0); // fake timers starts at time 0 - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, 0); // fake timers starts at time 0 + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, @@ -887,10 +892,10 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id': { item: 'a', time: 0, @@ -924,17 +929,17 @@ describe('Queue', () => { id: 'fake-id', validKeys: QueueStatuses, }, - getStorageEngine('localStorage'), + defaultLocalStorage, ); - foundQueue.set(foundQueue.validKeys.ACK, -15000); - foundQueue.set(foundQueue.validKeys.QUEUE, [ + foundQueue.set(foundQueue.validKeys.ACK as string, -15000); + foundQueue.set(foundQueue.validKeys.QUEUE as string, [ { item: 'a', time: 0, attemptNumber: 0, }, ]); - foundQueue.set(foundQueue.validKeys.IN_PROGRESS, { + foundQueue.set(foundQueue.validKeys.IN_PROGRESS as string, { 'task-id': { item: 'b', time: 1, @@ -975,10 +980,10 @@ describe('Queue', () => { queue.maxAttempts = 2; queue.processQueueCb = (item, done) => { - if (!calls[item.index]) { - calls[item.index] = 1; + if (!calls[(item as Record).index]) { + calls[(item as Record).index] = 1; } else { - calls[item.index]++; + calls[(item as Record).index]++; } done(new Error()); @@ -997,7 +1002,7 @@ describe('Queue', () => { }); it('should limit inProgress using maxItems', () => { - const waiting: Function[] = []; + const waiting: (() => void)[] = []; let i; queue.maxItems = 100; @@ -1031,7 +1036,7 @@ describe('Queue', () => { // resolved all waiting items while (waiting.length > 0) { - waiting.pop()(); + waiting.pop()?.(); } // inProgress should now be empty @@ -1050,6 +1055,7 @@ describe('Queue', () => { let batchQueue = new RetryQueue( 'batchQueue', { + // @ts-expect-error testing invalid options batch: {}, }, () => {}, @@ -1064,6 +1070,7 @@ describe('Queue', () => { batch: { enabled: true, maxSize: 1024, + // @ts-expect-error testing invalid options maxItems: '1', }, }, @@ -1083,6 +1090,7 @@ describe('Queue', () => { { batch: { enabled: true, + // @ts-expect-error testing invalid options maxSize: '3', maxItems: 20, }, @@ -1205,14 +1213,6 @@ describe('Queue', () => { describe('end-to-end', () => { let queue: RetryQueue; - const defaultPluginsManager = new PluginsManager( - defaultPluginEngine, - defaultErrorHandler, - defaultLogger, - ); - - const defaultStoreManager = new StoreManager(defaultPluginsManager); - beforeEach(() => { queue = new RetryQueue( 'e2e_test', diff --git a/packages/analytics-js-plugins/__tests__/xhrQueue/index.test.ts b/packages/analytics-js-plugins/__tests__/xhrQueue/index.test.ts index 360f5ff5e2..2bfdf81e5e 100644 --- a/packages/analytics-js-plugins/__tests__/xhrQueue/index.test.ts +++ b/packages/analytics-js-plugins/__tests__/xhrQueue/index.test.ts @@ -1,18 +1,17 @@ /* eslint-disable import/no-extraneous-dependencies */ import { batch } from '@preact/signals-core'; -import { HttpClient } from '@rudderstack/analytics-js/services/HttpClient'; -import { state } from '@rudderstack/analytics-js/state'; import { mergeDeepRight } from '@rudderstack/analytics-js-common/utilities/object'; -import { PluginsManager } from '@rudderstack/analytics-js/components/pluginsManager'; -import { defaultPluginEngine } from '@rudderstack/analytics-js/services/PluginEngine'; -import { defaultErrorHandler } from '@rudderstack/analytics-js/services/ErrorHandler'; -import { defaultLogger } from '@rudderstack/analytics-js/services/Logger'; -import { StoreManager } from '@rudderstack/analytics-js/services/StoreManager'; +import { defaultStoreManager } from '@rudderstack/analytics-js-common/__mocks__/StoreManager'; import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; -import type { IHttpClient } from '@rudderstack/analytics-js-common/types/HttpClient'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import { defaultErrorHandler } from '@rudderstack/analytics-js-common/__mocks__/ErrorHandler'; +import type { ExtensionPoint } from '@rudderstack/analytics-js-common/types/PluginEngine'; +import type { RetryQueue } from '../../src/utilities/retryQueue/RetryQueue'; +import type { QueueItem, QueueItemData } from '../../src/types/plugins'; +import { resetState, state } from '../../__mocks__/state'; import { XhrQueue } from '../../src/xhrQueue'; import { Schedule } from '../../src/utilities/retryQueue/Schedule'; +import { defaultHttpClient } from '../../__mocks__/HttpClient'; jest.mock('@rudderstack/analytics-js-common/utilities/timestamp', () => ({ ...jest.requireActual('@rudderstack/analytics-js-common/utilities/timestamp'), @@ -25,19 +24,8 @@ jest.mock('@rudderstack/analytics-js-common/utilities/uuId', () => ({ })); describe('XhrQueue', () => { - const defaultPluginsManager = new PluginsManager( - defaultPluginEngine, - defaultErrorHandler, - defaultLogger, - ); - - const defaultStoreManager = new StoreManager(defaultPluginsManager); - - const mockLogger = { - error: jest.fn(), - } as unknown as ILogger; - beforeAll(() => { + resetState(); batch(() => { state.lifecycle.writeKey.value = 'sampleWriteKey'; state.lifecycle.activeDataplaneUrl.value = 'https://sampleurl.com'; @@ -51,33 +39,35 @@ describe('XhrQueue', () => { }); }); - const httpClient = new HttpClient(); - it('should add itself to the loaded plugins list on initialized', () => { - XhrQueue().initialize(state); + XhrQueue()?.initialize?.(state); expect(state.plugins.loadedPlugins.value).toContain('XhrQueue'); }); it('should return a queue object on init', () => { - const queue = XhrQueue().dataplaneEventsQueue?.init( + const queue = (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).init?.( state, - httpClient, + defaultHttpClient, defaultStoreManager, defaultErrorHandler, defaultLogger, - ); + ) as RetryQueue; expect(queue).toBeDefined(); expect(queue.name).toBe('rudder_sampleWriteKey'); }); it('should add item in queue on enqueue', () => { - const queue = XhrQueue().dataplaneEventsQueue?.init(state, httpClient, defaultStoreManager); + const queue = (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).init?.( + state, + defaultHttpClient, + defaultStoreManager, + ) as RetryQueue; const addItemSpy = jest.spyOn(queue, 'addItem'); - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -87,11 +77,11 @@ describe('XhrQueue', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; - XhrQueue().dataplaneEventsQueue?.enqueue(state, queue, event); + (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).enqueue?.(state, queue, event); - expect(addItemSpy).toBeCalledWith({ + expect(addItemSpy).toHaveBeenCalledWith({ url: 'https://sampleurl.com/v1/track', headers: { AnonymousId: 'c2FtcGxlQW5vbklk', // Base64 encoded anonymousId @@ -103,15 +93,18 @@ describe('XhrQueue', () => { }); it('should process queue item on start', () => { - const mockHttpClient = { - getAsyncData: ({ callback }) => { - callback(true); - }, - setAuthHeader: jest.fn(), - }; - const queue = XhrQueue().dataplaneEventsQueue?.init(state, mockHttpClient, defaultStoreManager); + // Mock getAsyncData to return a successful response - const event: RudderEvent = { + defaultHttpClient.getAsyncData.mockImplementation(({ callback }) => { + callback?.(true); + }); + const queue = (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).init?.( + state, + defaultHttpClient, + defaultStoreManager, + ) as RetryQueue; + + const event = { type: 'track', event: 'test', userId: 'test', @@ -121,17 +114,17 @@ describe('XhrQueue', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; const queueProcessCbSpy = jest.spyOn(queue, 'processQueueCb'); - XhrQueue().dataplaneEventsQueue?.enqueue(state, queue, event); + (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).enqueue?.(state, queue, event); // Explicitly start the queue to process the item // In actual implementation, this is done based on the state signals queue.start(); - expect(queueProcessCbSpy).toBeCalledWith( + expect(queueProcessCbSpy).toHaveBeenCalledWith( { url: 'https://sampleurl.com/v1/track', headers: { @@ -146,26 +139,24 @@ describe('XhrQueue', () => { ); // Item is successfully processed and removed from queue - expect(queue.getStorageEntry('queue').length).toBe(0); + expect((queue.getStorageEntry('queue') as QueueItem[]).length).toBe(0); queueProcessCbSpy.mockRestore(); }); it('should log error on retryable failure and requeue the item', () => { - const mockHttpClient = { - getAsyncData: ({ callback }) => { - callback(false, { error: 'some error', xhr: { status: 429 } }); - }, - setAuthHeader: jest.fn(), - } as unknown as IHttpClient; + // Mock getAsyncData to return a retryable failure - const queue = XhrQueue().dataplaneEventsQueue?.init( + defaultHttpClient.getAsyncData.mockImplementation(({ callback }) => { + callback?.(false, { error: 'some error', xhr: { status: 429 } }); + }); + const queue = (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).init?.( state, - mockHttpClient, + defaultHttpClient, defaultStoreManager, undefined, - mockLogger, - ); + defaultLogger, + ) as RetryQueue; const schedule = new Schedule(); // Override the timestamp generation function to return a fixed value @@ -173,7 +164,7 @@ describe('XhrQueue', () => { queue.schedule = schedule; - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -183,15 +174,15 @@ describe('XhrQueue', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; - XhrQueue().dataplaneEventsQueue?.enqueue(state, queue, event); + (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).enqueue?.(state, queue, event); // Explicitly start the queue to process the item // In actual implementation, this is done based on the state signals queue.start(); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://sampleurl.com/v1/track. It/they will be retried.', ); @@ -229,18 +220,13 @@ describe('XhrQueue', () => { }; }); - const mockHttpClient = { - getAsyncData: jest.fn(), - setAuthHeader: jest.fn(), - } as unknown as IHttpClient; - - const queue = XhrQueue().dataplaneEventsQueue?.init( + const queue = (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).init?.( state, - mockHttpClient, + defaultHttpClient, defaultStoreManager, undefined, - mockLogger, - ); + defaultLogger, + ) as RetryQueue; const queueProcessCbSpy = jest.spyOn(queue, 'processQueueCb'); const schedule = new Schedule(); @@ -249,7 +235,7 @@ describe('XhrQueue', () => { queue.schedule = schedule; - const event: RudderEvent = { + const event = { type: 'track', event: 'test', userId: 'test', @@ -259,9 +245,9 @@ describe('XhrQueue', () => { anonymousId: 'sampleAnonId', messageId: 'test', originalTimestamp: 'test', - }; + } as unknown as RudderEvent; - const event2: RudderEvent = { + const event2 = { type: 'track', event: 'test2', userId: 'test2', @@ -271,16 +257,16 @@ describe('XhrQueue', () => { anonymousId: 'sampleAnonId', messageId: 'test2', originalTimestamp: 'test2', - }; + } as unknown as RudderEvent; - XhrQueue().dataplaneEventsQueue?.enqueue(state, queue, event); - XhrQueue().dataplaneEventsQueue?.enqueue(state, queue, event2); + (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).enqueue?.(state, queue, event); + (XhrQueue()?.dataplaneEventsQueue as ExtensionPoint).enqueue?.(state, queue, event2); // Explicitly start the queue to process the item // In actual implementation, this is done based on the state signals queue.start(); - expect(queueProcessCbSpy).toBeCalledWith( + expect(queueProcessCbSpy).toHaveBeenCalledWith( [ { url: 'https://sampleurl.com/v1/track', @@ -303,7 +289,7 @@ describe('XhrQueue', () => { true, ); - expect(mockHttpClient.getAsyncData).toBeCalledWith({ + expect(defaultHttpClient.getAsyncData).toHaveBeenCalledWith({ url: 'https://sampleurl.com/v1/batch', options: { method: 'POST', diff --git a/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts b/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts index 9b1a15bdf4..c8a4957f3f 100644 --- a/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts +++ b/packages/analytics-js-plugins/__tests__/xhrQueue/utilities.test.ts @@ -1,8 +1,8 @@ import type { RudderEvent } from '@rudderstack/analytics-js-common/types/Event'; import type { ResponseDetails } from '@rudderstack/analytics-js-common/types/HttpClient'; -import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; -import { state, resetState } from '@rudderstack/analytics-js/state'; import { getCurrentTimeFormatted } from '@rudderstack/analytics-js-common/utilities/timestamp'; +import { defaultLogger } from '@rudderstack/analytics-js-common/__mocks__/Logger'; +import type { ApiObject } from '@rudderstack/analytics-js-common/types/ApiObject'; import { getNormalizedQueueOptions, getDeliveryUrl, @@ -10,18 +10,14 @@ import { logErrorOnFailure, getRequestInfo, getBatchDeliveryPayload, -} from '@rudderstack/analytics-js-plugins/xhrQueue/utilities'; +} from '../../src/xhrQueue/utilities'; +import { resetState, state } from '../../__mocks__/state'; jest.mock('@rudderstack/analytics-js-common/utilities/timestamp', () => ({ getCurrentTimeFormatted: () => '2021-01-01T00:00:00.000Z', })); describe('xhrQueue Plugin Utilities', () => { - const mockLogger = { - error: jest.fn(), - warn: jest.fn(), - } as unknown as ILogger; - describe('getNormalizedQueueOptions', () => { it('should return default queue options if input queue options is empty object', () => { const queueOptions = getNormalizedQueueOptions({}); @@ -36,6 +32,7 @@ describe('xhrQueue Plugin Utilities', () => { }); it('should return default queue options if input queue options is null', () => { + // @ts-expect-error Testing for null const queueOptions = getNormalizedQueueOptions(null); expect(queueOptions).toEqual({ @@ -48,6 +45,7 @@ describe('xhrQueue Plugin Utilities', () => { }); it('should return default queue options if input queue options is undefined', () => { + // @ts-expect-error Testing for undefined const queueOptions = getNormalizedQueueOptions(undefined); expect(queueOptions).toEqual({ @@ -122,9 +120,9 @@ describe('xhrQueue Plugin Utilities', () => { response: {}, } as ResponseDetails; - logErrorOnFailure(details, 'https://test.com/v1/page', false, 1, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', false, 1, 10, defaultLogger); - expect(mockLogger.error).not.toBeCalled(); + expect(defaultLogger.error).not.toHaveBeenCalled(); }); it('should log an error for delivery failure', () => { @@ -132,9 +130,9 @@ describe('xhrQueue Plugin Utilities', () => { error: {}, } as ResponseDetails; - logErrorOnFailure(details, 'https://test.com/v1/page', false, 1, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', false, 1, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. The event(s) will be dropped.', ); }); @@ -147,54 +145,59 @@ describe('xhrQueue Plugin Utilities', () => { }, } as ResponseDetails; - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried. Retry attempt 1 of 10.', ); // Retryable error but it's the first attempt - details.xhr.status = 429; + // @ts-expect-error Needed to set the status for testing + (details.xhr as XMLHttpRequest).status = 429; - logErrorOnFailure(details, 'https://test.com/v1/page', true, 0, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', true, 0, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried.', ); // 500 error - details.xhr.status = 500; + // @ts-expect-error Needed to set the status for testing + (details.xhr as XMLHttpRequest).status = 500; - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried. Retry attempt 1 of 10.', ); // 5xx error - details.xhr.status = 501; + // @ts-expect-error Needed to set the status for testing + (details.xhr as XMLHttpRequest).status = 501; - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. It/they will be retried. Retry attempt 1 of 10.', ); // 600 error - details.xhr.status = 600; + // @ts-expect-error Needed to set the status for testing + (details.xhr as XMLHttpRequest).status = 600; - logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', true, 1, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. The event(s) will be dropped.', ); // Retryable error but exhausted all tries - details.xhr.status = 520; + // @ts-expect-error Needed to set the status for testing + (details.xhr as XMLHttpRequest).status = 520; - logErrorOnFailure(details, 'https://test.com/v1/page', false, 10, 10, mockLogger); + logErrorOnFailure(details, 'https://test.com/v1/page', false, 10, 10, defaultLogger); - expect(mockLogger.error).toBeCalledWith( + expect(defaultLogger.error).toHaveBeenCalledWith( 'XhrQueuePlugin:: Failed to deliver event(s) to https://test.com/v1/page. Retries exhausted (10). The event(s) will be dropped.', ); }); @@ -219,7 +222,7 @@ describe('xhrQueue Plugin Utilities', () => { }, }; - const requestInfo = getRequestInfo(queueItemData, state, mockLogger); + const requestInfo = getRequestInfo(queueItemData, state, defaultLogger); expect(requestInfo).toEqual({ url: 'https://test.com/v1/track', @@ -260,7 +263,7 @@ describe('xhrQueue Plugin Utilities', () => { state.lifecycle.activeDataplaneUrl.value = 'https://test.dataplaneurl.com/'; - const requestInfo = getRequestInfo(queueItemData, state, mockLogger); + const requestInfo = getRequestInfo(queueItemData, state, defaultLogger); expect(requestInfo).toEqual({ url: 'https://test.dataplaneurl.com/v1/batch', @@ -294,7 +297,7 @@ describe('xhrQueue Plugin Utilities', () => { } as unknown as RudderEvent, ]; - expect(getBatchDeliveryPayload(events, currentTime, mockLogger)).toBe( + expect(getBatchDeliveryPayload(events, currentTime, defaultLogger)).toBe( '{"batch":[{"channel":"test","type":"track","anonymousId":"test","properties":{"test":"test"}},{"channel":"test","type":"track","anonymousId":"test","properties":{"test1":"test1"}}],"sentAt":"2021-01-01T00:00:00.000Z"}', ); }); @@ -324,13 +327,13 @@ describe('xhrQueue Plugin Utilities', () => { }, } as unknown as RudderEvent, ]; - expect(getBatchDeliveryPayload(events, currentTime, mockLogger)).toBe( + expect(getBatchDeliveryPayload(events, currentTime, defaultLogger)).toBe( '{"batch":[{"channel":"test","type":"track","anonymousId":"test","properties":{"test":"test"}},{"channel":"test","type":"track","anonymousId":"test","properties":{"test1":"test1","test3":{}}}],"sentAt":"2021-01-01T00:00:00.000Z"}', ); }); it('should return string with circular dependencies replaced with static string', () => { - const events = [ + const events: RudderEvent[] = [ { channel: 'test', type: 'track', @@ -354,10 +357,13 @@ describe('xhrQueue Plugin Utilities', () => { }, } as unknown as RudderEvent, ]; + const event2 = events[1] as RudderEvent; - events[1].properties.test5 = events[1]; + // Create a circular reference + // @ts-expect-error Testing for circular reference + (event2.properties as ApiObject).test5 = event2; - expect(getBatchDeliveryPayload(events, currentTime, mockLogger)).toContain( + expect(getBatchDeliveryPayload(events, currentTime, defaultLogger)).toContain( '[Circular Reference]', ); }); @@ -382,7 +388,7 @@ describe('xhrQueue Plugin Utilities', () => { } as unknown as RudderEvent, ]; - expect(getBatchDeliveryPayload(events, currentTime, mockLogger)).toBeNull(); + expect(getBatchDeliveryPayload(events, currentTime, defaultLogger)).toBeNull(); }); }); }); diff --git a/packages/analytics-js-plugins/package.json b/packages/analytics-js-plugins/package.json index d1029dd746..d40b626bfa 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.17", + "version": "3.6.20", "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 a2686e4447..5d1c6afbc6 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.17", - "title": "@rudderstack/analytics-js-plugins@3.6.17", - "discussion-category": "@rudderstack/analytics-js-plugins@3.6.17", + "tag": "@rudderstack/analytics-js-plugins@3.6.20", + "title": "@rudderstack/analytics-js-plugins@3.6.20", + "discussion-category": "@rudderstack/analytics-js-plugins@3.6.20", "notesFile": "./packages/analytics-js-plugins/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js-plugins/src/bugsnag/utils.ts b/packages/analytics-js-plugins/src/bugsnag/utils.ts index 135054ef83..e1c159dcae 100644 --- a/packages/analytics-js-plugins/src/bugsnag/utils.ts +++ b/packages/analytics-js-plugins/src/bugsnag/utils.ts @@ -136,7 +136,8 @@ const getNewClient = (state: ApplicationState, logger?: ILogger): BugsnagLib.Cli maxBreadcrumbs: 40, releaseStage: getReleaseStage(), user: { - id: state.source.value?.id || state.lifecycle.writeKey.value, + // Combination of source, session and visit ids + id: `${state.source.value?.id ?? (state.lifecycle.writeKey.value as string)}..${state.session.sessionInfo.value?.id ?? 'NA'}..${state.autoTrack?.pageLifecycle?.visitId?.value ?? 'NA'}`, }, logger, networkBreadcrumbsEnabled: false, diff --git a/packages/analytics-js-plugins/src/errorReporting/utils.ts b/packages/analytics-js-plugins/src/errorReporting/utils.ts index ced6d752b6..ef8fadc3bc 100644 --- a/packages/analytics-js-plugins/src/errorReporting/utils.ts +++ b/packages/analytics-js-plugins/src/errorReporting/utils.ts @@ -132,7 +132,8 @@ const getBugsnagErrorEvent = ( }, }, user: { - id: state.source.value?.id ?? (state.lifecycle.writeKey.value as string), + // Combination of source, session and visit ids + id: `${state.source.value?.id ?? (state.lifecycle.writeKey.value as string)}..${state.session.sessionInfo.value?.id ?? 'NA'}..${state.autoTrack?.pageLifecycle?.visitId?.value ?? 'NA'}`, }, }, ], diff --git a/packages/analytics-js-service-worker/CHANGELOG.md b/packages/analytics-js-service-worker/CHANGELOG.md index 96eb06f32d..09f57055b5 100644 --- a/packages/analytics-js-service-worker/CHANGELOG.md +++ b/packages/analytics-js-service-worker/CHANGELOG.md @@ -2,6 +2,16 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.2.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.16...@rudderstack/analytics-js-service-worker@3.2.17) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.14` +## [3.2.16](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.15...@rudderstack/analytics-js-service-worker@3.2.16) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.13` ## [3.2.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.14...@rudderstack/analytics-js-service-worker@3.2.15) (2024-11-22) ### Dependency Updates diff --git a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md index 20189037eb..5d53b24f3b 100644 --- a/packages/analytics-js-service-worker/CHANGELOG_LATEST.md +++ b/packages/analytics-js-service-worker/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.2.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.14...@rudderstack/analytics-js-service-worker@3.2.15) (2024-11-22) +## [3.2.17](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-service-worker@3.2.16...@rudderstack/analytics-js-service-worker@3.2.17) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.12` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` diff --git a/packages/analytics-js-service-worker/package.json b/packages/analytics-js-service-worker/package.json index db8c794958..2d2ff599ed 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.15", + "version": "3.2.17", "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 b0cf773415..d191528748 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.15", - "title": "rudderstack/analytics-js-service-worker@3.2.15", - "discussion-category": "rudderstack/analytics-js-service-worker@3.2.15", + "tag": "@rudderstack/analytics-js-service-worker@3.2.17", + "title": "rudderstack/analytics-js-service-worker@3.2.17", + "discussion-category": "rudderstack/analytics-js-service-worker@3.2.17", "notesFile": "./packages/analytics-js-service-worker/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/.size-limit.mjs b/packages/analytics-js/.size-limit.mjs index d55635d440..9dc1495ea0 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 KiB', + limit: '49.1 KiB', }, { name: 'Core - Legacy - NPM (UMD)', @@ -59,7 +59,7 @@ export default [ name: 'Core (Bundled) - Legacy - NPM (CJS)', path: 'dist/npm/legacy/bundled/cjs/index.cjs', import: '*', - limit: '49 KiB', + limit: '49.1 KiB', }, { name: 'Core (Bundled) - Legacy - NPM (UMD)', diff --git a/packages/analytics-js/CHANGELOG.md b/packages/analytics-js/CHANGELOG.md index 454955f82d..83940fd8f1 100644 --- a/packages/analytics-js/CHANGELOG.md +++ b/packages/analytics-js/CHANGELOG.md @@ -2,6 +2,26 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.11.16](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.15...@rudderstack/analytics-js@3.11.16) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js-cookies` updated to version `0.4.17` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.20` + +### Bug Fixes + +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) +* separator and make changes in bugsnag plugin ([b69347c](https://github.com/rudderlabs/rudder-sdk-js/commit/b69347cd9bbf3a395b2f557f8219287900ceca5a)) + +## [3.11.15](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.14...@rudderstack/analytics-js@3.11.15) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js-cookies` updated to version `0.4.16` +* `@rudderstack/analytics-js-common` updated to version `3.14.13` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.18` ## [3.11.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.13...@rudderstack/analytics-js@3.11.14) (2024-11-30) ### Dependency Updates diff --git a/packages/analytics-js/CHANGELOG_LATEST.md b/packages/analytics-js/CHANGELOG_LATEST.md index bfd8ae1f19..25932bb992 100644 --- a/packages/analytics-js/CHANGELOG_LATEST.md +++ b/packages/analytics-js/CHANGELOG_LATEST.md @@ -1,10 +1,13 @@ -## [3.11.14](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.13...@rudderstack/analytics-js@3.11.14) (2024-11-30) +## [3.11.16](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js@3.11.15...@rudderstack/analytics-js@3.11.16) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js-plugins` updated to version `3.6.17` +* `@rudderstack/analytics-js-cookies` updated to version `0.4.17` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` +* `@rudderstack/analytics-js-plugins` updated to version `3.6.20` ### Bug Fixes -* preload events not processed with detached load call ([#1953](https://github.com/rudderlabs/rudder-sdk-js/issues/1953)) ([6b0f66f](https://github.com/rudderlabs/rudder-sdk-js/commit/6b0f66f61745542f2b01c02c99b7514fd468db80)) +* remove circular dependency in packages ([#1973](https://github.com/rudderlabs/rudder-sdk-js/issues/1973)) ([e525496](https://github.com/rudderlabs/rudder-sdk-js/commit/e5254964310c2c73baaf4d0655c3e4025c5e7d2b)) +* separator and make changes in bugsnag plugin ([b69347c](https://github.com/rudderlabs/rudder-sdk-js/commit/b69347cd9bbf3a395b2f557f8219287900ceca5a)) diff --git a/packages/analytics-js/__tests__/services/StoreManager/top-domain/index.test.ts b/packages/analytics-js/__tests__/services/StoreManager/top-domain/index.test.ts index dc1e77410b..b1bf676a02 100644 --- a/packages/analytics-js/__tests__/services/StoreManager/top-domain/index.test.ts +++ b/packages/analytics-js/__tests__/services/StoreManager/top-domain/index.test.ts @@ -1,12 +1,12 @@ import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import type { CookieOptions } from '@rudderstack/analytics-js-common/types/Storage'; -import { cookie } from '@rudderstack/analytics-js-cookies/component-cookie'; +import { cookie } from '@rudderstack/analytics-js-common/component-cookie'; import { domain } from '../../../../src/services/StoreManager/top-domain'; let cookies: Record = {}; -jest.mock('@rudderstack/analytics-js-cookies/component-cookie', () => { - const originalModule = jest.requireActual('@rudderstack/analytics-js-cookies/component-cookie'); +jest.mock('@rudderstack/analytics-js-common/component-cookie', () => { + const originalModule = jest.requireActual('@rudderstack/analytics-js-common/component-cookie'); return { __esModule: true, diff --git a/packages/analytics-js/package.json b/packages/analytics-js/package.json index 105f9c6c61..f4017fb3a0 100644 --- a/packages/analytics-js/package.json +++ b/packages/analytics-js/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js", - "version": "3.11.14", + "version": "3.11.16", "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 8737e14abc..4adfc485a3 100644 --- a/packages/analytics-js/project.json +++ b/packages/analytics-js/project.json @@ -59,9 +59,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js@3.11.14", - "title": "@rudderstack/analytics-js@3.11.14", - "discussion-category": "@rudderstack/analytics-js@3.11.14", + "tag": "@rudderstack/analytics-js@3.11.16", + "title": "@rudderstack/analytics-js@3.11.16", + "discussion-category": "@rudderstack/analytics-js@3.11.16", "notesFile": "./packages/analytics-js/CHANGELOG_LATEST.md" } } diff --git a/packages/analytics-js/src/services/StoreManager/storages/CookieStorage.ts b/packages/analytics-js/src/services/StoreManager/storages/CookieStorage.ts index b857e618fd..b67eebd38a 100644 --- a/packages/analytics-js/src/services/StoreManager/storages/CookieStorage.ts +++ b/packages/analytics-js/src/services/StoreManager/storages/CookieStorage.ts @@ -4,7 +4,7 @@ import type { Nullable } from '@rudderstack/analytics-js-common/types/Nullable'; import type { ILogger } from '@rudderstack/analytics-js-common/types/Logger'; import { COOKIE_STORAGE } from '@rudderstack/analytics-js-common/constants/storages'; import { mergeDeepRight } from '@rudderstack/analytics-js-common/utilities/object'; -import { cookie } from '@rudderstack/analytics-js-cookies/component-cookie'; +import { cookie } from '@rudderstack/analytics-js-common/component-cookie'; import { isStorageAvailable } from '../../../components/capabilitiesManager/detection'; import { getDefaultCookieOptions } from './defaultOptions'; diff --git a/packages/analytics-js/src/services/StoreManager/top-domain/index.ts b/packages/analytics-js/src/services/StoreManager/top-domain/index.ts index cf5d2af383..c1b9e46ab8 100644 --- a/packages/analytics-js/src/services/StoreManager/top-domain/index.ts +++ b/packages/analytics-js/src/services/StoreManager/top-domain/index.ts @@ -1,4 +1,4 @@ -import { cookie } from '@rudderstack/analytics-js-cookies/component-cookie'; +import { cookie } from '@rudderstack/analytics-js-common/component-cookie'; import { STORAGE_TEST_TOP_LEVEL_DOMAIN } from '../../../constants/storage'; const legacyGetHostname = (href: string): string => { diff --git a/packages/analytics-v1.1/CHANGELOG.md b/packages/analytics-v1.1/CHANGELOG.md index 79603e65cf..29e52dbe08 100644 --- a/packages/analytics-v1.1/CHANGELOG.md +++ b/packages/analytics-v1.1/CHANGELOG.md @@ -2,6 +2,16 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [2.48.42](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.41...rudder-sdk-js@2.48.42) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.14` +## [2.48.41](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.40...rudder-sdk-js@2.48.41) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js-common` updated to version `3.14.13` ## [2.48.40](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.39...rudder-sdk-js@2.48.40) (2024-11-22) ### Dependency Updates diff --git a/packages/analytics-v1.1/CHANGELOG_LATEST.md b/packages/analytics-v1.1/CHANGELOG_LATEST.md index b3276b7c99..aa3a573d29 100644 --- a/packages/analytics-v1.1/CHANGELOG_LATEST.md +++ b/packages/analytics-v1.1/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [2.48.40](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.39...rudder-sdk-js@2.48.40) (2024-11-22) +## [2.48.42](https://github.com/rudderlabs/rudder-sdk-js/compare/rudder-sdk-js@2.48.41...rudder-sdk-js@2.48.42) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js-common` updated to version `3.14.12` +* `@rudderstack/analytics-js-common` updated to version `3.14.14` diff --git a/packages/analytics-v1.1/package.json b/packages/analytics-v1.1/package.json index 96502e5083..a86129ebd7 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.40", + "version": "2.48.42", "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 2e3c68a8dc..f6d34b5aaa 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.40", - "title": "rudder-sdk-js@2.48.40", - "discussion-category": "rudder-sdk-js@2.48.40", + "tag": "rudder-sdk-js@2.48.42", + "title": "rudder-sdk-js@2.48.42", + "discussion-category": "rudder-sdk-js@2.48.42", "notesFile": "./packages/analytics-v1.1/CHANGELOG_LATEST.md" } } diff --git a/packages/loading-scripts/CHANGELOG.md b/packages/loading-scripts/CHANGELOG.md index 7e0e50a784..c9fa4256b4 100644 --- a/packages/loading-scripts/CHANGELOG.md +++ b/packages/loading-scripts/CHANGELOG.md @@ -2,6 +2,16 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [3.0.59](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.58...@rudderstack/analytics-js-loading-scripts@3.0.59) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.16` +## [3.0.58](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.57...@rudderstack/analytics-js-loading-scripts@3.0.58) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.15` ## [3.0.57](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.56...@rudderstack/analytics-js-loading-scripts@3.0.57) (2024-11-30) ### Dependency Updates diff --git a/packages/loading-scripts/CHANGELOG_LATEST.md b/packages/loading-scripts/CHANGELOG_LATEST.md index a100572a21..10d6bcfd48 100644 --- a/packages/loading-scripts/CHANGELOG_LATEST.md +++ b/packages/loading-scripts/CHANGELOG_LATEST.md @@ -1,5 +1,5 @@ -## [3.0.57](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.56...@rudderstack/analytics-js-loading-scripts@3.0.57) (2024-11-30) +## [3.0.59](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-loading-scripts@3.0.58...@rudderstack/analytics-js-loading-scripts@3.0.59) (2024-12-17) ### Dependency Updates -* `@rudderstack/analytics-js` updated to version `3.11.14` +* `@rudderstack/analytics-js` updated to version `3.11.16` diff --git a/packages/loading-scripts/package.json b/packages/loading-scripts/package.json index 2ec0105971..5f809253f1 100644 --- a/packages/loading-scripts/package.json +++ b/packages/loading-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@rudderstack/analytics-js-loading-scripts", - "version": "3.0.57", + "version": "3.0.59", "private": true, "description": "Loading script for RudderStack JavaScript SDK", "main": "./src/index.js", diff --git a/packages/loading-scripts/project.json b/packages/loading-scripts/project.json index ecd1671a02..c5b8097d88 100644 --- a/packages/loading-scripts/project.json +++ b/packages/loading-scripts/project.json @@ -51,9 +51,9 @@ "github": { "executor": "@jscutlery/semver:github", "options": { - "tag": "@rudderstack/analytics-js-loading-scripts@3.0.57", - "title": "@rudderstack/analytics-js-loading-scripts@3.0.57", - "discussion-category": "@rudderstack/analytics-js-loading-scripts@3.0.57", + "tag": "@rudderstack/analytics-js-loading-scripts@3.0.59", + "title": "@rudderstack/analytics-js-loading-scripts@3.0.59", + "discussion-category": "@rudderstack/analytics-js-loading-scripts@3.0.59", "notesFile": "./packages/loading-scripts/CHANGELOG_LATEST.md" } } diff --git a/packages/sanity-suite/CHANGELOG.md b/packages/sanity-suite/CHANGELOG.md index 2aa48a473e..1314dbba2f 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.1.50](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.49...@rudderstack/analytics-js-sanity-suite@3.1.50) (2024-12-17) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.16` +## [3.1.49](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.48...@rudderstack/analytics-js-sanity-suite@3.1.49) (2024-12-06) + +### Dependency Updates + +* `@rudderstack/analytics-js` updated to version `3.11.15` ## [3.1.48](https://github.com/rudderlabs/rudder-sdk-js/compare/@rudderstack/analytics-js-sanity-suite@3.1.47...@rudderstack/analytics-js-sanity-suite@3.1.48) (2024-11-30) ### Dependency Updates diff --git a/packages/sanity-suite/package.json b/packages/sanity-suite/package.json index 0c9ee468c3..e2829a33ef 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.48", + "version": "3.1.50", "private": true, "description": "Sanity suite for testing JS SDK package", "main": "./dist/v3/cdn/testBook.js", diff --git a/sonar-project.properties b/sonar-project.properties index 769605adc0..55d6596a07 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.66.0 +sonar.projectVersion=3.70.0 # Meta-data for the project sonar.links.scm=https://github.com/rudderlabs/rudder-sdk-js