From 5d8ed95981d22e4c7c42c6517896e882a080a6dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Aug 2023 10:50:08 +0300 Subject: [PATCH 01/15] chore(deps): bump rudderlabs/github-action-check-size-limit from 2.4.0 to 2.6.0 (#1278) --- .github/workflows/build-and-quality-checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-quality-checks.yml b/.github/workflows/build-and-quality-checks.yml index 4ffb5baa2..3292a8062 100644 --- a/.github/workflows/build-and-quality-checks.yml +++ b/.github/workflows/build-and-quality-checks.yml @@ -36,7 +36,7 @@ jobs: npm run check:security - name: Execute bundle size checks - uses: rudderlabs/github-action-check-size-limit@v2.4.0 + uses: rudderlabs/github-action-check-size-limit@v2.6.0 env: HUSKY: 0 with: From d9296d3f39b116648ee8c2cdeafa56a5d6e90d2e Mon Sep 17 00:00:00 2001 From: George Bardis <109069547+bardisg@users.noreply.github.com> Date: Wed, 9 Aug 2023 17:15:59 +0300 Subject: [PATCH 02/15] ci: update v3 github actions (#1290) --- .../workflows/build-and-quality-checks-v3.yml | 47 +++++ .github/workflows/create-hotfix-branch-v3.yml | 21 ++ .github/workflows/deploy-beta-v3.yml | 54 ++++-- .github/workflows/deploy-dev-v3.yml | 64 ++++++ .github/workflows/deploy-npm-v3.yml | 154 +++++++++++++++ .github/workflows/deploy-prod-v3.yml | 182 ++++++++++++++++++ .github/workflows/deploy-staging-v3.yml | 66 +++++++ .github/workflows/draft-new-release-v3.yml | 96 +++++++++ .github/workflows/publish-new-release-v3.yml | 133 +++++++++++++ .github/workflows/rollback-v3.yml | 178 +++++++++++++++++ .github/workflows/test-v3.yml | 75 ++++++++ 11 files changed, 1052 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/build-and-quality-checks-v3.yml create mode 100644 .github/workflows/create-hotfix-branch-v3.yml create mode 100644 .github/workflows/deploy-dev-v3.yml create mode 100644 .github/workflows/deploy-npm-v3.yml create mode 100644 .github/workflows/deploy-prod-v3.yml create mode 100644 .github/workflows/deploy-staging-v3.yml create mode 100644 .github/workflows/draft-new-release-v3.yml create mode 100644 .github/workflows/publish-new-release-v3.yml create mode 100644 .github/workflows/rollback-v3.yml create mode 100644 .github/workflows/test-v3.yml diff --git a/.github/workflows/build-and-quality-checks-v3.yml b/.github/workflows/build-and-quality-checks-v3.yml new file mode 100644 index 000000000..ecae4883d --- /dev/null +++ b/.github/workflows/build-and-quality-checks-v3.yml @@ -0,0 +1,47 @@ +name: Build & Code Quality Checks v3 + +on: + pull_request: + branches: ['develop', 'main'] + types: ['opened', 'reopened', 'synchronize'] + +jobs: + build: + name: Build & Code Quality Checks v3 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + run: | + npm run setup:ci + + - name: Execute quality checks + run: | + npm run check:circular + npm run check:duplicates + + - name: Execute security checks + run: | + npm run check:security + + - name: Execute bundle size checks + uses: rudderlabs/github-action-check-size-limit@v2.6.0 + env: + HUSKY: 0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + install_script: setup:ci + build_script: check:size:build -- --concurrency 1 --scope '@rudderstack/analytics-js' --scope '@rudderstack/analytics-js-common' --scope '@rudderstack/analytics-js-plugins' --scope '@rudderstack/analytics-js-service-worker' --scope 'rudder-sdk-js' + script: npx lerna@6 exec --loglevel=silent --concurrency 1 --scope '@rudderstack/analytics-js' --scope '@rudderstack/analytics-js-common' --scope '@rudderstack/analytics-js-plugins' --scope '@rudderstack/analytics-js-service-worker' --scope 'rudder-sdk-js' -- npm run check:size:json --silent + is_monorepo: true diff --git a/.github/workflows/create-hotfix-branch-v3.yml b/.github/workflows/create-hotfix-branch-v3.yml new file mode 100644 index 000000000..407406590 --- /dev/null +++ b/.github/workflows/create-hotfix-branch-v3.yml @@ -0,0 +1,21 @@ +name: Create new hotfix branch v3 + +on: + workflow_dispatch: + inputs: + hotfix_name: + description: Hotfix branch name + required: true + +jobs: + create-branch: + name: Create new hotfix branch v3 + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + steps: + - name: Create branch + uses: peterjgrainger/action-create-branch@v2.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + branch: 'v3-hotfix/${{ github.event.inputs.hotfix_name }}' diff --git a/.github/workflows/deploy-beta-v3.yml b/.github/workflows/deploy-beta-v3.yml index c5131a288..5931ad30e 100644 --- a/.github/workflows/deploy-beta-v3.yml +++ b/.github/workflows/deploy-beta-v3.yml @@ -1,4 +1,4 @@ -name: Deploy BETA Feature v3 +name: Deploy BETA/BugBash Feature v3 on: workflow_dispatch: @@ -9,15 +9,24 @@ permissions: jobs: deploy-tag: - name: Deploy BETA Feature v3 + name: Deploy BETA/BugBash Feature v3 runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/heads/') # TODO: change value to refs/heads/beta/ before merging + if: startsWith(github.ref, 'refs/heads/beta/') || startsWith(github.ref, 'refs/tags/bugbash') steps: - name: Extract feature name from branch - shell: bash - # run: echo "branch=$(echo ${GITHUB_REF#refs/heads/beta})" >>$GITHUB_OUTPUT # TODO: change value to this one - run: echo "branch=v3" >>$GITHUB_OUTPUT id: extract_branch + shell: bash + run: | + source_branch_name=${GITHUB_REF##*/} + RELEASE_TYPE=beta + grep -q "bugbash/" <<< "${GITHUB_REF}" && RELEASE_TYPE=bugbash + FEATURE_NAME=${source_branch_name#bugbash/} + FEATURE_NAME=${FEATURE_NAME#beta/} + FEATURE_NAME=${FEATURE_NAME#refs/heads/} + FEATURE_NAME=${FEATURE_NAME#refs/tags/} + + echo "branch_name=$FEATURE_NAME" >> $GITHUB_OUTPUT + echo "branch_type=$RELEASE_TYPE" >> $GITHUB_OUTPUT - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v2 @@ -34,24 +43,33 @@ jobs: node-version-file: '.nvmrc' cache: 'npm' - - name: Build and sync files to S3 + - name: Install dependencies env: HUSKY: 0 - REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/beta/${{ steps.extract_branch.outputs.branch }}/modern/plugins' + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/modern/plugins' BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} - BUGSNAG_RELEASE_STAGE: 'beta' + BUGSNAG_RELEASE_STAGE: '${{ steps.extract_branch.outputs.branch_type }}' + run: | + npm run setup:ci + + - name: Build release artifacts + env: + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: '${{ steps.extract_branch.outputs.branch_type }}' run: | - npm run setup npm run build:browser npm run build:browser:modern - npm run build:integrations - name: Sync files to S3 beta folder run: | - aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/beta/${{ steps.extract_branch.outputs.branch }}/legacy/ --recursive --cache-control max-age=3600 - aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/beta/${{ steps.extract_branch.outputs.branch }}/modern/ --recursive --cache-control max-age=3600 - aws s3 cp packages/analytics-js-plugins/dist/cdn/legacy/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/beta/${{ steps.extract_branch.outputs.branch }}/legacy/plugins/ --recursive --cache-control max-age=3600 - aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/beta/${{ steps.extract_branch.outputs.branch }}/modern/plugins/ --recursive --cache-control max-age=3600 - aws s3 cp packages/analytics-v1.1/dist/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/beta/${{ steps.extract_branch.outputs.branch }}/legacy/js-integrations/ --recursive --cache-control max-age=3600 - aws s3 cp packages/analytics-v1.1/dist/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/beta/${{ steps.extract_branch.outputs.branch }}/modern/js-integrations/ --recursive --cache-control max-age=3600 - aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/beta/*" + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/modern/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ steps.extract_branch.outputs.branch_type }}/${{ steps.extract_branch.outputs.branch_name }}/*" + diff --git a/.github/workflows/deploy-dev-v3.yml b/.github/workflows/deploy-dev-v3.yml new file mode 100644 index 000000000..b5223447a --- /dev/null +++ b/.github/workflows/deploy-dev-v3.yml @@ -0,0 +1,64 @@ +name: Deploy to DEV v3 + +on: + workflow_dispatch: + pull_request: + branches: ['develop'] + types: + - closed + +permissions: + id-token: write # allows the JWT to be requested from GitHub's OIDC provider + contents: read # This is required for actions/checkout + +jobs: + deploy-tag: + name: Deploy to DEV v3 + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v3-hotfix/') || startsWith(github.ref, 'refs/heads/develop/') || github.event.pull_request.merged == true + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_DEV_ACCOUNT_ID }}:role/${{ secrets.AWS_DEV_S3_SYNC_ROLE }} + aws-region: us-east-1 + + - name: Checkout source branch + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/dev/latest/v3/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_DEV_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'development' + run: | + npm run setup:ci + + - name: Build files + env: + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/dev/latest/v3/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_DEV_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'development' + run: | + npm run build:browser + npm run build:browser:modern + + - name: Sync files to S3 + run: | + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/v3/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/v3/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/legacy/plugins/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/v3/legacy/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/v3/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/v3/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/v3/modern/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_DEV_S3_BUCKET_NAME }}/dev/latest/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_DEV_CF_DISTRIBUTION_ID }} --paths "/dev/latest*" diff --git a/.github/workflows/deploy-npm-v3.yml b/.github/workflows/deploy-npm-v3.yml new file mode 100644 index 000000000..a7dfc6985 --- /dev/null +++ b/.github/workflows/deploy-npm-v3.yml @@ -0,0 +1,154 @@ +name: Deploy to NPM v3 + +on: + workflow_dispatch: + pull_request: + branches: ['main'] + types: + - closed + +permissions: + id-token: write # allows the JWT to be requested from GitHub's OIDC provider + contents: read # This is required for actions/checkout + +jobs: + deploy-tag: + name: Deploy to NPM v3 + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/main') || github.event.pull_request.merged == true + steps: + - name: Checkout source branch + uses: actions/checkout@v3 + + - name: Get new version number + run: | + current_version_v1=$(jq -r .version packages/analytics-v1.1/package.json) + current_version_sw=$(jq -r .version packages/analytics-js-service-worker/package.json) + current_version=$(jq -r .version packages/analytics-js/package.json) + echo "CURRENT_VERSION_V1_VALUE=$current_version" >> $GITHUB_ENV + echo "CURRENT_VERSION_SW_VALUE=$current_version_sw" >> $GITHUB_ENV + echo "CURRENT_VERSION_VALUE=$current_version" >> $GITHUB_ENV + echo "DATE=$(date)" >> $GITHUB_ENV + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'production' + run: | + npm run setup:ci + + - name: Publish package to NPM + env: + HUSKY: 0 + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'production' + run: | + npm set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} + lerna publish from-package --no-private --contents dist/npm + + - name: Send message to Slack channel for v3 + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK v3 NPM Package' + NPM_PACKAGE_URL: 'https://www.npmjs.com/package/@rudderstack/analytics-js' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}" + } + } + ] + } + + - name: Send message to Slack channel for v1.1 + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK 1.1 NPM Package' + NPM_PACKAGE_URL: 'https://www.npmjs.com/package/rudder-sdk-js' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_V1_VALUE }}>*\n${{ env.DATE }}" + } + } + ] + } + + + - name: Send message to Slack channel for Service Worker + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK Service Worker NPM Package' + NPM_PACKAGE_URL: 'https://www.npmjs.com/package/@rudderstack/analytics-js-service-worker' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{ env.NPM_PACKAGE_URL }}|${{ env.CURRENT_VERSION_SW_VALUE }}>*\n${{ env.DATE }}" + } + } + ] + } diff --git a/.github/workflows/deploy-prod-v3.yml b/.github/workflows/deploy-prod-v3.yml new file mode 100644 index 000000000..c422a6e39 --- /dev/null +++ b/.github/workflows/deploy-prod-v3.yml @@ -0,0 +1,182 @@ +name: Deploy to PROD v3 + +on: + workflow_dispatch: + pull_request: + branches: ['main'] + types: + - closed + +permissions: + id-token: write # allows the JWT to be requested from GitHub's OIDC provider + contents: read # This is required for actions/checkout + +jobs: + deploy-tag: + name: Deploy to PROD v3 + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/main') || github.event.pull_request.merged == true + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_PROD_ACCOUNT_ID }}:role/${{ secrets.AWS_PROD_S3_SYNC_ROLE }} + aws-region: us-east-1 + + - name: Checkout source branch + uses: actions/checkout@v3 + + - name: Get new version number + run: | + current_version_v1=$(jq -r .version packages/analytics-v1.1/package.json) + current_version=$(jq -r .version packages/analytics-js/package.json) + echo "CURRENT_VERSION_V1_VALUE=$current_version" >> $GITHUB_ENV + echo "CURRENT_VERSION_VALUE=$current_version" >> $GITHUB_ENV + echo "DATE=$(date)" >> $GITHUB_ENV + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'production' + run: | + npm run setup:ci + + - name: Build release artifacts + env: + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'production' + run: | + npm run build:browser + npm run build:browser:modern + +# - name: Sync files to S3 v1.1 folder +# run: | +# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/rudder-analytics.min.js --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/rudder-analytics.min.js.map --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/js-integrations/ --recursive --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/list_integration_sdks.html --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/rudder-analytics.min.js --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/rudder-analytics.min.js.map --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/js-integrations/ --recursive --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/list_integration_sdks.html --cache-control max-age=3600 +# AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/v1.1*" +# +# - name: Sync files to S3 v1.1 versioned folder +# run: | +# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/rudder-analytics.min.js --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/rudder-analytics.min.js.map --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/js-integrations/ --recursive --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/list_integration_sdks.html --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/rudder-analytics.min.js --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/rudder-analytics.min.js.map --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/js-integrations/ --recursive --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/list_integration_sdks.html --cache-control max-age=3600 +# AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_V1_VALUE }}*" + + - name: Sync files to S3 v3 folder + run: | + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/v3/*" + + - name: Sync files to S3 v3 versioned folder + run: | + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_VALUE }}/*" + + - name: Sync files to S3 latest + run: | +# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/rudder-analytics.min.js --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/rudder-analytics.min.js.map --cache-control max-age=3600 +# aws s3 pc packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/js-integrations/ --recursive --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/list_integration_sdks.html --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/rudder-analytics.min.js --cache-control max-age=3600 +# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/rudder-analytics.min.js.map --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/js-integrations/ --recursive --cache-control max-age=3600 +# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/list_integration_sdks.html --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/latest*" + + - name: Send message to Slack channel v3 + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK v3 Browser Package' + CDN_URL: 'https://cdn.rudderlabs.com/v3/modern/rsa.min.js' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{ env.CDN_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}" + } + } + ] + } + +# - name: Send message to Slack channel +# id: slack +# uses: slackapi/slack-github-action@v1.24.0 +# env: +# SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} +# PROJECT_NAME: 'JS SDK v1.1 Browser Package' +# CDN_URL: 'https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js' +# with: +# channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} +# payload: | +# { +# "blocks": [ +# { +# "type": "header", +# "text": { +# "type": "plain_text", +# "text": "New release: ${{ env.PROJECT_NAME }}" +# } +# }, +# { +# "type": "divider" +# }, +# { +# "type": "section", +# "text": { +# "type": "mrkdwn", +# "text": "*Release: <${{ env.CDN_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}" +# } +# } +# ] +# } diff --git a/.github/workflows/deploy-staging-v3.yml b/.github/workflows/deploy-staging-v3.yml new file mode 100644 index 000000000..5b146aaa0 --- /dev/null +++ b/.github/workflows/deploy-staging-v3.yml @@ -0,0 +1,66 @@ +name: Deploy to STAGING v3 + +on: + workflow_dispatch: + +permissions: + id-token: write # allows the JWT to be requested from GitHub's OIDC provider + contents: read # This is required for actions/checkout + +jobs: + deploy-tag: + name: Deploy to STAGING v3 + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/develop') || startsWith(github.ref, 'refs/heads/v3-hotfix-release') || startsWith(github.ref, 'refs/heads/v3-release') || startsWith(github.ref, 'refs/heads/v3-hotfix/') + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_STAGING_ACCOUNT_ID }}:role/${{ secrets.AWS_STAGING_S3_SYNC_ROLE }} + aws-region: us-east-1 + + - name: Checkout source branch + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/staging/latest/v3/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_STAGING_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'development' + run: | + npm run setup:ci + + - name: Build assets + env: + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/staging/latest/v3/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_STAGING_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'development' + run: | + npm run build:browser --staging=true + npm run build:browser:modern --staging=true + + - name: Sync files to S3 v1.1 staging folder + run: | + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics-staging.min.js s3://${{ secrets.AWS_STAGING_S3_BUCKET_NAME }}/staging/latest/rudder-analytics-staging.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics-staging.min.js.map s3://${{ secrets.AWS_STAGING_S3_BUCKET_NAME }}/staging/latest/rudder-analytics-staging.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_STAGING_S3_BUCKET_NAME }}/staging/latest/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics-staging.min.js s3://${{ secrets.AWS_STAGING_S3_BUCKET_NAME }}/staging/latest/modern/rudder-analytics-staging.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics-staging.min.js.map s3://${{ secrets.AWS_STAGING_S3_BUCKET_NAME }}/staging/latest/modern/rudder-analytics-staging.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_STAGING_S3_BUCKET_NAME }}/staging/latest/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_STAGING_CF_DISTRIBUTION_ID }} --paths "/staging/latest*" + + - name: Sync files to S3 v3 staging folder + run: | + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/staging/latest/v3/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/staging/latest/v3/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/staging/latest/v3/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/staging/latest/v3/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/staging/latest/v3/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/staging/latest/v3*" diff --git a/.github/workflows/draft-new-release-v3.yml b/.github/workflows/draft-new-release-v3.yml new file mode 100644 index 000000000..4d90ceb7d --- /dev/null +++ b/.github/workflows/draft-new-release-v3.yml @@ -0,0 +1,96 @@ +name: Draft new release v3 + +on: + workflow_dispatch: + +jobs: + draft-new-release: + name: Draft a new release v3 + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/heads/develop') || startsWith(github.ref, 'refs/heads/v3-hotfix/') + steps: + - name: Checkout source branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + run: | + npm run setup:ci + + # In order to make a commit, we need to initialize a user. + # You may choose to write something less generic here if you want, it doesn't matter functionality wise. + - name: Initialize mandatory git config + run: | + git config user.name "GitHub actions" + git config user.email noreply@github.com + + # Calculate the next release version based on conventional semantic release + - name: Create release branch + id: create-release + env: + HUSKY: 0 + run: | + source_branch_name=${GITHUB_REF##*/} + release_type=v3-release + grep -q "v3-hotfix/" <<< "${GITHUB_REF}" && release_type=v3-hotfix-release + git fetch origin main + git fetch --tags origin + git merge origin/main + current_version=$(jq -r .version package.json) + + npm run bump-version:monorepo + new_version=$(jq -r .version package.json) + git reset --hard + + branch_name="${release_type}/${new_version}" + + echo "Source branch for new release is $source_branch_name" + echo "Current version is $current_version" + echo "Release type is $release_type" + echo "New version is $new_version" + echo "New release branch name is $branch_name" + git checkout -b "$branch_name" + git push --set-upstream origin "$branch_name" + + echo "source_branch_name=$source_branch_name" >> $GITHUB_OUTPUT + echo "branch_name=$branch_name" >> $GITHUB_OUTPUT + echo "new_version=$new_version" >> $GITHUB_OUTPUT + echo "CURRENT_VERSION_VALUE=$current_version" >> $GITHUB_ENV + echo "NEW_VERSION_VALUE=$new_version" >> $GITHUB_ENV + + - name: Update changelog & bump version + id: finish-release + env: + HUSKY: 0 + run: | + echo "Current version: $CURRENT_VERSION_VALUE" + echo "New version: $NEW_VERSION_VALUE" + git fetch --no-tags --prune --depth=100 origin main + npm run lerna:version + npm run bump-version:monorepo + npx replace $CURRENT_VERSION_VALUE $NEW_VERSION_VALUE sonar-project.properties + git add sonar-project.properties + git commit -m "chore: sync versions and generate release logs" -n + + - name: Push new version in release branch + run: | + git push --follow-tags + + - name: Create pull request into production + uses: repo-sync/pull-request@v2 + with: + source_branch: ${{ steps.create-release.outputs.branch_name }} + destination_branch: 'main' + github_token: ${{ secrets.PAT }} + pr_title: 'chore(release): pulling ${{ steps.create-release.outputs.branch_name }} into main' + pr_body: ':crown: *An automated PR*' + pr_reviewer: 'bardisg,MoumitaM,saikumarrs' diff --git a/.github/workflows/publish-new-release-v3.yml b/.github/workflows/publish-new-release-v3.yml new file mode 100644 index 000000000..b8d7b2c27 --- /dev/null +++ b/.github/workflows/publish-new-release-v3.yml @@ -0,0 +1,133 @@ +name: Publish new github release v3 + +on: + pull_request: + branches: + - main + types: + - closed + +jobs: + release: + name: Publish new release v3 + runs-on: ubuntu-latest + if: (startsWith(github.event.pull_request.head.ref, 'v3-release/') || startsWith(github.event.pull_request.head.ref, 'v3-hotfix-release/')) && github.event.pull_request.merged == true # only merged pull requests must trigger this job + steps: + - name: Extract version from branch name (for release branches) + id: extract-version + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#v3-} + VERSION=${VERSION#hotfix-} + VERSION=${VERSION#release/} + + echo "release_version=$VERSION" >> $GITHUB_OUTPUT + + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + run: | + npm run setup:ci + + # In order to make a commit, we need to initialize a user. + # You may choose to write something less generic here if you want, it doesn't matter functionality wise. + - name: Initialize mandatory git config + run: | + git config user.name "GitHub actions" + git config user.email noreply@github.com + + - name: Create Monorepo Release Tag + id: create_monorepo_release + run: | + git tag -a v${{ steps.extract-version.outputs.release_version }} -m "chore: release v${{ steps.extract-version.outputs.release_version }}" + git push origin refs/tags/v${{ steps.extract-version.outputs.release_version }} + + - name: Get the two latest versions + run: | + CURRENT_VERSION=$(git tag -l "v*" --sort=-version:refname | head -n 1) + LAST_VERSION=$(git tag -l "v*" --sort=-version:refname | head -n 2 | awk 'NR == 2 { print $1 }') + + echo "Current version: $CURRENT_VERSION" + echo "Previous version: $LAST_VERSION" + + echo "current_version=$(echo $CURRENT_VERSION)" >> $GITHUB_ENV + echo "last_version=$(echo $LAST_VERSION)" >> $GITHUB_ENV + echo "DATE=$(date)" >> $GITHUB_ENV + + - name: Create GitHub Releases + id: create_release + env: + HUSKY: 0 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + npm run release:github -- --base=$last_version --head=$current_version + + - name: Create pull request into develop + uses: repo-sync/pull-request@v2 + with: + source_branch: 'main' + destination_branch: 'develop' + github_token: ${{ secrets.PAT }} + pr_title: 'chore(release): pulling main into develop post release v${{ steps.extract-version.outputs.release_version }}' + pr_body: ':crown: *An automated PR*' + pr_reviewer: 'bardisg,MoumitaM,saikumarrs' + + - name: Delete hotfix release branch + uses: koj-co/delete-merged-action@master + if: startsWith(github.event.pull_request.head.ref, 'v3-hotfix-release/') + with: + branches: 'v3-hotfix-release/*' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Delete release branch + uses: koj-co/delete-merged-action@master + if: startsWith(github.event.pull_request.head.ref, 'v3-release/') + with: + branches: 'v3-release/*' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Send message to Slack channel + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK (v3) monorepo' + RELEASES_URL: 'https://github.com/rudderlabs/rudder-sdk-react-native/compare/' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{env.RELEASES_URL}}${{ steps.extract-previous-version.outputs.previous_version }}...v${{ steps.extract-version.outputs.release_version }}|v${{ steps.extract-version.outputs.release_version }}>*\n${{ env.DATE }}" + } + } + ] + } diff --git a/.github/workflows/rollback-v3.yml b/.github/workflows/rollback-v3.yml new file mode 100644 index 000000000..cf58d6253 --- /dev/null +++ b/.github/workflows/rollback-v3.yml @@ -0,0 +1,178 @@ +name: Rollback v3 + +on: + workflow_dispatch: + +permissions: + id-token: write # allows the JWT to be requested from GitHub's OIDC provider + contents: read # This is required for actions/checkout + +jobs: + deploy-tag: + name: Rollback v3 + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/main') + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ secrets.AWS_PROD_ACCOUNT_ID }}:role/${{ secrets.AWS_PROD_S3_SYNC_ROLE }} + aws-region: us-east-1 + + - name: Checkout source branch + uses: actions/checkout@v3 + + - name: Get new version number + run: | + current_version_v1=$(jq -r .version packages/analytics-v1.1/package.json) + current_version=$(jq -r .version packages/analytics-js/package.json) + echo "CURRENT_VERSION_V1_VALUE=$current_version" >> $GITHUB_ENV + echo "CURRENT_VERSION_VALUE=$current_version" >> $GITHUB_ENV + echo "DATE=$(date)" >> $GITHUB_ENV + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'production' + run: | + npm run setup:ci + + - name: Build release artifacts + env: + REMOTE_MODULES_BASE_PATH: 'https://cdn.rudderlabs.com/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins' + BUGSNAG_API_KEY: ${{ secrets.RS_PROD_BUGSNAG_API_KEY }} + BUGSNAG_RELEASE_STAGE: 'production' + run: | + npm run build:browser + npm run build:browser:modern + + - name: Sync files to S3 v1.1 folder + run: | + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/list_integration_sdks.html --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v1.1/modern/list_integration_sdks.html --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/v1.1*" + + - name: Sync files to S3 v1.1 versioned folder + run: | + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/list_integration_sdks.html --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_V1_VALUE }}/modern/list_integration_sdks.html --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_V1_VALUE }}*" + + - name: Sync files to S3 v3 folder + run: | + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/v3/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/v3/*" + + - name: Sync files to S3 v3 versioned folder + run: | + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_VALUE }}/*" + + - name: Sync files to S3 latest + run: | + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 pc packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/list_integration_sdks.html --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/rudder-analytics.min.js --cache-control max-age=3600 + aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/rudder-analytics.min.js.map --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/list_integration_sdks.html --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/legacy/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/plugins/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/legacy/js-integrations/ --recursive --cache-control max-age=3600 + aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/js-integrations/ --recursive --cache-control max-age=3600 + AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/latest*" + + - name: Send message to Slack channel v3 + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK v3 Browser Package Rollback' + CDN_URL: 'https://cdn.rudderlabs.com/v3/modern/rsa.min.js' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{ env.CDN_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}" + } + } + ] + } + + - name: Send message to Slack channel + id: slack + uses: slackapi/slack-github-action@v1.24.0 + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + PROJECT_NAME: 'JS SDK v1.1 Browser Package Rollback' + CDN_URL: 'https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js' + with: + channel-id: ${{ secrets.SLACK_RELEASE_CHANNEL_ID }} + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "New release: ${{ env.PROJECT_NAME }}" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Release: <${{ env.CDN_URL }}|${{ env.CURRENT_VERSION_VALUE }}>*\n${{ env.DATE }}" + } + } + ] + } diff --git a/.github/workflows/test-v3.yml b/.github/workflows/test-v3.yml new file mode 100644 index 000000000..2f4895218 --- /dev/null +++ b/.github/workflows/test-v3.yml @@ -0,0 +1,75 @@ +name: 'Unit Tests, Coverage & Sonar v3' + +on: + workflow_dispatch: + push: + branches: ['main', 'develop'] + pull_request: + branches: ['main', 'develop'] + types: ['opened', 'reopened', 'synchronize'] + +jobs: + build: + name: 'Unit Tests, Coverage & Sonar v3' + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + env: + HUSKY: 0 + run: | + npm run setup:ci + + - name: Execute unit tests + run: | + npm run test:ci + + - name: Execute linting check + run: | + npm run check:lint + + - name: Fix filesystem paths in generated reports + run: | + sed -i 's+home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js/reports/coverage/lcov.info + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js/reports/sonar/results-report.xml + + sed -i 's+home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-common/reports/coverage/lcov.info + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-common/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-common/reports/sonar/results-report.xml + + sed -i 's+home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-integrations/reports/coverage/lcov.info + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-integrations/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-integrations/reports/sonar/results-report.xml + + sed -i 's+home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-plugins/reports/coverage/lcov.info + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-plugins/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-plugins/reports/sonar/results-report.xml + + sed -i 's+home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-service-worker/reports/coverage/lcov.info + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-service-worker/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-js-service-worker/reports/sonar/results-report.xml + + sed -i 's+home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-v1.1/reports/coverage/lcov.info + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-v1.1/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/analytics-v1.1/reports/sonar/results-report.xml + + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/loading-scripts/reports/eslint.json + sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' packages/sanity-suite/reports/eslint.json + + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From d7e35177aa78791de72a9b9ef401d1855cf02913 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Wed, 9 Aug 2023 17:20:47 +0300 Subject: [PATCH 03/15] ci: fix github action syntax --- .github/workflows/deploy-prod-v3.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/deploy-prod-v3.yml b/.github/workflows/deploy-prod-v3.yml index c422a6e39..feb329a4e 100644 --- a/.github/workflows/deploy-prod-v3.yml +++ b/.github/workflows/deploy-prod-v3.yml @@ -100,16 +100,9 @@ jobs: aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/${{ env.CURRENT_VERSION_VALUE }}/modern/js-integrations/ --recursive --cache-control max-age=3600 AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/${{ env.CURRENT_VERSION_VALUE }}/*" + # TODO: add back in v1.1 deployment to latest from git history - name: Sync files to S3 latest run: | -# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/rudder-analytics.min.js --cache-control max-age=3600 -# aws s3 cp packages/analytics-v1.1/dist/cdn/legacy/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/rudder-analytics.min.js.map --cache-control max-age=3600 -# aws s3 pc packages/analytics-js-integrations/dist/cdn/legacy/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/js-integrations/ --recursive --cache-control max-age=3600 -# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/list_integration_sdks.html --cache-control max-age=3600 -# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/rudder-analytics.min.js --cache-control max-age=3600 -# aws s3 cp packages/analytics-v1.1/dist/cdn/modern/rudder-analytics.min.js.map s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/rudder-analytics.min.js.map --cache-control max-age=3600 -# aws s3 cp packages/analytics-js-integrations/dist/cdn/modern/js-integrations/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/js-integrations/ --recursive --cache-control max-age=3600 -# aws s3 cp packages/analytics-js-integrations/public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/list_integration_sdks.html --cache-control max-age=3600 aws s3 cp packages/analytics-js/dist/cdn/legacy/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/legacy/ --recursive --cache-control max-age=3600 aws s3 cp packages/analytics-js/dist/cdn/modern/iife/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/ --recursive --cache-control max-age=3600 aws s3 cp packages/analytics-js-plugins/dist/cdn/modern/plugins/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/v3/modern/plugins/ --recursive --cache-control max-age=3600 From 605ced7ad5407bd053c1380fae70cac7b9b5ff69 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Wed, 9 Aug 2023 18:07:23 +0300 Subject: [PATCH 04/15] chore(monorepo): bump version as prerelease on v3 draft release action --- .github/workflows/draft-new-release-v3.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/draft-new-release-v3.yml b/.github/workflows/draft-new-release-v3.yml index 4d90ceb7d..5af3a5826 100644 --- a/.github/workflows/draft-new-release-v3.yml +++ b/.github/workflows/draft-new-release-v3.yml @@ -79,6 +79,8 @@ jobs: npm run bump-version:monorepo npx replace $CURRENT_VERSION_VALUE $NEW_VERSION_VALUE sonar-project.properties git add sonar-project.properties + git add package-lock.json + git add package.json git commit -m "chore: sync versions and generate release logs" -n - name: Push new version in release branch From e796b2b8b6f361f303a44bdd83685eaca21bbd39 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Thu, 10 Aug 2023 10:52:53 +0300 Subject: [PATCH 05/15] ci: fix v3 npm deploy action --- .github/workflows/deploy-npm-v3.yml | 4 ++-- .github/workflows/deploy-prod-v3.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-npm-v3.yml b/.github/workflows/deploy-npm-v3.yml index a7dfc6985..ee9102131 100644 --- a/.github/workflows/deploy-npm-v3.yml +++ b/.github/workflows/deploy-npm-v3.yml @@ -89,7 +89,7 @@ jobs: } - name: Send message to Slack channel for v1.1 - id: slack + id: slackv1 uses: slackapi/slack-github-action@v1.24.0 env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} @@ -122,7 +122,7 @@ jobs: - name: Send message to Slack channel for Service Worker - id: slack + id: slackSw uses: slackapi/slack-github-action@v1.24.0 env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/deploy-prod-v3.yml b/.github/workflows/deploy-prod-v3.yml index feb329a4e..7b376f671 100644 --- a/.github/workflows/deploy-prod-v3.yml +++ b/.github/workflows/deploy-prod-v3.yml @@ -143,7 +143,7 @@ jobs: } # - name: Send message to Slack channel -# id: slack +# id: slackv1 # uses: slackapi/slack-github-action@v1.24.0 # env: # SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} From e4a098b72fda57d107e4c426aeb252919eb641e5 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Thu, 10 Aug 2023 15:13:39 +0300 Subject: [PATCH 06/15] ci: fix v3 npm deploy action --- .github/workflows/deploy-npm-v3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-npm-v3.yml b/.github/workflows/deploy-npm-v3.yml index ee9102131..f6490d5d5 100644 --- a/.github/workflows/deploy-npm-v3.yml +++ b/.github/workflows/deploy-npm-v3.yml @@ -54,7 +54,7 @@ jobs: BUGSNAG_RELEASE_STAGE: 'production' run: | npm set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} - lerna publish from-package --no-private --contents dist/npm + lerna@6 publish from-package --no-private --contents dist/npm - name: Send message to Slack channel for v3 id: slack From 609f7b99b5a421bec92cd35003c10cd11819a426 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Thu, 10 Aug 2023 15:23:06 +0300 Subject: [PATCH 07/15] ci: fix v3 npm deploy action --- .github/workflows/deploy-npm-v3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-npm-v3.yml b/.github/workflows/deploy-npm-v3.yml index f6490d5d5..f6ba56b05 100644 --- a/.github/workflows/deploy-npm-v3.yml +++ b/.github/workflows/deploy-npm-v3.yml @@ -54,7 +54,7 @@ jobs: BUGSNAG_RELEASE_STAGE: 'production' run: | npm set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} - lerna@6 publish from-package --no-private --contents dist/npm + npx lerna@6 publish from-package --no-private --contents dist/npm - name: Send message to Slack channel for v3 id: slack From 8c3a754b938edad0ba204fa95f4cfebc6e474fee Mon Sep 17 00:00:00 2001 From: George Bardis Date: Thu, 10 Aug 2023 15:27:59 +0300 Subject: [PATCH 08/15] ci: fix v3 npm deploy action --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cf82205f4..6a2a7988c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: sed -i 's+/home/runner/work/rudder-sdk-js/rudder-sdk-js+/github/workspace+g' reports/eslint.json - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@v1.9 + uses: SonarSource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From bc4005229ca6fe335449eef0de9c36c6ce8ef767 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Thu, 10 Aug 2023 15:33:15 +0300 Subject: [PATCH 09/15] ci: fix v3 npm deploy action --- .github/workflows/deploy-npm-v3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-npm-v3.yml b/.github/workflows/deploy-npm-v3.yml index f6ba56b05..a80f2e936 100644 --- a/.github/workflows/deploy-npm-v3.yml +++ b/.github/workflows/deploy-npm-v3.yml @@ -25,7 +25,7 @@ jobs: current_version_v1=$(jq -r .version packages/analytics-v1.1/package.json) current_version_sw=$(jq -r .version packages/analytics-js-service-worker/package.json) current_version=$(jq -r .version packages/analytics-js/package.json) - echo "CURRENT_VERSION_V1_VALUE=$current_version" >> $GITHUB_ENV + echo "CURRENT_VERSION_V1_VALUE=$current_version_v1" >> $GITHUB_ENV echo "CURRENT_VERSION_SW_VALUE=$current_version_sw" >> $GITHUB_ENV echo "CURRENT_VERSION_VALUE=$current_version" >> $GITHUB_ENV echo "DATE=$(date)" >> $GITHUB_ENV From 6661f2683787e4551963a816aa215be411071d19 Mon Sep 17 00:00:00 2001 From: George Bardis Date: Thu, 10 Aug 2023 17:46:54 +0300 Subject: [PATCH 10/15] ci: fix v3 publish release action --- .github/workflows/publish-new-release-v3.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish-new-release-v3.yml b/.github/workflows/publish-new-release-v3.yml index b8d7b2c27..ff5389ff6 100644 --- a/.github/workflows/publish-new-release-v3.yml +++ b/.github/workflows/publish-new-release-v3.yml @@ -65,14 +65,14 @@ jobs: echo "last_version=$(echo $LAST_VERSION)" >> $GITHUB_ENV echo "DATE=$(date)" >> $GITHUB_ENV - - name: Create GitHub Releases - id: create_release - env: - HUSKY: 0 - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - npm run release:github -- --base=$last_version --head=$current_version +# - name: Create GitHub Releases +# id: create_release +# env: +# HUSKY: 0 +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# run: | +# npm run release:github -- --base=$last_version --head=$current_version - name: Create pull request into develop uses: repo-sync/pull-request@v2 From 7afcc168de223c9765b13398c5bd1a78d9344333 Mon Sep 17 00:00:00 2001 From: Mihir Bhalala <77438541+mihir-4116@users.noreply.github.com> Date: Wed, 16 Aug 2023 16:45:37 +0530 Subject: [PATCH 11/15] fix(INT-183): resolve sonar issues and move integration script to separate file (#1249) * fix: integration specific bugsnag issues * fix(hubspot, intercom, keen): sonar issues * fix(kissmetrics, lemnisk, livechat, lotame): sonar issues * fix(kissmetrics, lemnisk, lotame): sonar issues * fix(optimizly): sonar issues * fix(adobe analytics, amplitude, appcues, axeptio): sonar code smells * chore: module exports improvements * fix(integrations): address Sonar code smells in index.js file * Revert "chore: module exports improvements" This reverts commit 8c97e94d8fcf83fc306fc191f19cb63cb8b59e21. * fix(clevertap, comscore, convertflow, criteo, dcmfloodlight): sonar code smells * fix(drip, engage, fbpixel, ga4, googleoptimize): sonar code smells * fix(cretio): security issue * chore: move sdk loader to separate file (#1195) * chore(axeptio): move sdk loader to separate file * chore(bingads): move sdk loader to separate file * chore: disable es-lint for loader files * fix: added no sonar schan for sdk loader * chore(chartbeat): move sdk loader to new file * chore(Comscore): move sdk loader to new file * chore(CustomerIO): fix schema * chore(Drip): fix schema * chore(Engage): move sdk loader to different file * chore(Fullstory): move sdk loader to different file * chore(Googleads): move sdk loader to different file * chore(GTM): move sdk loader to different file * chore(Heap): move sdk loader to different file * chore(Hotjar): move sdk loader to different file * chore(Intercom): move sdk loader to different file * chore(Kissmetrics): move sdk loader to different file * chore(Lemnisk): move sdk loader to different file * chore(Livechat): move sdk loader to different file * chore(Lytics): move sdk loader to different file * chore(Matomo): move sdk loader to different file * chore(MicrosoftClarity): move sdk loader to different file * chore(Mixpanel): move sdk loader to different file * chore(Moengage): move sdk loader to different file * chore(Olark): move sdk loader to different file * chore(Pendo): move sdk loader to different file * chore(PinterestTag): move sdk loader to different file * chore(Posthog): move sdk loader to different file * chore(Profitwell): move sdk loader to different file * chore(Qualtrics): move sdk loader to different file * chore(QuoraPixel): move sdk loader to different file * chore(RedditPixel): move sdk loader to different file * chore(Refiner): move sdk loader to different file * chore(Rockerbox): move sdk loader to different file * chore(Satismeter): move sdk loader to different file * fix: add logger import * chore(TiktoAds): move sdk loader to separate file * chore(vwo): move sdk loader to separate file * chore(YandexMetrica): move sdk loader to separate file * chore: rename loaderjs to nativeSdkLoaderjs and loader function to loadNativeSdk * chore(rollbar): move sdk loader to a separate file * chore(sendinblue): move sdk loader to a separate file * chore(snapengage): move sdk loader to a separate file * chore(snappixel): move sdk loader to a separate file * chore(woopra): move sdk loader to a separate file * chore(yandexmetrica): move sdk loader to a separate file * chore: refactor code * chore: refactor sdk loader files * chore: refactor sdk loader files * fix: make use of ScriptLoader function wherever possible * chore: refactor code * chore: removed es-lint disabled rules * fix(intercom, iterable, kissmetrics, klaviyo): sonar code smells * fix(launchdarkly, linkedininsighttag, livechat, lotame, lytics): sonar code smells * fix(matomo, mouseflow, optimizely, pendo, pinteresttag, podsights): sonar code smells * fix(pendo): utils file sonar code smells * fix(postaffiliatepro, posthog, profitwell, qualaroo): sonar code smells * fix(qualtrics, quorapixel, redditpixel, refiner): sonar code smells * fix(rockerbox, rollbar, satismeter, sendinblue): sonar code smells * fix(sentry, shynet, snapengage): sonar code smells * fix(snappixel, tiktok ads, tvsquared): sonar code smells * fix(vero, woopra, yandex metrica): sonar code smells * fix(fbpixel): track call refactor * fix(customerio): sonar code smells * fix(matomo): sonar code smells * chore: code improvements * fix(intercom): sonar code smells * fix(integrations): code smells * fix(integrations): sonar duplicate code * fix(dcmfloodlight): sonar security issue * fix(ga4): sonar duplicate code issue * feat(integrations): additional improvements * feat(integrations): additional improvements * chore: reverted generateRandomNumber function logic * chore: additional improvements * fix loader script * chore: code review changes * chore: added refiner track event check * fix(hubspot): identify and track calls * chore: shynet code review changes * chore: cretio code review changes * chore: loadscript fixes * chore: loadscript fixes * chore: code review changes --------- Co-authored-by: Ujjwal Abhishek <63387036+ujjwal-ab@users.noreply.github.com> Co-authored-by: ujjwal-ab --- .size-limit.js | 6 +- .../transformationHandler.js | 2 +- src/integrations/AdobeAnalytics/browser.js | 18 +- .../AdobeAnalytics/eCommHandle.js | 14 +- .../AdobeAnalytics/heartbeatHandle.js | 8 +- src/integrations/AdobeAnalytics/index.js | 4 +- src/integrations/Adroll/browser.js | 2 +- src/integrations/Adroll/index.js | 4 +- src/integrations/Adroll/util.js | 8 +- src/integrations/Amplitude/browser.js | 70 +-- src/integrations/Amplitude/index.js | 4 +- .../{loader.js => nativeSdkLoader.js} | 8 +- src/integrations/Amplitude/utils.js | 25 + src/integrations/Appcues/browser.js | 1 - src/integrations/Appcues/index.js | 4 +- src/integrations/Axeptio/browser.js | 20 +- src/integrations/Axeptio/index.js | 4 +- src/integrations/Axeptio/nativeSdkLoader.js | 14 + src/integrations/BingAds/browser.js | 33 +- src/integrations/BingAds/index.js | 4 +- src/integrations/BingAds/nativeSdkLoader.js | 30 + src/integrations/Braze/browser.js | 4 +- src/integrations/Braze/nativeSdkLoader.js | 4 +- src/integrations/Bugsnag/index.js | 4 +- src/integrations/Chartbeat/browser.js | 76 +-- src/integrations/Chartbeat/index.js | 4 +- src/integrations/Chartbeat/nativeSdkLoader.js | 7 + src/integrations/Clevertap/browser.js | 5 +- src/integrations/Clevertap/index.js | 4 +- src/integrations/Comscore/browser.js | 57 +- src/integrations/Comscore/index.js | 4 +- src/integrations/Comscore/nativeSdkLoader.js | 10 + src/integrations/ConvertFlow/browser.js | 1 - src/integrations/ConvertFlow/index.js | 4 +- src/integrations/ConvertFlow/utils.js | 2 +- src/integrations/Criteo/browser.js | 25 +- src/integrations/Criteo/index.js | 4 +- src/integrations/Criteo/utils.js | 231 ++++--- src/integrations/CustomerIO/browser.js | 31 +- src/integrations/CustomerIO/index.js | 4 +- .../CustomerIO/nativeSdkLoader.js | 28 + src/integrations/DCMFloodlight/browser.js | 12 +- src/integrations/DCMFloodlight/index.js | 4 +- src/integrations/DCMFloodlight/utils.js | 149 +++-- src/integrations/Drip/browser.js | 21 +- src/integrations/Drip/index.js | 4 +- src/integrations/Drip/nativeSdkLoader.js | 10 + src/integrations/Drip/utils.js | 2 +- src/integrations/Engage/browser.js | 34 +- src/integrations/Engage/index.js | 4 +- src/integrations/Engage/nativeSdkLoader.js | 25 + src/integrations/Engage/utils.js | 3 +- src/integrations/FacebookPixel/browser.js | 568 ++++-------------- src/integrations/FacebookPixel/index.js | 4 +- src/integrations/FacebookPixel/utils.js | 205 ++++++- src/integrations/Fullstory/browser.js | 113 +--- src/integrations/Fullstory/index.js | 4 +- src/integrations/Fullstory/nativeSdkLoader.js | 69 +++ src/integrations/GA/browser.js | 161 +++-- src/integrations/GA/index.js | 4 +- src/integrations/GA/index.test.js | 30 +- src/integrations/GA360/index.js | 4 +- src/integrations/GA4/browser.js | 6 +- src/integrations/GA4/index.js | 4 +- src/integrations/GA4/test/input.js | 137 ++--- src/integrations/GA4/test/output.js | 20 +- src/integrations/GoogleAds/browser.js | 22 +- src/integrations/GoogleAds/nativeSdkLoader.js | 26 + src/integrations/GoogleOptimize/browser.js | 4 +- src/integrations/GoogleOptimize/index.js | 4 +- src/integrations/GoogleTagManager/browser.js | 20 +- src/integrations/GoogleTagManager/index.js | 4 +- .../GoogleTagManager/nativeSdkLoader.js | 21 + src/integrations/Heap/browser.js | 38 +- src/integrations/Heap/index.js | 4 +- src/integrations/Heap/nativeSdkLoader.js | 40 ++ src/integrations/Hotjar/browser.js | 44 +- src/integrations/Hotjar/index.js | 4 +- src/integrations/Hotjar/nativeSdkLoader.js | 21 + src/integrations/HubSpot/browser.js | 117 ++-- src/integrations/HubSpot/index.js | 4 +- src/integrations/INTERCOM/browser.js | 169 ++---- src/integrations/INTERCOM/index.js | 4 +- src/integrations/INTERCOM/nativeSdkLoader.js | 46 ++ src/integrations/INTERCOM/utils.js | 68 +++ src/integrations/Iterable/browser.js | 102 ++-- src/integrations/Iterable/index.js | 4 +- src/integrations/Iterable/utils.js | 79 ++- src/integrations/June/index.js | 4 +- src/integrations/Keen/browser.js | 64 +- src/integrations/Keen/index.js | 4 +- src/integrations/Kissmetrics/browser.js | 194 +++--- src/integrations/Kissmetrics/index.js | 4 +- .../Kissmetrics/nativeSdkLoader.js | 23 + src/integrations/Klaviyo/browser.js | 58 +- src/integrations/Klaviyo/index.js | 4 +- src/integrations/Klaviyo/util.js | 66 +- src/integrations/LaunchDarkly/index.js | 4 +- src/integrations/LaunchDarkly/utils.js | 5 +- src/integrations/Lemnisk/browser.js | 57 +- src/integrations/Lemnisk/index.js | 4 +- src/integrations/Lemnisk/nativeSdkLoader.js | 34 ++ .../LinkedInInsightTag/browser.js | 1 + src/integrations/LinkedInInsightTag/index.js | 4 +- src/integrations/LiveChat/browser.js | 38 +- src/integrations/LiveChat/index.js | 4 +- src/integrations/LiveChat/nativeSdkLoader.js | 40 ++ src/integrations/LiveChat/util.js | 5 +- src/integrations/Lotame/LotameStorage.js | 3 +- src/integrations/Lotame/browser.js | 32 +- src/integrations/Lotame/index.js | 4 +- src/integrations/Lytics/browser.js | 124 +--- src/integrations/Lytics/index.js | 4 +- src/integrations/Lytics/nativeSdkLoader.js | 77 +++ src/integrations/Matomo/browser.js | 85 +-- src/integrations/Matomo/index.js | 5 +- src/integrations/Matomo/nativeSdkLoader.js | 20 + src/integrations/Matomo/util.js | 565 +++++++++-------- src/integrations/MicrosoftClarity/browser.js | 22 +- src/integrations/MicrosoftClarity/index.js | 4 +- .../MicrosoftClarity/nativeSdkLoader.js | 22 + src/integrations/Mixpanel/browser.js | 161 ++--- src/integrations/Mixpanel/index.js | 4 +- src/integrations/Mixpanel/nativeSdkLoader.js | 71 +++ src/integrations/Mixpanel/util.js | 31 +- src/integrations/MoEngage/browser.js | 89 +-- src/integrations/MoEngage/index.js | 4 +- src/integrations/MoEngage/nativeSdkLoader.js | 66 ++ src/integrations/Mouseflow/browser.js | 7 +- src/integrations/Mouseflow/index.js | 4 +- src/integrations/Olark/browser.js | 28 +- src/integrations/Olark/index.js | 4 +- src/integrations/Olark/nativeSdkLoader.js | 31 + src/integrations/Optimizely/browser.js | 82 ++- src/integrations/Optimizely/index.js | 4 +- src/integrations/Optimizely/utils.js | 23 + src/integrations/Pendo/browser.js | 40 +- src/integrations/Pendo/index.js | 4 +- src/integrations/Pendo/nativeSdkLoader.js | 32 + src/integrations/PinterestTag/browser.js | 32 +- src/integrations/PinterestTag/index.js | 4 +- .../PinterestTag/nativeSdkLoader.js | 19 + src/integrations/Podsights/browser.js | 10 +- src/integrations/Podsights/index.js | 4 +- src/integrations/Podsights/utils.js | 12 +- src/integrations/PostAffiliatePro/browser.js | 64 +- src/integrations/PostAffiliatePro/index.js | 4 +- src/integrations/PostAffiliatePro/utils.js | 56 +- src/integrations/Posthog/browser.js | 94 +-- src/integrations/Posthog/index.js | 4 +- src/integrations/Posthog/nativeSdkLoader.js | 48 ++ src/integrations/Posthog/utils.js | 36 ++ src/integrations/ProfitWell/browser.js | 28 +- src/integrations/ProfitWell/index.js | 4 +- .../ProfitWell/nativeSdkLoader.js | 25 + src/integrations/Qualaroo/index.js | 4 +- src/integrations/Qualaroo/utils.js | 1 + src/integrations/Qualtrics/browser.js | 91 +-- src/integrations/Qualtrics/index.js | 4 +- src/integrations/Qualtrics/nativeSdkLoader.js | 79 +++ src/integrations/QuantumMetric/index.js | 4 +- src/integrations/QuoraPixel/browser.js | 20 +- src/integrations/QuoraPixel/index.js | 4 +- .../QuoraPixel/nativeSdkLoader.js | 21 + src/integrations/RedditPixel/browser.js | 25 +- src/integrations/RedditPixel/index.js | 4 +- .../RedditPixel/nativeSdkLoader.js | 21 + src/integrations/Refiner/browser.js | 36 +- src/integrations/Refiner/index.js | 4 +- src/integrations/Refiner/nativeSdkLoader.js | 20 + src/integrations/Rockerbox/browser.js | 31 +- src/integrations/Rockerbox/index.js | 4 +- src/integrations/Rockerbox/nativeSdkLoader.js | 32 + src/integrations/RollBar/browser.js | 407 +------------ src/integrations/RollBar/index.js | 4 +- src/integrations/RollBar/nativeSdkLoader.js | 383 ++++++++++++ src/integrations/Satismeter/browser.js | 21 +- src/integrations/Satismeter/index.js | 4 +- .../Satismeter/nativeSdkLoader.js | 23 + src/integrations/Sendinblue/browser.js | 36 +- src/integrations/Sendinblue/index.js | 4 +- .../Sendinblue/nativeSdkLoader.js | 36 ++ src/integrations/Sendinblue/utils.js | 6 +- src/integrations/Sentry/browser.js | 6 +- src/integrations/Sentry/index.js | 4 +- src/integrations/Sentry/utils.js | 34 +- src/integrations/Shynet/browser.js | 8 +- src/integrations/Shynet/index.js | 4 +- src/integrations/SnapEngage/browser.js | 21 +- src/integrations/SnapEngage/index.js | 5 +- .../SnapEngage/nativeSdkLoader.js | 24 + src/integrations/SnapEngage/util.js | 10 +- src/integrations/SnapPixel/browser.js | 17 +- src/integrations/SnapPixel/index.js | 4 +- src/integrations/SnapPixel/nativeSdkLoader.js | 20 + src/integrations/SnapPixel/util.js | 269 ++++----- src/integrations/TVSquared/browser.js | 72 +-- src/integrations/TVSquared/index.js | 4 +- src/integrations/TVSquared/utils.js | 40 ++ src/integrations/TiktokAds/browser.js | 59 +- src/integrations/TiktokAds/index.js | 4 +- src/integrations/TiktokAds/nativeSdkLoader.js | 52 ++ src/integrations/VWO/browser.js | 55 +- src/integrations/VWO/index.js | 4 +- src/integrations/VWO/nativeSdkLoader.js | 64 ++ src/integrations/Vero/browser.js | 16 +- src/integrations/Vero/index.js | 4 +- src/integrations/Woopra/browser.js | 45 +- src/integrations/Woopra/index.js | 4 +- src/integrations/Woopra/nativeSdkLoader.js | 43 ++ src/integrations/YandexMetrica/browser.js | 55 +- src/integrations/YandexMetrica/index.js | 4 +- .../YandexMetrica/nativeSdkLoader.js | 35 ++ src/integrations/YandexMetrica/utils.js | 86 ++- 214 files changed, 4239 insertions(+), 4109 deletions(-) rename src/integrations/Amplitude/{loader.js => nativeSdkLoader.js} (95%) create mode 100644 src/integrations/Amplitude/utils.js create mode 100644 src/integrations/Axeptio/nativeSdkLoader.js create mode 100644 src/integrations/BingAds/nativeSdkLoader.js create mode 100644 src/integrations/Chartbeat/nativeSdkLoader.js create mode 100644 src/integrations/Comscore/nativeSdkLoader.js create mode 100644 src/integrations/CustomerIO/nativeSdkLoader.js create mode 100644 src/integrations/Drip/nativeSdkLoader.js create mode 100644 src/integrations/Engage/nativeSdkLoader.js create mode 100644 src/integrations/Fullstory/nativeSdkLoader.js create mode 100644 src/integrations/GoogleAds/nativeSdkLoader.js create mode 100644 src/integrations/GoogleTagManager/nativeSdkLoader.js create mode 100644 src/integrations/Heap/nativeSdkLoader.js create mode 100644 src/integrations/Hotjar/nativeSdkLoader.js create mode 100644 src/integrations/INTERCOM/nativeSdkLoader.js create mode 100644 src/integrations/INTERCOM/utils.js create mode 100644 src/integrations/Kissmetrics/nativeSdkLoader.js create mode 100644 src/integrations/Lemnisk/nativeSdkLoader.js create mode 100644 src/integrations/LiveChat/nativeSdkLoader.js create mode 100644 src/integrations/Lytics/nativeSdkLoader.js create mode 100644 src/integrations/Matomo/nativeSdkLoader.js create mode 100644 src/integrations/MicrosoftClarity/nativeSdkLoader.js create mode 100644 src/integrations/Mixpanel/nativeSdkLoader.js create mode 100644 src/integrations/MoEngage/nativeSdkLoader.js create mode 100644 src/integrations/Olark/nativeSdkLoader.js create mode 100644 src/integrations/Optimizely/utils.js create mode 100644 src/integrations/Pendo/nativeSdkLoader.js create mode 100644 src/integrations/PinterestTag/nativeSdkLoader.js create mode 100644 src/integrations/Posthog/nativeSdkLoader.js create mode 100644 src/integrations/Posthog/utils.js create mode 100644 src/integrations/ProfitWell/nativeSdkLoader.js create mode 100644 src/integrations/Qualtrics/nativeSdkLoader.js create mode 100644 src/integrations/QuoraPixel/nativeSdkLoader.js create mode 100644 src/integrations/RedditPixel/nativeSdkLoader.js create mode 100644 src/integrations/Refiner/nativeSdkLoader.js create mode 100644 src/integrations/Rockerbox/nativeSdkLoader.js create mode 100644 src/integrations/RollBar/nativeSdkLoader.js create mode 100644 src/integrations/Satismeter/nativeSdkLoader.js create mode 100644 src/integrations/Sendinblue/nativeSdkLoader.js create mode 100644 src/integrations/SnapEngage/nativeSdkLoader.js create mode 100644 src/integrations/SnapPixel/nativeSdkLoader.js create mode 100644 src/integrations/TVSquared/utils.js create mode 100644 src/integrations/TiktokAds/nativeSdkLoader.js create mode 100644 src/integrations/VWO/nativeSdkLoader.js create mode 100644 src/integrations/Woopra/nativeSdkLoader.js create mode 100644 src/integrations/YandexMetrica/nativeSdkLoader.js diff --git a/.size-limit.js b/.size-limit.js index a6380aea4..6246aee7f 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -7,19 +7,19 @@ module.exports = [ name: 'Core - CDN', path: 'dist/legacy/rudder-analytics.min.js', gzip: true, - limit: '37.5 kB', + limit: '40.9 kB', }, { name: 'All Integrations - CDN', path: 'dist/legacy/js-integrations/*.min.js', gzip: true, - limit: '435.5 kB', + limit: '443.5 kB', }, { name: 'Core - NPM', path: 'dist/npm-lib/index.js', gzip: true, - limit: '37.5 kB', + limit: '40.8 kB', }, { name: 'Service Worker - NPM', diff --git a/src/features/core/deviceModeTransformation/transformationHandler.js b/src/features/core/deviceModeTransformation/transformationHandler.js index afc88af99..a69c41e6a 100644 --- a/src/features/core/deviceModeTransformation/transformationHandler.js +++ b/src/features/core/deviceModeTransformation/transformationHandler.js @@ -120,7 +120,7 @@ class TransformationsHandler { status, errorMessage: 'Retries exhausted', }); - return; + } } } diff --git a/src/integrations/AdobeAnalytics/browser.js b/src/integrations/AdobeAnalytics/browser.js index 9fafe5265..7a1e19b6b 100644 --- a/src/integrations/AdobeAnalytics/browser.js +++ b/src/integrations/AdobeAnalytics/browser.js @@ -1,8 +1,4 @@ -/* eslint-disable no-param-reassign */ -/* eslint-disable no-underscore-dangle */ -/* eslint-disable camelcase */ /* eslint-disable class-methods-use-this */ - import * as utils from './util'; import * as ecommUtils from './eCommHandle'; import * as heartbeatUtils from './heartbeatHandle'; @@ -60,17 +56,13 @@ class AdobeAnalytics { } initAdobeAnalyticsClient() { - const { s } = window; + const { s, Visitor } = window; s.trackingServer = s.trackingServer || this.trackingServerUrl; s.trackingServerSecure = s.trackingServerSecure || this.trackingServerSecureUrl; - if ( - this.marketingCloudOrgId && - window.Visitor && - typeof window.Visitor.getInstance === 'function' - ) { - s.visitor = window.Visitor.getInstance(this.marketingCloudOrgId, { - trackingServer: window.s.trackingServer || this.trackingServerUrl, - trackingServerSecure: window.s.trackingServerSecure || this.trackingServerSecureUrl, + if (this.marketingCloudOrgId && Visitor && typeof Visitor.getInstance === 'function') { + s.visitor = Visitor.getInstance(this.marketingCloudOrgId, { + trackingServer: s.trackingServer || this.trackingServerUrl, + trackingServerSecure: s.trackingServerSecure || this.trackingServerSecureUrl, }); } } diff --git a/src/integrations/AdobeAnalytics/eCommHandle.js b/src/integrations/AdobeAnalytics/eCommHandle.js index 2e432d72e..60ba55736 100644 --- a/src/integrations/AdobeAnalytics/eCommHandle.js +++ b/src/integrations/AdobeAnalytics/eCommHandle.js @@ -1,5 +1,3 @@ -/* eslint-disable camelcase */ - import * as utils from './util'; const productViewHandle = (rudderElement, pageName) => { @@ -20,9 +18,9 @@ const productRemovedHandle = (rudderElement, pageName) => { const orderCompletedHandle = (rudderElement, pageName) => { utils.clearWindowSKeys(utils.getDynamicKeys()); const { properties } = rudderElement.message; - const { purchaseId, transactionId, order_id } = properties; - utils.updateWindowSKeys(purchaseId || order_id, 'purchaseID'); - utils.updateWindowSKeys(transactionId || order_id, 'transactionID'); + const { purchaseId, transactionId, order_id: orderId } = properties; + utils.updateWindowSKeys(purchaseId || orderId, 'purchaseID'); + utils.updateWindowSKeys(transactionId || orderId, 'transactionID'); utils.processEvent(rudderElement, 'purchase', pageName); }; @@ -30,9 +28,9 @@ const orderCompletedHandle = (rudderElement, pageName) => { const checkoutStartedHandle = (rudderElement, pageName) => { utils.clearWindowSKeys(utils.getDynamicKeys()); const { properties } = rudderElement.message; - const { purchaseId, transactionId, order_id } = properties; - utils.updateWindowSKeys(purchaseId || order_id, 'purchaseID'); - utils.updateWindowSKeys(transactionId || order_id, 'transactionID'); + const { purchaseId, transactionId, order_id: orderId } = properties; + utils.updateWindowSKeys(purchaseId || orderId, 'purchaseID'); + utils.updateWindowSKeys(transactionId || orderId, 'transactionID'); utils.processEvent(rudderElement, 'scCheckout', pageName); }; diff --git a/src/integrations/AdobeAnalytics/heartbeatHandle.js b/src/integrations/AdobeAnalytics/heartbeatHandle.js index 9c23695f0..5c95d9129 100644 --- a/src/integrations/AdobeAnalytics/heartbeatHandle.js +++ b/src/integrations/AdobeAnalytics/heartbeatHandle.js @@ -37,14 +37,14 @@ const initHeartbeat = (rudderElement) => { const { va } = window.ADB; const { message } = rudderElement; const { properties, context } = message; - const { channel, video_player, session_id } = properties; + const { channel, video_player, session_id, ovp } = properties; const mediaHeartbeatConfig = new va.MediaHeartbeatConfig(); const mediaHeartbeatDelegate = new va.MediaHeartbeatDelegate(); mediaHeartbeatConfig.trackingServer = config.heartbeatTrackingServerUrl; mediaHeartbeatConfig.channel = channel || ''; - mediaHeartbeatConfig.ovp = properties.ovp || 'unknown'; + mediaHeartbeatConfig.ovp = ovp || 'unknown'; mediaHeartbeatConfig.appVersion = context.app.version || 'unknown'; mediaHeartbeatConfig.playerName = video_player || 'unknown'; mediaHeartbeatConfig.ssl = config.sslHeartbeat; @@ -57,9 +57,7 @@ const initHeartbeat = (rudderElement) => { return playhead; }; - mediaHeartbeatDelegate.getQoSObject = () => { - return qosData; - }; + mediaHeartbeatDelegate.getQoSObject = () => qosData; mediaHeartbeats[session_id || 'default'] = { heartbeat: new va.MediaHeartbeat(mediaHeartbeatDelegate, mediaHeartbeatConfig, window.s), diff --git a/src/integrations/AdobeAnalytics/index.js b/src/integrations/AdobeAnalytics/index.js index ce520feb8..87b5c02ba 100644 --- a/src/integrations/AdobeAnalytics/index.js +++ b/src/integrations/AdobeAnalytics/index.js @@ -1,3 +1 @@ -import AdobeAnalytics from './browser'; - -export { AdobeAnalytics }; +export { default as AdobeAnalytics } from './browser'; diff --git a/src/integrations/Adroll/browser.js b/src/integrations/Adroll/browser.js index 2e1ab4ef2..a9c1cf8c4 100644 --- a/src/integrations/Adroll/browser.js +++ b/src/integrations/Adroll/browser.js @@ -83,7 +83,7 @@ class Adroll { data.adroll_segments = segmentId; window.__adroll.record_user(data); } else { - logger.error(`The event ${message.event} is not mapped to any segmentId. Aborting!`); + logger.error(`The event ${event} is not mapped to any segmentId. Aborting!`); } } // record_user fires the correct pixel in accordance with the event configured in the dashboard diff --git a/src/integrations/Adroll/index.js b/src/integrations/Adroll/index.js index e42278cac..76e93ec25 100644 --- a/src/integrations/Adroll/index.js +++ b/src/integrations/Adroll/index.js @@ -1,3 +1 @@ -import Adroll from './browser'; - -export { Adroll }; +export { default as Adroll } from './browser'; diff --git a/src/integrations/Adroll/util.js b/src/integrations/Adroll/util.js index e8a6e3066..a47d4b6c1 100644 --- a/src/integrations/Adroll/util.js +++ b/src/integrations/Adroll/util.js @@ -1,5 +1,3 @@ -/* eslint-disable no-param-reassign */ - // here we map the properties which give information about a singleproduct const PRODUCT_EVENTS = ['product clicked', 'product viewed', 'product added']; const ORDER_EVENTS = [ @@ -10,7 +8,8 @@ const ORDER_EVENTS = [ 'order updated', ]; -const productEvent = (properties) => { +const productEvent = (params) => { + const properties = params; if (properties.price) { properties.adroll_conversion_value = properties.price; delete properties.price; @@ -21,7 +20,8 @@ const productEvent = (properties) => { // here we map the properties which give information about the order // like order_id or revenue -const orderEvent = (properties) => { +const orderEvent = (params) => { + const properties = params; if (properties.orderId) { properties.order_id = properties.orderId; delete properties.orderId; diff --git a/src/integrations/Amplitude/browser.js b/src/integrations/Amplitude/browser.js index 05ec60075..26355ebcf 100644 --- a/src/integrations/Amplitude/browser.js +++ b/src/integrations/Amplitude/browser.js @@ -1,12 +1,11 @@ -/* eslint-disable no-param-reassign */ -/* eslint-disable camelcase */ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import Logger from '../../utils/logger'; import { type } from '../../utils/utils'; import { NAME } from './constants'; -import { loader } from './loader'; +import { loadNativeSdk } from './nativeSdkLoader'; +import { getTraitsToSetOnce, getTraitsToIncrement } from './utils'; const logger = new Logger(NAME); @@ -30,11 +29,10 @@ class Amplitude { this.trackGclid = config.trackGclid || false; this.saveParamsReferrerOncePerSession = config.saveParamsReferrerOncePerSession || false; this.deviceIdFromUrlParam = config.deviceIdFromUrlParam || false; - // this.mapQueryParams = config.mapQueryParams; this.trackRevenuePerProduct = config.trackRevenuePerProduct || false; this.preferAnonymousIdForDeviceId = config.preferAnonymousIdForDeviceId || false; - this.traitsToSetOnce = []; - this.traitsToIncrement = []; + this.traitsToSetOnce = getTraitsToSetOnce(config); + this.traitsToIncrement = getTraitsToIncrement(config); this.appendFieldsToEventProps = config.appendFieldsToEventProps || false; this.unsetParamsReferrerOnNewSession = config.unsetParamsReferrerOnNewSession || false; this.trackProductsOnce = config.trackProductsOnce || false; @@ -44,26 +42,11 @@ class Amplitude { propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError, destinationId: this.destinationId, } = destinationInfo ?? {}); - - if (config.traitsToSetOnce && config.traitsToSetOnce.length > 0) { - config.traitsToSetOnce.forEach((element) => { - if (element && element.traits && element.traits !== '') { - this.traitsToSetOnce.push(element.traits); - } - }); - } - if (config.traitsToIncrement && config.traitsToIncrement.length > 0) { - config.traitsToIncrement.forEach((element) => { - if (element && element.traits && element.traits !== '') { - this.traitsToIncrement.push(element.traits); - } - }); - } } init() { if (this.analytics.loadIntegration) { - loader(window, document); + loadNativeSdk(window, document); } const initOptions = { @@ -86,6 +69,15 @@ class Amplitude { } } + isLoaded() { + logger.debug('in Amplitude isLoaded'); + return !!window?.amplitude?.getInstance()?.options; + } + + isReady() { + return !!window?.amplitude?.getInstance()?.options; + } + identify(rudderElement) { logger.debug('in Amplitude identify'); @@ -182,23 +174,24 @@ class Amplitude { } trackingEventAndRevenuePerProduct(trackEventMessage, products, shouldTrackEventPerProduct) { - let { revenueType } = trackEventMessage.properties; - const { revenue, revenue_type } = trackEventMessage.properties; - revenueType = revenueType || revenue_type; + const eventMessage = trackEventMessage; + let { revenueType } = eventMessage.properties; + const { revenue, revenue_type: revenueTtype } = eventMessage.properties; + revenueType = revenueType || revenueTtype; products.forEach((product) => { - trackEventMessage.properties = product; - trackEventMessage.event = 'Product Purchased'; + eventMessage.properties = product; + eventMessage.event = 'Product Purchased'; if (this.trackRevenuePerProduct) { if (revenueType) { - trackEventMessage.properties.revenueType = revenueType; + eventMessage.properties.revenueType = revenueType; } if (revenue) { - trackEventMessage.properties.revenue = revenue; + eventMessage.properties.revenue = revenue; } - this.trackRevenue(trackEventMessage); + this.trackRevenue(eventMessage); } if (shouldTrackEventPerProduct) { - this.logEventAndCorrespondingRevenue(trackEventMessage, true); + this.logEventAndCorrespondingRevenue(eventMessage, true); } }); } @@ -303,11 +296,11 @@ class Amplitude { const { properties, event } = rudderMessage; let { price, productId, quantity } = properties; - const { revenue, product_id, revenue_type } = properties; + const { revenue, product_id: pId, revenue_type: revenueTtype } = properties; const revenueType = - properties.revenueType || revenue_type || mapRevenueType[event.toLowerCase()]; + properties.revenueType || revenueTtype || mapRevenueType[event.toLowerCase()]; - productId = productId || product_id; + productId = productId || pId; // If neither revenue nor price is present, then return // else send price and quantity from properties to amplitude @@ -354,15 +347,6 @@ class Amplitude { category: product.category, }; } - - isLoaded() { - logger.debug('in Amplitude isLoaded'); - return !!(window.amplitude && window.amplitude.getInstance().options); - } - - isReady() { - return !!(window.amplitude && window.amplitude.getInstance().options); - } } export default Amplitude; diff --git a/src/integrations/Amplitude/index.js b/src/integrations/Amplitude/index.js index 11a4906bd..cc7f3b8dc 100644 --- a/src/integrations/Amplitude/index.js +++ b/src/integrations/Amplitude/index.js @@ -1,3 +1 @@ -import Amplitude from './browser'; - -export { Amplitude }; +export { default as Amplitude } from './browser'; diff --git a/src/integrations/Amplitude/loader.js b/src/integrations/Amplitude/nativeSdkLoader.js similarity index 95% rename from src/integrations/Amplitude/loader.js rename to src/integrations/Amplitude/nativeSdkLoader.js index f97791582..af9ff0475 100644 --- a/src/integrations/Amplitude/loader.js +++ b/src/integrations/Amplitude/nativeSdkLoader.js @@ -1,8 +1,6 @@ import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; -// START-NO-SONAR-SCAN -/* eslint-disable */ -function loader(e, t) { +function loadNativeSdk(e, t) { var n = e.amplitude || { _q: [], _iq: {} }; var r = t.createElement('script'); r.type = 'text/javascript'; @@ -110,7 +108,5 @@ function loader(e, t) { e.amplitude = n; } window, document; -/* eslint-enable */ -// END-NO-SONAR-SCAN -export { loader }; +export { loadNativeSdk }; diff --git a/src/integrations/Amplitude/utils.js b/src/integrations/Amplitude/utils.js new file mode 100644 index 000000000..8bbf24b4f --- /dev/null +++ b/src/integrations/Amplitude/utils.js @@ -0,0 +1,25 @@ +const getTraitsToSetOnce = (config) => { + const traitsToSetOnce = []; + if (config.traitsToSetOnce && config.traitsToSetOnce.length > 0) { + config.traitsToSetOnce.forEach((element) => { + if (element?.traits && element.traits !== '') { + traitsToSetOnce.push(element.traits); + } + }); + } + return traitsToSetOnce; +}; + +const getTraitsToIncrement = (config) => { + const traitsToIncrement = []; + if (config.traitsToIncrement && config.traitsToIncrement.length > 0) { + config.traitsToIncrement.forEach((element) => { + if (element?.traits && element.traits !== '') { + traitsToIncrement.push(element.traits); + } + }); + } + return traitsToIncrement; +}; + +export { getTraitsToSetOnce, getTraitsToIncrement }; diff --git a/src/integrations/Appcues/browser.js b/src/integrations/Appcues/browser.js index bd6f20b30..34091c57b 100644 --- a/src/integrations/Appcues/browser.js +++ b/src/integrations/Appcues/browser.js @@ -17,7 +17,6 @@ class Appcues { propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError, destinationId: this.destinationId, } = destinationInfo ?? {}); - // this.sendToAllDestinations = config.sendToAll; } init() { diff --git a/src/integrations/Appcues/index.js b/src/integrations/Appcues/index.js index 56deb690c..a335eeb27 100644 --- a/src/integrations/Appcues/index.js +++ b/src/integrations/Appcues/index.js @@ -1,3 +1 @@ -import Appcues from './browser'; - -export { Appcues }; +export { default as Appcues } from './browser'; diff --git a/src/integrations/Axeptio/browser.js b/src/integrations/Axeptio/browser.js index 7f1482a63..aa8fb93a3 100644 --- a/src/integrations/Axeptio/browser.js +++ b/src/integrations/Axeptio/browser.js @@ -1,9 +1,10 @@ +/* eslint-disable func-names */ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import { NAME } from './constants'; import Logger from '../../utils/logger'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import makeACall from './utils'; +import { loadNativeSdk } from './nativeSdkLoader'; const logger = new Logger(NAME); @@ -23,22 +24,9 @@ class Axeptio { } = destinationInfo ?? {}); } - loadScript() { - window.axeptioSettings = { - clientId: this.clientId, - }; - (function (d, s) { - var t = d.getElementsByTagName(s)[0], - e = d.createElement(s); - e.async = true; - e.src = '//static.axept.io/sdk.js'; - e.setAttribute('data-loader', LOAD_ORIGIN), t.parentNode.insertBefore(e, t); - })(document, 'script'); - } - init() { logger.debug('===In init Axeptio==='); - this.loadScript(); + loadNativeSdk(this.clientId); } isLoaded() { @@ -57,7 +45,7 @@ class Axeptio { // this function is used to record the triggered axeptio events through callback recordAxeptioEvents() { window._axcb = window._axcb || []; - window._axcb.push(function () { + window._axcb.push(() => { window.__axeptioSDK.on( 'cookies:*', function (payload, event) { diff --git a/src/integrations/Axeptio/index.js b/src/integrations/Axeptio/index.js index 8b470267d..d73789b84 100644 --- a/src/integrations/Axeptio/index.js +++ b/src/integrations/Axeptio/index.js @@ -1,3 +1 @@ -import Axeptio from './browser'; - -export { Axeptio }; +export { default as Axeptio } from './browser'; diff --git a/src/integrations/Axeptio/nativeSdkLoader.js b/src/integrations/Axeptio/nativeSdkLoader.js new file mode 100644 index 000000000..5f2730a5f --- /dev/null +++ b/src/integrations/Axeptio/nativeSdkLoader.js @@ -0,0 +1,14 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(d, s, clientId) { + window.axeptioSettings = { + clientId, + }; + var t = d.getElementsByTagName(s)[0], + e = d.createElement(s); + e.async = true; + e.src = '//static.axept.io/sdk.js'; + e.setAttribute('data-loader', LOAD_ORIGIN), t.parentNode.insertBefore(e, t); +} + +export { loadNativeSdk }; diff --git a/src/integrations/BingAds/browser.js b/src/integrations/BingAds/browser.js index 284db5f2c..ec0624244 100644 --- a/src/integrations/BingAds/browser.js +++ b/src/integrations/BingAds/browser.js @@ -1,9 +1,9 @@ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; import { buildCommonPayload, buildEcommPayload, EXCLUSION_KEYS } from './utils'; import { removeUndefinedAndNullValues } from '../../utils/commonUtils'; import { extractCustomFields } from '../../utils/utils'; +import { loadNativeSdk } from './nativeSdkLoader'; class BingAds { constructor(config, analytics, destinationInfo) { @@ -21,38 +21,9 @@ class BingAds { this.uniqueId = `bing${this.tagID}`; } - /* eslint-disable */ - loadBingadsScript = () => { - ((w, d, t, r, u) => { - let f; - let n; - let i; - (w[u] = w[u] || []), - (f = () => { - const o = { - ti: this.tagID, - }; - (o.q = w[u]), (w[u] = new UET(o)); - }), - (n = d.createElement(t)), - (n.src = r), - (n.async = 1), - n.setAttribute('data-loader', LOAD_ORIGIN), - (n.onload = n.onreadystatechange = - function () { - const s = this.readyState; - (s && s !== 'loaded' && s !== 'complete' && typeof w['UET'] === 'function') || - (f(), (n.onload = n.onreadystatechange = null)); - }), - (i = d.getElementsByTagName(t)[0]), - i.parentNode.insertBefore(n, i); - })(window, document, 'script', 'https://bat.bing.com/bat.js', this.uniqueId); - }; - /* eslint-enable */ - init = () => { - this.loadBingadsScript(); logger.debug('===in init BingAds==='); + loadNativeSdk(this.uniqueId, this.tagID); }; isLoaded = () => { diff --git a/src/integrations/BingAds/index.js b/src/integrations/BingAds/index.js index 2b0ace9aa..fb6098376 100644 --- a/src/integrations/BingAds/index.js +++ b/src/integrations/BingAds/index.js @@ -1,3 +1 @@ -import { BingAds } from './browser'; - -export { BingAds }; +export { BingAds } from './browser'; diff --git a/src/integrations/BingAds/nativeSdkLoader.js b/src/integrations/BingAds/nativeSdkLoader.js new file mode 100644 index 000000000..ba92e7e40 --- /dev/null +++ b/src/integrations/BingAds/nativeSdkLoader.js @@ -0,0 +1,30 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(uniqueId, tagID) { + ((w, d, t, r, u) => { + let f; + let n; + let i; + (w[u] = w[u] || []), + (f = () => { + const o = { + ti: tagID, + }; + (o.q = w[u]), (w[u] = new UET(o)); + }), + (n = d.createElement(t)), + (n.src = r), + (n.async = 1), + n.setAttribute('data-loader', LOAD_ORIGIN), + (n.onload = n.onreadystatechange = + function () { + const s = this.readyState; + (s && s !== 'loaded' && s !== 'complete' && typeof w['UET'] === 'function') || + (f(), (n.onload = n.onreadystatechange = null)); + }), + (i = d.getElementsByTagName(t)[0]), + i.parentNode.insertBefore(n, i); + })(window, document, 'script', 'https://bat.bing.com/bat.js', uniqueId); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Braze/browser.js b/src/integrations/Braze/browser.js index 23ad00469..cdd7f3cf6 100644 --- a/src/integrations/Braze/browser.js +++ b/src/integrations/Braze/browser.js @@ -5,7 +5,7 @@ import { NAME } from './constants'; import Storage from '../../utils/storage/index'; import { isObject } from '../../utils/utils'; import { handlePurchase, formatGender, handleReservedProperties } from './utils'; -import { load } from './nativeSdkLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; const logger = new Logger(NAME); @@ -48,7 +48,7 @@ class Braze { init() { logger.debug('===in init Braze==='); - load(); + loadNativeSdk(); window.braze.initialize(this.appKey, { enableLogging: this.enableBrazeLogging, baseUrl: this.endPoint, diff --git a/src/integrations/Braze/nativeSdkLoader.js b/src/integrations/Braze/nativeSdkLoader.js index 3320eee21..75904f152 100644 --- a/src/integrations/Braze/nativeSdkLoader.js +++ b/src/integrations/Braze/nativeSdkLoader.js @@ -1,7 +1,7 @@ import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { BrazeOperationString } from './constants'; -const load = () => { +const loadNativeSdk = () => { // load braze +(function (a, p, P, b, y) { a.braze = {}; @@ -32,4 +32,4 @@ const load = () => { })(window, document, 'script'); }; -export { load }; +export { loadNativeSdk }; diff --git a/src/integrations/Bugsnag/index.js b/src/integrations/Bugsnag/index.js index 629380102..195867dff 100644 --- a/src/integrations/Bugsnag/index.js +++ b/src/integrations/Bugsnag/index.js @@ -1,3 +1 @@ -import { Bugsnag } from './browser'; - -export { Bugsnag }; +export { Bugsnag } from './browser'; diff --git a/src/integrations/Chartbeat/browser.js b/src/integrations/Chartbeat/browser.js index 73103c494..0f7b50383 100644 --- a/src/integrations/Chartbeat/browser.js +++ b/src/integrations/Chartbeat/browser.js @@ -1,3 +1,5 @@ +/* eslint-disable compat/compat */ +/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import onBody from 'on-body'; import logger from '../../utils/logUtil'; @@ -6,7 +8,7 @@ import { INTEGRATION_LOAD_CHECK_INTERVAL, } from '../../utils/constants'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class Chartbeat { constructor(config, analytics, destinationInfo) { @@ -14,10 +16,11 @@ class Chartbeat { logger.setLogLevel(analytics.logLevel); } this.analytics = analytics; // use this to modify failed integrations or for passing events from callback to other destinations - this._sf_async_config = window._sf_async_config = window._sf_async_config || {}; + window._sf_async_config = window._sf_async_config || {}; window._sf_async_config.useCanonical = true; window._sf_async_config.uid = config.uid; window._sf_async_config.domain = config.domain; + this._sf_async_config = window._sf_async_config; this.isVideo = !!config.video; this.sendNameAndCategoryAsTitle = config.sendNameAndCategoryAsTitle || true; this.subscriberEngagementKeys = config.subscriberEngagementKeys || []; @@ -36,12 +39,20 @@ class Chartbeat { logger.debug('===in init Chartbeat==='); } - identify(rudderElement) { - logger.debug('in Chartbeat identify'); + isLoaded() { + logger.debug('in Chartbeat isLoaded'); + if (!this.isFirstPageCallMade) { + return true; + } + return !!window.pSUPERFLY; } - track(rudderElement) { - logger.debug('in Chartbeat track'); + isFailed() { + return this.failed; + } + + isReady() { + return !!window.pSUPERFLY; } page(rudderElement) { @@ -68,22 +79,6 @@ class Chartbeat { } } - isLoaded() { - logger.debug('in Chartbeat isLoaded'); - if (!this.isFirstPageCallMade) { - return true; - } - return !!window.pSUPERFLY; - } - - isFailed() { - return this.failed; - } - - isReady() { - return !!window.pSUPERFLY; - } - loadConfig(rudderElement) { const { properties } = rudderElement.message; const category = properties ? properties.category : undefined; @@ -97,29 +92,24 @@ class Chartbeat { if (author) window._sf_async_config.authors = author; if (title) window._sf_async_config.title = title; - const _cbq = (window._cbq = window._cbq || []); + window._cbq = window._cbq || []; + const { _cbq } = window; - for (const key in properties) { - if (!properties.hasOwnProperty(key)) continue; - if (this.subscriberEngagementKeys.indexOf(key) > -1) { + Object.keys(properties) + .filter( + (key) => + Object.prototype.hasOwnProperty.call(properties, key) && + this.subscriberEngagementKeys.includes(key), + ) + .forEach((key) => { _cbq.push([key, properties[key]]); - } - } + }); } initAfterPage() { onBody(() => { const script = this.isVideo ? 'chartbeat_video.js' : 'chartbeat.js'; - function loadChartbeat() { - const e = document.createElement('script'); - const n = document.getElementsByTagName('script')[0]; - e.type = 'text/javascript'; - e.async = true; - e.src = `//static.chartbeat.com/js/${script}`; - e.setAttribute('data-loader', LOAD_ORIGIN); - n.parentNode.insertBefore(e, n); - } - loadChartbeat(); + loadNativeSdk(script); }); this._isReady(this).then((instance) => { @@ -141,16 +131,16 @@ class Chartbeat { if (this.isLoaded()) { this.failed = false; logger.debug('===chartbeat loaded successfully==='); - return resolve(instance); + resolve(instance); } if (time >= MAX_WAIT_FOR_INTEGRATION_LOAD) { this.failed = true; logger.debug('===chartbeat failed==='); - return resolve(instance); + resolve(instance); } - this.pause(INTEGRATION_LOAD_CHECK_INTERVAL).then(() => { - return this._isReady(instance, time + INTEGRATION_LOAD_CHECK_INTERVAL).then(resolve); - }); + this.pause(INTEGRATION_LOAD_CHECK_INTERVAL).then(() => + this._isReady(instance, time + INTEGRATION_LOAD_CHECK_INTERVAL).then(resolve), + ); }); } } diff --git a/src/integrations/Chartbeat/index.js b/src/integrations/Chartbeat/index.js index 2c3729805..c47907632 100644 --- a/src/integrations/Chartbeat/index.js +++ b/src/integrations/Chartbeat/index.js @@ -1,3 +1 @@ -import { Chartbeat } from './browser'; - -export { Chartbeat }; +export { Chartbeat } from './browser'; diff --git a/src/integrations/Chartbeat/nativeSdkLoader.js b/src/integrations/Chartbeat/nativeSdkLoader.js new file mode 100644 index 000000000..dd26e47dc --- /dev/null +++ b/src/integrations/Chartbeat/nativeSdkLoader.js @@ -0,0 +1,7 @@ +import ScriptLoader, { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(script) { + ScriptLoader('chatbeat', `//static.chartbeat.com/js/${script}`); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Clevertap/browser.js b/src/integrations/Clevertap/browser.js index 31b9e7b2a..5d76b1c23 100644 --- a/src/integrations/Clevertap/browser.js +++ b/src/integrations/Clevertap/browser.js @@ -99,7 +99,8 @@ class Clevertap { logger.debug('in clevertap identify'); const { message } = rudderElement; - if (!(message.context && message.context.traits)) { + const { context } = message; + if (!context?.traits) { logger.error('user traits not present'); return; } @@ -182,7 +183,7 @@ class Clevertap { logger.debug('in clevertap page'); const { name, properties } = rudderElement.message; let eventName; - if (properties && properties.category && name) { + if (properties?.category && name) { eventName = `WebPage Viewed ${name} ${properties.category}`; } else if (name) { eventName = `WebPage Viewed ${name}`; diff --git a/src/integrations/Clevertap/index.js b/src/integrations/Clevertap/index.js index 22ebf4632..a9a4eec67 100644 --- a/src/integrations/Clevertap/index.js +++ b/src/integrations/Clevertap/index.js @@ -1,3 +1 @@ -import Clevertap from './browser'; - -export { Clevertap }; +export { default as Clevertap } from './browser'; diff --git a/src/integrations/Comscore/browser.js b/src/integrations/Comscore/browser.js index f9daf4221..aa6b2e936 100644 --- a/src/integrations/Comscore/browser.js +++ b/src/integrations/Comscore/browser.js @@ -1,3 +1,5 @@ +/* eslint-disable compat/compat */ +/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { @@ -5,7 +7,7 @@ import { INTEGRATION_LOAD_CHECK_INTERVAL, } from '../../utils/constants'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class Comscore { constructor(config, analytics, destinationInfo) { @@ -31,12 +33,16 @@ class Comscore { logger.debug('===in init Comscore init==='); } - identify(rudderElement) { - logger.debug('in Comscore identify'); + isLoaded() { + logger.debug('in Comscore isLoaded'); + if (!this.isFirstPageCallMade) { + return true; + } + return !!window.COMSCORE; } - track(rudderElement) { - logger.debug('in Comscore track'); + isReady() { + return !!window.COMSCORE; } page(rudderElement) { @@ -56,9 +62,6 @@ class Comscore { this.replayEvents.push(['page', rudderElement]); return; } - const { properties } = rudderElement.message; - // window.COMSCORE.beacon({c1:"2", c2: ""}); - // this.comScoreParams = this.mapComscoreParams(properties); window.COMSCORE.beacon(this.comScoreParams); } } @@ -72,16 +75,7 @@ class Comscore { initAfterPage() { logger.debug('=====in initAfterPage====='); - (function () { - const s = document.createElement('script'); - const el = document.getElementsByTagName('script')[0]; - s.async = true; - s.setAttribute('data-loader', LOAD_ORIGIN); - s.src = `${ - document.location.protocol == 'https:' ? 'https://sb' : 'http://b' - }.scorecardresearch.com/beacon.js`; - el.parentNode.insertBefore(s, el); - })(); + loadNativeSdk(); this._isReady(this).then((instance) => { instance.replayEvents.forEach((event) => { @@ -100,15 +94,15 @@ class Comscore { return new Promise((resolve) => { if (this.isLoaded()) { this.failed = false; - return resolve(instance); + resolve(instance); } if (time >= MAX_WAIT_FOR_INTEGRATION_LOAD) { this.failed = true; - return resolve(instance); + resolve(instance); } - this.pause(INTEGRATION_LOAD_CHECK_INTERVAL).then(() => { - return this._isReady(instance, time + INTEGRATION_LOAD_CHECK_INTERVAL).then(resolve); - }); + this.pause(INTEGRATION_LOAD_CHECK_INTERVAL).then(() => + this._isReady(instance, time + INTEGRATION_LOAD_CHECK_INTERVAL).then(resolve), + ); }); } @@ -118,7 +112,7 @@ class Comscore { const comScoreParams = {}; - Object.keys(comScoreBeaconParamsMap).forEach(function (property) { + Object.keys(comScoreBeaconParamsMap).forEach((property) => { if (property in properties) { const key = comScoreBeaconParamsMap[property]; const value = properties[property]; @@ -128,24 +122,9 @@ class Comscore { comScoreParams.c1 = '2'; comScoreParams.c2 = this.c2ID; - /* if (this.options.comscorekw.length) { - comScoreParams.comscorekw = this.options.comscorekw; - } */ logger.debug('=====in mapComscoreParams=====', comScoreParams); return comScoreParams; } - - isLoaded() { - logger.debug('in Comscore isLoaded'); - if (!this.isFirstPageCallMade) { - return true; - } - return !!window.COMSCORE; - } - - isReady() { - return !!window.COMSCORE; - } } export { Comscore }; diff --git a/src/integrations/Comscore/index.js b/src/integrations/Comscore/index.js index b5ac6313e..18fe6652f 100644 --- a/src/integrations/Comscore/index.js +++ b/src/integrations/Comscore/index.js @@ -1,3 +1 @@ -import { Comscore } from './browser'; - -export { Comscore }; +export { Comscore } from './browser'; diff --git a/src/integrations/Comscore/nativeSdkLoader.js b/src/integrations/Comscore/nativeSdkLoader.js new file mode 100644 index 000000000..48b4cd07d --- /dev/null +++ b/src/integrations/Comscore/nativeSdkLoader.js @@ -0,0 +1,10 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + const src = `${ + document.location.protocol == 'https:' ? 'https://sb' : 'http://b' + }.scorecardresearch.com/beacon.js`; + ScriptLoader('comscore', src); +} + +export { loadNativeSdk }; diff --git a/src/integrations/ConvertFlow/browser.js b/src/integrations/ConvertFlow/browser.js index c84e78c98..91eb2091e 100644 --- a/src/integrations/ConvertFlow/browser.js +++ b/src/integrations/ConvertFlow/browser.js @@ -44,7 +44,6 @@ class ConvertFlow { return !!window.convertflow; } - // identify call to Convertflow identify(rudderElement) { logger.debug('===In convertflow Identify==='); const { message } = rudderElement; diff --git a/src/integrations/ConvertFlow/index.js b/src/integrations/ConvertFlow/index.js index b7e1494e9..56f53ca49 100644 --- a/src/integrations/ConvertFlow/index.js +++ b/src/integrations/ConvertFlow/index.js @@ -1,3 +1 @@ -import ConvertFlow from './browser'; - -export { ConvertFlow }; +export { default as ConvertFlow } from './browser'; diff --git a/src/integrations/ConvertFlow/utils.js b/src/integrations/ConvertFlow/utils.js index 1b3d467f7..e76200508 100644 --- a/src/integrations/ConvertFlow/utils.js +++ b/src/integrations/ConvertFlow/utils.js @@ -97,7 +97,7 @@ const trigger = (userDefinedEventsMapping, userDefinedEventsList, analytics) => ]; standardEventsList.forEach((events) => { if (userDefinedEventsList.includes(events)) { - window.addEventListener(events, function (event) { + window.addEventListener(events, (event) => { makeACall(standardEventsMap, event.type, event.detail, analytics); }); } diff --git a/src/integrations/Criteo/browser.js b/src/integrations/Criteo/browser.js index 6629db525..d65e94f41 100644 --- a/src/integrations/Criteo/browser.js +++ b/src/integrations/Criteo/browser.js @@ -1,13 +1,13 @@ -/* eslint-disable no-unused-expressions */ - +/* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import ScriptLoader from '../../utils/ScriptLoader'; import { - handleCommonFields, - generateExtraData, - handleProductView, - handlingEventDuo, + getDeviceType, handleListView, + handlingEventDuo, + handleProductView, + generateExtraData, + handleCommonFields, } from './utils'; import { NAME, supportedEvents } from './constants'; import { getHashFromArrayWithDuplicate } from '../../utils/commonUtils'; @@ -22,12 +22,7 @@ class Criteo { this.hashMethod = config.hashMethod; this.accountId = config.accountId; this.url = config.homePageUrl; - // eslint-disable-next-line no-nested-ternary - this.deviceType = /iPad/.test(navigator.userAgent) - ? 't' - : /Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Silk/.test(navigator.userAgent) - ? 'm' - : 'd'; + this.deviceType = getDeviceType(navigator.userAgent); this.fieldMapping = config.fieldMapping; this.eventsToStandard = config.eventsToStandard; this.OPERATOR_LIST = ['eq', 'gt', 'lt', 'ge', 'le', 'in']; @@ -51,13 +46,11 @@ class Criteo { window.criteo_q.push({ event: 'setSiteType', type: this.deviceType }); } - // eslint-disable-next-line class-methods-use-this isLoaded() { logger.debug('===in Criteo isLoaded==='); return !!(window.criteo_q && window.criteo_q.push !== Array.prototype.push); } - // eslint-disable-next-line class-methods-use-this isReady() { logger.debug('===in Criteo isReady==='); return !!(window.criteo_q && window.criteo_q.push !== Array.prototype.push); @@ -84,7 +77,7 @@ class Criteo { } const extraDataObject = generateExtraData(rudderElement, this.fieldMapping); - if (Object.keys(extraDataObject).length !== 0) { + if (Object.keys(extraDataObject).length > 0) { finalPayload.push({ event: 'setData', ...extraDataObject }); } @@ -153,7 +146,7 @@ class Criteo { }); const extraDataObject = generateExtraData(rudderElement, this.fieldMapping); - if (Object.keys(extraDataObject).length !== 0) { + if (Object.keys(extraDataObject).length > 0) { finalPayload.push({ event: 'setData', ...extraDataObject }); } window.criteo_q.push(finalPayload); diff --git a/src/integrations/Criteo/index.js b/src/integrations/Criteo/index.js index ccd4904d4..f0b0f1128 100644 --- a/src/integrations/Criteo/index.js +++ b/src/integrations/Criteo/index.js @@ -1,3 +1 @@ -import Criteo from './browser'; - -export { Criteo }; +export { default as Criteo } from './browser'; diff --git a/src/integrations/Criteo/utils.js b/src/integrations/Criteo/utils.js index 02c0e2d45..18427666d 100644 --- a/src/integrations/Criteo/utils.js +++ b/src/integrations/Criteo/utils.js @@ -2,19 +2,26 @@ import md5 from 'md5'; import { getHashFromArray, isDefinedAndNotNull } from '../../utils/commonUtils'; import logger from '../../utils/logUtil'; +/** + * Ref : https://help.criteo.com/kb/guide/en/all-criteo-onetag-events-and-parameters-vZbzbEeY86/Steps/775825,868657,868659 + * Ref : https://help.criteo.com/kb/guide/en/all-criteo-onetag-events-and-parameters-vZbzbEeY86/Steps/775825 + * @param {*} rudderElement + * @param {*} hashMethod + * @returns + */ const handleCommonFields = (rudderElement, hashMethod) => { const { message } = rudderElement; - const { properties } = message; + const { properties, userId, anonymousId } = message; const setEmail = {}; const setZipcode = {}; const finalRequest = [ - { event: 'setCustomerId', id: md5(message.userId) }, - { event: 'setRetailerVisitorId', id: md5(message.anonymousId) }, + { event: 'setCustomerId', id: md5(userId).toString() }, + { event: 'setRetailerVisitorId', id: md5(anonymousId).toString() }, ]; - if (properties && properties.email) { + if (properties?.email) { const email = properties.email.trim().toLowerCase(); setEmail.event = 'setEmail'; setEmail.hash_method = hashMethod; @@ -22,7 +29,7 @@ const handleCommonFields = (rudderElement, hashMethod) => { finalRequest.push(setEmail); } - if (properties && properties.zipCode) { + if (properties?.zipCode) { setZipcode.event = 'setZipcode'; setZipcode.zipCode = properties.zipCode || properties.zip; finalRequest.push(setZipcode); @@ -87,46 +94,114 @@ const handleProductView = (message, finalPayload) => { // ); }; -const handlingEventDuo = (message, finalPayload) => { - const { event, properties } = message; - const eventType = event.toLowerCase().trim(); +/** + * Validates product properties + * @param {*} product + * @param {*} index + * @returns + */ +const validateProduct = (product, index) => { + if (product.product_id && product.price && product.quantity) { + const elementaryProduct = { + id: String(product.product_id), + price: parseFloat(product.price), + quantity: parseInt(product.quantity, 10), + }; + return !Number.isNaN(elementaryProduct.price) && !Number.isNaN(elementaryProduct.quantity); + } + logger.debug(`[Criteo] product at index ${index} is skipped for insufficient information`); + return false; +}; + +/** + * Returns transformed products array + * @param {*} properties + * @returns + */ +const getProductInfo = (properties) => { const productInfo = []; - let elementaryProduct; - if (properties && properties.products && properties.products.length > 0) { + + if (properties?.products && properties.products.length > 0) { properties.products.forEach((product, index) => { - if (product.product_id && product.price && product.quantity) { - elementaryProduct = { + if (validateProduct(product, index)) { + productInfo.push({ id: String(product.product_id), price: parseFloat(product.price), quantity: parseInt(product.quantity, 10), - }; - if ( - !Number.isNaN(parseFloat(elementaryProduct.price)) && - !Number.isNaN(parseInt(elementaryProduct.quantity, 10)) - ) { - // all the above fields are mandatory - productInfo.push(elementaryProduct); - } - } else { - logger.debug(`[Criteo] product at index ${index} is skipped for insufficient information`); + }); } }); - if (productInfo.length === 0) { - logger.debug( - '[Criteo] None of the products had sufficient information or information is wrongly formatted', - ); - return; - } } else { logger.debug('[Criteo] Payload should consist of at least one product information'); + } + + return productInfo; +}; + +/** + * Adds order completed event to finalPayload + * @param {*} properties + * @param {*} finalPayload + * @param {*} productInfo + * @returns + */ +const processCompletedOrderEvent = (properties, finalPayload, productInfo) => { + const trackTransactionObject = { + event: 'trackTransaction', + id: String(properties.order_id), + item: productInfo, + }; + + if (!trackTransactionObject.id) { + logger.debug('[Criteo] order_id (Transaction Id) is a mandatory field'); + return; + } + + if (properties.new_customer === 1 || properties.new_customer === 0) { + trackTransactionObject.new_customer = properties.new_customer; + } + + if (properties.deduplication === 1 || properties.deduplication === 0) { + trackTransactionObject.deduplication = properties.deduplication; + } + + finalPayload.push(trackTransactionObject); +}; + +/** + * Adds view cart event to finalPayload + * @param {*} properties + * @param {*} finalPayload + * @param {*} productInfo + */ +const processViewedCartEvent = (finalPayload, productInfo) => { + const viewBasketObject = { + event: 'viewBasket', + item: productInfo, + }; + finalPayload.push(viewBasketObject); +}; + +/** + * Handles events + * @param {*} message + * @param {*} finalPayload + * @returns + */ +const handlingEventDuo = (message, finalPayload) => { + const { event, properties } = message; + const eventType = event.toLowerCase().trim(); + const productInfo = getProductInfo(properties); + + if (productInfo.length === 0) { + logger.debug( + '[Criteo] None of the products had sufficient information or information is wrongly formatted', + ); return; } + if (eventType === 'cart viewed') { - const viewBasketObject = { - event: 'viewBasket', - item: productInfo, - }; - finalPayload.push(viewBasketObject); + processViewedCartEvent(finalPayload, productInfo); // final example payload supported by the destination // window.criteo_q.push( // { event: "setAccount", account: YOUR_PARTNER_ID}, @@ -154,23 +229,7 @@ const handlingEventDuo = (message, finalPayload) => { } if (eventType === 'order completed') { - const trackTransactionObject = { - event: 'trackTransaction', - id: String(properties.order_id), - item: productInfo, - }; - if (!trackTransactionObject.id) { - logger.debug('[Criteo] order_id (Transaction Id) is a mandatory field'); - return; - } - if (properties.new_customer === 1 || properties.new_customer === 0) { - trackTransactionObject.new_customer = properties.new_customer; - } - if (properties.deduplication === 1 || properties.deduplication === 0) { - trackTransactionObject.deduplication = properties.deduplication; - } - finalPayload.push(trackTransactionObject); - + processCompletedOrderEvent(properties, finalPayload, productInfo); // final example payload supported by destination // window.criteo_q.push( // { event: "setAccount", account: YOUR_PARTNER_ID}, @@ -193,12 +252,37 @@ const handlingEventDuo = (message, finalPayload) => { } }; +/** + * Returns filterArray + * @param {*} properties + * @param {*} OPERATOR_LIST + * @returns + */ +const getFilterArray = (properties, OPERATOR_LIST) => { + const filterArray = []; + const FILTER_FIELDS = ['name', 'value']; + if (properties.filters) { + properties.filters.forEach((filter) => { + const filterObject = {}; + Object.keys(filter).forEach((key) => { + if ( + FILTER_FIELDS.includes(key) || + (key === 'operator' && OPERATOR_LIST.includes(filter.operator)) + ) { + filterObject[key] = filter[key]; + } + }); + filterArray.push(filterObject); + }); + } + + return filterArray; +}; + const handleListView = (message, finalPayload, OPERATOR_LIST) => { const { properties } = message; const productIdList = []; - const filterArray = []; const viewListObj = {}; - const FILTER_FIELDS = ['name', 'value']; if (properties.products && properties.products.length > 0) { properties.products.forEach((product) => { @@ -223,30 +307,37 @@ const handleListView = (message, finalPayload, OPERATOR_LIST) => { viewListObj.page_number = parseInt(properties.page_number, 10); } - if (properties.filters) { - properties.filters.forEach((filter) => { - const filterObject = {}; - Object.keys(filter).forEach((key) => { - if ( - FILTER_FIELDS.includes(key) || - (key === 'operator' && OPERATOR_LIST.includes(filter.operator)) - ) { - filterObject[key] = filter[key]; - } - }); - filterArray.push(filterObject); - }); - } + const filterArray = getFilterArray(properties, OPERATOR_LIST); if (filterArray.length > 0) { viewListObj.filters = filterArray; } finalPayload.push(viewListObj); }; +/** + * Returns device type + * @param {*} userAgent + * @returns + */ +const getDeviceType = (userAgent) => { + let deviceType; + + if (/iPad/.test(userAgent)) { + deviceType = 't'; + } else if (/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Silk/.test(userAgent)) { + deviceType = 'm'; + } else { + deviceType = 'd'; + } + + return deviceType; +}; + export { - handleCommonFields, - generateExtraData, - handleProductView, - handlingEventDuo, + getDeviceType, handleListView, + handlingEventDuo, + handleProductView, + generateExtraData, + handleCommonFields, }; diff --git a/src/integrations/CustomerIO/browser.js b/src/integrations/CustomerIO/browser.js index 5fa10f0e5..d932a9b73 100644 --- a/src/integrations/CustomerIO/browser.js +++ b/src/integrations/CustomerIO/browser.js @@ -1,9 +1,8 @@ -/* eslint-disable func-names */ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class CustomerIO { constructor(config, analytics, destinationInfo) { @@ -24,34 +23,8 @@ class CustomerIO { init() { logger.debug('===in init Customer IO init==='); - window._cio = window._cio || []; const { siteID } = this; - (function () { - let a; - let b; - let c; - // eslint-disable-next-line prefer-const - a = function (f) { - return function () { - // eslint-disable-next-line prefer-rest-params - window._cio.push([f].concat(Array.prototype.slice.call(arguments, 0))); - }; - }; - // eslint-disable-next-line prefer-const - b = ['load', 'identify', 'sidentify', 'track', 'page']; - // eslint-disable-next-line no-plusplus - for (c = 0; c < b.length; c++) { - window._cio[b[c]] = a(b[c]); - } - const t = document.createElement('script'); - const s = document.getElementsByTagName('script')[0]; - t.async = true; - t.setAttribute('data-loader', LOAD_ORIGIN); - t.id = 'cio-tracker'; - t.setAttribute('data-site-id', siteID); - t.src = 'https://assets.customer.io/assets/track.js'; - s.parentNode.insertBefore(t, s); - })(); + loadNativeSdk(siteID); } identify(rudderElement) { diff --git a/src/integrations/CustomerIO/index.js b/src/integrations/CustomerIO/index.js index f11e8d892..08aafefda 100644 --- a/src/integrations/CustomerIO/index.js +++ b/src/integrations/CustomerIO/index.js @@ -1,3 +1 @@ -import { CustomerIO } from './browser'; - -export { CustomerIO }; +export { CustomerIO } from './browser'; diff --git a/src/integrations/CustomerIO/nativeSdkLoader.js b/src/integrations/CustomerIO/nativeSdkLoader.js new file mode 100644 index 000000000..c5b64d3ac --- /dev/null +++ b/src/integrations/CustomerIO/nativeSdkLoader.js @@ -0,0 +1,28 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(siteID) { + window._cio = window._cio || []; + (function () { + let a; + let b; + let c; + a = function (f) { + return function () { + window._cio.push([f].concat(Array.prototype.slice.call(arguments, 0))); + }; + }; + b = ['load', 'identify', 'sidentify', 'track', 'page']; + for (c = 0; c < b.length; c++) { + window._cio[b[c]] = a(b[c]); + } + const t = document.createElement('script'); + const s = document.getElementsByTagName('script')[0]; + t.async = true; + t.setAttribute('data-loader', LOAD_ORIGIN); + t.id = 'cio-tracker'; + t.setAttribute('data-site-id', siteID); + t.src = 'https://assets.customer.io/assets/track.js'; + s.parentNode.insertBefore(t, s); + })(); +} +export { loadNativeSdk }; diff --git a/src/integrations/DCMFloodlight/browser.js b/src/integrations/DCMFloodlight/browser.js index 7e91cffcb..10fe60715 100644 --- a/src/integrations/DCMFloodlight/browser.js +++ b/src/integrations/DCMFloodlight/browser.js @@ -48,6 +48,7 @@ class DCMFloodlight { window.dataLayer = window.dataLayer || []; window.gtag = function gtag() { + // eslint-disable-next-line prefer-rest-params window.dataLayer.push(arguments); }; @@ -109,7 +110,7 @@ class DCMFloodlight { logger.debug('===In DCMFloodlight track==='); const { message } = rudderElement; - const { event } = rudderElement.message; + const { event } = message; let customFloodlightVariable; if (!event) { @@ -128,10 +129,7 @@ class DCMFloodlight { // find conversion event // knowing cat (activityTag), type (groupTag), (counter or sales), customVariable from config const conversionEvent = this.conversionEvents.find( - (cnEvent) => - cnEvent && - cnEvent.eventName && - cnEvent.eventName.trim().toLowerCase() === event.toLowerCase(), + (cnEvent) => cnEvent?.eventName?.trim().toLowerCase() === event.toLowerCase(), ); if (!conversionEvent) { @@ -144,7 +142,7 @@ class DCMFloodlight { this.groupTag = conversionEvent.floodlightGroupTag.trim(); } - const { salesTag } = conversionEvent; + const { salesTag, customVariables } = conversionEvent; if (!isValidCountingMethod(salesTag, countingMethod)) { logger.error( @@ -153,7 +151,7 @@ class DCMFloodlight { return; } - customFloodlightVariable = conversionEvent.customVariables || []; + customFloodlightVariable = customVariables || []; customFloodlightVariable = transformCustomVariable(customFloodlightVariable, message); customFloodlightVariable = removeUndefinedAndNullValues(customFloodlightVariable); diff --git a/src/integrations/DCMFloodlight/index.js b/src/integrations/DCMFloodlight/index.js index f549a1533..d27597053 100644 --- a/src/integrations/DCMFloodlight/index.js +++ b/src/integrations/DCMFloodlight/index.js @@ -1,3 +1 @@ -import DCMFloodlight from './browser'; - -export { DCMFloodlight }; +export { default as DCMFloodlight } from './browser'; diff --git a/src/integrations/DCMFloodlight/utils.js b/src/integrations/DCMFloodlight/utils.js index 2038e9195..76a1c04a7 100644 --- a/src/integrations/DCMFloodlight/utils.js +++ b/src/integrations/DCMFloodlight/utils.js @@ -1,15 +1,15 @@ -/* eslint-disable no-lonely-if */ -/* eslint-disable consistent-return */ import get from 'get-value'; import { GENERIC_FALSE_VALUES, GENERIC_TRUE_VALUES } from '../../utils/constants'; import logger from '../../utils/logUtil'; import { - isDefinedAndNotNull, isNotEmpty, + isDefinedAndNotNull, isDefinedNotNullNotEmpty, removeUndefinedAndNullValues, } from '../../utils/commonUtils'; +const matchIdKey = 'properties.matchId'; + /** * transform webapp dynamicForm custom floodlight variable * [ @@ -156,7 +156,7 @@ const buildCustomParamsUsingIntegrationsObject = (message, integrationObj) => { } } - const matchId = get(message, 'properties.matchId'); + const matchId = get(message, matchIdKey); if (matchId) { customParams.match_id = matchId; } @@ -164,6 +164,24 @@ const buildCustomParamsUsingIntegrationsObject = (message, integrationObj) => { return customParams; }; +/** + * Generate a cryptographically secure random number between 0 and 9999999999999 + * @returns + */ +const getRandomNumber = () => Math.random() * 10000000000000; + +/** + * Returns quantity parameter + * @param {*} message + * @returns + */ +const getQuantity = (message) => { + const qty = get(message, 'properties.quantity'); + const products = get(message, 'properties.products'); + const quantities = calculateQuantity(products); + return quantities || qty; +}; + const buildGtagTrackPayload = (message, salesTag, countingMethod, integrationObj) => { // Ref - https://support.google.com/campaignmanager/answer/7554821?hl=en#zippy=%2Ccustom-fields // we can pass custom variables to DCM and any values passed in it will override its default value @@ -183,17 +201,9 @@ const buildGtagTrackPayload = (message, salesTag, countingMethod, integrationObj // Ref - https://support.google.com/campaignmanager/answer/7554821#zippy=%2Cfields-in-event-snippets-for-sales-tags // possible values for counting method :- transactions, items_sold - if (countingMethod === 'items_sold') { - let qty = get(message, 'properties.quantity'); - // sums quantity from products array or fallback to properties.quantity - const products = get(message, 'properties.products'); - const quantities = calculateQuantity(products); - if (quantities) { - qty = quantities; - } - if (qty) { - eventSnippetPayload.quantity = parseFloat(qty); - } + const qty = getQuantity(message); + if (countingMethod === 'items_sold' && qty) { + eventSnippetPayload.quantity = parseFloat(qty); } } else { // counter tag @@ -220,7 +230,7 @@ const buildGtagTrackPayload = (message, salesTag, countingMethod, integrationObj }; const dcLat = get(message, 'context.device.adTrackingEnabled'); - const matchId = get(message, 'properties.matchId'); + const matchId = get(message, matchIdKey); if (isDefinedNotNullNotEmpty(dcLat)) { dcCustomParams.dc_lat = mapFlagValue('dc_lat', dcLat); @@ -236,60 +246,75 @@ const buildGtagTrackPayload = (message, salesTag, countingMethod, integrationObj return eventSnippetPayload; }; -const buildIframeTrackPayload = (message, salesTag, countingMethod, integrationObj) => { - const randomNum = Math.random() * 10000000000000; - // Ref - https://support.google.com/campaignmanager/answer/2823450?hl=en#zippy=%2Cother-parameters-for-iframe-and-image-tags:~:text=Other%20parameters%20for%20iFrame%20and%20image%20tags - // we can pass custom params to DCM. - // Total 9 params - ord, num, cost, qty, dc_lat, tag_for_child_directed_treatment, tfua, npa, match_id - let customParams = {}; +/** + * Returns customParams for sales tag + * @param {*} message + * @param {*} countingMethod + * @returns + */ +const buildCustomParamsForSalesTag = (message, countingMethod) => { + const customParams = { + ord: get(message, 'properties.orderId') || get(message, 'properties.order_id'), + cost: get(message, 'properties.revenue'), + }; - if (salesTag) { - // sales tag - customParams.ord = get(message, 'properties.orderId') || get(message, 'properties.order_id'); - customParams.cost = get(message, 'properties.revenue'); - - // Ref - https://support.google.com/campaignmanager/answer/2823450?hl=en#zippy=%2Chow-the-ord-parameter-is-displayed-for-each-counter-type%2Csales-activity-tags - if (countingMethod === 'transactions') { - customParams.qty = 1; - } else { - // counting method is item_sold - let qty = get(message, 'properties.quantity'); - // sums quantity from products array or fallback to properties.quantity - const products = get(message, 'properties.products'); - const quantities = calculateQuantity(products); - if (quantities) { - qty = quantities; - } - if (qty) { - customParams.qty = parseFloat(qty); - } - } + // Ref - https://support.google.com/campaignmanager/answer/2823450?hl=en#zippy=%2Chow-the-ord-parameter-is-displayed-for-each-counter-type%2Csales-activity-tags + if (countingMethod === 'transactions') { + customParams.qty = 1; } else { - // counter tag - // Ref - https://support.google.com/campaignmanager/answer/2823450?hl=en#zippy=%2Ccounter-activity-tags%2Chow-the-ord-parameter-is-displayed-for-each-counter-type - switch (countingMethod) { - case 'standard': - customParams.ord = get(message, 'properties.ord') || randomNum; - break; - case 'unique': - customParams.ord = 1; - customParams.num = get(message, 'properties.num') || randomNum; - break; - case 'per_session': - customParams.ord = get(message, 'properties.sessionId'); - break; - default: - break; + const qty = getQuantity(message); + if (qty) { + customParams.qty = parseFloat(qty); } } + return customParams; +}; + +/** + * Returns customParams for counter tag + * @param {*} message + * @param {*} countingMethod + * @returns + */ +const buildCustomParamsForCounterTag = (message, countingMethod) => { + const customParams = {}; + + const randomNumber = getRandomNumber(); + // Ref - https://support.google.com/campaignmanager/answer/2823450?hl=en#zippy=%2Ccounter-activity-tags%2Chow-the-ord-parameter-is-displayed-for-each-counter-type + switch (countingMethod) { + case 'standard': + customParams.ord = get(message, 'properties.ord') || randomNumber; + break; + case 'unique': + customParams.ord = 1; + customParams.num = get(message, 'properties.num') || randomNumber; + break; + case 'per_session': + customParams.ord = get(message, 'properties.sessionId'); + break; + default: + break; + } + + return customParams; +}; + +const buildIframeTrackPayload = (message, salesTag, countingMethod, integrationObj) => { + // Ref - https://support.google.com/campaignmanager/answer/2823450?hl=en#zippy=%2Cother-parameters-for-iframe-and-image-tags:~:text=Other%20parameters%20for%20iFrame%20and%20image%20tags + // we can pass custom params to DCM. + // Total 9 params - ord, num, cost, qty, dc_lat, tag_for_child_directed_treatment, tfua, npa, match_id + let customParams = salesTag + ? buildCustomParamsForSalesTag(message, countingMethod) + : buildCustomParamsForCounterTag(message, countingMethod); + customParams = { ...customParams, ...buildCustomParamsUsingIntegrationsObject(message, integrationObj), }; const dcLat = get(message, 'context.device.adTrackingEnabled'); - const matchId = get(message, 'properties.matchId'); + const matchId = get(message, matchIdKey); if (isDefinedNotNullNotEmpty(dcLat)) { customParams.dc_lat = mapFlagValue('dc_lat', dcLat); @@ -302,10 +327,10 @@ const buildIframeTrackPayload = (message, salesTag, countingMethod, integrationO }; export { - transformCustomVariable, - flattenPayload, mapFlagValue, + flattenPayload, + isValidCountingMethod, buildGtagTrackPayload, buildIframeTrackPayload, - isValidCountingMethod, + transformCustomVariable, }; diff --git a/src/integrations/Drip/browser.js b/src/integrations/Drip/browser.js index 7af9b98e8..230db75da 100644 --- a/src/integrations/Drip/browser.js +++ b/src/integrations/Drip/browser.js @@ -7,7 +7,7 @@ import { getDestinationExternalID } from './utils'; import { extractCustomFields } from '../../utils/utils'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class Drip { constructor(config, analytics, destinationInfo) { @@ -41,19 +41,7 @@ class Drip { init() { logger.debug('===In init Drip==='); - window._dcq = window._dcq || []; - window._dcs = window._dcs || {}; - window._dcs.account = this.accountId; - - (function () { - const dc = document.createElement('script'); - dc.type = 'text/javascript'; - dc.setAttribute('data-loader', LOAD_ORIGIN); - dc.async = true; - dc.src = `//tag.getdrip.com/${window._dcs.account}.js`; - const s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(dc, s); - })(); + loadNativeSdk(this.accountId); } isLoaded() { @@ -70,7 +58,8 @@ class Drip { logger.debug('===In Drip identify==='); const { message } = rudderElement; - if (!message.context || !message.context.traits) { + const { context } = message; + if (!context?.traits) { logger.error('user context or traits not present'); return; } @@ -142,7 +131,7 @@ class Drip { logger.debug('===In Drip track==='); const { message } = rudderElement; - const { event } = rudderElement.message; + const { event } = message; if (!event) { logger.error('Event name not present'); diff --git a/src/integrations/Drip/index.js b/src/integrations/Drip/index.js index 991ef8b0e..222d53e9b 100644 --- a/src/integrations/Drip/index.js +++ b/src/integrations/Drip/index.js @@ -1,3 +1 @@ -import Drip from './browser'; - -export { Drip }; +export { default as Drip } from './browser'; diff --git a/src/integrations/Drip/nativeSdkLoader.js b/src/integrations/Drip/nativeSdkLoader.js new file mode 100644 index 000000000..5053ad26f --- /dev/null +++ b/src/integrations/Drip/nativeSdkLoader.js @@ -0,0 +1,10 @@ +import ScriptLoader, { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(accountId) { + window._dcq = window._dcq || []; + window._dcs = window._dcs || {}; + window._dcs.account = accountId; + ScriptLoader('drip', `//tag.getdrip.com/${window._dcs.account}.js`); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Drip/utils.js b/src/integrations/Drip/utils.js index c488aedde..6de1536a3 100644 --- a/src/integrations/Drip/utils.js +++ b/src/integrations/Drip/utils.js @@ -13,7 +13,7 @@ function getDestinationExternalID(message, type) { let externalIdArray = null; let destinationExternalId = null; - if (message.context && message.context.externalId) { + if (message?.context?.externalId) { externalIdArray = message.context.externalId; } if (externalIdArray) { diff --git a/src/integrations/Engage/browser.js b/src/integrations/Engage/browser.js index 89e894d79..de719de7f 100644 --- a/src/integrations/Engage/browser.js +++ b/src/integrations/Engage/browser.js @@ -1,10 +1,10 @@ -/* eslint-disable*/ +/* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; -import { refinePayload, getDestinationExternalID } from './utils.js'; +import { refinePayload, getDestinationExternalID } from './utils'; import { getDefinedTraits } from '../../utils/utils'; import { removeUndefinedAndNullValues } from '../../utils/commonUtils'; +import { loadNativeSdk } from './nativeSdkLoader'; class Engage { constructor(config, analytics, destinationInfo) { @@ -26,25 +26,7 @@ class Engage { init() { logger.debug('===In init Engage==='); - !(function (n) { - if (!window.Engage) { - (window[n] = window[n] || {}), - (window[n].queue = window[n].queue || []), - (window.Engage = window.Engage || {}); - for (var e = ['init', 'identify', 'addAttribute', 'track'], i = 0; i < e.length; i++) - window.Engage[e[i]] = w(e[i]); - var d = document.createElement('script'); - (d.src = '//d2969mkc0xw38n.cloudfront.net/next/engage.min.js'), - d.setAttribute('data-loader', LOAD_ORIGIN), - (d.async = !0), - document.head.appendChild(d); - } - function w(e) { - return function () { - window[n].queue.push([e].concat([].slice.call(arguments))); - }; - } - })('engage'); + loadNativeSdk(); window.Engage.init({ key: this.api_key, @@ -73,10 +55,10 @@ class Engage { logger.error('externalId or userId is required for Identify call.'); return; } - let { originalTimestamp } = message; + const { originalTimestamp, context } = message; - const { traits } = message.context; - let payload = refinePayload(traits, (this.identifyFlag = true)); + const { traits } = context; + let payload = refinePayload(traits, true); payload.number = phone; payload.last_name = lastName; @@ -107,7 +89,7 @@ class Engage { let payload = refinePayload(properties); payload = removeUndefinedAndNullValues(payload); window.Engage.track(engageId, { - event: event, + event, timestamp: originalTimestamp, properties: payload, }); diff --git a/src/integrations/Engage/index.js b/src/integrations/Engage/index.js index 98087e690..e3ed9a831 100644 --- a/src/integrations/Engage/index.js +++ b/src/integrations/Engage/index.js @@ -1,3 +1 @@ -import Engage from './browser'; - -export { Engage }; +export { default as Engage } from './browser'; diff --git a/src/integrations/Engage/nativeSdkLoader.js b/src/integrations/Engage/nativeSdkLoader.js new file mode 100644 index 000000000..ddb8436f0 --- /dev/null +++ b/src/integrations/Engage/nativeSdkLoader.js @@ -0,0 +1,25 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + !(function (n) { + if (!window.Engage) { + (window[n] = window[n] || {}), + (window[n].queue = window[n].queue || []), + (window.Engage = window.Engage || {}); + for (var e = ['init', 'identify', 'addAttribute', 'track'], i = 0; i < e.length; i++) + window.Engage[e[i]] = w(e[i]); + var d = document.createElement('script'); + (d.src = '//d2969mkc0xw38n.cloudfront.net/next/engage.min.js'), + d.setAttribute('data-loader', LOAD_ORIGIN), + (d.async = !0), + document.head.appendChild(d); + } + function w(e) { + return function () { + window[n].queue.push([e].concat([].slice.call(arguments))); + }; + } + })('engage'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Engage/utils.js b/src/integrations/Engage/utils.js index 8acd27673..092cd11fb 100644 --- a/src/integrations/Engage/utils.js +++ b/src/integrations/Engage/utils.js @@ -15,7 +15,7 @@ import { identifyExcludeFields } from './constants'; function getDestinationExternalID(message, type) { let externalIdArray = null; let destinationExternalId = null; - if (message.context && message.context.externalId) { + if (message?.context?.externalId) { externalIdArray = message.context.externalId; } if (externalIdArray) { @@ -66,5 +66,4 @@ const refinePayload = (attributes, identifyFlag = false) => { return payload; }; -// eslint-disable-next-line import/prefer-default-export export { getDestinationExternalID, refinePayload }; diff --git a/src/integrations/FacebookPixel/browser.js b/src/integrations/FacebookPixel/browser.js index 6307fd54a..c1f12edf3 100644 --- a/src/integrations/FacebookPixel/browser.js +++ b/src/integrations/FacebookPixel/browser.js @@ -1,15 +1,26 @@ +/* eslint-disable func-names */ +/* eslint-disable prefer-rest-params */ +/* eslint-disable prefer-spread */ +/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import each from '@ndhoule/each'; import sha256 from 'crypto-js/sha256'; -import ScriptLoader from '../../utils/ScriptLoader'; import logger from '../../utils/logUtil'; +import ScriptLoader from '../../utils/ScriptLoader'; import { + merge, getEventId, - getContentCategory, buildPayLoad, - getHashedStatus + eventHelpers, + formatRevenue, + getContentType, + getHashedStatus, + getContentCategory, + getProductContentAndId, + getProductListViewedEventParams, + getProductsContentsAndContentIds, } from './utils'; -import { getHashFromArray, isDefined } from '../../utils/commonUtils'; +import { getHashFromArray } from '../../utils/commonUtils'; import { NAME, traitsMapper, reserveTraits } from './constants'; import { constructPayload } from '../../utils/utils'; @@ -19,14 +30,14 @@ class FacebookPixel { logger.setLogLevel(analytics.logLevel); } this.blacklistPiiProperties = config.blacklistPiiProperties; - this.categoryToContent = config.categoryToContent; + this.categoryToContent = config.categoryToContent || []; this.pixelId = config.pixelId; this.eventsToEvents = config.eventsToEvents; this.valueFieldIdentifier = config.valueFieldIdentifier; this.advancedMapping = config.advancedMapping; this.traitKeyToExternalId = config.traitKeyToExternalId; - this.legacyConversionPixelId = config.legacyConversionPixelId; - this.userIdAsPixelId = config.userIdAsPixelId; + this.legacyConversionPixelId = config.legacyConversionPixelId || []; + this.userIdAsPixelId = config.userIdAsPixelId || []; this.whitelistPiiProperties = config.whitelistPiiProperties; this.useUpdatedMapping = config.useUpdatedMapping; this.name = NAME; @@ -38,19 +49,7 @@ class FacebookPixel { } = destinationInfo ?? {}); } - // START-NO-SONAR-SCAN - /* eslint-disable */ init() { - if (this.categoryToContent === undefined) { - this.categoryToContent = []; - } - if (this.legacyConversionPixelId === undefined) { - this.legacyConversionPixelId = []; - } - if (this.userIdAsPixelId === undefined) { - this.userIdAsPixelId = []; - } - logger.debug('===in init FbPixel==='); window._fbq = function () { @@ -98,15 +97,6 @@ class FacebookPixel { } ScriptLoader('fbpixel-integration', 'https://connect.facebook.net/en_US/fbevents.js'); } - /* eslint-enable */ - // END-NO-SONAR-SCAN - /* eslint-disable sonarjs/no-redundant-jump */ - /* eslint-disable no-useless-return */ - /* eslint-disable no-continue */ - /* eslint-disable sonarjs/cognitive-complexity */ - /* eslint-disable no-prototype-builtins */ - /* eslint-disable no-restricted-syntax */ - /* eslint-disable class-methods-use-this */ isLoaded() { logger.debug('in FBPixel isLoaded'); @@ -118,12 +108,6 @@ class FacebookPixel { return !!(window.fbq && window.fbq.callMethod); } - // eslint-disable-next-line no-unused-vars - identify(rudderElement) { - logger.error('Identify is deprecated for Facebook Pixel'); - return; - } - page(rudderElement) { const { properties } = rudderElement.message; window.fbq('track', 'PageView', properties, { @@ -131,248 +115,99 @@ class FacebookPixel { }); } - // disable sonarjs/cognitive-complexity for this function - // as it is a complex function and needs to be refactored later track(rudderElement) { const self = this; - const { event, properties } = rudderElement.message; - let revValue; - let currVal; - if (properties) { - const { revenue, currency } = properties; - revValue = this.formatRevenue(revenue); - if (!isDefined(revValue)) { - logger.error("'properties.revenue' could not be converted to a number"); - } - currVal = currency || 'USD'; - } - const payload = buildPayLoad(rudderElement, this.whitelistPiiProperties, this.blacklistPiiProperties, getHashedStatus(rudderElement.message, this.name)); - - if (this.categoryToContent === undefined) { - this.categoryToContent = []; - } - if (this.legacyConversionPixelId === undefined) { - this.legacyConversionPixelId = []; - } - if (this.userIdAsPixelId === undefined) { - this.userIdAsPixelId = []; - } + const { event } = rudderElement.message; + const properties = eventHelpers.getProperties(rudderElement.message); + const { + id, + sku, + name, + price, + query, + revenue, + products, + quantity, + contentName, + product_id: productId, + product_name: productName, + } = properties; + let { value, category, currency } = properties; + + const revValue = formatRevenue(revenue); + eventHelpers.validateRevenue(revValue); + currency = eventHelpers.getCurrency(currency); + const payload = buildPayLoad( + rudderElement, + this.whitelistPiiProperties, + this.blacklistPiiProperties, + getHashedStatus(rudderElement.message, this.name), + ); const standard = this.eventsToEvents; const legacy = this.legacyConversionPixelId; const standardTo = getHashFromArray(standard); const legacyTo = getHashFromArray(legacy); const useValue = this.valueFieldIdentifier === 'properties.value'; - let products; - let quantity; - let category; - let prodId; - let prodName; - let value; - let price; - let query; - let contentName; - if (properties) { - products = properties.products; - quantity = properties.quantity; - category = properties.category; - prodId = properties.product_id || properties.sku || properties.id; - prodName = properties.product_name || properties.name; - value = revValue || this.formatRevenue(properties.value); - price = properties.price; - query = properties.query; - contentName = properties.contentName; - } - + const prodId = eventHelpers.getProdId(productId, sku, id); + value = eventHelpers.getFormattedRevenue(revValue, value); // check for category data type if (category && !getContentCategory(category)) { return; } category = getContentCategory(category); - const customProperties = buildPayLoad(rudderElement, this.whitelistPiiProperties, this.blacklistPiiProperties, getHashedStatus(rudderElement.message, this.name));; const derivedEventID = getEventId(rudderElement.message); - if (event === 'Product List Viewed') { - let contentType; - const contentIds = []; - const contents = []; - if (Array.isArray(products)) { - products.forEach((product) => { - if (product) { - const productId = product.product_id || product.sku || product.id; - if (isDefined(productId)) { - contentIds.push(productId); - contents.push({ - id: productId, - quantity: product.quantity || quantity || 1, - item_price: product.price, - }); - } - } - }); - } + if (event === 'Product List Viewed') { + const { contentIds, contentType, contents } = getProductListViewedEventParams(properties); - if (contentIds.length > 0) { - contentType = 'product'; - } else if (category) { - contentIds.push(category); - contents.push({ - id: category, - quantity: 1, - }); - contentType = 'product_group'; - } + const productInfo = { + content_ids: contentIds, + content_type: getContentType(rudderElement, contentType, this.categoryToContent), + contents, + content_category: eventHelpers.getCategory(category), + content_name: contentName, + value, + currency, + }; - window.fbq( - 'trackSingle', - self.pixelId, - 'ViewContent', - this.merge( - { - content_ids: contentIds, - content_type: this.getContentType(rudderElement, contentType), - contents, - content_category: category || '', - content_name: contentName, - value, - currency: currVal, - }, - customProperties, - ), - { - eventID: derivedEventID, - }, - ); - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value: revValue, - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); - } else if (event === 'Product Viewed') { - window.fbq( - 'trackSingle', + this.makeTrackSignalCall( self.pixelId, 'ViewContent', - this.merge( - { - content_ids: [prodId], - content_type: this.getContentType(rudderElement, 'product'), - content_name: prodName || '', - content_category: category || '', - currency: currVal, - value: useValue ? value : this.formatRevenue(price), - contents: [ - { - id: prodId, - quantity, - item_price: price, - }, - ], - }, - customProperties, - ), - { - eventID: derivedEventID, - }, + merge(productInfo, payload), + derivedEventID, ); + this.makeTrackSignalCalls(self.pixelId, event, legacyTo, derivedEventID, { + currency, + value: revValue, + }); + } else if (event === 'Product Viewed' || event === 'Product Added') { + const { contents, contentIds } = getProductContentAndId(prodId, quantity, price); - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value: useValue ? value : this.formatRevenue(price), - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); - } else if (event === 'Product Added') { - const contentIds = []; - const contents = []; - - if (prodId) { - contentIds.push(prodId); - contents.push({ - id: prodId, - quantity, - item_price: price, - }); - } const productInfo = { content_ids: contentIds, - content_type: this.getContentType(rudderElement, 'product'), - content_name: prodName || '', - content_category: category || '', - currency: currVal, - value: useValue ? value : this.formatRevenue(price), + content_type: getContentType(rudderElement, 'product', this.categoryToContent), + content_name: eventHelpers.getProdName(productName, name), + content_category: eventHelpers.getCategory(category), + currency, + value: eventHelpers.getValue(useValue, value, price), contents, }; - window.fbq( - 'trackSingle', + + this.makeTrackSignalCall( self.pixelId, - 'AddToCart', - this.merge(productInfo, customProperties), - { - eventID: derivedEventID, - }, + eventHelpers.getEventName(event), + merge(productInfo, payload), + derivedEventID, ); - - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value: useValue ? value : this.formatRevenue(price), - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); - this.merge(productInfo, customProperties); + this.makeTrackSignalCalls(self.pixelId, event, legacyTo, derivedEventID, { + currency, + value: productInfo.value, + }); } else if (event === 'Order Completed') { - const contentType = this.getContentType(rudderElement, 'product'); - const contentIds = []; - const contents = []; - if (Array.isArray(products)) { - products.forEach((product) => { - if (product) { - const pId = product.product_id || product.sku || product.id; - if (pId) { - contentIds.push(pId); - const content = { - id: pId, - quantity: product.quantity || quantity || 1, - item_price: product.price || price, - }; - contents.push(content); - } - } - }); - } else { - logger.debug('No product array found'); - } + const contentType = getContentType(rudderElement, 'product', this.categoryToContent); + const { contents, contentIds } = getProductsContentsAndContentIds(products, quantity, price); + // ref: https://developers.facebook.com/docs/meta-pixel/implementation/marketing-api#purchase // "trackSingle" feature is : // https://developers.facebook.com/ads/blog/post/v2/2017/11/28/event-tracking-with-multiple-pixels-tracksingle/ @@ -380,242 +215,99 @@ class FacebookPixel { const productInfo = { content_ids: contentIds, content_type: contentType, - currency: currVal, + currency, value: revValue, contents, num_items: contentIds.length, content_name: contentName, }; - window.fbq( - 'trackSingle', + this.makeTrackSignalCall( self.pixelId, 'Purchase', - this.merge(productInfo, customProperties), - { - eventID: derivedEventID, - }, + merge(productInfo, payload), + derivedEventID, ); - - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value: revValue, - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); + this.makeTrackSignalCalls(self.pixelId, event, legacyTo, derivedEventID, { + currency, + value: revValue, + }); } else if (event === 'Products Searched') { - const contentIds = []; - const contents = []; + const { contents, contentIds } = getProductContentAndId(prodId, quantity, price); - if (prodId) { - contentIds.push(prodId); - contents.push({ - id: prodId, - quantity, - item_price: price, - }); - } const productInfo = { content_ids: contentIds, - content_category: category || '', - currency: currVal, + content_category: eventHelpers.getCategory(category), + currency, value, contents, search_string: query, }; - window.fbq('trackSingle', self.pixelId, 'Search', this.merge(productInfo, customProperties), { - eventID: derivedEventID, - }); - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value, - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); + this.makeTrackSignalCall(self.pixelId, 'Search', merge(productInfo, payload), derivedEventID); + this.makeTrackSignalCalls(self.pixelId, event, legacyTo, derivedEventID, { currency, value }); } else if (event === 'Checkout Started') { let contentCategory = category; - const contentIds = []; - const contents = []; - if (Array.isArray(products)) { - products.forEach((product) => { - if (product) { - const pId = product.product_id || product.sku || product.id; - if (pId) { - contentIds.push(pId); - const content = { - id: pId, - quantity: product.quantity || quantity || 1, - item_price: product.price || price, - }; - contents.push(content); - } - } - }); - - if (!contentCategory && products[0] && products[0].category) { - contentCategory = products[0].category; - } + const { contents, contentIds } = getProductsContentsAndContentIds(products, quantity, price); + if (Array.isArray(products) && !contentCategory && products[0] && products[0].category) { + contentCategory = products[0].category; } const productInfo = { content_ids: contentIds, - content_type: this.getContentType(rudderElement, 'product'), + content_type: getContentType(rudderElement, 'product', this.categoryToContent), content_category: contentCategory, - currency: currVal, + currency, value: revValue, contents, num_items: contentIds.length, }; - window.fbq( - 'trackSingle', + + this.makeTrackSignalCall( self.pixelId, 'InitiateCheckout', - this.merge(productInfo, customProperties), - { - eventID: derivedEventID, - }, + merge(productInfo, payload), + derivedEventID, ); - - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value: revValue, - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); + this.makeTrackSignalCalls(self.pixelId, event, legacyTo, derivedEventID, { + currency, + value: revValue, + }); } else { logger.debug('inside custom'); - if (!standardTo[event?.toLowerCase()] && !legacyTo[event?.toLowerCase()]) { + if (eventHelpers.isCustomEventNotMapped(standardTo, legacyTo, event)) { logger.debug('inside custom not mapped'); - const payloadVal = buildPayLoad(rudderElement, this.whitelistPiiProperties, this.blacklistPiiProperties, getHashedStatus(rudderElement.message, this.name)); - payloadVal.value = revValue; - window.fbq('trackSingleCustom', self.pixelId, event, payloadVal, { + payload.value = revValue; + window.fbq('trackSingleCustom', self.pixelId, event, payload, { eventID: derivedEventID, }); } else { - each((val, key) => { - if (key === event.toLowerCase()) { - payload.currency = currVal; - payload.value = revValue; - - window.fbq('trackSingle', self.pixelId, val, payload, { - eventID: derivedEventID, - }); - } - }, standardTo); - - each((val, key) => { - if (key === event.toLowerCase()) { - window.fbq( - 'trackSingle', - self.pixelId, - val, - { - currency: currVal, - value: revValue, - }, - { - eventID: derivedEventID, - }, - ); - } - }, legacyTo); + payload.value = revValue; + payload.currency = currency; + this.makeTrackSignalCalls(self.pixelId, event, standardTo, derivedEventID, payload); + this.makeTrackSignalCalls(self.pixelId, event, legacyTo, derivedEventID, { + currency, + value: revValue, + }); } } } - /** - * Get the Facebook Content Type - * - * Can be `product`, `destination`, `flight` or `hotel`. - * - * This can be overridden within the message - * `options.integrations.FACEBOOK_PIXEL.contentType`, or alternatively you can - * set the "Map Categories to Facebook Content Types" setting within - * RudderStack config and then set the corresponding commerce category in - * `track()` properties. - * - * https://www.facebook.com/business/help/606577526529702?id=1205376682832142 - */ - getContentType(rudderElement, defaultValue) { - // Get the message-specific override if it exists in the options parameter of `track()` - const contentTypeMessageOverride = - rudderElement.message.integrations?.FACEBOOK_PIXEL?.contentType; - if (contentTypeMessageOverride) return contentTypeMessageOverride; - - // Otherwise check if there is a replacement set for all Facebook Pixel - // track calls of this category - const { category } = rudderElement.message.properties; - if (category) { - const categoryMapping = this.categoryToContent?.find((i) => i.from === category); - if (categoryMapping?.to) return categoryMapping.to; - } - - // Otherwise return the default value - return defaultValue; - } - - merge(obj1, obj2) { - const res = {}; - - // All properties of obj1 - Object.keys(obj1).forEach((propObj1) => { - if (Object.prototype.hasOwnProperty.call(obj1, propObj1)) { - res[propObj1] = obj1[propObj1]; - } - }); - - // Extra properties of obj2 - Object.keys(obj2).forEach((propObj2) => { - if ( - Object.prototype.hasOwnProperty.call(obj2, propObj2) && - !Object.prototype.hasOwnProperty.call(res, propObj2) - ) { - res[propObj2] = obj2[propObj2]; + makeTrackSignalCalls(pixelId, event, array, derivedEventID, payload) { + each((val, key) => { + if (key === event.toLowerCase()) { + window.fbq('trackSingle', pixelId, val, payload, { + eventID: derivedEventID, + }); } - }); - - return res; + }, array); } - formatRevenue(revenue) { - const formattedRevenue = parseFloat(parseFloat(revenue || 0).toFixed(2)); - if (Number.isNaN(formattedRevenue)) { - logger.error('Revenue could not be converted to number'); - } - return formattedRevenue; + makeTrackSignalCall(pixelId, event, payload, derivedEventID) { + window.fbq('trackSingle', pixelId, event, payload, { + eventID: derivedEventID, + }); } - } export default FacebookPixel; diff --git a/src/integrations/FacebookPixel/index.js b/src/integrations/FacebookPixel/index.js index d99fae8d4..d7e2f4561 100644 --- a/src/integrations/FacebookPixel/index.js +++ b/src/integrations/FacebookPixel/index.js @@ -1,3 +1 @@ -import FacebookPixel from './browser'; - -export { FacebookPixel }; +export { default as FacebookPixel } from './browser'; diff --git a/src/integrations/FacebookPixel/utils.js b/src/integrations/FacebookPixel/utils.js index 0f09970e9..6725cf182 100644 --- a/src/integrations/FacebookPixel/utils.js +++ b/src/integrations/FacebookPixel/utils.js @@ -2,6 +2,7 @@ import is from 'is'; import get from 'get-value'; import sha256 from 'crypto-js/sha256'; import logger from '../../utils/logUtil'; +import { isDefined } from '../../utils/commonUtils'; function getEventId(message) { return ( @@ -46,14 +47,14 @@ const getContentCategory = (category) => { const getHashedStatus = (message, integrationName) => { const val = get(message, `integrations.${integrationName}.hashed`); return val; -} +}; const buildPayLoad = ( - rudderElement, - configWhilistedProperties, + rudderElement, + configWhilistedProperties, configBlacklistedProperties, - hashedPii - ) =>{ + hashedPii, +) => { const dateFields = [ 'checkinDate', 'checkoutDate', @@ -90,13 +91,14 @@ const buildPayLoad = ( return acc; }, {}); const whitelistPiiPropertiesNames = whitelistPiiProperties.map( - (propObject) => propObject.whitelistPiiProperties) + (propObject) => propObject.whitelistPiiProperties, + ); const { properties } = rudderElement.message; const payload = Object.entries(properties).reduce((acc, [currPropName, currPropValue]) => { const isPropertyPii = - defaultPiiProperties.includes(currPropName) || + defaultPiiProperties.includes(currPropName) || Object.prototype.hasOwnProperty.call(shouldPropBeHashedMap, currPropName); const isProperyWhiteListed = whitelistPiiPropertiesNames.includes(currPropName); @@ -108,14 +110,13 @@ const buildPayLoad = ( } if (shouldPropBeHashedMap[currPropName] && typeof currPropValue === 'string') { - acc[currPropName] = hashedPii ? currPropValue.toString():sha256(currPropValue).toString(); + acc[currPropName] = hashedPii ? currPropValue.toString() : sha256(currPropValue).toString(); } else if ((!isPropertyPii || isProperyWhiteListed) && !isDateProp) { acc[currPropName] = currPropValue; } else { logger.debug( `[Facebook Pixel] PII Property '${currPropValue}' is neither hashed nor whitelisted and will be ignored`, ); - } return acc; @@ -123,4 +124,188 @@ const buildPayLoad = ( return payload; }; -export { getEventId, getContentCategory, buildPayLoad, getHashedStatus }; +const merge = (obj1, obj2) => { + const res = {}; + + // All properties of obj1 + Object.keys(obj1).forEach((propObj1) => { + if (Object.prototype.hasOwnProperty.call(obj1, propObj1)) { + res[propObj1] = obj1[propObj1]; + } + }); + + // Extra properties of obj2 + Object.keys(obj2).forEach((propObj2) => { + if ( + Object.prototype.hasOwnProperty.call(obj2, propObj2) && + !Object.prototype.hasOwnProperty.call(res, propObj2) + ) { + res[propObj2] = obj2[propObj2]; + } + }); + + return res; +}; + +/** + * Returns formatted revenue + * @param {*} revenue + * @returns + */ +const formatRevenue = (revenue) => { + const parsedRevenue = parseFloat(revenue); + const formattedRevenue = Number.isNaN(parsedRevenue) ? 0 : parseFloat(parsedRevenue.toFixed(2)); + + if (Number.isNaN(formattedRevenue)) { + logger.error('Revenue could not be converted to a number'); + } + + return formattedRevenue; +}; + +/** + * Get the Facebook Content Type + * + * Can be `product`, `destination`, `flight` or `hotel`. + * + * This can be overridden within the message + * `options.integrations.FACEBOOK_PIXEL.contentType`, or alternatively you can + * set the "Map Categories to Facebook Content Types" setting within + * RudderStack config and then set the corresponding commerce category in + * `track()` properties. + * + * https://www.facebook.com/business/help/606577526529702?id=1205376682832142 + * @param {*} rudderElement + * @param {*} defaultValue + * @param {*} categoryToContent + * @returns + */ +const getContentType = (rudderElement, defaultValue, categoryToContent) => { + // Get the message-specific override if it exists in the options parameter of `track()` + const contentTypeMessageOverride = + rudderElement.message.integrations?.FACEBOOK_PIXEL?.contentType; + if (contentTypeMessageOverride) return contentTypeMessageOverride; + + // Otherwise check if there is a replacement set for all Facebook Pixel + // track calls of this category + const { category } = rudderElement.message.properties; + if (category) { + const categoryMapping = categoryToContent?.find((i) => i.from === category); + if (categoryMapping?.to) return categoryMapping.to; + } + + // Otherwise return the default value + return defaultValue; +}; + +/** + * Returns contents, contentIds for products + * @param {*} products + * @param {*} quantity + * @param {*} price + * @returns + */ +const getProductsContentsAndContentIds = (products, quantity, price) => { + const contents = products + ? products + .filter((product) => product) + .map(({ product_id: prodId, sku, id, quantity: productQuantity, price: productPrice }) => { + const productId = prodId || sku || id; + return isDefined(productId) + ? { + id: productId, + quantity: productQuantity || quantity || 1, + item_price: productPrice || price, + } + : null; + }) + .filter((content) => content !== null) + : []; + + const contentIds = contents.map((content) => content.id); + + return { contents, contentIds }; +}; + +/** + * Returns contents, contentIds for single product + * @param {*} prodId + * @param {*} quantity + * @param {*} price + * @returns + */ +const getProductContentAndId = (prodId, quantity, price) => { + const contents = []; + const contentIds = []; + + if (prodId) { + contentIds.push(prodId); + contents.push({ + id: prodId, + quantity, + item_price: price, + }); + } + + return { contents, contentIds }; +}; + +/** + * Returns product list viewed event params + * @param {*} properties + * @returns + */ +const getProductListViewedEventParams = (properties) => { + const { products, category, quantity, price } = properties; + + const { contents, contentIds } = getProductsContentsAndContentIds(products, quantity, price); + + let contentType; + if (contentIds.length > 0) { + contentType = 'product'; + } else if (category) { + contentIds.push(category); + contents.push({ + id: category, + quantity: 1, + }); + contentType = 'product_group'; + } + + return { contentIds, contentType, contents }; +}; + +/** + * Helper functions object + */ +const eventHelpers = { + getCategory: (category) => category || '', + getCurrency: (currency) => currency || 'USD', + getProperties: (message) => message?.properties || {}, + getProdId: (productId, sku, id) => productId || sku || id, + getProdName: (productName, name) => productName || name || '', + getFormattedRevenue: (revValue, value) => revValue || formatRevenue(value), + getEventName: (event) => (event === 'Product Viewed' ? 'ViewContent' : 'AddToCart'), + getValue: (useValue, value, price) => (useValue ? value : formatRevenue(price)), + isCustomEventNotMapped: (standardTo, legacyTo, event) => + !standardTo[event?.toLowerCase()] && !legacyTo[event?.toLowerCase()], + validateRevenue: (revValue) => { + if (!isDefined(revValue)) { + logger.error("'properties.revenue' could not be converted to a number"); + } + }, +}; + +export { + merge, + getEventId, + buildPayLoad, + eventHelpers, + formatRevenue, + getContentType, + getHashedStatus, + getContentCategory, + getProductContentAndId, + getProductListViewedEventParams, + getProductsContentsAndContentIds, +}; diff --git a/src/integrations/Fullstory/browser.js b/src/integrations/Fullstory/browser.js index a555cc94b..f4c8ef7a2 100644 --- a/src/integrations/Fullstory/browser.js +++ b/src/integrations/Fullstory/browser.js @@ -1,9 +1,10 @@ +/* eslint-disable func-names */ /* eslint-disable class-methods-use-this */ -/* eslint-disable no-undef */ +/* eslint-disable no-underscore-dangle */ import camelcase from '../../utils/camelcase'; import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class Fullstory { constructor(config, analytics, destinationInfo) { @@ -23,13 +24,12 @@ class Fullstory { } static getFSProperties(properties) { - const FS_properties = {}; - Object.keys(properties).map(function (key, index) { - FS_properties[ - key === 'displayName' || key === 'email' ? key : Fullstory.camelCaseField(key) - ] = properties[key]; + const fsProperties = {}; + Object.keys(properties).forEach((key) => { + fsProperties[key === 'displayName' || key === 'email' ? key : Fullstory.camelCaseField(key)] = + properties[key]; }); - return FS_properties; + return fsProperties; } static camelCaseField(fieldName) { @@ -56,82 +56,21 @@ class Fullstory { return camelcase(fieldName); } + // eslint-disable-next-line sonarjs/cognitive-complexity init() { logger.debug('===in init FULLSTORY==='); - window._fs_debug = this.fs_debug_mode; - window._fs_host = this.fs_host; - window._fs_script = 'edge.fullstory.com/s/fs.js'; - window._fs_org = this.fs_org; - window._fs_namespace = 'FS'; - (function (m, n, e, t, l, o, g, y) { - if (e in m) { - if (m.console && m.console.log) { - m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].'); - } - return; - } - g = m[e] = function (a, b, s) { - g.q ? g.q.push([a, b, s]) : g._api(a, b, s); - }; - g.q = []; - o = n.createElement(t); - o.async = 1; - o.crossOrigin = 'anonymous'; - o.src = `https://${_fs_script}`; - o.setAttribute('data-loader', LOAD_ORIGIN); - y = n.getElementsByTagName(t)[0]; - y.parentNode.insertBefore(o, y); - g.identify = function (i, v, s) { - g(l, { uid: i }, s); - if (v) g(l, v, s); - }; - g.setUserVars = function (v, s) { - g(l, v, s); - }; - g.event = function (i, v, s) { - g('event', { n: i, p: v }, s); - }; - g.shutdown = function () { - g('rec', !1); - }; - g.restart = function () { - g('rec', !0); - }; - g.log = function (a, b) { - g('log', [a, b]); - }; - g.consent = function (a) { - g('consent', !arguments.length || a); - }; - g.identifyAccount = function (i, v) { - o = 'account'; - v = v || {}; - v.acctId = i; - g(o, v); - }; - g.clearUserCookie = function () {}; - g._w = {}; - y = 'XMLHttpRequest'; - g._w[y] = m[y]; - y = 'fetch'; - g._w[y] = m[y]; - if (m[y]) - m[y] = function () { - return g._w[y].apply(this, arguments); - }; - })(window, document, window._fs_namespace, 'script', 'user'); - + loadNativeSdk(this.fs_debug_mode, this.fs_host, this.fs_org); const { FULLSTORY } = this.analytics.loadOnlyIntegrations; // Checking if crossDomainSupport is their or not. if (FULLSTORY?.crossDomainSupport === true) { // This function will check if the customer hash is available or not in localStorage window._fs_identity = function () { if (window.localStorage) { - const { tata_customer_hash } = window.localStorage; - if (tata_customer_hash) { + const { tata_customer_hash: tataCustomerHash } = window.localStorage; + if (tataCustomerHash) { return { - uid: tata_customer_hash, - displayName: tata_customer_hash, + uid: tataCustomerHash, + displayName: tataCustomerHash, }; } } else { @@ -144,7 +83,7 @@ class Fullstory { (function () { function fs(api) { if (!window._fs_namespace) { - console.error('FullStory unavailable, window["_fs_namespace"] must be defined'); + logger.error('FullStory unavailable, window["_fs_namespace"] must be defined'); return undefined; } return api ? window[window._fs_namespace][api] : window[window._fs_namespace]; @@ -158,10 +97,8 @@ class Fullstory { return; } delay = Math.min(delay * 2, 1024); - if (totalTime > timeout) { - if (timeoutFn) { - timeoutFn(); - } + if (totalTime > timeout && timeoutFn) { + timeoutFn(); } totalTime += delay; setTimeout(resultFn, delay); @@ -190,6 +127,11 @@ class Fullstory { } } + isLoaded() { + logger.debug('in FULLSTORY isLoaded'); + return !!window.FS; + } + page(rudderElement) { logger.debug('in FULLSORY page'); const rudderMessage = rudderElement.message; @@ -204,10 +146,12 @@ class Fullstory { identify(rudderElement) { logger.debug('in FULLSORY identify'); + let { userId } = rudderElement.message; - const { traits } = rudderElement.message.context; - if (!userId) userId = rudderElement.message.anonymousId; + const { context, anonymousId } = rudderElement.message; + const { traits } = context; + if (!userId) userId = anonymousId; if (Object.keys(traits).length === 0 && traits.constructor === Object) window.FS.identify(userId); else window.FS.identify(userId, Fullstory.getFSProperties(traits)); @@ -220,11 +164,6 @@ class Fullstory { Fullstory.getFSProperties(rudderElement.message.properties), ); } - - isLoaded() { - logger.debug('in FULLSTORY isLoaded'); - return !!window.FS; - } } export default Fullstory; diff --git a/src/integrations/Fullstory/index.js b/src/integrations/Fullstory/index.js index fc4218a2c..dba306da9 100644 --- a/src/integrations/Fullstory/index.js +++ b/src/integrations/Fullstory/index.js @@ -1,3 +1 @@ -import Fullstory from './browser'; - -export { Fullstory }; +export { default as Fullstory } from './browser'; diff --git a/src/integrations/Fullstory/nativeSdkLoader.js b/src/integrations/Fullstory/nativeSdkLoader.js new file mode 100644 index 000000000..4f610768d --- /dev/null +++ b/src/integrations/Fullstory/nativeSdkLoader.js @@ -0,0 +1,69 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(fs_debug_mode, fs_host, fs_org) { + window._fs_debug = fs_debug_mode; + window._fs_host = fs_host; + window._fs_script = 'edge.fullstory.com/s/fs.js'; + window._fs_org = fs_org; + window._fs_namespace = 'FS'; + + (function (m, n, e, t, l, o, g, y) { + if (e in m) { + if (m.console && m.console.log) { + m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].'); + } + return; + } + g = m[e] = function (a, b, s) { + g.q ? g.q.push([a, b, s]) : g._api(a, b, s); + }; + g.q = []; + o = n.createElement(t); + o.async = 1; + o.crossOrigin = 'anonymous'; + o.src = `https://${_fs_script}`; + o.setAttribute('data-loader', LOAD_ORIGIN); + y = n.getElementsByTagName(t)[0]; + y.parentNode.insertBefore(o, y); + g.identify = function (i, v, s) { + g(l, { uid: i }, s); + if (v) g(l, v, s); + }; + g.setUserVars = function (v, s) { + g(l, v, s); + }; + g.event = function (i, v, s) { + g('event', { n: i, p: v }, s); + }; + g.shutdown = function () { + g('rec', !1); + }; + g.restart = function () { + g('rec', !0); + }; + g.log = function (a, b) { + g('log', [a, b]); + }; + g.consent = function (a) { + g('consent', !arguments.length || a); + }; + g.identifyAccount = function (i, v) { + o = 'account'; + v = v || {}; + v.acctId = i; + g(o, v); + }; + g.clearUserCookie = function () {}; + g._w = {}; + y = 'XMLHttpRequest'; + g._w[y] = m[y]; + y = 'fetch'; + g._w[y] = m[y]; + if (m[y]) + m[y] = function () { + return g._w[y].apply(this, arguments); + }; + })(window, document, window._fs_namespace, 'script', 'user'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/GA/browser.js b/src/integrations/GA/browser.js index e9c1e2a24..9a5ebcdcb 100644 --- a/src/integrations/GA/browser.js +++ b/src/integrations/GA/browser.js @@ -57,6 +57,7 @@ export default class GA { window.ga || function a() { window.ga.q = window.ga.q || []; + // eslint-disable-next-line prefer-rest-params window.ga.q.push(arguments); }; window.ga.l = new Date().getTime(); @@ -160,63 +161,80 @@ export default class GA { this.contentGroupingsArray, ); - if (Object.keys(custom).length) { + if (Object.keys(custom).length > 0) { window.ga(`${this.trackerName}set`, custom); } logger.debug('in GoogleAnalyticsManager identify'); } + // eslint-disable-next-line sonarjs/cognitive-complexity track(rudderElement) { const self = this; // Ecommerce events - const { event, properties, name } = rudderElement.message; - const options = this.extractCheckoutOptions(rudderElement); - const props = rudderElement.message.properties; - const { products, revenue } = properties; + const { event, properties, name, context } = rudderElement.message; let { total } = properties; + const { campaign } = context; + const { + tax, + step, + value, + revenue, + products, + shipping, + currency, + affiliation, + nonInteraction, + label: eventLabel, + order_id: orderId, + category: eventCategory, + } = properties; + + const options = this.extractCheckoutOptions(rudderElement); + const props = properties; const data = {}; - const eventCategory = rudderElement.message.properties.category; - const orderId = properties.order_id; const eventAction = event || name || ''; - const eventLabel = rudderElement.message.properties.label; let eventValue = ''; let payload; - const { campaign } = rudderElement.message.context; let params; let filters; let sorts; + + const orderIdNotPresentErrorMessage = 'order_id not present events are not sent to GA'; + if (event === 'Order Completed' && !this.enhancedEcommerce) { // order_id is required if (!orderId) { - logger.debug('order_id not present events are not sent to GA'); + logger.debug(orderIdNotPresentErrorMessage); return; } // add transaction window.ga(`${this.trackerName}ecommerce:addTransaction`, { - affiliation: properties.affiliation, - shipping: properties.shipping, + affiliation, + shipping, revenue: total || revenue, - tax: properties.tax, + tax, id: orderId, - currency: properties.currency, + currency, }); - // products added - products.forEach((product) => { - const productTrack = self.createProductTrack(rudderElement, product); - - window.ga(`${this.trackerName}ecommerce:addItem`, { - category: productTrack.properties.category, - quantity: productTrack.properties.quantity, - price: productTrack.properties.price, - name: productTrack.properties.name, - sku: productTrack.properties.sku, - id: orderId, - currency: productTrack.properties.currency, + if (Array.isArray(products)) { + // products added + products.forEach((product) => { + const productTrack = self.createProductTrack(rudderElement, product); + + window.ga(`${this.trackerName}ecommerce:addItem`, { + category: productTrack.properties.category, + quantity: productTrack.properties.quantity, + price: productTrack.properties.price, + name: productTrack.properties.name, + sku: productTrack.properties.sku, + id: orderId, + currency: productTrack.properties.currency, + }); }); - }); + } window.ga(`${this.trackerName}ecommerce:send`); } @@ -236,7 +254,7 @@ export default class GA { }); window.ga(`${this.trackerName}ec:setAction`, 'checkout', { - step: properties.step || 1, + step: step || 1, option: options || undefined, }); @@ -258,11 +276,10 @@ export default class GA { window.ga(`${this.trackerName}send`, 'event', 'Checkout', 'Option'); break; case 'Order Completed': - total = - rudderElement.message.properties.total || rudderElement.message.properties.revenue || 0; + total = total || revenue || 0; if (!orderId) { - logger.debug('order_id not present events are not sent to GA'); + logger.debug(orderIdNotPresentErrorMessage); return; } this.loadEnhancedEcommerce(rudderElement); @@ -285,7 +302,7 @@ export default class GA { break; case 'Order Refunded': if (!orderId) { - logger.debug('order_id not present events are not sent to GA'); + logger.debug(orderIdNotPresentErrorMessage); return; } this.loadEnhancedEcommerce(rudderElement); @@ -388,16 +405,8 @@ export default class GA { case 'Product List Filtered': props.filters = props.filters || []; props.sorts = props.sorts || []; - filters = props.filters - .map((obj) => { - return `${obj.type}:${obj.value}`; - }) - .join(); - sorts = props.sorters - .map((obj) => { - return `${obj.type}:${obj.value}`; - }) - .join(); + filters = props.filters.map((obj) => `${obj.type}:${obj.value}`).join(); + sorts = props.sorts.map((obj) => `${obj.type}:${obj.value}`).join(); this.loadEnhancedEcommerce(rudderElement); @@ -437,10 +446,8 @@ export default class GA { this.pushEnhancedEcommerce(rudderElement); break; default: - if (rudderElement.message.properties) { - eventValue = rudderElement.message.properties.value - ? rudderElement.message.properties.value - : rudderElement.message.properties.revenue; + if (properties) { + eventValue = value || revenue; } payload = { @@ -449,10 +456,7 @@ export default class GA { eventLabel, eventValue: this.formatValue(eventValue), // Allow users to override their nonInteraction integration setting for any single particluar event. - nonInteraction: - rudderElement.message.properties.nonInteraction !== undefined - ? !!rudderElement.message.properties.nonInteraction - : !!this.nonInteraction, + nonInteraction: nonInteraction !== undefined ? !!nonInteraction : !!this.nonInteraction, }; if (campaign) { @@ -465,17 +469,15 @@ export default class GA { payload = { payload, - ...this.setCustomDimenionsAndMetrics(rudderElement.message.properties), + ...this.setCustomDimenionsAndMetrics(properties), }; window.ga(`${this.trackerName}send`, 'event', payload.payload); logger.debug('in GoogleAnalyticsManager track'); } } else { - if (rudderElement.message.properties) { - eventValue = rudderElement.message.properties.value - ? rudderElement.message.properties.value - : rudderElement.message.properties.revenue; + if (properties) { + eventValue = value || revenue; } payload = { @@ -484,10 +486,7 @@ export default class GA { eventLabel, eventValue: this.formatValue(eventValue), // Allow users to override their nonInteraction integration setting for any single particluar event. - nonInteraction: - rudderElement.message.properties.nonInteraction !== undefined - ? !!rudderElement.message.properties.nonInteraction - : !!this.nonInteraction, + nonInteraction: nonInteraction !== undefined ? !!nonInteraction : !!this.nonInteraction, }; if (campaign) { @@ -500,7 +499,7 @@ export default class GA { payload = { payload, - ...this.setCustomDimenionsAndMetrics(rudderElement.message.properties), + ...this.setCustomDimenionsAndMetrics(properties), }; window.ga(`${this.trackerName}send`, 'event', payload.payload); @@ -511,26 +510,26 @@ export default class GA { page(rudderElement) { logger.debug('in GoogleAnalyticsManager page'); - const { category } = rudderElement.message.properties; - const eventProperties = rudderElement.message.properties; + const { properties, context } = rudderElement.message; + const { category, referrer } = properties; + const eventProperties = properties; let name; - if (rudderElement.message.properties.category && rudderElement.message.name) { - name = `${rudderElement.message.properties.category} ${rudderElement.message.name}`; - } else if (!rudderElement.message.properties.category && !rudderElement.message.name) { + if (category && rudderElement.message.name) { + name = `${category} ${rudderElement.message.name}`; + } else if (!category && !rudderElement.message.name) { name = ''; } else { - name = rudderElement.message.name || rudderElement.message.properties.category; + name = rudderElement.message.name || category; } - const campaign = rudderElement.message.context.campaign || {}; + const campaign = context.campaign || {}; let pageview = {}; const pagePath = this.path(eventProperties, this.includeSearch); - const pageReferrer = rudderElement.message.properties.referrer || ''; + const pageReferrer = referrer || ''; let pageTitle; - if (!rudderElement.message.properties.category && !rudderElement.message.name) - pageTitle = eventProperties.title; - else if (!rudderElement.message.properties.category) pageTitle = rudderElement.message.name; - else if (!rudderElement.message.name) pageTitle = rudderElement.message.properties.category; + if (!category && !rudderElement.message.name) pageTitle = eventProperties.title; + else if (!category) pageTitle = rudderElement.message.name; + else if (!rudderElement.message.name) pageTitle = category; else pageTitle = name; pageview.page = pagePath; @@ -652,7 +651,7 @@ export default class GA { this.metricsArray, this.contentGroupingsArray, ); - if (Object.keys(custom).length) { + if (Object.keys(custom).length > 0) { if (this.setAllMappedProps) { window.ga(`${this.trackerName}set`, custom); } else { @@ -675,10 +674,8 @@ export default class GA { */ path(properties, includeSearch) { let str = properties.path; - if (properties) { - if (includeSearch && properties.search) { - str += properties.search; - } + if (properties && includeSearch && properties.search) { + str += properties.search; } return str; } @@ -790,7 +787,7 @@ export default class GA { * @param {} products */ getProductPosition(item, products) { - const { position } = item.properties; + const { position, product_id: productId } = item.properties; if ( typeof position !== 'undefined' && @@ -800,13 +797,7 @@ export default class GA { return position; } - return ( - products - .map((x) => { - return x.product_id; - }) - .indexOf(item.properties.product_id) + 1 - ); + return products.map((x) => x.product_id).indexOf(productId) + 1; } /** diff --git a/src/integrations/GA/index.js b/src/integrations/GA/index.js index e93d4a44c..848461831 100644 --- a/src/integrations/GA/index.js +++ b/src/integrations/GA/index.js @@ -1,3 +1 @@ -import GA from './browser'; - -export { GA }; +export { default as GA } from './browser'; diff --git a/src/integrations/GA/index.test.js b/src/integrations/GA/index.test.js index 5d3070e04..fff7f7c88 100644 --- a/src/integrations/GA/index.test.js +++ b/src/integrations/GA/index.test.js @@ -13,13 +13,17 @@ const destinationInfo = { destinationId: 'sample-destination-id', }; +const trackingId = 'UA-143161493-8'; +const eventName = 'test track'; +const label = 'test label'; + GA.prototype.loadScript = jest.fn(); describe('GA init tests', () => { let googleAnalytics; beforeEach(() => { googleAnalytics = new GA( - { trackingID: 'UA-143161493-8' }, + { trackingID: trackingId }, { loadIntegration: true }, destinationInfo, ); @@ -38,7 +42,7 @@ describe('GA init tests', () => { expect(typeof window.ga.l).toBe('number'); // expect(window.ga.q[0]).toEqual(); expect(window.ga.q[0][0]).toEqual('create'); - expect(window.ga.q[0][1]).toEqual('UA-143161493-8'); + expect(window.ga.q[0][1]).toEqual(trackingId); expect(window.ga.q[0][2]).toEqual({ cookieDomain: 'auto', siteSpeedSampleRate: 1, @@ -51,11 +55,10 @@ describe('GA init tests', () => { }); describe('GA page', () => { - let googleAnalytics; beforeEach(() => { googleAnalytics = new GA( { - trackingID: 'UA-143161493-8', + trackingID: trackingId, dimensions: [ { from: 'testDimension', @@ -111,11 +114,10 @@ describe('GA init tests', () => { }); describe('GA simple non ecomm event', () => { - let googleAnalytics; beforeEach(() => { googleAnalytics = new GA( { - trackingID: 'UA-143161493-8', + trackingID: trackingId, dimensions: [], metrics: [], contentGroupings: [], @@ -131,10 +133,10 @@ describe('GA init tests', () => { googleAnalytics.track({ message: { context: {}, - event: 'test track', + event: eventName, properties: { value: 20, - label: 'test label', + label, }, }, }); @@ -145,8 +147,8 @@ describe('GA init tests', () => { expect(window.ga.mock.calls[0][2]).toEqual({ eventCategory: 'All', - eventAction: 'test track', - eventLabel: 'test label', + eventAction: eventName, + eventLabel: label, eventValue: 20, nonInteraction: false, }); @@ -156,11 +158,11 @@ describe('GA init tests', () => { googleAnalytics.track({ message: { context: {}, - event: 'test track', + event: eventName, properties: { category: 'test cat', value: 20, - label: 'test label', + label, nonInteraction: 1, }, }, @@ -172,8 +174,8 @@ describe('GA init tests', () => { expect(window.ga.mock.calls[0][2]).toEqual({ eventCategory: 'test cat', - eventAction: 'test track', - eventLabel: 'test label', + eventAction: eventName, + eventLabel: label, eventValue: 20, nonInteraction: true, }); diff --git a/src/integrations/GA360/index.js b/src/integrations/GA360/index.js index 4040d889c..26ac8780d 100644 --- a/src/integrations/GA360/index.js +++ b/src/integrations/GA360/index.js @@ -1,3 +1 @@ -import { GA360 } from './browser'; - -export { GA360 }; +export { GA360 } from './browser'; diff --git a/src/integrations/GA4/browser.js b/src/integrations/GA4/browser.js index 290600a0b..8e907783e 100644 --- a/src/integrations/GA4/browser.js +++ b/src/integrations/GA4/browser.js @@ -209,7 +209,8 @@ export default class GA4 { sendGAEvent(event, parameters, checkRequiredParameters, eventMappingObj, integrations) { if (checkRequiredParameters && !hasRequiredParameters(parameters, eventMappingObj)) { - throw Error('Payload must have required parameters..'); + logger.error('Payload must have required parameters..'); + return; } const params = { ...parameters }; params.send_to = this.measurementId; @@ -253,7 +254,8 @@ export default class GA4 { const { properties, integrations } = rudderElement.message; const { products } = properties; if (!event || isReservedName(event)) { - throw Error('Cannot call un-named/reserved named track event'); + logger.error('Cannot call un-named/reserved named track event'); + return; } // get GA4 event name and corresponding configs defined to add properties to that event const eventMappingArray = getDestinationEventName(event); diff --git a/src/integrations/GA4/index.js b/src/integrations/GA4/index.js index 613ab8d11..152b5305b 100644 --- a/src/integrations/GA4/index.js +++ b/src/integrations/GA4/index.js @@ -1,3 +1 @@ -import GA4 from './browser'; - -export { GA4 }; +export { default as GA4 } from './browser'; diff --git a/src/integrations/GA4/test/input.js b/src/integrations/GA4/test/input.js index 3dd40b6db..1ec635575 100644 --- a/src/integrations/GA4/test/input.js +++ b/src/integrations/GA4/test/input.js @@ -1,5 +1,43 @@ const rudderanalytics = []; +const url = 'https://www.website.com/product/path'; +const imageUrl = 'https://www.website.com/product/path.png'; +const imageUrl1 = 'https://www.website.com/product/path.jpg'; + +const product = { + product_id: '123', + sku: 'F15', + category: 'Games', + name: 'Game', + brand: 'Gamepro', + variant: '111', + price: 13.49, + quantity: 11, + coupon: 'DISC21', + position: 1, +}; + +const products = [ + { + product_id: '123', + sku: 'G-32', + name: 'Monopoly', + price: 14, + quantity: 1, + category: 'Games', + url, + image_url: imageUrl1, + }, + { + product_id: '345', + sku: 'F-32', + name: 'UNO', + price: 3.45, + quantity: 2, + category: 'Games', + }, +]; + rudderanalytics.track('Products Searched', { query: 'HDMI cable', }); @@ -84,18 +122,9 @@ rudderanalytics.track('Promotion Clicked', { }); rudderanalytics.track('Product Clicked', { - product_id: '123', - sku: 'F15', - category: 'Games', - name: 'Game', - brand: 'Gamepro', - variant: '111', - price: 13.49, - quantity: 11, - coupon: 'DISC21', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.png', + ...product, + url, + image_url: imageUrl, }); rudderanalytics.track('Product Viewed', { @@ -110,38 +139,20 @@ rudderanalytics.track('Product Viewed', { coupon: 'DISC21', currency: 'USD', position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.png', + url, + image_url: imageUrl, }); rudderanalytics.track('Product Added', { - product_id: '123', - sku: 'F15', - category: 'Games', - name: 'Game', - brand: 'Gamepro', - variant: '111', - price: 13.49, - quantity: 11, - coupon: 'DISC21', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.png', + ...product, + url, + image_url: imageUrl, }); rudderanalytics.track('Product Removed', { - product_id: '123', - sku: 'F15', - category: 'Games', - name: 'Game', - brand: 'Gamepro', - variant: '111', - price: 13.49, - quantity: 11, - coupon: 'DISC21', - position: 1, - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.png', + ...product, + url, + image_url: imageUrl, }); rudderanalytics.track('Cart Viewed', { @@ -154,8 +165,8 @@ rudderanalytics.track('Cart Viewed', { price: 14.99, position: 1, category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', + url, + image_url: imageUrl1, }, { product_id: '345', @@ -178,26 +189,7 @@ rudderanalytics.track('Checkout Started', { discount: 1.5, coupon: 'ImagePro', currency: 'USD', - products: [ - { - product_id: '123', - sku: 'G-32', - name: 'Monopoly', - price: 14, - quantity: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '345', - sku: 'F-32', - name: 'UNO', - price: 3.45, - quantity: 2, - category: 'Games', - }, - ], + products, }); rudderanalytics.track('Payment Info Entered', { @@ -217,26 +209,7 @@ rudderanalytics.track('Order Completed', { discount: 1.5, coupon: 'ImagePro', currency: 'USD', - products: [ - { - product_id: '123', - sku: 'G-32', - name: 'Monopoly', - price: 14, - quantity: 1, - category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', - }, - { - product_id: '345', - sku: 'F-32', - name: 'UNO', - price: 3.45, - quantity: 2, - category: 'Games', - }, - ], + products, }); rudderanalytics.track('Order Refunded', { @@ -251,8 +224,8 @@ rudderanalytics.track('Order Refunded', { price: 17, quantity: 1, category: 'Games', - url: 'https://www.website.com/product/path', - image_url: 'https://www.website.com/product/path.jpg', + url, + image_url: imageUrl1, }, { product_id: '345', diff --git a/src/integrations/GA4/test/output.js b/src/integrations/GA4/test/output.js index 0e3388f5c..af9ab0b75 100644 --- a/src/integrations/GA4/test/output.js +++ b/src/integrations/GA4/test/output.js @@ -1,4 +1,6 @@ const gtag = []; +const itemListName = "What's New"; +const affiliation = 'Apple Store'; gtag('event', 'search', { search_term: 'HDMI cable' }); @@ -12,7 +14,7 @@ gtag('event', 'view_item_list', { index: 2, item_category: 'Games and Entertainment', item_list_id: 'list1', - item_list_name: "What's New", + item_list_name: itemListName, }, { item_id: '343344ff5567ff3', @@ -21,10 +23,10 @@ gtag('event', 'view_item_list', { index: 21, item_category: 'Card Games', item_list_id: 'list1', - item_list_name: "What's New", + item_list_name: itemListName, }, ], - item_list_name: "What's New", + item_list_name: itemListName, }); gtag('event', 'Product List Filtered', { @@ -190,7 +192,7 @@ window.gtag('event', 'begin_checkout', { price: 14, quantity: 1, item_category: 'Games', - affiliation: 'Apple Store', + affiliation, coupon: 'ImagePro', currency: 'USD', }, @@ -200,12 +202,12 @@ window.gtag('event', 'begin_checkout', { price: 3.45, quantity: 2, item_category: 'Games', - affiliation: 'Apple Store', + affiliation, coupon: 'ImagePro', currency: 'USD', }, ], - affiliation: 'Apple Store', + affiliation, shipping: 22, tax: 1, coupon: 'ImagePro', @@ -231,7 +233,7 @@ window.gtag('event', 'purchase', { price: 14, quantity: 1, item_category: 'Games', - affiliation: 'Apple Store', + affiliation, coupon: 'ImagePro', currency: 'USD', }, @@ -241,12 +243,12 @@ window.gtag('event', 'purchase', { price: 3.45, quantity: 2, item_category: 'Games', - affiliation: 'Apple Store', + affiliation, coupon: 'ImagePro', currency: 'USD', }, ], - affiliation: 'Apple Store', + affiliation, value: 20, shipping: 22, tax: 1, diff --git a/src/integrations/GoogleAds/browser.js b/src/integrations/GoogleAds/browser.js index 0a69bca54..92af5490f 100644 --- a/src/integrations/GoogleAds/browser.js +++ b/src/integrations/GoogleAds/browser.js @@ -1,6 +1,5 @@ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { getHashFromArrayWithDuplicate, removeUndefinedAndNullValues, @@ -12,6 +11,7 @@ import { getConversionData, } from './utils'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class GoogleAds { constructor(config, analytics, destinationInfo) { @@ -48,25 +48,7 @@ class GoogleAds { init() { const sourceUrl = `https://www.googletagmanager.com/gtag/js?id=${this.conversionId}`; - (function (id, src, document) { - logger.debug(`in script loader=== ${id}`); - const js = document.createElement('script'); - js.src = src; - js.async = 1; - js.setAttribute('data-loader', LOAD_ORIGIN); - js.type = 'text/javascript'; - js.id = id; - const e = document.getElementsByTagName('head')[0]; - logger.debug('==script==', e); - e.appendChild(js); - })('googleAds-integration', sourceUrl, document); - - window.dataLayer = window.dataLayer || []; - window.gtag = function () { - // eslint-disable-next-line prefer-rest-params - window.dataLayer.push(arguments); - }; - window.gtag('js', new Date()); + loadNativeSdk(sourceUrl); // Additional Settings diff --git a/src/integrations/GoogleAds/nativeSdkLoader.js b/src/integrations/GoogleAds/nativeSdkLoader.js new file mode 100644 index 000000000..e30d7b7b3 --- /dev/null +++ b/src/integrations/GoogleAds/nativeSdkLoader.js @@ -0,0 +1,26 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import logger from '../../utils/logUtil'; + +function loadNativeSdk(sourceUrl) { + (function (id, src, document) { + logger.debug(`in script loader=== ${id}`); + const js = document.createElement('script'); + js.src = src; + js.async = 1; + js.setAttribute('data-loader', LOAD_ORIGIN); + js.type = 'text/javascript'; + js.id = id; + const e = document.getElementsByTagName('head')[0]; + logger.debug('==script==', e); + e.appendChild(js); + })('googleAds-integration', sourceUrl, document); + + window.dataLayer = window.dataLayer || []; + window.gtag = function () { + // eslint-disable-next-line prefer-rest-params + window.dataLayer.push(arguments); + }; + window.gtag('js', new Date()); +} + +export { loadNativeSdk }; diff --git a/src/integrations/GoogleOptimize/browser.js b/src/integrations/GoogleOptimize/browser.js index 44ad640f5..1d2b9e6f9 100644 --- a/src/integrations/GoogleOptimize/browser.js +++ b/src/integrations/GoogleOptimize/browser.js @@ -1,3 +1,5 @@ +/* eslint-disable prefer-rest-params */ +/* eslint-disable no-inner-declarations */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; @@ -44,7 +46,7 @@ class GoogleOptimize { ); window.dataLayer = window.dataLayer || []; function gtag() { - dataLayer.push(arguments); + window.dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', `${this.trackingId}`); diff --git a/src/integrations/GoogleOptimize/index.js b/src/integrations/GoogleOptimize/index.js index e1daa87e6..58b8f9d44 100644 --- a/src/integrations/GoogleOptimize/index.js +++ b/src/integrations/GoogleOptimize/index.js @@ -1,3 +1 @@ -import GoogleOptimize from './browser'; - -export { GoogleOptimize }; +export { default as GoogleOptimize } from './browser'; diff --git a/src/integrations/GoogleTagManager/browser.js b/src/integrations/GoogleTagManager/browser.js index 2308ac610..258f3ec20 100644 --- a/src/integrations/GoogleTagManager/browser.js +++ b/src/integrations/GoogleTagManager/browser.js @@ -1,7 +1,7 @@ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class GoogleTagManager { constructor(config, analytics, destinationInfo) { @@ -22,23 +22,7 @@ class GoogleTagManager { init() { logger.debug('===in init GoogleTagManager==='); - const defaultUrl = `https://www.googletagmanager.com`; - - // ref: https://developers.google.com/tag-platform/tag-manager/server-side/send-data#update_the_gtmjs_source_domain - - window.finalUrl = this.serverUrl ? this.serverUrl : defaultUrl; - - (function (w, d, s, l, i) { - w[l] = w[l] || []; - w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); - const f = d.getElementsByTagName(s)[0]; - const j = d.createElement(s); - const dl = l !== 'dataLayer' ? `&l=${l}` : ''; - j.setAttribute('data-loader', LOAD_ORIGIN); - j.async = true; - j.src = `${window.finalUrl}/gtm.js?id=${i}${dl}`; - f.parentNode.insertBefore(j, f); - })(window, document, 'script', 'dataLayer', this.containerID); + loadNativeSdk(this.containerID, this.serverUrl); } identify(rudderElement) { diff --git a/src/integrations/GoogleTagManager/index.js b/src/integrations/GoogleTagManager/index.js index 863e9aa3d..8e06df24f 100644 --- a/src/integrations/GoogleTagManager/index.js +++ b/src/integrations/GoogleTagManager/index.js @@ -1,3 +1 @@ -import { GoogleTagManager } from './browser'; - -export { GoogleTagManager }; +export { GoogleTagManager } from './browser'; diff --git a/src/integrations/GoogleTagManager/nativeSdkLoader.js b/src/integrations/GoogleTagManager/nativeSdkLoader.js new file mode 100644 index 000000000..c52bb00bc --- /dev/null +++ b/src/integrations/GoogleTagManager/nativeSdkLoader.js @@ -0,0 +1,21 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(containerID, serverUrl) { + const defaultUrl = `https://www.googletagmanager.com`; + // ref: https://developers.google.com/tag-platform/tag-manager/server-side/send-data#update_the_gtmjs_source_domain + + window.finalUrl = serverUrl ? serverUrl : defaultUrl; + (function (w, d, s, l, i) { + w[l] = w[l] || []; + w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); + const f = d.getElementsByTagName(s)[0]; + const j = d.createElement(s); + const dl = l !== 'dataLayer' ? `&l=${l}` : ''; + j.setAttribute('data-loader', LOAD_ORIGIN); + j.async = true; + j.src = `${window.finalUrl}/gtm.js?id=${i}${dl}`; + f.parentNode.insertBefore(j, f); + })(window, document, 'script', 'dataLayer', containerID); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Heap/browser.js b/src/integrations/Heap/browser.js index 7f60edb66..e75394968 100644 --- a/src/integrations/Heap/browser.js +++ b/src/integrations/Heap/browser.js @@ -1,9 +1,8 @@ /* eslint-disable class-methods-use-this */ - import processHeapProperties from './util'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import logger from '../../utils/logUtil'; +import { loadNativeSdk } from './nativeSdkLoader'; class Heap { constructor(config, analytics, destinationInfo) { @@ -26,40 +25,7 @@ class Heap { */ init() { - (window.heap = window.heap || []), - (heap.load = function (e, t) { - (window.heap.appid = e), (window.heap.config = t = t || {}); - const r = document.createElement('script'); - (r.type = 'text/javascript'), - (r.async = !0), - r.setAttribute('data-loader', LOAD_ORIGIN), - (r.src = `https://cdn.heapanalytics.com/js/heap-${e}.js`); - const a = document.getElementsByTagName('script')[0]; - a.parentNode.insertBefore(r, a); - for ( - let n = function (e) { - return function () { - heap.push([e].concat(Array.prototype.slice.call(arguments, 0))); - }; - }, - p = [ - 'addEventProperties', - 'addUserProperties', - 'clearEventProperties', - 'identify', - 'resetIdentity', - 'removeEventProperty', - 'setEventProperties', - 'track', - 'unsetEventProperty', - ], - o = 0; - o < p.length; - o++ - ) - heap[p[o]] = n(p[o]); - }); - window.heap.load(this.appId); + loadNativeSdk(this.appId); } /** diff --git a/src/integrations/Heap/index.js b/src/integrations/Heap/index.js index 1d72677f3..78b7627d1 100644 --- a/src/integrations/Heap/index.js +++ b/src/integrations/Heap/index.js @@ -1,3 +1 @@ -import Heap from './browser'; - -export { Heap }; +export { default as Heap } from './browser'; diff --git a/src/integrations/Heap/nativeSdkLoader.js b/src/integrations/Heap/nativeSdkLoader.js new file mode 100644 index 000000000..1c562bdc8 --- /dev/null +++ b/src/integrations/Heap/nativeSdkLoader.js @@ -0,0 +1,40 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(appId) { + (window.heap = window.heap || []), + (heap.load = function (e, t) { + (window.heap.appid = e), (window.heap.config = t = t || {}); + const r = document.createElement('script'); + (r.type = 'text/javascript'), + (r.async = !0), + r.setAttribute('data-loader', LOAD_ORIGIN), + (r.src = `https://cdn.heapanalytics.com/js/heap-${e}.js`); + const a = document.getElementsByTagName('script')[0]; + a.parentNode.insertBefore(r, a); + for ( + let n = function (e) { + return function () { + heap.push([e].concat(Array.prototype.slice.call(arguments, 0))); + }; + }, + p = [ + 'addEventProperties', + 'addUserProperties', + 'clearEventProperties', + 'identify', + 'resetIdentity', + 'removeEventProperty', + 'setEventProperties', + 'track', + 'unsetEventProperty', + ], + o = 0; + o < p.length; + o++ + ) + heap[p[o]] = n(p[o]); + }); + window.heap.load(appId); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Hotjar/browser.js b/src/integrations/Hotjar/browser.js index 7964b2ab5..455a931aa 100644 --- a/src/integrations/Hotjar/browser.js +++ b/src/integrations/Hotjar/browser.js @@ -1,8 +1,8 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class Hotjar { constructor(config, analytics, destinationInfo) { @@ -22,25 +22,20 @@ class Hotjar { init() { logger.debug('===In init Hotjar==='); - - window.hotjarSiteId = this.siteId; - (function (h, o, t, j, a, r) { - h.hj = - h.hj || - function () { - (h.hj.q = h.hj.q || []).push(arguments); - }; - h._hjSettings = { hjid: h.hotjarSiteId, hjsv: 6 }; - a = o.getElementsByTagName('head')[0]; - r = o.createElement('script'); - r.setAttribute('data-loader', LOAD_ORIGIN); - r.async = 1; - r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; - a.appendChild(r); - })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); + loadNativeSdk(this.siteId); this._ready = true; } + isLoaded() { + logger.debug('===In isLoaded Hotjar==='); + return this._ready; + } + + isReady() { + logger.debug('===In isReady Hotjar==='); + return this._ready; + } + identify(rudderElement) { logger.debug('===In Hotjar identify==='); @@ -65,6 +60,11 @@ class Hotjar { return; } + if (typeof event !== 'string') { + logger.error('Event name should be string'); + return; + } + // event name must not exceed 750 characters and can only contain alphanumeric, underscores, and dashes. // Ref - https://help.hotjar.com/hc/en-us/articles/4405109971095#the-events-api-call window.hj('event', event.replace(/\s\s+/g, ' ').substring(0, 750).replaceAll(' ', '_')); @@ -74,16 +74,6 @@ class Hotjar { logger.debug('===In Hotjar page==='); logger.debug('[Hotjar] page:: method not supported'); } - - isLoaded() { - logger.debug('===In isLoaded Hotjar==='); - return this._ready; - } - - isReady() { - logger.debug('===In isReady Hotjar==='); - return this._ready; - } } export { Hotjar }; diff --git a/src/integrations/Hotjar/index.js b/src/integrations/Hotjar/index.js index ab1193585..c1cf3f0b7 100644 --- a/src/integrations/Hotjar/index.js +++ b/src/integrations/Hotjar/index.js @@ -1,3 +1 @@ -import { Hotjar } from './browser'; - -export { Hotjar }; +export { Hotjar } from './browser'; diff --git a/src/integrations/Hotjar/nativeSdkLoader.js b/src/integrations/Hotjar/nativeSdkLoader.js new file mode 100644 index 000000000..7276a4255 --- /dev/null +++ b/src/integrations/Hotjar/nativeSdkLoader.js @@ -0,0 +1,21 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(siteId) { + window.hotjarSiteId = siteId; + (function (h, o, t, j, a, r) { + h.hj = + h.hj || + function () { + (h.hj.q = h.hj.q || []).push(arguments); + }; + h._hjSettings = { hjid: h.hotjarSiteId, hjsv: 6 }; + a = o.getElementsByTagName('head')[0]; + r = o.createElement('script'); + r.setAttribute('data-loader', LOAD_ORIGIN); + r.async = 1; + r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; + a.appendChild(r); + })(window, document, 'https://static.hotjar.com/c/hotjar-', '.js?sv='); +} + +export { loadNativeSdk }; diff --git a/src/integrations/HubSpot/browser.js b/src/integrations/HubSpot/browser.js index 26d2b8b4e..edf43c71c 100644 --- a/src/integrations/HubSpot/browser.js +++ b/src/integrations/HubSpot/browser.js @@ -1,7 +1,9 @@ +/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import ScriptLoader from '../../utils/ScriptLoader'; import logger from '../../utils/logUtil'; import { NAME } from './constants'; +import { getDefinedTraits } from '../../utils/utils'; class HubSpot { constructor(config, analytics, destinationInfo) { @@ -19,92 +21,75 @@ class HubSpot { } init() { - const hubspotJs = `https://js.hs-scripts.com/${this.hubId}.js`; - ScriptLoader('hubspot-integration', hubspotJs); - + const hubSpotJs = `https://js.hs-scripts.com/${this.hubId}.js`; + ScriptLoader('hubspot-integration', hubSpotJs); logger.debug('===in init HS==='); } + isLoaded() { + logger.debug('in HubSpotAnalyticsManager isLoaded'); + return !!(window._hsq && window._hsq.push !== Array.prototype.push); + } + + isReady() { + return !!(window._hsq && window._hsq.push !== Array.prototype.push); + } + identify(rudderElement) { - logger.debug('in HubspotAnalyticsManager identify'); + logger.debug('in HubSpotAnalyticsManager identify'); - const { traits } = rudderElement.message.context; - const traitsValue = {}; + const { message } = rudderElement; + const { traits } = message.context; - for (const k in traits) { - if (!!Object.getOwnPropertyDescriptor(traits, k) && traits[k]) { - const hubspotkey = k; // k.startsWith("rl_") ? k.substring(3, k.length) : k; - if (Object.prototype.toString.call(traits[k]) == '[object Date]') { - traitsValue[hubspotkey] = traits[k].getTime(); - } else { - traitsValue[hubspotkey] = traits[k]; - } - } + const { userId, email } = getDefinedTraits(message); + + if (!email) { + logger.error('Email is required'); + return; + } + + const traitsValue = {}; + if (userId) { + traitsValue.id = userId; } - /* if (traitsValue["address"]) { - let address = traitsValue["address"]; - //traitsValue.delete(address) - delete traitsValue["address"]; - for (let k in address) { - if (!!Object.getOwnPropertyDescriptor(address, k) && address[k]) { - let hubspotkey = k;//k.startsWith("rl_") ? k.substring(3, k.length) : k; - hubspotkey = hubspotkey == "street" ? "address" : hubspotkey; - traitsValue[hubspotkey] = address[k]; + + if (traits) { + Object.keys(traits).forEach((key) => { + if (Object.prototype.hasOwnProperty.call(traits, key)) { + const value = traits[key]; + traitsValue[key] = value instanceof Date ? value.getTime() : value; } - } - } */ - const userProperties = rudderElement.message.context.user_properties; - for (const k in userProperties) { - if (!!Object.getOwnPropertyDescriptor(userProperties, k) && userProperties[k]) { - const hubspotkey = k; // k.startsWith("rl_") ? k.substring(3, k.length) : k; - traitsValue[hubspotkey] = userProperties[k]; - } + }); } logger.debug(traitsValue); - if (typeof window !== undefined) { - const _hsq = (window._hsq = window._hsq || []); - _hsq.push(['identify', traitsValue]); + if (typeof window !== 'undefined') { + window._hsq.push(['identify', traitsValue]); } } track(rudderElement) { - logger.debug('in HubspotAnalyticsManager track'); - const _hsq = (window._hsq = window._hsq || []); - const eventValue = {}; - eventValue.id = rudderElement.message.event; - if ( - rudderElement.message.properties && - (rudderElement.message.properties.revenue || rudderElement.message.properties.value) - ) { - eventValue.value = - rudderElement.message.properties.revenue || rudderElement.message.properties.value; - } - _hsq.push(['trackEvent', eventValue]); - } + logger.debug('in HubSpotAnalyticsManager track'); - page(rudderElement) { - logger.debug('in HubspotAnalyticsManager page'); - const _hsq = (window._hsq = window._hsq || []); - // logger.debug("path: " + rudderElement.message.properties.path); - // _hsq.push(["setPath", rudderElement.message.properties.path]); - /* _hsq.push(["identify",{ - email: "testtrackpage@email.com" - }]); */ - if (rudderElement.message.properties && rudderElement.message.properties.path) { - _hsq.push(['setPath', rudderElement.message.properties.path]); - } - _hsq.push(['trackPageView']); - } + const { properties, event } = rudderElement.message; + const eventValue = { + name: event, + properties: properties || {}, + }; - isLoaded() { - logger.debug('in hubspot isLoaded'); - return !!(window._hsq && window._hsq.push !== Array.prototype.push); + window._hsq.push(['trackCustomBehavioralEvent', eventValue]); } - isReady() { - return !!(window._hsq && window._hsq.push !== Array.prototype.push); + page(rudderElement) { + logger.debug('in HubSpotAnalyticsManager page'); + + const { properties } = rudderElement.message; + const { path } = properties; + if (path) { + window._hsq.push(['setPath', path]); + } + window._hsq.push(['trackPageView']); } } diff --git a/src/integrations/HubSpot/index.js b/src/integrations/HubSpot/index.js index cf2bfc67f..5e33846b8 100644 --- a/src/integrations/HubSpot/index.js +++ b/src/integrations/HubSpot/index.js @@ -1,3 +1 @@ -import { HubSpot } from './browser'; - -export { HubSpot }; +export { HubSpot } from './browser'; diff --git a/src/integrations/INTERCOM/browser.js b/src/integrations/INTERCOM/browser.js index 84d583449..428c728e9 100644 --- a/src/integrations/INTERCOM/browser.js +++ b/src/integrations/INTERCOM/browser.js @@ -1,9 +1,9 @@ /* eslint-disable class-methods-use-this */ -import md5 from 'md5'; import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; import { flattenJsonPayload } from '../../utils/utils'; +import { processNameField, processCompanyField, processIdentityVerificationProps } from './utils'; class INTERCOM { constructor(config, analytics, destinationInfo) { @@ -12,9 +12,7 @@ class INTERCOM { } this.analytics = analytics; this.name = NAME; - this.API_KEY = config.apiKey; - this.APP_ID = config.appId; - this.MOBILE_APP_ID = config.mobileAppId; + this.appId = config.appId; ({ shouldApplyDeviceModeTransformation: this.shouldApplyDeviceModeTransformation, propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError, @@ -23,150 +21,71 @@ class INTERCOM { } init() { - window.intercomSettings = { - app_id: this.APP_ID, - }; + loadNativeSdk(this.appId); + } - (function () { - const w = window; - const ic = w.Intercom; - if (typeof ic === 'function') { - ic('reattach_activator'); - ic('update', w.intercomSettings); - } else { - const d = document; - var i = function () { - i.c(arguments); - }; - i.q = []; - i.c = function (args) { - i.q.push(args); - }; - w.Intercom = i; - const l = function () { - const s = d.createElement('script'); - s.setAttribute('data-loader', LOAD_ORIGIN); - s.type = 'text/javascript'; - s.async = true; - s.src = `https://widget.intercom.io/widget/${window.intercomSettings.app_id}`; - const x = d.getElementsByTagName('script')[0]; - x.parentNode.insertBefore(s, x); - }; - if (document.readyState === 'complete') { - l(); - window.intercom_code = true; - } else if (w.attachEvent) { - w.attachEvent('onload', l); - window.intercom_code = true; - } else { - w.addEventListener('load', l, false); - window.intercom_code = true; - } - } - })(); + isLoaded() { + return !!window.intercom_code; } - page() { - // Get new messages of the current user - window.Intercom('update'); + isReady() { + return !!window.intercom_code; } identify(rudderElement) { - const rawPayload = {}; - const { context } = rudderElement.message; - - const identityVerificationProps = context.Intercom ? context.Intercom : null; - if (identityVerificationProps != null) { - // user hash - const userHash = context.Intercom.user_hash ? context.Intercom.user_hash : null; - - if (userHash != null) { - rawPayload.user_hash = userHash; - } + const { context, userId } = rudderElement.message; + const { traits, Intercom } = context; - // hide default launcher - const hideDefaultLauncher = context.Intercom.hideDefaultLauncher - ? context.Intercom.hideDefaultLauncher - : null; - - if (hideDefaultLauncher != null) { - rawPayload.hide_default_launcher = hideDefaultLauncher; - } - } - - // populate name if firstname and lastname is populated - // if name is not set - const { firstName, lastName, name } = context.traits; - if (!name && (firstName || lastName)) { - context.traits.name = `${firstName} ${lastName}`.trim(); - } + const identityVerificationProps = Intercom || null; + const rawPayload = processIdentityVerificationProps(identityVerificationProps); + traits.name = processNameField(traits); // map rudderPayload to desired - Object.keys(context.traits).forEach((field) => { - if (context.traits.hasOwnProperty(field)) { - const value = context.traits[field]; + Object.keys(traits).forEach((field) => { + if (Object.prototype.hasOwnProperty.call(traits, field)) { + const value = traits[field]; switch (field) { case 'createdAt': rawPayload.created_at = value; - rawPayload[field] = context.traits[field]; + rawPayload[field] = value; break; case 'anonymousId': rawPayload.user_id = value; - rawPayload[field] = context.traits[field]; + rawPayload[field] = value; break; case 'company': - { - const companies = []; - const company = {}; - // special handling string - if (typeof context.traits[field] === 'string') { - company.company_id = md5(context.traits[field]); - } - const companyFields = - (typeof context.traits[field] === 'object' && Object.keys(context.traits[field])) || - []; - companyFields.forEach((key) => { - if (companyFields.includes(key)) { - if (key != 'id') { - company[key] = context.traits[field][key]; - } else { - company.company_id = context.traits[field][key]; - } - } - }); - - if (typeof context.traits[field] === 'object' && !companyFields.includes('id')) { - company.company_id = md5(company.name); - } - - companies.push(company); - rawPayload.companies = companies; - } + rawPayload.companies = [processCompanyField(value)]; break; case 'avatar': - { - rawPayload.avatar = {}; - rawPayload.avatar.type = 'avatar'; - rawPayload.avatar.image_url = value; - } + rawPayload.avatar = { + type: 'avatar', + image_url: value, + }; break; default: - rawPayload[field] = context.traits[field]; + rawPayload[field] = value; break; } } }); - rawPayload.user_id = rudderElement.message.userId; + rawPayload.user_id = userId; window.Intercom('update', rawPayload); } track(rudderElement) { const rawPayload = {}; const { message } = rudderElement; + const { event, userId, anonymousId, originalTimestamp } = message; + const properties = message?.properties || {}; + + if (event) { + rawPayload.event_name = event; + } - const properties = message.properties ? Object.keys(message.properties) : null; - properties.forEach((property) => { - const value = message.properties[property]; + rawPayload.user_id = userId || anonymousId; + + Object.keys(properties).forEach((property) => { + const value = properties[property]; if (value && typeof value !== 'object' && !Array.isArray(value)) { rawPayload[property] = value; } else if (value && typeof value === 'object') { @@ -174,20 +93,14 @@ class INTERCOM { } }); - if (message.event) { - rawPayload.event_name = message.event; - } - rawPayload.user_id = message.userId ? message.userId : message.anonymousId; - rawPayload.created_at = Math.floor(new Date(message.originalTimestamp).getTime() / 1000); - window.Intercom('trackEvent', rawPayload.event_name, rawPayload); - } + rawPayload.created_at = Math.floor(new Date(originalTimestamp).getTime() / 1000); - isLoaded() { - return !!window.intercom_code; + window.Intercom('trackEvent', rawPayload.event_name, rawPayload); } - isReady() { - return !!window.intercom_code; + page() { + // Get new messages of the current user + window.Intercom('update'); } } diff --git a/src/integrations/INTERCOM/index.js b/src/integrations/INTERCOM/index.js index c5c24d656..f5c0d2d13 100644 --- a/src/integrations/INTERCOM/index.js +++ b/src/integrations/INTERCOM/index.js @@ -1,3 +1 @@ -import { INTERCOM } from './browser'; - -export { INTERCOM }; +export { INTERCOM } from './browser'; diff --git a/src/integrations/INTERCOM/nativeSdkLoader.js b/src/integrations/INTERCOM/nativeSdkLoader.js new file mode 100644 index 000000000..725af591a --- /dev/null +++ b/src/integrations/INTERCOM/nativeSdkLoader.js @@ -0,0 +1,46 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(appId) { + window.intercomSettings = { + app_id: appId, + }; + (function () { + const w = window; + const ic = w.Intercom; + if (typeof ic === 'function') { + ic('reattach_activator'); + ic('update', w.intercomSettings); + } else { + const d = document; + var i = function () { + i.c(arguments); + }; + i.q = []; + i.c = function (args) { + i.q.push(args); + }; + w.Intercom = i; + const l = function () { + const s = d.createElement('script'); + s.setAttribute('data-loader', LOAD_ORIGIN); + s.type = 'text/javascript'; + s.async = true; + s.src = `https://widget.intercom.io/widget/${window.intercomSettings.app_id}`; + const x = d.getElementsByTagName('script')[0]; + x.parentNode.insertBefore(s, x); + }; + if (document.readyState === 'complete') { + l(); + window.intercom_code = true; + } else if (w.attachEvent) { + w.attachEvent('onload', l); + window.intercom_code = true; + } else { + w.addEventListener('load', l, false); + window.intercom_code = true; + } + } + })(); +} + +export { loadNativeSdk }; diff --git a/src/integrations/INTERCOM/utils.js b/src/integrations/INTERCOM/utils.js new file mode 100644 index 000000000..0fdc44ec2 --- /dev/null +++ b/src/integrations/INTERCOM/utils.js @@ -0,0 +1,68 @@ +import md5 from 'md5'; +/** + * Returns payload + * @param {*} props + * @returns + */ +const processIdentityVerificationProps = (props) => { + const payload = {}; + if (props) { + const userHash = props.user_hash || null; + if (userHash) { + payload.user_hash = userHash; + } + + const hideDefaultLauncher = props.hideDefaultLauncher || null; + if (hideDefaultLauncher) { + payload.hide_default_launcher = hideDefaultLauncher; + } + } + + return payload; +}; + +/** + * Returns company payload + * @param {*} companyField + * @returns + */ +const processCompanyField = (companyField) => { + const company = {}; + if (typeof companyField === 'string') { + company.company_id = md5(companyField); + } + + const companyFields = typeof companyField === 'object' ? Object.keys(companyField) : []; + companyFields.forEach((key) => { + if (key !== 'id') { + company[key] = companyField[key]; + } else { + company.company_id = companyField[key]; + } + }); + + if (typeof companyField === 'object' && !companyFields.includes('id')) { + company.company_id = md5(company.name); + } + + return company; +}; + +/** + * Returns name parameter + * @param {*} traits + * @returns + */ +const processNameField = (traits) => { + const { firstName, lastName, name } = traits; + /** + * populate name if firstName and lastName is populated + * if name is not set + */ + if (!name && (firstName || lastName)) { + return `${firstName} ${lastName}`.trim(); + } + return name; +}; + +export { processNameField, processCompanyField, processIdentityVerificationProps }; diff --git a/src/integrations/Iterable/browser.js b/src/integrations/Iterable/browser.js index 9da92e41f..e1bb108e8 100644 --- a/src/integrations/Iterable/browser.js +++ b/src/integrations/Iterable/browser.js @@ -1,18 +1,18 @@ /* eslint-disable class-methods-use-this */ import get from 'get-value'; import Logger from '../../utils/logger'; -import { formPurchaseEventPayload, existsInMapping } from './utils'; - import { - isDefinedAndNotNull, - removeUndefinedAndNullValues, - isNotEmpty, -} from '../../utils/commonUtils'; + formPurchaseEventPayload, + existsInMapping, + extractJWT, + prepareInAppMessagesPayload, +} from './utils'; +import { isNotEmpty, removeUndefinedAndNullValues } from '../../utils/commonUtils'; import { NAME } from './constants'; import ScriptLoader from '../../utils/ScriptLoader'; const logger = new Logger(NAME); - +const iterableWebSdk = '@iterable/web-sdk'; class Iterable { constructor(config, analytics, destinationInfo) { if (analytics.logLevel) { @@ -60,50 +60,39 @@ class Iterable { isLoaded() { logger.debug('===In isLoaded Iterable==='); - return !!window['@iterable/web-sdk']; + return !!window[iterableWebSdk]; } isReady() { logger.debug('===In isReady Iterable==='); - return !!window['@iterable/web-sdk']; + return !!window[iterableWebSdk]; } identify(rudderElement) { logger.debug('===In identify Iterable'); const { message } = rudderElement; - const { integrations } = message; - const userEmail = message.traits?.email || message.context?.traits?.email; - const userId = message.userId; - - async function extractJWT(message) { - if (integrations && integrations.ITERABLE) { - const { jwt_token } = integrations.ITERABLE; - if (isDefinedAndNotNull(jwt_token)) return jwt_token; - } else { - logger.error('The JWT token was not passed, The SDK could not be initialised.'); - return; - } + const { integrations, traits, context, userId } = message; + const userEmail = traits?.email || context?.traits?.email; + + const jwtToken = extractJWT(integrations); + + if (!jwtToken) { + logger.error('The JWT token was not passed, The SDK could not be initialised'); + return; } // Initialize the iterable SDK with the proper apiKey and the passed JWT - let wd = window['@iterable/web-sdk'].initialize(this.apiKey, extractJWT); - switch (this.initialisationIdentifier) { - case 'email': - wd.setEmail(userEmail).then(() => { - logger.debug('userEmail set'); - }); - break; - case 'userId': - wd.setUserID(userId).then(() => { - logger.debug('userId set'); - }); - break; - default: - wd.setEmail(userEmail).then(() => { - logger.debug('userEmail set'); - }); - break; + const wd = window[iterableWebSdk].initialize(this.apiKey, extractJWT); + + if (this.initialisationIdentifier === 'userId') { + wd.setUserID(userId).then(() => { + logger.debug('userId set'); + }); + } else { + wd.setEmail(userEmail).then(() => { + logger.debug('userEmail set'); + }); } /* Available pop-up push notification settings configurable from UI this.animationDuration, @@ -124,30 +113,9 @@ class Iterable { this.closeButtonPosition, */ // Reference : https://github.com/iterable/iterable-web-sdk - let getInAppMessagesPayload = { - count: 20, - animationDuration: Number(this.animationDuration) || 400, - displayInterval: Number(this.displayInterval) || 30000, - onOpenScreenReaderMessage: this.onOpenScreenReaderMessage || undefined, - onOpenNodeToTakeFocus: this.onOpenNodeToTakeFocus || undefined, - packageName: this.packageName || undefined, - rightOffset: this.rightOffset || undefined, - topOffset: this.topOffset || undefined, - bottomOffset: this.bottomOffset || undefined, - handleLinks: this.handleLinks || undefined, - closeButton: { - color: this.closeButtonColor || 'red', - size: this.closeButtonSize || '16px', - topOffset: this.closeButtonColorTopOffset || '4%', - sideOffset: this.closeButtonColorSideOffset || '4%', - iconPath: this.iconPath || undefined, - isRequiredToDismissMessage: this.isRequiredToDismissMessage || undefined, - position: this.closeButtonPosition || 'top-right', - }, - }; - getInAppMessagesPayload = removeUndefinedAndNullValues(getInAppMessagesPayload); - - const { request } = window['@iterable/web-sdk'].getInAppMessages(getInAppMessagesPayload, { + const getInAppMessagesPayload = removeUndefinedAndNullValues(prepareInAppMessagesPayload(this)); + + const { request } = window[iterableWebSdk].getInAppMessages(getInAppMessagesPayload, { display: 'immediate', }); // fetchAppEvents is a class function now available throughout @@ -159,8 +127,8 @@ class Iterable { logger.debug('===In track Iterable==='); const { message } = rudderElement; - const { event } = message; - const eventPayload = removeUndefinedAndNullValues(message.properties); + const { event, properties } = message; + const eventPayload = removeUndefinedAndNullValues(properties); const userEmail = get(message, 'context.traits.email'); const userId = get(message, 'userId'); if (!event) { @@ -174,7 +142,7 @@ class Iterable { this.fetchAppEvents(); // send a track call for getinappMessages if option enabled in config if (this.sendTrackForInapp) { - window['@iterable/web-sdk'] + window[iterableWebSdk] .track({ email: userEmail, userId, @@ -189,7 +157,7 @@ class Iterable { ) { // purchase events const purchaseEventPayload = formPurchaseEventPayload(message); - window['@iterable/web-sdk'].trackPurchase(purchaseEventPayload); + window[iterableWebSdk].trackPurchase(purchaseEventPayload); } else { // custom events if event is not mapped /* fields available for custom track event @@ -207,7 +175,7 @@ class Iterable { // Either email or userId must be passed in to identify the user. // If both are passed in, email takes precedence. logger.debug(`The event ${event} is not mapped in the dashboard, firing a custom event`); - window['@iterable/web-sdk'] + window[iterableWebSdk] .track({ email: userEmail, userId, eventName: event, dataFields: eventPayload }) .then(logger.debug('Track a custom event.')); } diff --git a/src/integrations/Iterable/index.js b/src/integrations/Iterable/index.js index aeb72685f..6e6feeeac 100644 --- a/src/integrations/Iterable/index.js +++ b/src/integrations/Iterable/index.js @@ -1,3 +1 @@ -import Iterable from './browser'; - -export { Iterable }; +export { default as Iterable } from './browser'; diff --git a/src/integrations/Iterable/utils.js b/src/integrations/Iterable/utils.js index 8280ff449..524869c62 100644 --- a/src/integrations/Iterable/utils.js +++ b/src/integrations/Iterable/utils.js @@ -1,4 +1,5 @@ import { getDataFromSource } from '../../utils/utils'; +import { isDefinedAndNotNull } from '../../utils/commonUtils'; const ITEMS_MAPPING = [ { src: 'product_id', dest: 'id' }, @@ -22,10 +23,10 @@ function getMappingObject(properties, mappings) { } function formPurchaseEventPayload(message) { - let purchaseEventPayload = {}; - const { products } = message.properties; - purchaseEventPayload.id = message.properties.order_id || message.properties.checkout_id; - purchaseEventPayload.total = message.properties.total; + const purchaseEventPayload = {}; + const { products, order_id: orderId, checkout_id: checkoutId, total } = message.properties; + purchaseEventPayload.id = orderId || checkoutId; + purchaseEventPayload.total = total; purchaseEventPayload.items = []; const lineItems = []; if (products) { @@ -34,15 +35,25 @@ function formPurchaseEventPayload(message) { lineItems.push(product); }); } else { + const { + product_id: productId, + sku, + price, + quantity, + image_url: imageUrl, + url, + name, + } = message.properties; // if product related info is on properties root - let product = {}; - product.id = message.properties.product_id; - product.sku = message.properties.sku; - product.name = message.properties.name; - product.price = message.properties.price; - product.quantity = message.properties.quantity; - product.imageUrl = message.properties.image_url; - product.url = message.properties.url; + const product = { + url, + sku, + name, + price, + quantity, + imageUrl, + id: productId, + }; lineItems.push(product); } purchaseEventPayload.items = lineItems; @@ -52,9 +63,49 @@ function formPurchaseEventPayload(message) { function existsInMapping(mappedEvents, event) { let mapped = false; mappedEvents.forEach((e) => { - if (e.eventName == event) mapped = true; + if (e.eventName.toLowerCase() === event.toLowerCase()) mapped = true; }); return mapped; } -export { formPurchaseEventPayload, existsInMapping }; +/** + * Returns jwt token + * @param {*} integrations + * @returns + */ +const extractJWT = (integrations) => { + if (integrations?.ITERABLE) { + const { jwt_token: jwtToken } = integrations.ITERABLE; + return isDefinedAndNotNull(jwtToken) ? jwtToken : undefined; + } + return undefined; +}; + +/** + * Returns inappmessages payload + * @param {*} config + * @returns + */ +const prepareInAppMessagesPayload = (config) => ({ + count: 20, + animationDuration: Number(config.animationDuration) || 400, + displayInterval: Number(config.displayInterval) || 30000, + onOpenScreenReaderMessage: config.onOpenScreenReaderMessage, + onOpenNodeToTakeFocus: config.onOpenNodeToTakeFocus, + packageName: config.packageName, + rightOffset: config.rightOffset, + topOffset: config.topOffset, + bottomOffset: config.bottomOffset, + handleLinks: config.handleLinks, + closeButton: { + color: config.closeButtonColor || 'red', + size: config.closeButtonSize || '16px', + topOffset: config.closeButtonColorTopOffset || '4%', + sideOffset: config.closeButtonColorSideOffset || '4%', + iconPath: config.iconPath, + isRequiredToDismissMessage: config.isRequiredToDismissMessage, + position: config.closeButtonPosition || 'top-right', + }, +}); + +export { formPurchaseEventPayload, existsInMapping, extractJWT, prepareInAppMessagesPayload }; diff --git a/src/integrations/June/index.js b/src/integrations/June/index.js index 947c99af7..b44dd0f47 100644 --- a/src/integrations/June/index.js +++ b/src/integrations/June/index.js @@ -1,3 +1 @@ -import June from './browser'; - -export { June }; +export { default as June } from './browser'; diff --git a/src/integrations/Keen/browser.js b/src/integrations/Keen/browser.js index 44df7aa39..813d28468 100644 --- a/src/integrations/Keen/browser.js +++ b/src/integrations/Keen/browser.js @@ -1,3 +1,5 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable no-use-before-define */ import logger from '../../utils/logUtil'; import ScriptLoader from '../../utils/ScriptLoader'; import { NAME } from './constants'; @@ -29,29 +31,39 @@ class Keen { const check = setInterval(checkAndInitKeen.bind(this), 1000); function initKeen(object) { - object.client = new window.KeenTracking({ + const client = new window.KeenTracking({ projectId: object.projectID, writeKey: object.writeKey, }); - return object.client; + return client; } function checkAndInitKeen() { - if (window.KeenTracking !== undefined && window.KeenTracking !== void 0) { + if (typeof window.KeenTracking !== 'undefined') { this.client = initKeen(this); clearInterval(check); } } } + isLoaded() { + logger.debug('in Keen isLoaded'); + return !!(this.client != null); + } + + isReady() { + return !!(this.client != null); + } + identify(rudderElement) { logger.debug('in Keen identify'); - const { traits } = rudderElement.message.context; - const userId = rudderElement.message.userId - ? rudderElement.message.userId - : rudderElement.message.anonymousId; - let properties = rudderElement.message.properties - ? Object.assign(properties, rudderElement.message.properties) - : {}; + + const { message } = rudderElement; + let { userId } = message; + const { context, anonymousId } = message; + const { traits } = context; + let properties = message?.properties || {}; + + userId = userId || anonymousId; properties.user = { userId, traits, @@ -71,10 +83,10 @@ class Keen { page(rudderElement) { logger.debug('in Keen page'); + + let { properties } = rudderElement.message; const pageName = rudderElement.message.name; - const pageCategory = rudderElement.message.properties - ? rudderElement.message.properties.category - : undefined; + const pageCategory = properties.category || undefined; let name = 'Loaded a Page'; if (pageName) { name = `Viewed ${pageName} page`; @@ -82,25 +94,15 @@ class Keen { if (pageCategory && pageName) { name = `Viewed ${pageCategory} ${pageName} page`; } - - let { properties } = rudderElement.message; properties = this.getAddOn(properties); this.client.recordEvent(name, properties); } - isLoaded() { - logger.debug('in Keen isLoaded'); - return !!(this.client != null); - } - - isReady() { - return !!(this.client != null); - } - getAddOn(properties) { + const params = properties; const addOns = []; if (this.ipAddon) { - properties.ip_address = '${keen.ip}'; + params.ip_address = '${keen.ip}'; addOns.push({ name: 'keen:ip_to_geo', input: { @@ -110,7 +112,7 @@ class Keen { }); } if (this.uaAddon) { - properties.user_agent = '${keen.user_agent}'; + params.user_agent = '${keen.user_agent}'; addOns.push({ name: 'keen:ua_parser', input: { @@ -120,7 +122,7 @@ class Keen { }); } if (this.urlAddon) { - properties.page_url = document.location.href; + params.page_url = document.location.href; addOns.push({ name: 'keen:url_parser', input: { @@ -130,8 +132,8 @@ class Keen { }); } if (this.referrerAddon) { - properties.page_url = document.location.href; - properties.referrer_url = document.referrer; + params.page_url = document.location.href; + params.referrer_url = document.referrer; addOns.push({ name: 'keen:referrer_parser', input: { @@ -141,10 +143,10 @@ class Keen { output: 'referrer_info', }); } - properties.keen = { + params.keen = { addons: addOns, }; - return properties; + return params; } } diff --git a/src/integrations/Keen/index.js b/src/integrations/Keen/index.js index 2419bd32f..4341db91d 100644 --- a/src/integrations/Keen/index.js +++ b/src/integrations/Keen/index.js @@ -1,3 +1 @@ -import { Keen } from './browser'; - -export { Keen }; +export { Keen } from './browser'; diff --git a/src/integrations/Kissmetrics/browser.js b/src/integrations/Kissmetrics/browser.js index 85fea1ec9..5c7d4bb94 100644 --- a/src/integrations/Kissmetrics/browser.js +++ b/src/integrations/Kissmetrics/browser.js @@ -1,10 +1,12 @@ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable class-methods-use-this */ import is from 'is'; import extend from '@ndhoule/extend'; import each from 'component-each'; import { getRevenue } from '../../utils/utils'; import logger from '../../utils/logUtil'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadeNativeSdk } from './nativeSdkLoader'; class Kissmetrics { constructor(config, analytics, destinationInfo) { @@ -24,131 +26,106 @@ class Kissmetrics { init() { logger.debug('===in init Kissmetrics==='); - window._kmq = window._kmq || []; - - const _kmk = window._kmk || this.apiKey; - function _kms(u) { - setTimeout(function () { - const d = document; - const f = d.getElementsByTagName('script')[0]; - const s = d.createElement('script'); - s.type = 'text/javascript'; - s.async = true; - s.setAttribute('data-loader', LOAD_ORIGIN); - s.src = u; - f.parentNode.insertBefore(s, f); - }, 1); - } - _kms('//i.kissmetrics.com/i.js'); - _kms(`//scripts.kissmetrics.com/${_kmk}.2.js`); + loadeNativeSdk(this.apiKey); if (this.isEnvMobile()) { window._kmq.push(['set', { 'Mobile Session': 'Yes' }]); } } + isLoaded() { + return is.object(window.KM); + } + + isReady() { + return is.object(window.KM); + } + isEnvMobile() { - return ( - navigator.userAgent.match(/Android/i) || - navigator.userAgent.match(/BlackBerry/i) || - navigator.userAgent.match(/IEMobile/i) || - navigator.userAgent.match(/Opera Mini/i) || - navigator.userAgent.match(/iPad/i) || - navigator.userAgent.match(/iPhone|iPod/i) - ); + const mobileRegex = /android|blackberry|iemobile|opera mini|ipad|iphone|ipod/i; + return mobileRegex.test(navigator.userAgent); } // source : https://github.com/segment-integrations/analytics.js-integration-kissmetrics/blob/master/lib/index.js toUnixTimestamp(date) { - date = new Date(date); - return Math.floor(date.getTime() / 1000); + const newDate = new Date(date); + return Math.floor(newDate.getTime() / 1000); } // source : https://github.com/segment-integrations/analytics.js-integration-kissmetrics/blob/master/lib/index.js clean(obj) { let ret = {}; - for (const k in obj) { - if (obj.hasOwnProperty(k)) { - const value = obj[k]; - if (value === null || typeof value === 'undefined') continue; - - // convert date to unix + Object.keys(obj).forEach((key) => { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const value = obj[key]; if (is.date(value)) { - ret[k] = this.toUnixTimestamp(value); - continue; - } - - // leave boolean as is - if (is.bool(value)) { - ret[k] = value; - continue; - } - - // leave numbers as is - if (is.number(value)) { - ret[k] = value; - continue; - } - - // convert non objects to strings - logger.debug(value.toString()); - if (value.toString() !== '[object Object]') { - ret[k] = value.toString(); - continue; - } - - // json - // must flatten including the name of the original trait/property - const nestedObj = {}; - nestedObj[k] = value; - const flattenedObj = this.flatten(nestedObj, { safe: true }); + // convert date to unix + ret[key] = this.toUnixTimestamp(value); + } else if (is.bool(value) || is.number(value)) { + // leave boolean and numbers as is + ret[key] = value; + } else if (value.toString() !== '[object Object]') { + // convert non objects to strings + logger.debug(value.toString()); + ret[key] = value.toString(); + } else { + // json + // must flatten including the name of the original trait/property + const nestedObj = {}; + nestedObj[key] = value; + const flattenedObj = this.flatten(nestedObj, { safe: true }); + + // stringify arrays inside nested object to be consistent with top level behavior of arrays + Object.keys(flattenedObj).forEach((objKey) => { + if (is.array(flattenedObj[objKey])) { + flattenedObj[objKey] = flattenedObj[objKey].toString(); + } + }); - // stringify arrays inside nested object to be consistent with top level behavior of arrays - for (const key in flattenedObj) { - if (is.array(flattenedObj[key])) { - flattenedObj[key] = flattenedObj[key].toString(); - } + ret = extend(ret, flattenedObj); + delete ret[key]; } - - ret = extend(ret, flattenedObj); - delete ret[k]; } - } + }); return ret; } // source : https://github.com/segment-integrations/analytics.js-integration-kissmetrics/blob/master/lib/index.js flatten(target, opts) { - opts = opts || {}; + const options = opts || {}; - const delimiter = opts.delimiter || '.'; - let { maxDepth } = opts; + const delimiter = options.delimiter || '.'; + let { maxDepth } = options; + const { safe } = options; let currentDepth = 1; const output = {}; + // eslint-disable-next-line consistent-return function step(object, prev) { + // eslint-disable-next-line no-restricted-syntax for (const key in object) { - if (object.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(object, key)) { const value = object[key]; - const isarray = opts.safe && is.array(value); + const isarray = safe && is.array(value); const type = Object.prototype.toString.call(value); const isobject = type === '[object Object]' || type === '[object Array]'; const arr = []; const newKey = prev ? prev + delimiter + key : key; - if (!opts.maxDepth) { + if (!options.maxDepth) { maxDepth = currentDepth + 1; } - for (const keys in value) { - if (value.hasOwnProperty(keys)) { - arr.push(keys); + Object.keys(value).forEach((valueKey) => { + if (Object.prototype.hasOwnProperty.call(value, valueKey)) { + arr.push(valueKey); } - } + }); - if (!isarray && isobject && arr.length && currentDepth < maxDepth) { + if (!isarray && isobject && arr.length > 0 && currentDepth < maxDepth) { + // eslint-disable-next-line no-plusplus ++currentDepth; return step(value, newKey); } @@ -166,7 +143,7 @@ class Kissmetrics { // source : https://github.com/segment-integrations/analytics.js-integration-kissmetrics/blob/master/lib/index.js prefix(event, properties) { const prefixed = {}; - each(properties, function (key, val) { + each(properties, (key, val) => { if (key === 'Billing Amount') { prefixed[key] = val; } else if (key === 'revenue') { @@ -180,23 +157,22 @@ class Kissmetrics { } identify(rudderElement) { - logger.debug('in Kissmetrics identify'); - const traits = this.clean(rudderElement.message.context.traits); - const userId = - rudderElement.message.userId && rudderElement.message.userId != '' - ? rudderElement.message.userId - : undefined; - - if (userId) { + logger.debug('in KissMetrics identify'); + + const { userId, context } = rudderElement.message; + const { traits } = context; + const userTraits = this.clean(traits); + + if (userId && userId !== '') { window._kmq.push(['identify', userId]); } if (traits) { - window._kmq.push(['set', traits]); + window._kmq.push(['set', userTraits]); } } track(rudderElement) { - logger.debug('in Kissmetrics track'); + logger.debug('in KissMetrics track'); const { event } = rudderElement.message; let properties = JSON.parse(JSON.stringify(rudderElement.message.properties)); @@ -236,11 +212,11 @@ class Kissmetrics { } page(rudderElement) { - logger.debug('in Kissmetrics page'); + logger.debug('in KissMetrics page'); + + let { properties } = rudderElement.message; const pageName = rudderElement.message.name; - const pageCategory = rudderElement.message.properties - ? rudderElement.message.properties.category - : undefined; + const pageCategory = properties.category || undefined; let name = 'Loaded a Page'; if (pageName) { name = `Viewed ${pageName} page`; @@ -249,7 +225,6 @@ class Kissmetrics { name = `Viewed ${pageCategory} ${pageName} page`; } - let { properties } = rudderElement.message; if (this.prefixProperties) { properties = this.prefix('Page', properties); } @@ -258,28 +233,21 @@ class Kissmetrics { } alias(rudderElement) { - const prev = rudderElement.message.previousId; - const { userId } = rudderElement.message; - window._kmq.push(['alias', userId, prev]); + logger.debug('in KissMetrics alias'); + + const { previousId, userId } = rudderElement.message; + window._kmq.push(['alias', userId, previousId]); } group(rudderElement) { - const { groupId } = rudderElement.message; - let groupTraits = rudderElement.message.traits; - groupTraits = this.prefix('Group', groupTraits); + logger.debug('in KissMetrics group'); + + const { groupId, traits } = rudderElement.message; + const groupTraits = this.prefix('Group', traits); if (groupId) { groupTraits['Group - id'] = groupId; } window._kmq.push(['set', groupTraits]); - logger.debug('in Kissmetrics group'); - } - - isLoaded() { - return is.object(window.KM); - } - - isReady() { - return is.object(window.KM); } } diff --git a/src/integrations/Kissmetrics/index.js b/src/integrations/Kissmetrics/index.js index caead5d82..2637e2150 100644 --- a/src/integrations/Kissmetrics/index.js +++ b/src/integrations/Kissmetrics/index.js @@ -1,3 +1 @@ -import { Kissmetrics } from './browser'; - -export { Kissmetrics }; +export { Kissmetrics } from './browser'; diff --git a/src/integrations/Kissmetrics/nativeSdkLoader.js b/src/integrations/Kissmetrics/nativeSdkLoader.js new file mode 100644 index 000000000..57f209c4b --- /dev/null +++ b/src/integrations/Kissmetrics/nativeSdkLoader.js @@ -0,0 +1,23 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadeNativeSdk(apiKey) { + window._kmq = window._kmq || []; + + const _kmk = window._kmk || apiKey; + function _kms(u) { + setTimeout(function () { + const d = document; + const f = d.getElementsByTagName('script')[0]; + const s = d.createElement('script'); + s.type = 'text/javascript'; + s.async = true; + s.setAttribute('data-loader', LOAD_ORIGIN); + s.src = u; + f.parentNode.insertBefore(s, f); + }, 1); + } + _kms('//i.kissmetrics.com/i.js'); + _kms(`//scripts.kissmetrics.com/${_kmk}.2.js`); +} + +export { loadeNativeSdk }; diff --git a/src/integrations/Klaviyo/browser.js b/src/integrations/Klaviyo/browser.js index 6e49efd64..79672a45b 100644 --- a/src/integrations/Klaviyo/browser.js +++ b/src/integrations/Klaviyo/browser.js @@ -141,43 +141,41 @@ class Klaviyo { track(rudderElement) { const { message } = rudderElement; - if (message.properties) { - // ecomm events - let event = get(message, 'event'); - event = event ? event.trim().toLowerCase() : event; - if (this.ecomEvents.includes(event)) { - let payload = ecommEventPayload(this.eventNameMapping[event], message); - const eventName = this.eventNameMapping[event]; - let customProperties = {}; - customProperties = extractCustomFields( - message, - customProperties, - ['properties'], - this.ecomExclusionKeys, - ); - if (isNotEmpty(customProperties)) { - payload = { ...payload, ...customProperties }; - } - if (isNotEmpty(payload)) { - window._learnq.push(['track', eventName, payload]); - } - } else { - const propsPayload = message.properties; - if (propsPayload.revenue) { - propsPayload.$value = propsPayload.revenue; - delete propsPayload.revenue; - } - window._learnq.push(['track', message.event, propsPayload]); + const { event } = message; + const properties = message?.properties || {}; + + if (this.ecomEvents.includes(event)) { + let payload = ecommEventPayload(this.eventNameMapping[event], message); + const eventName = this.eventNameMapping[event]; + const customProperties = extractCustomFields( + message, + {}, + ['properties'], + this.ecomExclusionKeys, + ); + if (isNotEmpty(customProperties)) { + payload = { ...payload, ...customProperties }; + } + if (isNotEmpty(payload)) { + window._learnq.push(['track', eventName, payload]); } - } else window._learnq.push(['track', message.event]); + } else { + const propsPayload = properties; + if (propsPayload.revenue) { + propsPayload.$value = propsPayload.revenue; + delete propsPayload.revenue; + } + window._learnq.push(['track', event, propsPayload]); + } } page(rudderElement) { const { message } = rudderElement; + const properties = message?.properties || {}; if (this.sendPageAsTrack) { let eventName; - if (message.properties && message.properties.category && message.name) { - eventName = `Viewed ${message.properties.category} ${message.name} page`; + if (properties?.category && message.name) { + eventName = `Viewed ${properties.category} ${message.name} page`; } else if (message.name) { eventName = `Viewed ${message.name} page`; } else { diff --git a/src/integrations/Klaviyo/index.js b/src/integrations/Klaviyo/index.js index 58ee717a4..c728a47eb 100644 --- a/src/integrations/Klaviyo/index.js +++ b/src/integrations/Klaviyo/index.js @@ -1,3 +1 @@ -import Klaviyo from './browser'; - -export { Klaviyo }; +export { default as Klaviyo } from './browser'; diff --git a/src/integrations/Klaviyo/util.js b/src/integrations/Klaviyo/util.js index c452bcea5..d6096fc24 100644 --- a/src/integrations/Klaviyo/util.js +++ b/src/integrations/Klaviyo/util.js @@ -2,20 +2,40 @@ import get from 'get-value'; import { isNotEmpty, removeUndefinedAndNullValues } from '../../utils/commonUtils'; +const categoryKey = 'properties.categories'; + const itemsPayload = (item) => { - const itemObj = {}; - itemObj.ProductID = item.product_id; - itemObj.SKU = item.sku; - itemObj.ProductName = item.name; - itemObj.Quantity = item.quantity; - itemObj.ItemPrice = item.price; - itemObj.RowTotal = item.total; - itemObj.ProductURL = item.url; - itemObj.ImageURL = item.image_url; - itemObj.ProductCategories = item.categories; + const itemObj = { + ProductID: item.product_id, + SKU: item.sku, + ProductName: item.name, + Quantity: item.quantity, + ItemPrice: item.price, + RowTotal: item.total, + ProductURL: item.url, + ImageURL: item.image_url, + ProductCategories: item.categories, + }; return itemObj; }; +/** + * Returns items array + * @param {*} items + * @returns + */ +const prepareItemsArray = (items) => { + const itemArr = []; + items.forEach((element) => { + let item = itemsPayload(element); + item = removeUndefinedAndNullValues(item); + if (isNotEmpty(item)) { + itemArr.push(item); + } + }); + return itemArr; +}; + const ecommEventPayload = (event, message) => { let payload = {}; switch (event) { @@ -28,7 +48,7 @@ const ecommEventPayload = (event, message) => { payload.Brand = get(message, 'properties.brand'); payload.Price = get(message, 'properties.price'); payload.CompareAtPrice = get(message, 'properties.compare_at_price'); - payload.Categories = get(message, 'properties.categories'); + payload.Categories = get(message, categoryKey); break; } case 'Added to Cart': { @@ -40,38 +60,22 @@ const ecommEventPayload = (event, message) => { payload.AddedItemURL = get(message, 'properties.url'); payload.AddedItemPrice = get(message, 'properties.price'); payload.AddedItemQuantity = get(message, 'properties.quantity'); - payload.AddedItemCategories = get(message, 'properties.categories'); + payload.AddedItemCategories = get(message, categoryKey); payload.ItemNames = get(message, 'properties.item_names'); payload.CheckoutURL = get(message, 'properties.checkout_url'); if (message.properties.items && Array.isArray(message.properties.items)) { - const itemArr = []; - message.properties.items.forEach((element) => { - let item = itemsPayload(element); - item = removeUndefinedAndNullValues(item); - if (isNotEmpty(item)) { - itemArr.push(item); - } - }); - payload.Items = itemArr; + payload.Items = prepareItemsArray(message.properties.items); } break; } case 'Started Checkout': { payload.$event_id = get(message, 'properties.order_id'); payload.$value = get(message, 'properties.value'); - payload.Categories = get(message, 'properties.categories'); + payload.Categories = get(message, categoryKey); payload.CheckoutURL = get(message, 'properties.checkout_url'); payload.ItemNames = get(message, 'item_names'); if (message.properties.items && Array.isArray(message.properties.items)) { - const itemArr = []; - message.properties.items.forEach((element) => { - let item = itemsPayload(element); - item = removeUndefinedAndNullValues(item); - if (isNotEmpty(item)) { - itemArr.push(item); - } - }); - payload.Items = itemArr; + payload.Items = prepareItemsArray(message.properties.items); } break; } diff --git a/src/integrations/LaunchDarkly/index.js b/src/integrations/LaunchDarkly/index.js index a1da2b494..7a206e20b 100644 --- a/src/integrations/LaunchDarkly/index.js +++ b/src/integrations/LaunchDarkly/index.js @@ -1,3 +1 @@ -import LaunchDarkly from './browser'; - -export { LaunchDarkly }; +export { default as LaunchDarkly } from './browser'; diff --git a/src/integrations/LaunchDarkly/utils.js b/src/integrations/LaunchDarkly/utils.js index 84c0e5c1a..4635a0815 100644 --- a/src/integrations/LaunchDarkly/utils.js +++ b/src/integrations/LaunchDarkly/utils.js @@ -1,6 +1,7 @@ const createUser = (message, anonymousUsersSharedKey = undefined) => { - const user = {}; - user.key = message.userId || message.anonymousId; + const user = { + key: message.userId || message.anonymousId, + }; const { traits } = message.context; if (traits.anonymous !== undefined) { user.anonymous = traits.anonymous; diff --git a/src/integrations/Lemnisk/browser.js b/src/integrations/Lemnisk/browser.js index 931f09ada..9fbf401d3 100644 --- a/src/integrations/Lemnisk/browser.js +++ b/src/integrations/Lemnisk/browser.js @@ -1,10 +1,7 @@ -/* eslint-disable no-var */ -/* eslint-disable no-param-reassign */ -/* eslint-disable func-names */ /* eslint-disable class-methods-use-this */ import Logger from '../../utils/logger'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; const logger = new Logger(NAME); class Lemnisk { @@ -25,34 +22,7 @@ class Lemnisk { init() { logger.debug('===in init Lemnisk Marketing Automation==='); - (function (window, tag, o, a, r) { - var methods = ['init', 'page', 'track', 'identify']; - window.lmSMTObj = window.lmSMTObj || []; - - for (var i = 0; i < methods.length; i++) { - lmSMTObj[methods[i]] = (function (methodName) { - return function () { - lmSMTObj.push([methodName].concat(Array.prototype.slice.call(arguments))); - }; - })(methods[i]); - } - // eslint-disable-next-line no-param-reassign - a = o.getElementsByTagName('head')[0]; - // eslint-disable-next-line no-param-reassign - r = o.createElement('script'); - r.setAttribute('data-loader', LOAD_ORIGIN); - r.type = 'text/javascript'; - r.async = 1; - r.src = tag; - a.appendChild(r); - })( - window, - document.location.protocol === 'https:' - ? `https://cdn25.lemnisk.co/ssp/st/${this.accountId}.js` - : `http://cdn25.lemnisk.co/ssp/st/${this.accountId}.js`, - document, - ); - window.lmSMTObj.init(this.sdkWriteKey); + loadNativeSdk(this.accountId, this.sdkWriteKey); } isLoaded() { @@ -67,16 +37,17 @@ class Lemnisk { identify(rudderElement) { logger.debug('===In Lemnisk Marketing Automation identify==='); + const userId = rudderElement.message.userId || rudderElement.message.anonymousId; if (!userId) { logger.debug('[Lemnisk] identify:: user id is required'); return; } - // disabling eslint as message will be there iinn any case - // eslint-disable-next-line no-unsafe-optional-chaining - const { traits } = rudderElement.message?.context; - traits['isRudderEvents'] = true; - window.lmSMTObj.identify(rudderElement.message.userId, traits); + const { message } = rudderElement; + const context = message?.context || {}; + const traits = context?.traits || {}; + traits.isRudderEvents = true; + window.lmSMTObj.identify(userId, traits); } track(rudderElement) { @@ -88,10 +59,10 @@ class Lemnisk { return; } if (properties) { - properties['isRudderEvents'] = true; + properties.isRudderEvents = true; window.lmSMTObj.track(event, properties); } else { - window.lmSMTObj.track(event,{'isRudderEvents': true}); + window.lmSMTObj.track(event, { isRudderEvents: true }); } } @@ -99,15 +70,15 @@ class Lemnisk { logger.debug('===In Lemnisk Marketing Automation page==='); const { name, properties } = rudderElement.message; if (name && !properties) { - window.lmSMTObj.page(name,{'isRudderEvents': true}); + window.lmSMTObj.page(name, { isRudderEvents: true }); } else if (!name && properties) { - properties['isRudderEvents'] = true; + properties.isRudderEvents = true; window.lmSMTObj.page(properties); } else if (name && properties) { - properties['isRudderEvents'] = true; + properties.isRudderEvents = true; window.lmSMTObj.page(name, properties); } else { - window.lmSMTObj.page({'isRudderEvents': true}); + window.lmSMTObj.page({ isRudderEvents: true }); } } } diff --git a/src/integrations/Lemnisk/index.js b/src/integrations/Lemnisk/index.js index 35d6b85a0..162702e10 100644 --- a/src/integrations/Lemnisk/index.js +++ b/src/integrations/Lemnisk/index.js @@ -1,3 +1 @@ -import Lemnisk from './browser'; - -export { Lemnisk }; +export { default as Lemnisk } from './browser'; diff --git a/src/integrations/Lemnisk/nativeSdkLoader.js b/src/integrations/Lemnisk/nativeSdkLoader.js new file mode 100644 index 000000000..2eac41caa --- /dev/null +++ b/src/integrations/Lemnisk/nativeSdkLoader.js @@ -0,0 +1,34 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(accountId, sdkWriteKey) { + (function (window, tag, o, a, r) { + var methods = ['init', 'page', 'track', 'identify']; + window.lmSMTObj = window.lmSMTObj || []; + + for (var i = 0; i < methods.length; i++) { + lmSMTObj[methods[i]] = (function (methodName) { + return function () { + lmSMTObj.push([methodName].concat(Array.prototype.slice.call(arguments))); + }; + })(methods[i]); + } + // eslint-disable-next-line no-param-reassign + a = o.getElementsByTagName('head')[0]; + // eslint-disable-next-line no-param-reassign + r = o.createElement('script'); + r.setAttribute('data-loader', LOAD_ORIGIN); + r.type = 'text/javascript'; + r.async = 1; + r.src = tag; + a.appendChild(r); + })( + window, + document.location.protocol === 'https:' + ? `https://cdn25.lemnisk.co/ssp/st/${accountId}.js` + : `http://cdn25.lemnisk.co/ssp/st/${accountId}.js`, + document, + ); + window.lmSMTObj.init(sdkWriteKey); +} + +export { loadNativeSdk }; diff --git a/src/integrations/LinkedInInsightTag/browser.js b/src/integrations/LinkedInInsightTag/browser.js index 4e1e16415..53e19455b 100644 --- a/src/integrations/LinkedInInsightTag/browser.js +++ b/src/integrations/LinkedInInsightTag/browser.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import ScriptLoader from '../../utils/ScriptLoader'; diff --git a/src/integrations/LinkedInInsightTag/index.js b/src/integrations/LinkedInInsightTag/index.js index 53ef7c13c..c1b468af2 100644 --- a/src/integrations/LinkedInInsightTag/index.js +++ b/src/integrations/LinkedInInsightTag/index.js @@ -1,3 +1 @@ -import LinkedInInsightTag from './browser'; - -export { LinkedInInsightTag }; +export { default as LinkedInInsightTag } from './browser'; diff --git a/src/integrations/LiveChat/browser.js b/src/integrations/LiveChat/browser.js index 4d7570e2e..3abf3e7b8 100644 --- a/src/integrations/LiveChat/browser.js +++ b/src/integrations/LiveChat/browser.js @@ -7,6 +7,7 @@ import { recordingLiveChatEvents } from './util'; import { isObject } from '../../utils/utils'; import { flattenJson } from '../../utils/commonUtils'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class LiveChat { constructor(config, analytics, destinationInfo) { @@ -29,42 +30,7 @@ class LiveChat { init() { logger.debug('===in init Livechat==='); - window.__lc = window.__lc || {}; - window.__lc.license = this.licenseId; - (function (n, t, c) { - function i(n) { - return e._h ? e._h.apply(null, n) : e._q.push(n); - } - var e = { - _q: [], - _h: null, - _v: '2.0', - on: function () { - i(['on', c.call(arguments)]); - }, - once: function () { - i(['once', c.call(arguments)]); - }, - off: function () { - i(['off', c.call(arguments)]); - }, - get: function () { - if (!e._h) throw new Error("[LiveChatWidget] You can't use getters before load."); - return i(['get', c.call(arguments)]); - }, - call: function () { - i(['call', c.call(arguments)]); - }, - init: function () { - var n = t.createElement('script'); - (n.async = !0), - (n.type = 'text/javascript'), - (n.src = 'https://cdn.livechatinc.com/tracking.js'), - t.head.appendChild(n); - }, - }; - !n.__lc.asyncInit && e.init(), (n.LiveChatWidget = n.LiveChatWidget || e); - })(window, document, [].slice); + loadNativeSdk(this.licenseId); } isLoaded() { diff --git a/src/integrations/LiveChat/index.js b/src/integrations/LiveChat/index.js index 64f66678a..5969faa1f 100644 --- a/src/integrations/LiveChat/index.js +++ b/src/integrations/LiveChat/index.js @@ -1,3 +1 @@ -import LiveChat from './browser'; - -export { LiveChat }; +export { default as LiveChat } from './browser'; diff --git a/src/integrations/LiveChat/nativeSdkLoader.js b/src/integrations/LiveChat/nativeSdkLoader.js new file mode 100644 index 000000000..1e92986a5 --- /dev/null +++ b/src/integrations/LiveChat/nativeSdkLoader.js @@ -0,0 +1,40 @@ +function loadNativeSdk(licenseId) { + window.__lc = window.__lc || {}; + window.__lc.license = licenseId; + (function (n, t, c) { + function i(n) { + return e._h ? e._h.apply(null, n) : e._q.push(n); + } + var e = { + _q: [], + _h: null, + _v: '2.0', + on: function () { + i(['on', c.call(arguments)]); + }, + once: function () { + i(['once', c.call(arguments)]); + }, + off: function () { + i(['off', c.call(arguments)]); + }, + get: function () { + if (!e._h) throw new Error("[LiveChatWidget] You can't use getters before load."); + return i(['get', c.call(arguments)]); + }, + call: function () { + i(['call', c.call(arguments)]); + }, + init: function () { + var n = t.createElement('script'); + (n.async = !0), + (n.type = 'text/javascript'), + (n.src = 'https://cdn.livechatinc.com/tracking.js'), + t.head.appendChild(n); + }, + }; + !n.__lc.asyncInit && e.init(), (n.LiveChatWidget = n.LiveChatWidget || e); + })(window, document, [].slice); +} + +export { loadNativeSdk }; diff --git a/src/integrations/LiveChat/util.js b/src/integrations/LiveChat/util.js index a8e7ba390..7bcfa0ccf 100644 --- a/src/integrations/LiveChat/util.js +++ b/src/integrations/LiveChat/util.js @@ -1,3 +1,4 @@ +/* eslint-disable func-names */ import { getHashFromArray } from '../../utils/commonUtils'; const integrationContext = { @@ -57,9 +58,9 @@ function recordingLiveChatEvents( 'availability_changed', 'customer_status_changed', 'rich_message_button_clicked', - ].forEach(function (eventName) { + ].forEach((eventName) => { if (userDefinedEventsList.includes(eventName)) { - api.on(eventName, function (payload) { + api.on(eventName, () => { makeACall(standardEventsMap, eventName, updateEventNames, analytics); }); } diff --git a/src/integrations/Lotame/LotameStorage.js b/src/integrations/Lotame/LotameStorage.js index 5fdd20f95..08cef6167 100644 --- a/src/integrations/Lotame/LotameStorage.js +++ b/src/integrations/Lotame/LotameStorage.js @@ -1,4 +1,3 @@ -import logger from '../../utils/logUtil'; import Storage from '../../utils/storage'; const defaults = { @@ -7,7 +6,7 @@ const defaults = { class LotameStorage { constructor() { - this.storage = Storage; // new Storage(); + this.storage = Storage; } setLotameSynchTime(value) { diff --git a/src/integrations/Lotame/browser.js b/src/integrations/Lotame/browser.js index 014d9285c..2ffa99df9 100644 --- a/src/integrations/Lotame/browser.js +++ b/src/integrations/Lotame/browser.js @@ -16,7 +16,7 @@ class Lotame { this.dspUrlSettingsPixel = config.dspUrlSettingsPixel; this.dspUrlSettingsIframe = config.dspUrlSettingsIframe; this.mappings = {}; - config.mappings.forEach((mapping) => { + config?.mappings?.forEach((mapping) => { const { key } = mapping; const { value } = mapping; this.mappings[key] = value; @@ -33,6 +33,15 @@ class Lotame { window.LOTAME_SYNCH_CALLBACK = () => {}; } + isLoaded() { + logger.debug('in Lotame isLoaded'); + return true; + } + + isReady() { + return true; + } + addPixel(source, width, height) { logger.debug(`Adding pixel for :: ${source}`); @@ -98,14 +107,16 @@ class Lotame { } compileUrl(map, url) { + let compiledUrl = url; Object.keys(map).forEach((key) => { - if (map.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(map, key)) { + const value = map[key]; const replaceKey = `{{${key}}}`; const regex = new RegExp(replaceKey, 'gi'); - url = url.replace(regex, map[key]); + compiledUrl = compiledUrl.replace(regex, value); } }); - return url; + return compiledUrl; } identify(rudderElement) { @@ -114,10 +125,6 @@ class Lotame { this.syncPixel(userId); } - track(rudderElement) { - logger.debug('track not supported for lotame'); - } - page(rudderElement) { logger.debug('in Lotame page'); @@ -160,15 +167,6 @@ class Lotame { const difference = Math.floor((currentTime - lastSynchedTime) / (1000 * 3600 * 24)); return difference >= 7; } - - isLoaded() { - logger.debug('in Lotame isLoaded'); - return true; - } - - isReady() { - return true; - } } export { Lotame }; diff --git a/src/integrations/Lotame/index.js b/src/integrations/Lotame/index.js index 479d6a40f..ced9116c6 100644 --- a/src/integrations/Lotame/index.js +++ b/src/integrations/Lotame/index.js @@ -1,3 +1 @@ -import { Lotame } from './browser'; - -export { Lotame }; +export { Lotame } from './browser'; diff --git a/src/integrations/Lytics/browser.js b/src/integrations/Lytics/browser.js index d7c41cb61..67eae4c60 100644 --- a/src/integrations/Lytics/browser.js +++ b/src/integrations/Lytics/browser.js @@ -1,26 +1,7 @@ -/* eslint-disable no-param-reassign */ /* eslint-disable class-methods-use-this */ -// disabled these for Lytics js tag -/* eslint-disable no-plusplus */ -/* eslint-disable block-scoped-var */ -/* eslint-disable no-sequences */ -/* eslint-disable yoda */ -/* eslint-disable prefer-spread */ -// disabling these eslint which are caused by the Lytics js tag - -/* eslint-disable prefer-rest-params */ -/* eslint-disable no-var */ -/* eslint-disable vars-on-top */ -/* eslint-disable no-return-assign */ -/* eslint-disable no-shadow */ -/* eslint-disable no-void */ -/* eslint-disable no-unused-expressions */ -/* eslint-disable one-var */ -/* eslint-disable lines-around-directive */ -/* eslint-disable strict */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class Lytics { constructor(config, analytics, destinationInfo) { @@ -42,78 +23,7 @@ class Lytics { } loadLyticsScript() { - (function () { - 'use strict'; - var o = window.jstag || (window.jstag = {}), - r = []; - function n(e) { - o[e] = function () { - for (var n = arguments.length, t = new Array(n), i = 0; i < n; i++) t[i] = arguments[i]; - r.push([e, t]); - }; - } - n('send'), - n('mock'), - n('identify'), - n('pageView'), - n('unblock'), - n('getid'), - n('setid'), - n('loadEntity'), - n('getEntity'), - n('on'), - n('once'), - n('call'), - (o.loadScript = function (n, t, i) { - var e = document.createElement('script'); - (e.async = !0), - (e.src = n), - (e.onload = t), - (e.onerror = i), - e.setAttribute('data-loader', LOAD_ORIGIN); - var o = document.getElementsByTagName('script')[0], - r = (o && o.parentNode) || document.head || document.body, - c = o || r.lastChild; - return null != c ? r.insertBefore(e, c) : r.appendChild(e), this; - }), - (o.init = function n(t) { - return ( - (this.config = t), - this.loadScript(t.src, function () { - if (o.init === n) throw new Error('Load error!'); - // eslint-disable-next-line no-unused-expressions - o.init(o.config), - // eslint-disable-next-line func-names - (function () { - for (var n = 0; n < r.length; n++) { - var t = r[n][0], - i = r[n][1]; - o[t].apply(o, i); - } - r = void 0; - })(); - }), - this - ); - }); - })(); - - // Define config and initialize Lytics tracking tag. - window.jstag.init({ - loadid: this.loadid, - blocked: this.blockload, - stream: this.stream, - sessecs: 1800, - src: - document.location.protocol === 'https:' - ? `https://c.lytics.io/api/tag/${this.accountId}/latest.min.js` - : `http://c.lytics.io/api/tag/${this.accountId}/latest.min.js`, - pageAnalysis: { - dataLayerPull: { - disabled: true, - }, - }, - }); + loadNativeSdk(this.loadid, this.blockload, this.stream, this.accountId); } init() { @@ -134,44 +44,44 @@ class Lytics { identify(rudderElement) { logger.debug('in Lytics identify'); - // eslint-disable-next-line camelcase - const user_id = rudderElement.message.userId || rudderElement.message.anonymousId; + const userId = rudderElement.message.userId || rudderElement.message.anonymousId; const { traits } = rudderElement.message.context; - const payload = { user_id, ...traits }; - this.handleName(payload); + let payload = { user_id: userId, ...traits }; + payload = this.handleName(payload); window.jstag.send(this.stream, payload); } page(rudderElement) { logger.debug('in Lytics page'); const { properties } = rudderElement.message; - const payload = { event: rudderElement.message.name, ...properties }; - this.handleName(payload); + let payload = { event: rudderElement.message.name, ...properties }; + payload = this.handleName(payload); window.jstag.pageView(this.stream, payload); } track(rudderElement) { logger.debug('in Lytics track'); const { properties } = rudderElement.message; - const payload = { _e: rudderElement.message.event, ...properties }; - this.handleName(payload); + let payload = { _e: rudderElement.message.event, ...properties }; + payload = this.handleName(payload); window.jstag.send(this.stream, payload); } handleName(payload) { + const params = payload; this.forFirstName.forEach((key) => { - if (payload[key]) { - payload.first_name = payload[key]; - delete payload[key]; + if (params[key]) { + params.first_name = payload[key]; + delete params[key]; } }); this.forLastName.forEach((key) => { - if (payload[key]) { - payload.last_name = payload[key]; - delete payload[key]; + if (params[key]) { + params.last_name = payload[key]; + delete params[key]; } }); - return payload; + return params; } } export default Lytics; diff --git a/src/integrations/Lytics/index.js b/src/integrations/Lytics/index.js index 050417404..d50c2b43a 100644 --- a/src/integrations/Lytics/index.js +++ b/src/integrations/Lytics/index.js @@ -1,3 +1 @@ -import Lytics from './browser'; - -export { Lytics }; +export { default as Lytics } from './browser'; diff --git a/src/integrations/Lytics/nativeSdkLoader.js b/src/integrations/Lytics/nativeSdkLoader.js new file mode 100644 index 000000000..ceb63300e --- /dev/null +++ b/src/integrations/Lytics/nativeSdkLoader.js @@ -0,0 +1,77 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(loadid, blockload, stream, accountId) { + (function () { + 'use strict'; + var o = window.jstag || (window.jstag = {}), + r = []; + function n(e) { + o[e] = function () { + for (var n = arguments.length, t = new Array(n), i = 0; i < n; i++) t[i] = arguments[i]; + r.push([e, t]); + }; + } + n('send'), + n('mock'), + n('identify'), + n('pageView'), + n('unblock'), + n('getid'), + n('setid'), + n('loadEntity'), + n('getEntity'), + n('on'), + n('once'), + n('call'), + (o.loadScript = function (n, t, i) { + var e = document.createElement('script'); + (e.async = !0), + (e.src = n), + (e.onload = t), + (e.onerror = i), + e.setAttribute('data-loader', LOAD_ORIGIN); + var o = document.getElementsByTagName('script')[0], + r = (o && o.parentNode) || document.head || document.body, + c = o || r.lastChild; + return null != c ? r.insertBefore(e, c) : r.appendChild(e), this; + }), + (o.init = function n(t) { + return ( + (this.config = t), + this.loadScript(t.src, function () { + if (o.init === n) throw new Error('Load error!'); + // eslint-disable-next-line no-unused-expressions + o.init(o.config), + // eslint-disable-next-line func-names + (function () { + for (var n = 0; n < r.length; n++) { + var t = r[n][0], + i = r[n][1]; + o[t].apply(o, i); + } + r = void 0; + })(); + }), + this + ); + }); + })(); + // Define config and initialize Lytics tracking tag. + window.jstag.init({ + loadid: loadid, + blocked: blockload, + stream: stream, + sessecs: 1800, + src: + document.location.protocol === 'https:' + ? `https://c.lytics.io/api/tag/${accountId}/latest.min.js` + : `http://c.lytics.io/api/tag/${accountId}/latest.min.js`, + pageAnalysis: { + dataLayerPull: { + disabled: true, + }, + }, + }); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Matomo/browser.js b/src/integrations/Matomo/browser.js index 03d133b2d..d73903614 100644 --- a/src/integrations/Matomo/browser.js +++ b/src/integrations/Matomo/browser.js @@ -1,7 +1,6 @@ +/* eslint-disable no-unused-vars */ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ -// Research Spec: https://www.notion.so/rudderstacks/Matomo-c5a76c7838b94190a3374887b94a176e - import logger from '../../utils/logUtil'; import { NAME } from './constants'; @@ -12,7 +11,7 @@ import { checkCustomDimensions, } from './util'; import { getHashFromArrayWithDuplicate } from '../../utils/commonUtils'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class Matomo { constructor(config, analytics, destinationInfo) { @@ -56,20 +55,7 @@ class Matomo { } loadScript() { - window._paq = window._paq || []; - (function (serverUrl, siteId) { - let u = serverUrl; - window._paq.push(['setTrackerUrl', `${u}matomo.php`]); - window._paq.push(['setSiteId', siteId]); - const d = document; - const g = d.createElement('script'); - const s = d.getElementsByTagName('script')[0]; - g.async = true; - u = u.replace('https://', ''); - g.src = `//cdn.matomo.cloud/${u}matomo.js`; - g.setAttribute('data-loader', LOAD_ORIGIN); - s.parentNode.insertBefore(g, s); - })(this.serverUrl, this.siteId); + loadNativeSdk(this.serverUrl, this.siteId); } init() { @@ -82,65 +68,58 @@ class Matomo { return !!(window._paq && window._paq.push !== Array.prototype.push); } + pushValues(condition, values) { + if (condition) { + window._paq.push(values); + } + } + isReady() { logger.debug('===In isReady Matomo==='); - // Dasboard Event Settings + // Dashboard Event Settings if (window._paq && window._paq.push !== Array.prototype.push) { // Scans the entire DOM for all content blocks and tracks all impressions once the DOM ready event has been triggered. - if (this.trackAllContentImpressions) { - window._paq.push(['trackAllContentImpressions']); - } + this.pushValues(this.trackAllContentImpressions, ['trackAllContentImpressions']); // Scans the entire DOM for all content blocks when the page is loaded. Tracks an impression only if a content block is actually visible. - if (this.trackVisibleContentImpressions && this.checkOnScroll && this.timeIntervalInMs) { - window._paq.push([ - 'trackVisibleContentImpressions', - this.checkOnScroll, - this.timeIntervalInMs, - ]); - } + this.pushValues( + this.trackVisibleContentImpressions && this.checkOnScroll && this.timeIntervalInMs, + ['trackVisibleContentImpressions', this.checkOnScroll, this.timeIntervalInMs], + ); // Logs all content blocks found within a page to the console. - if (this.logAllContentBlocksOnPage) { - window._paq.push(['logAllContentBlocksOnPage']); - } + this.pushValues(this.logAllContentBlocksOnPage, ['logAllContentBlocksOnPage']); // Installs a heart beat timer to send additional requests to Matomo to measure the time spent in the visit. - if (this.enableHeartBeatTimer && this.activeTimeInseconds) { - window._paq.push(['enableHeartBeatTimer', this.activeTimeInseconds]); - } + this.pushValues(this.enableHeartBeatTimer && this.activeTimeInseconds, [ + 'enableHeartBeatTimer', + this.activeTimeInseconds, + ]); // Installs link tracking on all applicable link elements. - if (this.enableLinkTracking) { - window._paq.push(['enableLinkTracking', true]); - } + this.pushValues(this.enableLinkTracking, ['enableLinkTracking', true]); // Disables page performance tracking. - if (this.disablePerformanceTracking) { - window._paq.push(['disablePerformanceTracking']); - } + this.pushValues(this.disablePerformanceTracking, ['disablePerformanceTracking']); // Enables cross domain linking. It is useful if you own multiple domains and would like to track all the actions and - // pageviews of a specific visitor into the same visit. - if (this.enableCrossDomainLinking) { - window._paq.push(['enableCrossDomainLinking']); - } + // page views of a specific visitor into the same visit. + this.pushValues(this.enableCrossDomainLinking, ['enableCrossDomainLinking']); // Sets the cross domain linking timeout (in seconds). - if (this.setCrossDomainLinkingTimeout && this.timeout) { - window._paq.push(['setCrossDomainLinkingTimeout', this.timeout]); - } + this.pushValues(this.setCrossDomainLinkingTimeout && this.timeout, [ + 'setCrossDomainLinkingTimeout', + this.timeout, + ]); // Gets the query parameter to append to the links to handle cross domain linking. - if (this.getCrossDomainLinkingUrlParameter) { - window._paq.push(['getCrossDomainLinkingUrlParameter']); - } + this.pushValues(this.getCrossDomainLinkingUrlParameter, [ + 'getCrossDomainLinkingUrlParameter', + ]); // Disables the browser feature detection. - if (this.disableBrowserFeatureDetection) { - window._paq.push(['disableBrowserFeatureDetection']); - } + this.pushValues(this.disableBrowserFeatureDetection, ['disableBrowserFeatureDetection']); } return false; } diff --git a/src/integrations/Matomo/index.js b/src/integrations/Matomo/index.js index 4a19eb02c..cd26e4896 100644 --- a/src/integrations/Matomo/index.js +++ b/src/integrations/Matomo/index.js @@ -1,4 +1 @@ -import Matomo from './browser'; - -// eslint-disable-next-line import/prefer-default-export -export { Matomo }; +export { default as Matomo } from './browser'; diff --git a/src/integrations/Matomo/nativeSdkLoader.js b/src/integrations/Matomo/nativeSdkLoader.js new file mode 100644 index 000000000..51264d631 --- /dev/null +++ b/src/integrations/Matomo/nativeSdkLoader.js @@ -0,0 +1,20 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(serverUrl, siteId) { + window._paq = window._paq || []; + (function (serverUrl, siteId) { + let u = serverUrl; + window._paq.push(['setTrackerUrl', `${u}matomo.php`]); + window._paq.push(['setSiteId', siteId]); + const d = document; + const g = d.createElement('script'); + const s = d.getElementsByTagName('script')[0]; + g.async = true; + u = u.replace('https://', ''); + g.src = `//cdn.matomo.cloud/${u}matomo.js`; + g.setAttribute('data-loader', LOAD_ORIGIN); + s.parentNode.insertBefore(g, s); + })(serverUrl, siteId); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Matomo/util.js b/src/integrations/Matomo/util.js index 8e7301a17..fa0347356 100644 --- a/src/integrations/Matomo/util.js +++ b/src/integrations/Matomo/util.js @@ -1,16 +1,18 @@ +/* eslint-disable no-underscore-dangle */ import each from '@ndhoule/each'; import logger from '../../utils/logUtil'; import { getHashFromArray } from '../../utils/commonUtils'; import { NAME } from './constants'; +const userParameterRequiredErrorMessage = 'User parameter (sku or product_id) is required'; + /** If any event name matches with the goals list provided by the dashboard * we will call the trackGoal with the id provided in the mapping. * @param {} event * @param {} goalListMap */ const goalIdMapping = (event, goalListMap, message) => { - let revenue; - revenue = message.properties?.revenue; + const revenue = message.properties?.revenue; each((val, key) => { if (key === event) { val.forEach((v) => { @@ -21,6 +23,140 @@ const goalIdMapping = (event, goalListMap, message) => { }, goalListMap); }; +/** + * Matomo trackLink call + * @param {*} properties + * @param {*} context + * @returns + */ +const trackLink = (properties, context) => { + const { url: linkUrl, linkType } = properties; + const url = linkUrl || context?.page?.url || undefined; + + if (!url) { + logger.error('URL is missing'); + return; + } + + if (linkType !== 'link' && linkType !== 'download') { + logger.error("linkType can only be ('link' or 'download')"); + return; + } + + window._paq.push(['trackLink', url, linkType]); +}; + +/** + * Matomo trackSiteSearch call + * @param {*} properties + * @param {*} context + */ +const trackSiteSearch = (properties, context) => { + let { keyword } = properties; + const { category, resultsCount, search } = properties; + keyword = keyword || search || context?.page?.search; + window._paq.push(['trackSiteSearch', keyword, category, resultsCount]); +}; + +/** + * Matomo trackContentImpressionsWithinNode call + * @param {*} properties + * @returns + */ +const trackContentImpressionsWithinNode = (properties) => { + const domId = properties.domId || properties.dom_id; + + if (!domId) { + logger.error('domId is missing'); + return; + } + + window._paq.push(['trackContentImpressionsWithinNode', document.getElementById(domId)]); +}; + +/** + * Matomo trackContentInteractionNode call + * @param {*} properties + */ +const trackContentInteractionNode = (properties) => { + const { contentInteraction, dom_id: dId } = properties; + const domId = properties.domId || dId; + window._paq.push([ + 'trackContentInteractionNode', + document.getElementById(domId), + contentInteraction, + ]); +}; + +/** + * Matomo trackContentImpression call + * @param {*} properties + */ +const trackContentImpression = (properties) => { + const { contentName, contentPiece, contentTarget } = properties; + window._paq.push(['trackContentImpression', contentName, contentPiece, contentTarget]); +}; + +/** + * Matomo trackContentInteraction call + * @param {*} properties + */ +const trackContentInteraction = (properties) => { + const { contentInteraction, contentName, contentPiece, contentTarget } = properties; + window._paq.push([ + 'trackContentInteraction', + contentInteraction, + contentName, + contentPiece, + contentTarget, + ]); +}; + +/** + * Making a call for each standard events + * @param {*} value + * @param {*} message + */ +const makeACall = (value, message) => { + const { context } = message; + const properties = message?.properties || {}; + + value.forEach((v) => { + switch (v) { + case 'trackLink': + trackLink(properties, context); + break; + + case 'trackSiteSearch': + trackSiteSearch(properties, context); + break; + + case 'ping': + window._paq.push(['ping']); + break; + + case 'trackContentImpressionsWithinNode': + trackContentImpressionsWithinNode(properties); + break; + + case 'trackContentInteractionNode': + trackContentInteractionNode(properties); + break; + + case 'trackContentImpression': + trackContentImpression(properties); + break; + + case 'trackContentInteraction': + trackContentInteraction(properties); + break; + + default: + break; + } + }); +}; + /** Mapping Standard Events If any event name matches with the standard events list provided in the dashboard @param {} event @@ -30,250 +166,215 @@ const goalIdMapping = (event, goalListMap, message) => { const standardEventsMapping = (event, standardEventsMap, message) => { each((val, key) => { if (key === event) { - let url; - let linkType; - let keyword; - let category; - let resultsCount; - let contentInteraction; - let contentName; - let contentPiece; - let contentTarget; - let domId; - const { properties } = message; - val.forEach((v) => { - switch (v) { - case 'trackLink': - if (properties) url = properties.url; - if (!url) url = message.context ? message.context.page.url : undefined; - - if (properties) linkType = properties.linkType; - if (linkType !== 'link' && linkType !== 'download') { - logger.error("linkType can only be ('link' or 'download')"); - break; - } - window._paq.push(['trackLink', url, linkType]); - break; - - case 'trackSiteSearch': - if (properties) { - category = properties.category; - resultsCount = properties.resultsCount; - } - keyword = properties.keyword || properties.search || message.context?.page?.search; - - window._paq.push(['trackSiteSearch', keyword, category, resultsCount]); - break; - - case 'ping': - window._paq.push(['ping']); - break; - - case 'trackContentImpressionsWithinNode': - if (properties) domId = properties.domId || properties.dom_id; - window._paq.push(['trackContentImpressionsWithinNode', document.getElementById(domId)]); - break; - - case 'trackContentInteractionNode': - if (properties) { - domId = properties.domId || properties.dom_id; - contentInteraction = properties.contentInteraction; - } - window._paq.push([ - 'trackContentInteractionNode', - document.getElementById(domId), - contentInteraction, - ]); - break; - - case 'trackContentImpression': - if (properties) { - contentName = properties.contentName; - contentPiece = properties.contentPiece; - contentTarget = properties.contentTarget; - } - window._paq.push(['trackContentImpression', contentName, contentPiece, contentTarget]); - break; - - case 'trackContentInteraction': - if (properties) { - contentInteraction = properties.contentInteraction; - contentName = properties.contentName; - contentPiece = properties.contentPiece; - contentTarget = properties.contentTarget; - } - window._paq.push([ - 'trackContentInteraction', - contentInteraction, - contentName, - contentPiece, - contentTarget, - ]); - break; - - default: - break; - } - }); + makeACall(val, message); } }, standardEventsMap); }; +/** + * setEcommerceView matomo event + * @param {*} properties + * @returns + */ +const handleSetEcommerceView = (properties) => { + const { + sku, + product_id: productId, + name: productName, + category: categoryName, + price, + } = properties; + const productSKU = sku || productId; + + if (!productSKU) { + logger.error(userParameterRequiredErrorMessage); + return; + } + + if (!productName) { + logger.error('User parameter name is required'); + return; + } + + if (!categoryName) { + logger.error('User parameter category is required'); + return; + } + + if (!price) { + logger.error('User parameter price is required'); + return; + } + + window._paq.push(['setEcommerceView', productSKU, productName, categoryName, price]); + window._paq.push(['trackPageView']); +}; + +/** + * addEcommerceItem matomo event + * @param {*} properties + * @returns + */ +const handleAddEcommerceItem = (properties) => { + const { sku, product_id: productId, name, category, price, quantity } = properties; + if (!sku && !productId) { + logger.error(userParameterRequiredErrorMessage); + return; + } + + window._paq.push(['addEcommerceItem', sku || productId, name, category, price, quantity]); +}; + +/** + * removeEcommerceItem matomo event + * @param {*} properties + * @returns + */ +const handleRemoveEcommerceItem = (properties) => { + const { sku, product_id: productId } = properties; + const productSKU = sku || productId; + if (!productSKU) { + logger.error(userParameterRequiredErrorMessage); + return; + } + + window._paq.push(['removeEcommerceItem', productSKU]); +}; + +/** + * Returns grand and sub total values + * @param {*} total + * @param {*} products + * @param {*} shipping + * @param {*} tax + * @param {*} subT + * @returns + */ +const calculateGrandAndSubTotal = (total, products, shipping, tax, subT) => { + if (total) { + return { grandTotal: parseFloat(total), subT }; + } + + /** grandTotal is a mandatory field + * If grandTotal is not found inside the properties, we are calculating it manually + */ + let grandTotal = 0; + let subTotal = subT; + + if (Array.isArray(products) && products.length > 0) { + products.forEach((product) => { + if (product.price && product.quantity) { + grandTotal += parseFloat(product.price) * parseFloat(product.quantity); + } + }); + } + + /** subTotal is not a listed property in "Order Completed" event + * if user doesn't provide this property, then we are calculating it from grandTotal + * ref: https://matomo.org/faq/reports/analyse-ecommerce-reporting-to-improve-your-sales/#conversions-overview + */ + if (!subTotal) { + subTotal = grandTotal; + } + + // ref: https://matomo.org/faq/reports/analyse-ecommerce-reporting-to-improve-your-sales/#conversions-overview + if (shipping) { + grandTotal += parseFloat(shipping); + } + if (tax) { + grandTotal += parseFloat(tax); + } + + return { grandTotal, subTotal }; +}; + +/** + * trackEcommerceOrder matomo event + * @param {*} properties + * @param {*} products + * @returns + */ +const handleTrackEcommerceOrder = (properties, products) => { + const { order_id: oId, total, revenue, tax, shipping, discount, subTotal: subT } = properties; + let { orderId } = properties; + orderId = orderId || oId; + const { grandTotal, subTotal } = calculateGrandAndSubTotal( + total || revenue, + products, + shipping, + tax, + subT, + ); + + if (!oId && !orderId) { + logger.error('User parameter order_id is required'); + return; + } + + if (!grandTotal) { + logger.error('User parameter (total or revenue) is required'); + return; + } + + window._paq.push(['trackEcommerceOrder', orderId, grandTotal, subTotal, tax, shipping, discount]); +}; + +/** + * trackEcommerceCartUpdate matomo event + * @param {*} properties + */ +const handleTrackEcommerceCartUpdate = (properties) => { + const grandTotal = properties.total || properties.revenue; + window._paq.push(['trackEcommerceCartUpdate', grandTotal]); +}; + +/** + * trackEvent matomo event + * @param {*} event + * @param {*} properties + * @returns + */ +const handleGenericTrackEvent = (event, properties) => { + const { category, action, value } = properties; + if (!category) { + logger.error('User parameter category is required'); + return; + } + if (!action) { + logger.error('User parameter action is required'); + return; + } + + window._paq.push(['trackEvent', category, action, event, value]); +}; + /** Mapping Ecommerce Events * @param {} event * @param {} message */ const ecommerceEventsMapping = (event, message) => { - let productSKU; - let productName; - let categoryName; - let price; - let quantity; - let orderId; - let grandTotal; - let subTotal; - let tax; - let shipping; - let discount; - let category; - let action; - let name; - let value; - let products; // used for TRACK_ECOMMERCE_ORDER event - - const { properties } = message; + let products; + + const properties = message?.properties || {}; if (properties) { products = properties.products; } switch (event) { case 'SET_ECOMMERCE_VIEW': - if (properties) { - productSKU = properties.sku || properties.product_id; - productName = properties.name; - categoryName = properties.category; - price = properties.price; - } - if (!productSKU) { - logger.error('User parameter (sku or product_id) is required'); - break; - } - - if (!productName) { - logger.error('User parameter name is required'); - break; - } - - if (!categoryName) { - logger.error('User parameter category is required'); - break; - } - - if (!price) { - logger.error('User parameter price is required'); - break; - } - window._paq.push(['setEcommerceView', productSKU, productName, categoryName, price]); - window._paq.push(['trackPageView']); + handleSetEcommerceView(properties); break; case 'ADD_ECOMMERCE_ITEM': - if (properties) { - productSKU = properties.sku || properties.product_id; - productName = properties.name; - categoryName = properties.category; - price = properties.price; - quantity = properties.quantity; - } - if (!productSKU) { - logger.error('User parameter (sku or product_id) is required'); - break; - } - - window._paq.push([ - 'addEcommerceItem', - productSKU, - productName, - categoryName, - price, - quantity, - ]); + handleAddEcommerceItem(properties); break; case 'REMOVE_ECOMMERCE_ITEM': - if (properties) { - productSKU = properties.sku || properties.product_id; - } - if (!productSKU) { - logger.error('User parameter (sku or product_id) is required'); - break; - } - window._paq.push(['removeEcommerceItem', productSKU]); + handleRemoveEcommerceItem(properties); break; case 'TRACK_ECOMMERCE_ORDER': - if (properties) { - orderId = properties.order_id || properties.orderId; - grandTotal = properties.total || properties.revenue; - subTotal = properties.subTotal; - tax = properties.tax; - shipping = properties.shipping; - discount = properties.discount; - } - if (!orderId) { - logger.error('User parameter order_id is required'); - break; - } - - /** grandTotal is a mandatory field - * If grandTotal is not found inside the properties, we are calculating it manually - */ - if (!grandTotal) { - if (products && Array.isArray(products) && products.length > 0) { - grandTotal = 0; - // iterating through the products array and calculating grandTotal - for (let product = 0; product < products.length; product++) { - if (product.price && product.quantity) { - if (typeof price === 'string') price = parseFloat(price); - if (typeof quantity === 'string') quantity = parseFloat(quantity); - grandTotal += product.price * product.quantity; - } - } - } - - /** subTotal is not a listed property in "Order Completed" event - * if user doesn't provide this property, then we are calculating it from grandTotal - * ref: https://matomo.org/faq/reports/analyse-ecommerce-reporting-to-improve-your-sales/#conversions-overview - */ - if (!subTotal) { - subTotal = grandTotal; - } - - // ref: https://matomo.org/faq/reports/analyse-ecommerce-reporting-to-improve-your-sales/#conversions-overview - if (shipping) { - if (typeof shipping === 'string') shipping = parseFloat(shipping); - grandTotal += shipping; - } - if (tax) { - if (typeof tax === 'string') tax = parseFloat(tax); - grandTotal += tax; - } - } - - if (!grandTotal) { - logger.error('User parameter (total or revenue) is required'); - break; - } - - window._paq.push([ - 'trackEcommerceOrder', - orderId, - grandTotal, - subTotal, - tax, - shipping, - discount, - ]); + handleTrackEcommerceOrder(properties, products); break; case 'CLEAR_ECOMMERCE_CART': @@ -281,28 +382,12 @@ const ecommerceEventsMapping = (event, message) => { break; case 'TRACK_ECOMMERCE_CART_UPDATE': - if (properties) grandTotal = properties.total || properties.revenue; - window._paq.push(['trackEcommerceCartUpdate', grandTotal]); + handleTrackEcommerceCartUpdate(properties); break; default: // Generic track Event - if (properties) { - category = properties.category; - action = properties.action; - value = properties.value; - } - if (!category) { - logger.error('User parameter category is required'); - break; - } - if (!action) { - logger.error('User parameter action is required'); - break; - } - - name = message.event; - window._paq.push(['trackEvent', category, action, name, value]); + handleGenericTrackEvent(message.event, properties); break; } }; diff --git a/src/integrations/MicrosoftClarity/browser.js b/src/integrations/MicrosoftClarity/browser.js index 0843720db..a7020ce21 100644 --- a/src/integrations/MicrosoftClarity/browser.js +++ b/src/integrations/MicrosoftClarity/browser.js @@ -1,7 +1,8 @@ -/* eslint-disable */ +/* eslint-disable class-methods-use-this */ + import { NAME } from './constants'; import Logger from '../../utils/logger'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; const logger = new Logger(NAME); class MicrosoftClarity { @@ -21,22 +22,7 @@ class MicrosoftClarity { } loadScript() { - (function (c, l, a, r, i, t, y) { - c[a] = - c[a] || - function () { - (c[a].q = c[a].q || []).push(arguments); - }; - t = l.createElement(r); - t.async = 1; - t.src = 'https://www.clarity.ms/tag/' + i; - t.setAttribute('data-loader', LOAD_ORIGIN); - y = l.getElementsByTagName(r)[0]; - y.parentNode.insertBefore(t, y); - })(window, document, 'clarity', 'script', this.projectId); - if (this.cookieConsent) { - window.clarity('consent'); - } + loadNativeSdk(this.cookieConsent, this.projectId); } init() { diff --git a/src/integrations/MicrosoftClarity/index.js b/src/integrations/MicrosoftClarity/index.js index 56457ead0..deb024fa1 100644 --- a/src/integrations/MicrosoftClarity/index.js +++ b/src/integrations/MicrosoftClarity/index.js @@ -1,3 +1 @@ -import MicrosoftClarity from './browser'; - -export { MicrosoftClarity }; +export { default as MicrosoftClarity } from './browser'; diff --git a/src/integrations/MicrosoftClarity/nativeSdkLoader.js b/src/integrations/MicrosoftClarity/nativeSdkLoader.js new file mode 100644 index 000000000..fcce233df --- /dev/null +++ b/src/integrations/MicrosoftClarity/nativeSdkLoader.js @@ -0,0 +1,22 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(cookieConsent, projectId) { + (function (c, l, a, r, i, t, y) { + c[a] = + c[a] || + function () { + (c[a].q = c[a].q || []).push(arguments); + }; + t = l.createElement(r); + t.async = 1; + t.src = 'https://www.clarity.ms/tag/' + i; + t.setAttribute('data-loader', LOAD_ORIGIN); + y = l.getElementsByTagName(r)[0]; + y.parentNode.insertBefore(t, y); + })(window, document, 'clarity', 'script', projectId); + if (cookieConsent) { + window.clarity('consent'); + } +} + +export { loadNativeSdk }; diff --git a/src/integrations/Mixpanel/browser.js b/src/integrations/Mixpanel/browser.js index 560a0d774..67cc58bf3 100644 --- a/src/integrations/Mixpanel/browser.js +++ b/src/integrations/Mixpanel/browser.js @@ -1,41 +1,19 @@ -/* eslint-disable no-continue */ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable block-scoped-var */ -/* eslint-disable no-use-before-define */ -/* eslint-disable no-multi-assign */ -/* eslint-disable prefer-template */ -/* eslint-disable prefer-rest-params */ -/* eslint-disable no-undef */ -/* eslint-disable camelcase */ -/* eslint-disable no-shadow */ -/* eslint-disable no-plusplus */ -/* eslint-disable prefer-destructuring */ -/* eslint-disable no-param-reassign */ -/* eslint-disable eqeqeq */ -/* eslint-disable no-unused-expressions */ -/* eslint-disable func-names */ -/* eslint-disable no-var */ -/* eslint-disable yoda */ -/* eslint-disable no-nested-ternary */ -/* eslint-disable vars-on-top */ -/* eslint-disable one-var */ -/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import get from 'get-value'; import logger from '../../utils/logUtil'; import { pick, removeUndefinedAndNullValues, isNotEmpty } from '../../utils/commonUtils'; import { - parseConfigArray, - inverseObjectArrays, - extractTraits, - unionArrays, - extendTraits, mapTraits, + unionArrays, formatTraits, + extendTraits, + extractTraits, + parseConfigArray, + inverseObjectArrays, + getConsolidatedPageCalls, } from './util'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class Mixpanel { constructor(config, analytics, destinationInfo) { @@ -53,12 +31,7 @@ class Mixpanel { this.eventIncrements = config.eventIncrements || []; this.propIncrements = config.propIncrements || []; this.sourceName = config.sourceName; - this.consolidatedPageCalls = Object.prototype.hasOwnProperty.call( - config, - 'consolidatedPageCalls', - ) - ? config.consolidatedPageCalls - : true; + this.consolidatedPageCalls = getConsolidatedPageCalls(config); this.trackCategorizedPages = config.trackCategorizedPages || false; this.trackNamedPages = config.trackNamedPages || false; this.groupKeySettings = config.groupKeySettings || []; @@ -87,71 +60,7 @@ class Mixpanel { init() { logger.debug('===in init Mixpanel==='); // eslint-disable-next-line no-var - (function (f, b) { - if (!b.__SV) { - var e, g, i, h; - window.mixpanel = b; - b._i = []; - b.init = function (e, f, c) { - function g(a, d) { - var b = d.split('.'); - 2 == b.length && ((a = a[b[0]]), (d = b[1])); - a[d] = function () { - a.push([d].concat(Array.prototype.slice.call(arguments, 0))); - }; - } - var a = b; - 'undefined' !== typeof c ? (a = b[c] = []) : (c = 'mixpanel'); - a.people = a.people || []; - a.toString = function (a) { - var d = 'mixpanel'; - 'mixpanel' !== c && (d += '.' + c); - a || (d += ' (stub)'); - return d; - }; - a.people.toString = function () { - return a.toString(1) + '.people (stub)'; - }; - i = - 'disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking start_batch_senders people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove'.split( - ' ', - ); - for (h = 0; h < i.length; h++) g(a, i[h]); - var j = 'set set_once union unset remove delete'.split(' '); - a.get_group = function () { - function b(c) { - d[c] = function () { - call2_args = arguments; - call2 = [c].concat(Array.prototype.slice.call(call2_args, 0)); - a.push([e, call2]); - }; - } - for ( - var d = {}, e = ['get_group'].concat(Array.prototype.slice.call(arguments, 0)), c = 0; - c < j.length; - c++ - ) - b(j[c]); - return d; - }; - b._i.push([e, f, c]); - }; - b.__SV = 1.2; - e = f.createElement('script'); - e.type = 'text/javascript'; - e.async = !0; - e.setAttribute('data-loader', LOAD_ORIGIN); - e.src = - 'undefined' !== typeof MIXPANEL_CUSTOM_LIB_URL - ? MIXPANEL_CUSTOM_LIB_URL - : 'file:' === f.location.protocol && - '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js'.match(/^\/\//) - ? 'https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js' - : '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js'; - g = f.getElementsByTagName('script')[0]; - g.parentNode.insertBefore(e, g); - } - })(document, window.mixpanel || []); + loadNativeSdk(); const options = { cross_subdomain_cookie: this.crossSubdomainCookie || false, secure_cookie: this.secureCookie || false, @@ -159,7 +68,7 @@ class Mixpanel { if (this.persistence !== 'none') { options.persistence_name = this.persistence; } - if (this.dataResidency == 'eu') { + if (this.dataResidency === 'eu') { // https://developer.mixpanel.com/docs/implement-mixpanel#section-implementing-mixpanel-in-the-european-union-eu options.api_host = 'https://api-eu.mixpanel.com'; } @@ -170,12 +79,12 @@ class Mixpanel { logger.debug('in Mixpanel isLoaded'); logger.debug(!!(window.mixpanel && window.mixpanel.config)); window.mixpanel.register({ mp_lib: 'Rudderstack: web' }); - return !!(window.mixpanel && window.mixpanel.config); + return !!window?.mixpanel?.config; } isReady() { logger.debug('in Mixpanel isReady'); - return !!(window.mixpanel && window.mixpanel.config); + return !!window?.mixpanel?.config; } /** @@ -209,22 +118,22 @@ class Mixpanel { // determine which traits to union to existing properties and which to set as new properties const traitsToUnion = {}; const traitsToSet = {}; - for (const key in traits) { - if (!traits.hasOwnProperty(key)) continue; - - const trait = traits[key]; - if (Array.isArray(trait) && trait.length > 0) { - traitsToUnion[key] = trait; - // since mixpanel doesn't offer a union method for super properties we have to do it manually by retrieving the existing list super property - // from mixpanel and manually unioning to it ourselves - const existingTrait = window.mixpanel.get_property(key); - if (existingTrait && Array.isArray(existingTrait)) { - traits[key] = unionArrays(existingTrait, trait); + Object.keys(traits).forEach((trait) => { + if (Object.prototype.hasOwnProperty.call(traits, trait)) { + const value = traits[trait]; + if (Array.isArray(value) && value.length > 0) { + traitsToUnion[trait] = value; + // since mixpanel doesn't offer a union method for super properties we have to do it manually by retrieving the existing list super property + // from mixpanel and manually unioning to it ourselves + const existingTrait = window.mixpanel.get_property(trait); + if (existingTrait && Array.isArray(existingTrait)) { + traits[trait] = unionArrays(existingTrait, value); + } + } else { + traitsToSet[trait] = value; } - } else { - traitsToSet[key] = trait; } - } + }); if (this.setAllTraitsByDefault) { window.mixpanel.register(traits); @@ -291,7 +200,7 @@ class Mixpanel { const propIncrements = parseConfigArray(this.propIncrements, 'property'); const event = get(message, 'event'); const revenue = get(message, 'properties.revenue') || get(message, 'properties.total'); - const sourceName = this.sourceName; + const { sourceName, people } = this; let props = get(message, 'properties'); if (isNotEmpty(props)) { props = inverseObjectArrays(props); @@ -309,20 +218,20 @@ class Mixpanel { delete props.token; // Mixpanel People operations - if (this.people) { + if (people) { // increment event count, check if the current event exists in eventIncrements if (eventIncrements.indexOf(event) !== -1) { window.mixpanel.people.increment(event); - window.mixpanel.people.set('Last ' + event, new Date()); + window.mixpanel.people.set(`Last ${event}`, new Date()); } // increment property counts - // eslint-disable-next-line guard-for-in - for (const key in props) { - const prop = props[key]; - if (prop && propIncrements.indexOf(key) != -1) { - window.mixpanel.people.increment(key, prop); + Object.keys(props).forEach((prop) => { + const value = props[prop]; + if (value && propIncrements.includes(prop)) { + window.mixpanel.people.increment(prop, value); } - } + }); + // track revenue if (revenue) { window.mixpanel.people.track_charge(revenue); diff --git a/src/integrations/Mixpanel/index.js b/src/integrations/Mixpanel/index.js index 27cbb284e..773a010d8 100644 --- a/src/integrations/Mixpanel/index.js +++ b/src/integrations/Mixpanel/index.js @@ -1,3 +1 @@ -import Mixpanel from './browser'; - -export { Mixpanel }; +export { default as Mixpanel } from './browser'; diff --git a/src/integrations/Mixpanel/nativeSdkLoader.js b/src/integrations/Mixpanel/nativeSdkLoader.js new file mode 100644 index 000000000..c8f82f1ae --- /dev/null +++ b/src/integrations/Mixpanel/nativeSdkLoader.js @@ -0,0 +1,71 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(f, b) { + (function (f, b) { + if (!b.__SV) { + var e, g, i, h; + window.mixpanel = b; + b._i = []; + b.init = function (e, f, c) { + function g(a, d) { + var b = d.split('.'); + 2 == b.length && ((a = a[b[0]]), (d = b[1])); + a[d] = function () { + a.push([d].concat(Array.prototype.slice.call(arguments, 0))); + }; + } + var a = b; + 'undefined' !== typeof c ? (a = b[c] = []) : (c = 'mixpanel'); + a.people = a.people || []; + a.toString = function (a) { + var d = 'mixpanel'; + 'mixpanel' !== c && (d += '.' + c); + a || (d += ' (stub)'); + return d; + }; + a.people.toString = function () { + return a.toString(1) + '.people (stub)'; + }; + i = + 'disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking start_batch_senders people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove'.split( + ' ', + ); + for (h = 0; h < i.length; h++) g(a, i[h]); + var j = 'set set_once union unset remove delete'.split(' '); + a.get_group = function () { + function b(c) { + d[c] = function () { + call2_args = arguments; + call2 = [c].concat(Array.prototype.slice.call(call2_args, 0)); + a.push([e, call2]); + }; + } + for ( + var d = {}, e = ['get_group'].concat(Array.prototype.slice.call(arguments, 0)), c = 0; + c < j.length; + c++ + ) + b(j[c]); + return d; + }; + b._i.push([e, f, c]); + }; + b.__SV = 1.2; + e = f.createElement('script'); + e.type = 'text/javascript'; + e.async = !0; + e.setAttribute('data-loader', LOAD_ORIGIN); + e.src = + 'undefined' !== typeof MIXPANEL_CUSTOM_LIB_URL + ? MIXPANEL_CUSTOM_LIB_URL + : 'file:' === f.location.protocol && + '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js'.match(/^\/\//) + ? 'https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js' + : '//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js'; + g = f.getElementsByTagName('script')[0]; + g.parentNode.insertBefore(e, g); + } + })(document, window.mixpanel || []); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Mixpanel/util.js b/src/integrations/Mixpanel/util.js index 4033d4798..8a8d36044 100644 --- a/src/integrations/Mixpanel/util.js +++ b/src/integrations/Mixpanel/util.js @@ -1,6 +1,3 @@ -/* eslint-disable no-param-reassign */ -/* eslint-disable no-restricted-syntax */ -/* eslint-disable no-prototype-builtins */ import logger from '../../utils/logUtil'; import { getDefinedTraits, extractCustomFields } from '../../utils/utils'; @@ -95,11 +92,13 @@ const inverseObjectArrays = (input) => { }; const extractTraits = (traits, traitAliasesParam) => { - for (const [key, value] of Object.entries(traitAliasesParam)) { - traits[value] = traits[key]; - delete traits[key]; - } - return traits; + const extractedTraits = traits; + Object.keys(traitAliasesParam).forEach((key) => { + const value = traitAliasesParam[key]; + extractedTraits[value] = extractedTraits[key]; + delete extractedTraits[key]; + }); + return extractedTraits; }; /** @@ -152,7 +151,7 @@ const mapTraits = (arr) => { const ret = new Array(arr.length); arr.forEach((key) => { - if (traitAliases.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(traitAliases, key)) { ret.push(traitAliases[key]); } else { ret.push(key); @@ -162,12 +161,18 @@ const mapTraits = (arr) => { return ret; }; +const getConsolidatedPageCalls = (config) => + Object.prototype.hasOwnProperty.call(config, 'consolidatedPageCalls') + ? config.consolidatedPageCalls + : true; + export { - parseConfigArray, - inverseObjectArrays, - extractTraits, + mapTraits, unionArrays, extendTraits, - mapTraits, formatTraits, + extractTraits, + parseConfigArray, + inverseObjectArrays, + getConsolidatedPageCalls, }; diff --git a/src/integrations/MoEngage/browser.js b/src/integrations/MoEngage/browser.js index f798e3d04..bfcd441f0 100644 --- a/src/integrations/MoEngage/browser.js +++ b/src/integrations/MoEngage/browser.js @@ -1,7 +1,8 @@ +/* eslint-disable class-methods-use-this */ import each from '@ndhoule/each'; import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; // custom traits mapping context.traits --> moengage properties const traitsMap = { @@ -38,69 +39,7 @@ class MoEngage { init() { const self = this; logger.debug('===in init MoEngage==='); - // loading the script for moengage web sdk - /* eslint-disable */ - (function (i, s, o, g, r, a, m, n) { - i.moengage_object = r; - var t = {}; - var q = function (f) { - return function () { - (i.moengage_q = i.moengage_q || []).push({ f, a: arguments }); - }; - }; - var f = [ - 'track_event', - 'add_user_attribute', - 'add_first_name', - 'add_last_name', - 'add_email', - 'add_mobile', - 'add_user_name', - 'add_gender', - 'add_birthday', - 'destroy_session', - 'add_unique_user_id', - 'moe_events', - 'call_web_push', - 'track', - 'location_type_attribute', - ]; - var h = { onsite: ['getData', 'registerCallback'] }; - for (var k in f) { - t[f[k]] = q(f[k]); - } - for (var k in h) - for (var l in h[k]) { - null == t[k] && (t[k] = {}), (t[k][h[k][l]] = q(k + '.' + h[k][l])); - } - a = s.createElement(o); - m = s.getElementsByTagName(o)[0]; - a.async = 1; - a.setAttribute('data-loader', LOAD_ORIGIN); - a.src = g; - m.parentNode.insertBefore(a, m); - i.moe = - i.moe || - function () { - n = arguments[0]; - return t; - }; - a.onload = function () { - if (n) { - i[r] = moe(n); - } - }; - })( - window, - document, - 'script', - document.location.protocol === 'https:' - ? 'https://cdn.moengage.com/webpush/moe_webSdk.min.latest.js' - : 'http://cdn.moengage.com/webpush/moe_webSdk.min.latest.js', - 'Moengage', - ); - /* eslint-enable */ - + loadNativeSdk(); // setting the region if us then not needed. if (this.region !== 'US') { self.moeClient = window.moe({ @@ -117,15 +56,15 @@ class MoEngage { this.initialUserId = this.analytics.getUserId(); } - isLoaded = () => { + isLoaded() { logger.debug('in MoEngage isLoaded'); return !!window.moeBannerText; - }; + } - isReady = () => { + isReady() { logger.debug('in MoEngage isReady'); return !!window.moeBannerText; - }; + } track(rudderElement) { logger.debug('inside track'); @@ -135,10 +74,8 @@ class MoEngage { return; } const { event, properties, userId } = rudderElement.message; - if (userId) { - if (this.initialUserId !== userId) { - this.reset(); - } + if (userId && this.initialUserId !== userId) { + this.reset(); } // track event : https://docs.moengage.com/docs/tracking-events if (!event) { @@ -161,10 +98,10 @@ class MoEngage { identify(rudderElement) { const self = this; - const { userId } = rudderElement.message; + const { userId, context } = rudderElement.message; let traits = null; - if (rudderElement.message.context) { - traits = rudderElement.message.context.traits; + if (context) { + traits = context.traits; } // check if user id is same or not if (this.initialUserId !== userId) { @@ -177,7 +114,7 @@ class MoEngage { // track user attributes : https://docs.moengage.com/docs/tracking-web-user-attributes if (traits) { - each(function add(value, key) { + each((value, key) => { // check if name is present if (key === 'name') { self.moeClient.add_user_name(value); diff --git a/src/integrations/MoEngage/index.js b/src/integrations/MoEngage/index.js index 3170027c4..654fcc7f7 100644 --- a/src/integrations/MoEngage/index.js +++ b/src/integrations/MoEngage/index.js @@ -1,3 +1 @@ -import MoEngage from './browser'; - -export { MoEngage }; +export { default as MoEngage } from './browser'; diff --git a/src/integrations/MoEngage/nativeSdkLoader.js b/src/integrations/MoEngage/nativeSdkLoader.js new file mode 100644 index 000000000..c27e1fad4 --- /dev/null +++ b/src/integrations/MoEngage/nativeSdkLoader.js @@ -0,0 +1,66 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + // loading the script for moengage web sdk + (function (i, s, o, g, r, a, m, n) { + i.moengage_object = r; + var t = {}; + var q = function (f) { + return function () { + (i.moengage_q = i.moengage_q || []).push({ f, a: arguments }); + }; + }; + var f = [ + 'track_event', + 'add_user_attribute', + 'add_first_name', + 'add_last_name', + 'add_email', + 'add_mobile', + 'add_user_name', + 'add_gender', + 'add_birthday', + 'destroy_session', + 'add_unique_user_id', + 'moe_events', + 'call_web_push', + 'track', + 'location_type_attribute', + ]; + var h = { onsite: ['getData', 'registerCallback'] }; + for (var k in f) { + t[f[k]] = q(f[k]); + } + for (var k in h) + for (var l in h[k]) { + null == t[k] && (t[k] = {}), (t[k][h[k][l]] = q(k + '.' + h[k][l])); + } + a = s.createElement(o); + m = s.getElementsByTagName(o)[0]; + a.async = 1; + a.setAttribute('data-loader', LOAD_ORIGIN); + a.src = g; + m.parentNode.insertBefore(a, m); + i.moe = + i.moe || + function () { + n = arguments[0]; + return t; + }; + a.onload = function () { + if (n) { + i[r] = moe(n); + } + }; + })( + window, + document, + 'script', + document.location.protocol === 'https:' + ? 'https://cdn.moengage.com/webpush/moe_webSdk.min.latest.js' + : 'http://cdn.moengage.com/webpush/moe_webSdk.min.latest.js', + 'Moengage', + ); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Mouseflow/browser.js b/src/integrations/Mouseflow/browser.js index 712f7983e..5bb1d7271 100644 --- a/src/integrations/Mouseflow/browser.js +++ b/src/integrations/Mouseflow/browser.js @@ -50,9 +50,10 @@ class Mouseflow { identify(rudderElement) { logger.debug('===In mouseflow Identify==='); const { message } = rudderElement; - const { traits } = message.context; - const email = message.context.traits?.email || message.traits?.email; - const userId = message.userId || email || message.anonymousId; + const { context, traits: rootLevelTraits, anonymousId } = message; + const { traits } = context; + const email = traits?.email || rootLevelTraits?.email; + const userId = message?.userId || email || anonymousId; window._mfq.push(['stop']); if (userId) window.mouseflow.identify(userId); window.mouseflow.start(); diff --git a/src/integrations/Mouseflow/index.js b/src/integrations/Mouseflow/index.js index 3f3009314..8ce2ca8cd 100644 --- a/src/integrations/Mouseflow/index.js +++ b/src/integrations/Mouseflow/index.js @@ -1,3 +1 @@ -import Mouseflow from './browser'; - -export { Mouseflow }; +export { default as Mouseflow } from './browser'; diff --git a/src/integrations/Olark/browser.js b/src/integrations/Olark/browser.js index e53adfca6..7efc70dd4 100644 --- a/src/integrations/Olark/browser.js +++ b/src/integrations/Olark/browser.js @@ -2,10 +2,10 @@ /* eslint-disable class-methods-use-this */ import { NAME } from './constants'; import Logger from '../../utils/logger'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { recordingLiveChatEvents } from './utils'; import { getHashFromArray } from '../../utils/commonUtils'; import { getDefinedTraits } from '../../utils/utils'; +import { loadNativeSdk } from './nativeSdkLoader'; const logger = new Logger(NAME); class Olark { @@ -28,31 +28,7 @@ class Olark { } loadScript() { - (function (o, l, a, r, k, y) { - if (o.olark) return; - r = 'script'; - y = l.createElement(r); - r = l.getElementsByTagName(r)[0]; - y.async = 1; - y.src = '//' + a; - y.setAttribute('data-loader', LOAD_ORIGIN); - r.parentNode.insertBefore(y, r); - y = o.olark = function () { - k.s.push(arguments); - k.t.push(+new Date()); - }; - y.extend = function (i, j) { - y('extend', i, j); - }; - y.identify = function (i) { - y('identify', (k.i = i)); - }; - y.configure = function (i, j) { - y('configure', i, j); - k.c[i] = j; - }; - k = y._ = { s: [], t: [+new Date()], c: {}, l: a }; - })(window, document, 'static.olark.com/jsclient/loader.js'); + loadNativeSdk(); /* custom configuration goes here (www.olark.com/documentation) */ window.olark.identify(this.siteId); diff --git a/src/integrations/Olark/index.js b/src/integrations/Olark/index.js index 1d1b7042f..ff94e9a6d 100644 --- a/src/integrations/Olark/index.js +++ b/src/integrations/Olark/index.js @@ -1,3 +1 @@ -import Olark from './browser'; - -export { Olark }; +export { default as Olark } from './browser'; diff --git a/src/integrations/Olark/nativeSdkLoader.js b/src/integrations/Olark/nativeSdkLoader.js new file mode 100644 index 000000000..20367c900 --- /dev/null +++ b/src/integrations/Olark/nativeSdkLoader.js @@ -0,0 +1,31 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + (function (o, l, a, r, k, y) { + if (o.olark) return; + r = 'script'; + y = l.createElement(r); + r = l.getElementsByTagName(r)[0]; + y.async = 1; + y.src = '//' + a; + y.setAttribute('data-loader', LOAD_ORIGIN); + r.parentNode.insertBefore(y, r); + y = o.olark = function () { + k.s.push(arguments); + k.t.push(+new Date()); + }; + y.extend = function (i, j) { + y('extend', i, j); + }; + y.identify = function (i) { + y('identify', (k.i = i)); + }; + y.configure = function (i, j) { + y('configure', i, j); + k.c[i] = j; + }; + k = y._ = { s: [], t: [+new Date()], c: {}, l: a }; + })(window, document, 'static.olark.com/jsclient/loader.js'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Optimizely/browser.js b/src/integrations/Optimizely/browser.js index cabf977cf..af7ffb6bb 100644 --- a/src/integrations/Optimizely/browser.js +++ b/src/integrations/Optimizely/browser.js @@ -1,5 +1,5 @@ -/* eslint-disable no-param-reassign */ /* eslint-disable class-methods-use-this */ +import { mapRudderPropsToOptimizelyProps } from './utils'; import logger from '../../utils/logUtil'; import { NAME } from './constants'; @@ -34,6 +34,14 @@ class Optimizely { this.initOptimizelyIntegration(this.referrerOverride, this.sendDataToRudder); } + isLoaded() { + return !!(window.optimizely && window.optimizely.push !== Array.prototype.push); + } + + isReady() { + return !!(window.optimizely && window.optimizely.push !== Array.prototype.push); + } + referrerOverride = (referrer) => { if (referrer) { window.optimizelyEffectiveReferrer = referrer; @@ -44,10 +52,9 @@ class Optimizely { sendDataToRudder = (campaignState) => { logger.debug(campaignState); - const { experiment } = campaignState; - const { variation } = campaignState; + const { experiment, variation } = campaignState; const context = { integrations: { All: true } }; - const { audiences } = campaignState; + const { audiences, campaignName, id, isInCampaignHoldback } = campaignState; // Reformatting this data structure into hash map so concatenating variation ids and names is easier later const audiencesMap = {}; @@ -55,20 +62,24 @@ class Optimizely { audiencesMap[audience.id] = audience.name; }); - const audienceIds = Object.keys(audiencesMap).sort().join(); - const audienceNames = Object.values(audiencesMap).sort().join(', '); + const audienceIds = Object.keys(audiencesMap) + .sort((a, b) => a.localeCompare(b)) + .join(); + const audienceNames = Object.values(audiencesMap) + .sort((a, b) => a.localeCompare(b)) + .join(', '); if (this.sendExperimentTrack) { - const props = { - campaignName: campaignState.campaignName, - campaignId: campaignState.id, + let props = { + campaignName, + campaignId: id, experimentId: experiment.id, experimentName: experiment.name, variationName: variation.name, variationId: variation.id, audienceId: audienceIds, // eg. '7527562222,7527111138' audienceName: audienceNames, // eg. 'Peaky Blinders, Trust Tree' - isInCampaignHoldback: campaignState.isInCampaignHoldback, + isInCampaignHoldback, }; // If this was a redirect experiment and the effective referrer is different from document.referrer, @@ -82,20 +93,10 @@ class Optimizely { // For Google's nonInteraction flag if (this.sendExperimentTrackAsNonInteractive) props.nonInteraction = 1; - // If customCampaignProperties is provided overide the props with it. + // If customCampaignProperties is provided override the props with it. // If valid customCampaignProperties present it will override existing props. // const data = window.optimizely && window.optimizely.get("data"); - const data = campaignState; - if (data && this.customCampaignProperties.length > 0) { - for (let index = 0; index < this.customCampaignProperties.length; index += 1) { - const rudderProp = this.customCampaignProperties[index].from; - const optimizelyProp = this.customCampaignProperties[index].to; - if (typeof props[optimizelyProp] !== 'undefined') { - props[rudderProp] = props[optimizelyProp]; - delete props[optimizelyProp]; - } - } - } + props = mapRudderPropsToOptimizelyProps(props, campaignState, this.customCampaignProperties); // Send to Rudder this.analytics.track('Experiment Viewed', props, context); @@ -111,7 +112,7 @@ class Optimizely { initOptimizelyIntegration(referrerOverride, sendCampaignData) { const newActiveCampaign = (id, referrer) => { - const state = window.optimizely.get && window.optimizely.get('state'); + const state = window?.optimizely?.get('state'); if (state) { const activeCampaigns = state.getCampaignStates({ isActive: true, @@ -123,7 +124,7 @@ class Optimizely { }; const checkReferrer = () => { - const state = window.optimizely.get && window.optimizely.get('state'); + const state = window?.optimizely?.get('state'); if (state) { const referrer = state.getRedirectInfo() && state.getRedirectInfo().referrer; @@ -152,7 +153,7 @@ class Optimizely { const registerCurrentlyActiveCampaigns = () => { window.optimizely = window.optimizely || []; - const state = window.optimizely.get && window.optimizely.get('state'); + const state = window?.optimizely?.get('state'); if (state) { const referrer = checkReferrer(); const activeCampaigns = state.getCampaignStates({ @@ -205,36 +206,25 @@ class Optimizely { page(rudderElement) { logger.debug('in Optimizely web page'); - const { category } = rudderElement.message.properties; - const { name } = rudderElement.message; - /* const contextOptimizely = { - integrations: { All: false, Optimizely: true }, - }; */ + + const clonedRudderElement = rudderElement; + const { category } = clonedRudderElement.message.properties; + const { name } = clonedRudderElement.message; // categorized pages if (category && this.trackCategorizedPages) { - // this.analytics.track(`Viewed ${category} page`, {}, contextOptimizely); - rudderElement.message.event = `Viewed ${category} page`; - rudderElement.message.type = 'track'; - this.track(rudderElement); + clonedRudderElement.message.event = `Viewed ${category} page`; + clonedRudderElement.message.type = 'track'; + this.track(clonedRudderElement); } // named pages if (name && this.trackNamedPages) { - // this.analytics.track(`Viewed ${name} page`, {}, contextOptimizely); - rudderElement.message.event = `Viewed ${name} page`; - rudderElement.message.type = 'track'; - this.track(rudderElement); + clonedRudderElement.message.event = `Viewed ${name} page`; + clonedRudderElement.message.type = 'track'; + this.track(clonedRudderElement); } } - - isLoaded() { - return !!(window.optimizely && window.optimizely.push !== Array.prototype.push); - } - - isReady() { - return !!(window.optimizely && window.optimizely.push !== Array.prototype.push); - } } export default Optimizely; diff --git a/src/integrations/Optimizely/index.js b/src/integrations/Optimizely/index.js index 9d8eb519b..3ecdbb411 100644 --- a/src/integrations/Optimizely/index.js +++ b/src/integrations/Optimizely/index.js @@ -1,3 +1 @@ -import Optimizely from './browser'; - -export { Optimizely }; +export { default as Optimizely } from './browser'; diff --git a/src/integrations/Optimizely/utils.js b/src/integrations/Optimizely/utils.js new file mode 100644 index 000000000..25fc24689 --- /dev/null +++ b/src/integrations/Optimizely/utils.js @@ -0,0 +1,23 @@ +/** + * Returns updated mapped properties + * @param {*} props + * @param {*} campaignState + * @param {*} customCampaignProperties + * @returns + */ +const mapRudderPropsToOptimizelyProps = (props, campaignState, customCampaignProperties) => { + const properties = props; + if (campaignState && customCampaignProperties.length > 0) { + customCampaignProperties.forEach((customCampaignProperty) => { + const rudderProp = customCampaignProperties[customCampaignProperty].from; + const optimizelyProp = customCampaignProperties[customCampaignProperty].to; + if (typeof properties[optimizelyProp] !== 'undefined') { + properties[rudderProp] = properties[optimizelyProp]; + delete properties[optimizelyProp]; + } + }); + } + return properties; +}; + +export { mapRudderPropsToOptimizelyProps }; diff --git a/src/integrations/Pendo/browser.js b/src/integrations/Pendo/browser.js index 953b906cd..1723ad5e7 100644 --- a/src/integrations/Pendo/browser.js +++ b/src/integrations/Pendo/browser.js @@ -1,8 +1,7 @@ /* eslint-disable class-methods-use-this */ -/* eslint-disable lines-between-class-members */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class Pendo { constructor(config, analytics, destinationInfo) { @@ -21,32 +20,7 @@ class Pendo { } init() { - (function (apiKey) { - (function (p, e, n, d, o) { - let v; - let w; - let x; - let y; - let z; - o = p[d] = p[d] || {}; - o._q = []; - v = ['initialize', 'identify', 'updateOptions', 'pageLoad', 'track']; - for (w = 0, x = v.length; w < x; ++w) - (function (m) { - o[m] = - o[m] || - function () { - o._q[m === v[0] ? 'unshift' : 'push']([m].concat([].slice.call(arguments, 0))); - }; - })(v[w]); - y = e.createElement(n); - y.setAttribute('data-loader', LOAD_ORIGIN); - y.async = !0; - y.src = `https://cdn.pendo.io/agent/static/${apiKey}/pendo.js`; - z = e.getElementsByTagName(n)[0]; - z.parentNode.insertBefore(y, z); - })(window, document, 'script', 'pendo'); - })(this.apiKey); + loadNativeSdk(this.apiKey); this.initializeMe(); logger.debug('===in init Pendo==='); } @@ -91,8 +65,8 @@ class Pendo { let visitorObj = {}; let accountObj = {}; const { groupId } = this.analytics; - const id = - this.analytics.getUserId() || this.constructPendoAnonymousId(this.analytics.getAnonymousId()); + const { userId, anonymousId } = rudderElement.message; + const id = userId || this.constructPendoAnonymousId(anonymousId); visitorObj = { id, ...this.analytics.getUserTraits(), @@ -104,6 +78,7 @@ class Pendo { window.pendo.identify({ visitor: visitorObj, account: accountObj }); } + /* *Group call maps to an account for which visitor belongs. *It is same as identify call but here we send account object. @@ -121,7 +96,7 @@ class Pendo { if (userId) { visitorObj = { id: userId, - ...(context && context.traits), + ...context?.traits, }; } @@ -133,7 +108,8 @@ class Pendo { track(rudderElement) { const { event, properties } = rudderElement.message; if (!event) { - throw Error('Cannot call un-named track event'); + logger.error('Cannot call un-named track event'); + return; } const props = properties; window.pendo.track(event, props); diff --git a/src/integrations/Pendo/index.js b/src/integrations/Pendo/index.js index 222b295a9..e83e1ab31 100644 --- a/src/integrations/Pendo/index.js +++ b/src/integrations/Pendo/index.js @@ -1,3 +1 @@ -import { Pendo } from './browser'; - -export { Pendo }; +export { Pendo } from './browser'; diff --git a/src/integrations/Pendo/nativeSdkLoader.js b/src/integrations/Pendo/nativeSdkLoader.js new file mode 100644 index 000000000..a16a42592 --- /dev/null +++ b/src/integrations/Pendo/nativeSdkLoader.js @@ -0,0 +1,32 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(apiKey) { + (function (apiKey) { + (function (p, e, n, d, o) { + let v; + let w; + let x; + let y; + let z; + o = p[d] = p[d] || {}; + o._q = []; + v = ['initialize', 'identify', 'updateOptions', 'pageLoad', 'track']; + for (w = 0, x = v.length; w < x; ++w) + (function (m) { + o[m] = + o[m] || + function () { + o._q[m === v[0] ? 'unshift' : 'push']([m].concat([].slice.call(arguments, 0))); + }; + })(v[w]); + y = e.createElement(n); + y.setAttribute('data-loader', LOAD_ORIGIN); + y.async = !0; + y.src = `https://cdn.pendo.io/agent/static/${apiKey}/pendo.js`; + z = e.getElementsByTagName(n)[0]; + z.parentNode.insertBefore(y, z); + })(window, document, 'script', 'pendo'); + })(apiKey); +} + +export { loadNativeSdk }; diff --git a/src/integrations/PinterestTag/browser.js b/src/integrations/PinterestTag/browser.js index 0952e7e6b..911daf5fd 100644 --- a/src/integrations/PinterestTag/browser.js +++ b/src/integrations/PinterestTag/browser.js @@ -1,22 +1,22 @@ /* eslint-disable class-methods-use-this */ -import sha256 from 'crypto-js/sha256'; import get from 'get-value'; +import sha256 from 'crypto-js/sha256'; import logger from '../../utils/logUtil'; import { + propertyMapping, searchPropertyMapping, productPropertyMapping, - propertyMapping, pinterestPropertySupport, } from './propertyMappingConfig'; import { + getDefinedTraits, + getDataFromSource, flattenJsonPayload, isDefinedAndNotNull, - getDataFromSource, - getDefinedTraits, } from '../../utils/utils'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { getDestinationEventName } from './utils'; +import { loadNativeSdk } from './nativeSdkLoader'; export default class PinterestTag { constructor(config, analytics, destinationInfo) { @@ -39,24 +39,12 @@ export default class PinterestTag { } loadScript() { - !(function (e) { - if (!window.pintrk) { - window.pintrk = function () { - window.pintrk.queue.push(Array.prototype.slice.call(arguments)); - }; - const n = window.pintrk; - (n.queue = []), (n.version = '3.0'); - const t = document.createElement('script'); - (t.async = !0), (t.src = e), t.setAttribute('data-loader', LOAD_ORIGIN); - const r = document.getElementsByTagName('script')[0]; - r.parentNode.insertBefore(t, r); - } - })('https://s.pinimg.com/ct/core.js'); + loadNativeSdk(); } handleEnhancedMatch() { const userTraits = this.analytics.getUserTraits(); - const email = userTraits && userTraits.email; + const { email } = userTraits; if (email && this.enhancedMatch) { window.pintrk('load', this.tagId, { em: email, @@ -212,10 +200,10 @@ export default class PinterestTag { } track(rudderElement) { - if (!rudderElement.message || !rudderElement.message.event) { + const { message } = rudderElement; + if (!message?.event) { return; } - const { message } = rudderElement; const { properties, event, messageId } = message; const destEventArray = getDestinationEventName( event, @@ -244,7 +232,7 @@ export default class PinterestTag { identify() { const userTraits = this.analytics.getUserTraits(); - const email = userTraits && userTraits.email; + const { email } = userTraits; if (email) { const ldpObject = this.generateLdpObject(); window.pintrk('set', { em: email, ...ldpObject }); diff --git a/src/integrations/PinterestTag/index.js b/src/integrations/PinterestTag/index.js index 8b3e87cab..0701b9f84 100644 --- a/src/integrations/PinterestTag/index.js +++ b/src/integrations/PinterestTag/index.js @@ -1,3 +1 @@ -import PinterestTag from './browser'; - -export { PinterestTag }; +export { default as PinterestTag } from './browser'; diff --git a/src/integrations/PinterestTag/nativeSdkLoader.js b/src/integrations/PinterestTag/nativeSdkLoader.js new file mode 100644 index 000000000..35c32ff36 --- /dev/null +++ b/src/integrations/PinterestTag/nativeSdkLoader.js @@ -0,0 +1,19 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(e) { + !(function (e) { + if (!window.pintrk) { + window.pintrk = function () { + window.pintrk.queue.push(Array.prototype.slice.call(arguments)); + }; + const n = window.pintrk; + (n.queue = []), (n.version = '3.0'); + const t = document.createElement('script'); + (t.async = !0), (t.src = e), t.setAttribute('data-loader', LOAD_ORIGIN); + const r = document.getElementsByTagName('script')[0]; + r.parentNode.insertBefore(t, r); + } + })('https://s.pinimg.com/ct/core.js'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Podsights/browser.js b/src/integrations/Podsights/browser.js index ed3cd8f8b..fd574c66c 100644 --- a/src/integrations/Podsights/browser.js +++ b/src/integrations/Podsights/browser.js @@ -1,3 +1,5 @@ +/* eslint-disable prefer-rest-params */ +/* eslint-disable func-names */ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import get from 'get-value'; @@ -96,9 +98,9 @@ class Podsights { let events = []; const customEvent = eventsMappingFromCustomEvents[trimmedEvent] || []; const standardEvents = eventMappingFromStandardEvents[trimmedEvent] || []; - if (customEvent.length !== 0) { + if (customEvent.length > 0) { events = customEvent; - } else if (standardEvents.length !== 0) { + } else if (standardEvents.length > 0) { events = standardEvents; } else { logger.error(`===No Podsights Pixel mapped event found. Aborting!===`); @@ -157,9 +159,9 @@ class Podsights { * @param {Page} page */ page(rudderElement) { - const { properties } = rudderElement.message; + const { properties, context } = rudderElement.message; logger.debug('===In Podsights Page==='); - const { page } = rudderElement.message.context; + const { page } = context; let payload = properties; if (page) { payload = { diff --git a/src/integrations/Podsights/index.js b/src/integrations/Podsights/index.js index 6b9e1a223..3af37aa74 100644 --- a/src/integrations/Podsights/index.js +++ b/src/integrations/Podsights/index.js @@ -1,3 +1 @@ -import Podsights from './browser'; - -export { Podsights }; +export { default as Podsights } from './browser'; diff --git a/src/integrations/Podsights/utils.js b/src/integrations/Podsights/utils.js index 79f1bf12d..f7d5b35f9 100644 --- a/src/integrations/Podsights/utils.js +++ b/src/integrations/Podsights/utils.js @@ -1,6 +1,6 @@ +import { LINE_ITEMS_CONFIG } from './constants'; import { constructPayload } from '../../utils/utils'; import { removeUndefinedAndNullValues } from '../../utils/commonUtils'; -import { LINE_ITEMS_CONFIG } from './constants'; /** * This function is used to build payload with line_items, it will search from @@ -23,13 +23,13 @@ const payloadBuilder = (properties, CONFIG_EVENT) => { // if products is an array of objects, then we'll build each line_items payload from products. const productList = properties?.products; if (productList && Array.isArray(productList)) { - for (const product of productList) { + productList.forEach((product) => { const productDetails = constructPayload(product, LINE_ITEMS_CONFIG); lineItems.push({ ...lineItemsPayload, ...removeUndefinedAndNullValues(productDetails), }); - } + }); } else { lineItems.push(lineItemsPayload); } @@ -47,15 +47,15 @@ const payloadBuilder = (properties, CONFIG_EVENT) => { const payloadBuilderInList = (properties, CONFIG_EVENT) => { const payloadList = []; const productList = properties?.products; - let productPayload = constructPayload(properties, CONFIG_EVENT); + const productPayload = constructPayload(properties, CONFIG_EVENT); if (productList && Array.isArray(productList)) { - for (const product of productList) { + productList.forEach((product) => { const productDetails = constructPayload(product, CONFIG_EVENT); payloadList.push({ ...productPayload, ...removeUndefinedAndNullValues(productDetails), }); - } + }); } else { payloadList.push(productPayload); } diff --git a/src/integrations/PostAffiliatePro/browser.js b/src/integrations/PostAffiliatePro/browser.js index e8c4c3176..73f0e9ebe 100644 --- a/src/integrations/PostAffiliatePro/browser.js +++ b/src/integrations/PostAffiliatePro/browser.js @@ -1,7 +1,7 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable no-underscore-dangle */ import get from 'get-value'; -import updateSaleObject from './utils'; +import { updateSaleObject, getMergedProductIds } from './utils'; import ScriptLoader from '../../utils/ScriptLoader'; import logger from '../../utils/logUtil'; import { NAME } from './constants'; @@ -77,55 +77,57 @@ class PostAffiliatePro { const visitorId = get(message, 'userId'); window.PostAffTracker.setVisitorId(visitorId); } - // eslint-disable-next-line lines-between-class-members + track(rudderElement) { logger.debug('===In Post Affiliate Pro track==='); const clickEventsArr = this.clickEvents ? this.clickEvents.split(',') : null; const { message } = rudderElement; const { event } = message; const { properties } = message; + // We are going to call click event, for the event list given in dashboard only. - if (clickEventsArr && clickEventsArr.includes(event)) { + if (clickEventsArr?.includes(event)) { if (properties) { - if (properties.data1) window.Data1 = properties.data1; - if (properties.data2) window.Data2 = properties.data2; - if (properties.affiliateId) window.AffiliateID = properties.affiliateId; - if (properties.bannerId) window.BannerID = properties.bannerId; - if (properties.campaignId) window.CampaignID = properties.campaignId; - if (properties.channel) window.Channel = properties.channel; + const rudderToWindowPropertiesMap = [ + { property: 'data1', windowProperty: 'Data1' }, + { property: 'data2', windowProperty: 'Data2' }, + { property: 'affiliateId', windowProperty: 'AffiliateID' }, + { property: 'bannerId', windowProperty: 'BannerID' }, + { property: 'campaignId', windowProperty: 'CampaignID' }, + { property: 'channel', windowProperty: 'Channel' }, + ]; + + rudderToWindowPropertiesMap.forEach(({ property, windowProperty }) => { + if (Object.prototype.hasOwnProperty.call(properties, property)) { + window[windowProperty] = properties[property]; + } + }); } window.PostAffTracker.track(); } + // We are supporting only one event for sale. if (event === 'Order Completed') { - const productsArr = properties && properties.products ? properties.products : null; - if (productsArr) { - if (this.mergeProducts) { - window.sale = window.PostAffTracker.createSale(); - if (window.sale) updateSaleObject(window.sale, properties); - const mergedProductId = []; - for (let i = 0; i < productsArr.length; i += 1) - if (productsArr[i].product_id) mergedProductId.push(productsArr[i].product_id); - const merged = mergedProductId.join(); - if (merged) window.sale.setProductID(merged); - } else { - for (let i = 0; i < productsArr.length; i += 1) { - window[`sale${i}`] = window.PostAffTracker.createSale(); - updateSaleObject(window[`sale${i}`], properties); - if (productsArr[i].product_id) - window[`sale${i}`].setProductID(productsArr[i].product_id); + const productsArr = properties?.products || null; + if (productsArr && this.mergeProducts) { + window.sale = window.PostAffTracker.createSale(); + if (window.sale) updateSaleObject(window.sale, properties); + const merged = getMergedProductIds(productsArr); + window.sale.setProductID(merged); + } else if (productsArr) { + productsArr.forEach((product, index) => { + window[`sale${index}`] = window.PostAffTracker.createSale(); + updateSaleObject(window[`sale${index}`], properties); + if (product.product_id) { + window[`sale${index}`].setProductID(product.product_id); } - } + }); } else { - // If any product is not available. + // If any product is not available window.sale = window.PostAffTracker.createSale(); } window.PostAffTracker.register(); } } - - // reset() { - // window.PostAffTracker.setVisitorId(null); - // } } export default PostAffiliatePro; diff --git a/src/integrations/PostAffiliatePro/index.js b/src/integrations/PostAffiliatePro/index.js index 51e94d0d1..36f260665 100644 --- a/src/integrations/PostAffiliatePro/index.js +++ b/src/integrations/PostAffiliatePro/index.js @@ -1,3 +1 @@ -import PostAffiliatePro from './browser'; - -export { PostAffiliatePro }; +export { default as PostAffiliatePro } from './browser'; diff --git a/src/integrations/PostAffiliatePro/utils.js b/src/integrations/PostAffiliatePro/utils.js index a62ac96e8..a5b3027be 100644 --- a/src/integrations/PostAffiliatePro/utils.js +++ b/src/integrations/PostAffiliatePro/utils.js @@ -1,22 +1,40 @@ // This function helps to populate the sale object const updateSaleObject = (sale, properties) => { - if (properties.total) sale.setTotalCost(properties.total); - if (properties.fixedCost) sale.setFixedCost(properties.fixedCost); - if (properties.order_id) sale.setOrderID(properties.order_id); - // Post Affiliate Pro supports five extra data only. - if (properties.data1) sale.setData1(properties.data1); - if (properties.data2) sale.setData2(properties.data2); - if (properties.data3) sale.setData3(properties.data3); - if (properties.data4) sale.setData4(properties.data4); - if (properties.data5) sale.setData5(properties.data5); - if (properties.doNotDeleteCookies && properties.doNotDeleteCookies === true) - sale.doNotDeleteCookies(); - if (properties.status) sale.setStatus(properties.status); - if (properties.currency) sale.setCurrency(properties.currency); - if (properties.customCommision) sale.setCustomCommission(properties.customCommision); - if (properties.channel) sale.setChannelID(properties.channel); - if (properties.coupon) sale.setCoupon(properties.coupon); - if (properties.campaignId) sale.setCampaignID(properties.campaignId); - if (properties.affiliateId) sale.setAffiliateID(properties.affiliateId); + const saleMethodsMap = [ + { property: 'total', method: 'setTotalCost' }, + { property: 'fixedCost', method: 'setFixedCost' }, + { property: 'order_id', method: 'setOrderID' }, + { property: 'data1', method: 'setData1' }, + { property: 'data2', method: 'setData2' }, + { property: 'data3', method: 'setData3' }, + { property: 'data4', method: 'setData4' }, + { property: 'data5', method: 'setData5' }, + { property: 'doNotDeleteCookies', method: 'doNotDeleteCookies' }, + { property: 'status', method: 'setStatus' }, + { property: 'currency', method: 'setCurrency' }, + { property: 'customCommission', method: 'setCustomCommission' }, + { property: 'channel', method: 'setChannelID' }, + { property: 'coupon', method: 'setCoupon' }, + { property: 'campaignId', method: 'setCampaignID' }, + { property: 'affiliateId', method: 'setAffiliateID' }, + ]; + + saleMethodsMap.forEach(({ property, method }) => { + if (Object.prototype.hasOwnProperty.call(properties, property)) { + if (method === 'doNotDeleteCookies') { + sale[method](); + } else { + sale[method](properties[property]); + } + } + }); }; -export default updateSaleObject; + +const getMergedProductIds = (productsArr) => { + const mergedProductId = productsArr + .filter((product) => product.product_id) + .map((product) => product.product_id) + .join(); + return mergedProductId; +}; +export { updateSaleObject, getMergedProductIds }; diff --git a/src/integrations/Posthog/browser.js b/src/integrations/Posthog/browser.js index 5d6b481c9..1c81e3be0 100644 --- a/src/integrations/Posthog/browser.js +++ b/src/integrations/Posthog/browser.js @@ -1,10 +1,12 @@ /* eslint-disable no-undef */ +/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import get from 'get-value'; import logger from '../../utils/logUtil'; +import { getXhrHeaders, getPropertyBlackList } from './utils'; import { removeTrailingSlashes } from '../../utils/utils'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class Posthog { constructor(config, analytics, destinationInfo) { @@ -19,35 +21,14 @@ class Posthog { this.capturePageView = config.capturePageView || false; this.disableSessionRecording = config.disableSessionRecording || false; this.disableCookie = config.disableCookie || false; - this.propertyBlackList = []; - this.xhrHeaders = {}; + this.propertyBlackList = getPropertyBlackList(config); + this.xhrHeaders = getXhrHeaders(config); this.enableLocalStoragePersistence = config.enableLocalStoragePersistence; ({ shouldApplyDeviceModeTransformation: this.shouldApplyDeviceModeTransformation, propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError, destinationId: this.destinationId, } = destinationInfo ?? {}); - - if (config.xhrHeaders && config.xhrHeaders.length > 0) { - config.xhrHeaders.forEach((header) => { - if ( - header && - header.key && - header.value && - header.key.trim() !== '' && - header.value.trim() !== '' - ) { - this.xhrHeaders[header.key] = header.value; - } - }); - } - if (config.propertyBlackList && config.propertyBlackList.length > 0) { - config.propertyBlackList.forEach((element) => { - if (element && element.property && element.property.trim() !== '') { - this.propertyBlackList.push(element.property); - } - }); - } } init() { @@ -56,48 +37,7 @@ class Posthog { logger.debug('===[POSTHOG]: loadIntegration flag is disabled==='); return; } - !(function (t, e) { - var o, n, p, r; - e.__SV || - ((window.posthog = e), - (e._i = []), - (e.init = function (i, s, a) { - function g(t, e) { - var o = e.split('.'); - 2 == o.length && ((t = t[o[0]]), (e = o[1])), - (t[e] = function () { - t.push([e].concat(Array.prototype.slice.call(arguments, 0))); - }); - } - ((p = t.createElement('script')).type = 'text/javascript'), - (p.async = !0), - p.setAttribute('data-loader', LOAD_ORIGIN), - (p.src = s.api_host + '/static/array.js'), - (r = t.getElementsByTagName('script')[0]).parentNode.insertBefore(p, r); - var u = e; - for ( - void 0 !== a ? (u = e[a] = []) : (a = 'posthog'), - u.people = u.people || [], - u.toString = function (t) { - var e = 'posthog'; - return 'posthog' !== a && (e += '.' + a), t || (e += ' (stub)'), e; - }, - u.people.toString = function () { - return u.toString(1) + '.people (stub)'; - }, - o = - 'capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags'.split( - ' ', - ), - n = 0; - n < o.length; - n++ - ) - g(u, o[n]); - e._i.push([i, s, a]); - }), - (e.__SV = 1)); - })(document, window.posthog || []); + loadNativeSdk(); const configObject = { api_host: this.yourInstance, @@ -108,7 +48,7 @@ class Posthog { disable_cookie: this.disableCookie, }; - if (options && options.loaded) { + if (options?.loaded) { configObject.loaded = options.loaded; } if (this.xhrHeaders && Object.keys(this.xhrHeaders).length > 0) { @@ -121,6 +61,15 @@ class Posthog { posthog.init(this.teamApiKey, configObject); } + isLoaded() { + logger.debug('in Posthog isLoaded'); + return !!window?.posthog?.__loaded; + } + + isReady() { + return !!window?.posthog?.__loaded; + } + /** * superproperties should be part of rudderelement.message.integrations.POSTHOG object. * Once we call the posthog.register api, the corresponding property will be sent along with subsequent capture calls. @@ -128,7 +77,7 @@ class Posthog { */ processSuperProperties(rudderElement) { const { integrations } = rudderElement.message; - if (integrations && integrations.POSTHOG) { + if (integrations?.POSTHOG) { const { superProperties, setOnceProperties, unsetProperties } = integrations.POSTHOG; if (superProperties && Object.keys(superProperties).length > 0) { posthog.register(superProperties); @@ -200,15 +149,6 @@ class Posthog { this.processSuperProperties(rudderElement); } - - isLoaded() { - logger.debug('in Posthog isLoaded'); - return !!(window.posthog && window.posthog.__loaded); - } - - isReady() { - return !!(window.posthog && window.posthog.__loaded); - } } export default Posthog; diff --git a/src/integrations/Posthog/index.js b/src/integrations/Posthog/index.js index 5fc263299..d041706ee 100644 --- a/src/integrations/Posthog/index.js +++ b/src/integrations/Posthog/index.js @@ -1,3 +1 @@ -import Posthog from './browser'; - -export { Posthog }; +export { default as Posthog } from './browser'; diff --git a/src/integrations/Posthog/nativeSdkLoader.js b/src/integrations/Posthog/nativeSdkLoader.js new file mode 100644 index 000000000..c895c01ff --- /dev/null +++ b/src/integrations/Posthog/nativeSdkLoader.js @@ -0,0 +1,48 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + !(function (t, e) { + var o, n, p, r; + e.__SV || + ((window.posthog = e), + (e._i = []), + (e.init = function (i, s, a) { + function g(t, e) { + var o = e.split('.'); + 2 == o.length && ((t = t[o[0]]), (e = o[1])), + (t[e] = function () { + t.push([e].concat(Array.prototype.slice.call(arguments, 0))); + }); + } + ((p = t.createElement('script')).type = 'text/javascript'), + (p.async = !0), + p.setAttribute('data-loader', LOAD_ORIGIN), + (p.src = s.api_host + '/static/array.js'), + (r = t.getElementsByTagName('script')[0]).parentNode.insertBefore(p, r); + var u = e; + for ( + void 0 !== a ? (u = e[a] = []) : (a = 'posthog'), + u.people = u.people || [], + u.toString = function (t) { + var e = 'posthog'; + return 'posthog' !== a && (e += '.' + a), t || (e += ' (stub)'), e; + }, + u.people.toString = function () { + return u.toString(1) + '.people (stub)'; + }, + o = + 'capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags'.split( + ' ', + ), + n = 0; + n < o.length; + n++ + ) + g(u, o[n]); + e._i.push([i, s, a]); + }), + (e.__SV = 1)); + })(document, window.posthog || []); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Posthog/utils.js b/src/integrations/Posthog/utils.js new file mode 100644 index 000000000..3c9fc147e --- /dev/null +++ b/src/integrations/Posthog/utils.js @@ -0,0 +1,36 @@ +/** + * Returns xhrHeaders object + * @param {*} config + * @returns + */ +const getXhrHeaders = (config) => { + const xhrHeaders = {}; + if (config.xhrHeaders && config.xhrHeaders.length > 0) { + config.xhrHeaders.forEach((header) => { + if (header?.key?.trim() !== '' && header?.value?.trim() !== '') { + xhrHeaders[header.key] = header.value; + } + }); + } + + return xhrHeaders; +}; + +/** + * Returns propertyBlackList array + * @param {*} config + * @returns + */ +const getPropertyBlackList = (config) => { + const propertyBlackList = []; + if (config.propertyBlackList && config.propertyBlackList.length > 0) { + config.propertyBlackList.forEach((element) => { + if (element?.property?.trim() !== '') { + propertyBlackList.push(element.property); + } + }); + } + return propertyBlackList; +}; + +export { getXhrHeaders, getPropertyBlackList }; diff --git a/src/integrations/ProfitWell/browser.js b/src/integrations/ProfitWell/browser.js index 1ded64388..6c40f18fd 100644 --- a/src/integrations/ProfitWell/browser.js +++ b/src/integrations/ProfitWell/browser.js @@ -1,8 +1,8 @@ /* eslint-disable class-methods-use-this */ import get from 'get-value'; import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class ProfitWell { constructor(config, analytics, destinationInfo) { @@ -27,37 +27,17 @@ class ProfitWell { logger.debug('===[ProfitWell]: Public API Key not found==='); return; } - - window.publicApiKey = this.publicApiKey; - - const scriptTag = document.createElement('script'); - scriptTag.setAttribute('id', 'profitwell-js'); - scriptTag.setAttribute('data-pw-auth', window.publicApiKey); - document.body.appendChild(scriptTag); - - (function (i, s, o, g, r, a, m) { - i[o] = - i[o] || - function () { - (i[o].q = i[o].q || []).push(arguments); - }; - a = s.createElement(g); - m = s.getElementsByTagName(g)[0]; - a.async = 1; - a.setAttribute('data-loader', LOAD_ORIGIN); - a.src = `${r}?auth=${window.publicApiKey}`; - m.parentNode.insertBefore(a, m); - })(window, document, 'profitwell', 'script', 'https://public.profitwell.com/js/profitwell.js'); + loadNativeSdk(this.publicApiKey); } isLoaded() { logger.debug('===In isLoaded ProfitWell==='); - return !!(window.profitwell && window.profitwell.length !== 0); + return !!(window.profitwell && window.profitwell.length > 0); } isReady() { logger.debug('===In isReady ProfitWell==='); - return !!(window.profitwell && window.profitwell.length !== 0); + return !!(window.profitwell && window.profitwell.length > 0); } identify(rudderElement) { diff --git a/src/integrations/ProfitWell/index.js b/src/integrations/ProfitWell/index.js index 4ad33777e..78fcf7275 100644 --- a/src/integrations/ProfitWell/index.js +++ b/src/integrations/ProfitWell/index.js @@ -1,3 +1 @@ -import ProfitWell from './browser'; - -export { ProfitWell }; +export { default as ProfitWell } from './browser'; diff --git a/src/integrations/ProfitWell/nativeSdkLoader.js b/src/integrations/ProfitWell/nativeSdkLoader.js new file mode 100644 index 000000000..ae0aac11e --- /dev/null +++ b/src/integrations/ProfitWell/nativeSdkLoader.js @@ -0,0 +1,25 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(publicApiKey) { + window.publicApiKey = publicApiKey; + + const scriptTag = document.createElement('script'); + scriptTag.setAttribute('id', 'profitwell-js'); + scriptTag.setAttribute('data-pw-auth', window.publicApiKey); + document.body.appendChild(scriptTag); + (function (i, s, o, g, r, a, m) { + i[o] = + i[o] || + function () { + (i[o].q = i[o].q || []).push(arguments); + }; + a = s.createElement(g); + m = s.getElementsByTagName(g)[0]; + a.async = 1; + a.setAttribute('data-loader', LOAD_ORIGIN); + a.src = `${r}?auth=${window.publicApiKey}`; + m.parentNode.insertBefore(a, m); + })(window, document, 'profitwell', 'script', 'https://public.profitwell.com/js/profitwell.js'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Qualaroo/index.js b/src/integrations/Qualaroo/index.js index d715506a5..6212f5afc 100644 --- a/src/integrations/Qualaroo/index.js +++ b/src/integrations/Qualaroo/index.js @@ -1,3 +1 @@ -import Qualaroo from './browser'; - -export { Qualaroo }; +export { default as Qualaroo } from './browser'; diff --git a/src/integrations/Qualaroo/utils.js b/src/integrations/Qualaroo/utils.js index 829c27146..ee27cd13f 100644 --- a/src/integrations/Qualaroo/utils.js +++ b/src/integrations/Qualaroo/utils.js @@ -1,3 +1,4 @@ +/* eslint-disable func-names */ /* eslint-disable no-underscore-dangle */ import { getHashFromArray } from '../../utils/commonUtils'; diff --git a/src/integrations/Qualtrics/browser.js b/src/integrations/Qualtrics/browser.js index d0017e4d1..10d55ece8 100644 --- a/src/integrations/Qualtrics/browser.js +++ b/src/integrations/Qualtrics/browser.js @@ -1,17 +1,8 @@ -/* eslint-disable no-var */ -/* eslint-disable new-cap */ -/* eslint-disable no-empty */ -/* eslint-disable no-unneeded-ternary */ -/* eslint-disable class-methods-use-this */ -/* eslint-disable no-nested-ternary */ /* eslint-disable no-underscore-dangle */ -/* eslint-disable no-unused-expressions */ -// eslint-disable-next-line no-nested-ternary -// eslint-disable-next-line class-methods-use-this - +/* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class Qualtrics { constructor(config, analytics, destinationInfo) { @@ -42,80 +33,7 @@ class Qualtrics { return; } - const projectIdFormatted = this.projectId.replace(/_/g, '').toLowerCase().trim(); - const requestUrlFormatted = `https://${projectIdFormatted}-${this.brandId}.siteintercept.qualtrics.com/SIE/?Q_ZID=${this.projectId}`; - const requestIdFormatted = `QSI_S_${this.projectId}`; - - (function () { - var g = function (e, h, f, g) { - this.get = function (a) { - for (var a = `${a}=`, c = document.cookie.split(';'), b = 0, e = c.length; b < e; b++) { - for (var d = c[b]; d.charAt(0) == ' '; ) d = d.substring(1, d.length); - if (d.indexOf(a) == 0) return d.substring(a.length, d.length); - } - return null; - }; - this.set = function (a, c) { - var b = ''; - var b = new Date(); - b.setTime(b.getTime() + 6048e5); - b = `; expires=${b.toGMTString()}`; - document.cookie = `${a}=${c}${b}; path=/; `; - }; - this.check = function () { - var a = this.get(f); - if (a) a = a.split(':'); - else if (e != 100) - h == 'v' && (e = Math.random() >= e / 100 ? 0 : 100), - (a = [h, e, 0]), - this.set(f, a.join(':')); - else return !0; - var c = a[1]; - if (c == 100) return !0; - switch (a[0]) { - case 'v': - return !1; - case 'r': - return (c = a[2] % Math.floor(100 / c)), a[2]++, this.set(f, a.join(':')), !c; - } - return !0; - }; - this.go = function () { - if (this.check()) { - var a = document.createElement('script'); - a.type = 'text/javascript'; - a.setAttribute('data-loader', LOAD_ORIGIN); - a.src = g; - document.body && document.body.appendChild(a); - } - }; - this.start = function () { - var t = this; - document.readyState !== 'complete' - ? window.addEventListener - ? window.addEventListener( - 'load', - function () { - t.go(); - }, - !1, - ) - : window.attachEvent && - window.attachEvent('onload', function () { - t.go(); - }) - : t.go(); - }; - }; - try { - new g(100, 'r', requestIdFormatted, requestUrlFormatted).start(); - } catch (i) {} - })(); - - const div = document.createElement('div'); - div.setAttribute('id', String(this.projectId)); - window._qsie = window._qsie || []; - document.getElementsByTagName('head')[0].appendChild(div); + loadNativeSdk(this.projectId, this.brandId); } isLoaded() { @@ -142,8 +60,7 @@ class Qualtrics { } const { name, category, properties } = message; - const categoryField = - category || (properties && properties.category ? properties.category : null); + const categoryField = category || properties?.category || null; if (!categoryField && !name) { logger.debug('generic title is disabled and no name or category field found'); diff --git a/src/integrations/Qualtrics/index.js b/src/integrations/Qualtrics/index.js index ff114d1c3..5a4af8776 100644 --- a/src/integrations/Qualtrics/index.js +++ b/src/integrations/Qualtrics/index.js @@ -1,3 +1 @@ -import Qualtrics from './browser'; - -export { Qualtrics }; +export { default as Qualtrics } from './browser'; diff --git a/src/integrations/Qualtrics/nativeSdkLoader.js b/src/integrations/Qualtrics/nativeSdkLoader.js new file mode 100644 index 000000000..f2c229def --- /dev/null +++ b/src/integrations/Qualtrics/nativeSdkLoader.js @@ -0,0 +1,79 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(projectId, brandId) { + const projectIdFormatted = projectId.replace(/_/g, '').toLowerCase().trim(); + const requestUrlFormatted = `https://${projectIdFormatted}-${brandId}.siteintercept.qualtrics.com/SIE/?Q_ZID=${projectId}`; + const requestIdFormatted = `QSI_S_${projectId}`; + + (function () { + var g = function (e, h, f, g) { + this.get = function (a) { + for (var a = `${a}=`, c = document.cookie.split(';'), b = 0, e = c.length; b < e; b++) { + for (var d = c[b]; d.charAt(0) == ' '; ) d = d.substring(1, d.length); + if (d.indexOf(a) == 0) return d.substring(a.length, d.length); + } + return null; + }; + this.set = function (a, c) { + var b = ''; + var b = new Date(); + b.setTime(b.getTime() + 6048e5); + b = `; expires=${b.toGMTString()}`; + document.cookie = `${a}=${c}${b}; path=/; `; + }; + this.check = function () { + var a = this.get(f); + if (a) a = a.split(':'); + else if (e != 100) + h == 'v' && (e = Math.random() >= e / 100 ? 0 : 100), + (a = [h, e, 0]), + this.set(f, a.join(':')); + else return !0; + var c = a[1]; + if (c == 100) return !0; + switch (a[0]) { + case 'v': + return !1; + case 'r': + return (c = a[2] % Math.floor(100 / c)), a[2]++, this.set(f, a.join(':')), !c; + } + return !0; + }; + this.go = function () { + if (this.check()) { + var a = document.createElement('script'); + a.type = 'text/javascript'; + a.setAttribute('data-loader', LOAD_ORIGIN); + a.src = g; + document.body && document.body.appendChild(a); + } + }; + this.start = function () { + var t = this; + document.readyState !== 'complete' + ? window.addEventListener + ? window.addEventListener( + 'load', + function () { + t.go(); + }, + !1, + ) + : window.attachEvent && + window.attachEvent('onload', function () { + t.go(); + }) + : t.go(); + }; + }; + try { + new g(100, 'r', requestIdFormatted, requestUrlFormatted).start(); + } catch (i) {} + })(); + const div = document.createElement('div'); + div.setAttribute('id', String(projectId)); + window._qsie = window._qsie || []; + document.getElementsByTagName('head')[0].appendChild(div); +} + +export { loadNativeSdk }; diff --git a/src/integrations/QuantumMetric/index.js b/src/integrations/QuantumMetric/index.js index 13d1e9c76..08caa1256 100644 --- a/src/integrations/QuantumMetric/index.js +++ b/src/integrations/QuantumMetric/index.js @@ -1,3 +1 @@ -import QuantumMetric from './browser'; - -export { QuantumMetric }; +export { default as QuantumMetric } from './browser'; diff --git a/src/integrations/QuoraPixel/browser.js b/src/integrations/QuoraPixel/browser.js index 88494193c..774d5c90e 100644 --- a/src/integrations/QuoraPixel/browser.js +++ b/src/integrations/QuoraPixel/browser.js @@ -1,8 +1,8 @@ -/* eslint-disable */ +/* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { NAME } from './constants'; import { getHashFromArrayWithDuplicate } from '../../utils/commonUtils'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class QuoraPixel { constructor(config, analytics, destinationInfo) { @@ -21,21 +21,7 @@ class QuoraPixel { } loadScript() { - !(function (q, e, v, n, t, s) { - if (q.qp) return; - n = q.qp = function () { - n.qp ? n.qp.apply(n, arguments) : n.queue.push(arguments); - }; - n.queue = []; - t = document.createElement(e); - t.async = !0; - t.src = v; - t.setAttribute('data-loader', LOAD_ORIGIN); - s = document.getElementsByTagName(e)[0]; - s.parentNode.insertBefore(t, s); - })(window, 'script', 'https://a.quora.com/qevents.js'); - window.qp('init', this.pixelId); - window.qp('track', 'ViewContent'); + loadNativeSdk(this.pixelId); } init() { diff --git a/src/integrations/QuoraPixel/index.js b/src/integrations/QuoraPixel/index.js index 9c783434f..24f88b03c 100644 --- a/src/integrations/QuoraPixel/index.js +++ b/src/integrations/QuoraPixel/index.js @@ -1,3 +1 @@ -import QuoraPixel from './browser'; - -export { QuoraPixel }; +export { default as QuoraPixel } from './browser'; diff --git a/src/integrations/QuoraPixel/nativeSdkLoader.js b/src/integrations/QuoraPixel/nativeSdkLoader.js new file mode 100644 index 000000000..ec58bc7b7 --- /dev/null +++ b/src/integrations/QuoraPixel/nativeSdkLoader.js @@ -0,0 +1,21 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(pixelId) { + !(function (q, e, v, n, t, s) { + if (q.qp) return; + n = q.qp = function () { + n.qp ? n.qp.apply(n, arguments) : n.queue.push(arguments); + }; + n.queue = []; + t = document.createElement(e); + t.async = !0; + t.src = v; + t.setAttribute('data-loader', LOAD_ORIGIN); + s = document.getElementsByTagName(e)[0]; + s.parentNode.insertBefore(t, s); + })(window, 'script', 'https://a.quora.com/qevents.js'); + window.qp('init', pixelId); + window.qp('track', 'ViewContent'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/RedditPixel/browser.js b/src/integrations/RedditPixel/browser.js index f18ccf8ce..3b983be27 100644 --- a/src/integrations/RedditPixel/browser.js +++ b/src/integrations/RedditPixel/browser.js @@ -1,16 +1,9 @@ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ -/* eslint-disable prefer-rest-params */ -/* eslint-disable prefer-spread */ -/* eslint-disable no-multi-assign */ -/* eslint-disable func-names */ -/* eslint-disable no-var */ -/* eslint-disable vars-on-top */ -/* eslint-disable no-unused-expressions */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; import { getHashFromArrayWithDuplicate, getEventMappingFromConfig } from '../../utils/commonUtils'; +import { loadNativeSdk } from './nativeSdkLoader'; class RedditPixel { constructor(config, analytics, destinationInfo) { @@ -31,21 +24,7 @@ class RedditPixel { init() { logger.debug('===In init RedditPixel==='); - !(function (w, d) { - if (!w.rdt) { - var p = (w.rdt = function () { - p.sendEvent ? p.sendEvent.apply(p, arguments) : p.callQueue.push(arguments); - }); - p.callQueue = []; - var t = d.createElement('script'); - (t.src = 'https://www.redditstatic.com/ads/pixel.js'), (t.async = !0); - t.setAttribute('data-loader', LOAD_ORIGIN); - var s = d.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(t, s); - } - })(window, document); - - window.rdt('init', this.advertiserId); + loadNativeSdk(this.advertiserId); } isLoaded() { diff --git a/src/integrations/RedditPixel/index.js b/src/integrations/RedditPixel/index.js index 5d29eb396..cea96c466 100644 --- a/src/integrations/RedditPixel/index.js +++ b/src/integrations/RedditPixel/index.js @@ -1,3 +1 @@ -import RedditPixel from './browser'; - -export { RedditPixel }; +export { default as RedditPixel } from './browser'; diff --git a/src/integrations/RedditPixel/nativeSdkLoader.js b/src/integrations/RedditPixel/nativeSdkLoader.js new file mode 100644 index 000000000..a5b9fa5fe --- /dev/null +++ b/src/integrations/RedditPixel/nativeSdkLoader.js @@ -0,0 +1,21 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(advertiserId) { + !(function (w, d) { + if (!w.rdt) { + var p = (w.rdt = function () { + p.sendEvent ? p.sendEvent.apply(p, arguments) : p.callQueue.push(arguments); + }); + p.callQueue = []; + var t = d.createElement('script'); + (t.src = 'https://www.redditstatic.com/ads/pixel.js'), (t.async = !0); + t.setAttribute('data-loader', LOAD_ORIGIN); + var s = d.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(t, s); + } + })(window, document); + + window.rdt('init', advertiserId); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Refiner/browser.js b/src/integrations/Refiner/browser.js index 6a6a6caea..854ac9640 100644 --- a/src/integrations/Refiner/browser.js +++ b/src/integrations/Refiner/browser.js @@ -1,7 +1,7 @@ -/* eslint-disable */ +/* eslint-disable no-underscore-dangle */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; import { replaceUserTraits, replaceAccountTraits } from './utils'; class Refiner { @@ -22,20 +22,7 @@ class Refiner { } loadScript() { - window._refinerQueue = window._refinerQueue || []; - this._refiner = function () { - window._refinerQueue.push(arguments); - }; - (function () { - var a = document.createElement('script'); - a.setAttribute('data-loader', LOAD_ORIGIN); - a.type = 'text/javascript'; - a.async = !0; - a.src = 'https://js.refiner.io/v001/client.js'; - var b = document.getElementsByTagName('script')[0]; - b.parentNode.insertBefore(a, b); - })(); - this._refiner('setProject', this.apiKey); + loadNativeSdk(this.apiKey); } init() { @@ -57,7 +44,7 @@ class Refiner { logger.debug('===In Refiner Identify==='); const { message } = rudderElement; const { userId, traits, context } = message; - const email = message.traits?.email || message.context?.traits?.email; + const email = traits?.email || context?.traits?.email; if (!userId && !email) { logger.error('either one userId or email is required'); return; @@ -77,14 +64,25 @@ class Refiner { track(rudderElement) { logger.debug('===In Refiner track==='); const { event } = rudderElement.message; + + if (!event) { + logger.error('Event name not present'); + return; + } + + if (typeof event !== 'string') { + logger.error('Event name should be string'); + return; + } + this._refiner('trackEvent', event); } group(rudderElement) { logger.debug('===In Refiner Group==='); const { message } = rudderElement; - const { userId, groupId, traits } = message; - const userEmail = message.context?.traits?.email; + const { userId, groupId, traits, context } = message; + const userEmail = context?.traits?.email; if (!userId && !userEmail) { logger.error('either one userId or email is required'); return; diff --git a/src/integrations/Refiner/index.js b/src/integrations/Refiner/index.js index eb9287e6d..6e8faf0f7 100644 --- a/src/integrations/Refiner/index.js +++ b/src/integrations/Refiner/index.js @@ -1,3 +1 @@ -import Refiner from './browser'; - -export { Refiner }; +export { default as Refiner } from './browser'; diff --git a/src/integrations/Refiner/nativeSdkLoader.js b/src/integrations/Refiner/nativeSdkLoader.js new file mode 100644 index 000000000..c89b21232 --- /dev/null +++ b/src/integrations/Refiner/nativeSdkLoader.js @@ -0,0 +1,20 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(apiKey) { + window._refinerQueue = window._refinerQueue || []; + window._refiner = function () { + window._refinerQueue.push(arguments); + }; + window._refiner('setProject', apiKey); + (function () { + var a = document.createElement('script'); + a.setAttribute('data-loader', LOAD_ORIGIN); + a.type = 'text/javascript'; + a.async = !0; + a.src = 'https://js.refiner.io/v001/client.js'; + var b = document.getElementsByTagName('script')[0]; + b.parentNode.insertBefore(a, b); + })(); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Rockerbox/browser.js b/src/integrations/Rockerbox/browser.js index d331af14e..4824e90a3 100644 --- a/src/integrations/Rockerbox/browser.js +++ b/src/integrations/Rockerbox/browser.js @@ -1,8 +1,8 @@ -/* eslint-disable class-methods-use-this,prefer-rest-params, no-param-reassign */ +/* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { getHashFromArray } from '../../utils/commonUtils'; +import { loadNativeSdk } from './nativeSdkLoader'; class Rockerbox { constructor(config, analytics, destinationInfo) { @@ -25,32 +25,7 @@ class Rockerbox { init() { logger.debug('=== In init Rockerbox ==='); - const host = this.customDomain ? this.customDomain : 'getrockerbox.com'; - const library = this.customDomain && this.enableCookieSync ? 'wxyz.rb' : 'wxyz.v2'; - (function (d, RB) { - if (!window.RB) { - window.RB = RB; - RB.queue = RB.queue || []; - RB.track = - RB.track || - function () { - RB.queue.push(Array.prototype.slice.call(arguments)); - }; - RB.initialize = function (s) { - RB.source = s; - }; - const a = d.createElement('script'); - a.type = 'text/javascript'; - a.async = !0; - a.src = `https://${host}/assets/${library}.js`; - a.dataset.loader = LOAD_ORIGIN; - const f = d.getElementsByTagName('script')[0]; - f.parentNode.insertBefore(a, f); - } - })(document, window.RB || {}); - window.RB.disablePushState = true; - window.RB.queue = []; - window.RB.initialize(this.clientAuthId); + loadNativeSdk(this.customDomain, this.enableCookieSync, this.clientAuthId); } isLoaded() { diff --git a/src/integrations/Rockerbox/index.js b/src/integrations/Rockerbox/index.js index 69abd270e..b50aa2d14 100644 --- a/src/integrations/Rockerbox/index.js +++ b/src/integrations/Rockerbox/index.js @@ -1,3 +1 @@ -import Rockerbox from './browser'; - -export { Rockerbox }; +export { default as Rockerbox } from './browser'; diff --git a/src/integrations/Rockerbox/nativeSdkLoader.js b/src/integrations/Rockerbox/nativeSdkLoader.js new file mode 100644 index 000000000..592a8ae44 --- /dev/null +++ b/src/integrations/Rockerbox/nativeSdkLoader.js @@ -0,0 +1,32 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(customDomain, enableCookieSync, clientAuthId) { + const host = customDomain ? customDomain : 'getrockerbox.com'; + const library = customDomain && enableCookieSync ? 'wxyz.rb' : 'wxyz.v2'; + (function (d, RB) { + if (!window.RB) { + window.RB = RB; + RB.queue = RB.queue || []; + RB.track = + RB.track || + function () { + RB.queue.push(Array.prototype.slice.call(arguments)); + }; + RB.initialize = function (s) { + RB.source = s; + }; + const a = d.createElement('script'); + a.type = 'text/javascript'; + a.async = !0; + a.src = `https://${host}/assets/${library}.js`; + a.dataset.loader = LOAD_ORIGIN; + const f = d.getElementsByTagName('script')[0]; + f.parentNode.insertBefore(a, f); + } + })(document, window.RB || {}); + window.RB.disablePushState = true; + window.RB.queue = []; + window.RB.initialize(clientAuthId); +} + +export { loadNativeSdk }; diff --git a/src/integrations/RollBar/browser.js b/src/integrations/RollBar/browser.js index fffe11854..14b831093 100644 --- a/src/integrations/RollBar/browser.js +++ b/src/integrations/RollBar/browser.js @@ -1,9 +1,9 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ - import { isObject } from '../../utils/utils'; import logger from '../../utils/logUtil'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class RollBar { constructor(config, analytics, destinationInfo) { @@ -29,7 +29,7 @@ class RollBar { init() { logger.debug('===in init RollBar==='); - var _rollbarConfig = { + const _rollbarConfig = { accessToken: this.accessToken, captureUncaught: this.captureUncaughtException, captureUnhandledRejections: this.captureUnhandledRejections, @@ -44,398 +44,21 @@ class RollBar { }, }, }; - var msg = this.ignoredMessages; - if (msg.length > 0) { - var ret = []; + const { ignoredMessages } = this; + if (ignoredMessages.length > 0) { + const ret = []; // clean out array - for (var x = 0; x < msg.length; x++) { - if (msg[x] !== null && msg[x].singleIgnoredMessage !== '') - ret.push(msg[x].singleIgnoredMessage); - } + ignoredMessages.forEach((message) => { + if ( + ignoredMessages[message] !== null && + ignoredMessages[message].singleIgnoredMessage !== '' + ) + ret.push(ignoredMessages[message].singleIgnoredMessage); + }); _rollbarConfig.ignoredMessages = ret; } - // Rollbar Snippet - !(function (r) { - var e = {}; - function o(n) { - if (e[n]) return e[n].exports; - var t = (e[n] = { i: n, l: !1, exports: {} }); - return r[n].call(t.exports, t, t.exports, o), (t.l = !0), t.exports; - } - (o.m = r), - (o.c = e), - (o.d = function (r, e, n) { - o.o(r, e) || Object.defineProperty(r, e, { enumerable: !0, get: n }); - }), - (o.r = function (r) { - 'undefined' != typeof Symbol && - Symbol.toStringTag && - Object.defineProperty(r, Symbol.toStringTag, { value: 'Module' }), - Object.defineProperty(r, '__esModule', { value: !0 }); - }), - (o.t = function (r, e) { - if ((1 & e && (r = o(r)), 8 & e)) return r; - if (4 & e && 'object' == typeof r && r && r.__esModule) return r; - var n = Object.create(null); - if ( - (o.r(n), - Object.defineProperty(n, 'default', { enumerable: !0, value: r }), - 2 & e && 'string' != typeof r) - ) - for (var t in r) - o.d( - n, - t, - function (e) { - return r[e]; - }.bind(null, t), - ); - return n; - }), - (o.n = function (r) { - var e = - r && r.__esModule - ? function () { - return r.default; - } - : function () { - return r; - }; - return o.d(e, 'a', e), e; - }), - (o.o = function (r, e) { - return Object.prototype.hasOwnProperty.call(r, e); - }), - (o.p = ''), - o((o.s = 0)); - })([ - function (r, e, o) { - 'use strict'; - var n = o(1), - t = o(5); - (_rollbarConfig = _rollbarConfig || {}), - (_rollbarConfig.rollbarJsUrl = - _rollbarConfig.rollbarJsUrl || - 'https://cdn.rollbar.com/rollbarjs/refs/tags/v2.24.0/rollbar.min.js'), - (_rollbarConfig.async = void 0 === _rollbarConfig.async || _rollbarConfig.async); - var a = n.setupShim(window, _rollbarConfig), - l = t(_rollbarConfig); - (window.rollbar = n.Rollbar), - a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l); - }, - function (r, e, o) { - 'use strict'; - var n = o(2), - t = o(3); - function a(r) { - return function () { - try { - return r.apply(this, arguments); - } catch (r) { - try { - console.error('[Rollbar]: Internal error', r); - } catch (r) {} - } - }; - } - var l = 0; - function i(r, e) { - (this.options = r), (this._rollbarOldOnError = null); - var o = l++; - (this.shimId = function () { - return o; - }), - 'undefined' != typeof window && - window._rollbarShims && - (window._rollbarShims[o] = { handler: e, messages: [] }); - } - var s = o(4), - d = function (r, e) { - return new i(r, e); - }, - c = function (r) { - return new s(d, r); - }; - function u(r) { - return a(function () { - var e = this, - o = Array.prototype.slice.call(arguments, 0), - n = { shim: e, method: r, args: o, ts: new Date() }; - window._rollbarShims[this.shimId()].messages.push(n); - }); - } - (i.prototype.loadFull = function (r, e, o, n, t) { - var l = !1, - i = e.createElement('script'), - s = e.getElementsByTagName('script')[0], - d = s.parentNode; - (i.crossOrigin = ''), - (i.src = n.rollbarJsUrl), - o || (i.async = !0), - (i.onload = i.onreadystatechange = - a(function () { - if ( - !( - l || - (this.readyState && - 'loaded' !== this.readyState && - 'complete' !== this.readyState) - ) - ) { - i.onload = i.onreadystatechange = null; - try { - d.removeChild(i); - } catch (r) {} - (l = !0), - (function () { - var e; - if (void 0 === r._rollbarDidLoad) { - e = new Error('rollbar.js did not load'); - for (var o, n, a, l, i = 0; (o = r._rollbarShims[i++]); ) - for (o = o.messages || []; (n = o.shift()); ) - for (a = n.args || [], i = 0; i < a.length; ++i) - if ('function' == typeof (l = a[i])) { - l(e); - break; - } - } - 'function' == typeof t && t(e); - })(); - } - })), - d.insertBefore(i, s); - }), - (i.prototype.wrap = function (r, e, o) { - try { - var n; - if ( - ((n = - 'function' == typeof e - ? e - : function () { - return e || {}; - }), - 'function' != typeof r) - ) - return r; - if (r._isWrap) return r; - if ( - !r._rollbar_wrapped && - ((r._rollbar_wrapped = function () { - o && 'function' == typeof o && o.apply(this, arguments); - try { - return r.apply(this, arguments); - } catch (o) { - var e = o; - throw ( - (e && - ('string' == typeof e && (e = new String(e)), - (e._rollbarContext = n() || {}), - (e._rollbarContext._wrappedSource = r.toString()), - (window._rollbarWrappedError = e)), - e) - ); - } - }), - (r._rollbar_wrapped._isWrap = !0), - r.hasOwnProperty) - ) - for (var t in r) r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); - return r._rollbar_wrapped; - } catch (e) { - return r; - } - }); - for ( - var p = - 'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad'.split( - ',', - ), - f = 0; - f < p.length; - ++f - ) - i.prototype[p[f]] = u(p[f]); - r.exports = { - setupShim: function (r, e) { - if (r) { - var o = e.globalAlias || 'Rollbar'; - if ('object' == typeof r[o]) return r[o]; - (r._rollbarShims = {}), (r._rollbarWrappedError = null); - var l = new c(e); - return a(function () { - e.captureUncaught && - ((l._rollbarOldOnError = r.onerror), - n.captureUncaughtExceptions(r, l, !0), - e.wrapGlobalEventHandlers && t(r, l, !0)), - e.captureUnhandledRejections && n.captureUnhandledRejections(r, l, !0); - var a = e.autoInstrument; - return ( - !1 !== e.enabled && - (void 0 === a || !0 === a || ('object' == typeof a && a.network)) && - r.addEventListener && - (r.addEventListener('load', l.captureLoad.bind(l)), - r.addEventListener('DOMContentLoaded', l.captureDomContentLoaded.bind(l))), - (r[o] = l), - l - ); - })(); - } - }, - Rollbar: c, - }; - }, - function (r, e, o) { - 'use strict'; - function n(r, e, o, n) { - r._rollbarWrappedError && - (n[4] || (n[4] = r._rollbarWrappedError), - n[5] || (n[5] = r._rollbarWrappedError._rollbarContext), - (r._rollbarWrappedError = null)); - var t = e.handleUncaughtException.apply(e, n); - o && o.apply(r, n), 'anonymous' === t && (e.anonymousErrorsPending += 1); - } - r.exports = { - captureUncaughtExceptions: function (r, e, o) { - if (r) { - var t; - if ('function' == typeof e._rollbarOldOnError) t = e._rollbarOldOnError; - else if (r.onerror) { - for (t = r.onerror; t._rollbarOldOnError; ) t = t._rollbarOldOnError; - e._rollbarOldOnError = t; - } - e.handleAnonymousErrors(); - var a = function () { - var o = Array.prototype.slice.call(arguments, 0); - n(r, e, t, o); - }; - o && (a._rollbarOldOnError = t), (r.onerror = a); - } - }, - captureUnhandledRejections: function (r, e, o) { - if (r) { - 'function' == typeof r._rollbarURH && - r._rollbarURH.belongsToShim && - r.removeEventListener('unhandledrejection', r._rollbarURH); - var n = function (r) { - var o, n, t; - try { - o = r.reason; - } catch (r) { - o = void 0; - } - try { - n = r.promise; - } catch (r) { - n = '[unhandledrejection] error getting `promise` from event'; - } - try { - (t = r.detail), !o && t && ((o = t.reason), (n = t.promise)); - } catch (r) {} - o || (o = '[unhandledrejection] error getting `reason` from event'), - e && e.handleUnhandledRejection && e.handleUnhandledRejection(o, n); - }; - (n.belongsToShim = o), - (r._rollbarURH = n), - r.addEventListener('unhandledrejection', n); - } - }, - }; - }, - function (r, e, o) { - 'use strict'; - function n(r, e, o) { - if (e.hasOwnProperty && e.hasOwnProperty('addEventListener')) { - for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim; ) - n = n._rollbarOldAdd; - var t = function (e, o, t) { - n.call(this, e, r.wrap(o), t); - }; - (t._rollbarOldAdd = n), (t.belongsToShim = o), (e.addEventListener = t); - for (var a = e.removeEventListener; a._rollbarOldRemove && a.belongsToShim; ) - a = a._rollbarOldRemove; - var l = function (r, e, o) { - a.call(this, r, (e && e._rollbar_wrapped) || e, o); - }; - (l._rollbarOldRemove = a), (l.belongsToShim = o), (e.removeEventListener = l); - } - } - r.exports = function (r, e, o) { - if (r) { - var t, - a, - l = - 'EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload'.split( - ',', - ); - for (t = 0; t < l.length; ++t) - r[(a = l[t])] && r[a].prototype && n(e, r[a].prototype, o); - } - }; - }, - function (r, e, o) { - 'use strict'; - function n(r, e) { - (this.impl = r(e, this)), - (this.options = e), - (function (r) { - for ( - var e = function (r) { - return function () { - var e = Array.prototype.slice.call(arguments, 0); - if (this.impl[r]) return this.impl[r].apply(this.impl, e); - }; - }, - o = - 'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad'.split( - ',', - ), - n = 0; - n < o.length; - n++ - ) - r[o[n]] = e(o[n]); - })(n.prototype); - } - (n.prototype._swapAndProcessMessages = function (r, e) { - var o, n, t; - for (this.impl = r(this.options); (o = e.shift()); ) - (n = o.method), - (t = o.args), - this[n] && - 'function' == typeof this[n] && - ('captureDomContentLoaded' === n || 'captureLoad' === n - ? this[n].apply(this, [t[0], o.ts]) - : this[n].apply(this, t)); - return this; - }), - (r.exports = n); - }, - function (r, e, o) { - 'use strict'; - r.exports = function (r) { - return function (e) { - if (!e && !window._rollbarInitialized) { - for ( - var o, - n, - t = (r = r || {}).globalAlias || 'Rollbar', - a = window.rollbar, - l = function (r) { - return new a(r); - }, - i = 0; - (o = window._rollbarShims[i++]); - ) - n || (n = o.handler), o.handler._swapAndProcessMessages(l, o.messages); - (window[t] = n), (window._rollbarInitialized = !0); - } - }; - }; - }, - ]); - // End Rollbar Snippet + loadNativeSdk(_rollbarConfig); } isLoaded() { @@ -452,8 +75,8 @@ class RollBar { identify(rudderElement) { logger.debug('===In RollBar Identify==='); const { message } = rudderElement; - const { userId } = message; - const { traits } = rudderElement.message.context; + const { userId, context } = message; + const { traits } = context; const person = traits; if (person.name) { diff --git a/src/integrations/RollBar/index.js b/src/integrations/RollBar/index.js index d31ac3ee0..7c09e81ec 100644 --- a/src/integrations/RollBar/index.js +++ b/src/integrations/RollBar/index.js @@ -1,3 +1 @@ -import RollBar from './browser'; - -export { RollBar }; +export { default as RollBar } from './browser'; diff --git a/src/integrations/RollBar/nativeSdkLoader.js b/src/integrations/RollBar/nativeSdkLoader.js new file mode 100644 index 000000000..0de091b86 --- /dev/null +++ b/src/integrations/RollBar/nativeSdkLoader.js @@ -0,0 +1,383 @@ +const loadNativeSdk = (_rollbarConfig) => { + // Rollbar Snippet + !(function (r) { + var e = {}; + function o(n) { + if (e[n]) return e[n].exports; + var t = (e[n] = { i: n, l: !1, exports: {} }); + return r[n].call(t.exports, t, t.exports, o), (t.l = !0), t.exports; + } + (o.m = r), + (o.c = e), + (o.d = function (r, e, n) { + o.o(r, e) || Object.defineProperty(r, e, { enumerable: !0, get: n }); + }), + (o.r = function (r) { + 'undefined' != typeof Symbol && + Symbol.toStringTag && + Object.defineProperty(r, Symbol.toStringTag, { value: 'Module' }), + Object.defineProperty(r, '__esModule', { value: !0 }); + }), + (o.t = function (r, e) { + if ((1 & e && (r = o(r)), 8 & e)) return r; + if (4 & e && 'object' == typeof r && r && r.__esModule) return r; + var n = Object.create(null); + if ( + (o.r(n), + Object.defineProperty(n, 'default', { enumerable: !0, value: r }), + 2 & e && 'string' != typeof r) + ) + for (var t in r) + o.d( + n, + t, + function (e) { + return r[e]; + }.bind(null, t), + ); + return n; + }), + (o.n = function (r) { + var e = + r && r.__esModule + ? function () { + return r.default; + } + : function () { + return r; + }; + return o.d(e, 'a', e), e; + }), + (o.o = function (r, e) { + return Object.prototype.hasOwnProperty.call(r, e); + }), + (o.p = ''), + o((o.s = 0)); + })([ + function (r, e, o) { + 'use strict'; + var n = o(1), + t = o(5); + (_rollbarConfig = _rollbarConfig || {}), + (_rollbarConfig.rollbarJsUrl = + _rollbarConfig.rollbarJsUrl || + 'https://cdn.rollbar.com/rollbarjs/refs/tags/v2.24.0/rollbar.min.js'), + (_rollbarConfig.async = void 0 === _rollbarConfig.async || _rollbarConfig.async); + var a = n.setupShim(window, _rollbarConfig), + l = t(_rollbarConfig); + (window.rollbar = n.Rollbar), + a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l); + }, + function (r, e, o) { + 'use strict'; + var n = o(2), + t = o(3); + function a(r) { + return function () { + try { + return r.apply(this, arguments); + } catch (r) { + try { + console.error('[Rollbar]: Internal error', r); + } catch (r) {} + } + }; + } + var l = 0; + function i(r, e) { + (this.options = r), (this._rollbarOldOnError = null); + var o = l++; + (this.shimId = function () { + return o; + }), + 'undefined' != typeof window && + window._rollbarShims && + (window._rollbarShims[o] = { handler: e, messages: [] }); + } + var s = o(4), + d = function (r, e) { + return new i(r, e); + }, + c = function (r) { + return new s(d, r); + }; + function u(r) { + return a(function () { + var e = this, + o = Array.prototype.slice.call(arguments, 0), + n = { shim: e, method: r, args: o, ts: new Date() }; + window._rollbarShims[this.shimId()].messages.push(n); + }); + } + (i.prototype.loadFull = function (r, e, o, n, t) { + var l = !1, + i = e.createElement('script'), + s = e.getElementsByTagName('script')[0], + d = s.parentNode; + (i.crossOrigin = ''), + (i.src = n.rollbarJsUrl), + o || (i.async = !0), + (i.onload = i.onreadystatechange = + a(function () { + if ( + !( + l || + (this.readyState && + 'loaded' !== this.readyState && + 'complete' !== this.readyState) + ) + ) { + i.onload = i.onreadystatechange = null; + try { + d.removeChild(i); + } catch (r) {} + (l = !0), + (function () { + var e; + if (void 0 === r._rollbarDidLoad) { + e = new Error('rollbar.js did not load'); + for (var o, n, a, l, i = 0; (o = r._rollbarShims[i++]); ) + for (o = o.messages || []; (n = o.shift()); ) + for (a = n.args || [], i = 0; i < a.length; ++i) + if ('function' == typeof (l = a[i])) { + l(e); + break; + } + } + 'function' == typeof t && t(e); + })(); + } + })), + d.insertBefore(i, s); + }), + (i.prototype.wrap = function (r, e, o) { + try { + var n; + if ( + ((n = + 'function' == typeof e + ? e + : function () { + return e || {}; + }), + 'function' != typeof r) + ) + return r; + if (r._isWrap) return r; + if ( + !r._rollbar_wrapped && + ((r._rollbar_wrapped = function () { + o && 'function' == typeof o && o.apply(this, arguments); + try { + return r.apply(this, arguments); + } catch (o) { + var e = o; + throw ( + (e && + ('string' == typeof e && (e = new String(e)), + (e._rollbarContext = n() || {}), + (e._rollbarContext._wrappedSource = r.toString()), + (window._rollbarWrappedError = e)), + e) + ); + } + }), + (r._rollbar_wrapped._isWrap = !0), + r.hasOwnProperty) + ) + for (var t in r) r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); + return r._rollbar_wrapped; + } catch (e) { + return r; + } + }); + for ( + var p = + 'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad'.split( + ',', + ), + f = 0; + f < p.length; + ++f + ) + i.prototype[p[f]] = u(p[f]); + r.exports = { + setupShim: function (r, e) { + if (r) { + var o = e.globalAlias || 'Rollbar'; + if ('object' == typeof r[o]) return r[o]; + (r._rollbarShims = {}), (r._rollbarWrappedError = null); + var l = new c(e); + return a(function () { + e.captureUncaught && + ((l._rollbarOldOnError = r.onerror), + n.captureUncaughtExceptions(r, l, !0), + e.wrapGlobalEventHandlers && t(r, l, !0)), + e.captureUnhandledRejections && n.captureUnhandledRejections(r, l, !0); + var a = e.autoInstrument; + return ( + !1 !== e.enabled && + (void 0 === a || !0 === a || ('object' == typeof a && a.network)) && + r.addEventListener && + (r.addEventListener('load', l.captureLoad.bind(l)), + r.addEventListener('DOMContentLoaded', l.captureDomContentLoaded.bind(l))), + (r[o] = l), + l + ); + })(); + } + }, + Rollbar: c, + }; + }, + function (r, e, o) { + 'use strict'; + function n(r, e, o, n) { + r._rollbarWrappedError && + (n[4] || (n[4] = r._rollbarWrappedError), + n[5] || (n[5] = r._rollbarWrappedError._rollbarContext), + (r._rollbarWrappedError = null)); + var t = e.handleUncaughtException.apply(e, n); + o && o.apply(r, n), 'anonymous' === t && (e.anonymousErrorsPending += 1); + } + r.exports = { + captureUncaughtExceptions: function (r, e, o) { + if (r) { + var t; + if ('function' == typeof e._rollbarOldOnError) t = e._rollbarOldOnError; + else if (r.onerror) { + for (t = r.onerror; t._rollbarOldOnError; ) t = t._rollbarOldOnError; + e._rollbarOldOnError = t; + } + e.handleAnonymousErrors(); + var a = function () { + var o = Array.prototype.slice.call(arguments, 0); + n(r, e, t, o); + }; + o && (a._rollbarOldOnError = t), (r.onerror = a); + } + }, + captureUnhandledRejections: function (r, e, o) { + if (r) { + 'function' == typeof r._rollbarURH && + r._rollbarURH.belongsToShim && + r.removeEventListener('unhandledrejection', r._rollbarURH); + var n = function (r) { + var o, n, t; + try { + o = r.reason; + } catch (r) { + o = void 0; + } + try { + n = r.promise; + } catch (r) { + n = '[unhandledrejection] error getting `promise` from event'; + } + try { + (t = r.detail), !o && t && ((o = t.reason), (n = t.promise)); + } catch (r) {} + o || (o = '[unhandledrejection] error getting `reason` from event'), + e && e.handleUnhandledRejection && e.handleUnhandledRejection(o, n); + }; + (n.belongsToShim = o), (r._rollbarURH = n), r.addEventListener('unhandledrejection', n); + } + }, + }; + }, + function (r, e, o) { + 'use strict'; + function n(r, e, o) { + if (e.hasOwnProperty && e.hasOwnProperty('addEventListener')) { + for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim; ) + n = n._rollbarOldAdd; + var t = function (e, o, t) { + n.call(this, e, r.wrap(o), t); + }; + (t._rollbarOldAdd = n), (t.belongsToShim = o), (e.addEventListener = t); + for (var a = e.removeEventListener; a._rollbarOldRemove && a.belongsToShim; ) + a = a._rollbarOldRemove; + var l = function (r, e, o) { + a.call(this, r, (e && e._rollbar_wrapped) || e, o); + }; + (l._rollbarOldRemove = a), (l.belongsToShim = o), (e.removeEventListener = l); + } + } + r.exports = function (r, e, o) { + if (r) { + var t, + a, + l = + 'EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload'.split( + ',', + ); + for (t = 0; t < l.length; ++t) r[(a = l[t])] && r[a].prototype && n(e, r[a].prototype, o); + } + }; + }, + function (r, e, o) { + 'use strict'; + function n(r, e) { + (this.impl = r(e, this)), + (this.options = e), + (function (r) { + for ( + var e = function (r) { + return function () { + var e = Array.prototype.slice.call(arguments, 0); + if (this.impl[r]) return this.impl[r].apply(this.impl, e); + }; + }, + o = + 'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad'.split( + ',', + ), + n = 0; + n < o.length; + n++ + ) + r[o[n]] = e(o[n]); + })(n.prototype); + } + (n.prototype._swapAndProcessMessages = function (r, e) { + var o, n, t; + for (this.impl = r(this.options); (o = e.shift()); ) + (n = o.method), + (t = o.args), + this[n] && + 'function' == typeof this[n] && + ('captureDomContentLoaded' === n || 'captureLoad' === n + ? this[n].apply(this, [t[0], o.ts]) + : this[n].apply(this, t)); + return this; + }), + (r.exports = n); + }, + function (r, e, o) { + 'use strict'; + r.exports = function (r) { + return function (e) { + if (!e && !window._rollbarInitialized) { + for ( + var o, + n, + t = (r = r || {}).globalAlias || 'Rollbar', + a = window.rollbar, + l = function (r) { + return new a(r); + }, + i = 0; + (o = window._rollbarShims[i++]); + + ) + n || (n = o.handler), o.handler._swapAndProcessMessages(l, o.messages); + (window[t] = n), (window._rollbarInitialized = !0); + } + }; + }; + }, + ]); + // End Rollbar Snippet +}; + +export { loadNativeSdk }; diff --git a/src/integrations/Satismeter/browser.js b/src/integrations/Satismeter/browser.js index d1a9a4769..aa9b47dc7 100644 --- a/src/integrations/Satismeter/browser.js +++ b/src/integrations/Satismeter/browser.js @@ -1,10 +1,9 @@ -/* eslint-disable no-var */ /* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import { NAME } from './constants'; import Logger from '../../utils/logger'; import { recordSatismeterEvents } from './util'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; const logger = new Logger(NAME); class Satismeter { @@ -29,23 +28,7 @@ class Satismeter { init() { logger.debug('===In init Satismeter==='); - (function () { - window.satismeter = - window.satismeter || - function () { - (window.satismeter.q = window.satismeter.q || []).push(arguments); - }; - window.satismeter.l = 1 * new Date(); - var script = document.createElement('script'); - var parent = document.getElementsByTagName('script')[0].parentNode; - script.async = 1; - script.src = 'https://app.satismeter.com/js'; - script.setAttribute('data-loader', LOAD_ORIGIN), parent.appendChild(script); - })(); - - window.satismeter({ - writeKey: this.writeKey, - }); + loadNativeSdk(this.writeKey); } isLoaded() { diff --git a/src/integrations/Satismeter/index.js b/src/integrations/Satismeter/index.js index 8616430a9..4a386b163 100644 --- a/src/integrations/Satismeter/index.js +++ b/src/integrations/Satismeter/index.js @@ -1,3 +1 @@ -import Satismeter from "./browser"; - -export { Satismeter }; +export { default as Satismeter } from './browser'; diff --git a/src/integrations/Satismeter/nativeSdkLoader.js b/src/integrations/Satismeter/nativeSdkLoader.js new file mode 100644 index 000000000..b26948190 --- /dev/null +++ b/src/integrations/Satismeter/nativeSdkLoader.js @@ -0,0 +1,23 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(writeKey) { + (function () { + window.satismeter = + window.satismeter || + function () { + (window.satismeter.q = window.satismeter.q || []).push(arguments); + }; + window.satismeter.l = 1 * new Date(); + var script = document.createElement('script'); + var parent = document.getElementsByTagName('script')[0].parentNode; + script.async = 1; + script.src = 'https://app.satismeter.com/js'; + script.setAttribute('data-loader', LOAD_ORIGIN), parent.appendChild(script); + })(); + + window.satismeter({ + writeKey: writeKey, + }); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Sendinblue/browser.js b/src/integrations/Sendinblue/browser.js index 6462cf568..0b645df34 100644 --- a/src/integrations/Sendinblue/browser.js +++ b/src/integrations/Sendinblue/browser.js @@ -1,12 +1,11 @@ -/* eslint-disable func-names */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { prepareUserTraits, prepareTrackEventData, preparePagePayload } from './utils'; import { validateEmail, validatePhoneWithCountryCode } from '../../utils/commonUtils'; import { getDefinedTraits } from '../../utils/utils'; +import { loadNativeSdk } from './nativeSdkLoader'; class Sendinblue { constructor(config, analytics, destinationInfo) { @@ -27,36 +26,7 @@ class Sendinblue { loadScript() { const { clientKey } = this; - (function () { - window.sib = { - equeue: [], - client_key: clientKey, - }; - window.sendinblue = {}; - for (var j = ['track', 'identify', 'trackLink', 'page'], i = 0; i < j.length; i++) { - (function (k) { - window.sendinblue[k] = function () { - var arg = Array.prototype.slice.call(arguments); - ( - window.sib[k] || - function () { - var t = {}; - t[k] = arg; - window.sib.equeue.push(t); - } - )(arg[0], arg[1], arg[2], arg[3]); - }; - })(j[i]); - } - var n = document.createElement('script'), - i = document.getElementsByTagName('script')[0]; - (n.type = 'text/javascript'), - (n.id = 'sendinblue-js'), - (n.async = !0), - (n.src = 'https://sibautomation.com/sa.js?key=' + clientKey), - n.setAttribute('data-loader', LOAD_ORIGIN), - i.parentNode.insertBefore(n, i); - })(); + loadNativeSdk(clientKey); } init() { @@ -80,7 +50,7 @@ class Sendinblue { const { email, phone } = getDefinedTraits(message); if (!email || !validateEmail(email)) { - logger.error('[Sendinblue]:: provided email is invalid'); + logger.error('[Sendinblue]:: email is missing'); return; } diff --git a/src/integrations/Sendinblue/index.js b/src/integrations/Sendinblue/index.js index ba456e4ed..51bfdf5cc 100644 --- a/src/integrations/Sendinblue/index.js +++ b/src/integrations/Sendinblue/index.js @@ -1,3 +1 @@ -import Sendinblue from './browser'; - -export { Sendinblue }; +export { default as Sendinblue } from './browser'; diff --git a/src/integrations/Sendinblue/nativeSdkLoader.js b/src/integrations/Sendinblue/nativeSdkLoader.js new file mode 100644 index 000000000..43039a9b7 --- /dev/null +++ b/src/integrations/Sendinblue/nativeSdkLoader.js @@ -0,0 +1,36 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(clientKey) { + (function () { + window.sib = { + equeue: [], + client_key: clientKey, + }; + window.sendinblue = {}; + for (var j = ['track', 'identify', 'trackLink', 'page'], i = 0; i < j.length; i++) { + (function (k) { + window.sendinblue[k] = function () { + var arg = Array.prototype.slice.call(arguments); + ( + window.sib[k] || + function () { + var t = {}; + t[k] = arg; + window.sib.equeue.push(t); + } + )(arg[0], arg[1], arg[2], arg[3]); + }; + })(j[i]); + } + var n = document.createElement('script'), + i = document.getElementsByTagName('script')[0]; + (n.type = 'text/javascript'), + (n.id = 'sendinblue-js'), + (n.async = !0), + (n.src = 'https://sibautomation.com/sa.js?key=' + clientKey), + n.setAttribute('data-loader', LOAD_ORIGIN), + i.parentNode.insertBefore(n, i); + })(); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Sendinblue/utils.js b/src/integrations/Sendinblue/utils.js index f42a6a702..b048a09f8 100644 --- a/src/integrations/Sendinblue/utils.js +++ b/src/integrations/Sendinblue/utils.js @@ -70,7 +70,7 @@ const prepareTrackEventData = (message) => { let eventData = {}; if (isNotEmpty(properties)) { let id; - if (integrations && integrations[NAME]) { + if (integrations?.[NAME]) { const key = integrations[NAME]?.propertiesIdKey; if (key) { id = properties[key]; @@ -90,8 +90,8 @@ const refinePageProperties = (properties) => { }; const preparePagePayload = (message) => { - const { properties } = message; - const { page } = message.context; + const { properties, context } = message; + const { page } = context; const title = properties?.title || page?.title; const url = properties?.url || page?.url; diff --git a/src/integrations/Sentry/browser.js b/src/integrations/Sentry/browser.js index 97918b4ab..812a87102 100644 --- a/src/integrations/Sentry/browser.js +++ b/src/integrations/Sentry/browser.js @@ -1,8 +1,4 @@ -/* eslint-disable object-shorthand */ -/* eslint-disable func-names */ /* eslint-disable class-methods-use-this */ -/* eslint-disable no-unused-expressions */ - import get from 'get-value'; import logger from '../../utils/logUtil'; import { SentryScriptLoader, sentryInit } from './utils'; @@ -110,7 +106,7 @@ class Sentry { const payload = { id: userId, - email: email, + email, username: name, ip_address: ipAddress, ...traits, diff --git a/src/integrations/Sentry/index.js b/src/integrations/Sentry/index.js index 34d96eb72..356bc1d74 100644 --- a/src/integrations/Sentry/index.js +++ b/src/integrations/Sentry/index.js @@ -1,3 +1 @@ -import Sentry from './browser'; - -export { Sentry }; +export { default as Sentry } from './browser'; diff --git a/src/integrations/Sentry/utils.js b/src/integrations/Sentry/utils.js index a8e50a398..d118e650b 100644 --- a/src/integrations/Sentry/utils.js +++ b/src/integrations/Sentry/utils.js @@ -1,14 +1,13 @@ +/* eslint-disable no-restricted-syntax */ /* eslint-disable no-param-reassign */ -/* eslint-disable object-shorthand */ import logger from '../../utils/logUtil'; import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { isDefinedAndNotNullAndNotEmpty } from '../../utils/commonUtils'; -const convertObjectToArray = (objectInput, propertyName) => { - return objectInput +const convertObjectToArray = (objectInput, propertyName) => + objectInput .map((objectItem) => objectItem[propertyName]) .filter((e) => isDefinedAndNotNullAndNotEmpty(e)); -}; const SentryScriptLoader = (id, src, integrity) => { logger.debug(`in script loader=== ${id}`); @@ -55,30 +54,13 @@ const sentryInit = ( ignoreErrors: formattedIgnoreErrors, }; - let includePaths = []; - if (formattedIncludePaths.length > 0) { - // eslint-disable-next-line func-names - includePaths = formattedIncludePaths.map(function (path) { - let regex; - try { - regex = new RegExp(path); - } catch (e) { - // ignored - } - return regex; - }); - } - - if (includePaths.length > 0) { - sentryConfig.integrations = []; - sentryConfig.integrations.push( + sentryConfig.integrations = [ new window.Sentry.Integrations.RewriteFrames({ - iteratee: function (frame) { - // eslint-disable-next-line no-restricted-syntax - for (const path of includePaths) { + iteratee(frame) { + for (const path of formattedIncludePaths) { try { - if (frame.filename.match(path)) { + if (frame.filename.match(new RegExp(path))) { frame.in_app = true; return frame; } @@ -90,7 +72,7 @@ const sentryInit = ( return frame; }, }), - ); + ]; } return sentryConfig; }; diff --git a/src/integrations/Shynet/browser.js b/src/integrations/Shynet/browser.js index aa9a29b32..3f742abb9 100644 --- a/src/integrations/Shynet/browser.js +++ b/src/integrations/Shynet/browser.js @@ -1,3 +1,5 @@ +/* eslint-disable func-names */ +/* eslint-disable compat/compat */ import logger from '../../utils/logUtil'; import { NAME } from './constants'; import { generateUUID } from '../../utils/utils'; @@ -42,9 +44,7 @@ class Shynet { idempotency: this.idempotency, referrer: referrerName, location: url, - loadTime: - window.performance.timing.domContentLoadedEventEnd - - window.performance.timing.navigationStart, + loadTime: window.performance.now(), }); xhr.send(payloadBody); } catch (exp) { @@ -60,7 +60,7 @@ class Shynet { // taking default as 5 sec as used in shynet doc this.heartBeatTaskId = setInterval( this.sendHeartBeat, - parseInt(heartBeatFrequencyInMs || 5000), + parseInt(heartBeatFrequencyInMs || 5000, 10), ); this.sendHeartBeat(referrer, url); }, diff --git a/src/integrations/Shynet/index.js b/src/integrations/Shynet/index.js index f542cfb67..57c4df546 100644 --- a/src/integrations/Shynet/index.js +++ b/src/integrations/Shynet/index.js @@ -1,3 +1 @@ -import Shynet from './browser'; - -export { Shynet }; +export { default as Shynet } from './browser'; diff --git a/src/integrations/SnapEngage/browser.js b/src/integrations/SnapEngage/browser.js index 1019dae2e..7b6d240aa 100644 --- a/src/integrations/SnapEngage/browser.js +++ b/src/integrations/SnapEngage/browser.js @@ -7,7 +7,7 @@ import { NAME } from './constants'; import { recordingLiveChatEvents } from './util'; import { getHashFromArray } from '../../utils/commonUtils'; import { isObject } from '../../utils/utils'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class SnapEngage { constructor(config, analytics, destinationInfo) { @@ -28,24 +28,7 @@ class SnapEngage { } loadScript() { - (function (widgetId) { - const se = document.createElement('script'); - se.type = 'text/javascript'; - se.async = true; - se.src = `https://storage.googleapis.com/code.snapengage.com/js/${widgetId}.js`; - se.setAttribute('data-loader', LOAD_ORIGIN); - let done = false; - se.onload = se.onreadystatechange = function () { - if ( - !done && - (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') - ) { - done = true; - } - }; - const s = document.getElementsByTagName('script')[0]; - s.parentNode.insertBefore(se, s); - })(this.widgetId); + loadNativeSdk(this.widgetId); } init() { diff --git a/src/integrations/SnapEngage/index.js b/src/integrations/SnapEngage/index.js index 967957099..bb47b0db3 100644 --- a/src/integrations/SnapEngage/index.js +++ b/src/integrations/SnapEngage/index.js @@ -1,4 +1 @@ -import SnapEngage from './browser'; - -// eslint-disable-next-line import/prefer-default-export -export { SnapEngage }; +export { default as SnapEngage } from './browser'; diff --git a/src/integrations/SnapEngage/nativeSdkLoader.js b/src/integrations/SnapEngage/nativeSdkLoader.js new file mode 100644 index 000000000..d2d23f147 --- /dev/null +++ b/src/integrations/SnapEngage/nativeSdkLoader.js @@ -0,0 +1,24 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(widgetId) { + (function (widgetId) { + const se = document.createElement('script'); + se.type = 'text/javascript'; + se.async = true; + se.src = `https://storage.googleapis.com/code.snapengage.com/js/${widgetId}.js`; + se.setAttribute('data-loader', LOAD_ORIGIN); + let done = false; + se.onload = se.onreadystatechange = function () { + if ( + !done && + (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') + ) { + done = true; + } + }; + const s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(se, s); + })(widgetId); +} + +export { loadNativeSdk }; diff --git a/src/integrations/SnapEngage/util.js b/src/integrations/SnapEngage/util.js index 98c2f91bb..caf6cbb92 100644 --- a/src/integrations/SnapEngage/util.js +++ b/src/integrations/SnapEngage/util.js @@ -15,7 +15,7 @@ function flip(data) { function recordingLiveChatEvents(updateEventNames, standardEventsMap, analytics) { const eventNames = flip(standardEventsMap); - window.SnapEngage.setCallback('StartChat', function () { + window.SnapEngage.setCallback('StartChat', () => { let eventName = 'Live Chat Conversation Started'; if (updateEventNames && eventNames?.startChat) { eventName = eventNames.startChat; @@ -23,7 +23,7 @@ function recordingLiveChatEvents(updateEventNames, standardEventsMap, analytics) analytics.track(`${eventName}`, {}, { context: { integration: integrationContext } }); }); - window.SnapEngage.setCallback('ChatMessageReceived', function (agent) { + window.SnapEngage.setCallback('ChatMessageReceived', (agent) => { let eventName = 'Live Chat Message Received'; if (updateEventNames && eventNames?.chatMessageReceived) { eventName = eventNames.chatMessageReceived; @@ -35,7 +35,7 @@ function recordingLiveChatEvents(updateEventNames, standardEventsMap, analytics) ); }); - window.SnapEngage.setCallback('ChatMessageSent', function () { + window.SnapEngage.setCallback('ChatMessageSent', () => { let eventName = 'Live Chat Message Sent'; if (updateEventNames && eventNames?.chatMessageSent) { eventName = eventNames.chatMessageSent; @@ -43,7 +43,7 @@ function recordingLiveChatEvents(updateEventNames, standardEventsMap, analytics) analytics.track(`${eventName}`, {}, { context: { integration: integrationContext } }); }); - window.SnapEngage.setCallback('Close', function () { + window.SnapEngage.setCallback('Close', () => { let eventName = 'Live Chat Conversation Ended'; if (updateEventNames && eventNames?.close) { eventName = eventNames.close; @@ -51,7 +51,7 @@ function recordingLiveChatEvents(updateEventNames, standardEventsMap, analytics) analytics.track(`${eventName}`, {}, { context: { integration: integrationContext } }); }); - window.SnapEngage.setCallback('InlineButtonClicked', function () { + window.SnapEngage.setCallback('InlineButtonClicked', () => { let eventName = 'Inline Button Clicked'; if (updateEventNames && eventNames?.inlineButtonClicked) { eventName = eventNames.inlineButtonClicked; diff --git a/src/integrations/SnapPixel/browser.js b/src/integrations/SnapPixel/browser.js index 2385bbd33..663dc11b8 100644 --- a/src/integrations/SnapPixel/browser.js +++ b/src/integrations/SnapPixel/browser.js @@ -10,7 +10,7 @@ import { } from '../../utils/commonUtils'; import { ecommEventPayload, eventPayload, getUserEmailAndPhone, sendEvent } from './util'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class SnapPixel { constructor(config, analytics, destinationInfo) { @@ -70,20 +70,7 @@ class SnapPixel { init() { logger.debug('===In init SnapPixel==='); - (function (e, t, n) { - if (e.snaptr) return; - var a = (e.snaptr = function () { - a.handleRequest ? a.handleRequest.apply(a, arguments) : a.queue.push(arguments); - }); - a.queue = []; - const s = 'script'; - const r = t.createElement(s); - r.async = !0; - r.src = n; - r.setAttribute('data-loader', LOAD_ORIGIN); - const u = t.getElementsByTagName(s)[0]; - u.parentNode.insertBefore(r, u); - })(window, document, 'https://sc-static.net/scevent.min.js'); + loadNativeSdk(); const userTraits = Storage.getUserTraits(); diff --git a/src/integrations/SnapPixel/index.js b/src/integrations/SnapPixel/index.js index 5abaaf60b..c19bcfbc0 100644 --- a/src/integrations/SnapPixel/index.js +++ b/src/integrations/SnapPixel/index.js @@ -1,3 +1 @@ -import SnapPixel from './browser'; - -export { SnapPixel }; +export { default as SnapPixel } from './browser'; diff --git a/src/integrations/SnapPixel/nativeSdkLoader.js b/src/integrations/SnapPixel/nativeSdkLoader.js new file mode 100644 index 000000000..1b72b2f0b --- /dev/null +++ b/src/integrations/SnapPixel/nativeSdkLoader.js @@ -0,0 +1,20 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + (function (e, t, n) { + if (e.snaptr) return; + var a = (e.snaptr = function () { + a.handleRequest ? a.handleRequest.apply(a, arguments) : a.queue.push(arguments); + }); + a.queue = []; + const s = 'script'; + const r = t.createElement(s); + r.async = !0; + r.src = n; + r.setAttribute('data-loader', LOAD_ORIGIN); + const u = t.getElementsByTagName(s)[0]; + u.parentNode.insertBefore(r, u); + })(window, document, 'https://sc-static.net/scevent.min.js'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/SnapPixel/util.js b/src/integrations/SnapPixel/util.js index 62680c7d6..1836f35f5 100644 --- a/src/integrations/SnapPixel/util.js +++ b/src/integrations/SnapPixel/util.js @@ -2,11 +2,54 @@ import get from 'get-value'; import sha256 from 'crypto-js/sha256'; import logger from '../../utils/logUtil'; import { - isDefinedAndNotNull, isNotEmpty, + isDefinedAndNotNull, removeUndefinedAndNullValues, } from '../../utils/commonUtils'; +const orderIdKey = 'properties.order_id'; +const itemIdKey = 'properties.item_ids'; +const productsKey = 'properties.products'; +const productIdKey = 'properties.product_id'; +const productIdNotPresentMessage = 'product_id is not present'; + +const eventSpecificPayloadMap = { + 'order completed': { + itemIdsKey: productsKey, + transactionIdKey: orderIdKey, + }, + 'checkout started': { + itemIdsKey: productsKey, + transactionIdKey: orderIdKey, + }, + 'product added': { + itemIdsKey: productIdKey, + }, + 'payment info entered': { + itemIdsKey: itemIdKey, + transactionIdKey: 'properties.checkout_id', + }, + 'promotion clicked': { + itemIdsKey: itemIdKey, + }, + 'promotion viewed': { + itemIdsKey: itemIdKey, + }, + 'product added to wishlist': { + itemIdsKey: productIdKey, + }, + 'product viewed': { + itemIdsKey: productIdKey, + }, + 'product list viewed': { + itemIdsKey: productsKey, + }, + 'products searched': { + itemIdsKey: itemIdKey, + searchStringKey: 'properties.query', + }, +}; + const sendEvent = (event, payload) => { if (isNotEmpty(payload)) { window.snaptr('track', event, payload); @@ -17,8 +60,11 @@ const sendEvent = (event, payload) => { const getCommonEventPayload = (message, deduplicationKey, enableDeduplication) => { let payload = { - price: parseFloat(get(message, 'properties.price') || - get(message, "properties.value") || get(message, "properties.revenue")), + price: parseFloat( + get(message, 'properties.price') || + get(message, 'properties.value') || + get(message, 'properties.revenue'), + ), client_deduplication_id: get(message, 'properties.client_deduplication_id'), currency: get(message, 'properties.currency'), transaction_id: @@ -26,8 +72,10 @@ const getCommonEventPayload = (message, deduplicationKey, enableDeduplication) = item_category: get(message, 'properties.category'), description: get(message, 'properties.description'), search_string: get(message, 'properties.search_string'), - number_items: parseInt(get(message, 'properties.number_items') - || get(message, 'properties.quantity'), 10), + number_items: parseInt( + get(message, 'properties.number_items') || get(message, 'properties.quantity'), + 10, + ), payment_info_available: parseInt(get(message, 'properties.payment_info_available'), 10), sign_up_method: get(message, 'properties.sign_up_method'), success: parseInt(get(message, 'properties.success'), 10), @@ -48,154 +96,89 @@ const getCommonEventPayload = (message, deduplicationKey, enableDeduplication) = const eventPayload = (message, deduplicationKey, enableDeduplication) => { let payload = getCommonEventPayload(message, deduplicationKey, enableDeduplication); - payload.item_ids = get(message, 'properties.item_ids'); + payload.item_ids = get(message, itemIdKey); payload = removeUndefinedAndNullValues(payload); return payload; }; -const ecommEventPayload = (event, message, deduplicationKey, enableDeduplication) => { - let payload = getCommonEventPayload(message, deduplicationKey, enableDeduplication); - switch (event.toLowerCase().trim()) { - case 'order completed': { - let itemIds = []; - const products = get(message, 'properties.products'); - if (products && Array.isArray(products)) { - products.forEach((element, index) => { - const pId = element.product_id; - if (pId) { - itemIds.push(pId); - } else { - logger.debug(`product_id not present for product at index ${index}`); - } - }); - } else { - itemIds = null; - } - payload = { - ...payload, - transaction_id: get(message, 'properties.order_id'), - item_ids: itemIds, - }; - break; - } - case 'checkout started': { - let itemIds = []; - const products = get(message, 'properties.products'); - if (products && Array.isArray(products)) { - products.forEach((element, index) => { - const pId = element.product_id; - if (pId) { - itemIds.push(pId); - } else { - logger.debug(`product_id not present for product at index ${index}`); - } - }); +/** + * Returns productIds + * @param {*} message + * @returns + */ +const getItemIds = (message) => { + let itemIds = []; + const products = get(message, productsKey); + if (products && Array.isArray(products)) { + products.forEach((element, index) => { + const productId = element.product_id; + if (productId) { + itemIds.push(productId); } else { - itemIds = null; + logger.debug(`product_id not present for product at index ${index}`); } - payload = { - ...payload, - transaction_id: get(message, 'properties.order_id'), - item_ids: itemIds, - }; - break; - } - case 'product added': { - let itemIds = []; - const pId = get(message, 'properties.product_id'); - if (isDefinedAndNotNull(pId)) { - itemIds.push(pId); - } else { - logger.debug('product_id is not present'); - itemIds = null; - } - payload = { - ...payload, - item_ids: itemIds, - }; - break; - } - case 'payment info entered': - payload = { - ...payload, - transaction_id: get(message, 'properties.checkout_id'), - item_ids: get(message, 'properties.item_ids'), - }; - break; - case 'promotion clicked': - payload = { - ...payload, - item_ids: get(message, 'properties.item_ids'), - }; - break; - case 'promotion viewed': - payload = { - ...payload, - item_ids: get(message, 'properties.item_ids'), - }; - break; - case 'product added to wishlist': { - let itemIds = []; - const pId = get(message, 'properties.product_id'); - if (isDefinedAndNotNull(pId)) { - itemIds.push(pId); - } else { - logger.debug('product_id is not present'); - itemIds = null; - } - payload = { - ...payload, - item_ids: itemIds, - }; - break; + }); + } else { + itemIds = null; + } + return itemIds; +}; + +/** + * Returns productId + * @param {*} message + * @returns + */ +const getItemId = (message) => { + let itemIds = []; + const productId = get(message, productIdKey); + if (isDefinedAndNotNull(productId)) { + itemIds.push(productId); + } else { + logger.debug(productIdNotPresentMessage); + itemIds = null; + } + return itemIds; +}; + +/** + * Returns ecom events payload + * @param {*} event + * @param {*} message + * @param {*} deduplicationKey + * @param {*} enableDeduplication + * @returns + */ +const ecommEventPayload = (event, message, deduplicationKey, enableDeduplication) => { + const eventName = event.toLowerCase().trim(); + const specificPayload = eventSpecificPayloadMap[eventName]; + let payload = getCommonEventPayload(message, deduplicationKey, enableDeduplication); + + if (specificPayload) { + const { itemIdsKey, transactionIdKey, searchStringKey } = specificPayload; + let itemIds = []; + + if (itemIdsKey === productsKey) { + itemIds = getItemIds(message); + } else if (itemIdsKey === productIdKey) { + itemIds = getItemId(message); + } else { + itemIds = get(message, itemIdsKey); } - case 'product viewed': { - let itemIds = []; - const pId = get(message, 'properties.product_id'); - if (pId) { - itemIds.push(pId); - } else { - logger.debug('product_id is not present'); - itemIds = null; - } - payload = { - ...payload, - item_ids: itemIds, - }; - break; + + payload = { + ...payload, + item_ids: itemIds, + }; + + if (transactionIdKey) { + payload.transaction_id = get(message, transactionIdKey); } - case 'product list viewed': { - let itemIds = []; - const products = get(message, 'properties.products'); - if (products && Array.isArray(products)) { - products.forEach((element, index) => { - const pId = get(element, 'product_id'); - if (pId) { - itemIds.push(pId); - } else { - logger.debug(`product_id not present for product at index ${index}`); - } - }); - } else { - itemIds = null; - } - payload = { - ...payload, - item_ids: itemIds, - }; - break; + + if (searchStringKey) { + payload.search_string = get(message, searchStringKey); } - case 'products searched': - payload = { - ...payload, - search_string: get(message, 'properties.query'), - item_ids: get(message, 'properties.item_ids'), - }; - break; - default: - break; } - payload = removeUndefinedAndNullValues(payload); return payload; }; diff --git a/src/integrations/TVSquared/browser.js b/src/integrations/TVSquared/browser.js index 71cf76302..465cc7bb3 100644 --- a/src/integrations/TVSquared/browser.js +++ b/src/integrations/TVSquared/browser.js @@ -1,6 +1,9 @@ -/* eslint-disable camelcase */ +/* eslint-disable func-names */ +/* eslint-disable no-unused-vars */ +/* eslint-disable class-methods-use-this */ /* eslint-disable no-underscore-dangle */ import ScriptLoader from '../../utils/ScriptLoader'; +import { getAction } from './utils'; import logger from '../../utils/logUtil'; import { NAME } from './constants'; @@ -32,69 +35,48 @@ class TVSquared { ScriptLoader('TVSquared-integration', `${url}tv2track.js`); } - isLoaded = () => { + isLoaded() { logger.debug('in TVSqaured isLoaded'); return !!(window._tvq && window._tvq.push !== Array.prototype.push); - }; + } - isReady = () => { + isReady() { logger.debug('in TVSqaured isReady'); return !!(window._tvq && window._tvq.push !== Array.prototype.push); - }; + } - page = () => { + page(rudderElement) { window._tvq.push([ function () { this.deleteCustomVariable(5, 'page'); }, ]); window._tvq.push(['trackPageView']); - }; + } track(rudderElement) { - const { event, userId, anonymousId } = rudderElement.message; - const { revenue, productType, category, order_id, promotion_id } = - rudderElement.message.properties; - let i; - let j; - let whitelist = this.eventWhiteList.slice(); - whitelist = whitelist.filter((wl) => { - return wl.event !== ''; - }); - for (i = 0; i < whitelist.length; i += 1) { - if (event.toUpperCase() === whitelist[i].event.toUpperCase()) { - break; - } - if (i === whitelist.length - 1) { - return; - } + const { message } = rudderElement; + const { event, userId, anonymousId } = message; + + const whitelistEvents = this.eventWhiteList.filter((wl) => wl.event !== ''); + + const isEventInWhiteList = whitelistEvents.some( + (whitelistEvent) => whitelistEvent.event.toUpperCase() === event.toUpperCase(), + ); + + if (!isEventInWhiteList) { + return; } const session = { user: userId || anonymousId || '' }; - const action = { - rev: revenue ? this.formatRevenue(revenue) : '', - prod: category || productType || '', - id: order_id || '', - promo: promotion_id || '', - }; - let customMetrics = this.customMetrics.slice(); - customMetrics = customMetrics.filter((cm) => { - return cm.propertyName !== ''; - }); - if (customMetrics.length) { - for (j = 0; j < customMetrics.length; j += 1) { - const key = customMetrics[j].propertyName; - const value = rudderElement.message.properties[key]; - if (value) { - action[key] = value; - } - } - } + const action = getAction(message, this.customMetrics); + window._tvq.push([ function () { this.setCustomVariable(5, 'session', JSON.stringify(session), 'visit'); }, ]); + if (event.toUpperCase() !== 'RESPONSE') { window._tvq.push([ function () { @@ -110,11 +92,5 @@ class TVSquared { ]); } } - - formatRevenue = (revenue) => { - let rev = revenue; - rev = parseFloat(rev.toString().replace(/^[^\d.]*/, '')); - return rev; - }; } export default TVSquared; diff --git a/src/integrations/TVSquared/index.js b/src/integrations/TVSquared/index.js index 0b89338fe..5c2346e6d 100644 --- a/src/integrations/TVSquared/index.js +++ b/src/integrations/TVSquared/index.js @@ -1,3 +1 @@ -import TVSquared from './browser'; - -export { TVSquared }; +export { default as TVSquared } from './browser'; diff --git a/src/integrations/TVSquared/utils.js b/src/integrations/TVSquared/utils.js new file mode 100644 index 000000000..45fc972e1 --- /dev/null +++ b/src/integrations/TVSquared/utils.js @@ -0,0 +1,40 @@ +/** + * Returns formatted revenue + * @param {*} revenue + * @returns + */ +const formatRevenue = (revenue) => parseFloat(revenue.toString().replace(/^[^\d.]*/, '')); + +/** + * Returns action object + * @param {*} message + * @param {*} metrics + */ +const getAction = (message, metrics) => { + const { properties } = message; + const { + revenue, + productType, + category, + order_id: orderId, + promotion_id: promotionId, + } = properties; + + const action = { + rev: revenue ? formatRevenue(revenue) : '', + prod: category || productType || '', + id: orderId || '', + promo: promotionId || '', + }; + + const customMetrics = metrics.filter((m) => m.propertyName !== ''); + customMetrics.forEach((customMetric) => { + const key = customMetric.propertyName; + const value = properties[key]; + if (value) { + action[key] = value; + } + }); +}; + +export { getAction, formatRevenue }; diff --git a/src/integrations/TiktokAds/browser.js b/src/integrations/TiktokAds/browser.js index b76b8d24f..9ced181f4 100644 --- a/src/integrations/TiktokAds/browser.js +++ b/src/integrations/TiktokAds/browser.js @@ -1,16 +1,14 @@ -/* eslint-disable*/ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-underscore-dangle */ +/* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { NAME, eventNameMapping } from './constants'; import { - getHashFromArrayWithDuplicate, - getDestinationExternalID, isDefinedAndNotNull, + getDestinationExternalID, + getHashFromArrayWithDuplicate, } from '../../utils/commonUtils'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { getTrackResponse } from './util'; +import { loadNativeSdk } from './nativeSdkLoader'; // Docs : https://ads.tiktok.com/gateway/docs/index class TiktokAds { @@ -31,52 +29,7 @@ class TiktokAds { init() { logger.debug('===In init Tiktok Ads==='); - !(function (w, d, t) { - w.TiktokAnalyticsObject = t; - var ttq = (w[t] = w[t] || []); - (ttq.methods = [ - 'page', - 'track', - 'identify', - 'instances', - 'debug', - 'on', - 'off', - 'once', - 'ready', - 'alias', - 'group', - 'enableCookie', - 'disableCookie', - ]), - (ttq.setAndDefer = function (t, e) { - t[e] = function () { - t.push([e].concat(Array.prototype.slice.call(arguments, 0))); - }; - }); - for (var i = 0; i < ttq.methods.length; i++) ttq.setAndDefer(ttq, ttq.methods[i]); - (ttq.instance = function (t) { - for (var e = ttq._i[t] || [], n = 0; n < ttq.methods.length; n++) - ttq.setAndDefer(e, ttq.methods[n]); - return e; - }), - (ttq.load = function (e, n) { - var i = 'https://analytics.tiktok.com/i18n/pixel/events.js'; - (ttq._i = ttq._i || {}), - (ttq._i[e] = []), - (ttq._i[e]._u = i), - (ttq._t = ttq._t || {}), - (ttq._t[e] = +new Date()), - (ttq._o = ttq._o || {}), - (ttq._o[e] = n || {}); - var o = document.createElement('script'); - o.setAttribute('data-loader', LOAD_ORIGIN); - (o.type = 'text/javascript'), (o.async = !0), (o.src = i + '?sdkid=' + e + '&lib=' + t); - var a = document.getElementsByTagName('script')[0]; - a.parentNode.insertBefore(o, a); - }); - })(window, document, 'ttq'); - ttq.load(this.pixelCode); + loadNativeSdk(this.pixelCode); } isLoaded() { @@ -136,7 +89,7 @@ class TiktokAds { }); } else { event = eventNameMapping[event]; - const updatedProperties = getTrackResponse(message, Config, event); + const updatedProperties = getTrackResponse(message); window.ttq.track(event, updatedProperties); } } diff --git a/src/integrations/TiktokAds/index.js b/src/integrations/TiktokAds/index.js index 6dc634c3d..caac5942e 100644 --- a/src/integrations/TiktokAds/index.js +++ b/src/integrations/TiktokAds/index.js @@ -1,3 +1 @@ -import TiktokAds from './browser'; - -export { TiktokAds }; +export { default as TiktokAds } from './browser'; diff --git a/src/integrations/TiktokAds/nativeSdkLoader.js b/src/integrations/TiktokAds/nativeSdkLoader.js new file mode 100644 index 000000000..ea09b9974 --- /dev/null +++ b/src/integrations/TiktokAds/nativeSdkLoader.js @@ -0,0 +1,52 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(pixelCode) { + !(function (w, d, t) { + w.TiktokAnalyticsObject = t; + var ttq = (w[t] = w[t] || []); + (ttq.methods = [ + 'page', + 'track', + 'identify', + 'instances', + 'debug', + 'on', + 'off', + 'once', + 'ready', + 'alias', + 'group', + 'enableCookie', + 'disableCookie', + ]), + (ttq.setAndDefer = function (t, e) { + t[e] = function () { + t.push([e].concat(Array.prototype.slice.call(arguments, 0))); + }; + }); + for (var i = 0; i < ttq.methods.length; i++) ttq.setAndDefer(ttq, ttq.methods[i]); + (ttq.instance = function (t) { + for (var e = ttq._i[t] || [], n = 0; n < ttq.methods.length; n++) + ttq.setAndDefer(e, ttq.methods[n]); + return e; + }), + (ttq.load = function (e, n) { + var i = 'https://analytics.tiktok.com/i18n/pixel/events.js'; + (ttq._i = ttq._i || {}), + (ttq._i[e] = []), + (ttq._i[e]._u = i), + (ttq._t = ttq._t || {}), + (ttq._t[e] = +new Date()), + (ttq._o = ttq._o || {}), + (ttq._o[e] = n || {}); + var o = document.createElement('script'); + o.setAttribute('data-loader', LOAD_ORIGIN); + (o.type = 'text/javascript'), (o.async = !0), (o.src = i + '?sdkid=' + e + '&lib=' + t); + var a = document.getElementsByTagName('script')[0]; + a.parentNode.insertBefore(o, a); + }); + })(window, document, 'ttq'); + ttq.load(pixelCode); +} + +export { loadNativeSdk }; diff --git a/src/integrations/VWO/browser.js b/src/integrations/VWO/browser.js index 90e23c79d..ff89fb519 100644 --- a/src/integrations/VWO/browser.js +++ b/src/integrations/VWO/browser.js @@ -3,8 +3,8 @@ /* eslint-disable class-methods-use-this */ /* eslint-disable camelcase */ import logger from '../../utils/logUtil'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { NAME } from './constants'; +import { loadNativeSdk } from './nativeSdkLoader'; class VWO { constructor(config, analytics, destinationInfo) { @@ -36,58 +36,7 @@ class VWO { const library_tolerance = this.libraryTolerance; const use_existing_jquery = this.useExistingJquery; const { isSPA } = this; - window._vwo_code = (function () { - let f = false; - const d = document; - return { - use_existing_jquery() { - return use_existing_jquery; - }, - library_tolerance() { - return library_tolerance; - }, - finish() { - if (!f) { - f = true; - const a = d.getElementById('_vis_opt_path_hides'); - if (a) a.parentNode.removeChild(a); - } - }, - finished() { - return f; - }, - load(a) { - const b = d.createElement('script'); - b.src = a; - b.type = 'text/javascript'; - b.setAttribute('data-loader', LOAD_ORIGIN); - b.innerText; - b.onerror = function () { - _vwo_code.finish(); - }; - d.getElementsByTagName('head')[0].appendChild(b); - }, - init() { - const settings_timer = setTimeout('_vwo_code.finish()', settings_tolerance); - const a = d.createElement('style'); - const b = - 'body{opacity:0 !important;filter:alpha(opacity=0) !important;background:none !important;}'; - const h = d.getElementsByTagName('head')[0]; - a.setAttribute('id', '_vis_opt_path_hides'); - a.setAttribute('type', 'text/css'); - if (a.styleSheet) a.styleSheet.cssText = b; - else a.appendChild(d.createTextNode(b)); - h.appendChild(a); - this.load( - `//dev.visualwebsiteoptimizer.com/j.php?a=${account_id}&u=${encodeURIComponent( - d.URL, - )}&r=${Math.random()}&f=${+isSPA}`, - ); - return settings_timer; - }, - }; - })(); - window._vwo_settings_timer = window._vwo_code.init(); + loadNativeSdk(account_id, settings_tolerance, library_tolerance, use_existing_jquery, isSPA); } else { logger.debug('===[VWO]loadIntegration flag is disabled==='); } diff --git a/src/integrations/VWO/index.js b/src/integrations/VWO/index.js index e91b8cd95..45a767717 100644 --- a/src/integrations/VWO/index.js +++ b/src/integrations/VWO/index.js @@ -1,3 +1 @@ -import { VWO } from './browser'; - -export { VWO }; +export { VWO } from './browser'; diff --git a/src/integrations/VWO/nativeSdkLoader.js b/src/integrations/VWO/nativeSdkLoader.js new file mode 100644 index 000000000..5b8bab32c --- /dev/null +++ b/src/integrations/VWO/nativeSdkLoader.js @@ -0,0 +1,64 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk( + account_id, + settings_tolerance, + library_tolerance, + use_existing_jquery, + isSPA, +) { + window._vwo_code = (function () { + let f = false; + const d = document; + return { + use_existing_jquery() { + return use_existing_jquery; + }, + library_tolerance() { + return library_tolerance; + }, + finish() { + if (!f) { + f = true; + const a = d.getElementById('_vis_opt_path_hides'); + if (a) a.parentNode.removeChild(a); + } + }, + finished() { + return f; + }, + load(a) { + const b = d.createElement('script'); + b.src = a; + b.type = 'text/javascript'; + b.setAttribute('data-loader', LOAD_ORIGIN); + b.innerText; + b.onerror = function () { + _vwo_code.finish(); + }; + d.getElementsByTagName('head')[0].appendChild(b); + }, + init() { + const settings_timer = setTimeout('_vwo_code.finish()', settings_tolerance); + const a = d.createElement('style'); + const b = + 'body{opacity:0 !important;filter:alpha(opacity=0) !important;background:none !important;}'; + const h = d.getElementsByTagName('head')[0]; + a.setAttribute('id', '_vis_opt_path_hides'); + a.setAttribute('type', 'text/css'); + if (a.styleSheet) a.styleSheet.cssText = b; + else a.appendChild(d.createTextNode(b)); + h.appendChild(a); + this.load( + `//dev.visualwebsiteoptimizer.com/j.php?a=${account_id}&u=${encodeURIComponent( + d.URL, + )}&r=${Math.random()}&f=${+isSPA}`, + ); + return settings_timer; + }, + }; + })(); + window._vwo_settings_timer = window._vwo_code.init(); +} + +export { loadNativeSdk }; diff --git a/src/integrations/Vero/browser.js b/src/integrations/Vero/browser.js index 0a9f07056..0ea53ea60 100644 --- a/src/integrations/Vero/browser.js +++ b/src/integrations/Vero/browser.js @@ -45,17 +45,17 @@ class Vero { * @param {Object} tags */ addOrRemoveTags(message) { - const { integrations } = message; - if (integrations && integrations[NAME]) { + const { integrations, anonymousId, userId } = message; + if (integrations?.[NAME]) { const { tags } = integrations[NAME]; if (isDefinedAndNotNull(tags)) { - const userId = message.userId || message.anonymousId; + const id = userId || anonymousId; const addTags = Array.isArray(tags.add) ? tags.add : []; const removeTags = Array.isArray(tags.remove) ? tags.remove : []; window._veroq.push([ 'tags', { - id: userId, + id, add: addTags, remove: removeTags, }, @@ -97,18 +97,18 @@ class Vero { logger.debug('=== In Vero track ==='); const { message } = rudderElement; - const { event, properties } = message; + const { event, properties, anonymousId, userId } = message; if (!event) { logger.error('[Vero]: Event name from track call is missing!!==='); return; } - const userId = message.userId || message.anonymousId; + const id = userId || anonymousId; switch (event.toLowerCase()) { case 'unsubscribe': - window._veroq.push(['unsubscribe', userId]); + window._veroq.push(['unsubscribe', id]); break; case 'resubscribe': - window._veroq.push(['resubscribe', userId]); + window._veroq.push(['resubscribe', id]); break; default: window._veroq.push(['track', event, properties]); diff --git a/src/integrations/Vero/index.js b/src/integrations/Vero/index.js index ab1e3a396..7312cb328 100644 --- a/src/integrations/Vero/index.js +++ b/src/integrations/Vero/index.js @@ -1,3 +1 @@ -import Vero from './browser'; - -export { Vero }; +export { default as Vero } from './browser'; diff --git a/src/integrations/Woopra/browser.js b/src/integrations/Woopra/browser.js index 127029618..ac87cb3c8 100644 --- a/src/integrations/Woopra/browser.js +++ b/src/integrations/Woopra/browser.js @@ -1,10 +1,7 @@ -/* eslint-disable*/ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-underscore-dangle */ /* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; +import { loadNativeSdk } from './nativeSdkLoader'; class Woopra { constructor(config, analytics, destinationInfo) { @@ -33,43 +30,7 @@ class Woopra { init() { logger.debug('===In init Woopra==='); - !(function () { - var t, - o, - c, - e = window, - n = document, - r = arguments, - a = 'script', - i = [ - 'call', - 'cancelAction', - 'config', - 'identify', - 'push', - 'track', - 'trackClick', - 'trackForm', - 'update', - 'visit', - ], - s = function () { - var t, - o = this, - c = function (t) { - o[t] = function () { - return o._e.push([t].concat(Array.prototype.slice.call(arguments, 0))), o; - }; - }; - for (o._e = [], t = 0; t < i.length; t++) c(i[t]); - }; - for (e.__woo = e.__woo || {}, t = 0; t < r.length; t++) - e.__woo[r[t]] = e[r[t]] = e[r[t]] || new s(); - ((o = n.createElement(a)).async = 1), - (o.src = 'https://static.woopra.com/w.js'), - o.setAttribute('data-loader', LOAD_ORIGIN), - (c = n.getElementsByTagName(a)[0]).parentNode.insertBefore(o, c); - })('Woopra'); + loadNativeSdk(); window.Woopra.config({ domain: this.projectName, cookie_name: this.cookieName, @@ -87,7 +48,7 @@ class Woopra { isLoaded() { logger.debug('===In isLoaded Woopra==='); - return !!(window.Woopra && window.Woopra.loaded); + return !!window?.Woopra?.loaded; } isReady() { diff --git a/src/integrations/Woopra/index.js b/src/integrations/Woopra/index.js index b94ed3ca1..46bde6a13 100644 --- a/src/integrations/Woopra/index.js +++ b/src/integrations/Woopra/index.js @@ -1,3 +1 @@ -import Woopra from './browser'; - -export { Woopra }; +export { default as Woopra } from './browser'; diff --git a/src/integrations/Woopra/nativeSdkLoader.js b/src/integrations/Woopra/nativeSdkLoader.js new file mode 100644 index 000000000..05c05706e --- /dev/null +++ b/src/integrations/Woopra/nativeSdkLoader.js @@ -0,0 +1,43 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk() { + !(function () { + var t, + o, + c, + e = window, + n = document, + r = arguments, + a = 'script', + i = [ + 'call', + 'cancelAction', + 'config', + 'identify', + 'push', + 'track', + 'trackClick', + 'trackForm', + 'update', + 'visit', + ], + s = function () { + var t, + o = this, + c = function (t) { + o[t] = function () { + return o._e.push([t].concat(Array.prototype.slice.call(arguments, 0))), o; + }; + }; + for (o._e = [], t = 0; t < i.length; t++) c(i[t]); + }; + for (e.__woo = e.__woo || {}, t = 0; t < r.length; t++) + e.__woo[r[t]] = e[r[t]] = e[r[t]] || new s(); + ((o = n.createElement(a)).async = 1), + (o.src = 'https://static.woopra.com/w.js'), + o.setAttribute('data-loader', LOAD_ORIGIN), + (c = n.getElementsByTagName(a)[0]).parentNode.insertBefore(o, c); + })('Woopra'); +} + +export { loadNativeSdk }; diff --git a/src/integrations/YandexMetrica/browser.js b/src/integrations/YandexMetrica/browser.js index 44b203c09..5919dbf18 100644 --- a/src/integrations/YandexMetrica/browser.js +++ b/src/integrations/YandexMetrica/browser.js @@ -1,3 +1,4 @@ +/* eslint-disable class-methods-use-this */ import logger from '../../utils/logUtil'; import { ecommEventPayload, sendEvent, ecommerceEventMapping } from './utils'; @@ -6,8 +7,8 @@ import { getHashFromArrayWithDuplicate, } from '../../utils/commonUtils'; import { NAME } from './constants'; -import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; import { getDefinedTraits } from '../../utils/utils'; +import { loadNativeSdk } from './nativeSdkLoader'; class YandexMetrica { constructor(config, analytics, destinationInfo) { @@ -32,35 +33,14 @@ class YandexMetrica { } loadScript() { - (function (m, e, t, r, i, k, a) { - m[i] = - m[i] || - function () { - (m[i].a = m[i].a || []).push(arguments); - }; - m[i].l = 1 * new Date(); - for (var j = 0; j < document.scripts.length; j++) { - if (document.scripts[j].src === r) { - return; - } - } - (k = e.createElement(t)), - (a = e.getElementsByTagName(t)[0]), - (k.async = 1), - (k.src = r), - k.setAttribute('data-loader', LOAD_ORIGIN), - a.parentNode.insertBefore(k, a); - })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js', 'ym'); - - ym(this.tagId, 'init', { - clickmap: this.clickMap, - trackLinks: this.trackLinks, - accurateTrackBounce: this.accurateTrackBounce, - webvisor: this.webvisor, - ecommerce: this.containerName, - }); - window[`${this.containerName}`] = window[`${this.containerName}`] || []; - window[`${this.containerName}`].push({}); + loadNativeSdk( + this.tagId, + this.clickMap, + this.trackLinks, + this.accurateTrackBounce, + this.webvisor, + this.containerName, + ); } init() { @@ -85,7 +65,7 @@ class YandexMetrica { const { message } = rudderElement; const { userId } = getDefinedTraits(message); let payload = { UserID: userId }; - if (!(message.context && message.context.traits)) { + if (!message?.context?.traits) { logger.debug('user traits not present'); } else { const { traits } = message.context; @@ -100,7 +80,7 @@ class YandexMetrica { logger.debug('===In YandexMetrica track==='); const { message } = rudderElement; - const { event } = message; + const { event, properties } = message; const eventMappingFromConfigMap = getHashFromArrayWithDuplicate( this.eventNameToYandexEvent, 'from', @@ -115,22 +95,19 @@ class YandexMetrica { const ecomEvents = Object.keys(ecommerceEventMapping); const trimmedEvent = event.trim().replace(/\s+/g, '_'); - if (!message.properties) { + if (!properties) { logger.error('Properties is not present in the payload'); return; } if (eventMappingFromConfigMap[event]) { eventMappingFromConfigMap[event].forEach((eventType) => { - sendEvent( - this.containerName, - ecommEventPayload(eventType, message.properties, this.goalId), - ); + sendEvent(this.containerName, ecommEventPayload(eventType, properties, this.goalId)); }); } else if (ecomEvents.includes(trimmedEvent)) { sendEvent( this.containerName, - ecommEventPayload(ecommerceEventMapping[trimmedEvent], message.properties, this.goalId), + ecommEventPayload(ecommerceEventMapping[trimmedEvent], properties, this.goalId), ); } else { logger.error( @@ -143,7 +120,7 @@ class YandexMetrica { page(rudderElement) { logger.debug('===In YandexMetrica Page==='); const { message } = rudderElement; - if (!(message.context && message.context.page)) { + if (!message?.context?.page) { logger.error('page object containing page properties are not present in the payload'); return; } diff --git a/src/integrations/YandexMetrica/index.js b/src/integrations/YandexMetrica/index.js index 3da553bc6..aaaf1f1ee 100644 --- a/src/integrations/YandexMetrica/index.js +++ b/src/integrations/YandexMetrica/index.js @@ -1,3 +1 @@ -import YandexMetrica from './browser'; - -export { YandexMetrica }; +export { default as YandexMetrica } from './browser'; diff --git a/src/integrations/YandexMetrica/nativeSdkLoader.js b/src/integrations/YandexMetrica/nativeSdkLoader.js new file mode 100644 index 000000000..599a4d9e1 --- /dev/null +++ b/src/integrations/YandexMetrica/nativeSdkLoader.js @@ -0,0 +1,35 @@ +import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; + +function loadNativeSdk(tagId, clickmap, trackLinks, accurateTrackBounce, webvisor, ecommerce) { + (function (m, e, t, r, i, k, a) { + m[i] = + m[i] || + function () { + (m[i].a = m[i].a || []).push(arguments); + }; + m[i].l = 1 * new Date(); + for (var j = 0; j < document.scripts.length; j++) { + if (document.scripts[j].src === r) { + return; + } + } + (k = e.createElement(t)), + (a = e.getElementsByTagName(t)[0]), + (k.async = 1), + (k.src = r), + k.setAttribute('data-loader', LOAD_ORIGIN), + a.parentNode.insertBefore(k, a); + })(window, document, 'script', 'https://mc.yandex.ru/metrika/tag.js', 'ym'); + + ym(tagId, 'init', { + clickmap, + trackLinks, + accurateTrackBounce, + webvisor, + ecommerce, + }); + window[`${ecommerce}`] = window[`${ecommerce}`] || []; + window[`${ecommerce}`].push({}); +} + +export { loadNativeSdk }; diff --git a/src/integrations/YandexMetrica/utils.js b/src/integrations/YandexMetrica/utils.js index 4aad6d15e..0c6f04124 100644 --- a/src/integrations/YandexMetrica/utils.js +++ b/src/integrations/YandexMetrica/utils.js @@ -1,8 +1,8 @@ import logger from '../../utils/logUtil'; import { - removeUndefinedAndNullAndEmptyValues, removeUndefinedAndNullValues, + removeUndefinedAndNullAndEmptyValues, } from '../../utils/commonUtils'; // This function is used for sending the track event to yandex.metrica @@ -42,6 +42,7 @@ const itemProperties = (properties) => { * @param {*} products * @returns */ + const populatePayload = (eventType, properties, products) => { const payload = {}; products.push(itemProperties(properties)); @@ -54,20 +55,44 @@ const populatePayload = (eventType, properties, products) => { }; /** - * This function is used to prepare and return the final payload to be sent - * @param {*} eventType - This is the e-commerce event type of yandex.metrica - * @param {*} properties - Properties passed in the track call - * @param {*} goalId - goalId taken from UI - * @returns the responsePayload to be sent to yandex.metrica + * Returns actionField + * @param {*} properties + * @param {*} goalId + * @returns */ -const ecommEventPayload = (eventType, properties, goalId) => { +const getActionField = (properties, goalId) => { + const { order_id: orderId, coupon, revenue } = properties; + const actionField = { + id: orderId, + coupon, + goal_id: goalId, + revenue, + }; + + // converting the goal_id and revenue in actionField to int and float type + actionField.goal_id = actionField.goal_id + ? parseInt(actionField.goal_id, 10) + : actionField.goal_id; + actionField.revenue = actionField.revenue ? parseFloat(actionField.revenue) : actionField.revenue; + + return actionField; +}; + +/** + * Returns response payload + * @param {*} properties + * @param {*} eventType + * @returns + */ +const getResponsePayload = (properties, eventType) => { let responsePayload = {}; - const { products } = properties; const productsArray = []; + const { products, product_id: productId, name } = properties; - // checking for products array if available each of the product inside is used - // populate the final payload else the product information inside the properties - // is used + /** + * checking for products array if available each of the product inside is used + * populate the final payload else the product information inside the properties is used + */ if (products && Array.isArray(products)) { products.forEach((element, index) => { if (!(element.product_id || element.name)) { @@ -76,32 +101,37 @@ const ecommEventPayload = (eventType, properties, goalId) => { responsePayload = populatePayload(eventType, element, productsArray); } }); - } else if (!(properties.product_id || properties.name)) { + } else if (!(productId || name)) { logger.error(`None of product_id or name is present for the product`); } else { responsePayload = populatePayload(eventType, properties, productsArray); } + + return responsePayload; +}; + +/** + * This function is used to prepare and return the final payload to be sent + * @param {*} eventType - This is the e-commerce event type of yandex.metrica + * @param {*} properties - Properties passed in the track call + * @param {*} goalId - goalId taken from UI + * @returns the responsePayload to be sent to yandex.metrica + */ +const ecommEventPayload = (eventType, properties, goalId) => { + const { order_id: orderId } = properties; + + const responsePayload = getResponsePayload(properties, eventType); + // populating actionField object required for purchase event type if (eventType === 'purchase') { - if (!properties.order_id) { + if (!orderId) { logger.error('order_id is required for event type purchase'); } - const actionField = { - id: properties.order_id, - coupon: properties.coupon, - goal_id: goalId, - revenue: properties.revenue, - }; - // converting the goal_id and revenue in actionField to int and float type - actionField.goal_id = actionField.goal_id - ? parseInt(actionField.goal_id, 10) - : actionField.goal_id; - actionField.revenue = actionField.revenue - ? parseFloat(actionField.revenue) - : actionField.revenue; - responsePayload.ecommerce[eventType].actionField = - removeUndefinedAndNullAndEmptyValues(actionField); + responsePayload.ecommerce[eventType].actionField = removeUndefinedAndNullAndEmptyValues( + getActionField(properties, goalId), + ); } + return removeUndefinedAndNullValues(responsePayload); }; From 5ccc241792ab29d53cd9af706671bca8b392ac1b Mon Sep 17 00:00:00 2001 From: Moumita <36885121+MoumitaM@users.noreply.github.com> Date: Thu, 17 Aug 2023 09:34:00 +0530 Subject: [PATCH 12/15] ci: sync assets to s3 with deploy prod action (#1300) * ci: sync assets to s3 with deploy prod action * chore: updated file location --------- Co-authored-by: Moumita Mandal --- .github/workflows/deploy-prod.yml | 5 +++++ .../AdobeAnalytics}/adobe-analytics-heartbeat.js | 0 .../AdobeAnalytics}/adobe-analytics-js.js | 0 3 files changed, 5 insertions(+) rename assets/{adobe-analytics-js => integrations/AdobeAnalytics}/adobe-analytics-heartbeat.js (100%) rename assets/{adobe-analytics-js => integrations/AdobeAnalytics}/adobe-analytics-js.js (100%) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 2efc61ee5..dfbd88484 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -87,6 +87,11 @@ jobs: aws s3 cp public/list_integration_sdks.html s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/latest/modern/list_integration_sdks.html --cache-control max-age=3600 AWS_MAX_ATTEMPTS=10 aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/latest*" + - name: Sync assets to S3 + run: | + aws s3 cp assets/integrations/AdobeAnalytics/ s3://${{ secrets.AWS_PROD_S3_BUCKET_NAME }}/adobe-analytics-js --cache-control max-age=3600 + aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_PROD_CF_DISTRIBUTION_ID }} --paths "/adobe-analytics-js*" + - name: Send message to Slack channel id: slack uses: slackapi/slack-github-action@v1.24.0 diff --git a/assets/adobe-analytics-js/adobe-analytics-heartbeat.js b/assets/integrations/AdobeAnalytics/adobe-analytics-heartbeat.js similarity index 100% rename from assets/adobe-analytics-js/adobe-analytics-heartbeat.js rename to assets/integrations/AdobeAnalytics/adobe-analytics-heartbeat.js diff --git a/assets/adobe-analytics-js/adobe-analytics-js.js b/assets/integrations/AdobeAnalytics/adobe-analytics-js.js similarity index 100% rename from assets/adobe-analytics-js/adobe-analytics-js.js rename to assets/integrations/AdobeAnalytics/adobe-analytics-js.js From 5c5c37fc828e04b878ade63d871fab14138f68c6 Mon Sep 17 00:00:00 2001 From: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com> Date: Thu, 17 Aug 2023 11:03:53 +0530 Subject: [PATCH 13/15] chore: enable workflows for hotfix prs (#1307) --- .github/workflows/build-and-quality-checks-v3.yml | 2 +- .github/workflows/build-and-quality-checks.yml | 2 +- .github/workflows/check_pr_title.yml | 2 +- .github/workflows/test-v3.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-and-quality-checks-v3.yml b/.github/workflows/build-and-quality-checks-v3.yml index ecae4883d..5ba311175 100644 --- a/.github/workflows/build-and-quality-checks-v3.yml +++ b/.github/workflows/build-and-quality-checks-v3.yml @@ -2,7 +2,7 @@ name: Build & Code Quality Checks v3 on: pull_request: - branches: ['develop', 'main'] + branches: ['develop', 'main', 'hotfix/*'] types: ['opened', 'reopened', 'synchronize'] jobs: diff --git a/.github/workflows/build-and-quality-checks.yml b/.github/workflows/build-and-quality-checks.yml index 3292a8062..6df62e3aa 100644 --- a/.github/workflows/build-and-quality-checks.yml +++ b/.github/workflows/build-and-quality-checks.yml @@ -2,7 +2,7 @@ name: Build & Code Quality Checks on: pull_request: - branches: ['production', 'production-staging'] + branches: ['production', 'production-staging', 'hotfix/*'] types: ['opened', 'reopened', 'synchronize'] jobs: diff --git a/.github/workflows/check_pr_title.yml b/.github/workflows/check_pr_title.yml index 52089f607..0ffb41d6f 100644 --- a/.github/workflows/check_pr_title.yml +++ b/.github/workflows/check_pr_title.yml @@ -2,7 +2,7 @@ name: Check PR title on: pull_request: - branches: ['production', 'production-staging'] + branches: ['production', 'production-staging', 'hotfix/*'] types: ['opened', 'reopened', 'edited', 'synchronize'] jobs: diff --git a/.github/workflows/test-v3.yml b/.github/workflows/test-v3.yml index 2f4895218..ca030899a 100644 --- a/.github/workflows/test-v3.yml +++ b/.github/workflows/test-v3.yml @@ -5,7 +5,7 @@ on: push: branches: ['main', 'develop'] pull_request: - branches: ['main', 'develop'] + branches: ['main', 'develop', 'hotfix/*'] types: ['opened', 'reopened', 'synchronize'] jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a2a7988c..ebcacaefe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: push: branches: ['production', 'production-staging'] pull_request: - branches: ['production', 'production-staging'] + branches: ['production', 'production-staging', 'hotfix/*'] types: ['opened', 'reopened', 'synchronize'] jobs: From 66092d357ffa87c8a39ffa05a1cd4c57085c7d47 Mon Sep 17 00:00:00 2001 From: Anant Jain <62471433+anantjain45823@users.noreply.github.com> Date: Thu, 17 Aug 2023 20:06:35 +0530 Subject: [PATCH 14/15] fix: add support for EU region specific script (#1305) * fix: incorporating eu region during intialization * chore: merge production-staging --- src/integrations/CustomerIO/browser.js | 6 ++++-- src/integrations/CustomerIO/nativeSdkLoader.js | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/integrations/CustomerIO/browser.js b/src/integrations/CustomerIO/browser.js index d932a9b73..cc688943b 100644 --- a/src/integrations/CustomerIO/browser.js +++ b/src/integrations/CustomerIO/browser.js @@ -12,6 +12,7 @@ class CustomerIO { this.analytics = analytics; this.siteID = config.siteID; this.apiKey = config.apiKey; + this.datacenterEU = config.datacenterEU; this.sendPageNameInSDK = config.sendPageNameInSDK; this.name = NAME; ({ @@ -23,8 +24,9 @@ class CustomerIO { init() { logger.debug('===in init Customer IO init==='); - const { siteID } = this; - loadNativeSdk(siteID); + const { siteID, datacenterEU } = this; + loadNativeSdk(siteID, datacenterEU); + } identify(rudderElement) { diff --git a/src/integrations/CustomerIO/nativeSdkLoader.js b/src/integrations/CustomerIO/nativeSdkLoader.js index c5b64d3ac..fe0d1ae40 100644 --- a/src/integrations/CustomerIO/nativeSdkLoader.js +++ b/src/integrations/CustomerIO/nativeSdkLoader.js @@ -1,6 +1,6 @@ import { LOAD_ORIGIN } from '../../utils/ScriptLoader'; -function loadNativeSdk(siteID) { +function loadNativeSdk(siteID, datacenterEU) { window._cio = window._cio || []; (function () { let a; @@ -22,6 +22,9 @@ function loadNativeSdk(siteID) { t.id = 'cio-tracker'; t.setAttribute('data-site-id', siteID); t.src = 'https://assets.customer.io/assets/track.js'; + if (datacenterEU === true) { + t.src = 'https://assets.customer.io/assets/track-eu.js'; + } s.parentNode.insertBefore(t, s); })(); } From 5c390e9a929cf0ef99b7fd65c0a052932baf3141 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Fri, 18 Aug 2023 10:22:47 +0000 Subject: [PATCH 15/15] chore(release): 2.40.3 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/npm/package.json | 2 +- sonar-project.properties | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69510d3d..6904f0994 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [2.40.3](https://github.com/rudderlabs/rudder-sdk-js/compare/v2.40.2...v2.40.3) (2023-08-18) + + +### Bug Fixes + +* add support for EU region specific script ([#1305](https://github.com/rudderlabs/rudder-sdk-js/issues/1305)) ([66092d3](https://github.com/rudderlabs/rudder-sdk-js/commit/66092d357ffa87c8a39ffa05a1cd4c57085c7d47)) +* **INT-183:** resolve sonar issues and move integration script to separate file ([#1249](https://github.com/rudderlabs/rudder-sdk-js/issues/1249)) ([7afcc16](https://github.com/rudderlabs/rudder-sdk-js/commit/7afcc168de223c9765b13398c5bd1a78d9344333)), closes [#1195](https://github.com/rudderlabs/rudder-sdk-js/issues/1195) + ### [2.40.2](https://github.com/rudderlabs/rudder-sdk-js/compare/v2.40.1...v2.40.2) (2023-08-16) diff --git a/package.json b/package.json index 2cf9c0648..d430076d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rudder-analytics", - "version": "2.40.2", + "version": "2.40.3", "description": "", "main": "./dist/browser.min.js", "scripts": { diff --git a/packages/npm/package.json b/packages/npm/package.json index 0680bdf96..e9de6f08f 100644 --- a/packages/npm/package.json +++ b/packages/npm/package.json @@ -1,6 +1,6 @@ { "name": "rudder-sdk-js", - "version": "2.40.2", + "version": "2.40.3", "description": "RudderStack Javascript SDK", "main": "index.js", "module": "index.es.js", diff --git a/sonar-project.properties b/sonar-project.properties index 57fa5150b..db95e4717 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=2.40.2 +sonar.projectVersion=2.40.3 # Meta-data for the project sonar.links.scm=https://github.com/rudderlabs/rudder-sdk-js