From 5cac08d48a4380e18f35637b21c36adcd05f35c7 Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Mon, 5 Jun 2023 15:17:21 -0700
Subject: [PATCH 001/214] build: allow lerna version commands from v1.x branch
---
lerna.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lerna.json b/lerna.json
index fbc25b49f..5328648e6 100644
--- a/lerna.json
+++ b/lerna.json
@@ -7,7 +7,7 @@
"useWorkspaces": true,
"command": {
"version": {
- "allowBranch": "main",
+ "allowBranch": "v1.x",
"conventionalCommits": true,
"createRelease": "github",
"message": "chore(release): publish",
From cd8e6bd13a779937a61d32dd30439c89aeee4ab2 Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Mon, 5 Jun 2023 16:27:28 -0700
Subject: [PATCH 002/214] build: support release jobs for multiple versions
(#403)
---
.github/workflows/ci.yml | 2 +
.github/workflows/publish-v1.yml | 117 +++++++++++++++++++
.github/workflows/{cd.yml => publish-v2.yml} | 14 +--
.github/workflows/s3-upload-test.yml | 43 -------
4 files changed, 126 insertions(+), 50 deletions(-)
create mode 100644 .github/workflows/publish-v1.yml
rename .github/workflows/{cd.yml => publish-v2.yml} (92%)
delete mode 100644 .github/workflows/s3-upload-test.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5a67cd2ff..371afb256 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,9 +4,11 @@ on:
push:
branches:
- main
+ - v1.x
pull_request:
branches:
- main
+ - v1.x
jobs:
build:
diff --git a/.github/workflows/publish-v1.yml b/.github/workflows/publish-v1.yml
new file mode 100644
index 000000000..da4e5e204
--- /dev/null
+++ b/.github/workflows/publish-v1.yml
@@ -0,0 +1,117 @@
+name: Publish v1.x
+
+on:
+ workflow_dispatch:
+ inputs:
+ releaseType:
+ type: choice
+ description: Release Type
+ options:
+ - release
+ - prerelease
+ - graduate
+
+jobs:
+ authorize:
+ name: Authorize
+ runs-on: ubuntu-latest
+ steps:
+ - name: ${{ github.actor }} permission check to do a release
+ uses: 'lannonbr/repo-permission-check-action@2.0.2'
+ with:
+ permission: 'write'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+ needs: [authorize]
+ permissions:
+ id-token: write
+ contents: write
+ env:
+ RELEASE_TYPE: ${{ github.event.inputs.releaseType }}
+ strategy:
+ matrix:
+ node-version: [16.x]
+
+ steps:
+ - name: Check out git repository
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: v1.x
+
+ - name: Cache dependencies
+ uses: actions/cache@v3
+ with:
+ path: '**/node_modules'
+ key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
+
+ - name: Setup Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Install project dependencies
+ run: |
+ yarn install --frozen-lockfile
+
+ - name: Build all packages
+ run: |
+ yarn build
+
+ - name: Test all packages
+ run: |
+ yarn test
+
+ - name: Lint all packages
+ run: |
+ yarn lint
+
+ - name: Configure Git User
+ run: |
+ git config --global user.name amplitude-sdk-bot
+ git config --global user.email amplitude-sdk-bot@users.noreply.github.com
+
+ - name: Configure NPM User
+ run: |
+ echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_PUBLISH_TOKEN }}" > ~/.npmrc
+ npm whoami
+
+ - name: Configure AWS Credentials
+ uses: aws-actions/configure-aws-credentials@v1
+ with:
+ role-to-assume: arn:aws:iam::358203115967:role/github-actions-role
+ aws-region: us-west-2
+
+ # https://www.npmjs.com/package/@lerna/version#--conventional-prerelease
+ # patch: 1.0.0 -> 1.0.1-alpha.0
+ # minor: 1.0.0 -> 1.1.0-alpha.0
+ # major: 1.0.0 -> 2.0.0-alpha.0
+ - name: Create pre-release version
+ if: ${{ env.RELEASE_TYPE == 'prerelease'}}
+ run: |
+ GH_TOKEN=${{ secrets.GH_PUBLISH_TOKEN }} npm run deploy:version -- -y --conventional-prerelease
+
+ # https://www.npmjs.com/package/@lerna/version#--conventional-graduate
+ # 1.0.0-alpha.0 -> 1.0.1
+ - name: Create graduate version
+ if: ${{ env.RELEASE_TYPE == 'graduate'}}
+ run: |
+ GH_TOKEN=${{ secrets.GH_PUBLISH_TOKEN }} npm run deploy:version -- -y --conventional-graduate
+
+ # Use 'release' for the usual deployment
+ # NOTE: You probably want this
+ - name: Create release version
+ if: ${{ env.RELEASE_TYPE == 'release'}}
+ run: |
+ GH_TOKEN=${{ secrets.GH_PUBLISH_TOKEN }} npm run deploy:version -- -y
+
+ # Use 'from git' option if `lerna version` has already been run
+ - name: Publish Release to NPM
+ run: |
+ GH_TOKEN=${{ secrets.GH_PUBLISH_TOKEN }} npm run deploy:publish -- from-git -y
+ env:
+ S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
diff --git a/.github/workflows/cd.yml b/.github/workflows/publish-v2.yml
similarity index 92%
rename from .github/workflows/cd.yml
rename to .github/workflows/publish-v2.yml
index 2a4f9f201..1e5006065 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/publish-v2.yml
@@ -1,4 +1,4 @@
-name: Continuous Deployment
+name: Publish v2.x
on:
workflow_dispatch:
@@ -16,12 +16,12 @@ jobs:
name: Authorize
runs-on: ubuntu-latest
steps:
- - name: ${{ github.actor }} permission check to do a release
- uses: "lannonbr/repo-permission-check-action@2.0.2"
- with:
- permission: "write"
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: ${{ github.actor }} permission check to do a release
+ uses: 'lannonbr/repo-permission-check-action@2.0.2'
+ with:
+ permission: 'write'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
deploy:
name: Deploy
diff --git a/.github/workflows/s3-upload-test.yml b/.github/workflows/s3-upload-test.yml
deleted file mode 100644
index 635b24a95..000000000
--- a/.github/workflows/s3-upload-test.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-name: S3 Upload Test
-
-on: workflow_dispatch
-
-jobs:
- deploy:
- name: Deploy
- runs-on: ubuntu-latest
- permissions:
- id-token: write
- contents: read
- strategy:
- matrix:
- node-version: [16.x]
-
- steps:
- - name: Check out git repository
- uses: actions/checkout@v3
-
- - name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
-
- - name: Install project dependencies
- run: |
- yarn install --frozen-lockfile
-
- - name: Build all packages
- run: |
- yarn build
-
- - name: Configure AWS Credentials
- uses: aws-actions/configure-aws-credentials@v1
- with:
- role-to-assume: arn:aws:iam::358203115967:role/github-actions-role
- aws-region: us-west-2
-
- - name: Publish to S3
- run: |
- cd packages/analytics-browser && node scripts/publish/upload-to-s3.js
- env:
- S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
From 05eb25a635c91ee2f11d2391483678dd0768f80b Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 6 Jun 2023 00:04:29 +0000
Subject: [PATCH 003/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.0-beta.1
- @amplitude/analytics-browser@1.10.6-beta.0
- @amplitude/analytics-client-common@1.0.0-beta.1
- @amplitude/analytics-core@1.0.0-beta.1
- @amplitude/analytics-node-test@1.0.0-beta.1
- @amplitude/analytics-node@1.1.7-beta.0
- @amplitude/analytics-react-native@1.1.8-beta.0
- @amplitude/analytics-types@1.0.0-beta.1
- @amplitude/marketing-analytics-browser@1.0.0-beta.1
- @amplitude/plugin-page-view-tracking-browser@1.0.0-beta.1
- @amplitude/plugin-web-attribution-browser@1.0.0-beta.1
---
packages/analytics-browser-test/CHANGELOG.md | 9 +++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 9 +++++++++
packages/analytics-browser/package.json | 12 ++++++------
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 9 +++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 9 +++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 +++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 9 +++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 9 +++++++++
packages/analytics-react-native/package.json | 8 ++++----
packages/analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 9 +++++++++
packages/analytics-types/package.json | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/package.json | 14 +++++++-------
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 8 ++++----
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 8 ++++----
27 files changed, 142 insertions(+), 43 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index 04d7e39ea..28f8c7b48 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.4.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@2.7.0...@amplitude/analytics-browser-test@1.4.0-beta.1) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [2.7.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@2.6.2...@amplitude/analytics-browser-test@2.7.0) (2023-05-04)
### Features
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index e6a5f1ec8..cca8aa9f4 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.0-beta.0",
+ "version": "1.4.0-beta.1",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.5"
+ "@amplitude/analytics-browser": "^1.10.6-beta.0"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 70d676c63..34ee26082 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.10.6-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.5...@amplitude/analytics-browser@1.10.6-beta.0) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/analytics-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.10.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.4...@amplitude/analytics-browser@1.10.5) (2023-06-05)
**Note:** Version bump only for package @amplitude/analytics-browser
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 5e075ba18..5d769470d 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.10.5",
+ "version": "1.10.6-beta.0",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -43,11 +43,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0-beta.0",
- "@amplitude/analytics-core": "^1.0.0-beta.0",
- "@amplitude/analytics-types": "^1.0.0-beta.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.0-beta.0",
- "@amplitude/plugin-web-attribution-browser": "^1.0.0-beta.0",
+ "@amplitude/analytics-client-common": "^1.0.0-beta.1",
+ "@amplitude/analytics-core": "^1.0.0-beta.1",
+ "@amplitude/analytics-types": "^1.0.0-beta.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.0-beta.1",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.0-beta.1",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 95196138e..1540df58e 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u
Date: Tue, 6 Jun 2023 00:49:36 +0000
Subject: [PATCH 004/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.0
- @amplitude/analytics-browser@1.10.6
- @amplitude/analytics-client-common@1.0.0
- @amplitude/analytics-core@1.0.0
- @amplitude/analytics-node-test@1.0.0
- @amplitude/analytics-node@1.1.7
- @amplitude/analytics-react-native@1.1.8
- @amplitude/analytics-types@1.0.0
- @amplitude/marketing-analytics-browser@1.0.0
- @amplitude/plugin-page-view-tracking-browser@1.0.0
- @amplitude/plugin-web-attribution-browser@1.0.0
---
packages/analytics-browser-test/CHANGELOG.md | 9 +++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 9 +++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 12 ++++++------
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 9 +++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 9 +++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 +++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 9 +++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 9 +++++++++
packages/analytics-react-native/package.json | 8 ++++----
packages/analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 9 +++++++++
packages/analytics-types/package.json | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 14 +++++++-------
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 8 ++++----
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 8 ++++----
32 files changed, 150 insertions(+), 51 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index 28f8c7b48..5a9c21546 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.4.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.0-beta.1...@amplitude/analytics-browser-test@1.4.0) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.4.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@2.7.0...@amplitude/analytics-browser-test@1.4.0-beta.1) (2023-06-06)
**Note:** Version bump only for package @amplitude/analytics-browser-test
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index cca8aa9f4..86af55ecc 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.0-beta.1",
+ "version": "1.4.0",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.6-beta.0"
+ "@amplitude/analytics-browser": "^1.10.6"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 34ee26082..9af42e906 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.10.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.6-beta.0...@amplitude/analytics-browser@1.10.6) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/analytics-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.10.6-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.5...@amplitude/analytics-browser@1.10.6-beta.0) (2023-06-06)
**Note:** Version bump only for package @amplitude/analytics-browser
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index fb9a9ca12..ca05dd049 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index 6fe25dc9f..18820c265 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-wZAYoU/Dyp5YYHRl09XkA/xsjCGm1JgxV6S6nBBmjPIU9BEGyQvTKUEXbjCUoZPJ';
+ as.integrity = 'sha384-/pEyHhmhzhlIzPsIq1ptBvg2RLgkHo5wAhA7DEi6bJ+jNOQ7Us/bz8mfKQ8hGyZX';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.5-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.6-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 5d769470d..6d3345b8e 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.10.6-beta.0",
+ "version": "1.10.6",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -43,11 +43,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0-beta.1",
- "@amplitude/analytics-core": "^1.0.0-beta.1",
- "@amplitude/analytics-types": "^1.0.0-beta.1",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.0-beta.1",
- "@amplitude/plugin-web-attribution-browser": "^1.0.0-beta.1",
+ "@amplitude/analytics-client-common": "^1.0.0",
+ "@amplitude/analytics-core": "^1.0.0",
+ "@amplitude/analytics-types": "^1.0.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.0",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.0",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 1540df58e..e0aa00876 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index 6407ee1a7..6907fb8c4 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-MMalHt3JsD6i8mixGQEG238By7xYZ+Wfis8cupNugcLCLPUmgun3P7LiuUQde7ik';
+ as.integrity = 'sha384-tUv8KmK/ZnVrRFOEomj4a/y0P/8YBXFXzZLp0z7XtJ4jA21xYr0klVVwzNfbWuCb';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-0.8.2-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.0-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index ef03328b7..4a646a317 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-45UUPoBhjGCCKb9UnBTn9IDGJAnYh7htP6Wfisz7CZvSa763JxUcV+CW3CTn9k5X';
+ as.integrity = 'sha384-tkD0ly9SyHYmmRIB7UNGsvThI7j3D06eYIxxNbb44krcClCTELy2yQgLNxwk58/1';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-0.8.2-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.0-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index 7f7a1ca89..e348dad3d 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.0-beta.1",
+ "version": "1.0.0",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -42,12 +42,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.6-beta.0",
- "@amplitude/analytics-client-common": "^1.0.0-beta.1",
- "@amplitude/analytics-core": "^1.0.0-beta.1",
- "@amplitude/analytics-types": "^1.0.0-beta.1",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.0-beta.1",
- "@amplitude/plugin-web-attribution-browser": "^1.0.0-beta.1",
+ "@amplitude/analytics-browser": "^1.10.6",
+ "@amplitude/analytics-client-common": "^1.0.0",
+ "@amplitude/analytics-core": "^1.0.0",
+ "@amplitude/analytics-types": "^1.0.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.0",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.0",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index 8aa8738be..059488afe 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.0-beta.1';
+export const VERSION = '1.0.0';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 4fbb875ef..2f7ba0442 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.0-beta.1...@amplitude/plugin-page-view-tracking-browser@1.0.0) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.0.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@0.8.2...@amplitude/plugin-page-view-tracking-browser@1.0.0-beta.1) (2023-06-06)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index e2a56c83a..75dfc80ae 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.0-beta.1",
+ "version": "1.0.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -36,12 +36,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0-beta.1",
- "@amplitude/analytics-types": "^1.0.0-beta.1",
+ "@amplitude/analytics-client-common": "^1.0.0",
+ "@amplitude/analytics-types": "^1.0.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.6-beta.0",
+ "@amplitude/analytics-browser": "^1.10.6",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index 07b239c08..5087729cd 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.0-beta.1...@amplitude/plugin-web-attribution-browser@1.0.0) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.0.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.7.0...@amplitude/plugin-web-attribution-browser@1.0.0-beta.1) (2023-06-06)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index cdf776148..2ac2c998e 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.0-beta.1",
+ "version": "1.0.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -36,12 +36,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0-beta.1",
- "@amplitude/analytics-types": "^1.0.0-beta.1",
+ "@amplitude/analytics-client-common": "^1.0.0",
+ "@amplitude/analytics-types": "^1.0.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.6-beta.0",
+ "@amplitude/analytics-browser": "^1.10.6",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From d3091418156cda085f8ad667e8237b904979d059 Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Tue, 6 Jun 2023 00:03:29 -0700
Subject: [PATCH 005/214] build: remove unnecessary beta script guard (#409)
---
scripts/version/create-snippet.js | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/scripts/version/create-snippet.js b/scripts/version/create-snippet.js
index 5f5d0e482..f711a520f 100644
--- a/scripts/version/create-snippet.js
+++ b/scripts/version/create-snippet.js
@@ -4,9 +4,9 @@ const path = require('path');
const { snippet } = require('../templates/browser-snippet.template');
const { getName, getVersion } = require('../utils');
const babel = require('@babel/core');
-const yargs = require('yargs/yargs')
-const { hideBin } = require('yargs/helpers')
-const argv = yargs(hideBin(process.argv)).argv
+const yargs = require('yargs/yargs');
+const { hideBin } = require('yargs/helpers');
+const argv = yargs(hideBin(process.argv)).argv;
// Setup input
const inputDir = 'lib/scripts';
@@ -33,13 +33,11 @@ const encoding = 'base64';
const inputText = fs.readFileSync(inputPath, 'utf-8');
const integrity = algorithm + '-' + crypto.createHash(algorithm).update(inputText).digest(encoding);
const version = getVersion() || '';
-const outputText = header + snippet(getName()+nameSuffix, integrity, getVersion(), globalVar);
+const outputText = header + snippet(getName() + nameSuffix, integrity, getVersion(), globalVar);
const { code: transpiledOutputText } = babel.transformSync(outputText, {
presets: ['env'],
});
-if (!version.includes('beta')) {
- // Write to disk
- fs.writeFileSync(outputPath, transpiledOutputText);
- console.log(`Generated ${outputFile}`);
-}
\ No newline at end of file
+// Write to disk
+fs.writeFileSync(outputPath, transpiledOutputText);
+console.log(`Generated ${outputFile}`);
From ce17aabe3e0d04386ed201e6b9795ca8d42bd711 Mon Sep 17 00:00:00 2001
From: Justin Fiedler
Date: Tue, 6 Jun 2023 11:19:37 -0700
Subject: [PATCH 006/214] fix: don't automatically start new session on
setUserId (#400)
* fix: allow optionally starting new session on setUserId
* fix: update setUserId(id, startNewSession) always start new session if requested
* fix: update web-client to allow for `startNewSession`
* chore: update session integration tests
* chore: test fix
* chore: revert interface changes
* chore: revert interface changes in tests
* chore: update tests for new session change logic
* chore: fix set user test
---
.../analytics-browser-test/test/index.test.ts | 100 ------------------
.../analytics-browser/src/browser-client.ts | 1 -
.../test/browser-client.test.ts | 4 +-
3 files changed, 2 insertions(+), 103 deletions(-)
diff --git a/packages/analytics-browser-test/test/index.test.ts b/packages/analytics-browser-test/test/index.test.ts
index ac40ad773..9b3072dbd 100644
--- a/packages/analytics-browser-test/test/index.test.ts
+++ b/packages/analytics-browser-test/test/index.test.ts
@@ -982,16 +982,6 @@ describe('integration', () => {
eventType: 'Event in next session',
userId: 'user1@amplitude.com',
},
- {
- eventType: 'session_end',
- userId: 'user1@amplitude.com',
- },
- ],
- [
- {
- eventType: 'session_start',
- userId: 'user2@amplitude.com',
- },
],
]);
expect(payload).toEqual({
@@ -1155,44 +1145,6 @@ describe('integration', () => {
user_agent: userAgent,
user_id: 'user1@amplitude.com',
},
- {
- device_id: uuid,
- device_manufacturer: undefined,
- event_id: 6,
- event_type: 'session_end',
- insert_id: uuid,
- ip: '$remote',
- language: 'en-US',
- library,
- os_name: 'WebKit',
- os_version: '537.36',
- partner_id: undefined,
- plan: undefined,
- platform: 'Web',
- session_id: number,
- time: number,
- user_agent: userAgent,
- user_id: 'user1@amplitude.com',
- },
- {
- device_id: uuid,
- device_manufacturer: undefined,
- event_id: 7,
- event_type: 'session_start',
- insert_id: uuid,
- ip: '$remote',
- language: 'en-US',
- library,
- os_name: 'WebKit',
- os_version: '537.36',
- partner_id: undefined,
- plan: undefined,
- platform: 'Web',
- session_id: number,
- time: number,
- user_agent: userAgent,
- user_id: 'user2@amplitude.com',
- },
],
options: {
min_id_length: undefined,
@@ -1282,8 +1234,6 @@ describe('integration', () => {
expect(deviceIds[2]).toEqual(deviceIds[3]);
expect(deviceIds[3]).toEqual(deviceIds[4]);
expect(deviceIds[4]).toEqual(deviceIds[5]);
- expect(deviceIds[5]).toEqual(deviceIds[6]);
- expect(deviceIds[6]).not.toEqual(deviceIds[7]);
// The order of events in the payload is sorted by time of track fn invokation
// and not consistent with the time property
// Session events have overwritten time property
@@ -1322,18 +1272,6 @@ describe('integration', () => {
userId: 'user1@amplitude.com',
deviceId: uuid,
},
- {
- eventType: 'session_end',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- ],
- [
- {
- eventType: 'session_start',
- userId: undefined,
- deviceId: uuid,
- },
],
]);
expect(payload).toEqual({
@@ -1497,44 +1435,6 @@ describe('integration', () => {
user_agent: userAgent,
user_id: 'user1@amplitude.com',
},
- {
- device_id: uuid,
- device_manufacturer: undefined,
- event_id: 6,
- event_type: 'session_end',
- insert_id: uuid,
- ip: '$remote',
- language: 'en-US',
- library,
- os_name: 'WebKit',
- os_version: '537.36',
- partner_id: undefined,
- plan: undefined,
- platform: 'Web',
- session_id: number,
- time: number,
- user_agent: userAgent,
- user_id: 'user1@amplitude.com',
- },
- {
- device_id: uuid,
- device_manufacturer: undefined,
- event_id: 7,
- event_type: 'session_start',
- insert_id: uuid,
- ip: '$remote',
- language: 'en-US',
- library,
- os_name: 'WebKit',
- os_version: '537.36',
- partner_id: undefined,
- plan: undefined,
- platform: 'Web',
- session_id: number,
- time: number,
- user_agent: userAgent,
- user_id: undefined,
- },
],
options: {
min_id_length: undefined,
diff --git a/packages/analytics-browser/src/browser-client.ts b/packages/analytics-browser/src/browser-client.ts
index ab7650bcd..8547a036b 100644
--- a/packages/analytics-browser/src/browser-client.ts
+++ b/packages/analytics-browser/src/browser-client.ts
@@ -164,7 +164,6 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
}
if (userId !== this.config.userId || userId === undefined) {
this.config.userId = userId;
- this.setSessionId(Date.now());
setConnectorUserId(userId);
}
}
diff --git a/packages/analytics-browser/test/browser-client.test.ts b/packages/analytics-browser/test/browser-client.test.ts
index 8c648c8c7..ff77c7879 100644
--- a/packages/analytics-browser/test/browser-client.test.ts
+++ b/packages/analytics-browser/test/browser-client.test.ts
@@ -315,7 +315,7 @@ describe('browser-client', () => {
expect(client.getUserId()).toBe(USER_ID);
});
- test('should set user and session id with start and end session event', async () => {
+ test('should not send session events on set user', async () => {
jest.spyOn(CookieMigration, 'parseLegacyCookies').mockResolvedValueOnce({
optOut: false,
sessionId: 1,
@@ -347,7 +347,7 @@ describe('browser-client', () => {
client.setUserId(undefined);
expect(client.getUserId()).toBe(undefined);
- expect(track).toHaveBeenCalledTimes(2);
+ expect(track).toHaveBeenCalledTimes(0);
});
test('should defer set user id', () => {
From 8f527e4a6437f66640efb95877750dc50ea93f3e Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 6 Jun 2023 19:04:12 +0000
Subject: [PATCH 007/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.1
- @amplitude/analytics-browser@1.10.7
- @amplitude/marketing-analytics-browser@1.0.1
- @amplitude/plugin-page-view-tracking-browser@1.0.1
- @amplitude/plugin-web-attribution-browser@1.0.1
---
packages/analytics-browser-test/CHANGELOG.md | 13 +++++++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 13 +++++++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 6 +++---
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 8 ++++----
packages/marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 4 ++--
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 4 ++--
18 files changed, 77 insertions(+), 24 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index 5a9c21546..0709690b0 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.0...@amplitude/analytics-browser-test@1.4.1) (2023-06-06)
+
+### Bug Fixes
+
+- don't automatically start new session on setUserId
+ ([#400](https://github.com/amplitude/Amplitude-TypeScript/issues/400))
+ ([ce17aab](https://github.com/amplitude/Amplitude-TypeScript/commit/ce17aabe3e0d04386ed201e6b9795ca8d42bd711))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.4.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.0-beta.1...@amplitude/analytics-browser-test@1.4.0) (2023-06-06)
**Note:** Version bump only for package @amplitude/analytics-browser-test
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index 86af55ecc..7ba79d434 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.0",
+ "version": "1.4.1",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.6"
+ "@amplitude/analytics-browser": "^1.10.7"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 9af42e906..774ddc1a2 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.10.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.6...@amplitude/analytics-browser@1.10.7) (2023-06-06)
+
+### Bug Fixes
+
+- don't automatically start new session on setUserId
+ ([#400](https://github.com/amplitude/Amplitude-TypeScript/issues/400))
+ ([ce17aab](https://github.com/amplitude/Amplitude-TypeScript/commit/ce17aabe3e0d04386ed201e6b9795ca8d42bd711))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.10.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.6-beta.0...@amplitude/analytics-browser@1.10.6) (2023-06-06)
**Note:** Version bump only for package @amplitude/analytics-browser
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index ca05dd049..41237ac9a 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index 18820c265..ea820219a 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-/pEyHhmhzhlIzPsIq1ptBvg2RLgkHo5wAhA7DEi6bJ+jNOQ7Us/bz8mfKQ8hGyZX';
+ as.integrity = 'sha384-1RCGOEWBysSvMhkUFt9WiJ1yWQmI9CK/Epcmvz6EIhnaXEelKj6DQyduxW13vrki';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.6-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.7-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 6d3345b8e..015831d5b 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.10.6",
+ "version": "1.10.7",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -46,8 +46,8 @@
"@amplitude/analytics-client-common": "^1.0.0",
"@amplitude/analytics-core": "^1.0.0",
"@amplitude/analytics-types": "^1.0.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.0",
- "@amplitude/plugin-web-attribution-browser": "^1.0.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.1",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.1",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index e0aa00876..1d15830bb 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index 6907fb8c4..513249106 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-tUv8KmK/ZnVrRFOEomj4a/y0P/8YBXFXzZLp0z7XtJ4jA21xYr0klVVwzNfbWuCb';
+ as.integrity = 'sha384-mE2nuVUBrzajX9YbPZ8uOzONHjss0X7Z2RNoREFg60vDNS3SUPtSxZclXOIXu34H';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.0-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.1-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index 4a646a317..f8911227d 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-tkD0ly9SyHYmmRIB7UNGsvThI7j3D06eYIxxNbb44krcClCTELy2yQgLNxwk58/1';
+ as.integrity = 'sha384-U6E9CMsOG+jKnR35qw+9MKntJySFcpJWIl3ultGC94Bq51pzfLpEavF79VTqG5iU';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.0-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.1-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index e348dad3d..bcec4c930 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -42,12 +42,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.6",
+ "@amplitude/analytics-browser": "^1.10.7",
"@amplitude/analytics-client-common": "^1.0.0",
"@amplitude/analytics-core": "^1.0.0",
"@amplitude/analytics-types": "^1.0.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.0",
- "@amplitude/plugin-web-attribution-browser": "^1.0.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.1",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.1",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index 059488afe..e96d2876d 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.0';
+export const VERSION = '1.0.1';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 2f7ba0442..7f184b2bd 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.0...@amplitude/plugin-page-view-tracking-browser@1.0.1) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.0-beta.1...@amplitude/plugin-page-view-tracking-browser@1.0.0) (2023-06-06)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index 75dfc80ae..9742188db 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -41,7 +41,7 @@
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.6",
+ "@amplitude/analytics-browser": "^1.10.7",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index 5087729cd..556c52fbf 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.0...@amplitude/plugin-web-attribution-browser@1.0.1) (2023-06-06)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.0-beta.1...@amplitude/plugin-web-attribution-browser@1.0.0) (2023-06-06)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index 2ac2c998e..d00fb2b68 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -41,7 +41,7 @@
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.6",
+ "@amplitude/analytics-browser": "^1.10.7",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From a48ddc55cf578727256108e5983172942d419c0e Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Thu, 8 Jun 2023 15:28:43 -0700
Subject: [PATCH 008/214] docs: update instructions and docs to reference 1.0
(#419)
---
packages/analytics-browser/README.md | 4 ++--
packages/analytics-node/README.md | 4 ++--
packages/analytics-react-native/README.md | 4 ++--
packages/marketing-analytics-browser/README.md | 4 ++--
packages/plugin-page-view-tracking-browser/README.md | 6 +++---
packages/plugin-web-attribution-browser/README.md | 4 ++--
6 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index 41237ac9a..ce7fef47c 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -27,10 +27,10 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/analytics-browser
+npm install @amplitude/analytics-browser@^1.0.0
# yarn
-yarn add @amplitude/analytics-browser
+yarn add @amplitude/analytics-browser@^1.0.0
```
### Using script loader
diff --git a/packages/analytics-node/README.md b/packages/analytics-node/README.md
index 5a743cc43..86e00d75f 100644
--- a/packages/analytics-node/README.md
+++ b/packages/analytics-node/README.md
@@ -27,10 +27,10 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/analytics-node
+npm install @amplitude/analytics-node@^1.0.0
# yarn
-yarn add @amplitude/analytics-node
+yarn add @amplitude/analytics-node@^1.0.0
```
## Usage
diff --git a/packages/analytics-react-native/README.md b/packages/analytics-react-native/README.md
index 8b3c66b3b..a021e6e5e 100644
--- a/packages/analytics-react-native/README.md
+++ b/packages/analytics-react-native/README.md
@@ -19,11 +19,11 @@ To get started with using Amplitude React Native SDK, install the package to you
```sh
# npm
-npm install @amplitude/analytics-react-native
+npm install @amplitude/analytics-react-native@^1.0.0
npm install @react-native-async-storage/async-storage
# yarn
-yarn add @amplitude/analytics-react-native
+yarn add @amplitude/analytics-react-native@^1.0.0
yarn add @react-native-async-storage/async-storage
```
diff --git a/packages/marketing-analytics-browser/README.md b/packages/marketing-analytics-browser/README.md
index 089dbb291..f3d9bc589 100644
--- a/packages/marketing-analytics-browser/README.md
+++ b/packages/marketing-analytics-browser/README.md
@@ -27,10 +27,10 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/marketing-analytics-browser
+npm install @amplitude/marketing-analytics-browser@^1.0.0
# yarn
-yarn add @amplitude/marketing-analytics-browser
+yarn add @amplitude/marketing-analytics-browser@^1.0.0
```
### Using script loader
diff --git a/packages/plugin-page-view-tracking-browser/README.md b/packages/plugin-page-view-tracking-browser/README.md
index 601033034..7bf59d1ca 100644
--- a/packages/plugin-page-view-tracking-browser/README.md
+++ b/packages/plugin-page-view-tracking-browser/README.md
@@ -15,10 +15,10 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/plugin-page-view-tracking-browser
+npm install @amplitude/plugin-page-view-tracking-browser@^1.0.0
# yarn
-yarn add @amplitude/plugin-page-view-tracking-browser
+yarn add @amplitude/plugin-page-view-tracking-browser@^1.0.0
```
## Usage
@@ -50,7 +50,7 @@ const pageViewTracking = pageViewTrackingPlugin(amplitude, {
|Name|Type|Default|Description|
|-|-|-|-|
-|`trackOn`|`"attribution"` or `() => boolean`|`undefined`|Use this option to control when to track a page view event. By default, a page view event is sent on each SDK initialization. Use `() => boolean` to control sending page view events using custom conditional logic. Use `"attribution"` to send page view events with attribution events. This option requires using [@amplitude/plugin-web-attribution-browser](https://github.com/amplitude/Amplitude-TypeScript/tree/main/packages/plugin-web-attribution-browser).|
+|`trackOn`|`"attribution"` or `() => boolean`|`undefined`|Use this option to control when to track a page view event. By default, a page view event is sent on each SDK initialization. Use `() => boolean` to control sending page view events using custom conditional logic. Use `"attribution"` to send page view events with attribution events. This option requires using [@amplitude/plugin-web-attribution-browser](https://github.com/amplitude/Amplitude-TypeScript/tree/v1.x/packages/plugin-web-attribution-browser).|
|`trackHistoryChanges`|`"all"` or `"pathOnly"`|`undefined`|Use this option to subscribe to page view changes in a single page application like React.js. By default, page view changes in single page applications does not trigger a page view event. Use `"all"` to compare the full url changes. Use `"pathOnly"` to compare only url path changes.|
### 3. Install plugin to Amplitude SDK
diff --git a/packages/plugin-web-attribution-browser/README.md b/packages/plugin-web-attribution-browser/README.md
index 2cffd9ced..e95274e74 100644
--- a/packages/plugin-web-attribution-browser/README.md
+++ b/packages/plugin-web-attribution-browser/README.md
@@ -15,10 +15,10 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/plugin-web-attribution-browser
+npm install @amplitude/plugin-web-attribution-browser@^1.0.0
# yarn
-yarn add @amplitude/plugin-web-attribution-browser
+yarn add @amplitude/plugin-web-attribution-browser@^1.0.0
```
## Usage
From 86de7bd75c564601d980c029e548fe0303ba43f2 Mon Sep 17 00:00:00 2001
From: Justin Fiedler
Date: Thu, 8 Jun 2023 16:19:28 -0700
Subject: [PATCH 009/214] feat: log response body from API to logger (#415)
* feat: log response body from API to logger
* fix: fix test for 429 status, extend 'log unexpected error' test to increase coverage
* fix: add status code to response logging
* chore: typo
* fix: reject send errors
* chore: resolve promise for req.on('error'), remove throw error from SDK
* chore: remove superfluous logging on success
* chore: only log intermediate responses prior to retry
* chore: remove duplicate logging on "other": response
* chore: clean up test logic
---------
Co-authored-by: Andrey Sokolov
---
.../analytics-core/src/plugins/destination.ts | 72 ++++--
.../test/plugins/destination.test.ts | 208 +++++++++++++++++-
.../analytics-node/src/transports/http.ts | 2 +-
packages/analytics-types/src/response.ts | 4 +-
4 files changed, 260 insertions(+), 26 deletions(-)
diff --git a/packages/analytics-core/src/plugins/destination.ts b/packages/analytics-core/src/plugins/destination.ts
index 3781fdb45..602bf1c35 100644
--- a/packages/analytics-core/src/plugins/destination.ts
+++ b/packages/analytics-core/src/plugins/destination.ts
@@ -24,6 +24,23 @@ import { chunk } from '../utils/chunk';
import { buildResult } from '../utils/result-builder';
import { createServerConfig } from '../config';
+function getErrorMessage(error: unknown) {
+ if (error instanceof Error) return error.message;
+ return String(error);
+}
+
+export function getResponseBodyString(res: Response) {
+ let responseBodyString = '';
+ try {
+ if ('body' in res) {
+ responseBodyString = JSON.stringify(res.body, null, 2);
+ }
+ } catch {
+ // to avoid crash, but don't care about the error, add comment to avoid empty block lint error
+ }
+ return responseBodyString;
+}
+
export class Destination implements DestinationPlugin {
name = 'amplitude';
type = PluginType.DESTINATION as const;
@@ -141,45 +158,47 @@ export class Destination implements DestinationPlugin {
}
if (!useRetry) {
if ('body' in res) {
- let responseBody = '';
- try {
- responseBody = JSON.stringify(res.body, null, 2);
- } catch {
- // to avoid crash, but don't care about the error, add comment to avoid empty block lint error
- }
- this.fulfillRequest(list, res.statusCode, `${res.status}: ${responseBody}`);
+ this.fulfillRequest(list, res.statusCode, `${res.status}: ${getResponseBodyString(res)}`);
} else {
this.fulfillRequest(list, res.statusCode, res.status);
}
return;
}
- this.handleReponse(res, list);
+ this.handleResponse(res, list);
} catch (e) {
- this.fulfillRequest(list, 0, String(e));
+ const errorMessage = getErrorMessage(e);
+ this.config.loggerProvider.error(errorMessage);
+ this.fulfillRequest(list, 0, errorMessage);
}
}
- handleReponse(res: Response, list: Context[]) {
+ handleResponse(res: Response, list: Context[]) {
const { status } = res;
+
switch (status) {
- case Status.Success:
+ case Status.Success: {
this.handleSuccessResponse(res, list);
break;
-
- case Status.Invalid:
+ }
+ case Status.Invalid: {
this.handleInvalidResponse(res, list);
break;
-
- case Status.PayloadTooLarge:
+ }
+ case Status.PayloadTooLarge: {
this.handlePayloadTooLargeResponse(res, list);
break;
-
- case Status.RateLimit:
+ }
+ case Status.RateLimit: {
this.handleRateLimitResponse(res, list);
break;
+ }
+ default: {
+ // log intermediate event status before retry
+ this.config.loggerProvider.warn(`{code: 0, error: "Status '${status}' provided for ${list.length} events"}`);
- default:
- this.handleOtherReponse(list);
+ this.handleOtherResponse(list);
+ break;
+ }
}
}
@@ -209,6 +228,10 @@ export class Destination implements DestinationPlugin {
return true;
});
+ if (retry.length > 0) {
+ // log intermediate event status before retry
+ this.config.loggerProvider.warn(getResponseBodyString(res));
+ }
this.addToQueue(...retry);
}
@@ -217,6 +240,10 @@ export class Destination implements DestinationPlugin {
this.fulfillRequest(list, res.statusCode, res.body.error);
return;
}
+
+ // log intermediate event status before retry
+ this.config.loggerProvider.warn(getResponseBodyString(res));
+
this.config.flushQueueSize /= 2;
this.addToQueue(...list);
}
@@ -243,10 +270,15 @@ export class Destination implements DestinationPlugin {
return true;
});
+ if (retry.length > 0) {
+ // log intermediate event status before retry
+ this.config.loggerProvider.warn(getResponseBodyString(res));
+ }
+
this.addToQueue(...retry);
}
- handleOtherReponse(list: Context[]) {
+ handleOtherResponse(list: Context[]) {
this.addToQueue(
...list.map((context) => {
context.timeout = context.attempts * this.retryTimeout;
diff --git a/packages/analytics-core/test/plugins/destination.test.ts b/packages/analytics-core/test/plugins/destination.test.ts
index c3635d33b..41345a513 100644
--- a/packages/analytics-core/test/plugins/destination.test.ts
+++ b/packages/analytics-core/test/plugins/destination.test.ts
@@ -1,5 +1,5 @@
-import { Destination } from '../../src/plugins/destination';
-import { DestinationContext, Payload, Status } from '@amplitude/analytics-types';
+import { Destination, getResponseBodyString } from '../../src/plugins/destination';
+import { Config, DestinationContext, Logger, Payload, Result, Status } from '@amplitude/analytics-types';
import { API_KEY, useDefaultConfig } from '../helpers/default';
import {
INVALID_API_KEY,
@@ -8,6 +8,8 @@ import {
UNEXPECTED_ERROR_MESSAGE,
} from '../../src/messages';
+const jsons = (obj: any) => JSON.stringify(obj, null, 2);
+
describe('destination', () => {
describe('setup', () => {
test('should setup plugin', async () => {
@@ -373,7 +375,7 @@ describe('destination', () => {
expect(callback).toHaveBeenCalledWith({
event,
code: 400,
- message: `${Status.Invalid}: ${JSON.stringify(body, null, 2)}`,
+ message: `${Status.Invalid}: ${jsons(body)}`,
});
});
@@ -833,4 +835,204 @@ describe('destination', () => {
expect(transportProvider.send).toHaveBeenCalledTimes(1);
});
});
+
+ describe('logging', () => {
+ const getMockLogger = (): Logger => ({
+ log: jest.fn(),
+ debug: jest.fn(),
+ warn: jest.fn(),
+ error: jest.fn(),
+ enable: jest.fn(),
+ disable: jest.fn(),
+ });
+
+ test('should handle null loggerProvider', async () => {
+ class Http {
+ send = jest.fn().mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: Status.Success,
+ statusCode: 200,
+ body: {
+ message: 'success',
+ },
+ });
+ });
+ }
+
+ const transportProvider = new Http();
+ const destination = new Destination();
+
+ const config: Config = {
+ ...useDefaultConfig(),
+ flushQueueSize: 1,
+ flushIntervalMillis: 1,
+ transportProvider,
+ };
+ await destination.setup(config);
+ const event = {
+ event_type: 'event_type',
+ };
+ const result = await destination.execute(event);
+ expect(result).toEqual({
+ event,
+ message: SUCCESS_MESSAGE,
+ code: 200,
+ });
+ expect(transportProvider.send).toHaveBeenCalledTimes(1);
+ });
+
+ test.each([
+ {
+ statusCode: 400,
+ status: Status.Invalid,
+ body: {
+ code: 400,
+ error: 'error',
+ missingField: undefined,
+ eventsWithInvalidFields: {},
+ eventsWithMissingFields: {},
+ eventsWithInvalidIdLengths: {},
+ silencedEvents: [],
+ },
+ },
+ {
+ statusCode: 413,
+ status: Status.PayloadTooLarge,
+ body: {
+ code: 413,
+ error: 'error',
+ },
+ },
+ {
+ statusCode: 429,
+ status: Status.RateLimit,
+ body: {
+ code: 429,
+ error: 'error',
+ epsThreshold: 1,
+ throttledDevices: {},
+ throttledUsers: {},
+ exceededDailyQuotaDevices: {
+ '1': 1,
+ },
+ exceededDailyQuotaUsers: {
+ '2': 1,
+ },
+ throttledEvents: [0],
+ },
+ },
+ ])(
+ 'should log intermediate response body for retries of $statusCode $status',
+ async ({ statusCode, status, body }) => {
+ const response = {
+ status,
+ statusCode,
+ body,
+ };
+
+ class Http {
+ send = jest
+ .fn()
+ .mockImplementationOnce(() => {
+ return Promise.resolve(response);
+ })
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: Status.Success,
+ statusCode: 200,
+ body: {
+ message: SUCCESS_MESSAGE,
+ },
+ });
+ });
+ }
+ const transportProvider = new Http();
+ const destination = new Destination();
+ const loggerProvider = getMockLogger();
+ const eventCount = status === Status.PayloadTooLarge ? 2 : 1;
+ const config = {
+ ...useDefaultConfig(),
+ flushQueueSize: eventCount,
+ flushIntervalMillis: 1,
+ transportProvider,
+ loggerProvider,
+ };
+ await destination.setup(config);
+ destination.retryTimeout = 10;
+ destination.throttleTimeout = 1;
+ const event = {
+ event_type: 'event_type',
+ };
+ let result;
+ if (eventCount > 1) {
+ // Need 2 events for 413 to retry, send them both at the same time
+ result = await new Promise((resolve) => {
+ const context: DestinationContext = {
+ event,
+ attempts: 0,
+ callback: (result: Result) => resolve(result),
+ timeout: 0,
+ };
+ void destination.addToQueue(context, context);
+ });
+ } else {
+ result = await destination.execute(event);
+ }
+
+ expect(result).toEqual({
+ event,
+ message: SUCCESS_MESSAGE,
+ code: 200,
+ });
+
+ expect(transportProvider.send).toHaveBeenCalledTimes(eventCount + 1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ expect(loggerProvider.warn).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method,@typescript-eslint/restrict-template-expressions
+ expect(loggerProvider.warn).toHaveBeenCalledWith(jsons(response.body));
+ },
+ );
+
+ test.each([
+ { err: new Error('Error'), message: 'Error' },
+ { err: 'string error', message: 'string error' },
+ ])('should log unexpected error "$message"', async ({ err, message }) => {
+ const destination = new Destination();
+ const callback = jest.fn();
+ const context = {
+ attempts: 0,
+ callback,
+ event: {
+ event_type: 'event_type',
+ },
+ timeout: 0,
+ };
+ const transportProvider = {
+ send: jest.fn().mockImplementationOnce(() => {
+ throw err;
+ }),
+ };
+ const loggerProvider = getMockLogger();
+
+ await destination.setup({
+ ...useDefaultConfig(),
+ transportProvider,
+ loggerProvider,
+ });
+ await destination.send([context]);
+ expect(callback).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ expect(loggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ expect(loggerProvider.error).toHaveBeenCalledWith(message);
+ });
+
+ test('should parse response without body', async () => {
+ const result = getResponseBodyString({
+ status: Status.Unknown,
+ statusCode: 700,
+ });
+ expect(result).toBe('');
+ });
+ });
});
diff --git a/packages/analytics-node/src/transports/http.ts b/packages/analytics-node/src/transports/http.ts
index 1c44795f2..0c0c985b0 100644
--- a/packages/analytics-node/src/transports/http.ts
+++ b/packages/analytics-node/src/transports/http.ts
@@ -49,7 +49,7 @@ export class Http extends BaseTransport implements Transport {
}
});
});
- req.on('error', this.buildResponse.bind(this));
+ req.on('error', () => resolve(null));
req.end(requestPayload);
});
}
diff --git a/packages/analytics-types/src/response.ts b/packages/analytics-types/src/response.ts
index f2e8f4554..04e6108e5 100644
--- a/packages/analytics-types/src/response.ts
+++ b/packages/analytics-types/src/response.ts
@@ -65,7 +65,7 @@ export interface TimeoutResponse {
statusCode: number;
}
-export interface OtherReponse {
+export interface OtherResponse {
status: Exclude;
statusCode: number;
}
@@ -76,4 +76,4 @@ export type Response =
| PayloadTooLargeResponse
| RateLimitResponse
| TimeoutResponse
- | OtherReponse;
+ | OtherResponse;
From 0c03a57239e1048b1730fa58464b125ba9b8b6ba Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Thu, 8 Jun 2023 23:33:23 +0000
Subject: [PATCH 010/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.2
- @amplitude/analytics-browser@1.10.8
- @amplitude/analytics-client-common@1.0.1
- @amplitude/analytics-core@1.1.0
- @amplitude/analytics-node-test@1.0.1
- @amplitude/analytics-node@1.2.0
- @amplitude/analytics-react-native@1.1.9
- @amplitude/analytics-types@1.1.0
- @amplitude/marketing-analytics-browser@1.0.2
- @amplitude/plugin-page-view-tracking-browser@1.0.2
- @amplitude/plugin-web-attribution-browser@1.0.2
---
packages/analytics-browser-test/CHANGELOG.md | 9 +++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 9 +++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 12 ++++++------
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 9 +++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 12 ++++++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 +++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 12 ++++++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 9 +++++++++
packages/analytics-react-native/package.json | 8 ++++----
packages/analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 12 ++++++++++++
packages/analytics-types/package.json | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 14 +++++++-------
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 8 ++++----
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 8 ++++----
32 files changed, 159 insertions(+), 51 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index 0709690b0..ebf9e1d7c 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.1...@amplitude/analytics-browser-test@1.4.2) (2023-06-08)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.4.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.0...@amplitude/analytics-browser-test@1.4.1) (2023-06-06)
### Bug Fixes
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index 7ba79d434..c20f27036 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.1",
+ "version": "1.4.2",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.7"
+ "@amplitude/analytics-browser": "^1.10.8"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 774ddc1a2..2246a7d75 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.10.8](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.7...@amplitude/analytics-browser@1.10.8) (2023-06-08)
+
+**Note:** Version bump only for package @amplitude/analytics-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.10.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.6...@amplitude/analytics-browser@1.10.7) (2023-06-06)
### Bug Fixes
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index ce7fef47c..561338585 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index ea820219a..9b0323c79 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-1RCGOEWBysSvMhkUFt9WiJ1yWQmI9CK/Epcmvz6EIhnaXEelKj6DQyduxW13vrki';
+ as.integrity = 'sha384-GmHPBAFv1x8jEuQZmkqX6H0TvWj/UIZMKLZuB14iqzEdO5Ei6x+X5tmMCqiuzJ0+';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.7-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.8-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 015831d5b..9025819b5 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.10.7",
+ "version": "1.10.8",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -43,11 +43,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0",
- "@amplitude/analytics-core": "^1.0.0",
- "@amplitude/analytics-types": "^1.0.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.1",
- "@amplitude/plugin-web-attribution-browser": "^1.0.1",
+ "@amplitude/analytics-client-common": "^1.0.1",
+ "@amplitude/analytics-core": "^1.1.0",
+ "@amplitude/analytics-types": "^1.1.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.2",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.2",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 1d15830bb..187784e39 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!V(t,i))return!1}return!0},V=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&M(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return M(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},z=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||V(t,i)))},e}(),F=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},B=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},$=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return B(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),Q="Event rejected due to exceeded retry count",K=function(e){return{promise:e||Promise.resolve()}},H=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new $(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return K(this.dispatch(r))},e.prototype.identify=function(e,t){var i=F(e,t);return K(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return K(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new z;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return K(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return K(this.dispatch(n))},e.prototype.add=function(e){return this.config?K(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),K())},e.prototype.remove=function(e){return this.config?K(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),K())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(B(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,B(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=B(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return K(this.timeline.flush())},e}(),W=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return M(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),Z="Amplitude Logger ",G=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=ee(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:if(null===(s=o.sent()))return this.fulfillRequest(e,0,"Unexpected error occurred"),[2];if(!t){if("body"in s){a="";try{a=JSON.stringify(s.body,null,2)}catch(e){}this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(a))}else this.fulfillRequest(e,s.statusCode,s.status);return[2]}return this.handleReponse(s,e),[3,4];case 3:return u=o.sent(),this.fulfillRequest(e,0,String(u)),[3,4];case 4:return[2]}}))}))},e.prototype.handleReponse=function(e,t){switch(e.status){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.handleOtherReponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherReponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(B(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ie=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ne=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},re=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},oe=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=re(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},se=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ae)},ue=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ce=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),le=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[C,t,e.substring(0,i)].filter(Boolean).join("_")},de=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),pe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ce),fe="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},ve={exports:{}};q=ve,A=ve.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",D="Xiaomi",C="Zebra",j="Facebook",L=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},B=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?z(e,275):e,this},this.setUA(f),this};K.VERSION="0.7.31",K.BROWSER=L([a,l,"major"]),K.CPU=L([d]),K.DEVICE=L([s,c,u,p,f,h,v,g,b]),K.ENGINE=K.OS=L([a,l]),q.exports&&(A=q.exports=K),A.UAParser=K;var H=typeof e!==n&&(e.jQuery||e.Zepto);if(H&&!H.ua){var W=new K;H.ua=W.getResult(),H.ua.get=function(){return W.getUA()},H.ua.set=function(e){W.setUA(e);var t=W.getResult();for(var i in t)H.ua[i]=t[i]}}}("object"==typeof window?window:fe);var he=ve.exports,ge=function(){function e(){this.ua=new ve.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:me(),platform:"Web",os:be(this.ua),deviceModel:ye(this.ua)}},e}(),be=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},ye=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},me=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},we=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),_e=function(){return _e=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},bt={page_domain:"".concat(tt," Page Domain"),page_location:"".concat(tt," Page Location"),page_path:"".concat(tt," Page Path"),page_title:"".concat(tt," Page Title"),page_url:"".concat(tt," Page URL")},yt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),K(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new de).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Te()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new te).promise];case 11:return K.sent(),[4,this.add(new Ue).promise];case 12:return K.sent(),[4,this.add(gt()).promise];case 13:return K.sent(),[4,this.add(new Oe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(ot,((n={})[ut]=o,n[ct]=i.pathname,n[lt]=e.id,n[dt]=e.text,n[pt]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(nt,((n={})[ft]=e.id,n[vt]=e.name,n[ht]=e.action,n)),t.track(rt,((r={})[ft]=e.id,r[vt]=e.name,r[ht]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[A,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",A="Zebra",D="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},yt={page_domain:"".concat(it," Page Domain"),page_location:"".concat(it," Page Location"),page_path:"".concat(it," Page Path"),page_title:"".concat(it," Page Title"),page_url:"".concat(it," Page URL")},mt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),$(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new pe).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return K.sent(),[4,this.add(new qe).promise];case 12:return K.sent(),[4,this.add(bt()).promise];case 13:return K.sent(),[4,this.add(new xe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index 513249106..012bd5255 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-mE2nuVUBrzajX9YbPZ8uOzONHjss0X7Z2RNoREFg60vDNS3SUPtSxZclXOIXu34H';
+ as.integrity = 'sha384-esLjYq8Qgovxmz722pqEH0scVGMblCA2xQbwSRjQbtrbv5Q2RNz2ZfZkxd/G14eo';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.1-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.2-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index f8911227d..cc68ccb86 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-U6E9CMsOG+jKnR35qw+9MKntJySFcpJWIl3ultGC94Bq51pzfLpEavF79VTqG5iU';
+ as.integrity = 'sha384-rGGnTWnBLdyeMUzGdN6uTRsJp8WDl5k35FXoetLOSqh4UVaZbxK09sSW/Mzp6tzZ';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.1-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.2-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index bcec4c930..9d2e83812 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -42,12 +42,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.7",
- "@amplitude/analytics-client-common": "^1.0.0",
- "@amplitude/analytics-core": "^1.0.0",
- "@amplitude/analytics-types": "^1.0.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.1",
- "@amplitude/plugin-web-attribution-browser": "^1.0.1",
+ "@amplitude/analytics-browser": "^1.10.8",
+ "@amplitude/analytics-client-common": "^1.0.1",
+ "@amplitude/analytics-core": "^1.1.0",
+ "@amplitude/analytics-types": "^1.1.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.2",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.2",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index e96d2876d..926d3f2b3 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.1';
+export const VERSION = '1.0.2';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 7f184b2bd..576865c52 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.1...@amplitude/plugin-page-view-tracking-browser@1.0.2) (2023-06-08)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.0...@amplitude/plugin-page-view-tracking-browser@1.0.1) (2023-06-06)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index 9742188db..cf27c8fc0 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -36,12 +36,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0",
- "@amplitude/analytics-types": "^1.0.0",
+ "@amplitude/analytics-client-common": "^1.0.1",
+ "@amplitude/analytics-types": "^1.1.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.7",
+ "@amplitude/analytics-browser": "^1.10.8",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index 556c52fbf..354b623ce 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.1...@amplitude/plugin-web-attribution-browser@1.0.2) (2023-06-08)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.0...@amplitude/plugin-web-attribution-browser@1.0.1) (2023-06-06)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index d00fb2b68..2dd91be9b 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -36,12 +36,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0",
- "@amplitude/analytics-types": "^1.0.0",
+ "@amplitude/analytics-client-common": "^1.0.1",
+ "@amplitude/analytics-types": "^1.1.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.7",
+ "@amplitude/analytics-browser": "^1.10.8",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From c6c82bae762493a07922e08baca5db1bd91e483e Mon Sep 17 00:00:00 2001
From: Justin Fiedler
Date: Mon, 12 Jun 2023 15:48:41 -0700
Subject: [PATCH 011/214] feat: added extendSession() method to Browser Client
(#425)
* feat: added extendSession() method to Browser Client
* fix: implement extendSession() method for RN Client and Browser Client factory
* chore: code clean up
* chore: fix mocks
* chore: handle deferred config
* chore: fix comment in test
* chore: add snippet proxy test for extendSession()
---
.../src/browser-client-factory.ts | 6 ++
.../analytics-browser/src/browser-client.ts | 8 ++
packages/analytics-browser/src/index.ts | 1 +
.../test/browser-client.test.ts | 86 +++++++++++++++++++
.../analytics-browser/test/helpers/mock.ts | 1 +
packages/analytics-browser/test/index.test.ts | 2 +
.../src/react-native-client.ts | 14 +++
.../analytics-types/src/client/web-client.ts | 14 ++-
8 files changed, 131 insertions(+), 1 deletion(-)
diff --git a/packages/analytics-browser/src/browser-client-factory.ts b/packages/analytics-browser/src/browser-client-factory.ts
index b0c4efe42..bf8c4744e 100644
--- a/packages/analytics-browser/src/browser-client-factory.ts
+++ b/packages/analytics-browser/src/browser-client-factory.ts
@@ -107,6 +107,12 @@ export const createInstance = (): BrowserClient => {
getClientLogConfig(client),
getClientStates(client, ['config']),
),
+ extendSession: debugWrapper(
+ client.extendSession.bind(client),
+ 'extendSession',
+ getClientLogConfig(client),
+ getClientStates(client, ['config']),
+ ),
setOptOut: debugWrapper(
client.setOptOut.bind(client),
'setOptOut',
diff --git a/packages/analytics-browser/src/browser-client.ts b/packages/analytics-browser/src/browser-client.ts
index 8547a036b..7027847cc 100644
--- a/packages/analytics-browser/src/browser-client.ts
+++ b/packages/analytics-browser/src/browser-client.ts
@@ -221,6 +221,14 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
}
}
+ extendSession() {
+ if (!this.config) {
+ this.q.push(this.extendSession.bind(this));
+ return;
+ }
+ this.config.lastEventTime = Date.now();
+ }
+
setTransport(transport: TransportType) {
if (!this.config) {
this.q.push(this.setTransport.bind(this, transport));
diff --git a/packages/analytics-browser/src/index.ts b/packages/analytics-browser/src/index.ts
index 6f48549a5..b2cba6f06 100644
--- a/packages/analytics-browser/src/index.ts
+++ b/packages/analytics-browser/src/index.ts
@@ -3,6 +3,7 @@ import client from './browser-client-factory';
export { createInstance } from './browser-client-factory';
export const {
add,
+ extendSession,
flush,
getDeviceId,
getSessionId,
diff --git a/packages/analytics-browser/test/browser-client.test.ts b/packages/analytics-browser/test/browser-client.test.ts
index ff77c7879..78615b89d 100644
--- a/packages/analytics-browser/test/browser-client.test.ts
+++ b/packages/analytics-browser/test/browser-client.test.ts
@@ -551,6 +551,92 @@ describe('browser-client', () => {
});
});
+ describe('extendSession', () => {
+ test('should extend the current session without sending events', async () => {
+ const firstSessionId = 1;
+ const client = new AmplitudeBrowser();
+ await client.init(API_KEY, undefined, {
+ sessionTimeout: 20,
+ sessionId: firstSessionId,
+ flushQueueSize: 1,
+ flushIntervalMillis: 1,
+ }).promise;
+ // assert sessionId is set
+ expect(client.config.sessionId).toBe(firstSessionId);
+ expect(client.config.lastEventTime).toBeUndefined();
+
+ // send an event
+ await client.track('test 1').promise;
+ const eventTime1 = client.config.lastEventTime ?? -1;
+ expect(eventTime1 > 0).toBeTruthy();
+
+ // wait for session to almost expire, then extend it
+ await new Promise((resolve) =>
+ setTimeout(() => {
+ client.extendSession();
+ resolve();
+ }, 15),
+ );
+
+ // assert session id is unchanged
+ expect(client.config.sessionId).toBe(firstSessionId);
+ // assert last event time was updated
+ const extendedLastEventTime = client.config.lastEventTime ?? -1;
+ expect(extendedLastEventTime > 0).toBeTruthy();
+ expect(extendedLastEventTime > eventTime1).toBeTruthy();
+
+ // send another event just before session expires (again)
+ await new Promise((resolve) =>
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ setTimeout(async () => {
+ await client.track('test 2').promise;
+ resolve();
+ }, 15),
+ );
+
+ // assert session id is unchanged
+ expect(client.config.sessionId).toBe(firstSessionId);
+ // assert last event time was updated
+ const eventTime2 = client.config.lastEventTime ?? -1;
+ expect(eventTime2 > 0).toBeTruthy();
+ expect(eventTime2 > extendedLastEventTime).toBeTruthy();
+
+ // Wait for session to timeout, without extendSession()
+ await new Promise((resolve) =>
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ setTimeout(async () => {
+ await client.track('test 3').promise;
+ resolve();
+ }, 21),
+ );
+ // assert session id is changed
+ expect(client.config.sessionId).not.toBe(firstSessionId);
+ expect(client.config.sessionId ?? -1 > firstSessionId).toBeTruthy();
+ });
+
+ test('should extendSession using proxy', async () => {
+ const firstSessionId = 1;
+ const client = new AmplitudeBrowser();
+
+ // call extendSession() before init()
+ client.extendSession();
+
+ // init
+ await client.init(API_KEY, undefined, {
+ sessionTimeout: 20,
+ sessionId: firstSessionId,
+ flushQueueSize: 1,
+ flushIntervalMillis: 1,
+ lastEventTime: 0,
+ }).promise;
+
+ // assert sessionId is unchanged
+ expect(client.config.sessionId).toBe(firstSessionId);
+ // assert last event time was updated
+ expect(client.config.lastEventTime).not.toBe(0);
+ });
+ });
+
describe('setTransport', () => {
test('should set transport', async () => {
const fetch = new FetchTransport();
diff --git a/packages/analytics-browser/test/helpers/mock.ts b/packages/analytics-browser/test/helpers/mock.ts
index 1a55aef7d..d46704970 100644
--- a/packages/analytics-browser/test/helpers/mock.ts
+++ b/packages/analytics-browser/test/helpers/mock.ts
@@ -19,6 +19,7 @@ export const createAmplitudeMock = (): jest.MockedObject => ({
setDeviceId: jest.fn(),
getSessionId: jest.fn(),
setSessionId: jest.fn(),
+ extendSession: jest.fn(),
reset: jest.fn(),
setTransport: jest.fn(),
});
diff --git a/packages/analytics-browser/test/index.test.ts b/packages/analytics-browser/test/index.test.ts
index 894f3819a..b0090c54c 100644
--- a/packages/analytics-browser/test/index.test.ts
+++ b/packages/analytics-browser/test/index.test.ts
@@ -1,6 +1,7 @@
import {
add,
createInstance,
+ extendSession,
flush,
getDeviceId,
getSessionId,
@@ -28,6 +29,7 @@ describe('index', () => {
test('should expose apis', () => {
expect(typeof add).toBe('function');
expect(typeof createInstance).toBe('function');
+ expect(typeof extendSession).toBe('function');
expect(typeof flush).toBe('function');
expect(typeof groupIdentify).toBe('function');
expect(typeof getDeviceId).toBe('function');
diff --git a/packages/analytics-react-native/src/react-native-client.ts b/packages/analytics-react-native/src/react-native-client.ts
index 46ccd4316..d10f72d31 100644
--- a/packages/analytics-react-native/src/react-native-client.ts
+++ b/packages/analytics-react-native/src/react-native-client.ts
@@ -183,6 +183,14 @@ export class AmplitudeReactNative extends AmplitudeCore {
void this.setSessionIdInternal(sessionId, this.currentTimeMillis());
}
+ extendSession() {
+ if (!this.config) {
+ this.q.push(this.extendSession.bind(this));
+ return;
+ }
+ this.config.lastEventTime = this.currentTimeMillis();
+ }
+
private setSessionIdInternal(sessionId: number, eventTime: number) {
const previousSessionId = this.config.sessionId;
if (previousSessionId === sessionId) {
@@ -387,6 +395,12 @@ export const createInstance = (): ReactNativeClient => {
getClientLogConfig(client),
getClientStates(client, ['config']),
),
+ extendSession: debugWrapper(
+ client.extendSession.bind(client),
+ 'extendSession',
+ getClientLogConfig(client),
+ getClientStates(client, ['config']),
+ ),
setOptOut: debugWrapper(
client.setOptOut.bind(client),
'setOptOut',
diff --git a/packages/analytics-types/src/client/web-client.ts b/packages/analytics-types/src/client/web-client.ts
index 5d09e40ef..731678bf0 100644
--- a/packages/analytics-types/src/client/web-client.ts
+++ b/packages/analytics-types/src/client/web-client.ts
@@ -53,7 +53,7 @@ interface Client extends CoreClient {
/**
* Sets a new session ID.
- * When settign a custom session ID, make sure the value is in milliseconds since epoch (Unix Timestamp).
+ * When setting a custom session ID, make sure the value is in milliseconds since epoch (Unix Timestamp).
*
* ```typescript
* setSessionId(Date.now());
@@ -61,6 +61,18 @@ interface Client extends CoreClient {
*/
setSessionId(sessionId: number): void;
+ /**
+ * Extends the current session (advanced)
+ *
+ * Normally sessions are extended automatically by track()'ing events. If you want to extend the session without
+ * tracking and event, this will set the last user interaction to the current time.
+ *
+ * ```typescript
+ * extendSession();
+ * ```
+ */
+ extendSession(): void;
+
/**
* Anonymizes users after they log out, by:
*
From 5cfe645df26d9dc301f264ead0880dad6925a2ff Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 13 Jun 2023 00:04:17 +0000
Subject: [PATCH 012/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.3
- @amplitude/analytics-browser@1.11.0
- @amplitude/analytics-client-common@1.0.2
- @amplitude/analytics-core@1.1.1
- @amplitude/analytics-node-test@1.0.2
- @amplitude/analytics-node@1.2.1
- @amplitude/analytics-react-native@1.2.0
- @amplitude/analytics-types@1.2.0
- @amplitude/marketing-analytics-browser@1.0.3
- @amplitude/plugin-page-view-tracking-browser@1.0.3
- @amplitude/plugin-web-attribution-browser@1.0.3
---
packages/analytics-browser-test/CHANGELOG.md | 9 +++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 12 ++++++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 12 ++++++------
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 9 +++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 9 +++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 +++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 9 +++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 12 ++++++++++++
packages/analytics-react-native/package.json | 8 ++++----
packages/analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 12 ++++++++++++
packages/analytics-types/package.json | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 14 +++++++-------
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 8 ++++----
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 8 ++++----
32 files changed, 159 insertions(+), 51 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index ebf9e1d7c..ff4138537 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.2...@amplitude/analytics-browser-test@1.4.3) (2023-06-13)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.4.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.1...@amplitude/analytics-browser-test@1.4.2) (2023-06-08)
**Note:** Version bump only for package @amplitude/analytics-browser-test
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index c20f27036..7761cb358 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.2",
+ "version": "1.4.3",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.8"
+ "@amplitude/analytics-browser": "^1.11.0"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 2246a7d75..bbb729bb4 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.11.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.8...@amplitude/analytics-browser@1.11.0) (2023-06-13)
+
+### Features
+
+- added extendSession() method to Browser Client ([#425](https://github.com/amplitude/Amplitude-TypeScript/issues/425))
+ ([c6c82ba](https://github.com/amplitude/Amplitude-TypeScript/commit/c6c82bae762493a07922e08baca5db1bd91e483e))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.10.8](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.7...@amplitude/analytics-browser@1.10.8) (2023-06-08)
**Note:** Version bump only for package @amplitude/analytics-browser
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index 561338585..b39f0da86 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index 9b0323c79..ee6a95c3b 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-GmHPBAFv1x8jEuQZmkqX6H0TvWj/UIZMKLZuB14iqzEdO5Ei6x+X5tmMCqiuzJ0+';
+ as.integrity = 'sha384-88G7ldPwlcBAEMvub2fJXyrErbB+r+yi8fkKzYrr3iUDAkNi/0Y6kR2jyCMC1rfJ';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.10.8-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.11.0-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 9025819b5..73ae2af67 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.10.8",
+ "version": "1.11.0",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -43,11 +43,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.1",
- "@amplitude/analytics-core": "^1.1.0",
- "@amplitude/analytics-types": "^1.1.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.2",
- "@amplitude/plugin-web-attribution-browser": "^1.0.2",
+ "@amplitude/analytics-client-common": "^1.0.2",
+ "@amplitude/analytics-core": "^1.1.1",
+ "@amplitude/analytics-types": "^1.2.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.3",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.3",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 187784e39..7f02b4256 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[A,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",A="Zebra",D="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},yt={page_domain:"".concat(it," Page Domain"),page_location:"".concat(it," Page Location"),page_path:"".concat(it," Page Path"),page_title:"".concat(it," Page Title"),page_url:"".concat(it," Page URL")},mt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),$(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new pe).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return K.sent(),[4,this.add(new qe).promise];case 12:return K.sent(),[4,this.add(bt()).promise];case 13:return K.sent(),[4,this.add(new xe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[A,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",A="Zebra",D="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},yt={page_domain:"".concat(it," Page Domain"),page_location:"".concat(it," Page Location"),page_path:"".concat(it," Page Path"),page_title:"".concat(it," Page Title"),page_url:"".concat(it," Page URL")},mt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),$(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new pe).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return K.sent(),[4,this.add(new qe).promise];case 12:return K.sent(),[4,this.add(bt()).promise];case 13:return K.sent(),[4,this.add(new xe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index 012bd5255..f11bba734 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-esLjYq8Qgovxmz722pqEH0scVGMblCA2xQbwSRjQbtrbv5Q2RNz2ZfZkxd/G14eo';
+ as.integrity = 'sha384-dqd1zhQwCaPNwnnxfckFnnSnDdhem8MpuxJOSB6Yvxc1uRXwkeyIsPXpUnbBIu4u';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.2-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.3-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index cc68ccb86..d416f439b 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-rGGnTWnBLdyeMUzGdN6uTRsJp8WDl5k35FXoetLOSqh4UVaZbxK09sSW/Mzp6tzZ';
+ as.integrity = 'sha384-Bnx/ydv/ChaWFeFAQkQJzhK20h7Daqih3HYpGGtC0BFpXuhOLf794kHvO2p7YuZW';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.2-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.3-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index 9d2e83812..b240dfe79 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.2",
+ "version": "1.0.3",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -42,12 +42,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.10.8",
- "@amplitude/analytics-client-common": "^1.0.1",
- "@amplitude/analytics-core": "^1.1.0",
- "@amplitude/analytics-types": "^1.1.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.2",
- "@amplitude/plugin-web-attribution-browser": "^1.0.2",
+ "@amplitude/analytics-browser": "^1.11.0",
+ "@amplitude/analytics-client-common": "^1.0.2",
+ "@amplitude/analytics-core": "^1.1.1",
+ "@amplitude/analytics-types": "^1.2.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.3",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.3",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index 926d3f2b3..30cbd1ac5 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.2';
+export const VERSION = '1.0.3';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 576865c52..a7c738e6f 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.2...@amplitude/plugin-page-view-tracking-browser@1.0.3) (2023-06-13)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.1...@amplitude/plugin-page-view-tracking-browser@1.0.2) (2023-06-08)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index cf27c8fc0..3c9ab818f 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.2",
+ "version": "1.0.3",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -36,12 +36,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.1",
- "@amplitude/analytics-types": "^1.1.0",
+ "@amplitude/analytics-client-common": "^1.0.2",
+ "@amplitude/analytics-types": "^1.2.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.8",
+ "@amplitude/analytics-browser": "^1.11.0",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index 354b623ce..046ebe285 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.2...@amplitude/plugin-web-attribution-browser@1.0.3) (2023-06-13)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.1...@amplitude/plugin-web-attribution-browser@1.0.2) (2023-06-08)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index 2dd91be9b..a4e4358ea 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.2",
+ "version": "1.0.3",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -36,12 +36,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.1",
- "@amplitude/analytics-types": "^1.1.0",
+ "@amplitude/analytics-client-common": "^1.0.2",
+ "@amplitude/analytics-types": "^1.2.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.8",
+ "@amplitude/analytics-browser": "^1.11.0",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From 52f7b08cc9501e41a8e421c7f84458dea78db972 Mon Sep 17 00:00:00 2001
From: Andrey Sokolov
Date: Tue, 13 Jun 2023 20:39:05 +0400
Subject: [PATCH 013/214] fix: do not fetch advertising Id if adid tracking is
disabled (#424)
* fix: do not fetch advertising Id if adid tracking is disabled
* fix: ios compilation
---
.../reactnative/AmplitudeReactNativeModule.kt | 30 +++++++++++--------
.../reactnative/AndroidContextProvider.kt | 18 +++++++----
.../ios/AmplitudeReactNative.m | 2 +-
.../ios/AmplitudeReactNative.swift | 3 +-
.../src/plugins/context.ts | 4 +--
5 files changed, 35 insertions(+), 22 deletions(-)
diff --git a/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt b/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt
index 6e8d4d13d..d42085c9c 100644
--- a/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt
+++ b/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt
@@ -13,25 +13,31 @@ const val MODULE_NAME = "AmplitudeReactNative"
@ReactModule(name = MODULE_NAME)
class AmplitudeReactNativeModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
- private val androidContextProvider = AndroidContextProvider(reactContext.applicationContext, false)
+ private var androidContextProvider: AndroidContextProvider? = null
override fun getName(): String {
return MODULE_NAME
}
@ReactMethod
- private fun getApplicationContext(promise: Promise) {
+ private fun getApplicationContext(shouldTrackAdid: Boolean, promise: Promise) {
+ if (androidContextProvider == null) {
+ androidContextProvider = AndroidContextProvider(reactContext.applicationContext, false, shouldTrackAdid)
+ }
+
promise.resolve(WritableNativeMap().apply {
- putString("version", androidContextProvider.versionName)
- putString("platform", androidContextProvider.platform)
- putString("language", androidContextProvider.language)
- putString("osName", androidContextProvider.osName)
- putString("osVersion", androidContextProvider.osVersion)
- putString("deviceBrand", androidContextProvider.brand)
- putString("deviceManufacturer", androidContextProvider.manufacturer)
- putString("deviceModel", androidContextProvider.model)
- putString("carrier", androidContextProvider.carrier)
- putString("adid", androidContextProvider.advertisingId)
+ putString("version", androidContextProvider!!.versionName)
+ putString("platform", androidContextProvider!!.platform)
+ putString("language", androidContextProvider!!.language)
+ putString("osName", androidContextProvider!!.osName)
+ putString("osVersion", androidContextProvider!!.osVersion)
+ putString("deviceBrand", androidContextProvider!!.brand)
+ putString("deviceManufacturer", androidContextProvider!!.manufacturer)
+ putString("deviceModel", androidContextProvider!!.model)
+ putString("carrier", androidContextProvider!!.carrier)
+ if (androidContextProvider!!.advertisingId != null) {
+ putString("adid", androidContextProvider!!.advertisingId)
+ }
})
}
}
diff --git a/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AndroidContextProvider.kt b/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AndroidContextProvider.kt
index e414cb3cd..11297ad8a 100644
--- a/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AndroidContextProvider.kt
+++ b/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AndroidContextProvider.kt
@@ -20,8 +20,9 @@ import java.util.Locale
import java.util.UUID
import kotlin.collections.ArrayList
-class AndroidContextProvider(private val context: Context, locationListening: Boolean) {
+class AndroidContextProvider(private val context: Context, locationListening: Boolean, shouldTrackAdid: Boolean) {
var isLocationListening = true
+ var shouldTrackAdid = true
private var cachedInfo: CachedInfo? = null
private get() {
if (field == null) {
@@ -34,7 +35,7 @@ class AndroidContextProvider(private val context: Context, locationListening: Bo
* Internal class serves as a cache
*/
inner class CachedInfo {
- var advertisingId: String
+ var advertisingId: String?
val country: String?
val versionName: String?
val osName: String
@@ -201,7 +202,11 @@ class AndroidContextProvider(private val context: Context, locationListening: Bo
return locale.language
}
- private fun fetchAdvertisingId(): String {
+ private fun fetchAdvertisingId(): String? {
+ if (!shouldTrackAdid) {
+ return null
+ }
+
// This should not be called on the main thread.
return if ("Amazon" == fetchManufacturer()) {
fetchAndCacheAmazonAdvertisingId
@@ -237,14 +242,14 @@ class AndroidContextProvider(private val context: Context, locationListening: Bo
return appSetId
}
- private val fetchAndCacheAmazonAdvertisingId: String
+ private val fetchAndCacheAmazonAdvertisingId: String?
private get() {
val cr = context.contentResolver
limitAdTrackingEnabled = Secure.getInt(cr, SETTING_LIMIT_AD_TRACKING, 0) == 1
advertisingId = Secure.getString(cr, SETTING_ADVERTISING_ID)
return advertisingId
}
- private val fetchAndCacheGoogleAdvertisingId: String
+ private val fetchAndCacheGoogleAdvertisingId: String?
private get() {
try {
val AdvertisingIdClient = Class
@@ -340,7 +345,7 @@ class AndroidContextProvider(private val context: Context, locationListening: Bo
get() = cachedInfo!!.country
val language: String
get() = cachedInfo!!.language
- val advertisingId: String
+ val advertisingId: String?
get() = cachedInfo!!.advertisingId
val appSetId: String
get() = cachedInfo!!.appSetId // other causes// failed to get providers list
@@ -416,5 +421,6 @@ class AndroidContextProvider(private val context: Context, locationListening: Bo
init {
isLocationListening = locationListening
+ this.shouldTrackAdid = shouldTrackAdid
}
}
diff --git a/packages/analytics-react-native/ios/AmplitudeReactNative.m b/packages/analytics-react-native/ios/AmplitudeReactNative.m
index 1700bba9b..3c9a587a2 100644
--- a/packages/analytics-react-native/ios/AmplitudeReactNative.m
+++ b/packages/analytics-react-native/ios/AmplitudeReactNative.m
@@ -2,6 +2,6 @@
@interface RCT_EXTERN_MODULE(AmplitudeReactNative, NSObject)
-RCT_EXTERN_METHOD(getApplicationContext: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
+RCT_EXTERN_METHOD(getApplicationContext: (BOOL)shouldTrackAdid resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@end
diff --git a/packages/analytics-react-native/ios/AmplitudeReactNative.swift b/packages/analytics-react-native/ios/AmplitudeReactNative.swift
index 60432835d..d1d8d51a5 100644
--- a/packages/analytics-react-native/ios/AmplitudeReactNative.swift
+++ b/packages/analytics-react-native/ios/AmplitudeReactNative.swift
@@ -13,7 +13,8 @@ class ReactNative: NSObject {
@objc
func getApplicationContext(
- _ resolve: RCTPromiseResolveBlock,
+ _ shouldTrackAdid: Bool,
+ resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) -> Void {
let applicationContext: [String: String?] = [
diff --git a/packages/analytics-react-native/src/plugins/context.ts b/packages/analytics-react-native/src/plugins/context.ts
index 85db8b021..08391e616 100644
--- a/packages/analytics-react-native/src/plugins/context.ts
+++ b/packages/analytics-react-native/src/plugins/context.ts
@@ -22,7 +22,7 @@ type NativeContext = {
};
export interface AmplitudeReactNative {
- getApplicationContext(): Promise;
+ getApplicationContext(shouldTrackAdid: boolean): Promise;
}
export class Context implements BeforePlugin {
@@ -55,7 +55,7 @@ export class Context implements BeforePlugin {
async execute(context: Event): Promise {
const time = new Date().getTime();
- const nativeContext = await this.nativeModule?.getApplicationContext();
+ const nativeContext = await this.nativeModule?.getApplicationContext(this.config.trackingOptions.adid ?? false);
const appVersion = nativeContext?.version || this.config.appVersion;
const platform = nativeContext?.platform || BROWSER_PLATFORM;
const osName = nativeContext?.osName || this.uaResult.browser.name;
From 477399777aebd9a1a0a00dc0e11504399d0a4031 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 13 Jun 2023 16:53:37 +0000
Subject: [PATCH 014/214] chore(release): publish
- @amplitude/analytics-react-native@1.2.1
---
packages/analytics-react-native/CHANGELOG.md | 13 +++++++++++++
packages/analytics-react-native/package.json | 2 +-
packages/analytics-react-native/src/version.ts | 2 +-
3 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/packages/analytics-react-native/CHANGELOG.md b/packages/analytics-react-native/CHANGELOG.md
index e2ed696f5..364cb58fd 100644
--- a/packages/analytics-react-native/CHANGELOG.md
+++ b/packages/analytics-react-native/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.2.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-react-native@1.2.0...@amplitude/analytics-react-native@1.2.1) (2023-06-13)
+
+### Bug Fixes
+
+- do not fetch advertising Id if adid tracking is disabled
+ ([#424](https://github.com/amplitude/Amplitude-TypeScript/issues/424))
+ ([52f7b08](https://github.com/amplitude/Amplitude-TypeScript/commit/52f7b08cc9501e41a8e421c7f84458dea78db972))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.2.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-react-native@1.1.9...@amplitude/analytics-react-native@1.2.0) (2023-06-13)
### Features
diff --git a/packages/analytics-react-native/package.json b/packages/analytics-react-native/package.json
index 116448feb..9076767de 100644
--- a/packages/analytics-react-native/package.json
+++ b/packages/analytics-react-native/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-react-native",
- "version": "1.2.0",
+ "version": "1.2.1",
"description": "Official React Native SDK",
"keywords": [
"analytics",
diff --git a/packages/analytics-react-native/src/version.ts b/packages/analytics-react-native/src/version.ts
index 408a62f23..5ec4dffca 100644
--- a/packages/analytics-react-native/src/version.ts
+++ b/packages/analytics-react-native/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.2.0';
+export const VERSION = '1.2.1';
From b46f2f4ff9cce2848bdd9bf554377ca7ed47983c Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Tue, 13 Jun 2023 18:13:56 -0700
Subject: [PATCH 015/214] fix: session end events being assigned to a different
session id (#426) (#431)
---
.../analytics-browser-test/test/index.test.ts | 408 ++++++++++--------
.../analytics-browser/src/browser-client.ts | 71 ++-
.../src/cookie-migration/index.ts | 7 +-
.../analytics-browser/src/plugins/context.ts | 13 +-
.../src/plugins/session-handler.ts | 53 ---
.../test/browser-client.test.ts | 73 +++-
.../analytics-browser/test/config.test.ts | 3 +
.../test/cookie-migration/index.test.ts | 29 +-
.../test/plugins/context.test.ts | 11 +-
.../test/plugins/session-handler.test.ts | 97 -----
10 files changed, 388 insertions(+), 377 deletions(-)
delete mode 100644 packages/analytics-browser/src/plugins/session-handler.ts
delete mode 100644 packages/analytics-browser/test/plugins/session-handler.test.ts
diff --git a/packages/analytics-browser-test/test/index.test.ts b/packages/analytics-browser-test/test/index.test.ts
index 9b3072dbd..1354199be 100644
--- a/packages/analytics-browser-test/test/index.test.ts
+++ b/packages/analytics-browser-test/test/index.test.ts
@@ -6,7 +6,7 @@ import { default as nock } from 'nock';
import { success } from './responses';
import 'isomorphic-fetch';
import { path, SUCCESS_MESSAGE, url, uuidPattern } from './constants';
-import { PluginType, LogLevel, BaseEvent, Status } from '@amplitude/analytics-types';
+import { PluginType, LogLevel, Status } from '@amplitude/analytics-types';
import { UUID } from '@amplitude/analytics-core';
describe('integration', () => {
@@ -20,6 +20,12 @@ describe('integration', () => {
disabled: true,
},
};
+ const defaultTracking = {
+ pageViews: false,
+ sessions: false,
+ fileDownloads: false,
+ formInteractions: false,
+ };
let apiKey = '';
let client = amplitude.createInstance();
@@ -882,7 +888,25 @@ describe('integration', () => {
});
describe('session handler', () => {
- test('should send session events and replaced with known user', () => {
+ const previousSessionId = Date.now() - 31 * 60 * 1000; // now minus 31 minutes
+ const previousSessionLastEventTime = Date.now() - 31 * 60 * 1000; // now minus 31 minutes
+ const previousSessionLastEventId = 99;
+ const previousSessionDeviceId = 'a7a96s8d';
+ const previousSessionUserId = 'a7a96s8d';
+ beforeEach(() => {
+ // setup expired previous session
+ setLegacyCookie(
+ apiKey,
+ previousSessionDeviceId,
+ previousSessionUserId,
+ undefined,
+ previousSessionId,
+ previousSessionLastEventTime,
+ previousSessionLastEventId,
+ );
+ });
+
+ test('should send session events and replace with known user', () => {
let payload: any = undefined;
const send = jest.fn().mockImplementationOnce(async (_endpoint, p) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -898,6 +922,7 @@ describe('integration', () => {
};
});
client.init(apiKey, 'user1@amplitude.com', {
+ deviceId: UUID(),
defaultTracking: {
sessions: true,
},
@@ -920,77 +945,36 @@ describe('integration', () => {
}, 1000);
setTimeout(() => {
- client.setUserId('user2@amplitude.com'); // effectively creates a new session
- // Sends `session_end` event for previous user and session
- // Sends `session_start` event for next user and session
+ client.setUserId('user2@amplitude.com');
}, 2000);
return new Promise((resolve) => {
setTimeout(() => {
- // Tranform events to be grouped by session and ordered by time
- // Helps assert that events will show up correctly on Amplitude UI
- const compactEvents: { eventType: string; userId: string; sessionId: number; time: number }[] =
- payload.events.map((e: BaseEvent) => ({
- eventType: e.event_type,
- userId: e.user_id,
- sessionId: e.session_id,
- time: e.time,
- }));
- const compactEventsBySessionAndTime = Object.values(
- compactEvents.reduce<
- Record
- >((acc, curr) => {
- if (!acc[curr.sessionId]) {
- acc[curr.sessionId] = [];
- }
- acc[curr.sessionId].push(curr);
- return acc;
- }, {}),
- )
- .map((c) => c.sort((a, b) => a.time - b.time))
- .map((group) => group.map((c) => ({ eventType: c.eventType, userId: c.userId })));
-
- // The order of events in the payload is sorted by time of track fn invokation
- // and not consistent with the time property
- // Session events have overwritten time property
- // Assert that events grouped by session and ordered by time
- expect(compactEventsBySessionAndTime).toEqual([
- [
- {
- eventType: 'session_start',
- userId: 'user1@amplitude.com',
- },
- {
- eventType: '$identify',
- userId: 'user1@amplitude.com',
- },
- {
- eventType: 'Event in first session',
- userId: 'user1@amplitude.com',
- },
- {
- eventType: 'session_end',
- userId: 'user1@amplitude.com',
- },
- ],
- [
- {
- eventType: 'session_start',
- userId: 'user1@amplitude.com',
- },
- {
- eventType: 'Event in next session',
- userId: 'user1@amplitude.com',
- },
- ],
- ]);
expect(payload).toEqual({
api_key: apiKey,
events: [
+ {
+ // This is a `session_end` event for the previous session
+ device_id: previousSessionDeviceId,
+ event_id: 100,
+ event_type: 'session_end',
+ insert_id: uuid,
+ ip: '$remote',
+ language: 'en-US',
+ library,
+ os_name: 'WebKit',
+ os_version: '537.36',
+ partner_id: undefined,
+ plan: undefined,
+ platform: 'Web',
+ session_id: previousSessionId,
+ time: previousSessionId + 1,
+ user_agent: userAgent,
+ user_id: previousSessionUserId,
+ },
{
device_id: uuid,
- device_manufacturer: undefined,
- event_id: 0,
+ event_id: 101,
event_type: 'session_start',
insert_id: uuid,
ip: '$remote',
@@ -1008,8 +992,7 @@ describe('integration', () => {
},
{
device_id: uuid,
- device_manufacturer: undefined,
- event_id: 1,
+ event_id: 102,
event_type: '$identify',
insert_id: uuid,
ip: '$remote',
@@ -1071,8 +1054,7 @@ describe('integration', () => {
},
{
device_id: uuid,
- device_manufacturer: undefined,
- event_id: 2,
+ event_id: 103,
event_type: 'Event in first session',
insert_id: uuid,
ip: '$remote',
@@ -1090,9 +1072,8 @@ describe('integration', () => {
},
{
device_id: uuid,
- device_manufacturer: undefined,
- event_id: 3,
- event_type: 'Event in next session',
+ event_id: 104,
+ event_type: 'session_end',
insert_id: uuid,
ip: '$remote',
language: 'en-US',
@@ -1109,9 +1090,8 @@ describe('integration', () => {
},
{
device_id: uuid,
- device_manufacturer: undefined,
- event_id: 4,
- event_type: 'session_end',
+ event_id: 105,
+ event_type: 'session_start',
insert_id: uuid,
ip: '$remote',
language: 'en-US',
@@ -1128,9 +1108,8 @@ describe('integration', () => {
},
{
device_id: uuid,
- device_manufacturer: undefined,
- event_id: 5,
- event_type: 'session_start',
+ event_id: 106,
+ event_type: 'Event in next session',
insert_id: uuid,
ip: '$remote',
language: 'en-US',
@@ -1155,7 +1134,10 @@ describe('integration', () => {
});
});
- test('should send session events and replaced with unknown user', () => {
+ test('should send session events and replace with unknown user', () => {
+ // Reset previous session cookies
+ document.cookie = `amp_${apiKey.substring(0, 6)}=null; expires=1 Jan 1970 00:00:00 GMT`;
+
let payload: any = undefined;
const send = jest.fn().mockImplementationOnce(async (_endpoint, p) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -1193,87 +1175,21 @@ describe('integration', () => {
}, 1000);
setTimeout(() => {
- client.reset(); // effectively creates a new session
- // Sends `session_end` event for previous user and session
- // Sends `session_start` event for next user and session
+ client.reset();
}, 2000);
return new Promise((resolve) => {
setTimeout(() => {
- // Tranform events to be grouped by session and ordered by time
- // Helps assert that events will show up correctly on Amplitude UI
- const compactEvents: {
- eventType: string;
- userId: string;
- sessionId: number;
- time: number;
- deviceId: string;
- }[] = payload.events.map((e: BaseEvent) => ({
- eventType: e.event_type,
- userId: e.user_id,
- sessionId: e.session_id,
- deviceId: e.device_id,
- time: e.time,
- }));
- const compactEventsBySessionAndTime = Object.values(
- compactEvents.reduce<
- Record
- >((acc, curr) => {
- if (!acc[curr.sessionId]) {
- acc[curr.sessionId] = [];
- }
- acc[curr.sessionId].push(curr);
- return acc;
- }, {}),
- )
- .map((c) => c.sort((a, b) => a.time - b.time))
- .map((group) => group.map((c) => ({ eventType: c.eventType, userId: c.userId, deviceId: c.deviceId })));
- const deviceIds = compactEventsBySessionAndTime.flat().map((c) => c.deviceId);
- expect(deviceIds[0]).toEqual(deviceIds[1]);
- expect(deviceIds[1]).toEqual(deviceIds[2]);
- expect(deviceIds[2]).toEqual(deviceIds[3]);
- expect(deviceIds[3]).toEqual(deviceIds[4]);
- expect(deviceIds[4]).toEqual(deviceIds[5]);
- // The order of events in the payload is sorted by time of track fn invokation
- // and not consistent with the time property
- // Session events have overwritten time property
- // Assert that events grouped by session and ordered by time
- expect(compactEventsBySessionAndTime).toEqual([
- [
- {
- eventType: 'session_start',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- {
- eventType: '$identify',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- {
- eventType: 'Event in first session',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- {
- eventType: 'session_end',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- ],
- [
- {
- eventType: 'session_start',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- {
- eventType: 'Event in next session',
- userId: 'user1@amplitude.com',
- deviceId: uuid,
- },
- ],
- ]);
+ const deviceId0 = payload.events[0].device_id;
+ [
+ payload.events[1].device_id,
+ payload.events[2].device_id,
+ payload.events[3].device_id,
+ payload.events[4].device_id,
+ payload.events[5].device_id,
+ ].forEach((deviceId) => {
+ expect(deviceId).toEqual(deviceId0);
+ });
expect(payload).toEqual({
api_key: apiKey,
events: [
@@ -1382,7 +1298,8 @@ describe('integration', () => {
device_id: uuid,
device_manufacturer: undefined,
event_id: 3,
- event_type: 'Event in next session',
+ event_type: 'session_end',
+
insert_id: uuid,
ip: '$remote',
language: 'en-US',
@@ -1401,7 +1318,7 @@ describe('integration', () => {
device_id: uuid,
device_manufacturer: undefined,
event_id: 4,
- event_type: 'session_end',
+ event_type: 'session_start',
insert_id: uuid,
ip: '$remote',
language: 'en-US',
@@ -1420,7 +1337,7 @@ describe('integration', () => {
device_id: uuid,
device_manufacturer: undefined,
event_id: 5,
- event_type: 'session_start',
+ event_type: 'Event in next session',
insert_id: uuid,
ip: '$remote',
language: 'en-US',
@@ -1444,6 +1361,115 @@ describe('integration', () => {
}, 4000);
});
});
+
+ test('should handle expired session', async () => {
+ const scope = nock(url).post(path).reply(200, success);
+ await client.init(apiKey, undefined, {
+ ...opts,
+ defaultTracking,
+ }).promise;
+ const response = await client.track('test event', {
+ mode: 'test',
+ }).promise;
+ expect(response.event.session_id).not.toBe(previousSessionId);
+ expect(response.event).toEqual({
+ user_id: previousSessionUserId,
+ device_id: previousSessionDeviceId,
+ session_id: number,
+ time: number,
+ platform: 'Web',
+ language: 'en-US',
+ ip: '$remote',
+ insert_id: uuid,
+ partner_id: undefined,
+ event_type: 'test event',
+ event_id: 100,
+ event_properties: {
+ mode: 'test',
+ },
+ library: library,
+ os_name: 'WebKit',
+ os_version: '537.36',
+ user_agent: userAgent,
+ });
+ expect(response.code).toBe(200);
+ expect(response.message).toBe(SUCCESS_MESSAGE);
+ expect(document.cookie.includes('amp_')).toBe(false);
+ scope.done();
+ });
+
+ test('should handle expired session and set with options.sessionId', async () => {
+ const scope = nock(url).post(path).reply(200, success);
+ await client.init(apiKey, undefined, {
+ ...opts,
+ defaultTracking,
+ sessionId: 1,
+ }).promise;
+ const response = await client.track('test event', {
+ mode: 'test',
+ }).promise;
+ expect(response.event.session_id).toBe(1);
+ expect(response.event).toEqual({
+ user_id: previousSessionUserId,
+ device_id: previousSessionDeviceId,
+ session_id: number,
+ time: number,
+ platform: 'Web',
+ language: 'en-US',
+ ip: '$remote',
+ insert_id: uuid,
+ partner_id: undefined,
+ event_type: 'test event',
+ event_id: 100,
+ event_properties: {
+ mode: 'test',
+ },
+ library: library,
+ os_name: 'WebKit',
+ os_version: '537.36',
+ user_agent: userAgent,
+ });
+ expect(response.code).toBe(200);
+ expect(response.message).toBe(SUCCESS_MESSAGE);
+ expect(document.cookie.includes('amp_')).toBe(false);
+ scope.done();
+ });
+
+ test('should handle expired session and set with setSessionId', async () => {
+ const scope = nock(url).post(path).reply(200, success);
+ await client.init(apiKey, undefined, {
+ ...opts,
+ defaultTracking,
+ }).promise;
+ client.setSessionId(1);
+ const response = await client.track('test event', {
+ mode: 'test',
+ }).promise;
+ expect(response.event).toEqual({
+ user_id: previousSessionUserId,
+ device_id: previousSessionDeviceId,
+ session_id: 1,
+ time: number,
+ platform: 'Web',
+ language: 'en-US',
+ ip: '$remote',
+ insert_id: uuid,
+ partner_id: undefined,
+ event_type: 'test event',
+ event_id: 100,
+ event_properties: {
+ mode: 'test',
+ },
+ library: library,
+ os_name: 'WebKit',
+ os_version: '537.36',
+ user_agent: userAgent,
+ });
+ expect(response.code).toBe(200);
+ expect(response.message).toBe(SUCCESS_MESSAGE);
+ expect(document.cookie.includes('amp_')).toBe(false);
+ scope.done();
+ });
});
describe('default page view tracking', () => {
@@ -1746,23 +1772,38 @@ describe('integration', () => {
});
describe('cookie migration', () => {
+ const previousSessionId = Date.now(); // now minus 31 minutes
+ const previousSessionLastEventTime = Date.now(); // now minus 31 minutes
+ const previousSessionLastEventId = 99;
+ const previousSessionDeviceId = 'deviceId';
+ const previousSessionUserId = 'userId';
+
+ beforeEach(() => {
+ // setup expired previous session
+ setLegacyCookie(
+ apiKey,
+ previousSessionDeviceId,
+ previousSessionUserId,
+ undefined,
+ previousSessionId,
+ previousSessionLastEventTime,
+ previousSessionLastEventId,
+ );
+ });
+
test('should use old cookies', async () => {
const scope = nock(url).post(path).reply(200, success);
- const timestamp = Date.now();
- const time = timestamp.toString(32);
- const userId = 'userId';
- const encodedUserId = btoa(unescape(encodeURIComponent(userId)));
- document.cookie = `amp_${apiKey.substring(0, 6)}=deviceId.${encodedUserId}..${time}.${time}`;
await client.init(apiKey, undefined, {
...opts,
+ defaultTracking,
}).promise;
const response = await client.track('test event', {
mode: 'test',
}).promise;
expect(response.event).toEqual({
- user_id: userId,
- device_id: 'deviceId',
- session_id: timestamp,
+ user_id: previousSessionUserId,
+ device_id: previousSessionDeviceId,
+ session_id: previousSessionId,
time: number,
platform: 'Web',
os_name: 'WebKit',
@@ -1773,7 +1814,7 @@ describe('integration', () => {
insert_id: uuid,
partner_id: undefined,
event_type: 'test event',
- event_id: 0,
+ event_id: 100,
event_properties: {
mode: 'test',
},
@@ -1788,22 +1829,18 @@ describe('integration', () => {
test('should retain old cookies', async () => {
const scope = nock(url).post(path).reply(200, success);
- const timestamp = Date.now();
- const time = timestamp.toString(32);
- const userId = 'userId';
- const encodedUserId = btoa(unescape(encodeURIComponent(userId)));
- document.cookie = `amp_${apiKey.substring(0, 6)}=deviceId.${encodedUserId}..${time}.${time}`;
await client.init(apiKey, undefined, {
...opts,
cookieUpgrade: false,
+ defaultTracking,
}).promise;
const response = await client.track('test event', {
mode: 'test',
}).promise;
expect(response.event).toEqual({
- user_id: userId,
- device_id: 'deviceId',
- session_id: timestamp,
+ user_id: previousSessionUserId,
+ device_id: previousSessionDeviceId,
+ session_id: previousSessionId,
time: number,
platform: 'Web',
os_name: 'WebKit',
@@ -1814,7 +1851,7 @@ describe('integration', () => {
insert_id: uuid,
partner_id: undefined,
event_type: 'test event',
- event_id: 0,
+ event_id: 100,
event_properties: {
mode: 'test',
},
@@ -1980,3 +2017,22 @@ describe('integration', () => {
});
});
});
+
+const setLegacyCookie = (
+ apiKey: string,
+ deviceId?: string,
+ userId?: string,
+ optOut?: '1',
+ sessionId?: number,
+ lastEventTime?: number,
+ eventId?: number,
+) => {
+ document.cookie = `amp_${apiKey.substring(0, 6)}=${[
+ deviceId,
+ btoa(userId || ''),
+ optOut,
+ sessionId ? sessionId.toString(32) : '',
+ lastEventTime ? lastEventTime.toString(32) : '',
+ eventId ? eventId.toString(32) : '',
+ ].join('.')}`;
+};
diff --git a/packages/analytics-browser/src/browser-client.ts b/packages/analytics-browser/src/browser-client.ts
index 7027847cc..4215cd50d 100644
--- a/packages/analytics-browser/src/browser-client.ts
+++ b/packages/analytics-browser/src/browser-client.ts
@@ -15,6 +15,7 @@ import {
BrowserClient,
BrowserConfig,
BrowserOptions,
+ Event,
EventOptions,
Identify as IIdentify,
Revenue as IRevenue,
@@ -27,7 +28,6 @@ import { useBrowserConfig, createTransport, getTopLevelDomain, createCookieStora
import { parseLegacyCookies } from './cookie-migration';
import { webAttributionPlugin } from '@amplitude/plugin-web-attribution-browser';
import { pageViewTrackingPlugin } from '@amplitude/plugin-page-view-tracking-browser';
-import { sessionHandlerPlugin } from './plugins/session-handler';
import { formInteractionTracking } from './plugins/form-interaction-tracking';
import { fileDownloadTracking } from './plugins/file-download-tracking';
import { DEFAULT_PAGE_VIEW_EVENT, DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_START_EVENT } from './constants';
@@ -59,9 +59,9 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
// Step 2: Create browser config
const deviceId = options.deviceId ?? queryParams.deviceId ?? previousCookies?.deviceId ?? legacyCookies.deviceId;
- const sessionId = options.sessionId ?? previousCookies?.sessionId ?? legacyCookies.sessionId;
+ const sessionId = previousCookies?.sessionId ?? legacyCookies.sessionId;
const optOut = options.optOut ?? previousCookies?.optOut ?? legacyCookies.optOut;
- const lastEventId = previousCookies?.lastEventId;
+ const lastEventId = previousCookies?.lastEventId ?? legacyCookies.lastEventId;
const lastEventTime = previousCookies?.lastEventTime ?? legacyCookies.lastEventTime;
const userId = options.userId ?? previousCookies?.userId ?? legacyCookies.userId;
@@ -81,16 +81,17 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
await super._init(browserOptions);
- // Step 3: Manage session
- let isNewSession = !this.config.lastEventTime;
+ // Step 3: Set session ID
+ let isNewSession = false;
if (
+ // user has never sent an event
+ !this.config.lastEventTime ||
+ // user has no previous session ID
!this.config.sessionId ||
+ // has sent an event and has previous session but expired
(this.config.lastEventTime && Date.now() - this.config.lastEventTime > this.config.sessionTimeout)
) {
- // Either
- // 1) No previous session; or
- // 2) Previous session expired
- this.setSessionId(Date.now());
+ this.setSessionId(options.sessionId ?? this.config.sessionId ?? Date.now());
isNewSession = true;
}
@@ -107,7 +108,6 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
// Do not track any events before this
await this.add(new Destination()).promise;
await this.add(new Context()).promise;
- await this.add(sessionHandlerPlugin()).promise;
await this.add(new IdentityEventSender()).promise;
if (isFileDownloadTrackingEnabled(this.config.defaultTracking)) {
@@ -195,30 +195,40 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
this.q.push(this.setSessionId.bind(this, sessionId));
return;
}
+
+ // Prevents starting a new session with the same session ID
+ if (sessionId === this.config.sessionId) {
+ return;
+ }
+
const previousSessionId = this.getSessionId();
- const previousLastEventTime = this.config.lastEventTime;
+ const lastEventTime = this.config.lastEventTime;
+ let lastEventId = this.config.lastEventId ?? -1;
this.config.sessionId = sessionId;
this.config.lastEventTime = undefined;
if (isSessionTrackingEnabled(this.config.defaultTracking)) {
- if (previousSessionId && previousLastEventTime) {
- const eventOptions: EventOptions = {
+ if (previousSessionId && lastEventTime) {
+ this.track(DEFAULT_SESSION_END_EVENT, undefined, {
+ device_id: this.previousSessionDeviceId,
+ event_id: ++lastEventId,
session_id: previousSessionId,
- time: previousLastEventTime + 1,
- };
- eventOptions.device_id = this.previousSessionDeviceId;
- eventOptions.user_id = this.previousSessionUserId;
- this.track(DEFAULT_SESSION_END_EVENT, undefined, eventOptions);
+ time: lastEventTime + 1,
+ user_id: this.previousSessionUserId,
+ });
}
+ this.config.lastEventTime = this.config.sessionId;
this.track(DEFAULT_SESSION_START_EVENT, undefined, {
- session_id: sessionId,
- time: sessionId - 1,
+ event_id: ++lastEventId,
+ session_id: this.config.sessionId,
+ time: this.config.lastEventTime,
});
- this.previousSessionDeviceId = this.config.deviceId;
- this.previousSessionUserId = this.config.userId;
}
+
+ this.previousSessionDeviceId = this.config.deviceId;
+ this.previousSessionUserId = this.config.userId;
}
extendSession() {
@@ -269,4 +279,21 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
}
return super.revenue(revenue, eventOptions);
}
+
+ async process(event: Event) {
+ const currentTime = Date.now();
+ const lastEventTime = this.config.lastEventTime || Date.now();
+ const timeSinceLastEvent = currentTime - lastEventTime;
+
+ if (
+ event.event_type !== DEFAULT_SESSION_START_EVENT &&
+ event.event_type !== DEFAULT_SESSION_END_EVENT &&
+ (!event.session_id || event.session_id === this.getSessionId()) &&
+ timeSinceLastEvent > this.config.sessionTimeout
+ ) {
+ this.setSessionId(currentTime);
+ }
+
+ return super.process(event);
+ }
}
diff --git a/packages/analytics-browser/src/cookie-migration/index.ts b/packages/analytics-browser/src/cookie-migration/index.ts
index 751877e0e..81ebc22d1 100644
--- a/packages/analytics-browser/src/cookie-migration/index.ts
+++ b/packages/analytics-browser/src/cookie-migration/index.ts
@@ -2,7 +2,7 @@ import { BrowserOptions, UserSession } from '@amplitude/analytics-types';
import { getOldCookieName } from '@amplitude/analytics-client-common';
import { createCookieStorage, getDefaultConfig } from '../config';
-export const parseLegacyCookies = async (apiKey: string, options?: BrowserOptions): Promise => {
+export const parseLegacyCookies = async (apiKey: string, options: BrowserOptions): Promise => {
const storage = await createCookieStorage(options);
const oldCookieName = getOldCookieName(apiKey);
const cookies = await storage.getRaw(oldCookieName);
@@ -13,14 +13,15 @@ export const parseLegacyCookies = async (apiKey: string, options?: BrowserOption
};
}
- if (options?.cookieUpgrade ?? getDefaultConfig().cookieUpgrade) {
+ if (options.cookieUpgrade ?? getDefaultConfig().cookieUpgrade) {
await storage.remove(oldCookieName);
}
- const [deviceId, userId, optOut, sessionId, lastEventTime] = cookies.split('.');
+ const [deviceId, userId, optOut, sessionId, lastEventTime, lastEventId] = cookies.split('.');
return {
deviceId,
userId: decode(userId),
sessionId: parseTime(sessionId),
+ lastEventId: parseTime(lastEventId),
lastEventTime: parseTime(lastEventTime),
optOut: Boolean(optOut),
};
diff --git a/packages/analytics-browser/src/plugins/context.ts b/packages/analytics-browser/src/plugins/context.ts
index 63389648b..eef171c23 100644
--- a/packages/analytics-browser/src/plugins/context.ts
+++ b/packages/analytics-browser/src/plugins/context.ts
@@ -14,7 +14,6 @@ export class Context implements BeforePlugin {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
config: BrowserConfig;
- eventId = 0;
userAgent: string | undefined;
uaResult: UAParser.IResult;
library = `amplitude-ts/${VERSION}`;
@@ -29,18 +28,24 @@ export class Context implements BeforePlugin {
setup(config: BrowserConfig): Promise {
this.config = config;
- this.eventId = this.config.lastEventId ? this.config.lastEventId + 1 : 0;
return Promise.resolve(undefined);
}
async execute(context: Event): Promise {
const time = new Date().getTime();
+
const osName = this.uaResult.browser.name;
const osVersion = this.uaResult.browser.version;
const deviceModel = this.uaResult.device.model || this.uaResult.os.name;
const deviceVendor = this.uaResult.device.vendor;
- this.config.lastEventId = this.eventId;
+
+ const lastEventId = this.config.lastEventId ?? -1;
+ const nextEventId = context.event_id ?? lastEventId + 1;
+ this.config.lastEventId = nextEventId;
+ if (!context.time) {
+ this.config.lastEventTime = time;
+ }
const event: Event = {
user_id: this.config.userId,
@@ -65,7 +70,7 @@ export class Context implements BeforePlugin {
},
}),
...context,
- event_id: this.eventId++,
+ event_id: nextEventId,
library: this.library,
user_agent: this.userAgent,
};
diff --git a/packages/analytics-browser/src/plugins/session-handler.ts b/packages/analytics-browser/src/plugins/session-handler.ts
deleted file mode 100644
index edde0ede7..000000000
--- a/packages/analytics-browser/src/plugins/session-handler.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { BeforePlugin, BrowserClient, BrowserConfig, Event, PluginType } from '@amplitude/analytics-types';
-import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_START_EVENT } from '../constants';
-
-export const sessionHandlerPlugin = (): BeforePlugin => {
- // browserConfig is defined in setup() which will always be called first
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- let browserConfig: BrowserConfig;
- // amplitude is defined in setup() which will always be called first
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- let amplitude: BrowserClient;
-
- const name = '@amplitude/plugin-session-handler';
-
- const type = PluginType.BEFORE;
-
- const setup = async (config: BrowserConfig, client: BrowserClient) => {
- browserConfig = config;
- amplitude = client;
- };
-
- const execute = async (event: Event) => {
- const now = Date.now();
-
- if (event.event_type === DEFAULT_SESSION_START_EVENT || event.event_type === DEFAULT_SESSION_END_EVENT) {
- browserConfig.lastEventTime = now;
- return event;
- }
-
- const lastEventTime = browserConfig.lastEventTime || now;
- const timeSinceLastEvent = now - lastEventTime;
-
- if (timeSinceLastEvent > browserConfig.sessionTimeout) {
- // assigns new session
- amplitude.setSessionId(now);
- event.session_id = amplitude.getSessionId();
- event.time = now;
- } // else use existing session
-
- // updates last event time to extend time-based session
- browserConfig.lastEventTime = now;
-
- return event;
- };
-
- return {
- name,
- type,
- setup,
- execute,
- };
-};
diff --git a/packages/analytics-browser/test/browser-client.test.ts b/packages/analytics-browser/test/browser-client.test.ts
index 78615b89d..fce55fcf0 100644
--- a/packages/analytics-browser/test/browser-client.test.ts
+++ b/packages/analytics-browser/test/browser-client.test.ts
@@ -3,7 +3,7 @@ import * as core from '@amplitude/analytics-core';
import * as Config from '../src/config';
import * as CookieMigration from '../src/cookie-migration';
import { Status, TransportType, UserSession } from '@amplitude/analytics-types';
-import { FetchTransport, getAnalyticsConnector } from '@amplitude/analytics-client-common';
+import { FetchTransport, getAnalyticsConnector, getCookieName } from '@amplitude/analytics-client-common';
import * as SnippetHelper from '../src/utils/snippet-helper';
import * as fileDownloadTracking from '../src/plugins/file-download-tracking';
import * as formInteractionTracking from '../src/plugins/form-interaction-tracking';
@@ -19,6 +19,13 @@ describe('browser-client', () => {
disabled: true,
},
};
+ let apiKey = '';
+ let client = new AmplitudeBrowser();
+
+ beforeEach(() => {
+ apiKey = core.UUID();
+ client = new AmplitudeBrowser();
+ });
afterEach(() => {
// clean up cookies
@@ -43,9 +50,15 @@ describe('browser-client', () => {
optOut: false,
lastEventTime: Date.now(),
});
+ const cookieStorage = new core.MemoryStorage();
+ await cookieStorage.set(getCookieName(API_KEY), {
+ optOut: false,
+ lastEventId: 100,
+ });
const client = new AmplitudeBrowser();
await client.init(API_KEY, USER_ID, {
sessionId: Date.now(),
+ cookieStorage,
}).promise;
expect(parseLegacyCookies).toHaveBeenCalledTimes(1);
});
@@ -68,6 +81,7 @@ describe('browser-client', () => {
deviceId: DEVICE_ID,
sessionId: 1,
lastEventTime: Date.now() - 1000,
+ lastEventId: 100,
});
const cookieStorage = new core.MemoryStorage();
const client = new AmplitudeBrowser();
@@ -220,13 +234,12 @@ describe('browser-client', () => {
jest.spyOn(CookieMigration, 'parseLegacyCookies').mockResolvedValueOnce({
optOut: false,
lastEventTime: Date.now(),
+ sessionId: Date.now(),
});
const client = new AmplitudeBrowser();
const webAttributionPluginPlugin = jest.spyOn(webAttributionPlugin, 'webAttributionPlugin');
await client.init(API_KEY, USER_ID, {
optOut: false,
- attribution: {},
- sessionId: Date.now(),
transportProvider: {
send: async () => ({
status: Status.Success,
@@ -246,6 +259,7 @@ describe('browser-client', () => {
jest.spyOn(CookieMigration, 'parseLegacyCookies').mockResolvedValueOnce({
optOut: false,
lastEventTime: Date.now(),
+ sessionId: Date.now(),
});
const client = new AmplitudeBrowser();
const webAttributionPluginPlugin = jest.spyOn(webAttributionPlugin, 'webAttributionPlugin');
@@ -254,7 +268,6 @@ describe('browser-client', () => {
attribution: {
trackNewCampaigns: true,
},
- sessionId: Date.now(),
transportProvider: {
send: async () => ({
status: Status.Success,
@@ -503,13 +516,14 @@ describe('browser-client', () => {
}).promise;
client.setSessionId(2);
expect(client.getSessionId()).toBe(2);
- expect(track).toHaveBeenCalledTimes(1);
+ expect(track).toHaveBeenCalledTimes(3);
});
test('should set session id with start and end session event', async () => {
jest.spyOn(CookieMigration, 'parseLegacyCookies').mockResolvedValueOnce({
optOut: false,
sessionId: 1,
+ lastEventId: 100,
lastEventTime: Date.now() - 1000,
});
const client = new AmplitudeBrowser();
@@ -556,6 +570,7 @@ describe('browser-client', () => {
const firstSessionId = 1;
const client = new AmplitudeBrowser();
await client.init(API_KEY, undefined, {
+ ...attributionConfig,
sessionTimeout: 20,
sessionId: firstSessionId,
flushQueueSize: 1,
@@ -834,4 +849,52 @@ describe('browser-client', () => {
expect(convertProxyObjectToRealObject).toHaveBeenCalledTimes(1);
});
});
+
+ describe('process', () => {
+ test('should proceed with unexpired session', async () => {
+ const setSessionId = jest.spyOn(client, 'setSessionId');
+ await client.init(apiKey, undefined, {
+ optOut: true,
+ ...attributionConfig,
+ }).promise;
+ const result = await client.process({
+ event_type: 'event',
+ });
+ // once on init
+ expect(setSessionId).toHaveBeenCalledTimes(1);
+ expect(result.code).toBe(0);
+ });
+
+ test('should proceed with overriden session ID', async () => {
+ const setSessionId = jest.spyOn(client, 'setSessionId');
+ await client.init(apiKey, undefined, {
+ optOut: true,
+ ...attributionConfig,
+ }).promise;
+ const result = await client.process({
+ event_type: 'event',
+ session_id: -1,
+ });
+ // once on init
+ expect(setSessionId).toHaveBeenCalledTimes(1);
+ expect(result.code).toBe(0);
+ });
+
+ test('should reset session due to expired session', async () => {
+ const setSessionId = jest.spyOn(client, 'setSessionId');
+ await client.init(apiKey, undefined, {
+ optOut: true,
+ ...attributionConfig,
+ // force session to always be expired
+ sessionTimeout: -1,
+ }).promise;
+ const result = await client.process({
+ event_type: 'event',
+ });
+ // once on init
+ // and once on process
+ expect(setSessionId).toHaveBeenCalledTimes(2);
+ expect(result.code).toBe(0);
+ });
+ });
});
diff --git a/packages/analytics-browser/test/config.test.ts b/packages/analytics-browser/test/config.test.ts
index 133a17e07..ab583cbf2 100644
--- a/packages/analytics-browser/test/config.test.ts
+++ b/packages/analytics-browser/test/config.test.ts
@@ -114,6 +114,7 @@ describe('config', () => {
deviceId: 'device-device-device',
sessionId: -1,
userId: 'user-user-user',
+ lastEventId: 100,
lastEventTime: 1,
optOut: false,
});
@@ -124,6 +125,7 @@ describe('config', () => {
deviceId: 'device-device-device',
sessionId: -1,
userId: 'user-user-user',
+ lastEventId: 100,
lastEventTime: 1,
partnerId: 'partnerId',
plan: {
@@ -151,6 +153,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ _lastEventId: 100,
_lastEventTime: 1,
loggerProvider: logger,
logLevel: LogLevel.Warn,
diff --git a/packages/analytics-browser/test/cookie-migration/index.test.ts b/packages/analytics-browser/test/cookie-migration/index.test.ts
index 70939a69d..941210309 100644
--- a/packages/analytics-browser/test/cookie-migration/index.test.ts
+++ b/packages/analytics-browser/test/cookie-migration/index.test.ts
@@ -1,5 +1,5 @@
import { CookieStorage, getOldCookieName } from '@amplitude/analytics-client-common';
-import { Storage } from '@amplitude/analytics-types';
+import { Storage, UserSession } from '@amplitude/analytics-types';
import { decode, parseLegacyCookies, parseTime } from '../../src/cookie-migration';
import * as LocalStorageModule from '../../src/storage/local-storage';
@@ -39,12 +39,15 @@ describe('cookie-migration', () => {
const userId = 'userId';
const encodedUserId = btoa(unescape(encodeURIComponent(userId)));
const oldCookieName = getOldCookieName(API_KEY);
- document.cookie = `${oldCookieName}=deviceId.${encodedUserId}..${time}.${time}`;
- const cookies = await parseLegacyCookies(API_KEY);
+ const lastEventId = (0).toString(32);
+ document.cookie = `${oldCookieName}=deviceId.${encodedUserId}..${time}.${time}.${lastEventId}`;
+ const cookieStorage: Storage = new CookieStorage();
+ const cookies = await parseLegacyCookies(API_KEY, { cookieStorage });
expect(cookies).toEqual({
deviceId: 'deviceId',
userId: 'userId',
sessionId: timestamp,
+ lastEventId: 0,
lastEventTime: timestamp,
optOut: false,
});
@@ -60,15 +63,16 @@ describe('cookie-migration', () => {
const userId = 'userId';
const encodedUserId = btoa(unescape(encodeURIComponent(userId)));
const oldCookieName = getOldCookieName(API_KEY);
- document.cookie = `${oldCookieName}=deviceId.${encodedUserId}..${time}.${time}`;
- const cookies = await parseLegacyCookies(API_KEY, {
- cookieUpgrade: true,
- });
+ const lastEventId = (0).toString(32);
+ document.cookie = `${oldCookieName}=deviceId.${encodedUserId}..${time}.${time}.${lastEventId}`;
+ const cookieStorage: Storage = new CookieStorage();
+ const cookies = await parseLegacyCookies(API_KEY, { cookieStorage, cookieUpgrade: true });
expect(cookies).toEqual({
deviceId: 'deviceId',
userId: 'userId',
sessionId: timestamp,
lastEventTime: timestamp,
+ lastEventId: 0,
optOut: false,
});
@@ -83,21 +87,22 @@ describe('cookie-migration', () => {
const userId = 'userId';
const encodedUserId = btoa(unescape(encodeURIComponent(userId)));
const oldCookieName = getOldCookieName(API_KEY);
- document.cookie = `${oldCookieName}=deviceId.${encodedUserId}..${time}.${time}`;
- const cookies = await parseLegacyCookies(API_KEY, {
- cookieUpgrade: false,
- });
+ const lastEventId = (0).toString(32);
+ document.cookie = `${oldCookieName}=deviceId.${encodedUserId}..${time}.${time}.${lastEventId}`;
+ const cookieStorage: Storage = new CookieStorage();
+ const cookies = await parseLegacyCookies(API_KEY, { cookieStorage, cookieUpgrade: false });
expect(cookies).toEqual({
deviceId: 'deviceId',
userId: 'userId',
sessionId: timestamp,
lastEventTime: timestamp,
+ lastEventId: 0,
optOut: false,
});
const storage: Storage = new CookieStorage();
const cookies2 = await storage.getRaw(oldCookieName);
- expect(cookies2).toBe(`deviceId.${encodedUserId}..${time}.${time}`);
+ expect(cookies2).toBe(`deviceId.${encodedUserId}..${time}.${time}.${lastEventId}`);
});
});
diff --git a/packages/analytics-browser/test/plugins/context.test.ts b/packages/analytics-browser/test/plugins/context.test.ts
index 4c30acd8e..0e622fde2 100644
--- a/packages/analytics-browser/test/plugins/context.test.ts
+++ b/packages/analytics-browser/test/plugins/context.test.ts
@@ -10,7 +10,6 @@ describe('context', () => {
config.lastEventId = 1;
await context.setup(config);
expect(context.config.appVersion).toEqual('1.0.0');
- expect(context.eventId).toEqual(2);
expect(context.uaResult).toBeDefined();
});
@@ -19,7 +18,6 @@ describe('context', () => {
const config = useDefaultConfig();
await context.setup(config);
expect(context.config.appVersion).toBeUndefined();
- expect(context.eventId).toEqual(0);
expect(context.uaResult).toBeDefined();
});
});
@@ -112,15 +110,18 @@ describe('context', () => {
event_type: 'event_type',
device_id: 'new deviceId',
};
- const firstContextEvent = await context.execute(event);
+ const firstContextEvent = await context.execute({
+ ...event,
+ event_id: 100,
+ });
expect(firstContextEvent.app_version).toEqual('1.0.0');
- expect(firstContextEvent.event_id).toEqual(0);
+ expect(firstContextEvent.event_id).toEqual(100);
expect(firstContextEvent.event_type).toEqual('event_type');
expect(firstContextEvent.insert_id).toBeDefined();
expect(firstContextEvent.device_id).toEqual('new deviceId');
const secondContextEvent = await context.execute(event);
- expect(secondContextEvent.event_id).toEqual(1);
+ expect(secondContextEvent.event_id).toEqual(101);
});
describe('ingestionMetadata config', () => {
diff --git a/packages/analytics-browser/test/plugins/session-handler.test.ts b/packages/analytics-browser/test/plugins/session-handler.test.ts
deleted file mode 100644
index 413ad054d..000000000
--- a/packages/analytics-browser/test/plugins/session-handler.test.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-/* eslint-disable @typescript-eslint/unbound-method */
-
-import { createAmplitudeMock, createConfigurationMock } from '../helpers/mock';
-import { sessionHandlerPlugin } from '../../src/plugins/session-handler';
-
-describe('sessionHandlerPlugin', () => {
- let amplitude = createAmplitudeMock();
-
- beforeEach(() => {
- amplitude = createAmplitudeMock();
- });
-
- test('should use the existing session', async () => {
- const plugin = sessionHandlerPlugin();
- const time = Date.now();
- const config = createConfigurationMock({
- sessionId: 1,
- lastEventTime: time - 999,
- sessionTimeout: 1000,
- });
- await plugin.setup(config, amplitude);
- await plugin.execute({
- event_type: 'Test Event',
- });
-
- // assert session id is unchanged
- expect(config.sessionId).toBe(1);
-
- // assert last event time was updated
- expect(config.lastEventTime).not.toBe(time - 999);
-
- // assert no events are instrumented
- expect(amplitude.track).toHaveBeenCalledTimes(0);
- });
-
- test('should use the existing session with no lastEventTime', async () => {
- const plugin = sessionHandlerPlugin();
- const config = createConfigurationMock({
- sessionId: 1,
- lastEventTime: undefined,
- sessionTimeout: 1000,
- });
- await plugin.setup(config, amplitude);
- await plugin.execute({
- event_type: 'Test Event',
- });
-
- // assert session id is unchanged
- expect(config.sessionId).toBe(1);
-
- // assert last event time was updated
- expect(config.lastEventTime).not.toBeUndefined();
-
- // assert no events are instrumented
- expect(amplitude.track).toHaveBeenCalledTimes(0);
- });
-
- test('should assign new session', async () => {
- const plugin = sessionHandlerPlugin();
- const time = Date.now();
- const config = createConfigurationMock({
- sessionId: 1,
- lastEventTime: time - 1001,
- sessionTimeout: 1000,
- defaultTracking: {
- sessions: true,
- },
- });
- await plugin.setup(config, amplitude);
- await plugin.execute({
- event_type: 'Test Event',
- });
-
- // assert session id was changed
- expect(amplitude.setSessionId).toHaveBeenCalledTimes(1);
- });
-
- test.each(['session_start', 'session_end'])('should handle session events', async (eventType) => {
- const plugin = sessionHandlerPlugin();
- const time = Date.now();
- const config = createConfigurationMock({
- sessionId: 1,
- lastEventTime: time - 1001,
- sessionTimeout: 1000,
- defaultTracking: {
- sessions: true,
- },
- });
- await plugin.setup(config, amplitude);
- await plugin.execute({
- event_type: eventType,
- });
-
- // assert session id was changed
- expect(amplitude.setSessionId).toHaveBeenCalledTimes(0);
- });
-});
From 018337284d2bab9d35414e4a14f720b1234ff8ab Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 14 Jun 2023 03:35:12 +0000
Subject: [PATCH 016/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.4
- @amplitude/analytics-browser@1.11.1
- @amplitude/marketing-analytics-browser@1.0.4
- @amplitude/plugin-page-view-tracking-browser@1.0.4
- @amplitude/plugin-web-attribution-browser@1.0.4
---
packages/analytics-browser-test/CHANGELOG.md | 14 ++++++++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 14 ++++++++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 6 +++---
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 8 ++++----
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 4 ++--
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 4 ++--
18 files changed, 79 insertions(+), 24 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index ff4138537..ce25fbe9e 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.3...@amplitude/analytics-browser-test@1.4.4) (2023-06-14)
+
+### Bug Fixes
+
+- session end events being assigned to a different session id
+ ([#426](https://github.com/amplitude/Amplitude-TypeScript/issues/426))
+ ([#431](https://github.com/amplitude/Amplitude-TypeScript/issues/431))
+ ([b46f2f4](https://github.com/amplitude/Amplitude-TypeScript/commit/b46f2f4ff9cce2848bdd9bf554377ca7ed47983c))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.4.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.2...@amplitude/analytics-browser-test@1.4.3) (2023-06-13)
**Note:** Version bump only for package @amplitude/analytics-browser-test
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index 7761cb358..413c6b862 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.3",
+ "version": "1.4.4",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.11.0"
+ "@amplitude/analytics-browser": "^1.11.1"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index bbb729bb4..554ee24b8 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.11.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.11.0...@amplitude/analytics-browser@1.11.1) (2023-06-14)
+
+### Bug Fixes
+
+- session end events being assigned to a different session id
+ ([#426](https://github.com/amplitude/Amplitude-TypeScript/issues/426))
+ ([#431](https://github.com/amplitude/Amplitude-TypeScript/issues/431))
+ ([b46f2f4](https://github.com/amplitude/Amplitude-TypeScript/commit/b46f2f4ff9cce2848bdd9bf554377ca7ed47983c))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.11.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.10.8...@amplitude/analytics-browser@1.11.0) (2023-06-13)
### Features
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index b39f0da86..c248d306c 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index ee6a95c3b..ac7bb52b9 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-88G7ldPwlcBAEMvub2fJXyrErbB+r+yi8fkKzYrr3iUDAkNi/0Y6kR2jyCMC1rfJ';
+ as.integrity = 'sha384-So8wDvkfueWi1vtYoaT+2UMVdV5gytnrAUr8sumYyMQo4TNK4Lpct6VewTWsSNoH';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.11.0-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.11.1-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 73ae2af67..462ed8a8c 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.11.0",
+ "version": "1.11.1",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -46,8 +46,8 @@
"@amplitude/analytics-client-common": "^1.0.2",
"@amplitude/analytics-core": "^1.1.1",
"@amplitude/analytics-types": "^1.2.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.3",
- "@amplitude/plugin-web-attribution-browser": "^1.0.3",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.4",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.4",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 7f02b4256..057e50752 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[A,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",A="Zebra",D="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;ie.sessionTimeout&&(t.setSessionId(n),i.session_id=t.getSessionId(),i.time=n),e.lastEventTime=n,[2,i])}))}))}}},yt={page_domain:"".concat(it," Page Domain"),page_location:"".concat(it," Page Location"),page_path:"".concat(it," Page Path"),page_title:"".concat(it," Page Title"),page_url:"".concat(it," Page URL")},mt=function(e){function n(){return null!==e&&e.apply(this,arguments)||this}return t(n,e),n.prototype.init=function(e,t,n){return void 0===e&&(e=""),$(this._init(i(i({},n),{userId:t,apiKey:e})))},n.prototype._init=function(t){var n,s,u,c,l,d,p,v,h,g,b,m,w,_,I,k,E,S;return r(this,void 0,void 0,(function(){var T,O,x,P,R,N,q,A,D,C,j,L,M,V,z,F,B,$,Q=this;return o(this,(function(K){switch(K.label){case 0:return this.initializing?[2]:(this.initializing=!0,T=t,t.disableCookies?(O="",[3,5]):[3,1]);case 1:return null===(n=t.domain)||void 0===n?[3,2]:(x=n,[3,4]);case 2:return[4,r(void 0,void 0,void 0,(function(){var e,t,i,n,r,s,a;return o(this,(function(o){switch(o.label){case 0:return[4,(new pe).isEnabled()];case 1:if(!o.sent()||!W&&"undefined"==typeof location)return[2,""];for(e=null!=W?W:location.hostname,t=e.split("."),i=[],n="AMP_TLDTEST",r=t.length-2;r>=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(Date.now()),z=!0),(F=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return K.sent(),[4,this.add(new qe).promise];case 12:return K.sent(),[4,this.add(bt()).promise];case 13:return K.sent(),[4,this.add(new xe).promise];case 14:return K.sent(),("boolean"==typeof(H=this.config.defaultTracking)?H:null==H?void 0:H.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,16];case 15:K.sent(),K.label=16;case 16:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,18];case 17:K.sent(),K.label=18;case 18:return(null===(_=this.config.attribution)||void 0===_?void 0:_.disabled)?[3,20]:(B=function(){for(var e,t,n=this,s=[],u=0;u0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",D="Zebra",A="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;i=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return W.sent(),[4,this.add(new qe).promise];case 12:return W.sent(),[4,this.add(new xe).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(k=this.config.attribution)||void 0===k?void 0:k.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:ae(e.init.bind(e),"init",re(e),se(e,["config"])),add:ae(e.add.bind(e),"add",re(e),se(e,["config.apiKey","timeline.plugins"])),remove:ae(e.remove.bind(e),"remove",re(e),se(e,["config.apiKey","timeline.plugins"])),track:ae(e.track.bind(e),"track",re(e),se(e,["config.apiKey","timeline.queue.length"])),logEvent:ae(e.logEvent.bind(e),"logEvent",re(e),se(e,["config.apiKey","timeline.queue.length"])),identify:ae(e.identify.bind(e),"identify",re(e),se(e,["config.apiKey","timeline.queue.length"])),groupIdentify:ae(e.groupIdentify.bind(e),"groupIdentify",re(e),se(e,["config.apiKey","timeline.queue.length"])),setGroup:ae(e.setGroup.bind(e),"setGroup",re(e),se(e,["config.apiKey","timeline.queue.length"])),revenue:ae(e.revenue.bind(e),"revenue",re(e),se(e,["config.apiKey","timeline.queue.length"])),flush:ae(e.flush.bind(e),"flush",re(e),se(e,["config.apiKey","timeline.queue.length"])),getUserId:ae(e.getUserId.bind(e),"getUserId",re(e),se(e,["config","config.userId"])),setUserId:ae(e.setUserId.bind(e),"setUserId",re(e),se(e,["config","config.userId"])),getDeviceId:ae(e.getDeviceId.bind(e),"getDeviceId",re(e),se(e,["config","config.deviceId"])),setDeviceId:ae(e.setDeviceId.bind(e),"setDeviceId",re(e),se(e,["config","config.deviceId"])),reset:ae(e.reset.bind(e),"reset",re(e),se(e,["config","config.userId","config.deviceId"])),getSessionId:ae(e.getSessionId.bind(e),"getSessionId",re(e),se(e,["config"])),setSessionId:ae(e.setSessionId.bind(e),"setSessionId",re(e),se(e,["config"])),extendSession:ae(e.extendSession.bind(e),"extendSession",re(e),se(e,["config"])),setOptOut:ae(e.setOptOut.bind(e),"setOptOut",re(e),se(e,["config"])),setTransport:ae(e.setTransport.bind(e),"setTransport",re(e),se(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,kt=wt.flush,Et=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,Ut=wt.reset,qt=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:kt,getDeviceId:Et,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:Ut,revenue:qt,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:q,createInstance:mt,runQueuedFunctions:Re,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Re(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index f11bba734..c0a12184b 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-dqd1zhQwCaPNwnnxfckFnnSnDdhem8MpuxJOSB6Yvxc1uRXwkeyIsPXpUnbBIu4u';
+ as.integrity = 'sha384-/dfCBvYaIDAyRKZDCpZNiEv+OiiACVEqHLTnwo/90WCzmEptJAIC5hOf2ebqCyBY';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.3-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.4-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index d416f439b..246f31480 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-Bnx/ydv/ChaWFeFAQkQJzhK20h7Daqih3HYpGGtC0BFpXuhOLf794kHvO2p7YuZW';
+ as.integrity = 'sha384-PsQ24m4bUh9DzHyRSZbvWu1el7OYz39YDvB+JssWCe3O/9X0TmapEHWZBvi+HMqo';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.3-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.4-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index b240dfe79..1639b3d6e 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.3",
+ "version": "1.0.4",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -42,12 +42,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.11.0",
+ "@amplitude/analytics-browser": "^1.11.1",
"@amplitude/analytics-client-common": "^1.0.2",
"@amplitude/analytics-core": "^1.1.1",
"@amplitude/analytics-types": "^1.2.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.3",
- "@amplitude/plugin-web-attribution-browser": "^1.0.3",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.4",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.4",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index 30cbd1ac5..be9db6827 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.3';
+export const VERSION = '1.0.4';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index a7c738e6f..7118ce6ae 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.3...@amplitude/plugin-page-view-tracking-browser@1.0.4) (2023-06-14)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.2...@amplitude/plugin-page-view-tracking-browser@1.0.3) (2023-06-13)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index 3c9ab818f..ed7548f7e 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.3",
+ "version": "1.0.4",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -41,7 +41,7 @@
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.11.0",
+ "@amplitude/analytics-browser": "^1.11.1",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index 046ebe285..bba49a28f 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.3...@amplitude/plugin-web-attribution-browser@1.0.4) (2023-06-14)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.2...@amplitude/plugin-web-attribution-browser@1.0.3) (2023-06-13)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index a4e4358ea..81adaf24a 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.3",
+ "version": "1.0.4",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -41,7 +41,7 @@
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.11.0",
+ "@amplitude/analytics-browser": "^1.11.1",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From 9f0a180407168a1bb36779d56a816f99b181c20d Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Tue, 13 Jun 2023 21:03:15 -0700
Subject: [PATCH 017/214] build: update dist-tag (#418)
---
packages/analytics-browser/package.json | 3 ++-
packages/analytics-client-common/package.json | 3 ++-
packages/analytics-core/package.json | 3 ++-
packages/analytics-node/package.json | 3 ++-
packages/analytics-react-native/package.json | 3 ++-
packages/analytics-types/package.json | 3 ++-
packages/marketing-analytics-browser/package.json | 3 ++-
packages/plugin-page-view-tracking-browser/package.json | 3 ++-
packages/plugin-web-attribution-browser/package.json | 3 ++-
9 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 462ed8a8c..dcacb16a0 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -14,7 +14,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "v1"
},
"repository": {
"type": "git",
diff --git a/packages/analytics-client-common/package.json b/packages/analytics-client-common/package.json
index 56f3cd90c..8d9385f15 100644
--- a/packages/analytics-client-common/package.json
+++ b/packages/analytics-client-common/package.json
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "v1"
},
"repository": {
"type": "git",
diff --git a/packages/analytics-core/package.json b/packages/analytics-core/package.json
index 273fdb53a..6e64e5dc2 100644
--- a/packages/analytics-core/package.json
+++ b/packages/analytics-core/package.json
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "v1"
},
"repository": {
"type": "git",
diff --git a/packages/analytics-node/package.json b/packages/analytics-node/package.json
index e5a9b5938..fe949f843 100644
--- a/packages/analytics-node/package.json
+++ b/packages/analytics-node/package.json
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "latest"
},
"repository": {
"type": "git",
diff --git a/packages/analytics-react-native/package.json b/packages/analytics-react-native/package.json
index 9076767de..99e00b055 100644
--- a/packages/analytics-react-native/package.json
+++ b/packages/analytics-react-native/package.json
@@ -17,7 +17,8 @@
"types": "lib/typescript/index.d.ts",
"react-native": "src/index",
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "latest"
},
"repository": {
"type": "git",
diff --git a/packages/analytics-types/package.json b/packages/analytics-types/package.json
index c8327869e..39405d163 100644
--- a/packages/analytics-types/package.json
+++ b/packages/analytics-types/package.json
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "v1"
},
"repository": {
"type": "git",
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index 1639b3d6e..4e9f68a3e 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -14,7 +14,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "latest"
},
"repository": {
"type": "git",
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index ed7548f7e..245d00668 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "v1"
},
"repository": {
"type": "git",
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index 81adaf24a..f60c87a8f 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "v1"
},
"repository": {
"type": "git",
From bab630dec042d01eb703bd92530e632052f2fe33 Mon Sep 17 00:00:00 2001
From: Justin Fiedler
Date: Wed, 14 Jun 2023 08:12:45 -0700
Subject: [PATCH 018/214] fix: compact logged response body's for AMP-77261
(#430)
---
packages/analytics-core/src/plugins/destination.ts | 2 +-
packages/analytics-core/test/plugins/destination.test.ts | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/packages/analytics-core/src/plugins/destination.ts b/packages/analytics-core/src/plugins/destination.ts
index 602bf1c35..76d74a7fc 100644
--- a/packages/analytics-core/src/plugins/destination.ts
+++ b/packages/analytics-core/src/plugins/destination.ts
@@ -33,7 +33,7 @@ export function getResponseBodyString(res: Response) {
let responseBodyString = '';
try {
if ('body' in res) {
- responseBodyString = JSON.stringify(res.body, null, 2);
+ responseBodyString = JSON.stringify(res.body);
}
} catch {
// to avoid crash, but don't care about the error, add comment to avoid empty block lint error
diff --git a/packages/analytics-core/test/plugins/destination.test.ts b/packages/analytics-core/test/plugins/destination.test.ts
index 41345a513..8b8562d19 100644
--- a/packages/analytics-core/test/plugins/destination.test.ts
+++ b/packages/analytics-core/test/plugins/destination.test.ts
@@ -8,8 +8,6 @@ import {
UNEXPECTED_ERROR_MESSAGE,
} from '../../src/messages';
-const jsons = (obj: any) => JSON.stringify(obj, null, 2);
-
describe('destination', () => {
describe('setup', () => {
test('should setup plugin', async () => {
@@ -375,7 +373,7 @@ describe('destination', () => {
expect(callback).toHaveBeenCalledWith({
event,
code: 400,
- message: `${Status.Invalid}: ${jsons(body)}`,
+ message: `${Status.Invalid}: ${JSON.stringify(body)}`,
});
});
@@ -989,7 +987,7 @@ describe('destination', () => {
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(loggerProvider.warn).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/unbound-method,@typescript-eslint/restrict-template-expressions
- expect(loggerProvider.warn).toHaveBeenCalledWith(jsons(response.body));
+ expect(loggerProvider.warn).toHaveBeenCalledWith(JSON.stringify(response.body));
},
);
From 301ba1e94d67ead341bea225189d7794acdd6922 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 14 Jun 2023 17:15:55 +0000
Subject: [PATCH 019/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.5
- @amplitude/analytics-browser@1.11.2
- @amplitude/analytics-client-common@1.0.3
- @amplitude/analytics-core@1.1.2
- @amplitude/analytics-node-test@1.0.3
- @amplitude/analytics-node@1.2.2
- @amplitude/analytics-react-native@1.2.2
- @amplitude/analytics-types@1.2.1
- @amplitude/marketing-analytics-browser@1.0.5
- @amplitude/plugin-page-view-tracking-browser@1.0.5
- @amplitude/plugin-web-attribution-browser@1.0.5
---
packages/analytics-browser-test/CHANGELOG.md | 9 +++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 9 +++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 12 ++++++------
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 9 +++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 12 ++++++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 +++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 9 +++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 9 +++++++++
packages/analytics-react-native/package.json | 8 ++++----
packages/analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 9 +++++++++
packages/analytics-types/package.json | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 14 +++++++-------
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 9 +++++++++
.../plugin-page-view-tracking-browser/package.json | 8 ++++----
.../plugin-web-attribution-browser/CHANGELOG.md | 9 +++++++++
.../plugin-web-attribution-browser/package.json | 8 ++++----
32 files changed, 153 insertions(+), 51 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index ce25fbe9e..754f26251 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.4...@amplitude/analytics-browser-test@1.4.5) (2023-06-14)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.4.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.3...@amplitude/analytics-browser-test@1.4.4) (2023-06-14)
### Bug Fixes
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index 413c6b862..914059eaf 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.4",
+ "version": "1.4.5",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.11.1"
+ "@amplitude/analytics-browser": "^1.11.2"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 554ee24b8..646aea545 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.11.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.11.1...@amplitude/analytics-browser@1.11.2) (2023-06-14)
+
+**Note:** Version bump only for package @amplitude/analytics-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.11.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.11.0...@amplitude/analytics-browser@1.11.1) (2023-06-14)
### Bug Fixes
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index c248d306c..7332fe927 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index ac7bb52b9..c3d50c9ef 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-So8wDvkfueWi1vtYoaT+2UMVdV5gytnrAUr8sumYyMQo4TNK4Lpct6VewTWsSNoH';
+ as.integrity = 'sha384-bhExRHNdxBGFQ+AkmAurYWjqboez+iJTTaWsIubqgMeSgQZqjXMa8BI8890ijsCf';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.11.1-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.11.2-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index dcacb16a0..388ae14e8 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.11.1",
+ "version": "1.11.2",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -44,11 +44,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.2",
- "@amplitude/analytics-core": "^1.1.1",
- "@amplitude/analytics-types": "^1.2.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.4",
- "@amplitude/plugin-web-attribution-browser": "^1.0.4",
+ "@amplitude/analytics-client-common": "^1.0.3",
+ "@amplitude/analytics-core": "^1.1.2",
+ "@amplitude/analytics-types": "^1.2.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.5",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.5",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 057e50752..9d8797e08 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",D="Zebra",A="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;i=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return W.sent(),[4,this.add(new qe).promise];case 12:return W.sent(),[4,this.add(new xe).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(k=this.config.attribution)||void 0===k?void 0:k.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:ae(e.init.bind(e),"init",re(e),se(e,["config"])),add:ae(e.add.bind(e),"add",re(e),se(e,["config.apiKey","timeline.plugins"])),remove:ae(e.remove.bind(e),"remove",re(e),se(e,["config.apiKey","timeline.plugins"])),track:ae(e.track.bind(e),"track",re(e),se(e,["config.apiKey","timeline.queue.length"])),logEvent:ae(e.logEvent.bind(e),"logEvent",re(e),se(e,["config.apiKey","timeline.queue.length"])),identify:ae(e.identify.bind(e),"identify",re(e),se(e,["config.apiKey","timeline.queue.length"])),groupIdentify:ae(e.groupIdentify.bind(e),"groupIdentify",re(e),se(e,["config.apiKey","timeline.queue.length"])),setGroup:ae(e.setGroup.bind(e),"setGroup",re(e),se(e,["config.apiKey","timeline.queue.length"])),revenue:ae(e.revenue.bind(e),"revenue",re(e),se(e,["config.apiKey","timeline.queue.length"])),flush:ae(e.flush.bind(e),"flush",re(e),se(e,["config.apiKey","timeline.queue.length"])),getUserId:ae(e.getUserId.bind(e),"getUserId",re(e),se(e,["config","config.userId"])),setUserId:ae(e.setUserId.bind(e),"setUserId",re(e),se(e,["config","config.userId"])),getDeviceId:ae(e.getDeviceId.bind(e),"getDeviceId",re(e),se(e,["config","config.deviceId"])),setDeviceId:ae(e.setDeviceId.bind(e),"setDeviceId",re(e),se(e,["config","config.deviceId"])),reset:ae(e.reset.bind(e),"reset",re(e),se(e,["config","config.userId","config.deviceId"])),getSessionId:ae(e.getSessionId.bind(e),"getSessionId",re(e),se(e,["config"])),setSessionId:ae(e.setSessionId.bind(e),"setSessionId",re(e),se(e,["config"])),extendSession:ae(e.extendSession.bind(e),"extendSession",re(e),se(e,["config"])),setOptOut:ae(e.setOptOut.bind(e),"setOptOut",re(e),se(e,["config"])),setTransport:ae(e.setTransport.bind(e),"setTransport",re(e),se(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,kt=wt.flush,Et=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,Ut=wt.reset,qt=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:kt,getDeviceId:Et,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:Ut,revenue:qt,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:q,createInstance:mt,runQueuedFunctions:Re,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Re(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",D="Zebra",A="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;i=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return W.sent(),[4,this.add(new qe).promise];case 12:return W.sent(),[4,this.add(new xe).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(k=this.config.attribution)||void 0===k?void 0:k.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:ae(e.init.bind(e),"init",re(e),se(e,["config"])),add:ae(e.add.bind(e),"add",re(e),se(e,["config.apiKey","timeline.plugins"])),remove:ae(e.remove.bind(e),"remove",re(e),se(e,["config.apiKey","timeline.plugins"])),track:ae(e.track.bind(e),"track",re(e),se(e,["config.apiKey","timeline.queue.length"])),logEvent:ae(e.logEvent.bind(e),"logEvent",re(e),se(e,["config.apiKey","timeline.queue.length"])),identify:ae(e.identify.bind(e),"identify",re(e),se(e,["config.apiKey","timeline.queue.length"])),groupIdentify:ae(e.groupIdentify.bind(e),"groupIdentify",re(e),se(e,["config.apiKey","timeline.queue.length"])),setGroup:ae(e.setGroup.bind(e),"setGroup",re(e),se(e,["config.apiKey","timeline.queue.length"])),revenue:ae(e.revenue.bind(e),"revenue",re(e),se(e,["config.apiKey","timeline.queue.length"])),flush:ae(e.flush.bind(e),"flush",re(e),se(e,["config.apiKey","timeline.queue.length"])),getUserId:ae(e.getUserId.bind(e),"getUserId",re(e),se(e,["config","config.userId"])),setUserId:ae(e.setUserId.bind(e),"setUserId",re(e),se(e,["config","config.userId"])),getDeviceId:ae(e.getDeviceId.bind(e),"getDeviceId",re(e),se(e,["config","config.deviceId"])),setDeviceId:ae(e.setDeviceId.bind(e),"setDeviceId",re(e),se(e,["config","config.deviceId"])),reset:ae(e.reset.bind(e),"reset",re(e),se(e,["config","config.userId","config.deviceId"])),getSessionId:ae(e.getSessionId.bind(e),"getSessionId",re(e),se(e,["config"])),setSessionId:ae(e.setSessionId.bind(e),"setSessionId",re(e),se(e,["config"])),extendSession:ae(e.extendSession.bind(e),"extendSession",re(e),se(e,["config"])),setOptOut:ae(e.setOptOut.bind(e),"setOptOut",re(e),se(e,["config"])),setTransport:ae(e.setTransport.bind(e),"setTransport",re(e),se(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,kt=wt.flush,Et=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,Ut=wt.reset,qt=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:kt,getDeviceId:Et,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:Ut,revenue:qt,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:q,createInstance:mt,runQueuedFunctions:Re,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Re(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index c0a12184b..8be97ab3d 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-/dfCBvYaIDAyRKZDCpZNiEv+OiiACVEqHLTnwo/90WCzmEptJAIC5hOf2ebqCyBY';
+ as.integrity = 'sha384-GCneBnzsMTw+kmn6TwyFynia1fmqj+38TkwyYEyW8AkwF+Qw8IAh1CjqSgDYZ9X+';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.4-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.5-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index 246f31480..d4784919c 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-PsQ24m4bUh9DzHyRSZbvWu1el7OYz39YDvB+JssWCe3O/9X0TmapEHWZBvi+HMqo';
+ as.integrity = 'sha384-ocQG0VTdcJGeNGgEnHLUASJ+2qacfMa5iU5EsLRXLdGs/g2XAPDUI4zdjSMlhO5U';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.4-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.5-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index 4e9f68a3e..4d151e352 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.4",
+ "version": "1.0.5",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -43,12 +43,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.11.1",
- "@amplitude/analytics-client-common": "^1.0.2",
- "@amplitude/analytics-core": "^1.1.1",
- "@amplitude/analytics-types": "^1.2.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.4",
- "@amplitude/plugin-web-attribution-browser": "^1.0.4",
+ "@amplitude/analytics-browser": "^1.11.2",
+ "@amplitude/analytics-client-common": "^1.0.3",
+ "@amplitude/analytics-core": "^1.1.2",
+ "@amplitude/analytics-types": "^1.2.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.5",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.5",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index be9db6827..6cec2cb92 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.4';
+export const VERSION = '1.0.5';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 7118ce6ae..48be54e1c 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.4...@amplitude/plugin-page-view-tracking-browser@1.0.5) (2023-06-14)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.3...@amplitude/plugin-page-view-tracking-browser@1.0.4) (2023-06-14)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index 245d00668..d77195f30 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.4",
+ "version": "1.0.5",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,12 +37,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.2",
- "@amplitude/analytics-types": "^1.2.0",
+ "@amplitude/analytics-client-common": "^1.0.3",
+ "@amplitude/analytics-types": "^1.2.1",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.11.1",
+ "@amplitude/analytics-browser": "^1.11.2",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index bba49a28f..909d34853 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.4...@amplitude/plugin-web-attribution-browser@1.0.5) (2023-06-14)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.3...@amplitude/plugin-web-attribution-browser@1.0.4) (2023-06-14)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index f60c87a8f..3a1667300 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.4",
+ "version": "1.0.5",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,12 +37,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.2",
- "@amplitude/analytics-types": "^1.2.0",
+ "@amplitude/analytics-client-common": "^1.0.3",
+ "@amplitude/analytics-types": "^1.2.1",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.11.1",
+ "@amplitude/analytics-browser": "^1.11.2",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From 569464c698eb54b3da05e203ac90cf1a399d96ed Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Fri, 16 Jun 2023 14:51:56 -0700
Subject: [PATCH 020/214] feat: add option for instance name (#428) (#442)
---
packages/analytics-browser/src/browser-client.ts | 6 +++---
packages/analytics-browser/test/config.test.ts | 3 +++
.../src/analytics-connector.ts | 12 ++++++------
.../analytics-client-common/src/plugins/identity.ts | 6 ++++--
.../test/plugins/identity.test.ts | 13 +++++++++----
packages/analytics-core/src/config.ts | 3 +++
packages/analytics-core/test/config.test.ts | 2 ++
packages/analytics-node/test/config.test.ts | 2 ++
packages/analytics-react-native/test/config.test.ts | 3 +++
packages/analytics-types/src/config/core.ts | 1 +
10 files changed, 36 insertions(+), 15 deletions(-)
diff --git a/packages/analytics-browser/src/browser-client.ts b/packages/analytics-browser/src/browser-client.ts
index 4215cd50d..dcb99ddaa 100644
--- a/packages/analytics-browser/src/browser-client.ts
+++ b/packages/analytics-browser/src/browser-client.ts
@@ -98,7 +98,7 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
// Set up the analytics connector to integrate with the experiment SDK.
// Send events from the experiment SDK and forward identifies to the
// identity store.
- const connector = getAnalyticsConnector();
+ const connector = getAnalyticsConnector(options.instanceName);
connector.identityStore.setIdentity({
userId: this.config.userId,
deviceId: this.config.deviceId,
@@ -164,7 +164,7 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
}
if (userId !== this.config.userId || userId === undefined) {
this.config.userId = userId;
- setConnectorUserId(userId);
+ setConnectorUserId(userId, this.config.instanceName);
}
}
@@ -178,7 +178,7 @@ export class AmplitudeBrowser extends AmplitudeCore implements BrowserClient {
return;
}
this.config.deviceId = deviceId;
- setConnectorDeviceId(deviceId);
+ setConnectorDeviceId(deviceId, this.config.instanceName);
}
reset() {
diff --git a/packages/analytics-browser/test/config.test.ts b/packages/analytics-browser/test/config.test.ts
index ab583cbf2..71244c9bb 100644
--- a/packages/analytics-browser/test/config.test.ts
+++ b/packages/analytics-browser/test/config.test.ts
@@ -37,6 +37,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ instanceName: '$default_instance',
loggerProvider: logger,
logLevel: LogLevel.Warn,
minIdLength: undefined,
@@ -83,6 +84,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ instanceName: '$default_instance',
loggerProvider: logger,
logLevel: LogLevel.Warn,
minIdLength: undefined,
@@ -153,6 +155,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ instanceName: '$default_instance',
_lastEventId: 100,
_lastEventTime: 1,
loggerProvider: logger,
diff --git a/packages/analytics-client-common/src/analytics-connector.ts b/packages/analytics-client-common/src/analytics-connector.ts
index a2766d902..4d2fb87bb 100644
--- a/packages/analytics-client-common/src/analytics-connector.ts
+++ b/packages/analytics-client-common/src/analytics-connector.ts
@@ -1,15 +1,15 @@
import { AnalyticsConnector } from '@amplitude/analytics-connector';
-export const getAnalyticsConnector = (): AnalyticsConnector => {
- return AnalyticsConnector.getInstance('$default_instance');
+export const getAnalyticsConnector = (instanceName = '$default_instance'): AnalyticsConnector => {
+ return AnalyticsConnector.getInstance(instanceName);
};
-export const setConnectorUserId = (userId: string | undefined): void => {
+export const setConnectorUserId = (userId: string | undefined, instanceName?: string): void => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
- getAnalyticsConnector().identityStore.editIdentity().setUserId(userId).commit();
+ getAnalyticsConnector(instanceName).identityStore.editIdentity().setUserId(userId).commit();
};
-export const setConnectorDeviceId = (deviceId: string): void => {
- getAnalyticsConnector().identityStore.editIdentity().setDeviceId(deviceId).commit();
+export const setConnectorDeviceId = (deviceId: string, instanceName?: string): void => {
+ getAnalyticsConnector(instanceName).identityStore.editIdentity().setDeviceId(deviceId).commit();
};
diff --git a/packages/analytics-client-common/src/plugins/identity.ts b/packages/analytics-client-common/src/plugins/identity.ts
index 14a58a91c..3104047a2 100644
--- a/packages/analytics-client-common/src/plugins/identity.ts
+++ b/packages/analytics-client-common/src/plugins/identity.ts
@@ -15,7 +15,9 @@ export class IdentityEventSender implements BeforePlugin {
return context;
}
- setup(_: Config): Promise {
- return Promise.resolve(undefined);
+ async setup(config: Config) {
+ if (config.instanceName) {
+ this.identityStore = getAnalyticsConnector(config.instanceName).identityStore;
+ }
}
}
diff --git a/packages/analytics-client-common/test/plugins/identity.test.ts b/packages/analytics-client-common/test/plugins/identity.test.ts
index 8cc07b3aa..1e005d860 100644
--- a/packages/analytics-client-common/test/plugins/identity.test.ts
+++ b/packages/analytics-client-common/test/plugins/identity.test.ts
@@ -8,7 +8,7 @@ describe('identity', () => {
getAnalyticsConnector().identityStore.setIdentity({ userProperties: {} });
});
- test('should set identity in analytics connector on identify', async () => {
+ test('should set identity in analytics connector on identify with default instance', async () => {
const plugin = new IdentityEventSender();
await plugin.setup({} as Config);
const event = {
@@ -17,14 +17,17 @@ describe('identity', () => {
$set: { k: 'v' },
},
};
- void (await plugin.execute(event));
+ const result = await plugin.execute(event);
const identity = getAnalyticsConnector().identityStore.getIdentity();
+ expect(result).toEqual(event);
expect(identity.userProperties).toEqual({ k: 'v' });
});
- test('should not modify event on identify', async () => {
+ test('should set identity in analytics connector on identify with instance name', async () => {
const plugin = new IdentityEventSender();
- await plugin.setup({} as Config);
+ await plugin.setup({
+ instanceName: 'env',
+ } as Config);
const event = {
event_type: '$identify',
user_properties: {
@@ -32,7 +35,9 @@ describe('identity', () => {
},
};
const result = await plugin.execute(event);
+ const identity = getAnalyticsConnector('env').identityStore.getIdentity();
expect(result).toEqual(event);
+ expect(identity.userProperties).toEqual({ k: 'v' });
});
test('should do nothing on track event', async () => {
diff --git a/packages/analytics-core/src/config.ts b/packages/analytics-core/src/config.ts
index e2a3e86d0..b77aaa1f3 100644
--- a/packages/analytics-core/src/config.ts
+++ b/packages/analytics-core/src/config.ts
@@ -23,6 +23,7 @@ export const getDefaultConfig = () => ({
flushMaxRetries: 12,
flushQueueSize: 200,
flushIntervalMillis: 10000,
+ instanceName: '$default_instance',
logLevel: LogLevel.Warn,
loggerProvider: new Logger(),
optOut: false,
@@ -36,6 +37,7 @@ export class Config implements IConfig {
flushIntervalMillis: number;
flushMaxRetries: number;
flushQueueSize: number;
+ instanceName?: string;
loggerProvider: ILogger;
logLevel: LogLevel;
minIdLength?: number;
@@ -61,6 +63,7 @@ export class Config implements IConfig {
this.flushIntervalMillis = options.flushIntervalMillis ?? defaultConfig.flushIntervalMillis;
this.flushMaxRetries = options.flushMaxRetries || defaultConfig.flushMaxRetries;
this.flushQueueSize = options.flushQueueSize || defaultConfig.flushQueueSize;
+ this.instanceName = options.instanceName || defaultConfig.instanceName;
this.loggerProvider = options.loggerProvider || defaultConfig.loggerProvider;
this.logLevel = options.logLevel ?? defaultConfig.logLevel;
this.minIdLength = options.minIdLength;
diff --git a/packages/analytics-core/test/config.test.ts b/packages/analytics-core/test/config.test.ts
index 8becab36f..6903b27dd 100644
--- a/packages/analytics-core/test/config.test.ts
+++ b/packages/analytics-core/test/config.test.ts
@@ -22,6 +22,7 @@ describe('config', () => {
flushIntervalMillis: 10000,
flushMaxRetries: 12,
flushQueueSize: 200,
+ instanceName: '$default_instance',
logLevel: LogLevel.Warn,
loggerProvider: new Logger(),
minIdLength: undefined,
@@ -58,6 +59,7 @@ describe('config', () => {
flushIntervalMillis: 10000,
flushMaxRetries: 12,
flushQueueSize: 200,
+ instanceName: '$default_instance',
logLevel: LogLevel.Verbose,
loggerProvider: new Logger(),
minIdLength: undefined,
diff --git a/packages/analytics-node/test/config.test.ts b/packages/analytics-node/test/config.test.ts
index e755ab424..ebe0e65f5 100644
--- a/packages/analytics-node/test/config.test.ts
+++ b/packages/analytics-node/test/config.test.ts
@@ -16,6 +16,7 @@ describe('config', () => {
flushIntervalMillis: 10000,
flushMaxRetries: 12,
flushQueueSize: 200,
+ instanceName: '$default_instance',
loggerProvider: logger,
logLevel: LogLevel.Warn,
_optOut: false,
@@ -41,6 +42,7 @@ describe('config', () => {
flushIntervalMillis: 10000,
flushMaxRetries: 12,
flushQueueSize: 200,
+ instanceName: '$default_instance',
loggerProvider: logger,
logLevel: LogLevel.Warn,
_optOut: false,
diff --git a/packages/analytics-react-native/test/config.test.ts b/packages/analytics-react-native/test/config.test.ts
index e939b82f4..458ba04d6 100644
--- a/packages/analytics-react-native/test/config.test.ts
+++ b/packages/analytics-react-native/test/config.test.ts
@@ -34,6 +34,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ instanceName: '$default_instance',
loggerProvider: logger,
logLevel: LogLevel.Warn,
minIdLength: undefined,
@@ -83,6 +84,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ instanceName: '$default_instance',
loggerProvider: logger,
logLevel: LogLevel.Warn,
minIdLength: undefined,
@@ -152,6 +154,7 @@ describe('config', () => {
flushIntervalMillis: 1000,
flushMaxRetries: 5,
flushQueueSize: 30,
+ instanceName: '$default_instance',
_lastEventId: 2,
_lastEventTime: 1,
loggerProvider: logger,
diff --git a/packages/analytics-types/src/config/core.ts b/packages/analytics-types/src/config/core.ts
index 8e4171f47..20486b20c 100644
--- a/packages/analytics-types/src/config/core.ts
+++ b/packages/analytics-types/src/config/core.ts
@@ -11,6 +11,7 @@ export interface Config {
flushIntervalMillis: number;
flushMaxRetries: number;
flushQueueSize: number;
+ instanceName?: string;
logLevel: LogLevel;
loggerProvider: Logger;
minIdLength?: number;
From b31226dc0e10641a47511b619961f190e096517a Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Fri, 16 Jun 2023 14:57:03 -0700
Subject: [PATCH 021/214] fix: types for plugins to be env specific (#440)
(#441)
---
.../analytics-types/src/client/node-client.ts | 22 +++++++++-
.../analytics-types/src/client/web-client.ts | 41 ++++++++++++++++++-
packages/analytics-types/src/plugin.ts | 14 +++----
3 files changed, 68 insertions(+), 9 deletions(-)
diff --git a/packages/analytics-types/src/client/node-client.ts b/packages/analytics-types/src/client/node-client.ts
index 22fc39bb6..a1b3d6899 100644
--- a/packages/analytics-types/src/client/node-client.ts
+++ b/packages/analytics-types/src/client/node-client.ts
@@ -1,6 +1,7 @@
import { AmplitudeReturn } from '../amplitude-promise';
-import { NodeOptions } from '../config';
+import { NodeConfig, NodeOptions } from '../config';
import { CoreClient } from './core-client';
+import { Plugin } from '../plugin';
export interface NodeClient extends CoreClient {
/**
@@ -12,4 +13,23 @@ export interface NodeClient extends CoreClient {
* ```
*/
init(apiKey: string, options?: NodeOptions): AmplitudeReturn;
+
+ /**
+ * Adds a new plugin.
+ *
+ * ```typescript
+ * const plugin = {
+ * name: 'my-plugin',
+ * type: 'enrichment',
+ * async setup(config: NodeConfig, amplitude: NodeClient) {
+ * return;
+ * },
+ * async execute(event: Event) {
+ * return event;
+ * },
+ * };
+ * amplitude.add(plugin);
+ * ```
+ */
+ add(plugin: Plugin): AmplitudeReturn;
}
diff --git a/packages/analytics-types/src/client/web-client.ts b/packages/analytics-types/src/client/web-client.ts
index 731678bf0..385a6c13d 100644
--- a/packages/analytics-types/src/client/web-client.ts
+++ b/packages/analytics-types/src/client/web-client.ts
@@ -1,7 +1,8 @@
import { AmplitudeReturn } from '../amplitude-promise';
-import { BrowserOptions, ReactNativeOptions } from '../config';
+import { BrowserConfig, BrowserOptions, ReactNativeConfig, ReactNativeOptions } from '../config';
import { TransportType } from '../transport';
import { CoreClient } from './core-client';
+import { Plugin } from '../plugin';
interface Client extends CoreClient {
/**
@@ -116,6 +117,25 @@ export interface BrowserClient extends Client {
* ```
*/
setTransport(transport: TransportType): void;
+
+ /**
+ * Adds a new plugin.
+ *
+ * ```typescript
+ * const plugin = {
+ * name: 'my-plugin',
+ * type: 'enrichment',
+ * async setup(config: BrowserConfig, amplitude: BrowserClient) {
+ * return;
+ * },
+ * async execute(event: Event) {
+ * return event;
+ * },
+ * };
+ * amplitude.add(plugin);
+ * ```
+ */
+ add(plugin: Plugin): AmplitudeReturn;
}
export interface ReactNativeClient extends Client {
@@ -128,4 +148,23 @@ export interface ReactNativeClient extends Client {
* ```
*/
init(apiKey: string, userId?: string, options?: ReactNativeOptions): AmplitudeReturn;
+
+ /**
+ * Adds a new plugin.
+ *
+ * ```typescript
+ * const plugin = {
+ * name: 'my-plugin',
+ * type: 'enrichment',
+ * async setup(config: ReactNativeConfig, amplitude: ReactNativeClient) {
+ * return;
+ * },
+ * async execute(event: Event) {
+ * return event;
+ * },
+ * };
+ * amplitude.add(plugin);
+ * ```
+ */
+ add(plugin: Plugin): AmplitudeReturn;
}
diff --git a/packages/analytics-types/src/plugin.ts b/packages/analytics-types/src/plugin.ts
index c1e58f5e0..b40801223 100644
--- a/packages/analytics-types/src/plugin.ts
+++ b/packages/analytics-types/src/plugin.ts
@@ -9,26 +9,26 @@ export enum PluginType {
DESTINATION = 'destination',
}
-export interface BeforePlugin {
+export interface BeforePlugin {
name: string;
type: PluginType.BEFORE;
- setup(config: Config, client?: T): Promise;
+ setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
}
-export interface EnrichmentPlugin {
+export interface EnrichmentPlugin {
name: string;
type: PluginType.ENRICHMENT;
- setup(config: Config, client?: T): Promise;
+ setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
}
-export interface DestinationPlugin {
+export interface DestinationPlugin {
name: string;
type: PluginType.DESTINATION;
- setup(config: Config, client?: T): Promise;
+ setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
flush?(): Promise;
}
-export type Plugin = BeforePlugin | EnrichmentPlugin | DestinationPlugin;
+export type Plugin = BeforePlugin | EnrichmentPlugin | DestinationPlugin;
From ed299baca27a7eaa00f0dbc50b4e82d86aea51d9 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 30 May 2023 12:37:14 -0400
Subject: [PATCH 022/214] feat(plugins): mvp of session replay plugin
---
packages/plugin-session-replay/CHANGELOG.md | 363 ++++++++++++++++++
packages/plugin-session-replay/README.md | 100 +++++
packages/plugin-session-replay/jest.config.js | 10 +
packages/plugin-session-replay/package.json | 57 +++
.../plugin-session-replay/rollup.config.js | 6 +
packages/plugin-session-replay/src/helpers.ts | 0
packages/plugin-session-replay/src/index.ts | 2 +
.../src/session-replay.ts | 71 ++++
.../src/typings/session-replay.ts | 8 +
.../plugin-session-replay/tsconfig.es5.json | 10 +
.../plugin-session-replay/tsconfig.esm.json | 10 +
packages/plugin-session-replay/tsconfig.json | 11 +
yarn.lock | 14 +-
13 files changed, 654 insertions(+), 8 deletions(-)
create mode 100644 packages/plugin-session-replay/CHANGELOG.md
create mode 100644 packages/plugin-session-replay/README.md
create mode 100644 packages/plugin-session-replay/jest.config.js
create mode 100644 packages/plugin-session-replay/package.json
create mode 100644 packages/plugin-session-replay/rollup.config.js
create mode 100644 packages/plugin-session-replay/src/helpers.ts
create mode 100644 packages/plugin-session-replay/src/index.ts
create mode 100644 packages/plugin-session-replay/src/session-replay.ts
create mode 100644 packages/plugin-session-replay/src/typings/session-replay.ts
create mode 100644 packages/plugin-session-replay/tsconfig.es5.json
create mode 100644 packages/plugin-session-replay/tsconfig.esm.json
create mode 100644 packages/plugin-session-replay/tsconfig.json
diff --git a/packages/plugin-session-replay/CHANGELOG.md b/packages/plugin-session-replay/CHANGELOG.md
new file mode 100644
index 000000000..2666c3996
--- /dev/null
+++ b/packages/plugin-session-replay/CHANGELOG.md
@@ -0,0 +1,363 @@
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.7.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.7...@amplitude/plugin-web-attribution-browser@0.7.0) (2023-05-04)
+
+### Features
+
+- add attribution tracking for linkedin click id li_fat_id
+ ([ca81f3d](https://github.com/amplitude/Amplitude-TypeScript/commit/ca81f3d75ece7e0e23a1bc1b6889107d53a60a86))
+- add rtd_cid for Reddit campaign tracking/attribution
+ ([784e080](https://github.com/amplitude/Amplitude-TypeScript/commit/784e080aa129c37e850d7f34115beb9770044e4e))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.6...@amplitude/plugin-web-attribution-browser@0.6.7) (2023-04-27)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.5...@amplitude/plugin-web-attribution-browser@0.6.6) (2023-04-27)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.4...@amplitude/plugin-web-attribution-browser@0.6.5) (2023-04-25)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.3...@amplitude/plugin-web-attribution-browser@0.6.4) (2023-04-06)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.2...@amplitude/plugin-web-attribution-browser@0.6.3) (2023-03-31)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.2-beta.0...@amplitude/plugin-web-attribution-browser@0.6.2) (2023-03-31)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.2-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.1...@amplitude/plugin-web-attribution-browser@0.6.2-beta.0) (2023-03-31)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.1-beta.1...@amplitude/plugin-web-attribution-browser@0.6.1) (2023-03-03)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.1-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.1-beta.0...@amplitude/plugin-web-attribution-browser@0.6.1-beta.1) (2023-03-03)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.6.1-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0...@amplitude/plugin-web-attribution-browser@0.6.1-beta.0) (2023-03-03)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.6.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.4...@amplitude/plugin-web-attribution-browser@0.6.0) (2023-02-27)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.6.0-beta.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.3...@amplitude/plugin-web-attribution-browser@0.6.0-beta.4) (2023-02-27)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.6.0-beta.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.2...@amplitude/plugin-web-attribution-browser@0.6.0-beta.3) (2023-02-26)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.6.0-beta.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.1...@amplitude/plugin-web-attribution-browser@0.6.0-beta.2) (2023-02-25)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.6.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.0...@amplitude/plugin-web-attribution-browser@0.6.0-beta.1) (2023-02-24)
+
+### Bug Fixes
+
+- improper cookie usage ([#330](https://github.com/amplitude/Amplitude-TypeScript/issues/330))
+ ([e670091](https://github.com/amplitude/Amplitude-TypeScript/commit/e670091e59014bb35bd9b3ec2a7192f259393575))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [0.6.0-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.7...@amplitude/plugin-web-attribution-browser@0.6.0-beta.0) (2023-02-24)
+
+### Bug Fixes
+
+- remove client parameter requirement for page view tracking plugin
+ ([#329](https://github.com/amplitude/Amplitude-TypeScript/issues/329))
+ ([1e01575](https://github.com/amplitude/Amplitude-TypeScript/commit/1e015750b52880ca63afa5162cb482995f04d1c6))
+
+### Features
+
+- pass amplitude instance to plugin.setup for enhanced plugin capabilities
+ ([#328](https://github.com/amplitude/Amplitude-TypeScript/issues/328))
+ ([91eeaa0](https://github.com/amplitude/Amplitude-TypeScript/commit/91eeaa0d6bff6bde39538bb54548a938df784462))
+- retrofit web attribution and page view plugins to browser SDK
+ ([#331](https://github.com/amplitude/Amplitude-TypeScript/issues/331))
+ ([ba845d3](https://github.com/amplitude/Amplitude-TypeScript/commit/ba845d3329bd6bebe3b89f24f4f316088c2d62b9))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.5.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.6...@amplitude/plugin-web-attribution-browser@0.5.7) (2023-02-09)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.5.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.5...@amplitude/plugin-web-attribution-browser@0.5.6) (2023-02-02)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.5.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.5-beta.0...@amplitude/plugin-web-attribution-browser@0.5.5) (2023-01-31)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.5.5-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.4...@amplitude/plugin-web-attribution-browser@0.5.5-beta.0) (2023-01-26)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.5.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.3...@amplitude/plugin-web-attribution-browser@0.5.4) (2023-01-11)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+## [0.5.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.2...@amplitude/plugin-web-attribution-browser@0.5.3) (2022-12-21)
+
+### Bug Fixes
+
+- upgrade dependencies to resolve dependabot vulnerability alerts
+ ([#299](https://github.com/amplitude/Amplitude-TypeScript/issues/299))
+ ([7dd1cd1](https://github.com/amplitude/Amplitude-TypeScript/commit/7dd1cd1b23a71981a4ad90b4b30cc9b7d28c4412))
+
+### Reverts
+
+- Revert "Updated dependencies"
+ ([7ca9964](https://github.com/amplitude/Amplitude-TypeScript/commit/7ca9964781e4b900c6c027bdddf2ae1e7428ba18))
+
+## [0.5.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.1...@amplitude/plugin-web-attribution-browser@0.5.2) (2022-12-06)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.5.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.0...@amplitude/plugin-web-attribution-browser@0.5.1) (2022-12-05)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# [0.5.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.4.2...@amplitude/plugin-web-attribution-browser@0.5.0) (2022-11-28)
+
+### Features
+
+- add utm_id tracking ([#284](https://github.com/amplitude/Amplitude-TypeScript/issues/284))
+ ([f72dcf1](https://github.com/amplitude/Amplitude-TypeScript/commit/f72dcf1788ebc84544aaee1dc41b1d1ba6e4c06e))
+
+## [0.4.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.4.1...@amplitude/plugin-web-attribution-browser@0.4.2) (2022-11-22)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.4.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.4.0...@amplitude/plugin-web-attribution-browser@0.4.1) (2022-11-15)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# [0.4.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.3.0...@amplitude/plugin-web-attribution-browser@0.4.0) (2022-11-01)
+
+### Features
+
+- ignore subdomains when comparing newness of campaigns
+ ([#260](https://github.com/amplitude/Amplitude-TypeScript/issues/260))
+ ([8bb2b76](https://github.com/amplitude/Amplitude-TypeScript/commit/8bb2b76faf37783a58e953391468bd31c089e3a3))
+
+# [0.3.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.5...@amplitude/plugin-web-attribution-browser@0.3.0) (2022-11-01)
+
+### Features
+
+- enhance logger with debug information ([#254](https://github.com/amplitude/Amplitude-TypeScript/issues/254))
+ ([5c6175e](https://github.com/amplitude/Amplitude-TypeScript/commit/5c6175e9372cbeea264ddb34c6cc68148063d4f7))
+
+## [0.2.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.4...@amplitude/plugin-web-attribution-browser@0.2.5) (2022-10-30)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.2.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.3...@amplitude/plugin-web-attribution-browser@0.2.4) (2022-10-26)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.2.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.3-beta.1...@amplitude/plugin-web-attribution-browser@0.2.3) (2022-10-25)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.2.3-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.3-beta.0...@amplitude/plugin-web-attribution-browser@0.2.3-beta.1) (2022-10-25)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.2.3-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.2...@amplitude/plugin-web-attribution-browser@0.2.3-beta.0) (2022-10-25)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.2.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.1...@amplitude/plugin-web-attribution-browser@0.2.2) (2022-10-25)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.2.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.0...@amplitude/plugin-web-attribution-browser@0.2.1) (2022-10-14)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# [0.2.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.6...@amplitude/plugin-web-attribution-browser@0.2.0) (2022-10-04)
+
+### Features
+
+- add gbraid and wbraid as campaign parameters ([#242](https://github.com/amplitude/Amplitude-TypeScript/issues/242))
+ ([514b7cd](https://github.com/amplitude/Amplitude-TypeScript/commit/514b7cdea9fee0c4e61479b087f7acdfea889350))
+
+## [0.1.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.5...@amplitude/plugin-web-attribution-browser@0.1.6) (2022-09-30)
+
+### Bug Fixes
+
+- resolve web attribution is not tracking the first direct/organic traffic
+ ([#239](https://github.com/amplitude/Amplitude-TypeScript/issues/239))
+ ([98a3363](https://github.com/amplitude/Amplitude-TypeScript/commit/98a33633a7a6de7ee147c8cbf690e5546ce53163))
+
+## [0.1.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4...@amplitude/plugin-web-attribution-browser@0.1.5) (2022-09-30)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.1.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4-beta.2...@amplitude/plugin-web-attribution-browser@0.1.4) (2022-09-28)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.1.4-beta.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4-beta.1...@amplitude/plugin-web-attribution-browser@0.1.4-beta.2) (2022-09-27)
+
+### Bug Fixes
+
+- js script export name for marketing analytics plugins
+ ([aa7b05c](https://github.com/amplitude/Amplitude-TypeScript/commit/aa7b05cb192e23924081a363f3567573f76a3b62))
+
+## [0.1.4-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4-beta.0...@amplitude/plugin-web-attribution-browser@0.1.4-beta.1) (2022-09-27)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.1.4-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.3...@amplitude/plugin-web-attribution-browser@0.1.4-beta.0) (2022-09-26)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.1.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.2...@amplitude/plugin-web-attribution-browser@0.1.3) (2022-09-26)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+## [0.1.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.1...@amplitude/plugin-web-attribution-browser@0.1.2) (2022-09-26)
+
+### Bug Fixes
+
+- update base config to include additional click ids
+ ([#229](https://github.com/amplitude/Amplitude-TypeScript/issues/229))
+ ([5596931](https://github.com/amplitude/Amplitude-TypeScript/commit/55969310714c43f138e1702ba285fd4dadcdcb44))
+
+## [0.1.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.0...@amplitude/plugin-web-attribution-browser@0.1.1) (2022-09-22)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# 0.1.0 (2022-09-16)
+
+### Features
+
+- new marketing analytics plugin ([#213](https://github.com/amplitude/Amplitude-TypeScript/issues/213))
+ ([02ff174](https://github.com/amplitude/Amplitude-TypeScript/commit/02ff174e3361173dbf15ed3acf72e950810e174f))
diff --git a/packages/plugin-session-replay/README.md b/packages/plugin-session-replay/README.md
new file mode 100644
index 000000000..2cffd9ced
--- /dev/null
+++ b/packages/plugin-session-replay/README.md
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+# @amplitude/plugin-web-attribution-browser
+
+Official Browser SDK plugin for web attribution tracking
+
+## Installation
+
+This package is published on NPM registry and is available to be installed using npm and yarn.
+
+```sh
+# npm
+npm install @amplitude/plugin-web-attribution-browser
+
+# yarn
+yarn add @amplitude/plugin-web-attribution-browser
+```
+
+## Usage
+
+This plugin works on top of Amplitude Browser SDK and adds web attribution tracking features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v1.4.0` or later.
+
+### 1. Import Amplitude packages
+
+* `@amplitude/analytics-browser`
+* `@amplitude/plugin-web-attribution-browser`
+
+```typescript
+import * as amplitude from '@amplitude/analytics-browser';
+import { webAttributionPlugin } from '@amplitude/plugin-web-attribution-browser';
+```
+
+### 2. Instantiate page view plugin
+
+The plugin requires 1 parameter, which is the `amplitude` instance. The plugin also accepts an optional second parameter, which is an `Object` to configure the plugin based on your use case.
+
+```typescript
+const webAttributionTracking = webAttributionPlugin(amplitude, {
+ disabled: undefined,
+ excludeReferrers: undefined,
+ initialEmptyValue: undefined,
+ resetSessionOnNewCampaign: undefined,
+});
+```
+
+#### Options
+
+|Name|Type|Default|Description|
+|-|-|-|-|
+|`disabled`|`boolean`|`false`|Use this option to enable or disable web attribution tracking. By default, upon installing this plugin, web attribution tracking is enabled.|
+|`excludeReferrers`|`string[]`|`[]`|Use this option to prevent the plugin from tracking campaigns parameters from specific referrers. For example: `subdomain.domain.com`.|
+|`initialEmptyValue`|`string`|`"EMPTY"`|Use this option to specify empty values for [first-touch attribution](https://www.docs.developers.amplitude.com/data/sdks/marketing-analytics-browser/#first-touch-attribution).|
+|`resetSessionOnNewCampaign`|`boolean`|`false`|Use this option to control whether a new session should start on a new campaign.|
+
+### 3. Install plugin to Amplitude SDK
+
+```typescript
+amplitude.add(webAttributionTracking);
+```
+
+### 4. Initialize Amplitude SDK
+
+```typescript
+amplitude.init('API_KEY');
+```
+
+## Resulting web attribution event
+
+This plugin tracks campaign parameters based on your configuration. A web attribution event is composed of the following values:
+
+#### Event type
+* `"$idenfity"`
+
+#### User properties
+
+|Property|Description|
+|-|-|
+|`utm_source`|URL query parameter value for `utm_source`|
+|`utm_medium`|URL query parameter value for `utm_medium`|
+|`utm_campaign`|URL query parameter value for `utm_campaign`|
+|`utm_term`|URL query parameter value for `utm_term`|
+|`utm_content`|URL query parameter value for `utm_content`|
+|`referrer`|Referring webstite or `document.referrer`|
+|`referring_domain`|Referring website's domain, including subdomain|
+|`dclid`|URL query parameter value for `dclid`|
+|`gbraid`|URL query parameter value for `gbraid`|
+|`gclid`|URL query parameter value for `gclid`|
+|`fbclid`|URL query parameter value for `fbclid`|
+|`ko_click_id`|URL query parameter value for `ko_click_id`|
+|`li_fat_id`|URL query parameter value for `li_fat_id`|
+|`msclkid`|URL query parameter value for `msclkid`|
+|`rtd_cid`|URL query parameter value for `rtd_cid`|
+|`ttclid`|URL query parameter value for `ttclid`|
+|`twclid`|URL query parameter value for `twclid`|
+|`wbraid`|URL query parameter value for `wbraid`|
diff --git a/packages/plugin-session-replay/jest.config.js b/packages/plugin-session-replay/jest.config.js
new file mode 100644
index 000000000..dc4094b18
--- /dev/null
+++ b/packages/plugin-session-replay/jest.config.js
@@ -0,0 +1,10 @@
+const baseConfig = require('../../jest.config.js');
+const package = require('./package');
+
+module.exports = {
+ ...baseConfig,
+ displayName: package.name,
+ rootDir: '.',
+ testEnvironment: 'jsdom',
+ coveragePathIgnorePatterns: ['index.ts'],
+};
diff --git a/packages/plugin-session-replay/package.json b/packages/plugin-session-replay/package.json
new file mode 100644
index 000000000..d83cb4bc2
--- /dev/null
+++ b/packages/plugin-session-replay/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "@amplitude/plugin-session-replay",
+ "version": "1.0.0-beta.0",
+ "description": "",
+ "author": "Amplitude Inc",
+ "homepage": "https://github.com/amplitude/Amplitude-TypeScript",
+ "license": "MIT",
+ "main": "lib/cjs/index.js",
+ "module": "lib/esm/index.js",
+ "types": "lib/esm/index.d.ts",
+ "sideEffects": false,
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/amplitude/Amplitude-TypeScript.git"
+ },
+ "scripts": {
+ "build": "yarn bundle && yarn build:es5 && yarn build:esm",
+ "bundle": "rollup --config rollup.config.js",
+ "build:es5": "tsc -p ./tsconfig.es5.json",
+ "build:esm": "tsc -p ./tsconfig.esm.json",
+ "clean": "rimraf node_modules lib coverage",
+ "fix": "yarn fix:eslint & yarn fix:prettier",
+ "fix:eslint": "eslint '{src,test}/**/*.ts' --fix",
+ "fix:prettier": "prettier --write \"{src,test}/**/*.ts\"",
+ "lint": "yarn lint:eslint & yarn lint:prettier",
+ "lint:eslint": "eslint '{src,test}/**/*.ts'",
+ "lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
+ "publish": "node ../../scripts/publish/upload-to-s3.js",
+ "test": "jest",
+ "typecheck": "tsc -p ./tsconfig.json"
+ },
+ "bugs": {
+ "url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
+ },
+ "dependencies": {
+ "@amplitude/analytics-client-common": "^1.0.0-beta.0",
+ "@amplitude/analytics-types": "^1.0.0-beta.0",
+ "rrweb": "^2.0.0-alpha.4",
+ "tslib": "^2.4.1"
+ },
+ "devDependencies": {
+ "@amplitude/analytics-browser": "^1.10.3",
+ "@rollup/plugin-commonjs": "^23.0.4",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^10.0.1",
+ "rollup": "^2.79.1",
+ "rollup-plugin-execute": "^1.1.1",
+ "rollup-plugin-gzip": "^3.1.0",
+ "rollup-plugin-terser": "^7.0.2"
+ },
+ "files": [
+ "lib"
+ ]
+}
diff --git a/packages/plugin-session-replay/rollup.config.js b/packages/plugin-session-replay/rollup.config.js
new file mode 100644
index 000000000..8fbd9d329
--- /dev/null
+++ b/packages/plugin-session-replay/rollup.config.js
@@ -0,0 +1,6 @@
+import { iife, umd } from '../../scripts/build/rollup.config';
+
+iife.input = umd.input;
+iife.output.name = 'sessionReplay';
+
+export default [umd, iife];
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/plugin-session-replay/src/index.ts b/packages/plugin-session-replay/src/index.ts
new file mode 100644
index 000000000..779a4865c
--- /dev/null
+++ b/packages/plugin-session-replay/src/index.ts
@@ -0,0 +1,2 @@
+export { sessionReplayPlugin as plugin, sessionReplayPlugin } from './session-replay';
+export * as Types from './typings/session-replay';
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
new file mode 100644
index 000000000..adda3751e
--- /dev/null
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -0,0 +1,71 @@
+import { BrowserClient, BrowserConfig, EnrichmentPlugin, Event, PluginType } from '@amplitude/analytics-types';
+import { record } from 'rrweb';
+import { CreateSessionReplayPlugin, Options } from './typings/session-replay';
+
+export const SESSION_REPLAY_SERVER_URL = 'https://api2.amplitude.com/sessions/track';
+
+export const sessionReplayPlugin: CreateSessionReplayPlugin = function (options: Options = {}) {
+ let amplitudeConfig: BrowserConfig | undefined;
+ // let amplitude: BrowserClient | undefined;
+
+ options = {
+ ...options,
+ };
+
+ let events: string[] = [];
+
+ const plugin: EnrichmentPlugin = {
+ name: '@amplitude/plugin-session-replay',
+ type: PluginType.ENRICHMENT,
+
+ setup: async function (config: BrowserConfig, client: BrowserClient) {
+ console.log('in setup', 'config', config, 'client', client);
+ amplitudeConfig = config;
+
+ config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ },
+
+ execute: async (event: Event) => {
+ console.log('event', event);
+ // todo: this should be a constant/type
+ if (event.event_type === 'session_start') {
+ record({
+ emit(event) {
+ events.push(JSON.stringify(event));
+ },
+ maskAllInputs: true,
+ });
+ }
+
+ if (event.event_type === 'session_end' && events.length) {
+ const payload = {
+ api_key: amplitudeConfig?.apiKey,
+ device_id: amplitudeConfig?.deviceId,
+ session_id: amplitudeConfig?.sessionId,
+ start_timestamp: amplitudeConfig?.sessionId,
+ events_batch: {
+ version: 1,
+ events,
+ seq_number: 1,
+ },
+ };
+
+ const options: RequestInit = {
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: '*/*',
+ },
+ body: JSON.stringify(payload),
+ method: 'POST',
+ };
+ events = [];
+ const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
+ console.log('res', res);
+ }
+
+ return event;
+ },
+ };
+
+ return plugin;
+};
diff --git a/packages/plugin-session-replay/src/typings/session-replay.ts b/packages/plugin-session-replay/src/typings/session-replay.ts
new file mode 100644
index 000000000..cc9f7bd26
--- /dev/null
+++ b/packages/plugin-session-replay/src/typings/session-replay.ts
@@ -0,0 +1,8 @@
+/* eslint-disable @typescript-eslint/no-empty-interface */
+import { EnrichmentPlugin } from '@amplitude/analytics-types';
+
+export interface Options {}
+
+export interface CreateSessionReplayPlugin {
+ (options?: Options): EnrichmentPlugin;
+}
diff --git a/packages/plugin-session-replay/tsconfig.es5.json b/packages/plugin-session-replay/tsconfig.es5.json
new file mode 100644
index 000000000..77e041d3f
--- /dev/null
+++ b/packages/plugin-session-replay/tsconfig.es5.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "module": "commonjs",
+ "noEmit": false,
+ "outDir": "lib/cjs",
+ "rootDir": "./src"
+ }
+}
diff --git a/packages/plugin-session-replay/tsconfig.esm.json b/packages/plugin-session-replay/tsconfig.esm.json
new file mode 100644
index 000000000..bec981eee
--- /dev/null
+++ b/packages/plugin-session-replay/tsconfig.esm.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "module": "es6",
+ "noEmit": false,
+ "outDir": "lib/esm",
+ "rootDir": "./src"
+ }
+}
diff --git a/packages/plugin-session-replay/tsconfig.json b/packages/plugin-session-replay/tsconfig.json
new file mode 100644
index 000000000..955dcce78
--- /dev/null
+++ b/packages/plugin-session-replay/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src/**/*", "test/**/*"],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "esModuleInterop": true,
+ "lib": ["dom"],
+ "noEmit": true,
+ "rootDir": ".",
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index ecd604a1f..33a296cca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,14 +2,12 @@
# yarn lockfile v1
-"@amplitude/analytics-connector@^1.4.5":
- version "1.4.6"
- resolved "https://registry.yarnpkg.com/@amplitude/analytics-connector/-/analytics-connector-1.4.6.tgz#60a66eaf0bcdcd17db4f414ff340c69e63116a72"
- integrity sha512-6jD2pOosRD4y8DT8StUCz7yTd5ZDkdOU9/AWnlWKM5qk90Mz7sdZrdZ9H7sA/L3yOJEpQOYZgQplQdWWUzyWug==
- dependencies:
- "@amplitude/ua-parser-js" "0.7.31"
+"@amplitude/analytics-connector@^1.4.8":
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/@amplitude/analytics-connector/-/analytics-connector-1.4.8.tgz#dd801303db2662bc51be7e0194eeb8bd72267c42"
+ integrity sha512-dFW7c7Wb6Ng7vbmzwbaXZSpqfBx37ukamJV9ErFYYS8vGZK/Hkbt3M7fZHBI4WFU6CCwakr2ZXPme11uGPYWkQ==
-"@amplitude/ua-parser-js@0.7.31", "@amplitude/ua-parser-js@^0.7.31":
+"@amplitude/ua-parser-js@^0.7.31":
version "0.7.31"
resolved "https://registry.yarnpkg.com/@amplitude/ua-parser-js/-/ua-parser-js-0.7.31.tgz#749bf7cb633cfcc7ff3c10805bad7c5f6fbdbc61"
integrity sha512-+z8UGRaj13Pt5NDzOnkTBy49HE2CX64jeL0ArB86HAtilpnfkPB7oqkigN7Lf2LxscMg4QhFD7mmCfedh3rqTg==
@@ -9561,7 +9559,7 @@ nocache@^3.0.1:
resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.4.tgz#5b37a56ec6e09fc7d401dceaed2eab40c8bfdf79"
integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==
-nock@^13.2.4, nock@^13.2.9:
+nock@^13.2.4:
version "13.2.9"
resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.9.tgz#4faf6c28175d36044da4cfa68e33e5a15086ad4c"
integrity sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==
From c88d85ae4c041f05064d2e703b9cb0a0edd008f7 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 31 May 2023 10:29:44 -0400
Subject: [PATCH 023/214] feat(plugins): implemented retry logic for session
replay
---
packages/plugin-session-replay/CHANGELOG.md | 352 ------------------
packages/plugin-session-replay/src/helpers.ts | 15 +
packages/plugin-session-replay/src/index.ts | 2 +-
.../plugin-session-replay/src/messages.ts | 7 +
.../src/session-replay.ts | 230 +++++++++---
.../src/typings/session-replay.ts | 10 +
6 files changed, 206 insertions(+), 410 deletions(-)
create mode 100644 packages/plugin-session-replay/src/messages.ts
diff --git a/packages/plugin-session-replay/CHANGELOG.md b/packages/plugin-session-replay/CHANGELOG.md
index 2666c3996..b67c38fc7 100644
--- a/packages/plugin-session-replay/CHANGELOG.md
+++ b/packages/plugin-session-replay/CHANGELOG.md
@@ -3,358 +3,6 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-# [0.7.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.7...@amplitude/plugin-web-attribution-browser@0.7.0) (2023-05-04)
-
-### Features
-
-- add attribution tracking for linkedin click id li_fat_id
- ([ca81f3d](https://github.com/amplitude/Amplitude-TypeScript/commit/ca81f3d75ece7e0e23a1bc1b6889107d53a60a86))
-- add rtd_cid for Reddit campaign tracking/attribution
- ([784e080](https://github.com/amplitude/Amplitude-TypeScript/commit/784e080aa129c37e850d7f34115beb9770044e4e))
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.6...@amplitude/plugin-web-attribution-browser@0.6.7) (2023-04-27)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.5...@amplitude/plugin-web-attribution-browser@0.6.6) (2023-04-27)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.4...@amplitude/plugin-web-attribution-browser@0.6.5) (2023-04-25)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.3...@amplitude/plugin-web-attribution-browser@0.6.4) (2023-04-06)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.2...@amplitude/plugin-web-attribution-browser@0.6.3) (2023-03-31)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.2-beta.0...@amplitude/plugin-web-attribution-browser@0.6.2) (2023-03-31)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.2-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.1...@amplitude/plugin-web-attribution-browser@0.6.2-beta.0) (2023-03-31)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.1-beta.1...@amplitude/plugin-web-attribution-browser@0.6.1) (2023-03-03)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.1-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.1-beta.0...@amplitude/plugin-web-attribution-browser@0.6.1-beta.1) (2023-03-03)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.6.1-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0...@amplitude/plugin-web-attribution-browser@0.6.1-beta.0) (2023-03-03)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# [0.6.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.4...@amplitude/plugin-web-attribution-browser@0.6.0) (2023-02-27)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# [0.6.0-beta.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.3...@amplitude/plugin-web-attribution-browser@0.6.0-beta.4) (2023-02-27)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# [0.6.0-beta.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.2...@amplitude/plugin-web-attribution-browser@0.6.0-beta.3) (2023-02-26)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# [0.6.0-beta.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.1...@amplitude/plugin-web-attribution-browser@0.6.0-beta.2) (2023-02-25)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# [0.6.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.6.0-beta.0...@amplitude/plugin-web-attribution-browser@0.6.0-beta.1) (2023-02-24)
-
-### Bug Fixes
-
-- improper cookie usage ([#330](https://github.com/amplitude/Amplitude-TypeScript/issues/330))
- ([e670091](https://github.com/amplitude/Amplitude-TypeScript/commit/e670091e59014bb35bd9b3ec2a7192f259393575))
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# [0.6.0-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.7...@amplitude/plugin-web-attribution-browser@0.6.0-beta.0) (2023-02-24)
-
-### Bug Fixes
-
-- remove client parameter requirement for page view tracking plugin
- ([#329](https://github.com/amplitude/Amplitude-TypeScript/issues/329))
- ([1e01575](https://github.com/amplitude/Amplitude-TypeScript/commit/1e015750b52880ca63afa5162cb482995f04d1c6))
-
-### Features
-
-- pass amplitude instance to plugin.setup for enhanced plugin capabilities
- ([#328](https://github.com/amplitude/Amplitude-TypeScript/issues/328))
- ([91eeaa0](https://github.com/amplitude/Amplitude-TypeScript/commit/91eeaa0d6bff6bde39538bb54548a938df784462))
-- retrofit web attribution and page view plugins to browser SDK
- ([#331](https://github.com/amplitude/Amplitude-TypeScript/issues/331))
- ([ba845d3](https://github.com/amplitude/Amplitude-TypeScript/commit/ba845d3329bd6bebe3b89f24f4f316088c2d62b9))
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.5.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.6...@amplitude/plugin-web-attribution-browser@0.5.7) (2023-02-09)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.5.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.5...@amplitude/plugin-web-attribution-browser@0.5.6) (2023-02-02)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.5.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.5-beta.0...@amplitude/plugin-web-attribution-browser@0.5.5) (2023-01-31)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.5.5-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.4...@amplitude/plugin-web-attribution-browser@0.5.5-beta.0) (2023-01-26)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.5.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.3...@amplitude/plugin-web-attribution-browser@0.5.4) (2023-01-11)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-## [0.5.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.2...@amplitude/plugin-web-attribution-browser@0.5.3) (2022-12-21)
-
-### Bug Fixes
-
-- upgrade dependencies to resolve dependabot vulnerability alerts
- ([#299](https://github.com/amplitude/Amplitude-TypeScript/issues/299))
- ([7dd1cd1](https://github.com/amplitude/Amplitude-TypeScript/commit/7dd1cd1b23a71981a4ad90b4b30cc9b7d28c4412))
-
-### Reverts
-
-- Revert "Updated dependencies"
- ([7ca9964](https://github.com/amplitude/Amplitude-TypeScript/commit/7ca9964781e4b900c6c027bdddf2ae1e7428ba18))
-
-## [0.5.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.1...@amplitude/plugin-web-attribution-browser@0.5.2) (2022-12-06)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.5.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.5.0...@amplitude/plugin-web-attribution-browser@0.5.1) (2022-12-05)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# [0.5.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.4.2...@amplitude/plugin-web-attribution-browser@0.5.0) (2022-11-28)
-
-### Features
-
-- add utm_id tracking ([#284](https://github.com/amplitude/Amplitude-TypeScript/issues/284))
- ([f72dcf1](https://github.com/amplitude/Amplitude-TypeScript/commit/f72dcf1788ebc84544aaee1dc41b1d1ba6e4c06e))
-
-## [0.4.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.4.1...@amplitude/plugin-web-attribution-browser@0.4.2) (2022-11-22)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.4.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.4.0...@amplitude/plugin-web-attribution-browser@0.4.1) (2022-11-15)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# [0.4.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.3.0...@amplitude/plugin-web-attribution-browser@0.4.0) (2022-11-01)
-
-### Features
-
-- ignore subdomains when comparing newness of campaigns
- ([#260](https://github.com/amplitude/Amplitude-TypeScript/issues/260))
- ([8bb2b76](https://github.com/amplitude/Amplitude-TypeScript/commit/8bb2b76faf37783a58e953391468bd31c089e3a3))
-
-# [0.3.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.5...@amplitude/plugin-web-attribution-browser@0.3.0) (2022-11-01)
-
-### Features
-
-- enhance logger with debug information ([#254](https://github.com/amplitude/Amplitude-TypeScript/issues/254))
- ([5c6175e](https://github.com/amplitude/Amplitude-TypeScript/commit/5c6175e9372cbeea264ddb34c6cc68148063d4f7))
-
-## [0.2.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.4...@amplitude/plugin-web-attribution-browser@0.2.5) (2022-10-30)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.2.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.3...@amplitude/plugin-web-attribution-browser@0.2.4) (2022-10-26)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.2.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.3-beta.1...@amplitude/plugin-web-attribution-browser@0.2.3) (2022-10-25)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.2.3-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.3-beta.0...@amplitude/plugin-web-attribution-browser@0.2.3-beta.1) (2022-10-25)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.2.3-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.2...@amplitude/plugin-web-attribution-browser@0.2.3-beta.0) (2022-10-25)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.2.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.1...@amplitude/plugin-web-attribution-browser@0.2.2) (2022-10-25)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.2.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.2.0...@amplitude/plugin-web-attribution-browser@0.2.1) (2022-10-14)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-# [0.2.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.6...@amplitude/plugin-web-attribution-browser@0.2.0) (2022-10-04)
-
-### Features
-
-- add gbraid and wbraid as campaign parameters ([#242](https://github.com/amplitude/Amplitude-TypeScript/issues/242))
- ([514b7cd](https://github.com/amplitude/Amplitude-TypeScript/commit/514b7cdea9fee0c4e61479b087f7acdfea889350))
-
-## [0.1.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.5...@amplitude/plugin-web-attribution-browser@0.1.6) (2022-09-30)
-
-### Bug Fixes
-
-- resolve web attribution is not tracking the first direct/organic traffic
- ([#239](https://github.com/amplitude/Amplitude-TypeScript/issues/239))
- ([98a3363](https://github.com/amplitude/Amplitude-TypeScript/commit/98a33633a7a6de7ee147c8cbf690e5546ce53163))
-
-## [0.1.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4...@amplitude/plugin-web-attribution-browser@0.1.5) (2022-09-30)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.1.4](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4-beta.2...@amplitude/plugin-web-attribution-browser@0.1.4) (2022-09-28)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.1.4-beta.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4-beta.1...@amplitude/plugin-web-attribution-browser@0.1.4-beta.2) (2022-09-27)
-
-### Bug Fixes
-
-- js script export name for marketing analytics plugins
- ([aa7b05c](https://github.com/amplitude/Amplitude-TypeScript/commit/aa7b05cb192e23924081a363f3567573f76a3b62))
-
-## [0.1.4-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.4-beta.0...@amplitude/plugin-web-attribution-browser@0.1.4-beta.1) (2022-09-27)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.1.4-beta.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.3...@amplitude/plugin-web-attribution-browser@0.1.4-beta.0) (2022-09-26)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.1.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.2...@amplitude/plugin-web-attribution-browser@0.1.3) (2022-09-26)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
-## [0.1.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.1...@amplitude/plugin-web-attribution-browser@0.1.2) (2022-09-26)
-
-### Bug Fixes
-
-- update base config to include additional click ids
- ([#229](https://github.com/amplitude/Amplitude-TypeScript/issues/229))
- ([5596931](https://github.com/amplitude/Amplitude-TypeScript/commit/55969310714c43f138e1702ba285fd4dadcdcb44))
-
-## [0.1.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@0.1.0...@amplitude/plugin-web-attribution-browser@0.1.1) (2022-09-22)
-
-**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
-
# 0.1.0 (2022-09-16)
### Features
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
index e69de29bb..de5a7638d 100644
--- a/packages/plugin-session-replay/src/helpers.ts
+++ b/packages/plugin-session-replay/src/helpers.ts
@@ -0,0 +1,15 @@
+// Creates an array of elements split into groups the length of size.
+// If array can't be split evenly, the final chunk will be the remaining elements.
+// Works similary as https://lodash.com/docs/4.17.15#chunk
+
+export const chunk = (arr: T[], size: number) => {
+ const chunkSize = Math.max(size, 1);
+ return arr.reduce((chunks, element, index) => {
+ const chunkIndex = Math.floor(index / chunkSize);
+ if (!chunks[chunkIndex]) {
+ chunks[chunkIndex] = [];
+ }
+ chunks[chunkIndex].push(element);
+ return chunks;
+ }, []);
+};
diff --git a/packages/plugin-session-replay/src/index.ts b/packages/plugin-session-replay/src/index.ts
index 779a4865c..4a1605e03 100644
--- a/packages/plugin-session-replay/src/index.ts
+++ b/packages/plugin-session-replay/src/index.ts
@@ -1,2 +1,2 @@
-export { sessionReplayPlugin as plugin, sessionReplayPlugin } from './session-replay';
+export { SessionReplayPlugin as Plugin, SessionReplayPlugin } from './session-replay';
export * as Types from './typings/session-replay';
diff --git a/packages/plugin-session-replay/src/messages.ts b/packages/plugin-session-replay/src/messages.ts
new file mode 100644
index 000000000..adce6761f
--- /dev/null
+++ b/packages/plugin-session-replay/src/messages.ts
@@ -0,0 +1,7 @@
+export const SUCCESS_MESSAGE = 'Session replay event batch tracked successfully';
+export const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';
+export const MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';
+export const OPT_OUT_MESSAGE = 'Event skipped due to optOut config';
+export const MISSING_API_KEY_MESSAGE = 'Session replay event batch rejected due to missing API key';
+export const INVALID_API_KEY = 'Invalid API key';
+export const CLIENT_NOT_INITIALIZED = 'Client not initialized';
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index adda3751e..d51e7b784 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -1,71 +1,187 @@
-import { BrowserClient, BrowserConfig, EnrichmentPlugin, Event, PluginType } from '@amplitude/analytics-types';
+import { BaseTransport } from '@amplitude/analytics-core';
+import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
import { record } from 'rrweb';
-import { CreateSessionReplayPlugin, Options } from './typings/session-replay';
+import { MAX_RETRIES_EXCEEDED_MESSAGE, MISSING_API_KEY_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
+import { SessionReplayContext } from './typings/session-replay';
export const SESSION_REPLAY_SERVER_URL = 'https://api2.amplitude.com/sessions/track';
-export const sessionReplayPlugin: CreateSessionReplayPlugin = function (options: Options = {}) {
- let amplitudeConfig: BrowserConfig | undefined;
- // let amplitude: BrowserClient | undefined;
+export class SessionReplayPlugin implements EnrichmentPlugin {
+ name = '@amplitude/plugin-session-replay';
+ type = PluginType.ENRICHMENT as const;
+ // this.config is defined in setup() which will always be called first
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ config: BrowserConfig;
+ retryTimeout = 1000;
+ events: string[][] = [];
+ private scheduled: ReturnType | null = null;
+ queue: SessionReplayContext[] = [];
- options = {
- ...options,
- };
+ async setup(config: BrowserConfig) {
+ console.log('setup called', config);
+ this.events = [];
+ this.config = config;
- let events: string[] = [];
+ config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ }
- const plugin: EnrichmentPlugin = {
- name: '@amplitude/plugin-session-replay',
- type: PluginType.ENRICHMENT,
-
- setup: async function (config: BrowserConfig, client: BrowserClient) {
- console.log('in setup', 'config', config, 'client', client);
- amplitudeConfig = config;
+ async execute(event: Event) {
+ // console.log('event', event);
+ // todo: this should be a constant/type
+ if (event.event_type === 'session_start') {
+ this.events.push([]);
+ console.log('starting new session', this.events);
+ record({
+ emit: (event) => {
+ console.log('events in emit', this.events);
+ this.events[this.events.length - 1].push(JSON.stringify(event));
+ },
+ maskAllInputs: true,
+ });
+ } else if (event.event_type === 'session_end' && this.events.length) {
+ console.log('ending session', this.events);
+ try {
+ this.addToQueue({
+ events: this.events[this.events.length - 1],
+ index: this.events.length - 1,
+ attempts: 0,
+ timeout: 0,
+ });
+ } catch (e) {
+ this.config.loggerProvider.error(e);
+ }
+ }
- config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
- },
+ return event;
+ }
- execute: async (event: Event) => {
- console.log('event', event);
- // todo: this should be a constant/type
- if (event.event_type === 'session_start') {
- record({
- emit(event) {
- events.push(JSON.stringify(event));
- },
- maskAllInputs: true,
- });
+ addToQueue(...list: SessionReplayContext[]) {
+ const tryable = list.filter((context) => {
+ console.log('this.config.flushMaxRetries', this.config.flushMaxRetries);
+ if (context.attempts < this.config.flushMaxRetries) {
+ context.attempts += 1;
+ return true;
}
+ throw new Error(`${MAX_RETRIES_EXCEEDED_MESSAGE}, batch index, ${context.index}`);
+ });
+ console.log('tryable items', tryable);
+ tryable.forEach((context) => {
+ this.queue = this.queue.concat(context);
+ if (context.timeout === 0) {
+ this.schedule(this.config.flushIntervalMillis);
+ return;
+ }
+
+ setTimeout(() => {
+ context.timeout = 0;
+ this.schedule(0);
+ }, context.timeout);
+ });
+
+ // this.saveEvents();
+ }
- if (event.event_type === 'session_end' && events.length) {
- const payload = {
- api_key: amplitudeConfig?.apiKey,
- device_id: amplitudeConfig?.deviceId,
- session_id: amplitudeConfig?.sessionId,
- start_timestamp: amplitudeConfig?.sessionId,
- events_batch: {
- version: 1,
- events,
- seq_number: 1,
- },
- };
-
- const options: RequestInit = {
- headers: {
- 'Content-Type': 'application/json',
- Accept: '*/*',
- },
- body: JSON.stringify(payload),
- method: 'POST',
- };
- events = [];
- const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
- console.log('res', res);
+ schedule(timeout: number) {
+ if (this.scheduled) return;
+ this.scheduled = setTimeout(() => {
+ void this.flush(true).then(() => {
+ if (this.queue.length > 0) {
+ this.schedule(timeout);
+ }
+ });
+ }, timeout);
+ }
+
+ async flush(useRetry = false) {
+ const list: SessionReplayContext[] = [];
+ const later: SessionReplayContext[] = [];
+ this.queue.forEach((context) => (context.timeout === 0 ? list.push(context) : later.push(context)));
+ this.queue = later;
+
+ if (this.scheduled) {
+ clearTimeout(this.scheduled);
+ this.scheduled = null;
+ }
+
+ // const batches = chunk(list, this.config.flushQueueSize); // todo do we need to chunk
+ try {
+ await Promise.all(list.map((context) => this.send(context, useRetry)));
+ } catch (e) {
+ this.config.loggerProvider.error(e);
+ }
+ }
+
+ async send(context: SessionReplayContext, useRetry = true) {
+ if (!this.config.apiKey) {
+ return Promise.reject(new Error(MISSING_API_KEY_MESSAGE));
+ }
+ const payload = {
+ api_key: this.config.apiKey,
+ device_id: this.config.deviceId,
+ session_id: this.config.sessionId,
+ start_timestamp: this.config.sessionId,
+ events_batch: {
+ version: 1,
+ events: context.events,
+ seq_number: context.index,
+ },
+ };
+ try {
+ const options: RequestInit = {
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: '*/*',
+ },
+ body: JSON.stringify(payload),
+ method: 'POST',
+ };
+ const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
+ console.log('res', res);
+ if (res === null) {
+ return Promise.reject(new Error(UNEXPECTED_ERROR_MESSAGE));
}
+ if (!useRetry) {
+ let responseBody = '';
+ try {
+ responseBody = JSON.stringify(res.body, null, 2);
+ } catch {
+ // to avoid crash, but don't care about the error, add comment to avoid empty block lint error
+ }
+ return Promise.resolve(`${res.status}: ${responseBody}`);
+ }
+ return this.handleReponse(res, context);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ }
+
+ async handleReponse(res: Response, context: SessionReplayContext) {
+ const { status } = res;
+ const parsedStatus = new BaseTransport().buildStatus(status);
+ switch (parsedStatus) {
+ case Status.Success:
+ return this.handleSuccessResponse(res);
+ break;
+
+ default:
+ return this.handleOtherResponse(context);
+ }
+ }
- return event;
- },
- };
+ async handleSuccessResponse(res: Response) {
+ return Promise.resolve(`${res.status}`);
+ }
- return plugin;
-};
+ async handleOtherResponse(context: SessionReplayContext) {
+ try {
+ this.addToQueue({
+ ...context,
+ timeout: context.attempts * this.retryTimeout,
+ });
+ } catch (e) {
+ return Promise.reject(new Error(e as string));
+ }
+ return Promise.resolve(`Retrying batch at index ${context.index}`);
+ }
+}
diff --git a/packages/plugin-session-replay/src/typings/session-replay.ts b/packages/plugin-session-replay/src/typings/session-replay.ts
index cc9f7bd26..f6cb6dd17 100644
--- a/packages/plugin-session-replay/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay/src/typings/session-replay.ts
@@ -6,3 +6,13 @@ export interface Options {}
export interface CreateSessionReplayPlugin {
(options?: Options): EnrichmentPlugin;
}
+
+export type Events = string[];
+
+export interface SessionReplayContext {
+ events: Events;
+ index: number;
+ attempts: number;
+ timeout: number;
+ //todo callback?
+}
From e85de00d868f88c85245b17311755f0fc315c513 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 31 May 2023 11:28:01 -0400
Subject: [PATCH 024/214] feat(plugins): batching requests at max list size
---
packages/plugin-session-replay/src/helpers.ts | 14 +++++++
.../src/session-replay.ts | 42 +++++++++++--------
2 files changed, 38 insertions(+), 18 deletions(-)
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
index de5a7638d..9303df6d1 100644
--- a/packages/plugin-session-replay/src/helpers.ts
+++ b/packages/plugin-session-replay/src/helpers.ts
@@ -1,3 +1,17 @@
+const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
+const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+// const MAX_EVENT_LIST_SIZE_IN_BYTES = 350000
+
+export const shouldSplitEventsList = (eventsList: string[], nextEventString: string): boolean => {
+ const sizeOfNextEvent = new Blob([nextEventString]).size;
+ const sizeOfEventsList = new Blob(eventsList).size;
+ console.log('sizeOfEventsList', sizeOfEventsList);
+ if (sizeOfEventsList + sizeOfNextEvent >= MAX_EVENT_LIST_SIZE_IN_BYTES) {
+ return true;
+ }
+ return false;
+};
+
// Creates an array of elements split into groups the length of size.
// If array can't be split evenly, the final chunk will be the remaining elements.
// Works similary as https://lodash.com/docs/4.17.15#chunk
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index d51e7b784..c15bcbc21 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -1,6 +1,7 @@
import { BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
import { record } from 'rrweb';
+import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, MISSING_API_KEY_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import { SessionReplayContext } from './typings/session-replay';
@@ -20,45 +21,52 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
async setup(config: BrowserConfig) {
console.log('setup called', config);
- this.events = [];
+ this.events = [[]];
this.config = config;
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
}
async execute(event: Event) {
- // console.log('event', event);
// todo: this should be a constant/type
if (event.event_type === 'session_start') {
- this.events.push([]);
+ this.events = [[]];
console.log('starting new session', this.events);
record({
emit: (event) => {
- console.log('events in emit', this.events);
- this.events[this.events.length - 1].push(JSON.stringify(event));
+ const eventString = JSON.stringify(event);
+ const shouldSplit = shouldSplitEventsList(this.events[this.events.length - 1], eventString);
+ if (shouldSplit) {
+ this.sendEventsList(this.events[this.events.length - 1], this.events.length - 1);
+ this.events.push([]);
+ }
+ this.events[this.events.length - 1].push(eventString);
},
maskAllInputs: true,
});
} else if (event.event_type === 'session_end' && this.events.length) {
console.log('ending session', this.events);
- try {
- this.addToQueue({
- events: this.events[this.events.length - 1],
- index: this.events.length - 1,
- attempts: 0,
- timeout: 0,
- });
- } catch (e) {
- this.config.loggerProvider.error(e);
- }
+ this.sendEventsList(this.events[this.events.length - 1], this.events.length - 1);
}
return event;
}
+ sendEventsList(events: string[], index: number) {
+ try {
+ this.addToQueue({
+ events,
+ index,
+ attempts: 0,
+ timeout: 0,
+ });
+ } catch (e) {
+ this.config.loggerProvider.error(e);
+ }
+ }
+
addToQueue(...list: SessionReplayContext[]) {
const tryable = list.filter((context) => {
- console.log('this.config.flushMaxRetries', this.config.flushMaxRetries);
if (context.attempts < this.config.flushMaxRetries) {
context.attempts += 1;
return true;
@@ -78,8 +86,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.schedule(0);
}, context.timeout);
});
-
- // this.saveEvents();
}
schedule(timeout: number) {
From 07889568f4f8e269825db077393c34857a2ccdfe Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 1 Jun 2023 12:36:52 -0400
Subject: [PATCH 025/214] feat(plugins): implementing indexeddb storage for
session replay
---
packages/plugin-session-replay/package.json | 1 +
packages/plugin-session-replay/src/helpers.ts | 7 +-
.../plugin-session-replay/src/messages.ts | 1 +
.../src/session-replay.ts | 226 ++++++++++++------
.../src/typings/session-replay.ts | 11 +-
yarn.lock | 5 +
6 files changed, 176 insertions(+), 75 deletions(-)
diff --git a/packages/plugin-session-replay/package.json b/packages/plugin-session-replay/package.json
index d83cb4bc2..be02e5817 100644
--- a/packages/plugin-session-replay/package.json
+++ b/packages/plugin-session-replay/package.json
@@ -38,6 +38,7 @@
"dependencies": {
"@amplitude/analytics-client-common": "^1.0.0-beta.0",
"@amplitude/analytics-types": "^1.0.0-beta.0",
+ "idb-keyval": "^6.2.1",
"rrweb": "^2.0.0-alpha.4",
"tslib": "^2.4.1"
},
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
index 9303df6d1..49e7ae1ae 100644
--- a/packages/plugin-session-replay/src/helpers.ts
+++ b/packages/plugin-session-replay/src/helpers.ts
@@ -1,11 +1,10 @@
-const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
-const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-// const MAX_EVENT_LIST_SIZE_IN_BYTES = 350000
+// const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
+// const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+const MAX_EVENT_LIST_SIZE_IN_BYTES = 80000;
export const shouldSplitEventsList = (eventsList: string[], nextEventString: string): boolean => {
const sizeOfNextEvent = new Blob([nextEventString]).size;
const sizeOfEventsList = new Blob(eventsList).size;
- console.log('sizeOfEventsList', sizeOfEventsList);
if (sizeOfEventsList + sizeOfNextEvent >= MAX_EVENT_LIST_SIZE_IN_BYTES) {
return true;
}
diff --git a/packages/plugin-session-replay/src/messages.ts b/packages/plugin-session-replay/src/messages.ts
index adce6761f..898cdc023 100644
--- a/packages/plugin-session-replay/src/messages.ts
+++ b/packages/plugin-session-replay/src/messages.ts
@@ -5,3 +5,4 @@ export const OPT_OUT_MESSAGE = 'Event skipped due to optOut config';
export const MISSING_API_KEY_MESSAGE = 'Session replay event batch rejected due to missing API key';
export const INVALID_API_KEY = 'Invalid API key';
export const CLIENT_NOT_INITIALIZED = 'Client not initialized';
+export const STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index c15bcbc21..8cfea7772 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -1,12 +1,13 @@
-import { BaseTransport } from '@amplitude/analytics-core';
+import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
+import { get, update } from 'idb-keyval';
import { record } from 'rrweb';
import { shouldSplitEventsList } from './helpers';
-import { MAX_RETRIES_EXCEEDED_MESSAGE, MISSING_API_KEY_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
-import { SessionReplayContext } from './typings/session-replay';
-
-export const SESSION_REPLAY_SERVER_URL = 'https://api2.amplitude.com/sessions/track';
+import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE } from './messages';
+import { Events, IDBStore, SessionReplayContext } from './typings/session-replay';
+const SESSION_REPLAY_SERVER_URL = 'https://api2.amplitude.com/sessions/track';
+const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
export class SessionReplayPlugin implements EnrichmentPlugin {
name = '@amplitude/plugin-session-replay';
type = PluginType.ENRICHMENT as const;
@@ -14,66 +15,104 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
config: BrowserConfig;
+ storageKey = '';
retryTimeout = 1000;
- events: string[][] = [];
+ events: Events = [];
+ currentSequenceId = 0;
private scheduled: ReturnType | null = null;
queue: SessionReplayContext[] = [];
async setup(config: BrowserConfig) {
- console.log('setup called', config);
- this.events = [[]];
+ config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
this.config = config;
+ this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
- config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ await this.emptyStoreAndReset();
+
+ this.recordEvents();
}
async execute(event: Event) {
- // todo: this should be a constant/type
- if (event.event_type === 'session_start') {
- this.events = [[]];
- console.log('starting new session', this.events);
- record({
- emit: (event) => {
- const eventString = JSON.stringify(event);
- const shouldSplit = shouldSplitEventsList(this.events[this.events.length - 1], eventString);
- if (shouldSplit) {
- this.sendEventsList(this.events[this.events.length - 1], this.events.length - 1);
- this.events.push([]);
- }
- this.events[this.events.length - 1].push(eventString);
- },
- maskAllInputs: true,
- });
- } else if (event.event_type === 'session_end' && this.events.length) {
- console.log('ending session', this.events);
- this.sendEventsList(this.events[this.events.length - 1], this.events.length - 1);
+ // TODO: this should be a constant/type
+ if (event.event_type === 'session_end') {
+ if (event.session_id) {
+ this.sendEventsList({
+ events: this.events,
+ sequenceId: this.currentSequenceId,
+ sessionId: event.session_id,
+ });
+ }
+ this.events = [];
+ this.currentSequenceId = 0;
}
return event;
}
- sendEventsList(events: string[], index: number) {
- try {
- this.addToQueue({
- events,
- index,
- attempts: 0,
- timeout: 0,
- });
- } catch (e) {
- this.config.loggerProvider.error(e);
+ async emptyStoreAndReset() {
+ const storedReplaySessions = await this.getAllSessionEventsFromStore();
+ if (storedReplaySessions) {
+ for (const sessionId in storedReplaySessions) {
+ const storedReplayEvents = storedReplaySessions[sessionId];
+ if (storedReplayEvents.events.length) {
+ this.sendEventsList({
+ events: storedReplayEvents.events,
+ sequenceId: storedReplayEvents.sequenceId,
+ sessionId: parseInt(sessionId, 10),
+ });
+ }
+ }
+ this.events = [];
+ const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
+ this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
+ this.storeEventsForSession([], this.currentSequenceId);
}
}
+ recordEvents() {
+ record({
+ emit: (event) => {
+ const eventString = JSON.stringify(event);
+ const shouldSplit = shouldSplitEventsList(this.events, eventString);
+ if (shouldSplit) {
+ this.sendEventsList({
+ events: this.events,
+ sequenceId: this.currentSequenceId,
+ sessionId: this.config.sessionId as number,
+ });
+ this.events = [];
+ this.currentSequenceId++;
+ }
+ this.events.push(eventString);
+ this.storeEventsForSession(this.events, this.currentSequenceId);
+ },
+ maskAllInputs: true,
+ });
+ }
+
+ sendEventsList({ events, sequenceId, sessionId }: { events: string[]; sequenceId: number; sessionId: number }) {
+ this.addToQueue({
+ events,
+ sequenceId,
+ attempts: 0,
+ timeout: 0,
+ sessionId,
+ });
+ }
+
addToQueue(...list: SessionReplayContext[]) {
const tryable = list.filter((context) => {
if (context.attempts < this.config.flushMaxRetries) {
context.attempts += 1;
return true;
}
- throw new Error(`${MAX_RETRIES_EXCEEDED_MESSAGE}, batch index, ${context.index}`);
+ // TODO: should we keep events in indexdb to retry on a refresh? Whats destination behavior?
+ this.completeRequest({
+ context,
+ err: `${MAX_RETRIES_EXCEEDED_MESSAGE}, batch sequence id, ${context.sequenceId}`,
+ });
+ return false;
});
- console.log('tryable items', tryable);
tryable.forEach((context) => {
this.queue = this.queue.concat(context);
if (context.timeout === 0) {
@@ -110,27 +149,19 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.scheduled = null;
}
- // const batches = chunk(list, this.config.flushQueueSize); // todo do we need to chunk
- try {
- await Promise.all(list.map((context) => this.send(context, useRetry)));
- } catch (e) {
- this.config.loggerProvider.error(e);
- }
+ await Promise.all(list.map((context) => this.send(context, useRetry)));
}
async send(context: SessionReplayContext, useRetry = true) {
- if (!this.config.apiKey) {
- return Promise.reject(new Error(MISSING_API_KEY_MESSAGE));
- }
const payload = {
api_key: this.config.apiKey,
device_id: this.config.deviceId,
- session_id: this.config.sessionId,
- start_timestamp: this.config.sessionId,
+ session_id: context.sessionId,
+ start_timestamp: context.sessionId,
events_batch: {
version: 1,
events: context.events,
- seq_number: context.index,
+ seq_number: context.sequenceId,
},
};
try {
@@ -143,9 +174,8 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
method: 'POST',
};
const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
- console.log('res', res);
if (res === null) {
- return Promise.reject(new Error(UNEXPECTED_ERROR_MESSAGE));
+ this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE, removeEvents: false });
}
if (!useRetry) {
let responseBody = '';
@@ -154,40 +184,98 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
} catch {
// to avoid crash, but don't care about the error, add comment to avoid empty block lint error
}
- return Promise.resolve(`${res.status}: ${responseBody}`);
+ this.completeRequest({ context, success: `${res.status}: ${responseBody}` });
+ } else {
+ this.handleReponse(res, context);
}
- return this.handleReponse(res, context);
} catch (e) {
- return Promise.reject(e);
+ this.completeRequest({ context, err: e as string, removeEvents: false });
}
}
- async handleReponse(res: Response, context: SessionReplayContext) {
+ handleReponse(res: Response, context: SessionReplayContext) {
const { status } = res;
const parsedStatus = new BaseTransport().buildStatus(status);
switch (parsedStatus) {
case Status.Success:
- return this.handleSuccessResponse(res);
+ this.handleSuccessResponse(res, context);
break;
-
default:
- return this.handleOtherResponse(context);
+ this.handleOtherResponse(context);
}
}
- async handleSuccessResponse(res: Response) {
- return Promise.resolve(`${res.status}`);
+ handleSuccessResponse(res: Response, context: SessionReplayContext) {
+ this.completeRequest({ context, success: `${res.status}` });
}
- async handleOtherResponse(context: SessionReplayContext) {
+ handleOtherResponse(context: SessionReplayContext) {
+ this.addToQueue({
+ ...context,
+ timeout: context.attempts * this.retryTimeout,
+ });
+ }
+
+ async getAllSessionEventsFromStore() {
try {
- this.addToQueue({
- ...context,
- timeout: context.attempts * this.retryTimeout,
+ const storedReplaySessionContexts: IDBStore | undefined = await get(this.storageKey);
+
+ return storedReplaySessionContexts;
+ } catch (e) {
+ this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
+ }
+ return undefined;
+ }
+
+ storeEventsForSession(events: Events, sequenceId: number) {
+ try {
+ void update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
+ if (this.config.sessionId) {
+ return {
+ ...sessionMap,
+ [this.config.sessionId]: {
+ events: events,
+ sequenceId,
+ },
+ };
+ }
+ return sessionMap || {};
});
} catch (e) {
- return Promise.reject(new Error(e as string));
+ this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
+ }
+ }
+
+ removeSessionEventsStore(sessionId: number | undefined) {
+ if (sessionId) {
+ try {
+ void update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
+ sessionMap = sessionMap || {};
+ delete sessionMap[sessionId];
+ return sessionMap;
+ });
+ } catch (e) {
+ this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
+ }
+ }
+ }
+
+ completeRequest({
+ context,
+ err,
+ success,
+ removeEvents = true,
+ }: {
+ context: SessionReplayContext;
+ err?: string;
+ success?: string;
+ removeEvents?: boolean;
+ }) {
+ removeEvents && this.removeSessionEventsStore(context.sessionId);
+ if (err) {
+ this.config.loggerProvider.error(err);
+ } else if (success) {
+ this.config.loggerProvider.log(success);
}
- return Promise.resolve(`Retrying batch at index ${context.index}`);
}
}
diff --git a/packages/plugin-session-replay/src/typings/session-replay.ts b/packages/plugin-session-replay/src/typings/session-replay.ts
index f6cb6dd17..12329dc8e 100644
--- a/packages/plugin-session-replay/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay/src/typings/session-replay.ts
@@ -11,8 +11,15 @@ export type Events = string[];
export interface SessionReplayContext {
events: Events;
- index: number;
+ sequenceId: number;
attempts: number;
timeout: number;
- //todo callback?
+ sessionId: number;
+}
+
+export interface IDBStore {
+ [sessionId: number]: {
+ events: Events;
+ sequenceId: number;
+ };
}
diff --git a/yarn.lock b/yarn.lock
index 33a296cca..7770d2e1e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7378,6 +7378,11 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+idb-keyval@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33"
+ integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==
+
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
From 95305010618222f9efb9d807c7acf2c389597d9e Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 5 Jun 2023 11:21:14 -0400
Subject: [PATCH 026/214] feat(plugins): add indentifier to session start
---
packages/plugin-session-replay/src/helpers.ts | 22 +++----------------
.../src/session-replay.ts | 9 ++++++--
2 files changed, 10 insertions(+), 21 deletions(-)
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
index 49e7ae1ae..e2c0c189b 100644
--- a/packages/plugin-session-replay/src/helpers.ts
+++ b/packages/plugin-session-replay/src/helpers.ts
@@ -1,6 +1,6 @@
-// const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
-// const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-const MAX_EVENT_LIST_SIZE_IN_BYTES = 80000;
+const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
+const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+// const MAX_EVENT_LIST_SIZE_IN_BYTES = 80000;
export const shouldSplitEventsList = (eventsList: string[], nextEventString: string): boolean => {
const sizeOfNextEvent = new Blob([nextEventString]).size;
@@ -10,19 +10,3 @@ export const shouldSplitEventsList = (eventsList: string[], nextEventString: str
}
return false;
};
-
-// Creates an array of elements split into groups the length of size.
-// If array can't be split evenly, the final chunk will be the remaining elements.
-// Works similary as https://lodash.com/docs/4.17.15#chunk
-
-export const chunk = (arr: T[], size: number) => {
- const chunkSize = Math.max(size, 1);
- return arr.reduce((chunks, element, index) => {
- const chunkIndex = Math.floor(index / chunkSize);
- if (!chunks[chunkIndex]) {
- chunks[chunkIndex] = [];
- }
- chunks[chunkIndex].push(element);
- return chunks;
- }, []);
-};
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index 8cfea7772..570453fdf 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -34,7 +34,12 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
async execute(event: Event) {
// TODO: this should be a constant/type
- if (event.event_type === 'session_end') {
+ if (event.event_type === 'session_start') {
+ event.event_properties = {
+ ...event.event_properties,
+ session_replay_enabled: true,
+ };
+ } else if (event.event_type === 'session_end') {
if (event.session_id) {
this.sendEventsList({
events: this.events,
@@ -45,7 +50,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.events = [];
this.currentSequenceId = 0;
}
-
return event;
}
@@ -84,6 +88,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.currentSequenceId++;
}
this.events.push(eventString);
+ console.log('this.events', this.events);
this.storeEventsForSession(this.events, this.currentSequenceId);
},
maskAllInputs: true,
From 86ae48b0f487d81ad9ff6518a03f77fc4e142bb8 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 6 Jun 2023 10:13:25 -0400
Subject: [PATCH 027/214] feat(plugins): fix for first event fired for session
replay
---
.../plugin-session-replay/src/session-replay.ts | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index 570453fdf..0456f3b69 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -21,9 +21,14 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
currentSequenceId = 0;
private scheduled: ReturnType | null = null;
queue: SessionReplayContext[] = [];
+ shouldLogReplayEnabled = false;
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ if (!config.sessionId) {
+ this.shouldLogReplayEnabled = true;
+ }
+
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
@@ -32,13 +37,14 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.recordEvents();
}
- async execute(event: Event) {
+ execute(event: Event) {
// TODO: this should be a constant/type
- if (event.event_type === 'session_start') {
+ if (event.event_type === 'session_start' || this.shouldLogReplayEnabled) {
event.event_properties = {
...event.event_properties,
session_replay_enabled: true,
};
+ this.shouldLogReplayEnabled = false;
} else if (event.event_type === 'session_end') {
if (event.session_id) {
this.sendEventsList({
@@ -50,7 +56,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.events = [];
this.currentSequenceId = 0;
}
- return event;
+ return Promise.resolve(event);
}
async emptyStoreAndReset() {
@@ -88,7 +94,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.currentSequenceId++;
}
this.events.push(eventString);
- console.log('this.events', this.events);
this.storeEventsForSession(this.events, this.currentSequenceId);
},
maskAllInputs: true,
From c30e3dbec8781c8906394e586c5516109f6b1192 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 8 Jun 2023 10:29:33 -0700
Subject: [PATCH 028/214] feat(plugins): restart the recording on session start
---
.../src/session-replay.ts | 26 +++++++++----------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index 0456f3b69..23002a997 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -6,7 +6,7 @@ import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import { Events, IDBStore, SessionReplayContext } from './typings/session-replay';
-const SESSION_REPLAY_SERVER_URL = 'https://api2.amplitude.com/sessions/track';
+const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
export class SessionReplayPlugin implements EnrichmentPlugin {
name = '@amplitude/plugin-session-replay';
@@ -21,13 +21,10 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
currentSequenceId = 0;
private scheduled: ReturnType | null = null;
queue: SessionReplayContext[] = [];
- shouldLogReplayEnabled = false;
+ stopRecordingEvents: ReturnType | null = null;
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
- if (!config.sessionId) {
- this.shouldLogReplayEnabled = true;
- }
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
@@ -38,13 +35,14 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
}
execute(event: Event) {
- // TODO: this should be a constant/type
- if (event.event_type === 'session_start' || this.shouldLogReplayEnabled) {
- event.event_properties = {
- ...event.event_properties,
- session_replay_enabled: true,
- };
- this.shouldLogReplayEnabled = false;
+ event.event_properties = {
+ ...event.event_properties,
+ session_replay_enabled: true,
+ };
+
+ if (event.event_type === 'session_start' && this.stopRecordingEvents) {
+ this.stopRecordingEvents();
+ this.recordEvents();
} else if (event.event_type === 'session_end') {
if (event.session_id) {
this.sendEventsList({
@@ -80,8 +78,9 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
}
recordEvents() {
- record({
+ this.stopRecordingEvents = record({
emit: (event) => {
+ console.log('event', event);
const eventString = JSON.stringify(event);
const shouldSplit = shouldSplitEventsList(this.events, eventString);
if (shouldSplit) {
@@ -96,7 +95,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.events.push(eventString);
this.storeEventsForSession(this.events, this.currentSequenceId);
},
- maskAllInputs: true,
});
}
From 216f4d38d5863d133e19c9d89c0c6004e6cce294 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 21 Jun 2023 10:11:08 -0400
Subject: [PATCH 029/214] feat(plugins): session replay setting up tests
---
.../src/session-replay.ts | 1 -
.../test/session-replay.test.ts | 853 ++++++++++++++++++
2 files changed, 853 insertions(+), 1 deletion(-)
create mode 100644 packages/plugin-session-replay/test/session-replay.test.ts
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index 23002a997..4891e8a06 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -80,7 +80,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
recordEvents() {
this.stopRecordingEvents = record({
emit: (event) => {
- console.log('event', event);
const eventString = JSON.stringify(event);
const shouldSplit = shouldSplitEventsList(this.events, eventString);
if (shouldSplit) {
diff --git a/packages/plugin-session-replay/test/session-replay.test.ts b/packages/plugin-session-replay/test/session-replay.test.ts
new file mode 100644
index 000000000..e067943ba
--- /dev/null
+++ b/packages/plugin-session-replay/test/session-replay.test.ts
@@ -0,0 +1,853 @@
+import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
+import { Logger } from '@amplitude/analytics-core';
+import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
+import { SessionReplayPlugin } from '../src/session-replay';
+
+describe('SessionReplayPlugin', () => {
+ const mockConfig: BrowserConfig = {
+ apiKey: 'static_key',
+ flushIntervalMillis: 0,
+ flushMaxRetries: 0,
+ flushQueueSize: 0,
+ logLevel: LogLevel.None,
+ loggerProvider: new Logger(),
+ optOut: false,
+ serverUrl: 'url',
+ transportProvider: new FetchTransport(),
+ useBatch: false,
+
+ cookieExpiration: 365,
+ cookieSameSite: 'Lax',
+ cookieSecure: false,
+ cookieStorage: new CookieStorage(),
+ cookieUpgrade: true,
+ disableCookies: false,
+ domain: '.amplitude.com',
+ sessionTimeout: 30 * 60 * 1000,
+ trackingOptions: {
+ ipAddress: true,
+ language: true,
+ platform: true,
+ },
+ };
+ describe('setup', () => {
+ test('should setup plugin', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ await sessionReplay.setup(mockConfig);
+ expect(sessionReplay.config.transportProvider).toBeDefined();
+ expect(sessionReplay.config.serverUrl).toBe('url');
+ expect(sessionReplay.config.flushMaxRetries).toBe(0);
+ expect(sessionReplay.config.flushQueueSize).toBe(0);
+ expect(sessionReplay.config.flushIntervalMillis).toBe(0);
+ expect(sessionReplay.storageKey).toBe('');
+ });
+
+ // test('should read from storage', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const config = useDefaultConfig();
+ // const event = {
+ // event_type: 'hello',
+ // };
+ // config.storageProvider = {
+ // isEnabled: async () => true,
+ // get: async () => undefined,
+ // set: async () => undefined,
+ // remove: async () => undefined,
+ // reset: async () => undefined,
+ // getRaw: async () => undefined,
+ // };
+ // const get = jest.spyOn(config.storageProvider, 'get').mockResolvedValueOnce([event]);
+ // const execute = jest.spyOn(destination, 'execute').mockReturnValueOnce(
+ // Promise.resolve({
+ // event,
+ // message: Status.Success,
+ // code: 200,
+ // }),
+ // );
+ // await destination.setup(config);
+ // expect(get).toHaveBeenCalledTimes(1);
+ // expect(execute).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should be ok with undefined storage', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const config = useDefaultConfig();
+ // config.storageProvider = undefined;
+ // const execute = jest.spyOn(destination, 'execute');
+ // await destination.setup(config);
+ // expect(execute).toHaveBeenCalledTimes(0);
+ // });
+ });
+
+ // describe('execute', () => {
+ // test('should execute plugin', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const addToQueue = jest.spyOn(destination, 'addToQueue').mockImplementation((context: DestinationContext) => {
+ // context.callback({ event, code: 200, message: Status.Success });
+ // });
+ // await destination.execute(event);
+ // expect(addToQueue).toHaveBeenCalledTimes(1);
+ // });
+ // });
+
+ // describe('addToQueue', () => {
+ // test('should add to queue and schedule a flush', () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.config = {
+ // ...useDefaultConfig(),
+ // flushIntervalMillis: 0,
+ // };
+ // const schedule = jest.spyOn(destination, 'schedule').mockReturnValueOnce(undefined);
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const context = {
+ // event,
+ // callback: () => undefined,
+ // attempts: 0,
+ // timeout: 0,
+ // };
+ // destination.addToQueue(context);
+ // expect(schedule).toHaveBeenCalledTimes(1);
+ // expect(context.attempts).toBe(1);
+ // });
+ // });
+
+ // describe('schedule', () => {
+ // beforeEach(() => {
+ // jest.useFakeTimers();
+ // });
+
+ // afterEach(() => {
+ // jest.useRealTimers();
+ // });
+
+ // test('should schedule a flush', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ // (destination as any).scheduled = null;
+ // destination.queue = [
+ // {
+ // event: { event_type: 'event_type' },
+ // attempts: 0,
+ // callback: () => undefined,
+ // timeout: 0,
+ // },
+ // ];
+ // const flush = jest
+ // .spyOn(destination, 'flush')
+ // .mockImplementationOnce(() => {
+ // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ // (destination as any).scheduled = null;
+ // return Promise.resolve(undefined);
+ // })
+ // .mockReturnValueOnce(Promise.resolve(undefined));
+ // destination.schedule(0);
+ // // exhause first setTimeout
+ // jest.runAllTimers();
+ // // wait for next tick to call nested setTimeout
+ // await Promise.resolve();
+ // // exhause nested setTimeout
+ // jest.runAllTimers();
+ // expect(flush).toHaveBeenCalledTimes(2);
+ // });
+
+ // test('should not schedule if one is already in progress', () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ // (destination as any).scheduled = setTimeout(jest.fn, 0);
+ // const flush = jest.spyOn(destination, 'flush').mockReturnValueOnce(Promise.resolve(undefined));
+ // destination.schedule(0);
+ // expect(flush).toHaveBeenCalledTimes(0);
+ // });
+ // });
+
+ // describe('flush', () => {
+ // test('should get batch and call send', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 1,
+ // };
+ // destination.queue = [
+ // {
+ // event: { event_type: 'event_type' },
+ // attempts: 0,
+ // callback: () => undefined,
+ // timeout: 0,
+ // },
+ // ];
+ // const send = jest.spyOn(destination, 'send').mockReturnValueOnce(Promise.resolve());
+ // const result = await destination.flush();
+ // expect(destination.queue).toEqual([]);
+ // expect(result).toBe(undefined);
+ // expect(send).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should send with queue', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.config = {
+ // ...useDefaultConfig(),
+ // };
+ // destination.queue = [
+ // {
+ // event: { event_type: 'event_type' },
+ // attempts: 0,
+ // callback: () => undefined,
+ // timeout: 0,
+ // },
+ // ];
+ // const send = jest.spyOn(destination, 'send').mockReturnValueOnce(Promise.resolve());
+ // const result = await destination.flush();
+ // expect(destination.queue).toEqual([]);
+ // expect(result).toBe(undefined);
+ // expect(send).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should send later', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.config = {
+ // ...useDefaultConfig(),
+ // };
+ // destination.queue = [
+ // {
+ // event: { event_type: 'event_type' },
+ // attempts: 0,
+ // callback: () => undefined,
+ // timeout: 1000,
+ // },
+ // ];
+ // const send = jest.spyOn(destination, 'send').mockReturnValueOnce(Promise.resolve());
+ // const result = await destination.flush();
+ // expect(destination.queue).toEqual([
+ // {
+ // event: { event_type: 'event_type' },
+ // attempts: 0,
+ // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ // callback: expect.any(Function),
+ // timeout: 1000,
+ // },
+ // ]);
+ // expect(result).toBe(undefined);
+ // expect(send).toHaveBeenCalledTimes(0);
+ // });
+ // });
+
+ // describe('send', () => {
+ // test('should include min id length', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const callback = jest.fn();
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const context = {
+ // attempts: 0,
+ // callback,
+ // event,
+ // timeout: 0,
+ // };
+ // const transportProvider = {
+ // send: jest.fn().mockImplementationOnce((_url: string, payload: Payload) => {
+ // expect(payload.options?.min_id_length).toBe(10);
+ // return Promise.resolve({
+ // status: Status.Success,
+ // statusCode: 200,
+ // body: {
+ // eventsIngested: 1,
+ // payloadSizeBytes: 1,
+ // serverUploadTime: 1,
+ // },
+ // });
+ // }),
+ // };
+ // await destination.setup({
+ // ...useDefaultConfig(),
+ // transportProvider,
+ // apiKey: API_KEY,
+ // minIdLength: 10,
+ // });
+ // await destination.send([context]);
+ // expect(callback).toHaveBeenCalledTimes(1);
+ // expect(callback).toHaveBeenCalledWith({
+ // event,
+ // code: 200,
+ // message: SUCCESS_MESSAGE,
+ // });
+ // });
+
+ // test('should not include extra', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const callback = jest.fn();
+ // const event = {
+ // event_type: 'event_type',
+ // extra: { 'extra-key': 'extra-value' },
+ // };
+ // const context = {
+ // attempts: 0,
+ // callback,
+ // event,
+ // timeout: 0,
+ // };
+ // const transportProvider = {
+ // send: jest.fn().mockImplementationOnce((_url: string, payload: Payload) => {
+ // expect(payload.options?.min_id_length).toBe(10);
+ // expect(payload.events.some((event) => !!event.extra)).toBeFalsy();
+ // return Promise.resolve({
+ // status: Status.Success,
+ // statusCode: 200,
+ // body: {
+ // eventsIngested: 1,
+ // payloadSizeBytes: 1,
+ // serverUploadTime: 1,
+ // },
+ // });
+ // }),
+ // };
+ // await destination.setup({
+ // ...useDefaultConfig(),
+ // transportProvider,
+ // apiKey: API_KEY,
+ // minIdLength: 10,
+ // });
+ // await destination.send([context]);
+ // expect(callback).toHaveBeenCalledTimes(1);
+ // expect(callback).toHaveBeenCalledWith({
+ // event,
+ // code: 200,
+ // message: SUCCESS_MESSAGE,
+ // });
+ // });
+
+ // test('should not retry', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const callback = jest.fn();
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const context = {
+ // attempts: 0,
+ // callback,
+ // event,
+ // timeout: 0,
+ // };
+ // const transportProvider = {
+ // send: jest.fn().mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.Failed,
+ // statusCode: 500,
+ // });
+ // }),
+ // };
+ // await destination.setup({
+ // ...useDefaultConfig(),
+ // transportProvider,
+ // apiKey: API_KEY,
+ // });
+ // await destination.send([context], false);
+ // expect(callback).toHaveBeenCalledTimes(1);
+ // expect(callback).toHaveBeenCalledWith({
+ // event,
+ // code: 500,
+ // message: Status.Failed,
+ // });
+ // });
+
+ // test('should provide error details', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const callback = jest.fn();
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const context = {
+ // attempts: 0,
+ // callback,
+ // event,
+ // timeout: 0,
+ // };
+ // const body = {
+ // error: 'Request missing required field',
+ // missingField: 'user_id',
+ // };
+ // const transportProvider = {
+ // send: jest.fn().mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.Invalid,
+ // statusCode: 400,
+ // body,
+ // });
+ // }),
+ // };
+ // await destination.setup({
+ // ...useDefaultConfig(),
+ // transportProvider,
+ // apiKey: API_KEY,
+ // });
+ // await destination.send([context], false);
+ // expect(callback).toHaveBeenCalledTimes(1);
+ // expect(callback).toHaveBeenCalledWith({
+ // event,
+ // code: 400,
+ // message: `${Status.Invalid}: ${JSON.stringify(body, null, 2)}`,
+ // });
+ // });
+
+ // test('should handle no api key', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const callback = jest.fn();
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const context = {
+ // attempts: 0,
+ // callback,
+ // event,
+ // timeout: 0,
+ // };
+ // const transportProvider = {
+ // send: jest.fn().mockImplementationOnce(() => {
+ // throw new Error();
+ // }),
+ // };
+ // await destination.setup({
+ // ...useDefaultConfig(),
+ // transportProvider,
+ // apiKey: '',
+ // });
+ // await destination.send([context]);
+ // expect(callback).toHaveBeenCalledTimes(1);
+ // expect(callback).toHaveBeenCalledWith({
+ // event,
+ // code: 400,
+ // message: MISSING_API_KEY_MESSAGE,
+ // });
+ // });
+
+ // test('should handle unexpected error', async () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // const callback = jest.fn();
+ // const context = {
+ // attempts: 0,
+ // callback,
+ // event: {
+ // event_type: 'event_type',
+ // },
+ // timeout: 0,
+ // };
+ // const transportProvider = {
+ // send: jest.fn().mockImplementationOnce(() => {
+ // throw new Error();
+ // }),
+ // };
+ // await destination.setup({
+ // ...useDefaultConfig(),
+ // transportProvider,
+ // });
+ // await destination.send([context]);
+ // expect(callback).toHaveBeenCalledTimes(1);
+ // });
+ // });
+
+ // describe('saveEvents', () => {
+ // test('should save to storage provider', () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.config = useDefaultConfig();
+ // destination.config.storageProvider = {
+ // isEnabled: async () => true,
+ // get: async () => undefined,
+ // set: async () => undefined,
+ // remove: async () => undefined,
+ // reset: async () => undefined,
+ // getRaw: async () => undefined,
+ // };
+ // const set = jest.spyOn(destination.config.storageProvider, 'set').mockResolvedValueOnce(undefined);
+ // destination.saveEvents();
+ // expect(set).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should be ok with no storage provider', () => {
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.config = useDefaultConfig();
+ // destination.config.storageProvider = undefined;
+ // expect(destination.saveEvents()).toBe(undefined);
+ // });
+ // });
+
+ // describe('module level integration', () => {
+ // const successResponse = {
+ // status: Status.Success,
+ // statusCode: 200,
+ // body: {
+ // eventsIngested: 1,
+ // payloadSizeBytes: 1,
+ // serverUploadTime: 1,
+ // },
+ // };
+
+ // test('should handle unexpected error', async () => {
+ // class Http {
+ // send = jest.fn().mockImplementationOnce(() => {
+ // return Promise.resolve(null);
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const result = await destination.execute({
+ // event_type: 'event_type',
+ // });
+ // expect(result.code).toBe(0);
+ // expect(result.message).toBe(UNEXPECTED_ERROR_MESSAGE);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should not retry with invalid api key', async () => {
+ // class Http {
+ // send = jest.fn().mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.Invalid,
+ // statusCode: 400,
+ // body: {
+ // error: INVALID_API_KEY,
+ // },
+ // });
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.retryTimeout = 10;
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const results = await Promise.all([
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // ]);
+ // expect(results[0].code).toBe(400);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should handle retry for 400 error', async () => {
+ // class Http {
+ // send = jest
+ // .fn()
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.Invalid,
+ // statusCode: 400,
+ // body: {
+ // error: 'error',
+ // missingField: '',
+ // eventsWithInvalidFields: { a: [0] },
+ // eventsWithMissingFields: { b: [] },
+ // eventsWithInvalidIdLengths: {},
+ // silencedEvents: [],
+ // },
+ // });
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve(successResponse);
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.retryTimeout = 10;
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const results = await Promise.all([
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // ]);
+ // expect(results[0].code).toBe(400);
+ // expect(results[1].code).toBe(200);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(2);
+ // });
+
+ // test('should handle retry for 400 error with missing body field', async () => {
+ // class Http {
+ // send = jest.fn().mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.Invalid,
+ // statusCode: 400,
+ // body: {
+ // error: 'error',
+ // missingField: 'key',
+ // eventsWithInvalidFields: {},
+ // eventsWithMissingFields: {},
+ // silencedEvents: [],
+ // },
+ // });
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const results = await Promise.all([
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // ]);
+ // expect(results[0].code).toBe(400);
+ // expect(results[1].code).toBe(400);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should handle retry for 413 error with flushQueueSize of 1', async () => {
+ // class Http {
+ // send = jest.fn().mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.PayloadTooLarge,
+ // statusCode: 413,
+ // body: {
+ // error: 'error',
+ // },
+ // });
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 1,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const event = {
+ // event_type: 'event_type',
+ // };
+ // const result = await destination.execute(event);
+ // expect(result).toEqual({
+ // event,
+ // message: 'error',
+ // code: 413,
+ // });
+ // expect(transportProvider.send).toHaveBeenCalledTimes(1);
+ // });
+
+ // test('should handle retry for 413 error', async () => {
+ // class Http {
+ // send = jest
+ // .fn()
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.PayloadTooLarge,
+ // statusCode: 413,
+ // body: {
+ // error: 'error',
+ // },
+ // });
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve(successResponse);
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve(successResponse);
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.retryTimeout = 10;
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // await Promise.all([
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // ]);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(3);
+ // });
+
+ // test('should handle retry for 429 error', async () => {
+ // class Http {
+ // send = jest
+ // .fn()
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // status: Status.RateLimit,
+ // statusCode: 429,
+ // body: {
+ // error: 'error',
+ // epsThreshold: 1,
+ // throttledDevices: {},
+ // throttledUsers: {},
+ // exceededDailyQuotaDevices: {
+ // '1': 1,
+ // },
+ // exceededDailyQuotaUsers: {
+ // '2': 1,
+ // },
+ // throttledEvents: [0],
+ // },
+ // });
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve(successResponse);
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve(successResponse);
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.retryTimeout = 10;
+ // destination.throttleTimeout = 1;
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 4,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const results = await Promise.all([
+ // // throttled
+ // destination.execute({
+ // event_type: 'event_type',
+ // user_id: '0',
+ // device_id: '0',
+ // }),
+ // // exceed daily device quota
+ // destination.execute({
+ // event_type: 'event_type',
+ // user_id: '1',
+ // device_id: '1',
+ // }),
+ // // exceed daily user quota
+ // destination.execute({
+ // event_type: 'event_type',
+ // user_id: '2',
+ // device_id: '2',
+ // }),
+ // // success
+ // destination.execute({
+ // event_type: 'event_type',
+ // user_id: '3',
+ // device_id: '3',
+ // }),
+ // ]);
+ // expect(results[0].code).toBe(200);
+ // expect(results[1].code).toBe(429);
+ // expect(results[2].code).toBe(429);
+ // expect(results[3].code).toBe(200);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(2);
+ // });
+
+ // test('should handle retry for 500 error', async () => {
+ // class Http {
+ // send = jest
+ // .fn()
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // statusCode: 500,
+ // status: Status.Failed,
+ // });
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve(successResponse);
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.retryTimeout = 10;
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // await Promise.all([
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // ]);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(2);
+ // });
+
+ // test('should handle retry for 503 error', async () => {
+ // class Http {
+ // send = jest
+ // .fn()
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // statusCode: 500,
+ // status: Status.Failed,
+ // });
+ // })
+ // .mockImplementationOnce(() => {
+ // return Promise.resolve({
+ // statusCode: 500,
+ // status: Status.Failed,
+ // });
+ // });
+ // }
+ // const transportProvider = new Http();
+ // const sessionReplay = new SessionReplayPlugin();
+ // destination.retryTimeout = 10;
+ // const config = {
+ // ...useDefaultConfig(),
+ // flushMaxRetries: 1,
+ // flushQueueSize: 2,
+ // flushIntervalMillis: 500,
+ // transportProvider,
+ // };
+ // await destination.setup(config);
+ // const results = await Promise.all([
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // destination.execute({
+ // event_type: 'event_type',
+ // }),
+ // ]);
+ // expect(results[0].code).toBe(500);
+ // expect(results[1].code).toBe(500);
+ // expect(transportProvider.send).toHaveBeenCalledTimes(1);
+ // });
+ // });
+});
From 6211a3e33c215e429a005128e50323203369fc7a Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 22 Jun 2023 12:51:44 -0400
Subject: [PATCH 030/214] feat(plugins): add test coverage to session replay
plugin
---
.../src/session-replay.ts | 21 +-
.../test/session-replay.test.ts | 1302 +++++++----------
2 files changed, 522 insertions(+), 801 deletions(-)
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index 4891e8a06..0dd9db568 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -1,6 +1,6 @@
import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
-import { get, update } from 'idb-keyval';
+import * as IDBKeyVal from 'idb-keyval';
import { record } from 'rrweb';
import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE } from './messages';
@@ -28,7 +28,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
-
await this.emptyStoreAndReset();
this.recordEvents();
@@ -39,7 +38,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
...event.event_properties,
session_replay_enabled: true,
};
-
if (event.event_type === 'session_start' && this.stopRecordingEvents) {
this.stopRecordingEvents();
this.recordEvents();
@@ -193,27 +191,26 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
}
this.completeRequest({ context, success: `${res.status}: ${responseBody}` });
} else {
- this.handleReponse(res, context);
+ this.handleReponse(res.status, context);
}
} catch (e) {
this.completeRequest({ context, err: e as string, removeEvents: false });
}
}
- handleReponse(res: Response, context: SessionReplayContext) {
- const { status } = res;
+ handleReponse(status: number, context: SessionReplayContext) {
const parsedStatus = new BaseTransport().buildStatus(status);
switch (parsedStatus) {
case Status.Success:
- this.handleSuccessResponse(res, context);
+ this.handleSuccessResponse(context);
break;
default:
this.handleOtherResponse(context);
}
}
- handleSuccessResponse(res: Response, context: SessionReplayContext) {
- this.completeRequest({ context, success: `${res.status}` });
+ handleSuccessResponse(context: SessionReplayContext) {
+ this.completeRequest({ context, success: `Session Replay events tracked successfully` });
}
handleOtherResponse(context: SessionReplayContext) {
@@ -225,7 +222,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
async getAllSessionEventsFromStore() {
try {
- const storedReplaySessionContexts: IDBStore | undefined = await get(this.storageKey);
+ const storedReplaySessionContexts: IDBStore | undefined = await IDBKeyVal.get(this.storageKey);
return storedReplaySessionContexts;
} catch (e) {
@@ -236,7 +233,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
storeEventsForSession(events: Events, sequenceId: number) {
try {
- void update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
+ void IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
if (this.config.sessionId) {
return {
...sessionMap,
@@ -256,7 +253,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
removeSessionEventsStore(sessionId: number | undefined) {
if (sessionId) {
try {
- void update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
+ void IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
sessionMap = sessionMap || {};
delete sessionMap[sessionId];
return sessionMap;
diff --git a/packages/plugin-session-replay/test/session-replay.test.ts b/packages/plugin-session-replay/test/session-replay.test.ts
index e067943ba..00e9fb3d1 100644
--- a/packages/plugin-session-replay/test/session-replay.test.ts
+++ b/packages/plugin-session-replay/test/session-replay.test.ts
@@ -1,16 +1,46 @@
+/* eslint-disable jest/expect-expect */
import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
-import { Logger } from '@amplitude/analytics-core';
import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
+import * as IDBKeyVal from 'idb-keyval';
+import * as RRWeb from 'rrweb';
import { SessionReplayPlugin } from '../src/session-replay';
+jest.mock('idb-keyval');
+type MockedIDBKeyVal = jest.Mocked;
+
+jest.mock('rrweb');
+type MockedRRWeb = jest.Mocked;
+
+const mockEvent =
+ '{"type":4,"data":{"href":"https://analytics.amplitude.com/","width":1728,"height":154},"timestamp":1687358660935}';
+
+async function runScheduleTimers() {
+ // exhause first setTimeout
+ jest.runAllTimers();
+ // wait for next tick to call nested setTimeout
+ await Promise.resolve();
+ // exhause nested setTimeout
+ jest.runAllTimers();
+}
+
describe('SessionReplayPlugin', () => {
+ const { get, update } = IDBKeyVal as MockedIDBKeyVal;
+ const { record } = RRWeb as MockedRRWeb;
+ let originalFetch: typeof global.fetch;
const mockConfig: BrowserConfig = {
apiKey: 'static_key',
flushIntervalMillis: 0,
- flushMaxRetries: 0,
+ flushMaxRetries: 1,
flushQueueSize: 0,
logLevel: LogLevel.None,
- loggerProvider: new Logger(),
+ loggerProvider: {
+ error: jest.fn(),
+ log: jest.fn(),
+ disable: jest.fn(),
+ enable: jest.fn(),
+ warn: jest.fn(),
+ debug: jest.fn(),
+ },
optOut: false,
serverUrl: 'url',
transportProvider: new FetchTransport(),
@@ -30,824 +60,518 @@ describe('SessionReplayPlugin', () => {
platform: true,
},
};
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+ beforeEach(() => {
+ originalFetch = global.fetch;
+ global.fetch = jest.fn(() =>
+ Promise.resolve({
+ status: 200,
+ }),
+ ) as jest.Mock;
+ });
+ afterEach(() => {
+ jest.clearAllMocks();
+ global.fetch = originalFetch;
+ });
+ afterAll(() => {
+ jest.useRealTimers();
+ });
describe('setup', () => {
test('should setup plugin', async () => {
const sessionReplay = new SessionReplayPlugin();
await sessionReplay.setup(mockConfig);
expect(sessionReplay.config.transportProvider).toBeDefined();
expect(sessionReplay.config.serverUrl).toBe('url');
- expect(sessionReplay.config.flushMaxRetries).toBe(0);
+ expect(sessionReplay.config.flushMaxRetries).toBe(1);
expect(sessionReplay.config.flushQueueSize).toBe(0);
expect(sessionReplay.config.flushIntervalMillis).toBe(0);
- expect(sessionReplay.storageKey).toBe('');
+ expect(sessionReplay.storageKey).toBe('AMP_replay_unsent_static_key');
});
- // test('should read from storage', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const config = useDefaultConfig();
- // const event = {
- // event_type: 'hello',
- // };
- // config.storageProvider = {
- // isEnabled: async () => true,
- // get: async () => undefined,
- // set: async () => undefined,
- // remove: async () => undefined,
- // reset: async () => undefined,
- // getRaw: async () => undefined,
- // };
- // const get = jest.spyOn(config.storageProvider, 'get').mockResolvedValueOnce([event]);
- // const execute = jest.spyOn(destination, 'execute').mockReturnValueOnce(
- // Promise.resolve({
- // event,
- // message: Status.Success,
- // code: 200,
- // }),
- // );
- // await destination.setup(config);
- // expect(get).toHaveBeenCalledTimes(1);
- // expect(execute).toHaveBeenCalledTimes(1);
- // });
-
- // test('should be ok with undefined storage', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const config = useDefaultConfig();
- // config.storageProvider = undefined;
- // const execute = jest.spyOn(destination, 'execute');
- // await destination.setup(config);
- // expect(execute).toHaveBeenCalledTimes(0);
- // });
- });
-
- // describe('execute', () => {
- // test('should execute plugin', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const event = {
- // event_type: 'event_type',
- // };
- // const addToQueue = jest.spyOn(destination, 'addToQueue').mockImplementation((context: DestinationContext) => {
- // context.callback({ event, code: 200, message: Status.Success });
- // });
- // await destination.execute(event);
- // expect(addToQueue).toHaveBeenCalledTimes(1);
- // });
- // });
-
- // describe('addToQueue', () => {
- // test('should add to queue and schedule a flush', () => {
- // const sessionReplay = new SessionReplayPlugin();
- // destination.config = {
- // ...useDefaultConfig(),
- // flushIntervalMillis: 0,
- // };
- // const schedule = jest.spyOn(destination, 'schedule').mockReturnValueOnce(undefined);
- // const event = {
- // event_type: 'event_type',
- // };
- // const context = {
- // event,
- // callback: () => undefined,
- // attempts: 0,
- // timeout: 0,
- // };
- // destination.addToQueue(context);
- // expect(schedule).toHaveBeenCalledTimes(1);
- // expect(context.attempts).toBe(1);
- // });
- // });
-
- // describe('schedule', () => {
- // beforeEach(() => {
- // jest.useFakeTimers();
- // });
+ // polluting fetch
+ test('should read events from storage and send them, then reset storage for session', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ mockConfig.sessionId = 456;
+ get.mockResolvedValueOnce({
+ 123: {
+ events: [mockEvent],
+ sequenceId: 3,
+ },
+ 456: {
+ events: [mockEvent],
+ sequenceId: 1,
+ },
+ });
+ const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
- // afterEach(() => {
- // jest.useRealTimers();
- // });
+ await sessionReplay.setup(mockConfig);
+ jest.runAllTimers();
+ expect(send).toHaveBeenCalledTimes(2);
- // test('should schedule a flush', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- // (destination as any).scheduled = null;
- // destination.queue = [
- // {
- // event: { event_type: 'event_type' },
- // attempts: 0,
- // callback: () => undefined,
- // timeout: 0,
- // },
- // ];
- // const flush = jest
- // .spyOn(destination, 'flush')
- // .mockImplementationOnce(() => {
- // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- // (destination as any).scheduled = null;
- // return Promise.resolve(undefined);
- // })
- // .mockReturnValueOnce(Promise.resolve(undefined));
- // destination.schedule(0);
- // // exhause first setTimeout
- // jest.runAllTimers();
- // // wait for next tick to call nested setTimeout
- // await Promise.resolve();
- // // exhause nested setTimeout
- // jest.runAllTimers();
- // expect(flush).toHaveBeenCalledTimes(2);
- // });
+ // Sending first stored session events
+ expect(send.mock.calls[0][0]).toEqual({
+ attempts: 1,
+ events: [mockEvent],
+ sequenceId: 3,
+ sessionId: 123,
+ timeout: 0,
+ });
+ // Sending second stored session events
+ expect(send.mock.calls[1][0]).toEqual({
+ attempts: 1,
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 456,
+ timeout: 0,
+ });
- // test('should not schedule if one is already in progress', () => {
- // const sessionReplay = new SessionReplayPlugin();
- // // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- // (destination as any).scheduled = setTimeout(jest.fn, 0);
- // const flush = jest.spyOn(destination, 'flush').mockReturnValueOnce(Promise.resolve(undefined));
- // destination.schedule(0);
- // expect(flush).toHaveBeenCalledTimes(0);
- // });
- // });
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1]({})).toEqual({
+ '456': {
+ events: [],
+ sequenceId: 2,
+ },
+ });
+ });
+ test('should record events', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ await sessionReplay.setup(mockConfig);
+ jest.runAllTimers();
+ expect(record).toHaveBeenCalledTimes(1);
+ });
+ });
- // describe('flush', () => {
- // test('should get batch and call send', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // destination.config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 1,
- // };
- // destination.queue = [
- // {
- // event: { event_type: 'event_type' },
- // attempts: 0,
- // callback: () => undefined,
- // timeout: 0,
- // },
- // ];
- // const send = jest.spyOn(destination, 'send').mockReturnValueOnce(Promise.resolve());
- // const result = await destination.flush();
- // expect(destination.queue).toEqual([]);
- // expect(result).toBe(undefined);
- // expect(send).toHaveBeenCalledTimes(1);
- // });
+ describe('execute', () => {
+ test('should add event property for session_replay_enabled', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ await sessionReplay.setup(mockConfig);
+ const event = {
+ event_type: 'event_type',
+ event_properties: {
+ property_a: true,
+ property_b: 123,
+ },
+ };
- // test('should send with queue', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // destination.config = {
- // ...useDefaultConfig(),
- // };
- // destination.queue = [
- // {
- // event: { event_type: 'event_type' },
- // attempts: 0,
- // callback: () => undefined,
- // timeout: 0,
- // },
- // ];
- // const send = jest.spyOn(destination, 'send').mockReturnValueOnce(Promise.resolve());
- // const result = await destination.flush();
- // expect(destination.queue).toEqual([]);
- // expect(result).toBe(undefined);
- // expect(send).toHaveBeenCalledTimes(1);
- // });
+ const enrichedEvent = await sessionReplay.execute(event);
+ expect(enrichedEvent.event_properties).toEqual({
+ property_a: true,
+ property_b: 123,
+ session_replay_enabled: true,
+ });
+ });
- // test('should send later', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // destination.config = {
- // ...useDefaultConfig(),
- // };
- // destination.queue = [
- // {
- // event: { event_type: 'event_type' },
- // attempts: 0,
- // callback: () => undefined,
- // timeout: 1000,
- // },
- // ];
- // const send = jest.spyOn(destination, 'send').mockReturnValueOnce(Promise.resolve());
- // const result = await destination.flush();
- // expect(destination.queue).toEqual([
- // {
- // event: { event_type: 'event_type' },
- // attempts: 0,
- // // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- // callback: expect.any(Function),
- // timeout: 1000,
- // },
- // ]);
- // expect(result).toBe(undefined);
- // expect(send).toHaveBeenCalledTimes(0);
- // });
- // });
+ test('should restart recording events when session_start fires', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const mockStopRecordingEvents = jest.fn();
+ record.mockReturnValue(mockStopRecordingEvents);
+ await sessionReplay.setup(mockConfig);
+ jest.runAllTimers();
+ const event = {
+ event_type: 'session_start',
+ };
+ await sessionReplay.execute(event);
+ expect(mockStopRecordingEvents).toHaveBeenCalledTimes(1);
+ expect(record).toHaveBeenCalledTimes(2);
+ });
- // describe('send', () => {
- // test('should include min id length', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const callback = jest.fn();
- // const event = {
- // event_type: 'event_type',
- // };
- // const context = {
- // attempts: 0,
- // callback,
- // event,
- // timeout: 0,
- // };
- // const transportProvider = {
- // send: jest.fn().mockImplementationOnce((_url: string, payload: Payload) => {
- // expect(payload.options?.min_id_length).toBe(10);
- // return Promise.resolve({
- // status: Status.Success,
- // statusCode: 200,
- // body: {
- // eventsIngested: 1,
- // payloadSizeBytes: 1,
- // serverUploadTime: 1,
- // },
- // });
- // }),
- // };
- // await destination.setup({
- // ...useDefaultConfig(),
- // transportProvider,
- // apiKey: API_KEY,
- // minIdLength: 10,
- // });
- // await destination.send([context]);
- // expect(callback).toHaveBeenCalledTimes(1);
- // expect(callback).toHaveBeenCalledWith({
- // event,
- // code: 200,
- // message: SUCCESS_MESSAGE,
- // });
- // });
+ test('should send the current events list when session_end fires', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
- // test('should not include extra', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const callback = jest.fn();
- // const event = {
- // event_type: 'event_type',
- // extra: { 'extra-key': 'extra-value' },
- // };
- // const context = {
- // attempts: 0,
- // callback,
- // event,
- // timeout: 0,
- // };
- // const transportProvider = {
- // send: jest.fn().mockImplementationOnce((_url: string, payload: Payload) => {
- // expect(payload.options?.min_id_length).toBe(10);
- // expect(payload.events.some((event) => !!event.extra)).toBeFalsy();
- // return Promise.resolve({
- // status: Status.Success,
- // statusCode: 200,
- // body: {
- // eventsIngested: 1,
- // payloadSizeBytes: 1,
- // serverUploadTime: 1,
- // },
- // });
- // }),
- // };
- // await destination.setup({
- // ...useDefaultConfig(),
- // transportProvider,
- // apiKey: API_KEY,
- // minIdLength: 10,
- // });
- // await destination.send([context]);
- // expect(callback).toHaveBeenCalledTimes(1);
- // expect(callback).toHaveBeenCalledWith({
- // event,
- // code: 200,
- // message: SUCCESS_MESSAGE,
- // });
- // });
+ const event = {
+ event_type: 'session_end',
+ session_id: 789,
+ };
+ sessionReplay.events = [mockEvent];
+ await sessionReplay.execute(event);
+ jest.runAllTimers();
+ expect(send).toHaveBeenCalledTimes(1);
- // test('should not retry', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const callback = jest.fn();
- // const event = {
- // event_type: 'event_type',
- // };
- // const context = {
- // attempts: 0,
- // callback,
- // event,
- // timeout: 0,
- // };
- // const transportProvider = {
- // send: jest.fn().mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.Failed,
- // statusCode: 500,
- // });
- // }),
- // };
- // await destination.setup({
- // ...useDefaultConfig(),
- // transportProvider,
- // apiKey: API_KEY,
- // });
- // await destination.send([context], false);
- // expect(callback).toHaveBeenCalledTimes(1);
- // expect(callback).toHaveBeenCalledWith({
- // event,
- // code: 500,
- // message: Status.Failed,
- // });
- // });
+ // Sending first stored session events
+ expect(send.mock.calls[0][0]).toEqual({
+ attempts: 1,
+ events: [mockEvent],
+ sequenceId: 0,
+ sessionId: 789,
+ timeout: 0,
+ });
+ });
+ });
- // test('should provide error details', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const callback = jest.fn();
- // const event = {
- // event_type: 'event_type',
- // };
- // const context = {
- // attempts: 0,
- // callback,
- // event,
- // timeout: 0,
- // };
- // const body = {
- // error: 'Request missing required field',
- // missingField: 'user_id',
- // };
- // const transportProvider = {
- // send: jest.fn().mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.Invalid,
- // statusCode: 400,
- // body,
- // });
- // }),
- // };
- // await destination.setup({
- // ...useDefaultConfig(),
- // transportProvider,
- // apiKey: API_KEY,
- // });
- // await destination.send([context], false);
- // expect(callback).toHaveBeenCalledTimes(1);
- // expect(callback).toHaveBeenCalledWith({
- // event,
- // code: 400,
- // message: `${Status.Invalid}: ${JSON.stringify(body, null, 2)}`,
- // });
- // });
+ describe('addToQueue', () => {
+ test('should add to queue and schedule a flush', () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const schedule = jest.spyOn(sessionReplay, 'schedule').mockReturnValueOnce(undefined);
+ const context = {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
+ sessionReplay.addToQueue(context);
+ expect(schedule).toHaveBeenCalledTimes(1);
+ expect(context.attempts).toBe(1);
+ });
+ });
- // test('should handle no api key', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const callback = jest.fn();
- // const event = {
- // event_type: 'event_type',
- // };
- // const context = {
- // attempts: 0,
- // callback,
- // event,
- // timeout: 0,
- // };
- // const transportProvider = {
- // send: jest.fn().mockImplementationOnce(() => {
- // throw new Error();
- // }),
- // };
- // await destination.setup({
- // ...useDefaultConfig(),
- // transportProvider,
- // apiKey: '',
- // });
- // await destination.send([context]);
- // expect(callback).toHaveBeenCalledTimes(1);
- // expect(callback).toHaveBeenCalledWith({
- // event,
- // code: 400,
- // message: MISSING_API_KEY_MESSAGE,
- // });
- // });
+ describe('schedule', () => {
+ test('should schedule a flush', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ (sessionReplay as any).scheduled = null;
+ sessionReplay.queue = [
+ {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ },
+ ];
+ const flush = jest
+ .spyOn(sessionReplay, 'flush')
+ .mockImplementationOnce(() => {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ (sessionReplay as any).scheduled = null;
+ return Promise.resolve(undefined);
+ })
+ .mockReturnValueOnce(Promise.resolve(undefined));
+ sessionReplay.schedule(0);
+ await runScheduleTimers();
+ expect(flush).toHaveBeenCalledTimes(2);
+ });
- // test('should handle unexpected error', async () => {
- // const sessionReplay = new SessionReplayPlugin();
- // const callback = jest.fn();
- // const context = {
- // attempts: 0,
- // callback,
- // event: {
- // event_type: 'event_type',
- // },
- // timeout: 0,
- // };
- // const transportProvider = {
- // send: jest.fn().mockImplementationOnce(() => {
- // throw new Error();
- // }),
- // };
- // await destination.setup({
- // ...useDefaultConfig(),
- // transportProvider,
- // });
- // await destination.send([context]);
- // expect(callback).toHaveBeenCalledTimes(1);
- // });
- // });
+ test('should not schedule if one is already in progress', () => {
+ const sessionReplay = new SessionReplayPlugin();
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ (sessionReplay as any).scheduled = setTimeout(jest.fn, 0);
+ const flush = jest.spyOn(sessionReplay, 'flush').mockReturnValueOnce(Promise.resolve(undefined));
+ sessionReplay.schedule(0);
+ expect(flush).toHaveBeenCalledTimes(0);
+ });
+ });
- // describe('saveEvents', () => {
- // test('should save to storage provider', () => {
- // const sessionReplay = new SessionReplayPlugin();
- // destination.config = useDefaultConfig();
- // destination.config.storageProvider = {
- // isEnabled: async () => true,
- // get: async () => undefined,
- // set: async () => undefined,
- // remove: async () => undefined,
- // reset: async () => undefined,
- // getRaw: async () => undefined,
- // };
- // const set = jest.spyOn(destination.config.storageProvider, 'set').mockResolvedValueOnce(undefined);
- // destination.saveEvents();
- // expect(set).toHaveBeenCalledTimes(1);
- // });
+ describe('flush', () => {
+ test('should call send', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.queue = [
+ {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ },
+ ];
+ const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
+ const result = await sessionReplay.flush();
+ expect(sessionReplay.queue).toEqual([]);
+ expect(result).toBe(undefined);
+ expect(send).toHaveBeenCalledTimes(1);
+ });
- // test('should be ok with no storage provider', () => {
- // const sessionReplay = new SessionReplayPlugin();
- // destination.config = useDefaultConfig();
- // destination.config.storageProvider = undefined;
- // expect(destination.saveEvents()).toBe(undefined);
- // });
- // });
+ test('should send later', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.queue = [
+ {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 1000,
+ },
+ ];
+ const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
+ const result = await sessionReplay.flush();
+ expect(sessionReplay.queue).toEqual([
+ {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 1000,
+ },
+ ]);
+ expect(result).toBe(undefined);
+ expect(send).toHaveBeenCalledTimes(0);
+ });
+ });
- // describe('module level integration', () => {
- // const successResponse = {
- // status: Status.Success,
- // statusCode: 200,
- // body: {
- // eventsIngested: 1,
- // payloadSizeBytes: 1,
- // serverUploadTime: 1,
- // },
- // };
+ describe('send', () => {
+ test('should make a request correctly', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const context = {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
- // test('should handle unexpected error', async () => {
- // class Http {
- // send = jest.fn().mockImplementationOnce(() => {
- // return Promise.resolve(null);
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const result = await destination.execute({
- // event_type: 'event_type',
- // });
- // expect(result.code).toBe(0);
- // expect(result.message).toBe(UNEXPECTED_ERROR_MESSAGE);
- // expect(transportProvider.send).toHaveBeenCalledTimes(1);
- // });
+ await sessionReplay.send(context);
+ expect(fetch).toHaveBeenCalledTimes(1);
+ expect(fetch).toHaveBeenCalledWith('https://api-secure.amplitude.com/sessions/track', {
+ body: JSON.stringify({
+ api_key: 'static_key',
+ session_id: 123,
+ start_timestamp: 123,
+ events_batch: { version: 1, events: [mockEvent], seq_number: 1 },
+ }),
+ headers: { Accept: '*/*', 'Content-Type': 'application/json' },
+ method: 'POST',
+ });
+ });
+ test('should remove session events from IDB store upon success', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const context = {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
+ await sessionReplay.send(context);
+ jest.runAllTimers();
+ const mockIDBStore = {
+ 123: {
+ events: [mockEvent],
+ sequenceId: 3,
+ },
+ 456: {
+ events: [mockEvent],
+ sequenceId: 1,
+ },
+ };
- // test('should not retry with invalid api key', async () => {
- // class Http {
- // send = jest.fn().mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.Invalid,
- // statusCode: 400,
- // body: {
- // error: INVALID_API_KEY,
- // },
- // });
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // destination.retryTimeout = 10;
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const results = await Promise.all([
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // ]);
- // expect(results[0].code).toBe(400);
- // expect(transportProvider.send).toHaveBeenCalledTimes(1);
- // });
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ 456: {
+ events: [mockEvent],
+ sequenceId: 1,
+ },
+ });
+ });
+ test('should not remove session events from IDB store upon failure', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const context = {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
+ (global.fetch as jest.Mock).mockImplementationOnce(() => Promise.reject());
- // test('should handle retry for 400 error', async () => {
- // class Http {
- // send = jest
- // .fn()
- // .mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.Invalid,
- // statusCode: 400,
- // body: {
- // error: 'error',
- // missingField: '',
- // eventsWithInvalidFields: { a: [0] },
- // eventsWithMissingFields: { b: [] },
- // eventsWithInvalidIdLengths: {},
- // silencedEvents: [],
- // },
- // });
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve(successResponse);
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // destination.retryTimeout = 10;
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const results = await Promise.all([
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // ]);
- // expect(results[0].code).toBe(400);
- // expect(results[1].code).toBe(200);
- // expect(transportProvider.send).toHaveBeenCalledTimes(2);
- // });
+ await sessionReplay.send(context);
+ jest.runAllTimers();
- // test('should handle retry for 400 error with missing body field', async () => {
- // class Http {
- // send = jest.fn().mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.Invalid,
- // statusCode: 400,
- // body: {
- // error: 'error',
- // missingField: 'key',
- // eventsWithInvalidFields: {},
- // eventsWithMissingFields: {},
- // silencedEvents: [],
- // },
- // });
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const results = await Promise.all([
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // ]);
- // expect(results[0].code).toBe(400);
- // expect(results[1].code).toBe(400);
- // expect(transportProvider.send).toHaveBeenCalledTimes(1);
- // });
+ expect(update).not.toHaveBeenCalled();
+ });
- // test('should handle retry for 413 error with flushQueueSize of 1', async () => {
- // class Http {
- // send = jest.fn().mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.PayloadTooLarge,
- // statusCode: 413,
- // body: {
- // error: 'error',
- // },
- // });
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 1,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const event = {
- // event_type: 'event_type',
- // };
- // const result = await destination.execute(event);
- // expect(result).toEqual({
- // event,
- // message: 'error',
- // code: 413,
- // });
- // expect(transportProvider.send).toHaveBeenCalledTimes(1);
- // });
+ test('should retry if retry param is true', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const context = {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
+ (global.fetch as jest.Mock)
+ .mockImplementationOnce(() =>
+ Promise.resolve({
+ status: 500,
+ }),
+ )
+ .mockImplementationOnce(() =>
+ Promise.resolve({
+ status: 200,
+ }),
+ );
+ const addToQueue = jest.spyOn(sessionReplay, 'addToQueue');
- // test('should handle retry for 413 error', async () => {
- // class Http {
- // send = jest
- // .fn()
- // .mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.PayloadTooLarge,
- // statusCode: 413,
- // body: {
- // error: 'error',
- // },
- // });
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve(successResponse);
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve(successResponse);
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // destination.retryTimeout = 10;
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // await Promise.all([
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // ]);
- // expect(transportProvider.send).toHaveBeenCalledTimes(3);
- // });
+ await sessionReplay.send(context, true);
+ expect(addToQueue).toHaveBeenCalledTimes(1);
+ expect(addToQueue).toHaveBeenCalledWith({
+ ...context,
+ attempts: 1,
+ timeout: 0,
+ });
+ await runScheduleTimers();
+ });
- // test('should handle retry for 429 error', async () => {
- // class Http {
- // send = jest
- // .fn()
- // .mockImplementationOnce(() => {
- // return Promise.resolve({
- // status: Status.RateLimit,
- // statusCode: 429,
- // body: {
- // error: 'error',
- // epsThreshold: 1,
- // throttledDevices: {},
- // throttledUsers: {},
- // exceededDailyQuotaDevices: {
- // '1': 1,
- // },
- // exceededDailyQuotaUsers: {
- // '2': 1,
- // },
- // throttledEvents: [0],
- // },
- // });
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve(successResponse);
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve(successResponse);
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // destination.retryTimeout = 10;
- // destination.throttleTimeout = 1;
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 4,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const results = await Promise.all([
- // // throttled
- // destination.execute({
- // event_type: 'event_type',
- // user_id: '0',
- // device_id: '0',
- // }),
- // // exceed daily device quota
- // destination.execute({
- // event_type: 'event_type',
- // user_id: '1',
- // device_id: '1',
- // }),
- // // exceed daily user quota
- // destination.execute({
- // event_type: 'event_type',
- // user_id: '2',
- // device_id: '2',
- // }),
- // // success
- // destination.execute({
- // event_type: 'event_type',
- // user_id: '3',
- // device_id: '3',
- // }),
- // ]);
- // expect(results[0].code).toBe(200);
- // expect(results[1].code).toBe(429);
- // expect(results[2].code).toBe(429);
- // expect(results[3].code).toBe(200);
- // expect(transportProvider.send).toHaveBeenCalledTimes(2);
- // });
+ test('should not retry if retry param is false', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const context = {
+ events: [mockEvent],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
+ (global.fetch as jest.Mock).mockImplementationOnce(() =>
+ Promise.resolve({
+ status: 500,
+ }),
+ );
+ const addToQueue = jest.spyOn(sessionReplay, 'addToQueue');
- // test('should handle retry for 500 error', async () => {
- // class Http {
- // send = jest
- // .fn()
- // .mockImplementationOnce(() => {
- // return Promise.resolve({
- // statusCode: 500,
- // status: Status.Failed,
- // });
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve(successResponse);
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // destination.retryTimeout = 10;
- // const config = {
- // ...useDefaultConfig(),
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // await Promise.all([
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // ]);
- // expect(transportProvider.send).toHaveBeenCalledTimes(2);
- // });
+ await sessionReplay.send(context, false);
+ expect(addToQueue).toHaveBeenCalledTimes(0);
+ });
+ });
- // test('should handle retry for 503 error', async () => {
- // class Http {
- // send = jest
- // .fn()
- // .mockImplementationOnce(() => {
- // return Promise.resolve({
- // statusCode: 500,
- // status: Status.Failed,
- // });
- // })
- // .mockImplementationOnce(() => {
- // return Promise.resolve({
- // statusCode: 500,
- // status: Status.Failed,
- // });
- // });
- // }
- // const transportProvider = new Http();
- // const sessionReplay = new SessionReplayPlugin();
- // destination.retryTimeout = 10;
- // const config = {
- // ...useDefaultConfig(),
- // flushMaxRetries: 1,
- // flushQueueSize: 2,
- // flushIntervalMillis: 500,
- // transportProvider,
- // };
- // await destination.setup(config);
- // const results = await Promise.all([
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // destination.execute({
- // event_type: 'event_type',
- // }),
- // ]);
- // expect(results[0].code).toBe(500);
- // expect(results[1].code).toBe(500);
- // expect(transportProvider.send).toHaveBeenCalledTimes(1);
- // });
- // });
+ describe('module level integration', () => {
+ const mockLoggerProvider = {
+ error: jest.fn(),
+ log: jest.fn(),
+ logLevel: 1,
+ disable: jest.fn(),
+ enable: jest.fn(),
+ warn: jest.fn(),
+ debug: jest.fn(),
+ };
+ test('should handle unexpected error', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ (fetch as jest.Mock).mockImplementationOnce(() => Promise.reject('API Failure'));
+ await sessionReplay.setup(config);
+ await sessionReplay.execute({
+ event_type: 'session_end',
+ session_id: 456,
+ });
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(1);
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual('API Failure');
+ });
+ test('should handle retry for 400 error', async () => {
+ (fetch as jest.Mock)
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 400,
+ });
+ })
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 200,
+ });
+ });
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.retryTimeout = 10;
+ const config = {
+ ...mockConfig,
+ flushMaxRetries: 2,
+ loggerProvider: mockLoggerProvider,
+ };
+ await sessionReplay.setup(config);
+ // Log is called from setup, but that's not what we're testing here
+ mockLoggerProvider.log.mockClear();
+ await sessionReplay.execute({
+ event_type: 'session_end',
+ session_id: 456,
+ });
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(2);
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(0);
+ expect(mockLoggerProvider.log).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual('Session Replay events tracked successfully');
+ });
+ test('should handle retry for 413 error with flushQueueSize of 1', async () => {
+ (fetch as jest.Mock)
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 413,
+ });
+ })
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 200,
+ });
+ });
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ flushMaxRetries: 2,
+ };
+ await sessionReplay.setup(config);
+ const event = {
+ event_type: 'session_end',
+ session_id: 456,
+ };
+ await sessionReplay.execute(event);
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(2);
+ });
+ test('should handle retry for 500 error', async () => {
+ (fetch as jest.Mock)
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 500,
+ });
+ })
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 200,
+ });
+ });
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ flushMaxRetries: 2,
+ };
+ await sessionReplay.setup(config);
+ const event = {
+ event_type: 'session_end',
+ session_id: 456,
+ };
+ await sessionReplay.execute(event);
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(2);
+ });
+ test('should handle retry for 503 error', async () => {
+ (fetch as jest.Mock)
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 503,
+ });
+ })
+ .mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 200,
+ });
+ });
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ flushMaxRetries: 2,
+ };
+ await sessionReplay.setup(config);
+ const event = {
+ event_type: 'session_end',
+ session_id: 456,
+ };
+ await sessionReplay.execute(event);
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(2);
+ });
+ });
});
From f9d98cd1ad24a191bb265bb16d6dd88201de7823 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 22 Jun 2023 13:11:06 -0400
Subject: [PATCH 031/214] feat(plugins): cleanup and update readme and
changelog
---
packages/plugin-session-replay/CHANGELOG.md | 5 +-
packages/plugin-session-replay/README.md | 80 +++----------------
packages/plugin-session-replay/src/helpers.ts | 1 -
.../plugin-session-replay/src/messages.ts | 4 -
.../src/session-replay.ts | 5 +-
.../test/session-replay.test.ts | 4 +-
yarn.lock | 5 --
7 files changed, 19 insertions(+), 85 deletions(-)
diff --git a/packages/plugin-session-replay/CHANGELOG.md b/packages/plugin-session-replay/CHANGELOG.md
index b67c38fc7..ea145a276 100644
--- a/packages/plugin-session-replay/CHANGELOG.md
+++ b/packages/plugin-session-replay/CHANGELOG.md
@@ -3,9 +3,8 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-# 0.1.0 (2022-09-16)
+# 0.1.0 (2023-06-22)
### Features
-- new marketing analytics plugin ([#213](https://github.com/amplitude/Amplitude-TypeScript/issues/213))
- ([02ff174](https://github.com/amplitude/Amplitude-TypeScript/commit/02ff174e3361173dbf15ed3acf72e950810e174f))
+- new session replay plugin ([#393](https://github.com/amplitude/Amplitude-TypeScript/pull/393))
diff --git a/packages/plugin-session-replay/README.md b/packages/plugin-session-replay/README.md
index 2cffd9ced..b8e56e84c 100644
--- a/packages/plugin-session-replay/README.md
+++ b/packages/plugin-session-replay/README.md
@@ -5,9 +5,9 @@
-# @amplitude/plugin-web-attribution-browser
+# @amplitude/plugin-session-replay
-Official Browser SDK plugin for web attribution tracking
+Official Browser SDK plugin for session replay
## Installation
@@ -15,86 +15,32 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/plugin-web-attribution-browser
+npm install @amplitude/plugin-session-replay
# yarn
-yarn add @amplitude/plugin-web-attribution-browser
+yarn add @amplitude/plugin-session-replay
```
## Usage
-This plugin works on top of Amplitude Browser SDK and adds web attribution tracking features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v1.4.0` or later.
+This plugin works on top of Amplitude Browser SDK and adds session replay features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v2.0.0` or later.
### 1. Import Amplitude packages
* `@amplitude/analytics-browser`
-* `@amplitude/plugin-web-attribution-browser`
+* `@amplitude/plugin-session-replay`
```typescript
import * as amplitude from '@amplitude/analytics-browser';
-import { webAttributionPlugin } from '@amplitude/plugin-web-attribution-browser';
+import { SessionReplayPlugin } from '@amplitude/plugin-session-replay';
```
-### 2. Instantiate page view plugin
+### 2. Instantiate session replay plugin and install plugin to Amplitude SDK
-The plugin requires 1 parameter, which is the `amplitude` instance. The plugin also accepts an optional second parameter, which is an `Object` to configure the plugin based on your use case.
+The plugin must be registered with the amplitude instance via the following code:
```typescript
-const webAttributionTracking = webAttributionPlugin(amplitude, {
- disabled: undefined,
- excludeReferrers: undefined,
- initialEmptyValue: undefined,
- resetSessionOnNewCampaign: undefined,
-});
-```
-
-#### Options
-
-|Name|Type|Default|Description|
-|-|-|-|-|
-|`disabled`|`boolean`|`false`|Use this option to enable or disable web attribution tracking. By default, upon installing this plugin, web attribution tracking is enabled.|
-|`excludeReferrers`|`string[]`|`[]`|Use this option to prevent the plugin from tracking campaigns parameters from specific referrers. For example: `subdomain.domain.com`.|
-|`initialEmptyValue`|`string`|`"EMPTY"`|Use this option to specify empty values for [first-touch attribution](https://www.docs.developers.amplitude.com/data/sdks/marketing-analytics-browser/#first-touch-attribution).|
-|`resetSessionOnNewCampaign`|`boolean`|`false`|Use this option to control whether a new session should start on a new campaign.|
-
-### 3. Install plugin to Amplitude SDK
-
-```typescript
-amplitude.add(webAttributionTracking);
-```
-
-### 4. Initialize Amplitude SDK
-
-```typescript
-amplitude.init('API_KEY');
-```
-
-## Resulting web attribution event
-
-This plugin tracks campaign parameters based on your configuration. A web attribution event is composed of the following values:
-
-#### Event type
-* `"$idenfity"`
-
-#### User properties
-
-|Property|Description|
-|-|-|
-|`utm_source`|URL query parameter value for `utm_source`|
-|`utm_medium`|URL query parameter value for `utm_medium`|
-|`utm_campaign`|URL query parameter value for `utm_campaign`|
-|`utm_term`|URL query parameter value for `utm_term`|
-|`utm_content`|URL query parameter value for `utm_content`|
-|`referrer`|Referring webstite or `document.referrer`|
-|`referring_domain`|Referring website's domain, including subdomain|
-|`dclid`|URL query parameter value for `dclid`|
-|`gbraid`|URL query parameter value for `gbraid`|
-|`gclid`|URL query parameter value for `gclid`|
-|`fbclid`|URL query parameter value for `fbclid`|
-|`ko_click_id`|URL query parameter value for `ko_click_id`|
-|`li_fat_id`|URL query parameter value for `li_fat_id`|
-|`msclkid`|URL query parameter value for `msclkid`|
-|`rtd_cid`|URL query parameter value for `rtd_cid`|
-|`ttclid`|URL query parameter value for `ttclid`|
-|`twclid`|URL query parameter value for `twclid`|
-|`wbraid`|URL query parameter value for `wbraid`|
+amplitude.init(API_KEY)
+const sessionReplayTracking = new SessionReplayPlugin();
+amplitude.add(sessionReplayTracking)
+```
\ No newline at end of file
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
index e2c0c189b..f6b2025e5 100644
--- a/packages/plugin-session-replay/src/helpers.ts
+++ b/packages/plugin-session-replay/src/helpers.ts
@@ -1,6 +1,5 @@
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-// const MAX_EVENT_LIST_SIZE_IN_BYTES = 80000;
export const shouldSplitEventsList = (eventsList: string[], nextEventString: string): boolean => {
const sizeOfNextEvent = new Blob([nextEventString]).size;
diff --git a/packages/plugin-session-replay/src/messages.ts b/packages/plugin-session-replay/src/messages.ts
index 898cdc023..8b93b23da 100644
--- a/packages/plugin-session-replay/src/messages.ts
+++ b/packages/plugin-session-replay/src/messages.ts
@@ -1,8 +1,4 @@
export const SUCCESS_MESSAGE = 'Session replay event batch tracked successfully';
export const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';
export const MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';
-export const OPT_OUT_MESSAGE = 'Event skipped due to optOut config';
-export const MISSING_API_KEY_MESSAGE = 'Session replay event batch rejected due to missing API key';
-export const INVALID_API_KEY = 'Invalid API key';
-export const CLIENT_NOT_INITIALIZED = 'Client not initialized';
export const STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index 0dd9db568..cbb47a174 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -3,7 +3,7 @@ import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amp
import * as IDBKeyVal from 'idb-keyval';
import { record } from 'rrweb';
import { shouldSplitEventsList } from './helpers';
-import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE } from './messages';
+import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import { Events, IDBStore, SessionReplayContext } from './typings/session-replay';
const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
@@ -111,7 +111,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
context.attempts += 1;
return true;
}
- // TODO: should we keep events in indexdb to retry on a refresh? Whats destination behavior?
this.completeRequest({
context,
err: `${MAX_RETRIES_EXCEEDED_MESSAGE}, batch sequence id, ${context.sequenceId}`,
@@ -210,7 +209,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
}
handleSuccessResponse(context: SessionReplayContext) {
- this.completeRequest({ context, success: `Session Replay events tracked successfully` });
+ this.completeRequest({ context, success: SUCCESS_MESSAGE });
}
handleOtherResponse(context: SessionReplayContext) {
diff --git a/packages/plugin-session-replay/test/session-replay.test.ts b/packages/plugin-session-replay/test/session-replay.test.ts
index 00e9fb3d1..1370114b4 100644
--- a/packages/plugin-session-replay/test/session-replay.test.ts
+++ b/packages/plugin-session-replay/test/session-replay.test.ts
@@ -1,8 +1,8 @@
-/* eslint-disable jest/expect-expect */
import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
+import { SUCCESS_MESSAGE } from '../src/messages';
import { SessionReplayPlugin } from '../src/session-replay';
jest.mock('idb-keyval');
@@ -493,7 +493,7 @@ describe('SessionReplayPlugin', () => {
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(0);
expect(mockLoggerProvider.log).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual('Session Replay events tracked successfully');
+ expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(SUCCESS_MESSAGE);
});
test('should handle retry for 413 error with flushQueueSize of 1', async () => {
(fetch as jest.Mock)
diff --git a/yarn.lock b/yarn.lock
index 7770d2e1e..33a296cca 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7378,11 +7378,6 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
-idb-keyval@^6.2.1:
- version "6.2.1"
- resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33"
- integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==
-
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
From e371754434984ac44cc9462d5eac114d6142c938 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 22 Jun 2023 14:59:03 -0400
Subject: [PATCH 032/214] feat(plugins): complete test coverage
---
packages/plugin-session-replay/src/helpers.ts | 7 +-
.../src/session-replay.ts | 47 +--
.../test/session-replay.test.ts | 270 +++++++++++++++---
3 files changed, 260 insertions(+), 64 deletions(-)
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay/src/helpers.ts
index f6b2025e5..67d59de84 100644
--- a/packages/plugin-session-replay/src/helpers.ts
+++ b/packages/plugin-session-replay/src/helpers.ts
@@ -1,10 +1,7 @@
-const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
-const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-
-export const shouldSplitEventsList = (eventsList: string[], nextEventString: string): boolean => {
+export const shouldSplitEventsList = (eventsList: string[], nextEventString: string, maxListSize: number): boolean => {
const sizeOfNextEvent = new Blob([nextEventString]).size;
const sizeOfEventsList = new Blob(eventsList).size;
- if (sizeOfEventsList + sizeOfNextEvent >= MAX_EVENT_LIST_SIZE_IN_BYTES) {
+ if (sizeOfEventsList + sizeOfNextEvent >= maxListSize) {
return true;
}
return false;
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index cbb47a174..b7c5f766f 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -8,6 +8,9 @@ import { Events, IDBStore, SessionReplayContext } from './typings/session-replay
const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
+const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
+const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+
export class SessionReplayPlugin implements EnrichmentPlugin {
name = '@amplitude/plugin-session-replay';
type = PluginType.ENRICHMENT as const;
@@ -22,6 +25,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
private scheduled: ReturnType | null = null;
queue: SessionReplayContext[] = [];
stopRecordingEvents: ReturnType | null = null;
+ maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
@@ -71,7 +75,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.events = [];
const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
- this.storeEventsForSession([], this.currentSequenceId);
+ void this.storeEventsForSession([], this.currentSequenceId);
}
}
@@ -79,7 +83,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.stopRecordingEvents = record({
emit: (event) => {
const eventString = JSON.stringify(event);
- const shouldSplit = shouldSplitEventsList(this.events, eventString);
+ const shouldSplit = shouldSplitEventsList(this.events, eventString, this.maxPersistedEventsSize);
if (shouldSplit) {
this.sendEventsList({
events: this.events,
@@ -90,7 +94,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.currentSequenceId++;
}
this.events.push(eventString);
- this.storeEventsForSession(this.events, this.currentSequenceId);
+ void this.storeEventsForSession(this.events, this.currentSequenceId);
},
});
}
@@ -180,6 +184,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
if (res === null) {
this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE, removeEvents: false });
+ return;
}
if (!useRetry) {
let responseBody = '';
@@ -230,36 +235,32 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
return undefined;
}
- storeEventsForSession(events: Events, sequenceId: number) {
+ async storeEventsForSession(events: Events, sequenceId: number) {
try {
- void IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
- if (this.config.sessionId) {
- return {
- ...sessionMap,
+ await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
+ return {
+ ...sessionMap,
+ ...(this.config.sessionId && {
[this.config.sessionId]: {
events: events,
sequenceId,
},
- };
- }
- return sessionMap || {};
+ }),
+ };
});
} catch (e) {
this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
}
}
- removeSessionEventsStore(sessionId: number | undefined) {
- if (sessionId) {
- try {
- void IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
- sessionMap = sessionMap || {};
- delete sessionMap[sessionId];
- return sessionMap;
- });
- } catch (e) {
- this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
- }
+ async removeSessionEventsStore(sessionId: number) {
+ try {
+ await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
+ delete sessionMap[sessionId];
+ return sessionMap;
+ });
+ } catch (e) {
+ this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
}
}
@@ -274,7 +275,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
success?: string;
removeEvents?: boolean;
}) {
- removeEvents && this.removeSessionEventsStore(context.sessionId);
+ removeEvents && context.sessionId && this.removeSessionEventsStore(context.sessionId);
if (err) {
this.config.loggerProvider.error(err);
} else if (success) {
diff --git a/packages/plugin-session-replay/test/session-replay.test.ts b/packages/plugin-session-replay/test/session-replay.test.ts
index 1370114b4..3cc661bab 100644
--- a/packages/plugin-session-replay/test/session-replay.test.ts
+++ b/packages/plugin-session-replay/test/session-replay.test.ts
@@ -1,8 +1,10 @@
+/* eslint-disable jest/expect-expect */
import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
-import { SUCCESS_MESSAGE } from '../src/messages';
+import { shouldSplitEventsList } from '../src/helpers';
+import { SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from '../src/messages';
import { SessionReplayPlugin } from '../src/session-replay';
jest.mock('idb-keyval');
@@ -11,8 +13,12 @@ type MockedIDBKeyVal = jest.Mocked;
jest.mock('rrweb');
type MockedRRWeb = jest.Mocked;
-const mockEvent =
- '{"type":4,"data":{"href":"https://analytics.amplitude.com/","width":1728,"height":154},"timestamp":1687358660935}';
+const mockEvent = {
+ type: 4,
+ data: { href: 'https://analytics.amplitude.com/', width: 1728, height: 154 },
+ timestamp: 1687358660935,
+};
+const mockEventString = JSON.stringify(mockEvent);
async function runScheduleTimers() {
// exhause first setTimeout
@@ -23,6 +29,16 @@ async function runScheduleTimers() {
jest.runAllTimers();
}
+const mockLoggerProvider = {
+ error: jest.fn(),
+ log: jest.fn(),
+ logLevel: 1,
+ disable: jest.fn(),
+ enable: jest.fn(),
+ warn: jest.fn(),
+ debug: jest.fn(),
+};
+
describe('SessionReplayPlugin', () => {
const { get, update } = IDBKeyVal as MockedIDBKeyVal;
const { record } = RRWeb as MockedRRWeb;
@@ -45,7 +61,7 @@ describe('SessionReplayPlugin', () => {
serverUrl: 'url',
transportProvider: new FetchTransport(),
useBatch: false,
-
+ sessionId: 123,
cookieExpiration: 365,
cookieSameSite: 'Lax',
cookieSecure: false,
@@ -90,30 +106,32 @@ describe('SessionReplayPlugin', () => {
expect(sessionReplay.storageKey).toBe('AMP_replay_unsent_static_key');
});
- // polluting fetch
test('should read events from storage and send them, then reset storage for session', async () => {
const sessionReplay = new SessionReplayPlugin();
- mockConfig.sessionId = 456;
+ const config = {
+ ...mockConfig,
+ sessionId: 456,
+ };
get.mockResolvedValueOnce({
123: {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 3,
},
456: {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
},
});
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
- await sessionReplay.setup(mockConfig);
+ await sessionReplay.setup(config);
jest.runAllTimers();
expect(send).toHaveBeenCalledTimes(2);
// Sending first stored session events
expect(send.mock.calls[0][0]).toEqual({
attempts: 1,
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 3,
sessionId: 123,
timeout: 0,
@@ -121,7 +139,7 @@ describe('SessionReplayPlugin', () => {
// Sending second stored session events
expect(send.mock.calls[1][0]).toEqual({
attempts: 1,
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 456,
timeout: 0,
@@ -135,6 +153,22 @@ describe('SessionReplayPlugin', () => {
},
});
});
+ test('should handle no stored events', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ };
+ get.mockResolvedValueOnce({});
+ await sessionReplay.setup(config);
+ expect(sessionReplay.currentSequenceId).toBe(0);
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1]({})).toEqual({
+ '123': {
+ events: [],
+ sequenceId: 0,
+ },
+ });
+ });
test('should record events', async () => {
const sessionReplay = new SessionReplayPlugin();
await sessionReplay.setup(mockConfig);
@@ -186,7 +220,7 @@ describe('SessionReplayPlugin', () => {
event_type: 'session_end',
session_id: 789,
};
- sessionReplay.events = [mockEvent];
+ sessionReplay.events = [mockEventString];
await sessionReplay.execute(event);
jest.runAllTimers();
expect(send).toHaveBeenCalledTimes(1);
@@ -194,7 +228,7 @@ describe('SessionReplayPlugin', () => {
// Sending first stored session events
expect(send.mock.calls[0][0]).toEqual({
attempts: 1,
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 0,
sessionId: 789,
timeout: 0,
@@ -202,13 +236,62 @@ describe('SessionReplayPlugin', () => {
});
});
+ describe('recordEvents', () => {
+ test('should store events in class and in IDB', () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.recordEvents();
+ expect(sessionReplay.events).toEqual([]);
+ const recordArg = record.mock.calls[0][0];
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(sessionReplay.events).toEqual([mockEventString]);
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1]({})).toEqual({
+ 123: {
+ events: [mockEventString],
+ sequenceId: 0,
+ },
+ });
+ });
+
+ test('should split the events list and send', () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.maxPersistedEventsSize = 20;
+ const events = ['#'.repeat(20)];
+ sessionReplay.events = events;
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ const sendEventsListMock = jest.spyOn(sessionReplay, 'sendEventsList').mockImplementationOnce(() => {});
+ sessionReplay.recordEvents();
+
+ const recordArg = record.mock.calls[0][0];
+ recordArg?.emit && recordArg?.emit(mockEvent);
+
+ expect(sendEventsListMock).toHaveBeenCalledTimes(1);
+ expect(sendEventsListMock).toHaveBeenCalledWith({
+ events,
+ sequenceId: 0,
+ sessionId: 123,
+ });
+
+ expect(sessionReplay.events).toEqual([mockEventString]);
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1]({})).toEqual({
+ 123: {
+ events: [mockEventString],
+ sequenceId: 1,
+ },
+ });
+ });
+ });
+
describe('addToQueue', () => {
test('should add to queue and schedule a flush', () => {
const sessionReplay = new SessionReplayPlugin();
sessionReplay.config = mockConfig;
const schedule = jest.spyOn(sessionReplay, 'schedule').mockReturnValueOnce(undefined);
const context = {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -218,6 +301,34 @@ describe('SessionReplayPlugin', () => {
expect(schedule).toHaveBeenCalledTimes(1);
expect(context.attempts).toBe(1);
});
+
+ test('should not add to queue if attemps are greater than allowed retries', () => {
+ const sessionReplay = new SessionReplayPlugin();
+ sessionReplay.config = {
+ ...mockConfig,
+ flushMaxRetries: 1,
+ };
+ const completeRequest = jest.spyOn(sessionReplay, 'completeRequest').mockReturnValueOnce(undefined);
+ const context = {
+ events: [mockEventString],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 1,
+ timeout: 0,
+ };
+ sessionReplay.addToQueue(context);
+ expect(completeRequest).toHaveBeenCalledTimes(1);
+ expect(completeRequest).toHaveBeenCalledWith({
+ context: {
+ events: [mockEventString],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 1,
+ timeout: 0,
+ },
+ err: 'Session replay event batch rejected due to exceeded retry count, batch sequence id, 1',
+ });
+ });
});
describe('schedule', () => {
@@ -227,7 +338,7 @@ describe('SessionReplayPlugin', () => {
(sessionReplay as any).scheduled = null;
sessionReplay.queue = [
{
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -263,7 +374,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
sessionReplay.queue = [
{
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -282,7 +393,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
sessionReplay.queue = [
{
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -293,7 +404,7 @@ describe('SessionReplayPlugin', () => {
const result = await sessionReplay.flush();
expect(sessionReplay.queue).toEqual([
{
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -310,7 +421,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = new SessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -324,7 +435,7 @@ describe('SessionReplayPlugin', () => {
api_key: 'static_key',
session_id: 123,
start_timestamp: 123,
- events_batch: { version: 1, events: [mockEvent], seq_number: 1 },
+ events_batch: { version: 1, events: [mockEventString], seq_number: 1 },
}),
headers: { Accept: '*/*', 'Content-Type': 'application/json' },
method: 'POST',
@@ -334,7 +445,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = new SessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -344,11 +455,11 @@ describe('SessionReplayPlugin', () => {
jest.runAllTimers();
const mockIDBStore = {
123: {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 3,
},
456: {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
},
};
@@ -356,7 +467,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
456: {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
},
});
@@ -365,7 +476,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = new SessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -383,7 +494,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = new SessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -416,7 +527,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = new SessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
- events: [mockEvent],
+ events: [mockEventString],
sequenceId: 1,
sessionId: 123,
attempts: 0,
@@ -435,15 +546,6 @@ describe('SessionReplayPlugin', () => {
});
describe('module level integration', () => {
- const mockLoggerProvider = {
- error: jest.fn(),
- log: jest.fn(),
- logLevel: 1,
- disable: jest.fn(),
- enable: jest.fn(),
- warn: jest.fn(),
- debug: jest.fn(),
- };
test('should handle unexpected error', async () => {
const sessionReplay = new SessionReplayPlugin();
const config = {
@@ -573,5 +675,101 @@ describe('SessionReplayPlugin', () => {
await runScheduleTimers();
expect(fetch).toHaveBeenCalledTimes(2);
});
+ test('should handle unexpected error where fetch response is null', async () => {
+ (fetch as jest.Mock).mockImplementationOnce(() => {
+ return Promise.resolve(null);
+ });
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ flushMaxRetries: 2,
+ loggerProvider: mockLoggerProvider,
+ };
+ await sessionReplay.setup(config);
+ const event = {
+ event_type: 'session_end',
+ session_id: 456,
+ };
+ await sessionReplay.execute(event);
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(1);
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(UNEXPECTED_ERROR_MESSAGE);
+ });
+ });
+
+ describe('idb error handling', () => {
+ test('getAllSessionEventsFromStore should catch errors', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ get.mockImplementationOnce(() => Promise.reject('error'));
+ await sessionReplay.getAllSessionEventsFromStore();
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
+ 'Failed to store session replay events in IndexedDB: error',
+ );
+ });
+ test('storeEventsForSession should catch errors', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ update.mockImplementationOnce(() => Promise.reject('error'));
+ await sessionReplay.storeEventsForSession([mockEventString], 0);
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
+ 'Failed to store session replay events in IndexedDB: error',
+ );
+ });
+ test('removeSessionEventsStore should catch errors', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ update.mockImplementationOnce(() => Promise.reject('error'));
+ await sessionReplay.removeSessionEventsStore(123);
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
+ 'Failed to store session replay events in IndexedDB: error',
+ );
+ });
+ test('removeSessionEventsStore should handle an undefined store', async () => {
+ const sessionReplay = new SessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ update.mockImplementationOnce(() => Promise.resolve());
+ await sessionReplay.removeSessionEventsStore(123);
+ expect(update.mock.calls[0][1](undefined)).toEqual({});
+ });
+ });
+
+ describe('shouldSplitEventsList', () => {
+ test('shouldSplitEventsList should return true if size of events list plus size of next event is over the max size', () => {
+ const eventsList = ['#'.repeat(20)];
+ const nextEvent = 'a';
+ const result = shouldSplitEventsList(eventsList, nextEvent, 20);
+ expect(result).toBe(true);
+ });
+ test('shouldSplitEventsList should return false if size of events list plus size of next event is under the max size', () => {
+ const eventsList = ['#'.repeat(20)];
+ const nextEvent = 'a';
+ const result = shouldSplitEventsList(eventsList, nextEvent, 22);
+ expect(result).toBe(false);
+ });
});
});
From 1365bae315076eecd8568fabbc72dc07efcfaf82 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Fri, 23 Jun 2023 13:29:10 -0400
Subject: [PATCH 033/214] feat(plugins): add packFn to session replay
---
packages/plugin-session-replay/src/session-replay.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index b7c5f766f..b687751c9 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -1,7 +1,7 @@
import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
-import { record } from 'rrweb';
+import { pack, record } from 'rrweb';
import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import { Events, IDBStore, SessionReplayContext } from './typings/session-replay';
@@ -96,6 +96,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.events.push(eventString);
void this.storeEventsForSession(this.events, this.currentSequenceId);
},
+ packFn: pack,
});
}
From 1b8f037bd6913f71db73f97e39175858bf7b1954 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 26 Jun 2023 13:37:40 -0400
Subject: [PATCH 034/214] feat(plugins): pr feedback
---
packages/plugin-session-replay/CHANGELOG.md | 10 ----------
packages/plugin-session-replay/README.md | 6 +++---
packages/plugin-session-replay/package.json | 13 +++++++------
packages/plugin-session-replay/src/index.ts | 1 -
.../src/typings/session-replay.ts | 9 ---------
5 files changed, 10 insertions(+), 29 deletions(-)
delete mode 100644 packages/plugin-session-replay/CHANGELOG.md
diff --git a/packages/plugin-session-replay/CHANGELOG.md b/packages/plugin-session-replay/CHANGELOG.md
deleted file mode 100644
index ea145a276..000000000
--- a/packages/plugin-session-replay/CHANGELOG.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Change Log
-
-All notable changes to this project will be documented in this file. See
-[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
-
-# 0.1.0 (2023-06-22)
-
-### Features
-
-- new session replay plugin ([#393](https://github.com/amplitude/Amplitude-TypeScript/pull/393))
diff --git a/packages/plugin-session-replay/README.md b/packages/plugin-session-replay/README.md
index b8e56e84c..a1ed2a77e 100644
--- a/packages/plugin-session-replay/README.md
+++ b/packages/plugin-session-replay/README.md
@@ -23,16 +23,16 @@ yarn add @amplitude/plugin-session-replay
## Usage
-This plugin works on top of Amplitude Browser SDK and adds session replay features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v2.0.0` or later.
+This plugin works on top of Amplitude Browser SDK and adds session replay features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v1.0.0` or later.
### 1. Import Amplitude packages
* `@amplitude/analytics-browser`
-* `@amplitude/plugin-session-replay`
+* `@amplitude/plugin-session-replay-browser`
```typescript
import * as amplitude from '@amplitude/analytics-browser';
-import { SessionReplayPlugin } from '@amplitude/plugin-session-replay';
+import { SessionReplayPlugin } from '@amplitude/plugin-session-replay-browser';
```
### 2. Instantiate session replay plugin and install plugin to Amplitude SDK
diff --git a/packages/plugin-session-replay/package.json b/packages/plugin-session-replay/package.json
index be02e5817..eff7229dd 100644
--- a/packages/plugin-session-replay/package.json
+++ b/packages/plugin-session-replay/package.json
@@ -1,6 +1,6 @@
{
- "name": "@amplitude/plugin-session-replay",
- "version": "1.0.0-beta.0",
+ "name": "@amplitude/plugin-session-replay-browser",
+ "version": "0.0.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -10,7 +10,8 @@
"types": "lib/esm/index.d.ts",
"sideEffects": false,
"publishConfig": {
- "access": "public"
+ "access": "public",
+ "tag": "beta"
},
"repository": {
"type": "git",
@@ -36,14 +37,14 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.0-beta.0",
- "@amplitude/analytics-types": "^1.0.0-beta.0",
+ "@amplitude/analytics-client-common": ">=1 <3",
+ "@amplitude/analytics-types": ">=1 <3",
"idb-keyval": "^6.2.1",
"rrweb": "^2.0.0-alpha.4",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.10.3",
+ "@amplitude/analytics-browser": ">=1 <3",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-session-replay/src/index.ts b/packages/plugin-session-replay/src/index.ts
index 4a1605e03..7ec717f3b 100644
--- a/packages/plugin-session-replay/src/index.ts
+++ b/packages/plugin-session-replay/src/index.ts
@@ -1,2 +1 @@
export { SessionReplayPlugin as Plugin, SessionReplayPlugin } from './session-replay';
-export * as Types from './typings/session-replay';
diff --git a/packages/plugin-session-replay/src/typings/session-replay.ts b/packages/plugin-session-replay/src/typings/session-replay.ts
index 12329dc8e..21445cbb5 100644
--- a/packages/plugin-session-replay/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay/src/typings/session-replay.ts
@@ -1,12 +1,3 @@
-/* eslint-disable @typescript-eslint/no-empty-interface */
-import { EnrichmentPlugin } from '@amplitude/analytics-types';
-
-export interface Options {}
-
-export interface CreateSessionReplayPlugin {
- (options?: Options): EnrichmentPlugin;
-}
-
export type Events = string[];
export interface SessionReplayContext {
From 84b349c868308a759204dc7dbf9115bd5ee69ab3 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 26 Jun 2023 15:16:45 -0400
Subject: [PATCH 035/214] feat(plugins): update yarn.lock after changing branch
base
---
yarn.lock | 88 +++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 69 insertions(+), 19 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 33a296cca..0a43256b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,7 +2,7 @@
# yarn lockfile v1
-"@amplitude/analytics-connector@^1.4.8":
+"@amplitude/analytics-connector@^1.4.5":
version "1.4.8"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-connector/-/analytics-connector-1.4.8.tgz#dd801303db2662bc51be7e0194eeb8bd72267c42"
integrity sha512-dFW7c7Wb6Ng7vbmzwbaXZSpqfBx37ukamJV9ErFYYS8vGZK/Hkbt3M7fZHBI4WFU6CCwakr2ZXPme11uGPYWkQ==
@@ -3778,6 +3778,13 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
+"@rrweb/types@^2.0.0-alpha.4":
+ version "2.0.0-alpha.8"
+ resolved "https://registry.yarnpkg.com/@rrweb/types/-/types-2.0.0-alpha.8.tgz#29a6550dd833364a459af4be4ce3b233003fb78b"
+ integrity sha512-yAr6ZQrgmr7+qZU5DMGqYXnVsolC5epftmZtkOtgFD/bbvCWflNnl09M32hUjttlKCV1ohhmQGioXkCQ37IF7A==
+ dependencies:
+ rrweb-snapshot "^2.0.0-alpha.8"
+
"@sideway/address@^4.1.3":
version "4.1.4"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
@@ -3872,6 +3879,11 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/css-font-loading-module@0.0.7":
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz#2f98ede46acc0975de85c0b7b0ebe06041d24601"
+ integrity sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==
+
"@types/estree@*", "@types/estree@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
@@ -4125,6 +4137,11 @@
"@typescript-eslint/types" "5.46.1"
eslint-visitor-keys "^3.3.0"
+"@xstate/fsm@^1.4.0":
+ version "1.6.5"
+ resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.6.5.tgz#f599e301997ad7e3c572a0b1ff0696898081bea5"
+ integrity sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==
+
"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
@@ -5129,6 +5146,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+base64-arraybuffer@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
+ integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
+
base64-js@^1.1.2, base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -6690,6 +6712,11 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
+fflate@^0.4.4:
+ version "0.4.8"
+ resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
+ integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
+
figures@3.2.0, figures@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
@@ -7378,6 +7405,11 @@ iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+idb-keyval@^6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33"
+ integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==
+
ieee754@^1.1.13:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
@@ -9447,6 +9479,11 @@ minizlib@^2.1.1, minizlib@^2.1.2:
minipass "^3.0.0"
yallist "^4.0.0"
+mitt@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
+ integrity sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==
+
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -9559,10 +9596,10 @@ nocache@^3.0.1:
resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.4.tgz#5b37a56ec6e09fc7d401dceaed2eab40c8bfdf79"
integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==
-nock@^13.2.4:
- version "13.2.9"
- resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.9.tgz#4faf6c28175d36044da4cfa68e33e5a15086ad4c"
- integrity sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==
+nock@^13.2.4, nock@^13.2.9:
+ version "13.3.1"
+ resolved "https://registry.yarnpkg.com/nock/-/nock-13.3.1.tgz#f22d4d661f7a05ebd9368edae1b5dc0a62d758fc"
+ integrity sha512-vHnopocZuI93p2ccivFyGuUfzjq2fxNyNurp7816mlT5V5HF4SzXu8lvLrVzBbNqzs+ODooZ6OksuSUNM7Njkw==
dependencies:
debug "^4.1.0"
json-stringify-safe "^5.0.1"
@@ -10980,6 +11017,32 @@ rollup@^2.79.1:
optionalDependencies:
fsevents "~2.3.2"
+rrdom@^0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/rrdom/-/rrdom-0.1.7.tgz#f2f49bfd01b59291bb7b0d981371a5e02a18e2aa"
+ integrity sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==
+ dependencies:
+ rrweb-snapshot "^2.0.0-alpha.4"
+
+rrweb-snapshot@^2.0.0-alpha.4, rrweb-snapshot@^2.0.0-alpha.8:
+ version "2.0.0-alpha.8"
+ resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.8.tgz#5f93f8ab7d78b3de26f6a64e993ff68edd54e0cf"
+ integrity sha512-3Rb7c+mnDEADQ8N9qn9SDH5PzCyHlZ1cwZC932qRyt9O8kJWLM11JLYqqEyQCa2FZVQbzH2iAaCgnyM7A32p7A==
+
+rrweb@^2.0.0-alpha.4:
+ version "2.0.0-alpha.4"
+ resolved "https://registry.yarnpkg.com/rrweb/-/rrweb-2.0.0-alpha.4.tgz#3c7cf2f1bcf44f7a88dd3fad00ee8d6dd711f258"
+ integrity sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==
+ dependencies:
+ "@rrweb/types" "^2.0.0-alpha.4"
+ "@types/css-font-loading-module" "0.0.7"
+ "@xstate/fsm" "^1.4.0"
+ base64-arraybuffer "^1.0.1"
+ fflate "^0.4.4"
+ mitt "^3.0.0"
+ rrdom "^0.1.7"
+ rrweb-snapshot "^2.0.0-alpha.4"
+
run-async@^2.4.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
@@ -12495,20 +12558,7 @@ yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
-yargs@^17.0.0, yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2:
- version "17.6.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
- integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==
- dependencies:
- cliui "^8.0.1"
- escalade "^3.1.1"
- get-caller-file "^2.0.5"
- require-directory "^2.1.1"
- string-width "^4.2.3"
- y18n "^5.0.5"
- yargs-parser "^21.1.1"
-
-yargs@^17.7.1:
+yargs@^17.0.0, yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2, yargs@^17.7.1:
version "17.7.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967"
integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==
From 83ce8371f8edea800ff6ac11c6184d8cc1bb17c7 Mon Sep 17 00:00:00 2001
From: Andrey Sokolov
Date: Mon, 26 Jun 2023 23:29:51 +0400
Subject: [PATCH 036/214] fix: add app_set_id, idfa and idfv support (#456)
* fix: added support for Android app set ID
* fix: add support for idfa/idfv
* fix: change argument type for getApplicationContext() native method
* fix: remove direct support for idfa, add plugin example with idfa support
* fix: use a 3rd party package in example idfa plugin
---
.../react-native-idfa-plugin/idfaPlugin.ts | 25 +++++++++++++++++++
.../reactnative/AmplitudeReactNativeModule.kt | 8 +++---
.../ios/AmplitudeReactNative.m | 2 +-
.../ios/AmplitudeReactNative.swift | 13 +++++++---
.../ios/AppleContextProvider.swift | 12 +++++++++
packages/analytics-react-native/src/config.ts | 2 ++
.../src/plugins/context.ts | 18 ++++++++++---
.../test/config.test.ts | 6 +++++
.../test/helpers/default.ts | 2 ++
.../test/plugins/context.test.ts | 5 ++++
packages/analytics-types/src/base-event.ts | 1 +
.../src/config/react-native.ts | 2 ++
12 files changed, 85 insertions(+), 11 deletions(-)
create mode 100644 examples/plugins/react-native-idfa-plugin/idfaPlugin.ts
diff --git a/examples/plugins/react-native-idfa-plugin/idfaPlugin.ts b/examples/plugins/react-native-idfa-plugin/idfaPlugin.ts
new file mode 100644
index 000000000..52cda28e7
--- /dev/null
+++ b/examples/plugins/react-native-idfa-plugin/idfaPlugin.ts
@@ -0,0 +1,25 @@
+import { Types } from '@amplitude/analytics-react-native';
+import ReactNativeIdfaAaid from '@sparkfabrik/react-native-idfa-aaid';
+
+export default class IdfaPlugin implements Types.BeforePlugin {
+ name = 'idfa';
+ type = Types.PluginType.BEFORE as any;
+ idfa: string | null = null;
+
+ async setup(_config: Types.Config): Promise {
+ try {
+ const info = await ReactNativeIdfaAaid.getAdvertisingInfo();
+ this.idfa = info.id;
+ } catch (e) {
+ console.log(e);
+ }
+ return undefined;
+ }
+
+ async execute(context: Types.Event): Promise {
+ if (this.idfa) {
+ context.idfa = this.idfa;
+ }
+ return context;
+ }
+}
diff --git a/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt b/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt
index d42085c9c..e8fb30c16 100644
--- a/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt
+++ b/packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/AmplitudeReactNativeModule.kt
@@ -20,9 +20,10 @@ class AmplitudeReactNativeModule(private val reactContext: ReactApplicationConte
}
@ReactMethod
- private fun getApplicationContext(shouldTrackAdid: Boolean, promise: Promise) {
+ private fun getApplicationContext(options: ReadableMap, promise: Promise) {
+ val trackAdid = if (options.hasKey("adid")) options.getBoolean("adid") else false
if (androidContextProvider == null) {
- androidContextProvider = AndroidContextProvider(reactContext.applicationContext, false, shouldTrackAdid)
+ androidContextProvider = AndroidContextProvider(reactContext.applicationContext, false, trackAdid)
}
promise.resolve(WritableNativeMap().apply {
@@ -35,9 +36,10 @@ class AmplitudeReactNativeModule(private val reactContext: ReactApplicationConte
putString("deviceManufacturer", androidContextProvider!!.manufacturer)
putString("deviceModel", androidContextProvider!!.model)
putString("carrier", androidContextProvider!!.carrier)
- if (androidContextProvider!!.advertisingId != null) {
+ if (trackAdid) {
putString("adid", androidContextProvider!!.advertisingId)
}
+ putString("appSetId", androidContextProvider!!.appSetId)
})
}
}
diff --git a/packages/analytics-react-native/ios/AmplitudeReactNative.m b/packages/analytics-react-native/ios/AmplitudeReactNative.m
index 3c9a587a2..8f3aa47f2 100644
--- a/packages/analytics-react-native/ios/AmplitudeReactNative.m
+++ b/packages/analytics-react-native/ios/AmplitudeReactNative.m
@@ -2,6 +2,6 @@
@interface RCT_EXTERN_MODULE(AmplitudeReactNative, NSObject)
-RCT_EXTERN_METHOD(getApplicationContext: (BOOL)shouldTrackAdid resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
+RCT_EXTERN_METHOD(getApplicationContext: (NSDictionary*)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
@end
diff --git a/packages/analytics-react-native/ios/AmplitudeReactNative.swift b/packages/analytics-react-native/ios/AmplitudeReactNative.swift
index d1d8d51a5..282a9aba4 100644
--- a/packages/analytics-react-native/ios/AmplitudeReactNative.swift
+++ b/packages/analytics-react-native/ios/AmplitudeReactNative.swift
@@ -4,8 +4,6 @@ import React
@objc(AmplitudeReactNative)
class ReactNative: NSObject {
- private let appleContextProvider = AppleContextProvider()
-
@objc
static func requiresMainQueueSetup() -> Bool {
return false
@@ -13,11 +11,15 @@ class ReactNative: NSObject {
@objc
func getApplicationContext(
- _ shouldTrackAdid: Bool,
+ _ options: NSDictionary,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) -> Void {
- let applicationContext: [String: String?] = [
+ let trackingOptions = options as! [String: Bool]
+ let trackIdfv = trackingOptions["idfv"] ?? false
+ let appleContextProvider = AppleContextProvider(trackIdfv: trackIdfv)
+
+ var applicationContext: [String: String?] = [
"version": appleContextProvider.version,
"platform": appleContextProvider.platform,
"language": appleContextProvider.language,
@@ -26,6 +28,9 @@ class ReactNative: NSObject {
"deviceManufacturer": appleContextProvider.deviceManufacturer,
"deviceModel": appleContextProvider.deviceModel,
]
+ if (trackIdfv) {
+ applicationContext["idfv"] = appleContextProvider.idfv
+ }
resolve(applicationContext)
}
}
diff --git a/packages/analytics-react-native/ios/AppleContextProvider.swift b/packages/analytics-react-native/ios/AppleContextProvider.swift
index 12367d511..31ca43706 100644
--- a/packages/analytics-react-native/ios/AppleContextProvider.swift
+++ b/packages/analytics-react-native/ios/AppleContextProvider.swift
@@ -9,6 +9,14 @@ import Foundation
public let osVersion: String = AppleContextProvider.getOsVersion()
public let deviceManufacturer: String = AppleContextProvider.getDeviceManufacturer()
public let deviceModel: String = AppleContextProvider.getDeviceModel()
+ public var idfv: String? = nil
+
+ init(trackIdfv: Bool) {
+ super.init()
+ if (trackIdfv) {
+ fetchIdfv()
+ }
+ }
private static func getVersion() -> String? {
return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
@@ -41,6 +49,10 @@ import Foundation
return String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
}
+ private func fetchIdfv() {
+ self.idfv = UIDevice.current.identifierForVendor?.uuidString
+ }
+
private static func getDeviceModel() -> String {
let platform = getPlatformString()
// == iPhone ==
diff --git a/packages/analytics-react-native/src/config.ts b/packages/analytics-react-native/src/config.ts
index 6b8b9880e..d0c80e192 100644
--- a/packages/analytics-react-native/src/config.ts
+++ b/packages/analytics-react-native/src/config.ts
@@ -23,6 +23,8 @@ export const getDefaultConfig = () => {
osName: true,
osVersion: true,
platform: true,
+ appSetId: true,
+ idfv: true,
};
return {
cookieExpiration: 365,
diff --git a/packages/analytics-react-native/src/plugins/context.ts b/packages/analytics-react-native/src/plugins/context.ts
index 08391e616..8c4414a44 100644
--- a/packages/analytics-react-native/src/plugins/context.ts
+++ b/packages/analytics-react-native/src/plugins/context.ts
@@ -1,4 +1,10 @@
-import { BeforePlugin, ReactNativeConfig, Event, PluginType } from '@amplitude/analytics-types';
+import {
+ BeforePlugin,
+ ReactNativeConfig,
+ Event,
+ PluginType,
+ ReactNativeTrackingOptions,
+} from '@amplitude/analytics-types';
import UAParser from '@amplitude/ua-parser-js';
import { UUID } from '@amplitude/analytics-core';
import { getLanguage } from '@amplitude/analytics-client-common';
@@ -19,10 +25,12 @@ type NativeContext = {
deviceModel: string;
carrier: string;
adid: string;
+ appSetId: string;
+ idfv: string;
};
export interface AmplitudeReactNative {
- getApplicationContext(shouldTrackAdid: boolean): Promise;
+ getApplicationContext(options: ReactNativeTrackingOptions): Promise;
}
export class Context implements BeforePlugin {
@@ -55,7 +63,7 @@ export class Context implements BeforePlugin {
async execute(context: Event): Promise {
const time = new Date().getTime();
- const nativeContext = await this.nativeModule?.getApplicationContext(this.config.trackingOptions.adid ?? false);
+ const nativeContext = await this.nativeModule?.getApplicationContext(this.config.trackingOptions);
const appVersion = nativeContext?.version || this.config.appVersion;
const platform = nativeContext?.platform || BROWSER_PLATFORM;
const osName = nativeContext?.osName || this.uaResult.browser.name;
@@ -65,6 +73,8 @@ export class Context implements BeforePlugin {
const language = nativeContext?.language || getLanguage();
const carrier = nativeContext?.carrier;
const adid = nativeContext?.adid;
+ const appSetId = nativeContext?.appSetId;
+ const idfv = nativeContext?.idfv;
const event: Event = {
user_id: this.config.userId,
@@ -81,6 +91,8 @@ export class Context implements BeforePlugin {
...(this.config.trackingOptions.carrier && { carrier: carrier }),
...(this.config.trackingOptions.ipAddress && { ip: IP_ADDRESS }),
...(this.config.trackingOptions.adid && { adid: adid }),
+ ...(this.config.trackingOptions.appSetId && { android_app_set_id: appSetId }),
+ ...(this.config.trackingOptions.idfv && { idfv: idfv }),
insert_id: UUID(),
partner_id: this.config.partnerId,
plan: this.config.plan,
diff --git a/packages/analytics-react-native/test/config.test.ts b/packages/analytics-react-native/test/config.test.ts
index 458ba04d6..cc2470b29 100644
--- a/packages/analytics-react-native/test/config.test.ts
+++ b/packages/analytics-react-native/test/config.test.ts
@@ -55,6 +55,8 @@ describe('config', () => {
osName: true,
osVersion: true,
platform: true,
+ appSetId: true,
+ idfv: true,
},
transportProvider: new FetchTransport(),
useBatch: false,
@@ -106,6 +108,8 @@ describe('config', () => {
osName: true,
osVersion: true,
platform: true,
+ appSetId: true,
+ idfv: true,
},
transportProvider: new FetchTransport(),
useBatch: false,
@@ -185,6 +189,8 @@ describe('config', () => {
osName: true,
osVersion: true,
platform: true,
+ appSetId: true,
+ idfv: true,
},
transportProvider: new FetchTransport(),
useBatch: false,
diff --git a/packages/analytics-react-native/test/helpers/default.ts b/packages/analytics-react-native/test/helpers/default.ts
index ebf714a4b..6ebf51175 100644
--- a/packages/analytics-react-native/test/helpers/default.ts
+++ b/packages/analytics-react-native/test/helpers/default.ts
@@ -38,6 +38,8 @@ export const DEFAULT_OPTIONS: Partial = {
osName: true,
osVersion: true,
platform: true,
+ appSetId: true,
+ idfv: true,
},
transportProvider: {
send: () => Promise.resolve(null),
diff --git a/packages/analytics-react-native/test/plugins/context.test.ts b/packages/analytics-react-native/test/plugins/context.test.ts
index 679386bec..65e6ae06b 100644
--- a/packages/analytics-react-native/test/plugins/context.test.ts
+++ b/packages/analytics-react-native/test/plugins/context.test.ts
@@ -72,6 +72,8 @@ describe('context', () => {
osName: false,
osVersion: false,
platform: false,
+ appSetId: false,
+ idfv: false,
},
userId: 'user@amplitude.com',
});
@@ -92,6 +94,9 @@ describe('context', () => {
expect(firstContextEvent.os_version).toBeUndefined();
expect(firstContextEvent.language).toBeUndefined();
expect(firstContextEvent.ip).toBeUndefined();
+ expect(firstContextEvent.adid).toBeUndefined();
+ expect(firstContextEvent.android_app_set_id).toBeUndefined();
+ expect(firstContextEvent.idfv).toBeUndefined();
expect(firstContextEvent.device_id).toEqual('deviceId');
expect(firstContextEvent.session_id).toEqual(1);
expect(firstContextEvent.user_id).toEqual('user@amplitude.com');
diff --git a/packages/analytics-types/src/base-event.ts b/packages/analytics-types/src/base-event.ts
index 615667299..8585c3fb3 100644
--- a/packages/analytics-types/src/base-event.ts
+++ b/packages/analytics-types/src/base-event.ts
@@ -47,5 +47,6 @@ export interface EventOptions {
ingestion_metadata?: IngestionMetadataEventProperty;
partner_id?: string;
user_agent?: string;
+ android_app_set_id?: string;
extra?: { [key: string]: any };
}
diff --git a/packages/analytics-types/src/config/react-native.ts b/packages/analytics-types/src/config/react-native.ts
index da508a9d7..67fb7c911 100644
--- a/packages/analytics-types/src/config/react-native.ts
+++ b/packages/analytics-types/src/config/react-native.ts
@@ -9,6 +9,8 @@ export interface ReactNativeConfig extends BrowserConfig {
export interface ReactNativeTrackingOptions extends TrackingOptions {
adid?: boolean;
carrier?: boolean;
+ appSetId?: boolean;
+ idfv?: boolean;
}
type HiddenOptions = 'apiKey' | 'lastEventId';
From 72f4348e07d6cc065442f431032bc10ae4a095a7 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 26 Jun 2023 15:58:34 -0400
Subject: [PATCH 037/214] feat(plugins): adjust timing of setup to capture
early sent events
---
.../plugin-session-replay/src/session-replay.ts | 6 +++---
.../test/session-replay.test.ts | 14 ++++++++++++--
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index b687751c9..e70209910 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -32,9 +32,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
- await this.emptyStoreAndReset();
-
- this.recordEvents();
+ void this.emptyStoreAndReset();
}
execute(event: Event) {
@@ -61,6 +59,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
async emptyStoreAndReset() {
const storedReplaySessions = await this.getAllSessionEventsFromStore();
+ console.log('in empty store after await');
if (storedReplaySessions) {
for (const sessionId in storedReplaySessions) {
const storedReplayEvents = storedReplaySessions[sessionId];
@@ -76,6 +75,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
void this.storeEventsForSession([], this.currentSequenceId);
+ this.recordEvents();
}
}
diff --git a/packages/plugin-session-replay/test/session-replay.test.ts b/packages/plugin-session-replay/test/session-replay.test.ts
index 3cc661bab..195f980dc 100644
--- a/packages/plugin-session-replay/test/session-replay.test.ts
+++ b/packages/plugin-session-replay/test/session-replay.test.ts
@@ -112,7 +112,7 @@ describe('SessionReplayPlugin', () => {
...mockConfig,
sessionId: 456,
};
- get.mockResolvedValueOnce({
+ const mockGetResolution = Promise.resolve({
123: {
events: [mockEventString],
sequenceId: 3,
@@ -122,9 +122,11 @@ describe('SessionReplayPlugin', () => {
sequenceId: 1,
},
});
+ get.mockReturnValueOnce(mockGetResolution);
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
await sessionReplay.setup(config);
+ await mockGetResolution;
jest.runAllTimers();
expect(send).toHaveBeenCalledTimes(2);
@@ -158,8 +160,10 @@ describe('SessionReplayPlugin', () => {
const config = {
...mockConfig,
};
- get.mockResolvedValueOnce({});
+ const mockGetResolution = Promise.resolve({});
+ get.mockReturnValueOnce(mockGetResolution);
await sessionReplay.setup(config);
+ await mockGetResolution;
expect(sessionReplay.currentSequenceId).toBe(0);
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
@@ -170,8 +174,11 @@ describe('SessionReplayPlugin', () => {
});
});
test('should record events', async () => {
+ const mockGetResolution = Promise.resolve({});
+ get.mockReturnValueOnce(mockGetResolution);
const sessionReplay = new SessionReplayPlugin();
await sessionReplay.setup(mockConfig);
+ await mockGetResolution;
jest.runAllTimers();
expect(record).toHaveBeenCalledTimes(1);
});
@@ -201,7 +208,10 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = new SessionReplayPlugin();
const mockStopRecordingEvents = jest.fn();
record.mockReturnValue(mockStopRecordingEvents);
+ const mockGetResolution = Promise.resolve({});
+ get.mockReturnValueOnce(mockGetResolution);
await sessionReplay.setup(mockConfig);
+ await mockGetResolution;
jest.runAllTimers();
const event = {
event_type: 'session_start',
From 01c20d61e8c51b59dc7fdf83d4abd57391aecf71 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Mon, 26 Jun 2023 22:09:09 +0000
Subject: [PATCH 038/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.6
- @amplitude/analytics-browser@1.12.0
- @amplitude/analytics-client-common@1.1.0
- @amplitude/analytics-core@1.2.0
- @amplitude/analytics-node-test@1.0.4
- @amplitude/analytics-node@1.3.0
- @amplitude/analytics-react-native@1.3.0
- @amplitude/analytics-types@1.3.0
- @amplitude/marketing-analytics-browser@1.0.6
- @amplitude/plugin-page-view-tracking-browser@1.0.6
- @amplitude/plugin-web-attribution-browser@1.0.6
---
packages/analytics-browser-test/CHANGELOG.md | 9 ++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 13 ++++++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 12 +++++------
.../analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 13 ++++++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 13 ++++++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 ++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 13 ++++++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 18 ++++++++++++++++
packages/analytics-react-native/package.json | 8 +++----
.../analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 21 +++++++++++++++++++
packages/analytics-types/package.json | 2 +-
.../marketing-analytics-browser/CHANGELOG.md | 9 ++++++++
.../marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
.../marketing-analytics-browser/package.json | 14 ++++++-------
.../src/version.ts | 2 +-
.../CHANGELOG.md | 9 ++++++++
.../package.json | 8 +++----
.../CHANGELOG.md | 9 ++++++++
.../package.json | 8 +++----
32 files changed, 187 insertions(+), 51 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index 754f26251..03ef9016c 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.5...@amplitude/analytics-browser-test@1.4.6) (2023-06-26)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.4.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.4...@amplitude/analytics-browser-test@1.4.5) (2023-06-14)
**Note:** Version bump only for package @amplitude/analytics-browser-test
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index 914059eaf..96b473245 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.5",
+ "version": "1.4.6",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.11.2"
+ "@amplitude/analytics-browser": "^1.12.0"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index 646aea545..a8b538e4b 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.12.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.11.2...@amplitude/analytics-browser@1.12.0) (2023-06-26)
+
+### Features
+
+- add option for instance name ([#428](https://github.com/amplitude/Amplitude-TypeScript/issues/428))
+ ([#442](https://github.com/amplitude/Amplitude-TypeScript/issues/442))
+ ([569464c](https://github.com/amplitude/Amplitude-TypeScript/commit/569464c698eb54b3da05e203ac90cf1a399d96ed))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.11.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.11.1...@amplitude/analytics-browser@1.11.2) (2023-06-14)
**Note:** Version bump only for package @amplitude/analytics-browser
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index 7332fe927..a43a16b2c 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index c3d50c9ef..6c6c42737 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-bhExRHNdxBGFQ+AkmAurYWjqboez+iJTTaWsIubqgMeSgQZqjXMa8BI8890ijsCf';
+ as.integrity = 'sha384-s4Wx3hZn20lk10XSCWAIMwlpJRoowQ1Yt6E0gUEhrmytlmyODAskmfnOcu84UYAg';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.11.2-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.12.0-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index 388ae14e8..d73e6b7e7 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.11.2",
+ "version": "1.12.0",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -44,11 +44,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.3",
- "@amplitude/analytics-core": "^1.1.2",
- "@amplitude/analytics-types": "^1.2.1",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.5",
- "@amplitude/plugin-web-attribution-browser": "^1.0.5",
+ "@amplitude/analytics-client-common": "^1.1.0",
+ "@amplitude/analytics-core": "^1.2.0",
+ "@amplitude/analytics-types": "^1.3.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.6",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.6",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 9d8797e08..17b09d826 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",D="Zebra",A="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;i=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Oe()).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return W.sent(),[4,this.add(new qe).promise];case 12:return W.sent(),[4,this.add(new xe).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(k=this.config.attribution)||void 0===k?void 0:k.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:ae(e.init.bind(e),"init",re(e),se(e,["config"])),add:ae(e.add.bind(e),"add",re(e),se(e,["config.apiKey","timeline.plugins"])),remove:ae(e.remove.bind(e),"remove",re(e),se(e,["config.apiKey","timeline.plugins"])),track:ae(e.track.bind(e),"track",re(e),se(e,["config.apiKey","timeline.queue.length"])),logEvent:ae(e.logEvent.bind(e),"logEvent",re(e),se(e,["config.apiKey","timeline.queue.length"])),identify:ae(e.identify.bind(e),"identify",re(e),se(e,["config.apiKey","timeline.queue.length"])),groupIdentify:ae(e.groupIdentify.bind(e),"groupIdentify",re(e),se(e,["config.apiKey","timeline.queue.length"])),setGroup:ae(e.setGroup.bind(e),"setGroup",re(e),se(e,["config.apiKey","timeline.queue.length"])),revenue:ae(e.revenue.bind(e),"revenue",re(e),se(e,["config.apiKey","timeline.queue.length"])),flush:ae(e.flush.bind(e),"flush",re(e),se(e,["config.apiKey","timeline.queue.length"])),getUserId:ae(e.getUserId.bind(e),"getUserId",re(e),se(e,["config","config.userId"])),setUserId:ae(e.setUserId.bind(e),"setUserId",re(e),se(e,["config","config.userId"])),getDeviceId:ae(e.getDeviceId.bind(e),"getDeviceId",re(e),se(e,["config","config.deviceId"])),setDeviceId:ae(e.setDeviceId.bind(e),"setDeviceId",re(e),se(e,["config","config.deviceId"])),reset:ae(e.reset.bind(e),"reset",re(e),se(e,["config","config.userId","config.deviceId"])),getSessionId:ae(e.getSessionId.bind(e),"getSessionId",re(e),se(e,["config"])),setSessionId:ae(e.setSessionId.bind(e),"setSessionId",re(e),se(e,["config"])),extendSession:ae(e.extendSession.bind(e),"extendSession",re(e),se(e,["config"])),setOptOut:ae(e.setOptOut.bind(e),"setOptOut",re(e),se(e,["config"])),setTransport:ae(e.setTransport.bind(e),"setTransport",re(e),se(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,kt=wt.flush,Et=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,Ut=wt.reset,qt=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:kt,getDeviceId:Et,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:Ut,revenue:qt,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:q,createInstance:mt,runQueuedFunctions:Re,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Re(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",D="Zebra",A="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;i=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Oe(t.instanceName)).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return W.sent(),[4,this.add(new qe).promise];case 12:return W.sent(),[4,this.add(new xe).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(k=this.config.attribution)||void 0===k?void 0:k.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:ae(e.init.bind(e),"init",re(e),se(e,["config"])),add:ae(e.add.bind(e),"add",re(e),se(e,["config.apiKey","timeline.plugins"])),remove:ae(e.remove.bind(e),"remove",re(e),se(e,["config.apiKey","timeline.plugins"])),track:ae(e.track.bind(e),"track",re(e),se(e,["config.apiKey","timeline.queue.length"])),logEvent:ae(e.logEvent.bind(e),"logEvent",re(e),se(e,["config.apiKey","timeline.queue.length"])),identify:ae(e.identify.bind(e),"identify",re(e),se(e,["config.apiKey","timeline.queue.length"])),groupIdentify:ae(e.groupIdentify.bind(e),"groupIdentify",re(e),se(e,["config.apiKey","timeline.queue.length"])),setGroup:ae(e.setGroup.bind(e),"setGroup",re(e),se(e,["config.apiKey","timeline.queue.length"])),revenue:ae(e.revenue.bind(e),"revenue",re(e),se(e,["config.apiKey","timeline.queue.length"])),flush:ae(e.flush.bind(e),"flush",re(e),se(e,["config.apiKey","timeline.queue.length"])),getUserId:ae(e.getUserId.bind(e),"getUserId",re(e),se(e,["config","config.userId"])),setUserId:ae(e.setUserId.bind(e),"setUserId",re(e),se(e,["config","config.userId"])),getDeviceId:ae(e.getDeviceId.bind(e),"getDeviceId",re(e),se(e,["config","config.deviceId"])),setDeviceId:ae(e.setDeviceId.bind(e),"setDeviceId",re(e),se(e,["config","config.deviceId"])),reset:ae(e.reset.bind(e),"reset",re(e),se(e,["config","config.userId","config.deviceId"])),getSessionId:ae(e.getSessionId.bind(e),"getSessionId",re(e),se(e,["config"])),setSessionId:ae(e.setSessionId.bind(e),"setSessionId",re(e),se(e,["config"])),extendSession:ae(e.extendSession.bind(e),"extendSession",re(e),se(e,["config"])),setOptOut:ae(e.setOptOut.bind(e),"setOptOut",re(e),se(e,["config"])),setTransport:ae(e.setTransport.bind(e),"setTransport",re(e),se(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,kt=wt.flush,Et=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,Ut=wt.reset,qt=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:kt,getDeviceId:Et,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:Ut,revenue:qt,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:q,createInstance:mt,runQueuedFunctions:Re,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Re(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index 8be97ab3d..4a216eefc 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-GCneBnzsMTw+kmn6TwyFynia1fmqj+38TkwyYEyW8AkwF+Qw8IAh1CjqSgDYZ9X+';
+ as.integrity = 'sha384-uQFYsRWKr27i5VRoSShdW+SWnMC3RYcwlKIyk4cs8E1KrVgcdqeAI4Dca3cGejM+';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.5-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.6-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index d4784919c..cac52bf19 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-ocQG0VTdcJGeNGgEnHLUASJ+2qacfMa5iU5EsLRXLdGs/g2XAPDUI4zdjSMlhO5U';
+ as.integrity = 'sha384-LxzsESYOYIQDLuCbztiSAfn/WnK7IQMVua4D9OvBkNgfqKHs8lZOfldylsVDG7m4';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.5-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.6-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index 4d151e352..070ca4b1c 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.5",
+ "version": "1.0.6",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -43,12 +43,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.11.2",
- "@amplitude/analytics-client-common": "^1.0.3",
- "@amplitude/analytics-core": "^1.1.2",
- "@amplitude/analytics-types": "^1.2.1",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.5",
- "@amplitude/plugin-web-attribution-browser": "^1.0.5",
+ "@amplitude/analytics-browser": "^1.12.0",
+ "@amplitude/analytics-client-common": "^1.1.0",
+ "@amplitude/analytics-core": "^1.2.0",
+ "@amplitude/analytics-types": "^1.3.0",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.6",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.6",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index 6cec2cb92..3f6f127aa 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.5';
+export const VERSION = '1.0.6';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 48be54e1c..401e9f308 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.5...@amplitude/plugin-page-view-tracking-browser@1.0.6) (2023-06-26)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.4...@amplitude/plugin-page-view-tracking-browser@1.0.5) (2023-06-14)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index d77195f30..a2cd4b9a0 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.5",
+ "version": "1.0.6",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,12 +37,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.3",
- "@amplitude/analytics-types": "^1.2.1",
+ "@amplitude/analytics-client-common": "^1.1.0",
+ "@amplitude/analytics-types": "^1.3.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.11.2",
+ "@amplitude/analytics-browser": "^1.12.0",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index 909d34853..a1bb02e33 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.5...@amplitude/plugin-web-attribution-browser@1.0.6) (2023-06-26)
+
+**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.5](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.4...@amplitude/plugin-web-attribution-browser@1.0.5) (2023-06-14)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index 3a1667300..69a77c34d 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.5",
+ "version": "1.0.6",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,12 +37,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.0.3",
- "@amplitude/analytics-types": "^1.2.1",
+ "@amplitude/analytics-client-common": "^1.1.0",
+ "@amplitude/analytics-types": "^1.3.0",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.11.2",
+ "@amplitude/analytics-browser": "^1.12.0",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From 07fc403ed2e08dc6685de1d150f0518abd859e09 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 27 Jun 2023 09:52:29 -0400
Subject: [PATCH 039/214] feat(plugins): update name of property for session
replay
---
packages/plugin-session-replay/src/constants.ts | 5 +++++
packages/plugin-session-replay/src/session-replay.ts | 7 ++++---
2 files changed, 9 insertions(+), 3 deletions(-)
create mode 100644 packages/plugin-session-replay/src/constants.ts
diff --git a/packages/plugin-session-replay/src/constants.ts b/packages/plugin-session-replay/src/constants.ts
new file mode 100644
index 000000000..2ca6feab9
--- /dev/null
+++ b/packages/plugin-session-replay/src/constants.ts
@@ -0,0 +1,5 @@
+export const DEFAULT_EVENT_PROPERTY_PREFIX = '[Amplitude]';
+
+export const DEFAULT_SESSION_REPLAY_PROPERTY = `${DEFAULT_EVENT_PROPERTY_PREFIX} Session Recorded`;
+export const DEFAULT_SESSION_START_EVENT = 'session_start';
+export const DEFAULT_SESSION_END_EVENT = 'session_end';
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay/src/session-replay.ts
index e70209910..a49895506 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay/src/session-replay.ts
@@ -2,6 +2,7 @@ import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
+import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import { Events, IDBStore, SessionReplayContext } from './typings/session-replay';
@@ -38,12 +39,12 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
execute(event: Event) {
event.event_properties = {
...event.event_properties,
- session_replay_enabled: true,
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
};
- if (event.event_type === 'session_start' && this.stopRecordingEvents) {
+ if (event.event_type === DEFAULT_SESSION_START_EVENT && this.stopRecordingEvents) {
this.stopRecordingEvents();
this.recordEvents();
- } else if (event.event_type === 'session_end') {
+ } else if (event.event_type === DEFAULT_SESSION_END_EVENT) {
if (event.session_id) {
this.sendEventsList({
events: this.events,
From 2c76247f8b994a52650db26dca3603319d13857e Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 27 Jun 2023 14:22:32 -0400
Subject: [PATCH 040/214] feat(plugins): change file names
---
packages/analytics-core/src/core-client.ts | 16 ++++++-----
.../README.md | 10 +++----
.../jest.config.js | 0
.../package.json | 0
.../rollup.config.js | 0
.../src/constants.ts | 0
.../src/helpers.ts | 0
.../src/index.ts | 0
.../src/messages.ts | 0
.../src/session-replay.ts | 3 +-
.../src/typings/session-replay.ts | 28 +++++++++++++++++++
.../test/session-replay.test.ts | 0
.../tsconfig.es5.json | 0
.../tsconfig.esm.json | 0
.../tsconfig.json | 0
.../src/typings/session-replay.ts | 16 -----------
16 files changed, 43 insertions(+), 30 deletions(-)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/README.md (84%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/jest.config.js (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/package.json (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/rollup.config.js (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/src/constants.ts (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/src/helpers.ts (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/src/index.ts (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/src/messages.ts (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/src/session-replay.ts (99%)
create mode 100644 packages/plugin-session-replay-browser/src/typings/session-replay.ts
rename packages/{plugin-session-replay => plugin-session-replay-browser}/test/session-replay.test.ts (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/tsconfig.es5.json (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/tsconfig.esm.json (100%)
rename packages/{plugin-session-replay => plugin-session-replay-browser}/tsconfig.json (100%)
delete mode 100644 packages/plugin-session-replay/src/typings/session-replay.ts
diff --git a/packages/analytics-core/src/core-client.ts b/packages/analytics-core/src/core-client.ts
index 6d2c84f93..9ef6a1ccc 100644
--- a/packages/analytics-core/src/core-client.ts
+++ b/packages/analytics-core/src/core-client.ts
@@ -1,24 +1,24 @@
import {
- CoreClient,
+ BaseEvent,
Config,
+ CoreClient,
Event,
- BaseEvent,
EventOptions,
Identify,
Plugin,
- Revenue,
Result,
+ Revenue,
} from '@amplitude/analytics-types';
+import { CLIENT_NOT_INITIALIZED, OPT_OUT_MESSAGE } from './messages';
+import { Timeline } from './timeline';
import {
+ createGroupEvent,
createGroupIdentifyEvent,
createIdentifyEvent,
- createTrackEvent,
createRevenueEvent,
- createGroupEvent,
+ createTrackEvent,
} from './utils/event-builder';
-import { Timeline } from './timeline';
import { buildResult } from './utils/result-builder';
-import { CLIENT_NOT_INITIALIZED, OPT_OUT_MESSAGE } from './messages';
import { returnWrapper } from './utils/return-wrapper';
export class AmplitudeCore implements CoreClient {
@@ -53,6 +53,7 @@ export class AmplitudeCore implements CoreClient {
}
track(eventInput: BaseEvent | string, eventProperties?: Record, eventOptions?: EventOptions) {
+ console.log('track', eventInput);
const event = createTrackEvent(eventInput, eventProperties, eventOptions);
return returnWrapper(this.dispatch(event));
}
@@ -80,6 +81,7 @@ export class AmplitudeCore implements CoreClient {
}
add(plugin: Plugin) {
+ console.log('adding plugin');
if (!this.config) {
this.q.push(this.add.bind(this, plugin));
return returnWrapper();
diff --git a/packages/plugin-session-replay/README.md b/packages/plugin-session-replay-browser/README.md
similarity index 84%
rename from packages/plugin-session-replay/README.md
rename to packages/plugin-session-replay-browser/README.md
index a1ed2a77e..5d06a97c9 100644
--- a/packages/plugin-session-replay/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -5,7 +5,7 @@
-# @amplitude/plugin-session-replay
+# @amplitude/plugin-session-replay-browser
Official Browser SDK plugin for session replay
@@ -15,10 +15,10 @@ This package is published on NPM registry and is available to be installed using
```sh
# npm
-npm install @amplitude/plugin-session-replay
+npm install @amplitude/plugin-session-replay-browser
# yarn
-yarn add @amplitude/plugin-session-replay
+yarn add @amplitude/plugin-session-replay-browser
```
## Usage
@@ -40,7 +40,7 @@ import { SessionReplayPlugin } from '@amplitude/plugin-session-replay-browser';
The plugin must be registered with the amplitude instance via the following code:
```typescript
-amplitude.init(API_KEY)
+amplitude.init(API_KEY);
const sessionReplayTracking = new SessionReplayPlugin();
-amplitude.add(sessionReplayTracking)
+amplitude.add(sessionReplayTracking);
```
\ No newline at end of file
diff --git a/packages/plugin-session-replay/jest.config.js b/packages/plugin-session-replay-browser/jest.config.js
similarity index 100%
rename from packages/plugin-session-replay/jest.config.js
rename to packages/plugin-session-replay-browser/jest.config.js
diff --git a/packages/plugin-session-replay/package.json b/packages/plugin-session-replay-browser/package.json
similarity index 100%
rename from packages/plugin-session-replay/package.json
rename to packages/plugin-session-replay-browser/package.json
diff --git a/packages/plugin-session-replay/rollup.config.js b/packages/plugin-session-replay-browser/rollup.config.js
similarity index 100%
rename from packages/plugin-session-replay/rollup.config.js
rename to packages/plugin-session-replay-browser/rollup.config.js
diff --git a/packages/plugin-session-replay/src/constants.ts b/packages/plugin-session-replay-browser/src/constants.ts
similarity index 100%
rename from packages/plugin-session-replay/src/constants.ts
rename to packages/plugin-session-replay-browser/src/constants.ts
diff --git a/packages/plugin-session-replay/src/helpers.ts b/packages/plugin-session-replay-browser/src/helpers.ts
similarity index 100%
rename from packages/plugin-session-replay/src/helpers.ts
rename to packages/plugin-session-replay-browser/src/helpers.ts
diff --git a/packages/plugin-session-replay/src/index.ts b/packages/plugin-session-replay-browser/src/index.ts
similarity index 100%
rename from packages/plugin-session-replay/src/index.ts
rename to packages/plugin-session-replay-browser/src/index.ts
diff --git a/packages/plugin-session-replay/src/messages.ts b/packages/plugin-session-replay-browser/src/messages.ts
similarity index 100%
rename from packages/plugin-session-replay/src/messages.ts
rename to packages/plugin-session-replay-browser/src/messages.ts
diff --git a/packages/plugin-session-replay/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
similarity index 99%
rename from packages/plugin-session-replay/src/session-replay.ts
rename to packages/plugin-session-replay-browser/src/session-replay.ts
index a49895506..da6aa2e0e 100644
--- a/packages/plugin-session-replay/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -13,7 +13,7 @@ const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON s
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
export class SessionReplayPlugin implements EnrichmentPlugin {
- name = '@amplitude/plugin-session-replay';
+ name = '@amplitude/plugin-session-replay-browser';
type = PluginType.ENRICHMENT as const;
// this.config is defined in setup() which will always be called first
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -60,7 +60,6 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
async emptyStoreAndReset() {
const storedReplaySessions = await this.getAllSessionEventsFromStore();
- console.log('in empty store after await');
if (storedReplaySessions) {
for (const sessionId in storedReplaySessions) {
const storedReplayEvents = storedReplaySessions[sessionId];
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
new file mode 100644
index 000000000..2ebd2612b
--- /dev/null
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -0,0 +1,28 @@
+import { BrowserClient, EnrichmentPlugin } from '@amplitude/analytics-types';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface Options {}
+
+export interface SessionReplayPlugin {
+ (client: BrowserClient, options?: Options): EnrichmentPlugin;
+ (options?: Options): EnrichmentPlugin;
+}
+
+export type SessionReplayPluginParameters = [BrowserClient, Options?] | [Options?];
+
+export type Events = string[];
+
+export interface SessionReplayContext {
+ events: Events;
+ sequenceId: number;
+ attempts: number;
+ timeout: number;
+ sessionId: number;
+}
+
+export interface IDBStore {
+ [sessionId: number]: {
+ events: Events;
+ sequenceId: number;
+ };
+}
diff --git a/packages/plugin-session-replay/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
similarity index 100%
rename from packages/plugin-session-replay/test/session-replay.test.ts
rename to packages/plugin-session-replay-browser/test/session-replay.test.ts
diff --git a/packages/plugin-session-replay/tsconfig.es5.json b/packages/plugin-session-replay-browser/tsconfig.es5.json
similarity index 100%
rename from packages/plugin-session-replay/tsconfig.es5.json
rename to packages/plugin-session-replay-browser/tsconfig.es5.json
diff --git a/packages/plugin-session-replay/tsconfig.esm.json b/packages/plugin-session-replay-browser/tsconfig.esm.json
similarity index 100%
rename from packages/plugin-session-replay/tsconfig.esm.json
rename to packages/plugin-session-replay-browser/tsconfig.esm.json
diff --git a/packages/plugin-session-replay/tsconfig.json b/packages/plugin-session-replay-browser/tsconfig.json
similarity index 100%
rename from packages/plugin-session-replay/tsconfig.json
rename to packages/plugin-session-replay-browser/tsconfig.json
diff --git a/packages/plugin-session-replay/src/typings/session-replay.ts b/packages/plugin-session-replay/src/typings/session-replay.ts
deleted file mode 100644
index 21445cbb5..000000000
--- a/packages/plugin-session-replay/src/typings/session-replay.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export type Events = string[];
-
-export interface SessionReplayContext {
- events: Events;
- sequenceId: number;
- attempts: number;
- timeout: number;
- sessionId: number;
-}
-
-export interface IDBStore {
- [sessionId: number]: {
- events: Events;
- sequenceId: number;
- };
-}
From 61185acc3504b96be9ea8144fc62967937235b37 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 27 Jun 2023 14:56:25 -0400
Subject: [PATCH 041/214] feat(plugins): expose function as external interface
---
.../plugin-session-replay-browser/README.md | 4 +-
.../src/index.ts | 2 +-
.../src/session-replay.ts | 18 +++--
.../src/typings/session-replay.ts | 56 ++++++++++++---
.../test/session-replay.test.ts | 68 +++++++++----------
5 files changed, 99 insertions(+), 49 deletions(-)
diff --git a/packages/plugin-session-replay-browser/README.md b/packages/plugin-session-replay-browser/README.md
index 5d06a97c9..5f546afe4 100644
--- a/packages/plugin-session-replay-browser/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -32,7 +32,7 @@ This plugin works on top of Amplitude Browser SDK and adds session replay featur
```typescript
import * as amplitude from '@amplitude/analytics-browser';
-import { SessionReplayPlugin } from '@amplitude/plugin-session-replay-browser';
+import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser';
```
### 2. Instantiate session replay plugin and install plugin to Amplitude SDK
@@ -41,6 +41,6 @@ The plugin must be registered with the amplitude instance via the following code
```typescript
amplitude.init(API_KEY);
-const sessionReplayTracking = new SessionReplayPlugin();
+const sessionReplayTracking = sessionReplayPlugin();
amplitude.add(sessionReplayTracking);
```
\ No newline at end of file
diff --git a/packages/plugin-session-replay-browser/src/index.ts b/packages/plugin-session-replay-browser/src/index.ts
index 7ec717f3b..b84fa2fa0 100644
--- a/packages/plugin-session-replay-browser/src/index.ts
+++ b/packages/plugin-session-replay-browser/src/index.ts
@@ -1 +1 @@
-export { SessionReplayPlugin as Plugin, SessionReplayPlugin } from './session-replay';
+export { sessionReplayPlugin as plugin, sessionReplayPlugin } from './session-replay';
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index da6aa2e0e..d4d1926c7 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -1,18 +1,24 @@
import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
-import { BrowserConfig, EnrichmentPlugin, Event, PluginType, Status } from '@amplitude/analytics-types';
+import { BrowserConfig, Event, PluginType, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
-import { Events, IDBStore, SessionReplayContext } from './typings/session-replay';
+import {
+ Events,
+ IDBStore,
+ SessionReplayContext,
+ SessionReplayEnrichmentPlugin,
+ SessionReplayPlugin,
+} from './typings/session-replay';
const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-export class SessionReplayPlugin implements EnrichmentPlugin {
+class SessionReplay implements SessionReplayEnrichmentPlugin {
name = '@amplitude/plugin-session-replay-browser';
type = PluginType.ENRICHMENT as const;
// this.config is defined in setup() which will always be called first
@@ -36,7 +42,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
void this.emptyStoreAndReset();
}
- execute(event: Event) {
+ async execute(event: Event) {
event.event_properties = {
...event.event_properties,
[DEFAULT_SESSION_REPLAY_PROPERTY]: true,
@@ -284,3 +290,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin {
}
}
}
+
+export const sessionReplayPlugin: SessionReplayPlugin = () => {
+ return new SessionReplay();
+};
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 2ebd2612b..f0bd54a48 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -1,15 +1,9 @@
-import { BrowserClient, EnrichmentPlugin } from '@amplitude/analytics-types';
+import { BrowserClient, BrowserConfig, EnrichmentPlugin } from '@amplitude/analytics-types';
+import { record } from 'rrweb';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Options {}
-export interface SessionReplayPlugin {
- (client: BrowserClient, options?: Options): EnrichmentPlugin;
- (options?: Options): EnrichmentPlugin;
-}
-
-export type SessionReplayPluginParameters = [BrowserClient, Options?] | [Options?];
-
export type Events = string[];
export interface SessionReplayContext {
@@ -26,3 +20,49 @@ export interface IDBStore {
sequenceId: number;
};
}
+export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
+ config: BrowserConfig;
+ storageKey: string;
+ retryTimeout: number;
+ events: Events;
+ currentSequenceId: number;
+ queue: SessionReplayContext[];
+ stopRecordingEvents: ReturnType | null;
+ maxPersistedEventsSize: number;
+ emptyStoreAndReset: () => Promise;
+ recordEvents: () => void;
+ sendEventsList: ({
+ events,
+ sequenceId,
+ sessionId,
+ }: {
+ events: string[];
+ sequenceId: number;
+ sessionId: number;
+ }) => void;
+ addToQueue: (...list: SessionReplayContext[]) => void;
+ schedule: (timeout: number) => void;
+ flush: (useRetry?: boolean) => Promise;
+ send: (context: SessionReplayContext, useRetry?: boolean) => Promise;
+ completeRequest({
+ context,
+ err,
+ success,
+ removeEvents,
+ }: {
+ context: SessionReplayContext;
+ err?: string | undefined;
+ success?: string | undefined;
+ removeEvents?: boolean | undefined;
+ }): void;
+ getAllSessionEventsFromStore: () => Promise;
+ storeEventsForSession: (events: Events, sequenceId: number) => Promise;
+ removeSessionEventsStore: (sessionId: number) => Promise;
+}
+
+export interface SessionReplayPlugin {
+ (client: BrowserClient, options?: Options): SessionReplayEnrichmentPlugin;
+ (options?: Options): SessionReplayEnrichmentPlugin;
+}
+
+export type SessionReplayPluginParameters = [BrowserClient, Options?] | [Options?];
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 195f980dc..0c1f3d4d4 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -5,7 +5,7 @@ import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
import { shouldSplitEventsList } from '../src/helpers';
import { SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from '../src/messages';
-import { SessionReplayPlugin } from '../src/session-replay';
+import { sessionReplayPlugin } from '../src/session-replay';
jest.mock('idb-keyval');
type MockedIDBKeyVal = jest.Mocked;
@@ -96,7 +96,7 @@ describe('SessionReplayPlugin', () => {
});
describe('setup', () => {
test('should setup plugin', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
await sessionReplay.setup(mockConfig);
expect(sessionReplay.config.transportProvider).toBeDefined();
expect(sessionReplay.config.serverUrl).toBe('url');
@@ -107,7 +107,7 @@ describe('SessionReplayPlugin', () => {
});
test('should read events from storage and send them, then reset storage for session', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
sessionId: 456,
@@ -156,7 +156,7 @@ describe('SessionReplayPlugin', () => {
});
});
test('should handle no stored events', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
};
@@ -176,7 +176,7 @@ describe('SessionReplayPlugin', () => {
test('should record events', async () => {
const mockGetResolution = Promise.resolve({});
get.mockReturnValueOnce(mockGetResolution);
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
await sessionReplay.setup(mockConfig);
await mockGetResolution;
jest.runAllTimers();
@@ -185,8 +185,8 @@ describe('SessionReplayPlugin', () => {
});
describe('execute', () => {
- test('should add event property for session_replay_enabled', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ test('should add event property for [Amplitude] Session Recorded', async () => {
+ const sessionReplay = sessionReplayPlugin();
await sessionReplay.setup(mockConfig);
const event = {
event_type: 'event_type',
@@ -197,15 +197,15 @@ describe('SessionReplayPlugin', () => {
};
const enrichedEvent = await sessionReplay.execute(event);
- expect(enrichedEvent.event_properties).toEqual({
+ expect(enrichedEvent?.event_properties).toEqual({
property_a: true,
property_b: 123,
- session_replay_enabled: true,
+ '[Amplitude] Session Recorded': true,
});
});
test('should restart recording events when session_start fires', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const mockStopRecordingEvents = jest.fn();
record.mockReturnValue(mockStopRecordingEvents);
const mockGetResolution = Promise.resolve({});
@@ -222,7 +222,7 @@ describe('SessionReplayPlugin', () => {
});
test('should send the current events list when session_end fires', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
@@ -248,7 +248,7 @@ describe('SessionReplayPlugin', () => {
describe('recordEvents', () => {
test('should store events in class and in IDB', () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.recordEvents();
expect(sessionReplay.events).toEqual([]);
@@ -265,7 +265,7 @@ describe('SessionReplayPlugin', () => {
});
test('should split the events list and send', () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.maxPersistedEventsSize = 20;
const events = ['#'.repeat(20)];
@@ -297,7 +297,7 @@ describe('SessionReplayPlugin', () => {
describe('addToQueue', () => {
test('should add to queue and schedule a flush', () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const schedule = jest.spyOn(sessionReplay, 'schedule').mockReturnValueOnce(undefined);
const context = {
@@ -313,7 +313,7 @@ describe('SessionReplayPlugin', () => {
});
test('should not add to queue if attemps are greater than allowed retries', () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = {
...mockConfig,
flushMaxRetries: 1,
@@ -343,7 +343,7 @@ describe('SessionReplayPlugin', () => {
describe('schedule', () => {
test('should schedule a flush', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(sessionReplay as any).scheduled = null;
sessionReplay.queue = [
@@ -369,7 +369,7 @@ describe('SessionReplayPlugin', () => {
});
test('should not schedule if one is already in progress', () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(sessionReplay as any).scheduled = setTimeout(jest.fn, 0);
const flush = jest.spyOn(sessionReplay, 'flush').mockReturnValueOnce(Promise.resolve(undefined));
@@ -380,7 +380,7 @@ describe('SessionReplayPlugin', () => {
describe('flush', () => {
test('should call send', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.queue = [
{
@@ -399,7 +399,7 @@ describe('SessionReplayPlugin', () => {
});
test('should send later', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.queue = [
{
@@ -428,7 +428,7 @@ describe('SessionReplayPlugin', () => {
describe('send', () => {
test('should make a request correctly', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
events: [mockEventString],
@@ -452,7 +452,7 @@ describe('SessionReplayPlugin', () => {
});
});
test('should remove session events from IDB store upon success', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
events: [mockEventString],
@@ -483,7 +483,7 @@ describe('SessionReplayPlugin', () => {
});
});
test('should not remove session events from IDB store upon failure', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
events: [mockEventString],
@@ -501,7 +501,7 @@ describe('SessionReplayPlugin', () => {
});
test('should retry if retry param is true', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
events: [mockEventString],
@@ -534,7 +534,7 @@ describe('SessionReplayPlugin', () => {
});
test('should not retry if retry param is false', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
events: [mockEventString],
@@ -557,7 +557,7 @@ describe('SessionReplayPlugin', () => {
describe('module level integration', () => {
test('should handle unexpected error', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
loggerProvider: mockLoggerProvider,
@@ -586,7 +586,7 @@ describe('SessionReplayPlugin', () => {
status: 200,
});
});
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
sessionReplay.retryTimeout = 10;
const config = {
...mockConfig,
@@ -619,7 +619,7 @@ describe('SessionReplayPlugin', () => {
status: 200,
});
});
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
flushMaxRetries: 2,
@@ -645,7 +645,7 @@ describe('SessionReplayPlugin', () => {
status: 200,
});
});
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
flushMaxRetries: 2,
@@ -671,7 +671,7 @@ describe('SessionReplayPlugin', () => {
status: 200,
});
});
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
flushMaxRetries: 2,
@@ -689,7 +689,7 @@ describe('SessionReplayPlugin', () => {
(fetch as jest.Mock).mockImplementationOnce(() => {
return Promise.resolve(null);
});
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
flushMaxRetries: 2,
@@ -711,7 +711,7 @@ describe('SessionReplayPlugin', () => {
describe('idb error handling', () => {
test('getAllSessionEventsFromStore should catch errors', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
loggerProvider: mockLoggerProvider,
@@ -726,7 +726,7 @@ describe('SessionReplayPlugin', () => {
);
});
test('storeEventsForSession should catch errors', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
loggerProvider: mockLoggerProvider,
@@ -741,7 +741,7 @@ describe('SessionReplayPlugin', () => {
);
});
test('removeSessionEventsStore should catch errors', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
loggerProvider: mockLoggerProvider,
@@ -756,7 +756,7 @@ describe('SessionReplayPlugin', () => {
);
});
test('removeSessionEventsStore should handle an undefined store', async () => {
- const sessionReplay = new SessionReplayPlugin();
+ const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
loggerProvider: mockLoggerProvider,
From 4ace87ddc1fbdfaa0d2a33db939ef04e93ef0f70 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 27 Jun 2023 15:33:08 -0400
Subject: [PATCH 042/214] feat(plugins): remove mistake addition
---
packages/analytics-core/src/core-client.ts | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/packages/analytics-core/src/core-client.ts b/packages/analytics-core/src/core-client.ts
index 9ef6a1ccc..6d2c84f93 100644
--- a/packages/analytics-core/src/core-client.ts
+++ b/packages/analytics-core/src/core-client.ts
@@ -1,24 +1,24 @@
import {
- BaseEvent,
- Config,
CoreClient,
+ Config,
Event,
+ BaseEvent,
EventOptions,
Identify,
Plugin,
- Result,
Revenue,
+ Result,
} from '@amplitude/analytics-types';
-import { CLIENT_NOT_INITIALIZED, OPT_OUT_MESSAGE } from './messages';
-import { Timeline } from './timeline';
import {
- createGroupEvent,
createGroupIdentifyEvent,
createIdentifyEvent,
- createRevenueEvent,
createTrackEvent,
+ createRevenueEvent,
+ createGroupEvent,
} from './utils/event-builder';
+import { Timeline } from './timeline';
import { buildResult } from './utils/result-builder';
+import { CLIENT_NOT_INITIALIZED, OPT_OUT_MESSAGE } from './messages';
import { returnWrapper } from './utils/return-wrapper';
export class AmplitudeCore implements CoreClient {
@@ -53,7 +53,6 @@ export class AmplitudeCore implements CoreClient {
}
track(eventInput: BaseEvent | string, eventProperties?: Record, eventOptions?: EventOptions) {
- console.log('track', eventInput);
const event = createTrackEvent(eventInput, eventProperties, eventOptions);
return returnWrapper(this.dispatch(event));
}
@@ -81,7 +80,6 @@ export class AmplitudeCore implements CoreClient {
}
add(plugin: Plugin) {
- console.log('adding plugin');
if (!this.config) {
this.q.push(this.add.bind(this, plugin));
return returnWrapper();
From b782eb36cf8c34154139411880967f68609da90f Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 28 Jun 2023 15:11:46 +0000
Subject: [PATCH 043/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.1.0
---
packages/plugin-session-replay-browser/CHANGELOG.md | 13 +++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
2 files changed, 14 insertions(+), 1 deletion(-)
create mode 100644 packages/plugin-session-replay-browser/CHANGELOG.md
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
new file mode 100644
index 000000000..758270f9e
--- /dev/null
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# 0.1.0 (2023-06-28)
+
+### Features
+
+- **plugins:** change file names
+ ([2c76247](https://github.com/amplitude/Amplitude-TypeScript/commit/2c76247f8b994a52650db26dca3603319d13857e))
+- **plugins:** expose function as external interface
+ ([61185ac](https://github.com/amplitude/Amplitude-TypeScript/commit/61185acc3504b96be9ea8144fc62967937235b37))
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index eff7229dd..d2635ef02 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.0.0",
+ "version": "0.1.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 902aa39c8dcccfdc48f44312fe11a811a39cba10 Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Thu, 29 Jun 2023 17:01:23 -0700
Subject: [PATCH 044/214] fix: missing core dependency for web attribution
(#462)
---
packages/plugin-web-attribution-browser/package.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index 69a77c34d..c2d1c0d51 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -38,6 +38,7 @@
},
"dependencies": {
"@amplitude/analytics-client-common": "^1.1.0",
+ "@amplitude/analytics-core": "^1.2.0",
"@amplitude/analytics-types": "^1.3.0",
"tslib": "^2.4.1"
},
From 1394ddb3775c7e5b1a3da87e6a4c973a586c6382 Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Thu, 29 Jun 2023 17:48:40 -0700
Subject: [PATCH 045/214] fix: allow plugins to teardown to remove listeners
(#463)
---
.../src/plugins/file-download-tracking.ts | 38 +++++++++-
.../src/plugins/form-interaction-tracking.ts | 62 +++++++++++----
.../plugins/file-download-tracking.test.ts | 38 +++++++++-
.../plugins/form-interaction-tracking.test.ts | 76 +++++++++++++++----
packages/analytics-core/src/timeline.ts | 13 ++--
packages/analytics-core/test/timeline.test.ts | 48 ++++++++++++
packages/analytics-types/src/plugin.ts | 3 +
.../src/page-view-tracking.ts | 23 +++++-
.../test/page-view-tracking.test.ts | 28 +++++++
9 files changed, 286 insertions(+), 43 deletions(-)
diff --git a/packages/analytics-browser/src/plugins/file-download-tracking.ts b/packages/analytics-browser/src/plugins/file-download-tracking.ts
index 7e6087a08..faf9ad722 100644
--- a/packages/analytics-browser/src/plugins/file-download-tracking.ts
+++ b/packages/analytics-browser/src/plugins/file-download-tracking.ts
@@ -2,7 +2,31 @@ import { BrowserClient, PluginType, Event, EnrichmentPlugin } from '@amplitude/a
import { DEFAULT_FILE_DOWNLOAD_EVENT, FILE_EXTENSION, FILE_NAME, LINK_ID, LINK_TEXT, LINK_URL } from '../constants';
import { BrowserConfig } from '../config';
+interface EventListener {
+ element: Element;
+ type: 'click';
+ handler: () => void;
+}
+
export const fileDownloadTracking = (): EnrichmentPlugin => {
+ let observer: MutationObserver | undefined;
+ let eventListeners: EventListener[] = [];
+ const addEventListener = (element: Element, type: 'click', handler: () => void) => {
+ element.addEventListener(type, handler);
+ eventListeners.push({
+ element,
+ type,
+ handler,
+ });
+ };
+ const removeClickListeners = () => {
+ eventListeners.forEach(({ element, type, handler }) => {
+ /* istanbul ignore next */
+ element?.removeEventListener(type, handler);
+ });
+ eventListeners = [];
+ };
+
const name = '@amplitude/plugin-file-download-tracking-browser';
const type = PluginType.ENRICHMENT;
const setup = async (config: BrowserConfig, amplitude?: BrowserClient) => {
@@ -15,6 +39,11 @@ export const fileDownloadTracking = (): EnrichmentPlugin => {
return;
}
+ /* istanbul ignore if */
+ if (typeof document === 'undefined') {
+ return;
+ }
+
const addFileDownloadListener = (a: HTMLAnchorElement) => {
let url: URL;
try {
@@ -28,7 +57,7 @@ export const fileDownloadTracking = (): EnrichmentPlugin => {
const fileExtension = result?.[1];
if (fileExtension) {
- a.addEventListener('click', () => {
+ addEventListener(a, 'click', () => {
if (fileExtension) {
amplitude.track(DEFAULT_FILE_DOWNLOAD_EVENT, {
[FILE_EXTENSION]: fileExtension,
@@ -52,7 +81,7 @@ export const fileDownloadTracking = (): EnrichmentPlugin => {
// Adds listener to anchor tags added after initial load
/* istanbul ignore else */
if (typeof MutationObserver !== 'undefined') {
- const observer = new MutationObserver((mutations) => {
+ observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeName === 'A') {
@@ -72,11 +101,16 @@ export const fileDownloadTracking = (): EnrichmentPlugin => {
}
};
const execute = async (event: Event) => event;
+ const teardown = async () => {
+ observer?.disconnect();
+ removeClickListeners();
+ };
return {
name,
type,
setup,
execute,
+ teardown,
};
};
diff --git a/packages/analytics-browser/src/plugins/form-interaction-tracking.ts b/packages/analytics-browser/src/plugins/form-interaction-tracking.ts
index 380f7061f..359a5bf3c 100644
--- a/packages/analytics-browser/src/plugins/form-interaction-tracking.ts
+++ b/packages/analytics-browser/src/plugins/form-interaction-tracking.ts
@@ -8,7 +8,31 @@ import {
} from '../constants';
import { BrowserConfig } from '../config';
+interface EventListener {
+ element: Element;
+ type: 'change' | 'submit';
+ handler: () => void;
+}
+
export const formInteractionTracking = (): EnrichmentPlugin => {
+ let observer: MutationObserver | undefined;
+ let eventListeners: EventListener[] = [];
+ const addEventListener = (element: Element, type: 'change' | 'submit', handler: () => void) => {
+ element.addEventListener(type, handler);
+ eventListeners.push({
+ element,
+ type,
+ handler,
+ });
+ };
+ const removeClickListeners = () => {
+ eventListeners.forEach(({ element, type, handler }) => {
+ /* istanbul ignore next */
+ element?.removeEventListener(type, handler);
+ });
+ eventListeners = [];
+ };
+
const name = '@amplitude/plugin-form-interaction-tracking-browser';
const type = PluginType.ENRICHMENT;
const setup = async (config: BrowserConfig, amplitude?: BrowserClient) => {
@@ -21,25 +45,26 @@ export const formInteractionTracking = (): EnrichmentPlugin => {
return;
}
+ /* istanbul ignore if */
+ if (typeof document === 'undefined') {
+ return;
+ }
+
const addFormInteractionListener = (form: HTMLFormElement) => {
let hasFormChanged = false;
- form.addEventListener(
- 'change',
- () => {
- if (!hasFormChanged) {
- amplitude.track(DEFAULT_FORM_START_EVENT, {
- [FORM_ID]: form.id,
- [FORM_NAME]: form.name,
- [FORM_DESTINATION]: form.action,
- });
- }
- hasFormChanged = true;
- },
- {},
- );
+ addEventListener(form, 'change', () => {
+ if (!hasFormChanged) {
+ amplitude.track(DEFAULT_FORM_START_EVENT, {
+ [FORM_ID]: form.id,
+ [FORM_NAME]: form.name,
+ [FORM_DESTINATION]: form.action,
+ });
+ }
+ hasFormChanged = true;
+ });
- form.addEventListener('submit', () => {
+ addEventListener(form, 'submit', () => {
if (!hasFormChanged) {
amplitude.track(DEFAULT_FORM_START_EVENT, {
[FORM_ID]: form.id,
@@ -64,7 +89,7 @@ export const formInteractionTracking = (): EnrichmentPlugin => {
// Adds listener to anchor tags added after initial load
/* istanbul ignore else */
if (typeof MutationObserver !== 'undefined') {
- const observer = new MutationObserver((mutations) => {
+ observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeName === 'FORM') {
@@ -84,11 +109,16 @@ export const formInteractionTracking = (): EnrichmentPlugin => {
}
};
const execute = async (event: Event) => event;
+ const teardown = async () => {
+ observer?.disconnect();
+ removeClickListeners();
+ };
return {
name,
type,
setup,
execute,
+ teardown,
};
};
diff --git a/packages/analytics-browser/test/plugins/file-download-tracking.test.ts b/packages/analytics-browser/test/plugins/file-download-tracking.test.ts
index 513e720ff..27bc8ee23 100644
--- a/packages/analytics-browser/test/plugins/file-download-tracking.test.ts
+++ b/packages/analytics-browser/test/plugins/file-download-tracking.test.ts
@@ -29,7 +29,7 @@ describe('fileDownloadTracking', () => {
const plugin = fileDownloadTracking();
await plugin.setup(config, amplitude);
- // trigger change event
+ // trigger click event
document.getElementById('my-link-id')?.dispatchEvent(new Event('click'));
// assert file download event was tracked
@@ -41,6 +41,15 @@ describe('fileDownloadTracking', () => {
[LINK_TEXT]: 'my-link-text',
[LINK_URL]: 'https://analytics.amplitude.com/files/my-file.pdf',
});
+
+ // stop observer and listeners
+ await plugin.teardown?.();
+
+ // trigger click event
+ document.getElementById('my-link-id')?.dispatchEvent(new Event('click'));
+
+ // assert no additional event was tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(1);
});
test('should track file_download event for a dynamically added achor tag', async () => {
@@ -71,6 +80,25 @@ describe('fileDownloadTracking', () => {
[LINK_TEXT]: 'my-link-2-text',
[LINK_URL]: 'https://analytics.amplitude.com/files/my-file-2.pdf',
});
+
+ // stop observer and listeners
+ await plugin.teardown?.();
+
+ // add anchor element dynamically
+ const link3 = document.createElement('a');
+ link3.setAttribute('id', 'my-link-3-id');
+ link3.setAttribute('class', 'my-link-3-class');
+ link3.setAttribute('href', 'https://analytics.amplitude.com/files/my-file-3.pdf');
+ link3.text = 'my-link-3-text';
+ document.body.appendChild(link3);
+
+ // allow mutation observer to execute and event listener to be attached
+ await new Promise((r) => r(undefined)); // basically, await next clock tick
+ // trigger change event
+ link.dispatchEvent(new Event('click'));
+
+ // assert no additional file download event was tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(1);
});
test('should track file_download event for a dynamically added nested achor tag', async () => {
@@ -130,4 +158,12 @@ describe('fileDownloadTracking', () => {
const result = await plugin.execute(input);
expect(result).toEqual(input);
});
+
+ // eslint-disable-next-line jest/expect-expect
+ test('should teardown plugin', async () => {
+ const plugin = fileDownloadTracking();
+ await plugin.teardown?.();
+ // no explicit assertion
+ // test asserts that no error is thrown
+ });
});
diff --git a/packages/analytics-browser/test/plugins/form-interaction-tracking.test.ts b/packages/analytics-browser/test/plugins/form-interaction-tracking.test.ts
index 238b060bc..9a3f200ce 100644
--- a/packages/analytics-browser/test/plugins/form-interaction-tracking.test.ts
+++ b/packages/analytics-browser/test/plugins/form-interaction-tracking.test.ts
@@ -98,6 +98,35 @@ describe('formInteractionTracking', () => {
// assert second event was not tracked
expect(amplitude.track).toHaveBeenCalledTimes(1);
+
+ // stop observer and listeners
+ await plugin.teardown?.();
+
+ // add form element dynamically
+ const form3 = document.createElement('form');
+ form3.setAttribute('id', 'my-form-3-id');
+ form3.setAttribute('name', 'my-form-3-name');
+ form3.setAttribute('action', '/submit');
+
+ const text3 = document.createElement('input');
+ text3.setAttribute('type', 'text');
+ text3.setAttribute('id', 'my-text-3-id');
+
+ const submit3 = document.createElement('input');
+ submit3.setAttribute('type', 'submit');
+ submit3.setAttribute('id', 'my-submit-3-id');
+
+ form3.appendChild(text3);
+ form3.appendChild(submit3);
+ document.body.appendChild(form3);
+
+ // allow mutation observer to execute and event listener to be attached
+ await new Promise((r) => r(undefined)); // basically, await next clock tick
+ // trigger change event
+ form3.dispatchEvent(new Event('change'));
+
+ // assert no additional event was tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(1);
});
test('should track form_start event for a dynamically added nested form tag', async () => {
@@ -154,16 +183,22 @@ describe('formInteractionTracking', () => {
const plugin = formInteractionTracking();
await plugin.setup(config, amplitude);
- // trigger change event
- document.getElementById('my-form-id')?.dispatchEvent(new Event('submit'));
+ // trigger change event again
+ document.getElementById('my-form-id')?.dispatchEvent(new Event('change'));
- // assert both events were tracked
- expect(amplitude.track).toHaveBeenCalledTimes(2);
+ // assert first event was tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(1);
expect(amplitude.track).toHaveBeenNthCalledWith(1, '[Amplitude] Form Started', {
[FORM_ID]: 'my-form-id',
[FORM_NAME]: 'my-form-name',
[FORM_DESTINATION]: 'http://localhost/submit',
});
+
+ // trigger submit event
+ document.getElementById('my-form-id')?.dispatchEvent(new Event('submit'));
+
+ // assert second event was tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(2);
expect(amplitude.track).toHaveBeenNthCalledWith(2, '[Amplitude] Form Submitted', {
[FORM_ID]: 'my-form-id',
[FORM_NAME]: 'my-form-name',
@@ -175,29 +210,32 @@ describe('formInteractionTracking', () => {
// setup
const config = createConfigurationMock();
const plugin = formInteractionTracking();
- await plugin.setup(config, amplitude);
+ await plugin.setup?.(config, amplitude);
- // trigger change event again
- document.getElementById('my-form-id')?.dispatchEvent(new Event('change'));
+ // trigger change event
+ document.getElementById('my-form-id')?.dispatchEvent(new Event('submit'));
- // assert first event was tracked
- expect(amplitude.track).toHaveBeenCalledTimes(1);
+ // assert both events were tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(2);
expect(amplitude.track).toHaveBeenNthCalledWith(1, '[Amplitude] Form Started', {
[FORM_ID]: 'my-form-id',
[FORM_NAME]: 'my-form-name',
[FORM_DESTINATION]: 'http://localhost/submit',
});
-
- // trigger submit event
- document.getElementById('my-form-id')?.dispatchEvent(new Event('submit'));
-
- // assert second event was tracked
- expect(amplitude.track).toHaveBeenCalledTimes(2);
expect(amplitude.track).toHaveBeenNthCalledWith(2, '[Amplitude] Form Submitted', {
[FORM_ID]: 'my-form-id',
[FORM_NAME]: 'my-form-name',
[FORM_DESTINATION]: 'http://localhost/submit',
});
+
+ // stop observer and listeners
+ await plugin.teardown?.();
+
+ // trigger change event
+ document.getElementById('my-form-id')?.dispatchEvent(new Event('submit'));
+
+ // assert no additional event was tracked
+ expect(amplitude.track).toHaveBeenCalledTimes(2);
});
test('should not enrich events', async () => {
@@ -208,4 +246,12 @@ describe('formInteractionTracking', () => {
const result = await plugin.execute(input);
expect(result).toEqual(input);
});
+
+ // eslint-disable-next-line jest/expect-expect
+ test('should teardown plugin', async () => {
+ const plugin = formInteractionTracking();
+ await plugin.teardown?.();
+ // no explicit assertion
+ // test asserts that no error is thrown
+ });
});
diff --git a/packages/analytics-core/src/timeline.ts b/packages/analytics-core/src/timeline.ts
index 16f37346d..daea7bd13 100644
--- a/packages/analytics-core/src/timeline.ts
+++ b/packages/analytics-core/src/timeline.ts
@@ -27,16 +27,17 @@ export class Timeline {
this.plugins.push(plugin);
}
- deregister(pluginName: string) {
- this.plugins.splice(
- this.plugins.findIndex((plugin) => plugin.name === pluginName),
- 1,
- );
- return Promise.resolve();
+ async deregister(pluginName: string) {
+ const index = this.plugins.findIndex((plugin) => plugin.name === pluginName);
+ const plugin = this.plugins[index];
+ this.plugins.splice(index, 1);
+ await plugin.teardown?.();
}
reset(client: CoreClient) {
this.applying = false;
+ const plugins = this.plugins;
+ plugins.map((plugin) => plugin.teardown?.());
this.plugins = [];
this.client = client;
}
diff --git a/packages/analytics-core/test/timeline.test.ts b/packages/analytics-core/test/timeline.test.ts
index 6f53d1915..397205cb3 100644
--- a/packages/analytics-core/test/timeline.test.ts
+++ b/packages/analytics-core/test/timeline.test.ts
@@ -3,6 +3,7 @@ import { Event, Plugin, PluginType } from '@amplitude/analytics-types';
import { useDefaultConfig, promiseState } from './helpers/default';
import { createTrackEvent } from '../src/utils/event-builder';
import { AmplitudeCore } from '../src/core-client';
+import { UUID } from '../src/utils/uuid';
describe('timeline', () => {
let timeline = new Timeline(new AmplitudeCore());
@@ -11,8 +12,54 @@ describe('timeline', () => {
timeline = new Timeline(new AmplitudeCore());
});
+ describe('reset', () => {
+ test('should reset timeline', () => {
+ const timeline = new Timeline(new AmplitudeCore());
+ timeline.plugins = [];
+ timeline.reset(new AmplitudeCore());
+ expect(timeline.applying).toEqual(false);
+ expect(timeline.plugins).toEqual([]);
+ });
+
+ test('should reset timeline without plugin.teardown', () => {
+ const setup = jest.fn();
+ const timeline = new Timeline(new AmplitudeCore());
+ timeline.plugins = [
+ {
+ name: UUID(),
+ type: PluginType.BEFORE,
+ setup,
+ execute: async (e) => e,
+ },
+ ];
+ timeline.reset(new AmplitudeCore());
+ expect(setup).toHaveBeenCalledTimes(0);
+ expect(timeline.applying).toEqual(false);
+ expect(timeline.plugins).toEqual([]);
+ });
+
+ test('should reset timeline with plugin.teardown', () => {
+ const teardown = jest.fn();
+ const timeline = new Timeline(new AmplitudeCore());
+ timeline.plugins = [
+ {
+ name: UUID(),
+ type: PluginType.BEFORE,
+ setup: async () => undefined,
+ execute: async (e) => e,
+ teardown,
+ },
+ ];
+ timeline.reset(new AmplitudeCore());
+ expect(teardown).toHaveBeenCalledTimes(1);
+ expect(timeline.applying).toEqual(false);
+ expect(timeline.plugins).toEqual([]);
+ });
+ });
+
test('should update event using before/enrichment plugin', async () => {
const beforeSetup = jest.fn().mockReturnValue(Promise.resolve());
+ const beforeTeardown = jest.fn().mockReturnValue(Promise.resolve());
const beforeExecute = jest.fn().mockImplementation((event: Event) =>
Promise.resolve({
...event,
@@ -23,6 +70,7 @@ describe('timeline', () => {
name: 'plugin:before',
type: PluginType.BEFORE,
setup: beforeSetup,
+ teardown: beforeTeardown,
execute: beforeExecute,
};
const enrichmentSetup = jest.fn().mockReturnValue(Promise.resolve());
diff --git a/packages/analytics-types/src/plugin.ts b/packages/analytics-types/src/plugin.ts
index b40801223..bd4d8e04f 100644
--- a/packages/analytics-types/src/plugin.ts
+++ b/packages/analytics-types/src/plugin.ts
@@ -14,6 +14,7 @@ export interface BeforePlugin {
type: PluginType.BEFORE;
setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
+ teardown?(): Promise;
}
export interface EnrichmentPlugin {
@@ -21,6 +22,7 @@ export interface EnrichmentPlugin {
type: PluginType.ENRICHMENT;
setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
+ teardown?(): Promise;
}
export interface DestinationPlugin {
@@ -29,6 +31,7 @@ export interface DestinationPlugin {
setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
flush?(): Promise;
+ teardown?(): Promise;
}
export type Plugin = BeforePlugin | EnrichmentPlugin | DestinationPlugin;
diff --git a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts
index a7c639b4a..d9216aad5 100644
--- a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts
+++ b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts
@@ -23,6 +23,7 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (
let options: Options = {};
const globalScope = getGlobalScope();
let loggerProvider: Logger | undefined = undefined;
+ let pushState: undefined | ((data: any, unused: string, url?: string | URL | null) => void);
const [clientOrOptions, configOrUndefined] = args;
if (clientOrOptions && 'init' in clientOrOptions) {
@@ -65,6 +66,11 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (
previousURL = newURL;
};
+ /* istanbul ignore next */
+ const trackHistoryPageViewWrapper = () => {
+ void trackHistoryPageView();
+ };
+
const plugin = {
name: 'page-view-tracking',
type: PluginType.ENRICHMENT as const,
@@ -93,9 +99,11 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (
if (options.trackHistoryChanges && globalScope) {
/* istanbul ignore next */
- globalScope.addEventListener('popstate', () => {
- void trackHistoryPageView();
- });
+ globalScope.addEventListener('popstate', trackHistoryPageViewWrapper);
+
+ // Save reference to original push state, to be used in teardown
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ pushState = globalScope.history.pushState;
/* istanbul ignore next */
// There is no global browser listener for changes to history, so we have
@@ -130,6 +138,15 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (
}
return event;
},
+
+ teardown: async () => {
+ if (globalScope) {
+ globalScope.removeEventListener('popstate', trackHistoryPageViewWrapper);
+ if (pushState) {
+ globalScope.history.pushState = pushState;
+ }
+ }
+ },
};
// Required for unit tests
diff --git a/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts b/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts
index 187f6a057..479f45be4 100644
--- a/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts
+++ b/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts
@@ -488,6 +488,34 @@ describe('pageViewTrackingPlugin', () => {
});
});
+ describe('teardown', () => {
+ test('should call remove listeners', async () => {
+ const amplitude = createInstance();
+ const removeEventListener = jest.spyOn(window, 'removeEventListener');
+ const loggerProvider: Partial = {
+ log: jest.fn(),
+ };
+ const config: Partial = {
+ loggerProvider: loggerProvider as Logger,
+ };
+ const plugin = pageViewTrackingPlugin({
+ trackHistoryChanges: 'all',
+ });
+ await plugin.setup(config as Config, amplitude);
+ await plugin.teardown?.();
+ expect(removeEventListener).toHaveBeenCalledTimes(1);
+ });
+
+ test('should call remove listeners without proxy', async () => {
+ const removeEventListener = jest.spyOn(window, 'removeEventListener');
+ const plugin = pageViewTrackingPlugin({
+ trackHistoryChanges: 'all',
+ });
+ await plugin.teardown?.();
+ expect(removeEventListener).toHaveBeenCalledTimes(1);
+ });
+ });
+
test('shouldTrackHistoryPageView pathOnly option', () => {
const url1 = 'https://www.example.com/path/to/page';
const url2 = 'https://www.example.com/path/to/page?query=1';
From 603e3eff81a3d03082544541a673df955cf30118 Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Wed, 5 Jul 2023 09:06:16 -0700
Subject: [PATCH 046/214] fix: allow literal values for plugin type (#468)
---
packages/analytics-types/src/plugin.ts | 6 +++---
.../plugin-session-replay-browser/src/session-replay.ts | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/analytics-types/src/plugin.ts b/packages/analytics-types/src/plugin.ts
index bd4d8e04f..80d83386e 100644
--- a/packages/analytics-types/src/plugin.ts
+++ b/packages/analytics-types/src/plugin.ts
@@ -11,7 +11,7 @@ export enum PluginType {
export interface BeforePlugin {
name: string;
- type: PluginType.BEFORE;
+ type: PluginType.BEFORE | 'before';
setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
teardown?(): Promise;
@@ -19,7 +19,7 @@ export interface BeforePlugin {
export interface EnrichmentPlugin {
name: string;
- type: PluginType.ENRICHMENT;
+ type: PluginType.ENRICHMENT | 'enrichment';
setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
teardown?(): Promise;
@@ -27,7 +27,7 @@ export interface EnrichmentPlugin {
export interface DestinationPlugin {
name: string;
- type: PluginType.DESTINATION;
+ type: PluginType.DESTINATION | 'destination';
setup(config: U, client?: T): Promise;
execute(context: Event): Promise;
flush?(): Promise;
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index d4d1926c7..2808f23c9 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -1,5 +1,5 @@
import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
-import { BrowserConfig, Event, PluginType, Status } from '@amplitude/analytics-types';
+import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
@@ -20,7 +20,7 @@ const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BY
class SessionReplay implements SessionReplayEnrichmentPlugin {
name = '@amplitude/plugin-session-replay-browser';
- type = PluginType.ENRICHMENT as const;
+ type = 'enrichment' as const;
// this.config is defined in setup() which will always be called first
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
From 712158b1936d302a88f5d651dd13d4b1ff76c70b Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Mon, 3 Jul 2023 14:11:09 -0700
Subject: [PATCH 047/214] fix: handle concurrent cookie testing (#465)
---
packages/analytics-client-common/src/storage/cookie.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/packages/analytics-client-common/src/storage/cookie.ts b/packages/analytics-client-common/src/storage/cookie.ts
index 6d0ce8c26..6d48cb8de 100644
--- a/packages/analytics-client-common/src/storage/cookie.ts
+++ b/packages/analytics-client-common/src/storage/cookie.ts
@@ -3,6 +3,7 @@ import { getGlobalScope } from '../global-scope';
export class CookieStorage implements Storage {
options: CookieStorageOptions;
+ private static testValue: undefined | string;
constructor(options?: CookieStorageOptions) {
this.options = { ...options };
@@ -14,13 +15,13 @@ export class CookieStorage implements Storage {
return false;
}
- const random = String(Date.now());
+ CookieStorage.testValue = String(Date.now());
const testStrorage = new CookieStorage(this.options);
const testKey = 'AMP_TEST';
try {
- await testStrorage.set(testKey, random);
+ await testStrorage.set(testKey, CookieStorage.testValue);
const value = await testStrorage.get(testKey);
- return value === random;
+ return value === CookieStorage.testValue;
} catch {
/* istanbul ignore next */
return false;
From 4ebf96b8c611cc0c766446b35e8a209bf5af580b Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Mon, 3 Jul 2023 14:11:19 -0700
Subject: [PATCH 048/214] fix: handle concurrent history push state calls
(#466)
---
.../src/page-view-tracking.ts | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts
index d9216aad5..26947d6b7 100644
--- a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts
+++ b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts
@@ -57,13 +57,19 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (
const trackHistoryPageView = async (): Promise => {
const newURL = location.href;
+ const shouldTrackPageView =
+ shouldTrackHistoryPageView(options.trackHistoryChanges, newURL, previousURL || '') && shouldTrackOnPageLoad();
+ // Note: Update `previousURL` in the same clock tick as `shouldTrackHistoryPageView()`
+ // This was previously done after `amplitude?.track(await createPageViewEvent());` and
+ // causes a concurrency issue where app triggers `pushState` twice with the same URL target
+ // but `previousURL` is only updated after the second `pushState` producing two page viewed events
+ previousURL = newURL;
- if (shouldTrackHistoryPageView(options.trackHistoryChanges, newURL, previousURL || '') && shouldTrackOnPageLoad()) {
+ if (shouldTrackPageView) {
/* istanbul ignore next */
loggerProvider?.log('Tracking page view event');
amplitude?.track(await createPageViewEvent());
}
- previousURL = newURL;
};
/* istanbul ignore next */
From bc426d86b020aa96c9dc3a824aaaecf22213b32f Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 5 Jul 2023 16:45:31 +0000
Subject: [PATCH 049/214] chore(release): publish
- @amplitude/analytics-browser-test@1.4.7
- @amplitude/analytics-browser@1.12.1
- @amplitude/analytics-client-common@1.1.1
- @amplitude/analytics-core@1.2.1
- @amplitude/analytics-node-test@1.0.5
- @amplitude/analytics-node@1.3.1
- @amplitude/analytics-react-native@1.3.1
- @amplitude/analytics-types@1.3.1
- @amplitude/marketing-analytics-browser@1.0.7
- @amplitude/plugin-page-view-tracking-browser@1.0.7
- @amplitude/plugin-session-replay-browser@0.1.1
- @amplitude/plugin-web-attribution-browser@1.0.7
---
packages/analytics-browser-test/CHANGELOG.md | 9 +++++++++
packages/analytics-browser-test/package.json | 4 ++--
packages/analytics-browser/CHANGELOG.md | 12 ++++++++++++
packages/analytics-browser/README.md | 2 +-
.../generated/amplitude-snippet.js | 4 ++--
packages/analytics-browser/package.json | 12 ++++++------
packages/analytics-browser/playground/amplitude.js | 2 +-
packages/analytics-browser/src/version.ts | 2 +-
packages/analytics-client-common/CHANGELOG.md | 12 ++++++++++++
packages/analytics-client-common/package.json | 6 +++---
packages/analytics-core/CHANGELOG.md | 12 ++++++++++++
packages/analytics-core/package.json | 4 ++--
packages/analytics-node-test/CHANGELOG.md | 9 +++++++++
packages/analytics-node-test/package.json | 4 ++--
packages/analytics-node/CHANGELOG.md | 9 +++++++++
packages/analytics-node/package.json | 6 +++---
packages/analytics-node/src/version.ts | 2 +-
packages/analytics-react-native/CHANGELOG.md | 9 +++++++++
packages/analytics-react-native/package.json | 8 ++++----
packages/analytics-react-native/src/version.ts | 2 +-
packages/analytics-types/CHANGELOG.md | 14 ++++++++++++++
packages/analytics-types/package.json | 2 +-
packages/marketing-analytics-browser/CHANGELOG.md | 9 +++++++++
packages/marketing-analytics-browser/README.md | 2 +-
.../generated/amplitude-gtm-snippet.js | 4 ++--
.../generated/amplitude-snippet.js | 4 ++--
packages/marketing-analytics-browser/package.json | 14 +++++++-------
.../marketing-analytics-browser/src/version.ts | 2 +-
.../plugin-page-view-tracking-browser/CHANGELOG.md | 14 ++++++++++++++
.../plugin-page-view-tracking-browser/package.json | 8 ++++----
.../plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
.../plugin-session-replay-browser/package.json | 8 ++++----
.../plugin-web-attribution-browser/CHANGELOG.md | 12 ++++++++++++
.../plugin-web-attribution-browser/package.json | 10 +++++-----
34 files changed, 189 insertions(+), 56 deletions(-)
diff --git a/packages/analytics-browser-test/CHANGELOG.md b/packages/analytics-browser-test/CHANGELOG.md
index 03ef9016c..d4c19e1f4 100644
--- a/packages/analytics-browser-test/CHANGELOG.md
+++ b/packages/analytics-browser-test/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.4.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.6...@amplitude/analytics-browser-test@1.4.7) (2023-07-05)
+
+**Note:** Version bump only for package @amplitude/analytics-browser-test
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.4.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser-test@1.4.5...@amplitude/analytics-browser-test@1.4.6) (2023-06-26)
**Note:** Version bump only for package @amplitude/analytics-browser-test
diff --git a/packages/analytics-browser-test/package.json b/packages/analytics-browser-test/package.json
index 96b473245..253db8a1a 100644
--- a/packages/analytics-browser-test/package.json
+++ b/packages/analytics-browser-test/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser-test",
- "version": "1.4.6",
+ "version": "1.4.7",
"private": true,
"description": "",
"author": "Amplitude Inc",
@@ -16,7 +16,7 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.12.0"
+ "@amplitude/analytics-browser": "^1.12.1"
},
"devDependencies": {
"nock": "^13.2.4"
diff --git a/packages/analytics-browser/CHANGELOG.md b/packages/analytics-browser/CHANGELOG.md
index a8b538e4b..021c0ed45 100644
--- a/packages/analytics-browser/CHANGELOG.md
+++ b/packages/analytics-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.12.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.12.0...@amplitude/analytics-browser@1.12.1) (2023-07-05)
+
+### Bug Fixes
+
+- allow plugins to teardown to remove listeners ([#463](https://github.com/amplitude/Amplitude-TypeScript/issues/463))
+ ([1394ddb](https://github.com/amplitude/Amplitude-TypeScript/commit/1394ddb3775c7e5b1a3da87e6a4c973a586c6382))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.12.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-browser@1.11.2...@amplitude/analytics-browser@1.12.0) (2023-06-26)
### Features
diff --git a/packages/analytics-browser/README.md b/packages/analytics-browser/README.md
index a43a16b2c..3d7c89b8d 100644
--- a/packages/analytics-browser/README.md
+++ b/packages/analytics-browser/README.md
@@ -40,7 +40,7 @@ Alternatively, the package is also distributed through a CDN. Copy and paste the
```html
diff --git a/packages/analytics-browser/generated/amplitude-snippet.js b/packages/analytics-browser/generated/amplitude-snippet.js
index 6c6c42737..bb02adfd8 100644
--- a/packages/analytics-browser/generated/amplitude-snippet.js
+++ b/packages/analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-s4Wx3hZn20lk10XSCWAIMwlpJRoowQ1Yt6E0gUEhrmytlmyODAskmfnOcu84UYAg';
+ as.integrity = 'sha384-tVVRWU7GrpjrC44WiDzQSQ9/fCEp3KlzT6HvGeU9Q4YPkOziz0qa/azi73J6jBr6';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.12.0-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/analytics-browser-1.12.1-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/analytics-browser/package.json b/packages/analytics-browser/package.json
index d73e6b7e7..105308739 100644
--- a/packages/analytics-browser/package.json
+++ b/packages/analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-browser",
- "version": "1.12.0",
+ "version": "1.12.1",
"description": "Official Amplitude SDK for Web",
"keywords": [
"analytics",
@@ -44,11 +44,11 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.1.0",
- "@amplitude/analytics-core": "^1.2.0",
- "@amplitude/analytics-types": "^1.3.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.6",
- "@amplitude/plugin-web-attribution-browser": "^1.0.6",
+ "@amplitude/analytics-client-common": "^1.1.1",
+ "@amplitude/analytics-core": "^1.2.1",
+ "@amplitude/analytics-types": "^1.3.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.7",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.7",
"@amplitude/ua-parser-js": "^0.7.31",
"tslib": "^2.4.1"
},
diff --git a/packages/analytics-browser/playground/amplitude.js b/packages/analytics-browser/playground/amplitude.js
index 17b09d826..32e9b690f 100644
--- a/packages/analytics-browser/playground/amplitude.js
+++ b/packages/analytics-browser/playground/amplitude.js
@@ -1 +1 @@
-!function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){return this.plugins.splice(this.plugins.findIndex((function(t){return t.name===e})),1),Promise.resolve()},e.prototype.reset=function(e){this.applying=!1,this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,k,E;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),k={error:y},[3,16];case 15:try{g&&!g.done&&(E=h.return)&&E.call(h)}finally{if(k)throw k.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),ne=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},re=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},oe=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},se=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=oe(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},ae=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,ue)},ce=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),le=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,k;if("object"!=typeof e)return null;var E=e.code||0,S=this.buildStatus(E);switch(S){case h.Success:return{status:S,statusCode:E,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:E,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:E,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:E,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(k=e.throttled_events)&&void 0!==k?k:[]}};case h.Timeout:default:return{status:S,statusCode:E}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),de=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},pe=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e(this.options),n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),fe=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(le),ve="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},he={exports:{}};ee=he,te=he.exports,function(e,t){var i="function",n="undefined",r="object",o="string",s="model",a="name",u="type",c="vendor",l="version",d="architecture",p="console",f="mobile",v="tablet",h="smarttv",g="wearable",b="embedded",y="Amazon",m="Apple",w="ASUS",_="BlackBerry",I="Browser",k="Chrome",E="Firefox",S="Google",T="Huawei",O="LG",x="Microsoft",P="Motorola",R="Opera",N="Samsung",U="Sony",q="Xiaomi",D="Zebra",A="Facebook",C=function(e){for(var t={},i=0;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),ee.exports&&(te=ee.exports=$),te.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:ve);var ge=he.exports,be=function(){function e(){this.ua=new he.exports.UAParser("undefined"!=typeof navigator?navigator.userAgent:null).getResult()}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:we(),platform:"Web",os:ye(this.ua),deviceModel:me(this.ua)}},e}(),ye=function(e){var t,i;return[null===(t=e.browser)||void 0===t?void 0:t.name,null===(i=e.browser)||void 0===i?void 0:i.major].filter((function(e){return null!=e})).join(" ")},me=function(e){var t;return null===(t=e.os)||void 0===t?void 0:t.name},we=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},_e=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),Ie=function(){return Ie=Object.assign||function(e){for(var t,i=1,n=arguments.length;i=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Oe(t.instanceName)).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ie).promise];case 11:return W.sent(),[4,this.add(new qe).promise];case 12:return W.sent(),[4,this.add(new xe).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add({name:"@amplitude/plugin-file-download-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i,n;return o(this,(function(r){return t?(i=function(e){var i;try{i=new URL(e.href,window.location.href)}catch(e){return}var r=n.exec(i.href),o=null==r?void 0:r[1];o&&e.addEventListener("click",(function(){var n;o&&t.track(st,((n={})[ct]=o,n[lt]=i.pathname,n[dt]=e.id,n[pt]=e.text,n[ft]=e.href,n))}))},n=/\.(pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi|mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma)$/,Array.from(document.getElementsByTagName("a")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"A"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("a")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("File download tracking requires a later version of @amplitude/analytics-browser. File download events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add({name:"@amplitude/plugin-form-interaction-tracking-browser",type:f.ENRICHMENT,setup:function(e,t){return r(void 0,void 0,void 0,(function(){var i;return o(this,(function(n){return t?(i=function(e){var i=!1;e.addEventListener("change",(function(){var n;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),i=!0}),{}),e.addEventListener("submit",(function(){var n,r;i||t.track(rt,((n={})[vt]=e.id,n[ht]=e.name,n[gt]=e.action,n)),t.track(ot,((r={})[vt]=e.id,r[ht]=e.name,r[gt]=e.action,r)),i=!1}))},Array.from(document.getElementsByTagName("form")).forEach(i),"undefined"!=typeof MutationObserver&&new MutationObserver((function(e){e.forEach((function(e){e.addedNodes.forEach((function(e){"FORM"===e.nodeName&&i(e),"querySelectorAll"in e&&"function"==typeof e.querySelectorAll&&Array.from(e.querySelectorAll("form")).map(i)}))}))})).observe(document.body,{subtree:!0,childList:!0}),[2]):(e.loggerProvider.warn("Form interaction tracking requires a later version of @amplitude/analytics-browser. Form interaction events are not tracked."),[2])}))}))},execute:function(e){return r(void 0,void 0,void 0,(function(){return o(this,(function(t){return[2,e]}))}))}}).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(k=this.config.attribution)||void 0===k?void 0:k.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:ae(e.init.bind(e),"init",re(e),se(e,["config"])),add:ae(e.add.bind(e),"add",re(e),se(e,["config.apiKey","timeline.plugins"])),remove:ae(e.remove.bind(e),"remove",re(e),se(e,["config.apiKey","timeline.plugins"])),track:ae(e.track.bind(e),"track",re(e),se(e,["config.apiKey","timeline.queue.length"])),logEvent:ae(e.logEvent.bind(e),"logEvent",re(e),se(e,["config.apiKey","timeline.queue.length"])),identify:ae(e.identify.bind(e),"identify",re(e),se(e,["config.apiKey","timeline.queue.length"])),groupIdentify:ae(e.groupIdentify.bind(e),"groupIdentify",re(e),se(e,["config.apiKey","timeline.queue.length"])),setGroup:ae(e.setGroup.bind(e),"setGroup",re(e),se(e,["config.apiKey","timeline.queue.length"])),revenue:ae(e.revenue.bind(e),"revenue",re(e),se(e,["config.apiKey","timeline.queue.length"])),flush:ae(e.flush.bind(e),"flush",re(e),se(e,["config.apiKey","timeline.queue.length"])),getUserId:ae(e.getUserId.bind(e),"getUserId",re(e),se(e,["config","config.userId"])),setUserId:ae(e.setUserId.bind(e),"setUserId",re(e),se(e,["config","config.userId"])),getDeviceId:ae(e.getDeviceId.bind(e),"getDeviceId",re(e),se(e,["config","config.deviceId"])),setDeviceId:ae(e.setDeviceId.bind(e),"setDeviceId",re(e),se(e,["config","config.deviceId"])),reset:ae(e.reset.bind(e),"reset",re(e),se(e,["config","config.userId","config.deviceId"])),getSessionId:ae(e.getSessionId.bind(e),"getSessionId",re(e),se(e,["config"])),setSessionId:ae(e.setSessionId.bind(e),"setSessionId",re(e),se(e,["config"])),extendSession:ae(e.extendSession.bind(e),"extendSession",re(e),se(e,["config"])),setOptOut:ae(e.setOptOut.bind(e),"setOptOut",re(e),se(e,["config"])),setTransport:ae(e.setTransport.bind(e),"setTransport",re(e),se(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,kt=wt.flush,Et=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,Ut=wt.reset,qt=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:kt,getDeviceId:Et,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:Ut,revenue:qt,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:q,createInstance:mt,runQueuedFunctions:Re,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Re(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r0&&r[r.length-1])||6!==a[0]&&2!==a[0])){s=0;continue}if(3===a[0]&&(!r||a[1]>r[0]&&a[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function a(e,t){var i="function"==typeof Symbol&&e[Symbol.iterator];if(!i)return e;var n,r,o=i.call(e),s=[];try{for(;(void 0===t||t-- >0)&&!(n=o.next()).done;)s.push(n.value)}catch(e){r={error:e}}finally{try{n&&!n.done&&(i=o.return)&&i.call(o)}finally{if(r)throw r.error}}return s}function u(e,t,i){if(i||2===arguments.length)for(var n,r=0,o=t.length;r1e3)return!1;for(var t in e){var i=e[t];if(!L(t,i))return!1}return!0},L=function(e,t){var i,n;if("string"!=typeof e)return!1;if(Array.isArray(t)){var r=!0;try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;if(Array.isArray(u))return!1;if("object"==typeof u)r=r&&j(u);else if(!["number","string"].includes(typeof u))return!1;if(!r)return!1}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}}else{if(null==t)return!1;if("object"==typeof t)return j(t);if(!["number","string","boolean"].includes(typeof t))return!1}return!0},M=function(){function e(){this._propertySet=new Set,this._properties={}}return e.prototype.getUserProperties=function(){return i({},this._properties)},e.prototype.set=function(e,t){return this._safeSet(c.SET,e,t),this},e.prototype.setOnce=function(e,t){return this._safeSet(c.SET_ONCE,e,t),this},e.prototype.append=function(e,t){return this._safeSet(c.APPEND,e,t),this},e.prototype.prepend=function(e,t){return this._safeSet(c.PREPEND,e,t),this},e.prototype.postInsert=function(e,t){return this._safeSet(c.POSTINSERT,e,t),this},e.prototype.preInsert=function(e,t){return this._safeSet(c.PREINSERT,e,t),this},e.prototype.remove=function(e,t){return this._safeSet(c.REMOVE,e,t),this},e.prototype.add=function(e,t){return this._safeSet(c.ADD,e,t),this},e.prototype.unset=function(e){return this._safeSet(c.UNSET,e,"-"),this},e.prototype.clearAll=function(){return this._properties={},this._properties[c.CLEAR_ALL]="-",this},e.prototype._safeSet=function(e,t,i){if(this._validate(e,t,i)){var n=this._properties[e];return void 0===n&&(n={},this._properties[e]=n),n[t]=i,this._propertySet.add(t),!0}return!1},e.prototype._validate=function(e,t,i){return void 0===this._properties[c.CLEAR_ALL]&&(!this._propertySet.has(t)&&(e===c.ADD?"number"==typeof i:e===c.UNSET||e===c.REMOVE||L(t,i)))},e}(),V=function(e,t){return i(i({},t),{event_type:d.IDENTIFY,user_properties:e.getUserProperties()})},z=function(e,t,i){return void 0===t&&(t=0),void 0===i&&(i=h.Unknown),{event:e,code:t,message:i}},F=function(){function e(e){this.client=e,this.queue=[],this.applying=!1,this.plugins=[]}return e.prototype.register=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){switch(i.label){case 0:return[4,e.setup(t,this.client)];case 1:return i.sent(),this.plugins.push(e),[2]}}))}))},e.prototype.deregister=function(e){var t;return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:return i=this.plugins.findIndex((function(t){return t.name===e})),n=this.plugins[i],this.plugins.splice(i,1),[4,null===(t=n.teardown)||void 0===t?void 0:t.call(n)];case 1:return r.sent(),[2]}}))}))},e.prototype.reset=function(e){this.applying=!1,this.plugins.map((function(e){var t;return null===(t=e.teardown)||void 0===t?void 0:t.call(e)})),this.plugins=[],this.client=e},e.prototype.push=function(e){var t=this;return new Promise((function(i){t.queue.push([e,i]),t.scheduleApply(0)}))},e.prototype.scheduleApply=function(e){var t=this;this.applying||(this.applying=!0,setTimeout((function(){t.apply(t.queue.shift()).then((function(){t.applying=!1,t.queue.length>0&&t.scheduleApply(0)}))}),e))},e.prototype.apply=function(e){return r(this,void 0,void 0,(function(){var t,n,r,u,c,l,d,p,v,h,g,b,y,m,w,_,I,E,k;return o(this,(function(o){switch(o.label){case 0:if(!e)return[2];t=a(e,1),n=t[0],r=a(e,2),u=r[1],c=this.plugins.filter((function(e){return e.type===f.BEFORE})),o.label=1;case 1:o.trys.push([1,6,7,8]),l=s(c),d=l.next(),o.label=2;case 2:return d.done?[3,5]:[4,d.value.execute(i({},n))];case 3:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=4;case 4:return d=l.next(),[3,2];case 5:return[3,8];case 6:return p=o.sent(),_={error:p},[3,8];case 7:try{d&&!d.done&&(I=l.return)&&I.call(l)}finally{if(_)throw _.error}return[7];case 8:v=this.plugins.filter((function(e){return e.type===f.ENRICHMENT})),o.label=9;case 9:o.trys.push([9,14,15,16]),h=s(v),g=h.next(),o.label=10;case 10:return g.done?[3,13]:[4,g.value.execute(i({},n))];case 11:if(null===(b=o.sent()))return u({event:n,code:0,message:""}),[2];n=b,o.label=12;case 12:return g=h.next(),[3,10];case 13:return[3,16];case 14:return y=o.sent(),E={error:y},[3,16];case 15:try{g&&!g.done&&(k=h.return)&&k.call(h)}finally{if(E)throw E.error}return[7];case 16:return m=this.plugins.filter((function(e){return e.type===f.DESTINATION})),w=m.map((function(e){var t=i({},n);return e.execute(t).catch((function(e){return z(t,0,String(e))}))})),Promise.all(w).then((function(e){var t=a(e,1)[0];u(t)})),[2]}}))}))},e.prototype.flush=function(){return r(this,void 0,void 0,(function(){var e,t,i,n=this;return o(this,(function(r){switch(r.label){case 0:return e=this.queue,this.queue=[],[4,Promise.all(e.map((function(e){return n.apply(e)})))];case 1:return r.sent(),t=this.plugins.filter((function(e){return e.type===f.DESTINATION})),i=t.map((function(e){return e.flush&&e.flush()})),[4,Promise.all(i)];case 2:return r.sent(),[2]}}))}))},e}(),B="Event rejected due to exceeded retry count",$=function(e){return{promise:e||Promise.resolve()}},Q=function(){function e(e){void 0===e&&(e="$default"),this.initializing=!1,this.q=[],this.dispatchQ=[],this.logEvent=this.track.bind(this),this.timeline=new F(this),this.name=e}return e.prototype._init=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return this.config=e,this.timeline.reset(this),[4,this.runQueuedFunctions("q")];case 1:return t.sent(),[2]}}))}))},e.prototype.runQueuedFunctions=function(e){return r(this,void 0,void 0,(function(){var t,i,n,r,a,u;return o(this,(function(o){switch(o.label){case 0:t=this[e],this[e]=[],o.label=1;case 1:o.trys.push([1,6,7,8]),i=s(t),n=i.next(),o.label=2;case 2:return n.done?[3,5]:[4,(0,n.value)()];case 3:o.sent(),o.label=4;case 4:return n=i.next(),[3,2];case 5:return[3,8];case 6:return r=o.sent(),a={error:r},[3,8];case 7:try{n&&!n.done&&(u=i.return)&&u.call(i)}finally{if(a)throw a.error}return[7];case 8:return[2]}}))}))},e.prototype.track=function(e,t,n){var r=function(e,t,n){return i(i(i({},"string"==typeof e?{event_type:e}:e),n),t&&{event_properties:t})}(e,t,n);return $(this.dispatch(r))},e.prototype.identify=function(e,t){var i=V(e,t);return $(this.dispatch(i))},e.prototype.groupIdentify=function(e,t,n,r){var o=function(e,t,n,r){var o;return i(i({},r),{event_type:d.GROUP_IDENTIFY,group_properties:n.getUserProperties(),groups:(o={},o[e]=t,o)})}(e,t,n,r);return $(this.dispatch(o))},e.prototype.setGroup=function(e,t,n){var r=function(e,t,n){var r,o=new M;return o.set(e,t),i(i({},n),{event_type:d.IDENTIFY,user_properties:o.getUserProperties(),groups:(r={},r[e]=t,r)})}(e,t,n);return $(this.dispatch(r))},e.prototype.revenue=function(e,t){var n=function(e,t){return i(i({},t),{event_type:d.REVENUE,event_properties:e.getEventProperties()})}(e,t);return $(this.dispatch(n))},e.prototype.add=function(e){return this.config?$(this.timeline.register(e,this.config)):(this.q.push(this.add.bind(this,e)),$())},e.prototype.remove=function(e){return this.config?$(this.timeline.deregister(e)):(this.q.push(this.remove.bind(this,e)),$())},e.prototype.dispatchWithCallback=function(e,t){if(!this.config)return t(z(e,0,"Client not initialized"));this.process(e).then(t)},e.prototype.dispatch=function(e){return r(this,void 0,void 0,(function(){var t=this;return o(this,(function(i){return this.config?[2,this.process(e)]:[2,new Promise((function(i){t.dispatchQ.push(t.dispatchWithCallback.bind(t,e,i))}))]}))}))},e.prototype.process=function(e){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:return r.trys.push([0,2,,3]),this.config.optOut?[2,z(e,0,"Event skipped due to optOut config")]:[4,this.timeline.push(e)];case 1:return 200===(n=r.sent()).code?this.config.loggerProvider.log(n.message):this.config.loggerProvider.error(n.message),[2,n];case 2:return t=r.sent(),i=String(t),this.config.loggerProvider.error(i),[2,n=z(e,0,i)];case 3:return[2]}}))}))},e.prototype.setOptOut=function(e){this.config?this.config.optOut=Boolean(e):this.q.push(this.setOptOut.bind(this,Boolean(e)))},e.prototype.flush=function(){return $(this.timeline.flush())},e}(),K=function(){function e(){this.productId="",this.quantity=1,this.price=0}return e.prototype.setProductId=function(e){return this.productId=e,this},e.prototype.setQuantity=function(e){return e>0&&(this.quantity=e),this},e.prototype.setPrice=function(e){return this.price=e,this},e.prototype.setRevenueType=function(e){return this.revenueType=e,this},e.prototype.setRevenue=function(e){return this.revenue=e,this},e.prototype.setEventProperties=function(e){return j(e)&&(this.properties=e),this},e.prototype.getEventProperties=function(){var e=this.properties?i({},this.properties):{};return e[l.REVENUE_PRODUCT_ID]=this.productId,e[l.REVENUE_QUANTITY]=this.quantity,e[l.REVENUE_PRICE]=this.price,e[l.REVENUE_TYPE]=this.revenueType,e[l.REVENUE]=this.revenue,e},e}(),H="Amplitude Logger ",W=function(){function e(){this.logLevel=p.None}return e.prototype.disable=function(){this.logLevel=p.None},e.prototype.enable=function(e){void 0===e&&(e=p.Warn),this.logLevel=e},e.prototype.log=function(){for(var e=[],t=0;t0&&Promise.all(i.map((function(e){return n.execute(e)}))).catch(),[2,Promise.resolve(void 0)]}}))}))},e.prototype.execute=function(e){var t=this;return new Promise((function(i){var n={event:e,attempts:0,callback:function(e){return i(e)},timeout:0};t.addToQueue(n)}))},e.prototype.addToQueue=function(){for(var e=this,t=[],i=0;i0&&t.schedule(e)}))}),e))},e.prototype.flush=function(e){return void 0===e&&(e=!1),r(this,void 0,void 0,(function(){var t,i,n,r=this;return o(this,(function(o){switch(o.label){case 0:return t=[],i=[],this.queue.forEach((function(e){return 0===e.timeout?t.push(e):i.push(e)})),this.queue=i,this.scheduled&&(clearTimeout(this.scheduled),this.scheduled=null),s=t,a=this.config.flushQueueSize,u=Math.max(a,1),n=s.reduce((function(e,t,i){var n=Math.floor(i/u);return e[n]||(e[n]=[]),e[n].push(t),e}),[]),[4,Promise.all(n.map((function(t){return r.send(t,e)})))];case 1:return o.sent(),[2]}var s,a,u}))}))},e.prototype.send=function(e,t){return void 0===t&&(t=!0),r(this,void 0,void 0,(function(){var i,r,s,a,u;return o(this,(function(o){switch(o.label){case 0:if(!this.config.apiKey)return[2,this.fulfillRequest(e,400,"Event rejected due to missing API key")];i={api_key:this.config.apiKey,events:e.map((function(e){var t=e.event;return t.extra,n(t,["extra"])})),options:{min_id_length:this.config.minIdLength}},o.label=1;case 1:return o.trys.push([1,3,,4]),r=Y(this.config.serverUrl,this.config.serverZone,this.config.useBatch).serverUrl,[4,this.config.transportProvider.send(r,i)];case 2:return null===(s=o.sent())?(this.fulfillRequest(e,0,"Unexpected error occurred"),[2]):t?(this.handleResponse(s,e),[3,4]):("body"in s?this.fulfillRequest(e,s.statusCode,"".concat(s.status,": ").concat(X(s))):this.fulfillRequest(e,s.statusCode,s.status),[2]);case 3:return a=o.sent(),u=(c=a)instanceof Error?c.message:String(c),this.config.loggerProvider.error(u),this.fulfillRequest(e,0,u),[3,4];case 4:return[2]}var c}))}))},e.prototype.handleResponse=function(e,t){var i=e.status;switch(i){case h.Success:this.handleSuccessResponse(e,t);break;case h.Invalid:this.handleInvalidResponse(e,t);break;case h.PayloadTooLarge:this.handlePayloadTooLargeResponse(e,t);break;case h.RateLimit:this.handleRateLimitResponse(e,t);break;default:this.config.loggerProvider.warn("{code: 0, error: \"Status '".concat(i,"' provided for ").concat(t.length,' events"}')),this.handleOtherResponse(t)}},e.prototype.handleSuccessResponse=function(e,t){this.fulfillRequest(t,e.statusCode,"Event tracked successfully")},e.prototype.handleInvalidResponse=function(e,t){var i=this;if(e.body.missingField||e.body.error.startsWith("Invalid API key"))this.fulfillRequest(t,e.statusCode,e.body.error);else{var n=u(u(u(u([],a(Object.values(e.body.eventsWithInvalidFields)),!1),a(Object.values(e.body.eventsWithMissingFields)),!1),a(Object.values(e.body.eventsWithInvalidIdLengths)),!1),a(e.body.silencedEvents),!1).flat(),r=new Set(n),o=t.filter((function(t,n){if(!r.has(n))return!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));o.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(o),!1))}},e.prototype.handlePayloadTooLargeResponse=function(e,t){1!==t.length?(this.config.loggerProvider.warn(X(e)),this.config.flushQueueSize/=2,this.addToQueue.apply(this,u([],a(t),!1))):this.fulfillRequest(t,e.statusCode,e.body.error)},e.prototype.handleRateLimitResponse=function(e,t){var i=this,n=Object.keys(e.body.exceededDailyQuotaUsers),r=Object.keys(e.body.exceededDailyQuotaDevices),o=e.body.throttledEvents,s=new Set(n),c=new Set(r),l=new Set(o),d=t.filter((function(t,n){if(!(t.event.user_id&&s.has(t.event.user_id)||t.event.device_id&&c.has(t.event.device_id)))return l.has(n)&&(t.timeout=i.throttleTimeout),!0;i.fulfillRequest([t],e.statusCode,e.body.error)}));d.length>0&&this.config.loggerProvider.warn(X(e)),this.addToQueue.apply(this,u([],a(d),!1))},e.prototype.handleOtherResponse=function(e){var t=this;this.addToQueue.apply(this,u([],a(e.map((function(e){return e.timeout=e.attempts*t.retryTimeout,e}))),!1))},e.prototype.fulfillRequest=function(e,t,i){this.saveEvents(),e.forEach((function(e){return e.callback(z(e.event,t,i))}))},e.prototype.saveEvents=function(){if(this.config.storageProvider){var e=Array.from(this.queue.map((function(e){return e.event})));this.config.storageProvider.set(this.storageKey,e)}},e}(),te=function(e){return void 0===e&&(e=0),((new Error).stack||"").split("\n").slice(2+e).map((function(e){return e.trim()}))},ie=function(e){return function(){var t=i({},e.config);return{logger:t.loggerProvider,logLevel:t.logLevel}}},ne=function(e,t){var i,n;t=(t=t.replace(/\[(\w+)\]/g,".$1")).replace(/^\./,"");try{for(var r=s(t.split(".")),o=r.next();!o.done;o=r.next()){var a=o.value;if(!(a in e))return;e=e[a]}}catch(e){i={error:e}}finally{try{o&&!o.done&&(n=r.return)&&n.call(r)}finally{if(i)throw i.error}}return e},re=function(e,t){return function(){var i,n,r={};try{for(var o=s(t),a=o.next();!a.done;a=o.next()){var u=a.value;r[u]=ne(e,u)}}catch(e){i={error:e}}finally{try{a&&!a.done&&(n=o.return)&&n.call(o)}finally{if(i)throw i.error}}return r}},oe=function(e,t,i,n,r){return void 0===r&&(r=null),function(){for(var o=[],s=0;s>e/4).toString(16):(String(1e7)+String(-1e3)+String(-4e3)+String(-8e3)+String(-1e11)).replace(/[018]/g,se)},ae=function(){function e(){this.memoryStorage=new Map}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2,!0]}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return[2,this.memoryStorage.get(e)]}))}))},e.prototype.getRaw=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.get(e)];case 1:return[2,(t=i.sent())?JSON.stringify(t):void 0]}}))}))},e.prototype.set=function(e,t){return r(this,void 0,void 0,(function(){return o(this,(function(i){return this.memoryStorage.set(e,t),[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){return this.memoryStorage.delete(e),[2]}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return this.memoryStorage.clear(),[2]}))}))},e}(),ue=function(){function e(){}return e.prototype.send=function(e,t){return Promise.resolve(null)},e.prototype.buildResponse=function(e){var t,i,n,r,o,s,a,u,c,l,d,p,f,v,g,b,y,m,w,_,I,E;if("object"!=typeof e)return null;var k=e.code||0,S=this.buildStatus(k);switch(S){case h.Success:return{status:S,statusCode:k,body:{eventsIngested:null!==(t=e.events_ingested)&&void 0!==t?t:0,payloadSizeBytes:null!==(i=e.payload_size_bytes)&&void 0!==i?i:0,serverUploadTime:null!==(n=e.server_upload_time)&&void 0!==n?n:0}};case h.Invalid:return{status:S,statusCode:k,body:{error:null!==(r=e.error)&&void 0!==r?r:"",missingField:null!==(o=e.missing_field)&&void 0!==o?o:"",eventsWithInvalidFields:null!==(s=e.events_with_invalid_fields)&&void 0!==s?s:{},eventsWithMissingFields:null!==(a=e.events_with_missing_fields)&&void 0!==a?a:{},eventsWithInvalidIdLengths:null!==(u=e.events_with_invalid_id_lengths)&&void 0!==u?u:{},epsThreshold:null!==(c=e.eps_threshold)&&void 0!==c?c:0,exceededDailyQuotaDevices:null!==(l=e.exceeded_daily_quota_devices)&&void 0!==l?l:{},silencedDevices:null!==(d=e.silenced_devices)&&void 0!==d?d:[],silencedEvents:null!==(p=e.silenced_events)&&void 0!==p?p:[],throttledDevices:null!==(f=e.throttled_devices)&&void 0!==f?f:{},throttledEvents:null!==(v=e.throttled_events)&&void 0!==v?v:[]}};case h.PayloadTooLarge:return{status:S,statusCode:k,body:{error:null!==(g=e.error)&&void 0!==g?g:""}};case h.RateLimit:return{status:S,statusCode:k,body:{error:null!==(b=e.error)&&void 0!==b?b:"",epsThreshold:null!==(y=e.eps_threshold)&&void 0!==y?y:0,throttledDevices:null!==(m=e.throttled_devices)&&void 0!==m?m:{},throttledUsers:null!==(w=e.throttled_users)&&void 0!==w?w:{},exceededDailyQuotaDevices:null!==(_=e.exceeded_daily_quota_devices)&&void 0!==_?_:{},exceededDailyQuotaUsers:null!==(I=e.exceeded_daily_quota_users)&&void 0!==I?I:{},throttledEvents:null!==(E=e.throttled_events)&&void 0!==E?E:[]}};case h.Timeout:default:return{status:S,statusCode:k}}},e.prototype.buildStatus=function(e){return e>=200&&e<300?h.Success:429===e?h.RateLimit:413===e?h.PayloadTooLarge:408===e?h.Timeout:e>=400&&e<500?h.Invalid:e>=500?h.Failed:h.Unknown},e}(),ce=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},le=function(){function e(e){this.options=i({},e)}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i;return o(this,(function(n){switch(n.label){case 0:if(!b())return[2,!1];e.testValue=String(Date.now()),t=new e(this.options),i="AMP_TEST",n.label=1;case 1:return n.trys.push([1,4,5,7]),[4,t.set(i,e.testValue)];case 2:return n.sent(),[4,t.get(i)];case 3:return[2,n.sent()===e.testValue];case 4:return n.sent(),[2,!1];case 5:return[4,t.remove(i)];case 6:return n.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return[4,this.getRaw(e)];case 1:if(!(t=i.sent()))return[2,void 0];try{try{t=decodeURIComponent(atob(t))}catch(e){}return[2,JSON.parse(t)]}catch(e){return[2,void 0]}return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){var i,n,r;return o(this,(function(o){return i=b(),n=null!==(t=null==i?void 0:i.document.cookie.split("; "))&&void 0!==t?t:[],(r=n.find((function(t){return 0===t.indexOf(e+"=")})))?[2,r.substring(e.length+1)]:[2,void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){var n,r,s,a,u,c;return o(this,(function(o){try{n=null!==(i=this.options.expirationDays)&&void 0!==i?i:0,s=void 0,(r=null!==t?n:-1)&&((a=new Date).setTime(a.getTime()+24*r*60*60*1e3),s=a),u="".concat(e,"=").concat(btoa(encodeURIComponent(JSON.stringify(t)))),s&&(u+="; expires=".concat(s.toUTCString())),u+="; path=/",this.options.domain&&(u+="; domain=".concat(this.options.domain)),this.options.secure&&(u+="; Secure"),this.options.sameSite&&(u+="; SameSite=".concat(this.options.sameSite)),(c=b())&&(c.document.cookie=u)}catch(e){}return[2]}))}))},e.prototype.remove=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.set(e,null)];case 1:return t.sent(),[2]}}))}))},e.prototype.reset=function(){return r(this,void 0,void 0,(function(){return o(this,(function(e){return[2]}))}))},e}(),de=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i,n;return o(this,(function(r){switch(r.label){case 0:if("undefined"==typeof fetch)throw new Error("FetchTransport is not supported");return i={headers:{"Content-Type":"application/json",Accept:"*/*"},body:JSON.stringify(t),method:"POST"},[4,fetch(e,i)];case 1:return[4,r.sent().json()];case 2:return n=r.sent(),[2,this.buildResponse(n)]}}))}))},i}(ue),pe=function(){function e(){}return e.prototype.getApplicationContext=function(){return{versionName:this.versionName,language:fe(),platform:"Web",os:void 0,deviceModel:void 0}},e}(),fe=function(){return"undefined"!=typeof navigator&&(navigator.languages&&navigator.languages[0]||navigator.language)||""},ve=function(){function e(){this.queue=[]}return e.prototype.logEvent=function(e){this.receiver?this.receiver(e):this.queue.length<512&&this.queue.push(e)},e.prototype.setEventReceiver=function(e){this.receiver=e,this.queue.length>0&&(this.queue.forEach((function(t){e(t)})),this.queue=[])},e}(),he=function(){return he=Object.assign||function(e){for(var t,i=1,n=arguments.length;i0?2===u.length?typeof u[1]==i?this[u[0]]=u[1].call(this,l):this[u[0]]=u[1]:3===u.length?typeof u[1]!==i||u[1].exec&&u[1].test?this[u[0]]=l?l.replace(u[1],u[2]):t:this[u[0]]=l?u[1].call(this,l,u[2]):t:4===u.length&&(this[u[0]]=l?u[3].call(this,l.replace(u[1],u[2])):t):this[u]=l||t;d+=2}},z=function(e,i){for(var n in i)if(typeof i[n]===r&&i[n].length>0){for(var o=0;o275?M(e,275):e,this},this.setUA(f),this};$.VERSION="0.7.31",$.BROWSER=C([a,l,"major"]),$.CPU=C([d]),$.DEVICE=C([s,c,u,p,f,h,v,g,b]),$.ENGINE=$.OS=C([a,l]),be.exports&&(ye=be.exports=$),ye.UAParser=$;var Q=typeof e!==n&&(e.jQuery||e.Zepto);if(Q&&!Q.ua){var K=new $;Q.ua=K.getResult(),Q.ua.get=function(){return K.getUA()},Q.ua.set=function(e){K.setUA(e);var t=K.getResult();for(var i in t)Q.ua[i]=t[i]}}}("object"==typeof window?window:xe);var Re=Pe.exports,Ne=function(){function e(){this.name="context",this.type=f.BEFORE,this.library="amplitude-ts/".concat("1.12.1"),"undefined"!=typeof navigator&&(this.userAgent=navigator.userAgent),this.uaResult=new Re(this.userAgent).getResult()}return e.prototype.setup=function(e){return this.config=e,Promise.resolve(void 0)},e.prototype.execute=function(e){var t,n;return r(this,void 0,void 0,(function(){var r,s,a,u,c,l,d;return o(this,(function(o){return r=(new Date).getTime(),s=this.uaResult.browser.name,a=this.uaResult.browser.version,u=this.uaResult.device.model||this.uaResult.os.name,c=this.uaResult.device.vendor,l=null!==(t=this.config.lastEventId)&&void 0!==t?t:-1,d=null!==(n=e.event_id)&&void 0!==n?n:l+1,this.config.lastEventId=d,e.time||(this.config.lastEventTime=r),[2,i(i(i(i(i(i(i(i(i(i(i(i({user_id:this.config.userId,device_id:this.config.deviceId,session_id:this.config.sessionId,time:r},this.config.appVersion&&{app_version:this.config.appVersion}),this.config.trackingOptions.platform&&{platform:"Web"}),this.config.trackingOptions.osName&&{os_name:s}),this.config.trackingOptions.osVersion&&{os_version:a}),this.config.trackingOptions.deviceManufacturer&&{device_manufacturer:c}),this.config.trackingOptions.deviceModel&&{device_model:u}),this.config.trackingOptions.language&&{language:ke()}),this.config.trackingOptions.ipAddress&&{ip:"$remote"}),{insert_id:se(),partner_id:this.config.partnerId,plan:this.config.plan}),this.config.ingestionMetadata&&{ingestion_metadata:{source_name:this.config.ingestionMetadata.sourceName,source_version:this.config.ingestionMetadata.sourceVersion}}),e),{event_id:d,library:this.library,user_agent:this.userAgent})]}))}))},e}(),qe=function(){function e(){}return e.prototype.isEnabled=function(){return r(this,void 0,void 0,(function(){var t,i,n;return o(this,(function(r){switch(r.label){case 0:if(!b())return[2,!1];t=String(Date.now()),i=new e,n="AMP_TEST",r.label=1;case 1:return r.trys.push([1,4,5,7]),[4,i.set(n,t)];case 2:return r.sent(),[4,i.get(n)];case 3:return[2,r.sent()===t];case 4:return r.sent(),[2,!1];case 5:return[4,i.remove(n)];case 6:return r.sent(),[7];case 7:return[2]}}))}))},e.prototype.get=function(e){return r(this,void 0,void 0,(function(){var t;return o(this,(function(i){switch(i.label){case 0:return i.trys.push([0,2,,3]),[4,this.getRaw(e)];case 1:return(t=i.sent())?[2,JSON.parse(t)]:[2,void 0];case 2:return i.sent(),[2,void 0];case 3:return[2]}}))}))},e.prototype.getRaw=function(e){var t;return r(this,void 0,void 0,(function(){return o(this,(function(i){return[2,(null===(t=b())||void 0===t?void 0:t.localStorage.getItem(e))||void 0]}))}))},e.prototype.set=function(e,t){var i;return r(this,void 0,void 0,(function(){return o(this,(function(n){try{null===(i=b())||void 0===i||i.localStorage.setItem(e,JSON.stringify(t))}catch(e){}return[2]}))}))},e.prototype.remove=function(e){var t;return r(this,void 0,void 0,(function(){return o(this,(function(i){try{null===(t=b())||void 0===t||t.localStorage.removeItem(e)}catch(e){}return[2]}))}))},e.prototype.reset=function(){var e;return r(this,void 0,void 0,(function(){return o(this,(function(t){try{null===(e=b())||void 0===e||e.localStorage.clear()}catch(e){}return[2]}))}))},e}(),Ue=function(e){function i(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={done:4},t}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i=this;return o(this,(function(n){return[2,new Promise((function(n,r){"undefined"==typeof XMLHttpRequest&&r(new Error("XHRTransport is not supported."));var o=new XMLHttpRequest;o.open("POST",e,!0),o.onreadystatechange=function(){if(o.readyState===i.state.done)try{var e=o.responseText,t=JSON.parse(e),s=i.buildResponse(t);n(s)}catch(e){r(e)}},o.setRequestHeader("Content-Type","application/json"),o.setRequestHeader("Accept","*/*"),o.send(JSON.stringify(t))}))]}))}))},i}(ue),De=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i.prototype.send=function(e,t){return r(this,void 0,void 0,(function(){var i=this;return o(this,(function(n){return[2,new Promise((function(n,r){var o=b();if(!(null==o?void 0:o.navigator.sendBeacon))throw new Error("SendBeaconTransport is not supported");try{var s=JSON.stringify(t);return n(o.navigator.sendBeacon(e,JSON.stringify(t))?i.buildResponse({code:200,events_ingested:t.events.length,payload_size_bytes:s.length,server_upload_time:Date.now()}):i.buildResponse({code:500}))}catch(e){r(e)}}))]}))}))},i}(ue),Ae=function(){return{cookieExpiration:365,cookieSameSite:"Lax",cookieSecure:!1,cookieStorage:new ae,cookieUpgrade:!0,disableCookies:!1,domain:"",sessionTimeout:18e5,trackingOptions:{deviceManufacturer:!0,deviceModel:!0,ipAddress:!0,language:!0,osName:!0,osVersion:!0,platform:!0},transportProvider:new de}},Ce=function(e){function n(t,n){var r,o,s,a,u,c,l,d,p,f=this,v=Ae();return(f=e.call(this,i(i({flushIntervalMillis:1e3,flushMaxRetries:5,flushQueueSize:30,transportProvider:v.transportProvider},n),{apiKey:t}))||this)._optOut=!1,f.cookieStorage=null!==(r=null==n?void 0:n.cookieStorage)&&void 0!==r?r:v.cookieStorage,f.deviceId=null==n?void 0:n.deviceId,f.lastEventId=null==n?void 0:n.lastEventId,f.lastEventTime=null==n?void 0:n.lastEventTime,f.optOut=Boolean(null==n?void 0:n.optOut),f.sessionId=null==n?void 0:n.sessionId,f.userId=null==n?void 0:n.userId,f.appVersion=null==n?void 0:n.appVersion,f.attribution=null==n?void 0:n.attribution,f.cookieExpiration=null!==(o=null==n?void 0:n.cookieExpiration)&&void 0!==o?o:v.cookieExpiration,f.cookieSameSite=null!==(s=null==n?void 0:n.cookieSameSite)&&void 0!==s?s:v.cookieSameSite,f.cookieSecure=null!==(a=null==n?void 0:n.cookieSecure)&&void 0!==a?a:v.cookieSecure,f.cookieUpgrade=null!==(u=null==n?void 0:n.cookieUpgrade)&&void 0!==u?u:v.cookieUpgrade,f.defaultTracking=null==n?void 0:n.defaultTracking,f.disableCookies=null!==(c=null==n?void 0:n.disableCookies)&&void 0!==c?c:v.disableCookies,f.defaultTracking=null==n?void 0:n.defaultTracking,f.domain=null!==(l=null==n?void 0:n.domain)&&void 0!==l?l:v.domain,f.partnerId=null==n?void 0:n.partnerId,f.sessionTimeout=null!==(d=null==n?void 0:n.sessionTimeout)&&void 0!==d?d:v.sessionTimeout,f.trackingOptions=null!==(p=null==n?void 0:n.trackingOptions)&&void 0!==p?p:v.trackingOptions,f}return t(n,e),Object.defineProperty(n.prototype,"deviceId",{get:function(){return this._deviceId},set:function(e){this._deviceId!==e&&(this._deviceId=e,this.updateStorage())},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"userId",{get:function(){return this._userId},set:function(e){this._userId!==e&&(this._userId=e,this.updateStorage())},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"sessionId",{get:function(){return this._sessionId},set:function(e){this._sessionId!==e&&(this._sessionId=e,this.updateStorage())},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"optOut",{get:function(){return this._optOut},set:function(e){this._optOut!==e&&(this._optOut=e,this.updateStorage())},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"lastEventTime",{get:function(){return this._lastEventTime},set:function(e){this._lastEventTime!==e&&(this._lastEventTime=e,this.updateStorage())},enumerable:!1,configurable:!0}),Object.defineProperty(n.prototype,"lastEventId",{get:function(){return this._lastEventId},set:function(e){this._lastEventId!==e&&(this._lastEventId=e,this.updateStorage())},enumerable:!1,configurable:!0}),n.prototype.updateStorage=function(){var e,t={deviceId:this._deviceId,userId:this._userId,sessionId:this._sessionId,optOut:this._optOut,lastEventTime:this._lastEventTime,lastEventId:this._lastEventId};null===(e=this.cookieStorage)||void 0===e||e.set(ce(this.apiKey),t)},n}(G),je=function(e,t){return r(void 0,void 0,void 0,(function(){var n,r,s,a,u,c,l,d,p,f,v,h,g,b,y;return o(this,(function(o){switch(o.label){case 0:return n=Ae(),r=null!==(b=null==t?void 0:t.deviceId)&&void 0!==b?b:se(),s=null==t?void 0:t.lastEventId,a=null==t?void 0:t.lastEventTime,u=null==t?void 0:t.optOut,c=null==t?void 0:t.sessionId,l=null==t?void 0:t.userId,d=null==t?void 0:t.cookieStorage,p=null==t?void 0:t.domain,f=Ce.bind,v=[void 0,e],h=[i({},t)],g={cookieStorage:d,deviceId:r,domain:p,lastEventId:s,lastEventTime:a,optOut:u,sessionId:c},[4,Ve(t)];case 1:return[2,new(f.apply(Ce,v.concat([i.apply(void 0,h.concat([(g.storageProvider=o.sent(),g.trackingOptions=i(i({},n.trackingOptions),null==t?void 0:t.trackingOptions),g.transportProvider=null!==(y=null==t?void 0:t.transportProvider)&&void 0!==y?y:ze(null==t?void 0:t.transport),g.userId=l,g)]))])))]}}))}))},Le=function(e,t){return void 0===t&&(t=Ae()),r(void 0,void 0,void 0,(function(){var n,r,s;return o(this,(function(o){switch(o.label){case 0:return n=i(i({},t),e),r=null==e?void 0:e.cookieStorage,(s=!r)?[3,2]:[4,r.isEnabled()];case 1:s=!o.sent(),o.label=2;case 2:return s?[2,Me(n)]:[2,r]}}))}))},Me=function(e){return r(void 0,void 0,void 0,(function(){var t,i;return o(this,(function(n){switch(n.label){case 0:return t=new le({domain:e.domain,expirationDays:e.cookieExpiration,sameSite:e.cookieSameSite,secure:e.cookieSecure}),(i=e.disableCookies)?[3,2]:[4,t.isEnabled()];case 1:i=!n.sent(),n.label=2;case 2:return i?[4,(t=new qe).isEnabled()]:[3,4];case 3:n.sent()||(t=new ae),n.label=4;case 4:return[2,t]}}))}))},Ve=function(e){return r(void 0,void 0,void 0,(function(){var t,i,n,r,a,u,c;return o(this,(function(o){switch(o.label){case 0:if(e&&Object.prototype.hasOwnProperty.call(e,"storageProvider")&&!e.storageProvider)return[3,9];o.label=1;case 1:o.trys.push([1,7,8,9]),t=s([null==e?void 0:e.storageProvider,new qe]),i=t.next(),o.label=2;case 2:return i.done?[3,6]:(n=i.value,(r=n)?[4,n.isEnabled()]:[3,4]);case 3:r=o.sent(),o.label=4;case 4:if(r)return[2,n];o.label=5;case 5:return i=t.next(),[3,2];case 6:return[3,9];case 7:return a=o.sent(),u={error:a},[3,9];case 8:try{i&&!i.done&&(c=t.return)&&c.call(t)}finally{if(u)throw u.error}return[7];case 9:return[2,void 0]}}))}))},ze=function(e){return e===g.XHR?new Ue:e===g.SendBeacon?new De:Ae().transportProvider},Fe=function(e,t){return r(void 0,void 0,void 0,(function(){var i,n,r,s,u,c,l,d,p,f,v;return o(this,(function(o){switch(o.label){case 0:return[4,Le(t)];case 1:return i=o.sent(),n=function(e){return"".concat(D.toLowerCase(),"_").concat(e.substring(0,6))}(e),[4,i.getRaw(n)];case 2:return(r=o.sent())?(null!==(v=t.cookieUpgrade)&&void 0!==v?v:Ae().cookieUpgrade)?[4,i.remove(n)]:[3,4]:[2,{optOut:!1}];case 3:o.sent(),o.label=4;case 4:return s=a(r.split("."),6),u=s[0],c=s[1],l=s[2],d=s[3],p=s[4],f=s[5],[2,{deviceId:u,userId:$e(c),sessionId:Be(d),lastEventId:Be(f),lastEventTime:Be(p),optOut:Boolean(l)}]}}))}))},Be=function(e){var t=parseInt(e,32);if(!isNaN(t))return t},$e=function(e){if(atob&&escape&&e)try{return decodeURIComponent(escape(atob(e)))}catch(e){return}},Qe=function(e,t,i){return void 0===t&&(t=""),void 0===i&&(i=10),[D,t,e.substring(0,i)].filter(Boolean).join("_")},Ke=function(e){var t=e.split(".");return t.length<=2?e:t.slice(t.length-2,t.length).join(".")},He=function(e,t,i){var r;e.referrer;var o=e.referring_domain,s=n(e,["referrer","referring_domain"]),a=t||{};a.referrer;var u=a.referring_domain,c=n(a,["referrer","referring_domain"]);if(e.referring_domain&&(null===(r=i.excludeReferrers)||void 0===r?void 0:r.includes(e.referring_domain)))return!1;var l=JSON.stringify(s)!==JSON.stringify(c),d=Ke(o||"")!==Ke(u||"");return!t||l||d},We=function(e,t){var n=i(i({},N),e),r=Object.entries(n).reduce((function(e,i){var n,r=a(i,2),o=r[0],s=r[1];return e.setOnce("initial_".concat(o),null!==(n=null!=s?s:t.initialEmptyValue)&&void 0!==n?n:"EMPTY"),s?e.set(o,s):e.unset(o)}),new M);return V(r)},Ze=function(e){var t={};for(var i in e){var n=e[i];n&&(t[i]=n)}return t},Ge=function(){for(var e,t=[],n=0;n=0;--r)i.push(t.slice(r).join("."));r=0,o.label=2;case 2:return rthis.config.sessionTimeout)&&(this.setSessionId(null!==(I=null!==(_=t.sessionId)&&void 0!==_?_:this.config.sessionId)&&void 0!==I?I:Date.now()),B=!0),($=Ie(t.instanceName)).identityStore.setIdentity({userId:this.config.userId,deviceId:this.config.deviceId}),[4,this.add(new ee).promise];case 11:return W.sent(),[4,this.add(new Ne).promise];case 12:return W.sent(),[4,this.add(new Ee).promise];case 13:return W.sent(),("boolean"==typeof(Z=this.config.defaultTracking)?Z:null==Z?void 0:Z.fileDownloads)?[4,this.add(gt()).promise]:[3,15];case 14:W.sent(),W.label=15;case 15:return function(e){return"boolean"==typeof e?e:!!(null==e?void 0:e.formInteractions)}(this.config.defaultTracking)?[4,this.add(ht()).promise]:[3,17];case 16:W.sent(),W.label=17;case 17:return(null===(E=this.config.attribution)||void 0===E?void 0:E.disabled)?[3,19]:(Q=function(){for(var e,t,n=this,s=[],u=0;uthis.config.sessionTimeout&&this.setSessionId(i),[2,e.prototype.process.call(this,t)]}))}))},n}(Q),mt=function(){var e=new yt;return{init:oe(e.init.bind(e),"init",ie(e),re(e,["config"])),add:oe(e.add.bind(e),"add",ie(e),re(e,["config.apiKey","timeline.plugins"])),remove:oe(e.remove.bind(e),"remove",ie(e),re(e,["config.apiKey","timeline.plugins"])),track:oe(e.track.bind(e),"track",ie(e),re(e,["config.apiKey","timeline.queue.length"])),logEvent:oe(e.logEvent.bind(e),"logEvent",ie(e),re(e,["config.apiKey","timeline.queue.length"])),identify:oe(e.identify.bind(e),"identify",ie(e),re(e,["config.apiKey","timeline.queue.length"])),groupIdentify:oe(e.groupIdentify.bind(e),"groupIdentify",ie(e),re(e,["config.apiKey","timeline.queue.length"])),setGroup:oe(e.setGroup.bind(e),"setGroup",ie(e),re(e,["config.apiKey","timeline.queue.length"])),revenue:oe(e.revenue.bind(e),"revenue",ie(e),re(e,["config.apiKey","timeline.queue.length"])),flush:oe(e.flush.bind(e),"flush",ie(e),re(e,["config.apiKey","timeline.queue.length"])),getUserId:oe(e.getUserId.bind(e),"getUserId",ie(e),re(e,["config","config.userId"])),setUserId:oe(e.setUserId.bind(e),"setUserId",ie(e),re(e,["config","config.userId"])),getDeviceId:oe(e.getDeviceId.bind(e),"getDeviceId",ie(e),re(e,["config","config.deviceId"])),setDeviceId:oe(e.setDeviceId.bind(e),"setDeviceId",ie(e),re(e,["config","config.deviceId"])),reset:oe(e.reset.bind(e),"reset",ie(e),re(e,["config","config.userId","config.deviceId"])),getSessionId:oe(e.getSessionId.bind(e),"getSessionId",ie(e),re(e,["config"])),setSessionId:oe(e.setSessionId.bind(e),"setSessionId",ie(e),re(e,["config"])),extendSession:oe(e.extendSession.bind(e),"extendSession",ie(e),re(e,["config"])),setOptOut:oe(e.setOptOut.bind(e),"setOptOut",ie(e),re(e,["config"])),setTransport:oe(e.setTransport.bind(e),"setTransport",ie(e),re(e,["config"]))}},wt=mt(),_t=wt.add,It=wt.extendSession,Et=wt.flush,kt=wt.getDeviceId,St=wt.getSessionId,Tt=wt.getUserId,Ot=wt.groupIdentify,xt=wt.identify,Pt=wt.init,Rt=wt.logEvent,Nt=wt.remove,qt=wt.reset,Ut=wt.revenue,Dt=wt.setDeviceId,At=wt.setGroup,Ct=wt.setOptOut,jt=wt.setSessionId,Lt=wt.setTransport,Mt=wt.setUserId,Vt=wt.track,zt=Object.freeze({__proto__:null,add:_t,extendSession:It,flush:Et,getDeviceId:kt,getSessionId:St,getUserId:Tt,groupIdentify:Ot,identify:xt,init:Pt,logEvent:Rt,remove:Nt,reset:qt,revenue:Ut,setDeviceId:Dt,setGroup:At,setOptOut:Ct,setSessionId:jt,setTransport:Lt,setUserId:Mt,track:Vt,Types:U,createInstance:mt,runQueuedFunctions:Se,Revenue:K,Identify:M});!function(){var e=b();if(e){var t=function(e){var t=mt(),i=b();return i&&i.amplitude&&i.amplitude._iq&&e&&(i.amplitude._iq[e]=t),t};if(e.amplitude=Object.assign(e.amplitude||{},zt,{createInstance:t}),e.amplitude.invoked){var i=e.amplitude._q;e.amplitude._q=[],Se(zt,i);for(var n=Object.keys(e.amplitude._iq)||[],r=0;r
```html
diff --git a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
index 4a216eefc..abe75bd6d 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-gtm-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-uQFYsRWKr27i5VRoSShdW+SWnMC3RYcwlKIyk4cs8E1KrVgcdqeAI4Dca3cGejM+';
+ as.integrity = 'sha384-juCBofnD0wr/Qoa2qChzdXfo79rKhQedVQ+CXUPUj203nMMvPVjVN7KGTw+ONROe';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.6-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-gtm-1.0.7-min.js.gz';
as.onload = function () {
if (!window.amplitudeGTM.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/generated/amplitude-snippet.js b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
index cac52bf19..57be95124 100644
--- a/packages/marketing-analytics-browser/generated/amplitude-snippet.js
+++ b/packages/marketing-analytics-browser/generated/amplitude-snippet.js
@@ -49,10 +49,10 @@
amplitude.invoked = true;
var as = document.createElement('script');
as.type = 'text/javascript';
- as.integrity = 'sha384-LxzsESYOYIQDLuCbztiSAfn/WnK7IQMVua4D9OvBkNgfqKHs8lZOfldylsVDG7m4';
+ as.integrity = 'sha384-XaMbg2DwRNF/qnNtpb/cOoV+zkqyN+yl8FTT5d9XxigwSyhwjDlpSeZwbcBzun7j';
as.crossOrigin = 'anonymous';
as.async = true;
- as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.6-min.js.gz';
+ as.src = 'https://cdn.amplitude.com/libs/marketing-analytics-browser-1.0.7-min.js.gz';
as.onload = function () {
if (!window.amplitude.runQueuedFunctions) {
console.log('[Amplitude] Error: could not load SDK');
diff --git a/packages/marketing-analytics-browser/package.json b/packages/marketing-analytics-browser/package.json
index 070ca4b1c..f3f98143b 100644
--- a/packages/marketing-analytics-browser/package.json
+++ b/packages/marketing-analytics-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/marketing-analytics-browser",
- "version": "1.0.6",
+ "version": "1.0.7",
"description": "Official Amplitude SDK for Web and Marketing Analytics",
"keywords": [
"analytics",
@@ -43,12 +43,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-browser": "^1.12.0",
- "@amplitude/analytics-client-common": "^1.1.0",
- "@amplitude/analytics-core": "^1.2.0",
- "@amplitude/analytics-types": "^1.3.0",
- "@amplitude/plugin-page-view-tracking-browser": "^1.0.6",
- "@amplitude/plugin-web-attribution-browser": "^1.0.6",
+ "@amplitude/analytics-browser": "^1.12.1",
+ "@amplitude/analytics-client-common": "^1.1.1",
+ "@amplitude/analytics-core": "^1.2.1",
+ "@amplitude/analytics-types": "^1.3.1",
+ "@amplitude/plugin-page-view-tracking-browser": "^1.0.7",
+ "@amplitude/plugin-web-attribution-browser": "^1.0.7",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/marketing-analytics-browser/src/version.ts b/packages/marketing-analytics-browser/src/version.ts
index 3f6f127aa..ee5d17a8f 100644
--- a/packages/marketing-analytics-browser/src/version.ts
+++ b/packages/marketing-analytics-browser/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.0.6';
+export const VERSION = '1.0.7';
diff --git a/packages/plugin-page-view-tracking-browser/CHANGELOG.md b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
index 401e9f308..e9f365677 100644
--- a/packages/plugin-page-view-tracking-browser/CHANGELOG.md
+++ b/packages/plugin-page-view-tracking-browser/CHANGELOG.md
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.6...@amplitude/plugin-page-view-tracking-browser@1.0.7) (2023-07-05)
+
+### Bug Fixes
+
+- allow plugins to teardown to remove listeners ([#463](https://github.com/amplitude/Amplitude-TypeScript/issues/463))
+ ([1394ddb](https://github.com/amplitude/Amplitude-TypeScript/commit/1394ddb3775c7e5b1a3da87e6a4c973a586c6382))
+- handle concurrent history push state calls ([#466](https://github.com/amplitude/Amplitude-TypeScript/issues/466))
+ ([4ebf96b](https://github.com/amplitude/Amplitude-TypeScript/commit/4ebf96b8c611cc0c766446b35e8a209bf5af580b))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-tracking-browser@1.0.5...@amplitude/plugin-page-view-tracking-browser@1.0.6) (2023-06-26)
**Note:** Version bump only for package @amplitude/plugin-page-view-tracking-browser
diff --git a/packages/plugin-page-view-tracking-browser/package.json b/packages/plugin-page-view-tracking-browser/package.json
index a2cd4b9a0..8bb17ae6c 100644
--- a/packages/plugin-page-view-tracking-browser/package.json
+++ b/packages/plugin-page-view-tracking-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-tracking-browser",
- "version": "1.0.6",
+ "version": "1.0.7",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,12 +37,12 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.1.0",
- "@amplitude/analytics-types": "^1.3.0",
+ "@amplitude/analytics-client-common": "^1.1.1",
+ "@amplitude/analytics-types": "^1.3.1",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.12.0",
+ "@amplitude/analytics-browser": "^1.12.1",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 758270f9e..6411fb68e 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.1.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.0...@amplitude/plugin-session-replay-browser@0.1.1) (2023-07-05)
+
+### Bug Fixes
+
+- allow literal values for plugin type ([#468](https://github.com/amplitude/Amplitude-TypeScript/issues/468))
+ ([603e3ef](https://github.com/amplitude/Amplitude-TypeScript/commit/603e3eff81a3d03082544541a673df955cf30118))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# 0.1.0 (2023-06-28)
### Features
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index d2635ef02..73130a832 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,14 +37,14 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": ">=1 <3",
- "@amplitude/analytics-types": ">=1 <3",
+ "@amplitude/analytics-client-common": "^1.1.1",
+ "@amplitude/analytics-types": "^1.3.1",
"idb-keyval": "^6.2.1",
"rrweb": "^2.0.0-alpha.4",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": ">=1 <3",
+ "@amplitude/analytics-browser": "^1.12.1",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-web-attribution-browser/CHANGELOG.md b/packages/plugin-web-attribution-browser/CHANGELOG.md
index a1bb02e33..5f9ab3bb8 100644
--- a/packages/plugin-web-attribution-browser/CHANGELOG.md
+++ b/packages/plugin-web-attribution-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.7](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.6...@amplitude/plugin-web-attribution-browser@1.0.7) (2023-07-05)
+
+### Bug Fixes
+
+- missing core dependency for web attribution ([#462](https://github.com/amplitude/Amplitude-TypeScript/issues/462))
+ ([902aa39](https://github.com/amplitude/Amplitude-TypeScript/commit/902aa39c8dcccfdc48f44312fe11a811a39cba10))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.6](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-web-attribution-browser@1.0.5...@amplitude/plugin-web-attribution-browser@1.0.6) (2023-06-26)
**Note:** Version bump only for package @amplitude/plugin-web-attribution-browser
diff --git a/packages/plugin-web-attribution-browser/package.json b/packages/plugin-web-attribution-browser/package.json
index c2d1c0d51..e1104e9ce 100644
--- a/packages/plugin-web-attribution-browser/package.json
+++ b/packages/plugin-web-attribution-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-web-attribution-browser",
- "version": "1.0.6",
+ "version": "1.0.7",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
@@ -37,13 +37,13 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.1.0",
- "@amplitude/analytics-core": "^1.2.0",
- "@amplitude/analytics-types": "^1.3.0",
+ "@amplitude/analytics-client-common": "^1.1.1",
+ "@amplitude/analytics-core": "^1.2.1",
+ "@amplitude/analytics-types": "^1.3.1",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.12.0",
+ "@amplitude/analytics-browser": "^1.12.1",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
From fadd7c0cd24f78c6dd317098fe6dfad951ce206e Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 6 Jul 2023 15:45:18 -0400
Subject: [PATCH 050/214] fix(plugins): update timing of sending batches of
session replay events
---
.../src/helpers.ts | 8 --
.../src/session-replay.ts | 40 +++++-
.../src/typings/session-replay.ts | 3 +
.../test/session-replay.test.ts | 135 +++++++++++++++---
4 files changed, 155 insertions(+), 31 deletions(-)
delete mode 100644 packages/plugin-session-replay-browser/src/helpers.ts
diff --git a/packages/plugin-session-replay-browser/src/helpers.ts b/packages/plugin-session-replay-browser/src/helpers.ts
deleted file mode 100644
index 67d59de84..000000000
--- a/packages/plugin-session-replay-browser/src/helpers.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export const shouldSplitEventsList = (eventsList: string[], nextEventString: string, maxListSize: number): boolean => {
- const sizeOfNextEvent = new Blob([nextEventString]).size;
- const sizeOfEventsList = new Blob(eventsList).size;
- if (sizeOfEventsList + sizeOfNextEvent >= maxListSize) {
- return true;
- }
- return false;
-};
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 2808f23c9..5e75ba438 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -3,7 +3,6 @@ import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
-import { shouldSplitEventsList } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import {
Events,
@@ -17,6 +16,8 @@ const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/tra
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+const MIN_INTERVAL = 1 * 1000; // 1 second
+const MAX_INTERVAL = 10 * 1000; // 10 seconds
class SessionReplay implements SessionReplayEnrichmentPlugin {
name = '@amplitude/plugin-session-replay-browser';
@@ -33,6 +34,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
queue: SessionReplayContext[] = [];
stopRecordingEvents: ReturnType | null = null;
maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
+ interval = MIN_INTERVAL;
+ timeSinceLastSend: number | null = null;
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
@@ -47,8 +50,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
...event.event_properties,
[DEFAULT_SESSION_REPLAY_PROPERTY]: true,
};
- if (event.event_type === DEFAULT_SESSION_START_EVENT && this.stopRecordingEvents) {
- this.stopRecordingEvents();
+ if (event.event_type === DEFAULT_SESSION_START_EVENT) {
this.recordEvents();
} else if (event.event_type === DEFAULT_SESSION_END_EVENT) {
if (event.session_id) {
@@ -58,6 +60,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
sessionId: event.session_id,
});
}
+ this.stopRecordingEvents && this.stopRecordingEvents();
this.events = [];
this.currentSequenceId = 0;
}
@@ -88,8 +91,20 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
recordEvents() {
this.stopRecordingEvents = record({
emit: (event) => {
+ console.log('this.events', this.events);
const eventString = JSON.stringify(event);
- const shouldSplit = shouldSplitEventsList(this.events, eventString, this.maxPersistedEventsSize);
+ // Send the first recorded event immediately
+ if (!this.events.length && this.currentSequenceId === 0) {
+ this.sendEventsList({
+ events: [eventString],
+ sequenceId: this.currentSequenceId,
+ sessionId: this.config.sessionId as number,
+ });
+ this.currentSequenceId++;
+ return;
+ }
+
+ const shouldSplit = this.shouldSplitEventsList(eventString);
if (shouldSplit) {
this.sendEventsList({
events: this.events,
@@ -103,9 +118,23 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
void this.storeEventsForSession(this.events, this.currentSequenceId);
},
packFn: pack,
+ maskAllInputs: true,
});
}
+ shouldSplitEventsList = (nextEventString: string): boolean => {
+ const sizeOfNextEvent = new Blob([nextEventString]).size;
+ const sizeOfEventsList = new Blob(this.events).size;
+ if (sizeOfEventsList + sizeOfNextEvent >= this.maxPersistedEventsSize) {
+ return true;
+ }
+ if (this.timeSinceLastSend !== null && Date.now() - this.timeSinceLastSend > this.interval) {
+ this.interval = Math.min(MAX_INTERVAL, this.interval + MIN_INTERVAL);
+ return true;
+ }
+ return false;
+ };
+
sendEventsList({ events, sequenceId, sessionId }: { events: string[]; sequenceId: number; sessionId: number }) {
this.addToQueue({
events,
@@ -131,7 +160,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
tryable.forEach((context) => {
this.queue = this.queue.concat(context);
if (context.timeout === 0) {
- this.schedule(this.config.flushIntervalMillis);
+ this.schedule(0);
return;
}
@@ -286,6 +315,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
if (err) {
this.config.loggerProvider.error(err);
} else if (success) {
+ this.timeSinceLastSend = Date.now();
this.config.loggerProvider.log(success);
}
}
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index f0bd54a48..82bc720d3 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -26,11 +26,14 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
retryTimeout: number;
events: Events;
currentSequenceId: number;
+ interval: number;
queue: SessionReplayContext[];
+ timeSinceLastSend: number | null;
stopRecordingEvents: ReturnType | null;
maxPersistedEventsSize: number;
emptyStoreAndReset: () => Promise;
recordEvents: () => void;
+ shouldSplitEventsList: (nextEventString: string) => boolean;
sendEventsList: ({
events,
sequenceId,
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 0c1f3d4d4..2841b3d62 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -3,7 +3,6 @@ import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-commo
import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
-import { shouldSplitEventsList } from '../src/helpers';
import { SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from '../src/messages';
import { sessionReplayPlugin } from '../src/session-replay';
@@ -206,8 +205,6 @@ describe('SessionReplayPlugin', () => {
test('should restart recording events when session_start fires', async () => {
const sessionReplay = sessionReplayPlugin();
- const mockStopRecordingEvents = jest.fn();
- record.mockReturnValue(mockStopRecordingEvents);
const mockGetResolution = Promise.resolve({});
get.mockReturnValueOnce(mockGetResolution);
await sessionReplay.setup(mockConfig);
@@ -217,12 +214,13 @@ describe('SessionReplayPlugin', () => {
event_type: 'session_start',
};
await sessionReplay.execute(event);
- expect(mockStopRecordingEvents).toHaveBeenCalledTimes(1);
expect(record).toHaveBeenCalledTimes(2);
});
- test('should send the current events list when session_end fires', async () => {
+ test('should send the current events list when session_end fires and stop recording events', async () => {
const sessionReplay = sessionReplayPlugin();
+ const mockStopRecordingEvents = jest.fn();
+ sessionReplay.stopRecordingEvents = mockStopRecordingEvents;
sessionReplay.config = mockConfig;
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
@@ -232,6 +230,7 @@ describe('SessionReplayPlugin', () => {
};
sessionReplay.events = [mockEventString];
await sessionReplay.execute(event);
+ expect(mockStopRecordingEvents).toHaveBeenCalledTimes(1);
jest.runAllTimers();
expect(send).toHaveBeenCalledTimes(1);
@@ -247,24 +246,78 @@ describe('SessionReplayPlugin', () => {
});
describe('recordEvents', () => {
+ test('should send the first emitted event immediately', () => {
+ const sessionReplay = sessionReplayPlugin();
+ const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
+ sessionReplay.config = mockConfig;
+ sessionReplay.recordEvents();
+ expect(sessionReplay.events).toEqual([]);
+ const recordArg = record.mock.calls[0][0];
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(sessionReplay.events).toEqual([]);
+ jest.runAllTimers();
+ // Should not store events in IDB
+ expect(update).not.toHaveBeenCalled();
+ expect(send).toHaveBeenCalledTimes(1);
+ expect(send.mock.calls[0][0]).toEqual({
+ events: [mockEventString],
+ sequenceId: 0,
+ attempts: 1,
+ timeout: 0,
+ sessionId: 123,
+ });
+ });
+
test('should store events in class and in IDB', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.recordEvents();
expect(sessionReplay.events).toEqual([]);
const recordArg = record.mock.calls[0][0];
+ // Emit first event, which gets sent immediately
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ // Emit second event, which is stored in class and IDB
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sessionReplay.events).toEqual([mockEventString]);
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
123: {
events: [mockEventString],
- sequenceId: 0,
+ sequenceId: 1,
},
});
});
- test('should split the events list and send', () => {
+ test('should split the events list at an increasing interval and send', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.recordEvents();
+ sessionReplay.timeSinceLastSend = 1;
+ const dateNowMock = jest.spyOn(Date, 'now').mockReturnValue(1);
+ const sendEventsList = jest.spyOn(sessionReplay, 'sendEventsList');
+ const recordArg = record.mock.calls[0][0];
+ // Emit first event, which gets sent immediately
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(sendEventsList).toHaveBeenCalledTimes(1);
+ sendEventsList.mockClear();
+ // Emit second event, which is not sent immediately
+ console.log('before second event');
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(sendEventsList).toHaveBeenCalledTimes(0);
+ // Emit third event and advance timers to interval
+ dateNowMock.mockReturnValue(1002);
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(sendEventsList).toHaveBeenCalledTimes(1);
+ expect(sendEventsList).toHaveBeenCalledWith({
+ events: [mockEventString],
+ sequenceId: 1,
+ sessionId: 123,
+ });
+ expect(sessionReplay.events).toEqual([mockEventString]);
+ dateNowMock.mockClear();
+ });
+
+ test('should split the events list at max size and send', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.maxPersistedEventsSize = 20;
@@ -309,6 +362,7 @@ describe('SessionReplayPlugin', () => {
};
sessionReplay.addToQueue(context);
expect(schedule).toHaveBeenCalledTimes(1);
+ expect(schedule).toHaveBeenCalledWith(0);
expect(context.attempts).toBe(1);
});
@@ -769,17 +823,62 @@ describe('SessionReplayPlugin', () => {
});
describe('shouldSplitEventsList', () => {
- test('shouldSplitEventsList should return true if size of events list plus size of next event is over the max size', () => {
- const eventsList = ['#'.repeat(20)];
- const nextEvent = 'a';
- const result = shouldSplitEventsList(eventsList, nextEvent, 20);
- expect(result).toBe(true);
- });
- test('shouldSplitEventsList should return false if size of events list plus size of next event is under the max size', () => {
- const eventsList = ['#'.repeat(20)];
- const nextEvent = 'a';
- const result = shouldSplitEventsList(eventsList, nextEvent, 22);
- expect(result).toBe(false);
+ describe('event list size', () => {
+ test('should return true if size of events list plus size of next event is over the max size', () => {
+ const sessionReplay = sessionReplayPlugin();
+ const eventsList = ['#'.repeat(20)];
+ sessionReplay.events = eventsList;
+ sessionReplay.maxPersistedEventsSize = 20;
+ const nextEvent = 'a';
+ const result = sessionReplay.shouldSplitEventsList(nextEvent);
+ expect(result).toBe(true);
+ });
+ test('should return false if size of events list plus size of next event is under the max size', () => {
+ const sessionReplay = sessionReplayPlugin();
+ const eventsList = ['#'.repeat(20)];
+ sessionReplay.events = eventsList;
+ sessionReplay.maxPersistedEventsSize = 22;
+ const nextEvent = 'a';
+ const result = sessionReplay.shouldSplitEventsList(nextEvent);
+ expect(result).toBe(false);
+ });
+ });
+ describe('interval', () => {
+ test('should return false if timeSinceLastSend is null', () => {
+ const sessionReplay = sessionReplayPlugin();
+ const nextEvent = 'a';
+ const result = sessionReplay.shouldSplitEventsList(nextEvent);
+ expect(result).toBe(false);
+ });
+ test('should return false if it has not been long enough since last send', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.timeSinceLastSend = 1;
+ jest.spyOn(Date, 'now').mockReturnValue(2);
+ const nextEvent = 'a';
+ const result = sessionReplay.shouldSplitEventsList(nextEvent);
+ expect(result).toBe(false);
+ });
+ test('should return true if it has been long enough since last send', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.timeSinceLastSend = 1;
+ jest.spyOn(Date, 'now').mockReturnValue(1002);
+ const nextEvent = 'a';
+ const result = sessionReplay.shouldSplitEventsList(nextEvent);
+ expect(result).toBe(true);
+ });
+ test('should increase interval incrementally', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.timeSinceLastSend = 1;
+ jest.spyOn(Date, 'now').mockReturnValue(1000000);
+ const nextEvent = 'a';
+ for (let i = 1; i <= 10; i++) {
+ expect(sessionReplay.interval).toEqual(1000 * i);
+ sessionReplay.shouldSplitEventsList(nextEvent);
+ }
+ // Call one more time to go over the 10 max
+ sessionReplay.shouldSplitEventsList(nextEvent);
+ expect(sessionReplay.interval).toEqual(10000);
+ });
});
});
});
From fc263ed34a955f0bc987cf7281a374065f95045c Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 6 Jul 2023 15:54:44 -0400
Subject: [PATCH 051/214] fix(plugins): remove console log
---
packages/plugin-session-replay-browser/src/session-replay.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 5e75ba438..aa747c57f 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -91,7 +91,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
recordEvents() {
this.stopRecordingEvents = record({
emit: (event) => {
- console.log('this.events', this.events);
const eventString = JSON.stringify(event);
// Send the first recorded event immediately
if (!this.events.length && this.currentSequenceId === 0) {
From 721a3997673d700b6bda9302b076be0f3fab7c09 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 6 Jul 2023 16:06:28 -0400
Subject: [PATCH 052/214] fix(plugins): need to send first two events
immediately for replayer to work
---
.../src/session-replay.ts | 7 +++--
.../test/session-replay.test.ts | 29 ++++++++++---------
2 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index aa747c57f..650345273 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -92,13 +92,14 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.stopRecordingEvents = record({
emit: (event) => {
const eventString = JSON.stringify(event);
- // Send the first recorded event immediately
- if (!this.events.length && this.currentSequenceId === 0) {
+ // Send the first two recorded events immediately
+ if (this.events.length === 1 && this.currentSequenceId === 0) {
this.sendEventsList({
- events: [eventString],
+ events: this.events.concat(eventString),
sequenceId: this.currentSequenceId,
sessionId: this.config.sessionId as number,
});
+ this.events = [];
this.currentSequenceId++;
return;
}
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 2841b3d62..792df9fa1 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -246,7 +246,7 @@ describe('SessionReplayPlugin', () => {
});
describe('recordEvents', () => {
- test('should send the first emitted event immediately', () => {
+ test('should send the first two emitted events immediately', () => {
const sessionReplay = sessionReplayPlugin();
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
sessionReplay.config = mockConfig;
@@ -254,13 +254,12 @@ describe('SessionReplayPlugin', () => {
expect(sessionReplay.events).toEqual([]);
const recordArg = record.mock.calls[0][0];
recordArg?.emit && recordArg?.emit(mockEvent);
+ recordArg?.emit && recordArg?.emit(mockEvent);
expect(sessionReplay.events).toEqual([]);
jest.runAllTimers();
- // Should not store events in IDB
- expect(update).not.toHaveBeenCalled();
expect(send).toHaveBeenCalledTimes(1);
expect(send.mock.calls[0][0]).toEqual({
- events: [mockEventString],
+ events: [mockEventString, mockEventString],
sequenceId: 0,
attempts: 1,
timeout: 0,
@@ -274,13 +273,14 @@ describe('SessionReplayPlugin', () => {
sessionReplay.recordEvents();
expect(sessionReplay.events).toEqual([]);
const recordArg = record.mock.calls[0][0];
- // Emit first event, which gets sent immediately
+ // Emit first two events, which get sent immediately
recordArg?.emit && recordArg?.emit(mockEvent);
- // Emit second event, which is stored in class and IDB
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ // Emit third event, which is stored in class and IDB
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sessionReplay.events).toEqual([mockEventString]);
- expect(update).toHaveBeenCalledTimes(1);
- expect(update.mock.calls[0][1]({})).toEqual({
+ expect(update).toHaveBeenCalledTimes(2);
+ expect(update.mock.calls[1][1]({})).toEqual({
123: {
events: [mockEventString],
sequenceId: 1,
@@ -296,15 +296,15 @@ describe('SessionReplayPlugin', () => {
const dateNowMock = jest.spyOn(Date, 'now').mockReturnValue(1);
const sendEventsList = jest.spyOn(sessionReplay, 'sendEventsList');
const recordArg = record.mock.calls[0][0];
- // Emit first event, which gets sent immediately
+ // Emit first two events, which get sent immediately
+ recordArg?.emit && recordArg?.emit(mockEvent);
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(1);
sendEventsList.mockClear();
- // Emit second event, which is not sent immediately
- console.log('before second event');
+ // Emit third event, which is not sent immediately
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(0);
- // Emit third event and advance timers to interval
+ // Emit fourth event and advance timers to interval
dateNowMock.mockReturnValue(1002);
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(1);
@@ -321,6 +321,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.maxPersistedEventsSize = 20;
+ sessionReplay.currentSequenceId = 1;
const events = ['#'.repeat(20)];
sessionReplay.events = events;
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -333,7 +334,7 @@ describe('SessionReplayPlugin', () => {
expect(sendEventsListMock).toHaveBeenCalledTimes(1);
expect(sendEventsListMock).toHaveBeenCalledWith({
events,
- sequenceId: 0,
+ sequenceId: 1,
sessionId: 123,
});
@@ -342,7 +343,7 @@ describe('SessionReplayPlugin', () => {
expect(update.mock.calls[0][1]({})).toEqual({
123: {
events: [mockEventString],
- sequenceId: 1,
+ sequenceId: 2,
},
});
});
From 59802fbc0c949373a2fd3566ed3db6a871e442a4 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 10 Jul 2023 09:41:57 -0400
Subject: [PATCH 053/214] fix(plugins): pr feedback
---
.../plugin-session-replay-browser/package.json | 5 ++---
.../src/session-replay.ts | 12 +++++++++---
.../src/typings/session-replay.ts | 2 +-
.../test/session-replay.test.ts | 14 +++++++++-----
4 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 73130a832..a763c83c4 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -37,14 +37,13 @@
"url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
},
"dependencies": {
- "@amplitude/analytics-client-common": "^1.1.1",
- "@amplitude/analytics-types": "^1.3.1",
+ "@amplitude/analytics-types": ">=1 <3",
"idb-keyval": "^6.2.1",
"rrweb": "^2.0.0-alpha.4",
"tslib": "^2.4.1"
},
"devDependencies": {
- "@amplitude/analytics-browser": "^1.12.1",
+ "@amplitude/analytics-client-common": ">=1 <3",
"@rollup/plugin-commonjs": "^23.0.4",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-typescript": "^10.0.1",
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 650345273..333d6529a 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -35,7 +35,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
stopRecordingEvents: ReturnType | null = null;
maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
interval = MIN_INTERVAL;
- timeSinceLastSend: number | null = null;
+ timeAtLastSend: number | null = null;
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
@@ -122,13 +122,19 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
});
}
+ /**
+ * Determines whether to send the events list to the backend and start a new
+ * empty events list, based on the size of the list as well as the last time sent
+ * @param nextEventString
+ * @returns boolean
+ */
shouldSplitEventsList = (nextEventString: string): boolean => {
const sizeOfNextEvent = new Blob([nextEventString]).size;
const sizeOfEventsList = new Blob(this.events).size;
if (sizeOfEventsList + sizeOfNextEvent >= this.maxPersistedEventsSize) {
return true;
}
- if (this.timeSinceLastSend !== null && Date.now() - this.timeSinceLastSend > this.interval) {
+ if (this.timeAtLastSend !== null && Date.now() - this.timeAtLastSend > this.interval) {
this.interval = Math.min(MAX_INTERVAL, this.interval + MIN_INTERVAL);
return true;
}
@@ -315,7 +321,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
if (err) {
this.config.loggerProvider.error(err);
} else if (success) {
- this.timeSinceLastSend = Date.now();
+ this.timeAtLastSend = Date.now();
this.config.loggerProvider.log(success);
}
}
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 82bc720d3..0085e319a 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -28,7 +28,7 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
currentSequenceId: number;
interval: number;
queue: SessionReplayContext[];
- timeSinceLastSend: number | null;
+ timeAtLastSend: number | null;
stopRecordingEvents: ReturnType | null;
maxPersistedEventsSize: number;
emptyStoreAndReset: () => Promise;
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 792df9fa1..03b044688 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -251,10 +251,14 @@ describe('SessionReplayPlugin', () => {
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
sessionReplay.config = mockConfig;
sessionReplay.recordEvents();
+ // Confirm that no events have been set in events list yet
expect(sessionReplay.events).toEqual([]);
const recordArg = record.mock.calls[0][0];
+ // Emit two events manually (replicating what rrweb would do itself)
recordArg?.emit && recordArg?.emit(mockEvent);
recordArg?.emit && recordArg?.emit(mockEvent);
+ // Confirm that there are still no events in the events list
+ // (because they are sent immediately instead of stored)
expect(sessionReplay.events).toEqual([]);
jest.runAllTimers();
expect(send).toHaveBeenCalledTimes(1);
@@ -292,7 +296,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.recordEvents();
- sessionReplay.timeSinceLastSend = 1;
+ sessionReplay.timeAtLastSend = 1;
const dateNowMock = jest.spyOn(Date, 'now').mockReturnValue(1);
const sendEventsList = jest.spyOn(sessionReplay, 'sendEventsList');
const recordArg = record.mock.calls[0][0];
@@ -845,7 +849,7 @@ describe('SessionReplayPlugin', () => {
});
});
describe('interval', () => {
- test('should return false if timeSinceLastSend is null', () => {
+ test('should return false if timeAtLastSend is null', () => {
const sessionReplay = sessionReplayPlugin();
const nextEvent = 'a';
const result = sessionReplay.shouldSplitEventsList(nextEvent);
@@ -853,7 +857,7 @@ describe('SessionReplayPlugin', () => {
});
test('should return false if it has not been long enough since last send', () => {
const sessionReplay = sessionReplayPlugin();
- sessionReplay.timeSinceLastSend = 1;
+ sessionReplay.timeAtLastSend = 1;
jest.spyOn(Date, 'now').mockReturnValue(2);
const nextEvent = 'a';
const result = sessionReplay.shouldSplitEventsList(nextEvent);
@@ -861,7 +865,7 @@ describe('SessionReplayPlugin', () => {
});
test('should return true if it has been long enough since last send', () => {
const sessionReplay = sessionReplayPlugin();
- sessionReplay.timeSinceLastSend = 1;
+ sessionReplay.timeAtLastSend = 1;
jest.spyOn(Date, 'now').mockReturnValue(1002);
const nextEvent = 'a';
const result = sessionReplay.shouldSplitEventsList(nextEvent);
@@ -869,7 +873,7 @@ describe('SessionReplayPlugin', () => {
});
test('should increase interval incrementally', () => {
const sessionReplay = sessionReplayPlugin();
- sessionReplay.timeSinceLastSend = 1;
+ sessionReplay.timeAtLastSend = 1;
jest.spyOn(Date, 'now').mockReturnValue(1000000);
const nextEvent = 'a';
for (let i = 1; i <= 10; i++) {
From ee064fe94bd1f5c24f106c70b14cbfca2cda1364 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 11 Jul 2023 21:35:49 +0000
Subject: [PATCH 054/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.1.2
---
.../plugin-session-replay-browser/CHANGELOG.md | 18 ++++++++++++++++++
.../plugin-session-replay-browser/package.json | 2 +-
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 6411fb68e..9f302ee2f 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.1.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.1...@amplitude/plugin-session-replay-browser@0.1.2) (2023-07-11)
+
+### Bug Fixes
+
+- **plugins:** need to send first two events immediately for replayer to work
+ ([721a399](https://github.com/amplitude/Amplitude-TypeScript/commit/721a3997673d700b6bda9302b076be0f3fab7c09))
+- **plugins:** pr feedback
+ ([59802fb](https://github.com/amplitude/Amplitude-TypeScript/commit/59802fbc0c949373a2fd3566ed3db6a871e442a4))
+- **plugins:** remove console log
+ ([fc263ed](https://github.com/amplitude/Amplitude-TypeScript/commit/fc263ed34a955f0bc987cf7281a374065f95045c))
+- **plugins:** update timing of sending batches of session replay events
+ ([fadd7c0](https://github.com/amplitude/Amplitude-TypeScript/commit/fadd7c0cd24f78c6dd317098fe6dfad951ce206e))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.1.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.0...@amplitude/plugin-session-replay-browser@0.1.1) (2023-07-05)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index a763c83c4..1f21c59a1 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.1.1",
+ "version": "0.1.2",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 1a55aeede742f688f87f128a13cf41f91bda5224 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 13 Jul 2023 09:29:38 -0400
Subject: [PATCH 055/214] fix(plugins): change timing of record upon
initialization for session replay plugin
---
packages/plugin-session-replay-browser/src/session-replay.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 333d6529a..a5a6669ab 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -84,8 +84,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
void this.storeEventsForSession([], this.currentSequenceId);
- this.recordEvents();
}
+ this.recordEvents();
}
recordEvents() {
From 708d4b94d0d8714b35fe3984e604327e34b446f0 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 13 Jul 2023 09:32:15 -0400
Subject: [PATCH 056/214] fix(plugins): should only initiate recording on
session start if recording is not initiated
---
packages/plugin-session-replay-browser/src/session-replay.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index a5a6669ab..99c2913db 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -50,7 +50,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
...event.event_properties,
[DEFAULT_SESSION_REPLAY_PROPERTY]: true,
};
- if (event.event_type === DEFAULT_SESSION_START_EVENT) {
+ if (event.event_type === DEFAULT_SESSION_START_EVENT && !this.stopRecordingEvents) {
this.recordEvents();
} else if (event.event_type === DEFAULT_SESSION_END_EVENT) {
if (event.session_id) {
From 392e3d9b080374d805e32d9e8a99f92175ea2218 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Thu, 13 Jul 2023 17:37:34 +0000
Subject: [PATCH 057/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.1.3
---
.../plugin-session-replay-browser/CHANGELOG.md | 14 ++++++++++++++
.../plugin-session-replay-browser/package.json | 2 +-
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 9f302ee2f..dacc6d34f 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.1.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.2...@amplitude/plugin-session-replay-browser@0.1.3) (2023-07-13)
+
+### Bug Fixes
+
+- **plugins:** change timing of record upon initialization for session replay plugin
+ ([1a55aee](https://github.com/amplitude/Amplitude-TypeScript/commit/1a55aeede742f688f87f128a13cf41f91bda5224))
+- **plugins:** should only initiate recording on session start if recording is not initiated
+ ([708d4b9](https://github.com/amplitude/Amplitude-TypeScript/commit/708d4b94d0d8714b35fe3984e604327e34b446f0))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.1.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.1...@amplitude/plugin-session-replay-browser@0.1.2) (2023-07-11)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 1f21c59a1..d7e7203c0 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.1.2",
+ "version": "0.1.3",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From d2e9a0cc7c455dcdf13ea2fa303d2fbd50911536 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 19 Jul 2023 09:54:48 -0400
Subject: [PATCH 058/214] feat(plugins): solve timing issues with multiple tabs
---
.../src/session-replay.ts | 90 +++++++++++++------
.../src/typings/session-replay.ts | 2 +-
2 files changed, 62 insertions(+), 30 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 99c2913db..a631c4255 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -1,7 +1,8 @@
+import { getGlobalScope } from '@amplitude/analytics-client-common';
import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
-import { pack, record } from 'rrweb';
+import { record } from 'rrweb';
import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import {
@@ -16,7 +17,7 @@ const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/tra
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-const MIN_INTERVAL = 1 * 1000; // 1 second
+// const MIN_INTERVAL = 1 * 1000; // 1 second
const MAX_INTERVAL = 10 * 1000; // 10 seconds
class SessionReplay implements SessionReplayEnrichmentPlugin {
@@ -34,7 +35,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
queue: SessionReplayContext[] = [];
stopRecordingEvents: ReturnType | null = null;
maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
- interval = MIN_INTERVAL;
+ interval = MAX_INTERVAL;
timeAtLastSend: number | null = null;
async setup(config: BrowserConfig) {
@@ -43,6 +44,37 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
void this.emptyStoreAndReset();
+ this.timeAtLastSend = Date.now();
+
+ const GlobalScope = getGlobalScope();
+ if (GlobalScope && GlobalScope.window) {
+ GlobalScope.window.addEventListener('blur', () => {
+ // console.log('blur - sending')
+ console.log('blur - recording stopped', this.stopRecordingEvents);
+ this.stopRecordingEvents && this.stopRecordingEvents();
+ this.stopRecordingEvents = null;
+ });
+ GlobalScope.window.addEventListener('focus', () => {
+ console.log(
+ 'focus',
+ 'this.stopRecordingEvents',
+ this.stopRecordingEvents,
+ 'config.sessionId',
+ config.sessionId,
+ );
+ void this.getAllSessionEventsFromStore().then((storedReplaySessions) => {
+ console.log('storedReplaySessions', storedReplaySessions);
+ if (storedReplaySessions && storedReplaySessions[config.sessionId as number]) {
+ this.events = storedReplaySessions[config.sessionId as number].events;
+ this.currentSequenceId = storedReplaySessions[config.sessionId as number].sequenceId;
+ this.timeAtLastSend = Date.now();
+ }
+ if (!this.stopRecordingEvents) {
+ this.recordEvents();
+ }
+ });
+ });
+ }
}
async execute(event: Event) {
@@ -59,8 +91,10 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
sequenceId: this.currentSequenceId,
sessionId: event.session_id,
});
+ void this.removeSessionEventsStore(event.session_id);
}
this.stopRecordingEvents && this.stopRecordingEvents();
+ this.stopRecordingEvents = null;
this.events = [];
this.currentSequenceId = 0;
}
@@ -70,9 +104,17 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
async emptyStoreAndReset() {
const storedReplaySessions = await this.getAllSessionEventsFromStore();
if (storedReplaySessions) {
+ console.log('storedReplaySessions', storedReplaySessions);
for (const sessionId in storedReplaySessions) {
const storedReplayEvents = storedReplaySessions[sessionId];
if (storedReplayEvents.events.length) {
+ console.log(
+ 'sending from empty store',
+ 'events',
+ storedReplayEvents.events.length,
+ 'storedReplayEvents.sequenceId',
+ storedReplayEvents.sequenceId,
+ );
this.sendEventsList({
events: storedReplayEvents.events,
sequenceId: storedReplayEvents.sequenceId,
@@ -83,26 +125,18 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.events = [];
const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
- void this.storeEventsForSession([], this.currentSequenceId);
+ void this.storeEventsForSession([], this.currentSequenceId, this.config.sessionId as number);
+ }
+ if (!this.stopRecordingEvents) {
+ this.recordEvents();
}
- this.recordEvents();
}
recordEvents() {
this.stopRecordingEvents = record({
emit: (event) => {
+ console.log('recording event', event);
const eventString = JSON.stringify(event);
- // Send the first two recorded events immediately
- if (this.events.length === 1 && this.currentSequenceId === 0) {
- this.sendEventsList({
- events: this.events.concat(eventString),
- sequenceId: this.currentSequenceId,
- sessionId: this.config.sessionId as number,
- });
- this.events = [];
- this.currentSequenceId++;
- return;
- }
const shouldSplit = this.shouldSplitEventsList(eventString);
if (shouldSplit) {
@@ -115,9 +149,9 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.currentSequenceId++;
}
this.events.push(eventString);
- void this.storeEventsForSession(this.events, this.currentSequenceId);
+ void this.storeEventsForSession(this.events, this.currentSequenceId, this.config.sessionId as number);
},
- packFn: pack,
+ // packFn: pack,
maskAllInputs: true,
});
}
@@ -134,8 +168,9 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
if (sizeOfEventsList + sizeOfNextEvent >= this.maxPersistedEventsSize) {
return true;
}
- if (this.timeAtLastSend !== null && Date.now() - this.timeAtLastSend > this.interval) {
- this.interval = Math.min(MAX_INTERVAL, this.interval + MIN_INTERVAL);
+ if (this.timeAtLastSend !== null && Date.now() - this.timeAtLastSend > this.interval && this.events.length) {
+ // this.interval = Math.min(MAX_INTERVAL, this.interval + MIN_INTERVAL);
+ this.timeAtLastSend = Date.now();
return true;
}
return false;
@@ -277,17 +312,15 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
return undefined;
}
- async storeEventsForSession(events: Events, sequenceId: number) {
+ async storeEventsForSession(events: Events, sequenceId: number, sessionId: number) {
try {
await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
return {
...sessionMap,
- ...(this.config.sessionId && {
- [this.config.sessionId]: {
- events: events,
- sequenceId,
- },
- }),
+ [sessionId]: {
+ events: events,
+ sequenceId,
+ },
};
});
} catch (e) {
@@ -317,11 +350,10 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
success?: string;
removeEvents?: boolean;
}) {
- removeEvents && context.sessionId && this.removeSessionEventsStore(context.sessionId);
+ removeEvents && context.sessionId && this.storeEventsForSession([], context.sequenceId, context.sessionId);
if (err) {
this.config.loggerProvider.error(err);
} else if (success) {
- this.timeAtLastSend = Date.now();
this.config.loggerProvider.log(success);
}
}
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 0085e319a..1fa822259 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -59,7 +59,7 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
removeEvents?: boolean | undefined;
}): void;
getAllSessionEventsFromStore: () => Promise;
- storeEventsForSession: (events: Events, sequenceId: number) => Promise;
+ storeEventsForSession: (events: Events, sequenceId: number, sessionId: number) => Promise;
removeSessionEventsStore: (sessionId: number) => Promise;
}
From b73a4d81a74ed7adcdbd3b16ed2b4eab8d64bd05 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 20 Jul 2023 12:34:32 -0400
Subject: [PATCH 059/214] test(plugins): update tests for session replay
---
.../src/session-replay.ts | 141 +++--
.../src/typings/session-replay.ts | 19 +-
.../test/session-replay.test.ts | 580 ++++++++++++++----
3 files changed, 554 insertions(+), 186 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index a631c4255..ffcc7e669 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -2,12 +2,13 @@ import { getGlobalScope } from '@amplitude/analytics-client-common';
import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
-import { record } from 'rrweb';
+import { pack, record } from 'rrweb';
import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
import {
Events,
IDBStore,
+ RecordingStatus,
SessionReplayContext,
SessionReplayEnrichmentPlugin,
SessionReplayPlugin,
@@ -17,7 +18,7 @@ const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/tra
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-// const MIN_INTERVAL = 1 * 1000; // 1 second
+const MIN_INTERVAL = 1 * 1000; // 1 second
const MAX_INTERVAL = 10 * 1000; // 10 seconds
class SessionReplay implements SessionReplayEnrichmentPlugin {
@@ -35,7 +36,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
queue: SessionReplayContext[] = [];
stopRecordingEvents: ReturnType | null = null;
maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
- interval = MAX_INTERVAL;
+ interval = MIN_INTERVAL;
timeAtLastSend: number | null = null;
async setup(config: BrowserConfig) {
@@ -43,36 +44,16 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
- void this.emptyStoreAndReset();
- this.timeAtLastSend = Date.now();
+ await this.initialize(true);
const GlobalScope = getGlobalScope();
if (GlobalScope && GlobalScope.window) {
GlobalScope.window.addEventListener('blur', () => {
- // console.log('blur - sending')
- console.log('blur - recording stopped', this.stopRecordingEvents);
this.stopRecordingEvents && this.stopRecordingEvents();
this.stopRecordingEvents = null;
});
GlobalScope.window.addEventListener('focus', () => {
- console.log(
- 'focus',
- 'this.stopRecordingEvents',
- this.stopRecordingEvents,
- 'config.sessionId',
- config.sessionId,
- );
- void this.getAllSessionEventsFromStore().then((storedReplaySessions) => {
- console.log('storedReplaySessions', storedReplaySessions);
- if (storedReplaySessions && storedReplaySessions[config.sessionId as number]) {
- this.events = storedReplaySessions[config.sessionId as number].events;
- this.currentSequenceId = storedReplaySessions[config.sessionId as number].sequenceId;
- this.timeAtLastSend = Date.now();
- }
- if (!this.stopRecordingEvents) {
- this.recordEvents();
- }
- });
+ void this.initialize();
});
}
}
@@ -91,7 +72,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
sequenceId: this.currentSequenceId,
sessionId: event.session_id,
});
- void this.removeSessionEventsStore(event.session_id);
}
this.stopRecordingEvents && this.stopRecordingEvents();
this.stopRecordingEvents = null;
@@ -101,41 +81,57 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
return Promise.resolve(event);
}
- async emptyStoreAndReset() {
+ async initialize(shouldSendStoredEvents = false) {
+ this.timeAtLastSend = Date.now(); // Initialize this so we have a point of comparison when events are recorded
+ if (!this.config.sessionId) {
+ return;
+ }
const storedReplaySessions = await this.getAllSessionEventsFromStore();
- if (storedReplaySessions) {
- console.log('storedReplaySessions', storedReplaySessions);
- for (const sessionId in storedReplaySessions) {
- const storedReplayEvents = storedReplaySessions[sessionId];
- if (storedReplayEvents.events.length) {
- console.log(
- 'sending from empty store',
- 'events',
- storedReplayEvents.events.length,
- 'storedReplayEvents.sequenceId',
- storedReplayEvents.sequenceId,
- );
- this.sendEventsList({
- events: storedReplayEvents.events,
- sequenceId: storedReplayEvents.sequenceId,
- sessionId: parseInt(sessionId, 10),
- });
- }
+ const storedSequencesForSession = storedReplaySessions && storedReplaySessions[this.config.sessionId];
+ if (storedReplaySessions && storedSequencesForSession && storedSequencesForSession.sessionSequences) {
+ const storedSeqId = storedSequencesForSession.currentSequenceId;
+ const lastSequence = storedSequencesForSession.sessionSequences[storedSeqId];
+ if (lastSequence.status !== RecordingStatus.RECORDING) {
+ this.currentSequenceId = storedSeqId + 1;
+ this.events = [];
+ } else {
+ // Pick up recording where it was left off in another tab or window
+ this.currentSequenceId = storedSeqId;
+ this.events = lastSequence.events;
}
- this.events = [];
- const currentSessionStoredEvents = this.config.sessionId && storedReplaySessions[this.config.sessionId];
- this.currentSequenceId = currentSessionStoredEvents ? currentSessionStoredEvents.sequenceId + 1 : 0;
- void this.storeEventsForSession([], this.currentSequenceId, this.config.sessionId as number);
+ }
+ if (shouldSendStoredEvents && storedReplaySessions) {
+ this.sendStoredEvents(storedReplaySessions);
}
if (!this.stopRecordingEvents) {
this.recordEvents();
}
}
+ sendStoredEvents(storedReplaySessions: IDBStore) {
+ for (const sessionId in storedReplaySessions) {
+ const storedSequences = storedReplaySessions[sessionId].sessionSequences;
+ for (const storedSeqId in storedSequences) {
+ const seq = storedSequences[storedSeqId];
+ const numericSeqId = parseInt(storedSeqId, 10);
+ const numericSessionId = parseInt(sessionId, 10);
+ if (numericSessionId === this.config.sessionId && numericSeqId === this.currentSequenceId) {
+ continue;
+ }
+ if (seq.events.length && seq.status === RecordingStatus.RECORDING) {
+ this.sendEventsList({
+ events: seq.events,
+ sequenceId: numericSeqId,
+ sessionId: numericSessionId,
+ });
+ }
+ }
+ }
+ }
+
recordEvents() {
this.stopRecordingEvents = record({
emit: (event) => {
- console.log('recording event', event);
const eventString = JSON.stringify(event);
const shouldSplit = this.shouldSplitEventsList(eventString);
@@ -151,7 +147,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.events.push(eventString);
void this.storeEventsForSession(this.events, this.currentSequenceId, this.config.sessionId as number);
},
- // packFn: pack,
+ packFn: pack,
maskAllInputs: true,
});
}
@@ -169,7 +165,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
return true;
}
if (this.timeAtLastSend !== null && Date.now() - this.timeAtLastSend > this.interval && this.events.length) {
- // this.interval = Math.min(MAX_INTERVAL, this.interval + MIN_INTERVAL);
+ this.interval = Math.min(MAX_INTERVAL, this.interval + MIN_INTERVAL);
this.timeAtLastSend = Date.now();
return true;
}
@@ -314,12 +310,26 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
async storeEventsForSession(events: Events, sequenceId: number, sessionId: number) {
try {
- await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore | undefined): IDBStore => {
+ await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
+ const session = sessionMap[sessionId] || {
+ currentSequenceId: 0,
+ sessionSequences: [],
+ };
+ session.currentSequenceId = sequenceId;
+
+ const currentSequence = (session.sessionSequences && session.sessionSequences[sequenceId]) || {};
+
+ currentSequence.events = events;
+ currentSequence.status = RecordingStatus.RECORDING;
+
return {
...sessionMap,
[sessionId]: {
- events: events,
- sequenceId,
+ ...session,
+ sessionSequences: {
+ ...session.sessionSequences,
+ [sequenceId]: currentSequence,
+ },
},
};
});
@@ -328,10 +338,25 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
}
- async removeSessionEventsStore(sessionId: number) {
+ async cleanUpSessionEventsStore(sessionId: number, sequenceId: number) {
try {
await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
- delete sessionMap[sessionId];
+ const session = sessionMap[sessionId];
+ const sequenceToUpdate = session?.sessionSequences && session.sessionSequences[sequenceId];
+ if (!sequenceToUpdate) {
+ return sessionMap;
+ }
+
+ sequenceToUpdate.events = [];
+ sequenceToUpdate.status = RecordingStatus.SENT;
+
+ Object.entries(session.sessionSequences).forEach(([storedSeqId, sequence]) => {
+ const numericStoredSeqId = parseInt(storedSeqId, 10);
+ if (sequence.status === RecordingStatus.SENT && sequenceId !== numericStoredSeqId) {
+ delete session.sessionSequences[numericStoredSeqId];
+ }
+ });
+
return sessionMap;
});
} catch (e) {
@@ -350,7 +375,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
success?: string;
removeEvents?: boolean;
}) {
- removeEvents && context.sessionId && this.storeEventsForSession([], context.sequenceId, context.sessionId);
+ removeEvents && context.sessionId && this.cleanUpSessionEventsStore(context.sessionId, context.sequenceId);
if (err) {
this.config.loggerProvider.error(err);
} else if (success) {
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 1fa822259..823999f7d 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -14,10 +14,21 @@ export interface SessionReplayContext {
sessionId: number;
}
+export enum RecordingStatus {
+ RECORDING = 'recording',
+ SENDING = 'sending',
+ SENT = 'sent',
+}
+
+export interface IDBStoreSequence {
+ events: Events;
+ status: RecordingStatus;
+}
+
export interface IDBStore {
[sessionId: number]: {
- events: Events;
- sequenceId: number;
+ currentSequenceId: number;
+ sessionSequences: { [sequenceId: number]: IDBStoreSequence };
};
}
export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
@@ -31,7 +42,7 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
timeAtLastSend: number | null;
stopRecordingEvents: ReturnType | null;
maxPersistedEventsSize: number;
- emptyStoreAndReset: () => Promise;
+ initialize: (shouldSendStoredEvents?: boolean) => Promise;
recordEvents: () => void;
shouldSplitEventsList: (nextEventString: string) => boolean;
sendEventsList: ({
@@ -60,7 +71,7 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
}): void;
getAllSessionEventsFromStore: () => Promise;
storeEventsForSession: (events: Events, sequenceId: number, sessionId: number) => Promise;
- removeSessionEventsStore: (sessionId: number) => Promise;
+ cleanUpSessionEventsStore: (sessionId: number, sequenceId: number) => Promise;
}
export interface SessionReplayPlugin {
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 03b044688..01e412277 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -1,10 +1,12 @@
/* eslint-disable jest/expect-expect */
+import * as AnalyticsClientCommon from '@amplitude/analytics-client-common';
import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
import { SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from '../src/messages';
import { sessionReplayPlugin } from '../src/session-replay';
+import { IDBStore, RecordingStatus } from '../src/typings/session-replay';
jest.mock('idb-keyval');
type MockedIDBKeyVal = jest.Mocked;
@@ -42,6 +44,7 @@ describe('SessionReplayPlugin', () => {
const { get, update } = IDBKeyVal as MockedIDBKeyVal;
const { record } = RRWeb as MockedRRWeb;
let originalFetch: typeof global.fetch;
+ let addEventListenerMock: jest.Mock;
const mockConfig: BrowserConfig = {
apiKey: 'static_key',
flushIntervalMillis: 0,
@@ -85,6 +88,12 @@ describe('SessionReplayPlugin', () => {
status: 200,
}),
) as jest.Mock;
+ addEventListenerMock = jest.fn() as typeof addEventListenerMock;
+ jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValueOnce({
+ window: {
+ addEventListener: addEventListenerMock,
+ } as unknown as Window,
+ } as Window & typeof globalThis);
});
afterEach(() => {
jest.clearAllMocks();
@@ -105,31 +114,81 @@ describe('SessionReplayPlugin', () => {
expect(sessionReplay.storageKey).toBe('AMP_replay_unsent_static_key');
});
- test('should read events from storage and send them, then reset storage for session', async () => {
+ test('should call initalize with shouldSendStoredEvents=true', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const initalize = jest.spyOn(sessionReplay, 'initialize').mockReturnValueOnce(Promise.resolve());
+ await sessionReplay.setup(mockConfig);
+
+ expect(initalize).toHaveBeenCalledTimes(1);
+
+ expect(initalize.mock.calls[0]).toEqual([true]);
+ });
+ test('should set up blur and focus event listeners', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const stopRecordingMock = jest.fn();
+ sessionReplay.stopRecordingEvents = stopRecordingMock;
+ const initialize = jest.spyOn(sessionReplay, 'initialize').mockReturnValueOnce(Promise.resolve());
+ await sessionReplay.setup(mockConfig);
+ initialize.mockReset();
+ expect(addEventListenerMock).toHaveBeenCalledTimes(2);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(addEventListenerMock.mock.calls[0][0]).toEqual('blur');
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
+ const blurCallback = addEventListenerMock.mock.calls[0][1];
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+ blurCallback();
+ expect(stopRecordingMock).toHaveBeenCalled();
+ expect(sessionReplay.stopRecordingEvents).toEqual(null);
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(addEventListenerMock.mock.calls[1][0]).toEqual('focus');
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment
+ const focusCallback = addEventListenerMock.mock.calls[1][1];
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+ focusCallback();
+ expect(initialize).toHaveBeenCalled();
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(initialize.mock.calls[0]).toEqual([]);
+ });
+ });
+
+ describe('initalize', () => {
+ test('should read events from storage and send them if shouldSendStoredEvents is true', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
sessionId: 456,
};
- const mockGetResolution = Promise.resolve({
+ sessionReplay.config = config;
+ const mockGetResolution: Promise = Promise.resolve({
123: {
- events: [mockEventString],
- sequenceId: 3,
+ currentSequenceId: 3,
+ sessionSequences: {
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
},
456: {
- events: [mockEventString],
- sequenceId: 1,
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
},
});
get.mockReturnValueOnce(mockGetResolution);
const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
- await sessionReplay.setup(config);
+ await sessionReplay.initialize(true);
await mockGetResolution;
jest.runAllTimers();
- expect(send).toHaveBeenCalledTimes(2);
+ expect(send).toHaveBeenCalledTimes(1);
- // Sending first stored session events
+ // Should send only events from sequences marked as recording and not current session
expect(send.mock.calls[0][0]).toEqual({
attempts: 1,
events: [mockEventString],
@@ -137,48 +196,98 @@ describe('SessionReplayPlugin', () => {
sessionId: 123,
timeout: 0,
});
- // Sending second stored session events
- expect(send.mock.calls[1][0]).toEqual({
- attempts: 1,
- events: [mockEventString],
- sequenceId: 1,
+ });
+ test('should return early if using old format of IDBStore', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const config = {
+ ...mockConfig,
sessionId: 456,
- timeout: 0,
- });
-
- expect(update).toHaveBeenCalledTimes(1);
- expect(update.mock.calls[0][1]({})).toEqual({
- '456': {
- events: [],
- sequenceId: 2,
+ };
+ sessionReplay.config = config;
+ const mockGetResolution = Promise.resolve({
+ 123: {
+ events: [mockEventString],
+ sequenceId: 1,
+ },
+ 456: {
+ events: [mockEventString],
+ sequenceId: 1,
},
});
+ get.mockReturnValueOnce(mockGetResolution);
+ const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
+
+ await sessionReplay.initialize(true);
+ await mockGetResolution;
+ jest.runAllTimers();
+ expect(send).toHaveBeenCalledTimes(0);
});
- test('should handle no stored events', async () => {
+ test('should return early if session id not set', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
+ sessionId: undefined,
};
- const mockGetResolution = Promise.resolve({});
+ sessionReplay.config = config;
+ const getAllSessionEventsFromStore = jest
+ .spyOn(sessionReplay, 'getAllSessionEventsFromStore')
+ .mockReturnValueOnce(Promise.resolve({}));
+ await sessionReplay.initialize();
+ expect(getAllSessionEventsFromStore).not.toHaveBeenCalled();
+ });
+ test('should configure current sequence id and events correctly if last sequence was sending', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockGetResolution: Promise = Promise.resolve({
+ 123: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.SENDING,
+ },
+ },
+ },
+ });
get.mockReturnValueOnce(mockGetResolution);
- await sessionReplay.setup(config);
- await mockGetResolution;
- expect(sessionReplay.currentSequenceId).toBe(0);
- expect(update).toHaveBeenCalledTimes(1);
- expect(update.mock.calls[0][1]({})).toEqual({
- '123': {
- events: [],
- sequenceId: 0,
+ await sessionReplay.initialize();
+ expect(sessionReplay.currentSequenceId).toEqual(4);
+ expect(sessionReplay.events).toEqual([]);
+ });
+ test('should configure current sequence id and events correctly if last sequence was recording', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockGetResolution: Promise = Promise.resolve({
+ 123: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
},
});
+ get.mockReturnValueOnce(mockGetResolution);
+ await sessionReplay.initialize();
+ expect(sessionReplay.currentSequenceId).toEqual(3);
+ expect(sessionReplay.events).toEqual([mockEventString]);
});
- test('should record events', async () => {
+ test('should handle no stored events', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
const mockGetResolution = Promise.resolve({});
get.mockReturnValueOnce(mockGetResolution);
+ await sessionReplay.initialize();
+ expect(sessionReplay.currentSequenceId).toBe(0);
+ expect(sessionReplay.events).toEqual([]);
+ });
+ test('should record events', async () => {
const sessionReplay = sessionReplayPlugin();
- await sessionReplay.setup(mockConfig);
- await mockGetResolution;
- jest.runAllTimers();
+ sessionReplay.config = mockConfig;
+ const mockGetResolution = Promise.resolve({});
+ get.mockReturnValueOnce(mockGetResolution);
+ await sessionReplay.initialize();
expect(record).toHaveBeenCalledTimes(1);
});
});
@@ -246,48 +355,25 @@ describe('SessionReplayPlugin', () => {
});
describe('recordEvents', () => {
- test('should send the first two emitted events immediately', () => {
- const sessionReplay = sessionReplayPlugin();
- const send = jest.spyOn(sessionReplay, 'send').mockReturnValueOnce(Promise.resolve());
- sessionReplay.config = mockConfig;
- sessionReplay.recordEvents();
- // Confirm that no events have been set in events list yet
- expect(sessionReplay.events).toEqual([]);
- const recordArg = record.mock.calls[0][0];
- // Emit two events manually (replicating what rrweb would do itself)
- recordArg?.emit && recordArg?.emit(mockEvent);
- recordArg?.emit && recordArg?.emit(mockEvent);
- // Confirm that there are still no events in the events list
- // (because they are sent immediately instead of stored)
- expect(sessionReplay.events).toEqual([]);
- jest.runAllTimers();
- expect(send).toHaveBeenCalledTimes(1);
- expect(send.mock.calls[0][0]).toEqual({
- events: [mockEventString, mockEventString],
- sequenceId: 0,
- attempts: 1,
- timeout: 0,
- sessionId: 123,
- });
- });
-
test('should store events in class and in IDB', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.recordEvents();
expect(sessionReplay.events).toEqual([]);
const recordArg = record.mock.calls[0][0];
- // Emit first two events, which get sent immediately
- recordArg?.emit && recordArg?.emit(mockEvent);
- recordArg?.emit && recordArg?.emit(mockEvent);
- // Emit third event, which is stored in class and IDB
+ // Emit event, which is stored in class and IDB
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sessionReplay.events).toEqual([mockEventString]);
- expect(update).toHaveBeenCalledTimes(2);
- expect(update.mock.calls[1][1]({})).toEqual({
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1]({})).toEqual({
123: {
- events: [mockEventString],
- sequenceId: 1,
+ currentSequenceId: 0,
+ sessionSequences: {
+ 0: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
},
});
});
@@ -300,24 +386,20 @@ describe('SessionReplayPlugin', () => {
const dateNowMock = jest.spyOn(Date, 'now').mockReturnValue(1);
const sendEventsList = jest.spyOn(sessionReplay, 'sendEventsList');
const recordArg = record.mock.calls[0][0];
- // Emit first two events, which get sent immediately
- recordArg?.emit && recordArg?.emit(mockEvent);
- recordArg?.emit && recordArg?.emit(mockEvent);
- expect(sendEventsList).toHaveBeenCalledTimes(1);
- sendEventsList.mockClear();
- // Emit third event, which is not sent immediately
+ // Emit first event, which is not sent immediately
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(0);
- // Emit fourth event and advance timers to interval
+ // Emit second event and advance timers to interval
dateNowMock.mockReturnValue(1002);
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(1);
expect(sendEventsList).toHaveBeenCalledWith({
events: [mockEventString],
- sequenceId: 1,
+ sequenceId: 0,
sessionId: 123,
});
expect(sessionReplay.events).toEqual([mockEventString]);
+ expect(sessionReplay.currentSequenceId).toEqual(1);
dateNowMock.mockClear();
});
@@ -325,7 +407,7 @@ describe('SessionReplayPlugin', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.maxPersistedEventsSize = 20;
- sessionReplay.currentSequenceId = 1;
+ // Simulate as if many events have already been built up
const events = ['#'.repeat(20)];
sessionReplay.events = events;
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -338,7 +420,7 @@ describe('SessionReplayPlugin', () => {
expect(sendEventsListMock).toHaveBeenCalledTimes(1);
expect(sendEventsListMock).toHaveBeenCalledWith({
events,
- sequenceId: 1,
+ sequenceId: 0,
sessionId: 123,
});
@@ -346,8 +428,13 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
123: {
- events: [mockEventString],
- sequenceId: 2,
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
},
});
});
@@ -510,7 +597,7 @@ describe('SessionReplayPlugin', () => {
method: 'POST',
});
});
- test('should remove session events from IDB store upon success', async () => {
+ test('should update IDB store upon success', async () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
@@ -520,26 +607,13 @@ describe('SessionReplayPlugin', () => {
attempts: 0,
timeout: 0,
};
+ const cleanUpSessionEventsStore = jest
+ .spyOn(sessionReplay, 'cleanUpSessionEventsStore')
+ .mockReturnValueOnce(Promise.resolve());
await sessionReplay.send(context);
jest.runAllTimers();
- const mockIDBStore = {
- 123: {
- events: [mockEventString],
- sequenceId: 3,
- },
- 456: {
- events: [mockEventString],
- sequenceId: 1,
- },
- };
-
- expect(update).toHaveBeenCalledTimes(1);
- expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
- 456: {
- events: [mockEventString],
- sequenceId: 1,
- },
- });
+ expect(cleanUpSessionEventsStore).toHaveBeenCalledTimes(1);
+ expect(cleanUpSessionEventsStore.mock.calls[0]).toEqual([123, 1]);
});
test('should not remove session events from IDB store upon failure', async () => {
const sessionReplay = sessionReplayPlugin();
@@ -768,8 +842,8 @@ describe('SessionReplayPlugin', () => {
});
});
- describe('idb error handling', () => {
- test('getAllSessionEventsFromStore should catch errors', async () => {
+ describe('getAllSessionEventsFromStore', () => {
+ test('should catch errors', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
@@ -784,7 +858,117 @@ describe('SessionReplayPlugin', () => {
'Failed to store session replay events in IndexedDB: error',
);
});
- test('storeEventsForSession should catch errors', async () => {
+ });
+
+ describe('cleanUpSessionEventsStore', () => {
+ test('should update events and status for current session', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ 123: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 2: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ };
+ await sessionReplay.cleanUpSessionEventsStore(123, 3);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ 123: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 2: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ 3: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ });
+ });
+
+ test('should delete sent sequences for current session', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ 123: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 2: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ };
+ await sessionReplay.cleanUpSessionEventsStore(123, 3);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ 123: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 3: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ });
+ });
+ test('should catch errors', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
@@ -792,14 +976,164 @@ describe('SessionReplayPlugin', () => {
};
sessionReplay.config = config;
update.mockImplementationOnce(() => Promise.reject('error'));
- await sessionReplay.storeEventsForSession([mockEventString], 0);
+ await sessionReplay.cleanUpSessionEventsStore(123, 1);
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
'Failed to store session replay events in IndexedDB: error',
);
});
- test('removeSessionEventsStore should catch errors', async () => {
+ test('should handle an undefined store', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ update.mockImplementationOnce(() => Promise.resolve());
+ await sessionReplay.cleanUpSessionEventsStore(123, 1);
+ expect(update.mock.calls[0][1](undefined)).toEqual({});
+ });
+ });
+
+ describe('storeEventsForSession', () => {
+ test('should update the session current sequence id, and the current sequence events and status', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ 123: {
+ currentSequenceId: 2,
+ sessionSequences: {
+ 2: {
+ events: [],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ };
+ await sessionReplay.storeEventsForSession([mockEventString], 2, 123);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ 123: {
+ currentSequenceId: 2,
+ sessionSequences: {
+ 2: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ });
+ });
+ test('should add a new entry if none exist for sequence id', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ 123: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ };
+ await sessionReplay.storeEventsForSession([mockEventString], 2, 123);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ 123: {
+ currentSequenceId: 2,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ 2: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ });
+ });
+ test('should add a new entry if none exist for session id', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ 123: {
+ currentSequenceId: 2,
+ sessionSequences: {
+ 2: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ };
+ await sessionReplay.storeEventsForSession([mockEventString], 0, 456);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ 123: {
+ currentSequenceId: 2,
+ sessionSequences: {
+ 2: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 0,
+ sessionSequences: {
+ 0: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ });
+ });
+ test('should catch errors', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
@@ -807,14 +1141,14 @@ describe('SessionReplayPlugin', () => {
};
sessionReplay.config = config;
update.mockImplementationOnce(() => Promise.reject('error'));
- await sessionReplay.removeSessionEventsStore(123);
+ await sessionReplay.storeEventsForSession([mockEventString], 0, config.sessionId as number);
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
'Failed to store session replay events in IndexedDB: error',
);
});
- test('removeSessionEventsStore should handle an undefined store', async () => {
+ test('should handle an undefined store', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
...mockConfig,
@@ -822,8 +1156,18 @@ describe('SessionReplayPlugin', () => {
};
sessionReplay.config = config;
update.mockImplementationOnce(() => Promise.resolve());
- await sessionReplay.removeSessionEventsStore(123);
- expect(update.mock.calls[0][1](undefined)).toEqual({});
+ await sessionReplay.storeEventsForSession([mockEventString], 0, 456);
+ expect(update.mock.calls[0][1](undefined)).toEqual({
+ 456: {
+ currentSequenceId: 0,
+ sessionSequences: {
+ 0: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ });
});
});
@@ -863,27 +1207,15 @@ describe('SessionReplayPlugin', () => {
const result = sessionReplay.shouldSplitEventsList(nextEvent);
expect(result).toBe(false);
});
- test('should return true if it has been long enough since last send', () => {
+ test('should return true if it has been long enough since last send and events have been emitted', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.timeAtLastSend = 1;
- jest.spyOn(Date, 'now').mockReturnValue(1002);
+ sessionReplay.events = [mockEventString];
+ jest.spyOn(Date, 'now').mockReturnValue(100002);
const nextEvent = 'a';
const result = sessionReplay.shouldSplitEventsList(nextEvent);
expect(result).toBe(true);
});
- test('should increase interval incrementally', () => {
- const sessionReplay = sessionReplayPlugin();
- sessionReplay.timeAtLastSend = 1;
- jest.spyOn(Date, 'now').mockReturnValue(1000000);
- const nextEvent = 'a';
- for (let i = 1; i <= 10; i++) {
- expect(sessionReplay.interval).toEqual(1000 * i);
- sessionReplay.shouldSplitEventsList(nextEvent);
- }
- // Call one more time to go over the 10 max
- sessionReplay.shouldSplitEventsList(nextEvent);
- expect(sessionReplay.interval).toEqual(10000);
- });
});
});
});
From b770d44086306db15a378af6b1c3590afdb0ec58 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 20 Jul 2023 13:51:59 -0400
Subject: [PATCH 060/214] fix(plugins): reduce min interval to 500ms
---
packages/plugin-session-replay-browser/src/session-replay.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index ffcc7e669..719a70a9b 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -18,7 +18,7 @@ const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/tra
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-const MIN_INTERVAL = 1 * 1000; // 1 second
+const MIN_INTERVAL = 500; // 500 ms
const MAX_INTERVAL = 10 * 1000; // 10 seconds
class SessionReplay implements SessionReplayEnrichmentPlugin {
From 2c95641a226169afa89936f06afbbeef030f78e1 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Thu, 20 Jul 2023 18:10:45 +0000
Subject: [PATCH 061/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.2.0
---
.../plugin-session-replay-browser/CHANGELOG.md | 17 +++++++++++++++++
.../plugin-session-replay-browser/package.json | 2 +-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index dacc6d34f..435418cf4 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.2.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.3...@amplitude/plugin-session-replay-browser@0.2.0) (2023-07-20)
+
+### Bug Fixes
+
+- **plugins:** reduce min interval to 500ms
+ ([b770d44](https://github.com/amplitude/Amplitude-TypeScript/commit/b770d44086306db15a378af6b1c3590afdb0ec58))
+
+### Features
+
+- **plugins:** solve timing issues with multiple tabs
+ ([d2e9a0c](https://github.com/amplitude/Amplitude-TypeScript/commit/d2e9a0cc7c455dcdf13ea2fa303d2fbd50911536))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.1.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.2...@amplitude/plugin-session-replay-browser@0.1.3) (2023-07-13)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index d7e7203c0..27fcc3b5f 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.1.3",
+ "version": "0.2.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From d85e230193b0c4286586fb23a8e020413051c595 Mon Sep 17 00:00:00 2001
From: Alyssa Yu
Date: Tue, 25 Jul 2023 15:58:34 -0700
Subject: [PATCH 062/214] feat: user agent plugin (#503)
---
.../README.md | 75 +++++++++++
.../jest.config.js | 10 ++
.../package.json | 57 +++++++++
.../rollup.config.js | 6 +
.../src/index.ts | 2 +
.../src/typings/ua-parser.d.ts | 4 +
.../typings/user-agent-enrichment-plugin.ts | 13 ++
.../src/user-agent-enrichment-plugin.ts | 43 +++++++
.../test/user-agent.test.ts | 120 ++++++++++++++++++
.../tsconfig.es5.json | 10 ++
.../tsconfig.esm.json | 10 ++
.../tsconfig.json | 11 ++
12 files changed, 361 insertions(+)
create mode 100644 packages/plugin-user-agent-enrichment-browser/README.md
create mode 100644 packages/plugin-user-agent-enrichment-browser/jest.config.js
create mode 100644 packages/plugin-user-agent-enrichment-browser/package.json
create mode 100644 packages/plugin-user-agent-enrichment-browser/rollup.config.js
create mode 100644 packages/plugin-user-agent-enrichment-browser/src/index.ts
create mode 100644 packages/plugin-user-agent-enrichment-browser/src/typings/ua-parser.d.ts
create mode 100644 packages/plugin-user-agent-enrichment-browser/src/typings/user-agent-enrichment-plugin.ts
create mode 100644 packages/plugin-user-agent-enrichment-browser/src/user-agent-enrichment-plugin.ts
create mode 100644 packages/plugin-user-agent-enrichment-browser/test/user-agent.test.ts
create mode 100644 packages/plugin-user-agent-enrichment-browser/tsconfig.es5.json
create mode 100644 packages/plugin-user-agent-enrichment-browser/tsconfig.esm.json
create mode 100644 packages/plugin-user-agent-enrichment-browser/tsconfig.json
diff --git a/packages/plugin-user-agent-enrichment-browser/README.md b/packages/plugin-user-agent-enrichment-browser/README.md
new file mode 100644
index 000000000..dca10b9c8
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/README.md
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+# @amplitude/plugin-user-agent-enrichment-browser
+
+Official Browser SDK plugin for user agent enrichment.
+
+## Installation
+
+This package is published on NPM registry and is available to be installed using npm and yarn.
+
+```sh
+# npm
+npm install @amplitude/plugin-user-agent-enrichment-browser@^1.0.0
+
+# yarn
+yarn add @amplitude/plugin-user-agent-enrichment-browser@^1.0.0
+```
+
+## Usage
+
+This plugin works on top of the Amplitude Browser SDK. It's used for enriching events with user agent information using the @amplitude/ua-parser-js. The user agent identifies the application, operating system, vendor, and/or version of the requesting client.
+For Browser SDK v1.x, we use @amplitude/ua-parser-js internally to parse the user agent information. In Browser v2.x, we have removed this client-side user agent parser and have instead implemented server-side user agent parser. You can use this plugin to maintain consistency with the user agent information from earlier SDK versions.
+
+### 1. Import Amplitude packages
+
+* `@amplitude/analytics-browser`
+* `@amplitude/plugin-user-agent-enrichment-browser`
+
+```typescript
+import * as amplitude from '@amplitude/analytics-browser';
+import { userAgentEnrichmentPlugin } from '@amplitude/plugin-user-agent-enrichment-browser';
+```
+
+### 2. Instantiate user agent enrichment plugin
+
+The plugin accepts 1 optional parameter, which is an `Object` to disable/enable the corresponding tracking options. Each option is enabled by default.
+
+```typescript
+const uaPlugin = userAgentEnrichmentPlugin({
+ osName: true,
+ osVersion: true,
+ deviceManufacturer: false,
+ deviceModel: false,
+});
+```
+
+#### Options
+
+|Name|Type|Default|Description|
+|-|-|-|-|
+|`osName`|`boolean`|`true`| Enables enrichment of `os_name` property. |
+|`osVersion`|`boolean`|`true`| Enables enrichment of `os_version` property. |
+|`deviceManufacturer`|`boolean`|`true`| Enables enrichment of `device_manufacturer` property. |
+|`deviceModel`|`boolean`|`true`| Enables enrichment of `device_model` property. |
+
+### 3. Install plugin to Amplitude SDK
+
+```typescript
+amplitude.add(uaPlugin);
+```
+
+### 4. Initialize Amplitude SDK
+
+```typescript
+amplitude.init('API_KEY');
+```
+
+## Resulting page view event
+
+This plugin parses user agent information using @amplitude/ua-parser-js and enriches events based on your configuration. This affects the value of the following properties: `device_family`, `device_model`, `device_manufacturer`, `device_type`, `os`, `os_name`, and `os_version`.
\ No newline at end of file
diff --git a/packages/plugin-user-agent-enrichment-browser/jest.config.js b/packages/plugin-user-agent-enrichment-browser/jest.config.js
new file mode 100644
index 000000000..a0ef09957
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/jest.config.js
@@ -0,0 +1,10 @@
+const baseConfig = require('../../jest.config.js');
+const package = require('./package');
+
+module.exports = {
+ ...baseConfig,
+ displayName: package.name,
+ rootDir: '.',
+ testEnvironment: 'jsdom',
+ coveragePathIgnorePatterns: ['index.ts'],
+};
\ No newline at end of file
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
new file mode 100644
index 000000000..73bffb4e0
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "@amplitude/plugin-user-agent-enrichment-browser",
+ "version": "0.0.0",
+ "description": "",
+ "author": "Amplitude Inc",
+ "homepage": "https://github.com/amplitude/Amplitude-TypeScript",
+ "license": "MIT",
+ "main": "lib/cjs/index.js",
+ "module": "lib/esm/index.js",
+ "types": "lib/esm/index.d.ts",
+ "sideEffects": false,
+ "publishConfig": {
+ "access": "public",
+ "tag": "latest"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/amplitude/Amplitude-TypeScript.git"
+ },
+ "scripts": {
+ "build": "yarn bundle && yarn build:es5 && yarn build:esm",
+ "bundle": "rollup --config rollup.config.js",
+ "build:es5": "tsc -p ./tsconfig.es5.json",
+ "build:esm": "tsc -p ./tsconfig.esm.json",
+ "clean": "rimraf node_modules lib coverage",
+ "fix": "yarn fix:eslint & yarn fix:prettier",
+ "fix:eslint": "eslint '{src,test}/**/*.ts' --fix",
+ "fix:prettier": "prettier --write \"{src,test}/**/*.ts\"",
+ "lint": "yarn lint:eslint & yarn lint:prettier",
+ "lint:eslint": "eslint '{src,test}/**/*.ts'",
+ "lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
+ "test": "jest",
+ "typecheck": "tsc -p ./tsconfig.json"
+ },
+ "bugs": {
+ "url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
+ },
+ "dependencies": {
+ "@amplitude/analytics-client-common": "^2.0.4",
+ "@amplitude/analytics-types": "^2.1.1",
+ "@amplitude/ua-parser-js": "^0.7.33",
+ "tslib": "^2.4.1"
+ },
+ "devDependencies": {
+ "@amplitude/analytics-browser": "^2.1.2",
+ "@rollup/plugin-commonjs": "^23.0.4",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^10.0.1",
+ "rollup": "^2.79.1",
+ "rollup-plugin-execute": "^1.1.1",
+ "rollup-plugin-gzip": "^3.1.0",
+ "rollup-plugin-terser": "^7.0.2"
+ },
+ "files": [
+ "lib"
+ ]
+}
diff --git a/packages/plugin-user-agent-enrichment-browser/rollup.config.js b/packages/plugin-user-agent-enrichment-browser/rollup.config.js
new file mode 100644
index 000000000..8b1d5414a
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/rollup.config.js
@@ -0,0 +1,6 @@
+import { iife, umd } from '../../scripts/build/rollup.config';
+
+iife.input = umd.input;
+iife.output.name = 'amplitudeUserAgentEnrichmentPlugin';
+
+export default [umd, iife];
diff --git a/packages/plugin-user-agent-enrichment-browser/src/index.ts b/packages/plugin-user-agent-enrichment-browser/src/index.ts
new file mode 100644
index 000000000..594daf69f
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/src/index.ts
@@ -0,0 +1,2 @@
+export { userAgentEnrichmentPlugin } from './user-agent-enrichment-plugin';
+export { userAgentEnrichmentPlugin as plugin } from './user-agent-enrichment-plugin';
diff --git a/packages/plugin-user-agent-enrichment-browser/src/typings/ua-parser.d.ts b/packages/plugin-user-agent-enrichment-browser/src/typings/ua-parser.d.ts
new file mode 100644
index 000000000..bac2f15e9
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/src/typings/ua-parser.d.ts
@@ -0,0 +1,4 @@
+declare module '@amplitude/ua-parser-js' {
+ import UAParser from 'ua-parser-js';
+ export = UAParser;
+}
diff --git a/packages/plugin-user-agent-enrichment-browser/src/typings/user-agent-enrichment-plugin.ts b/packages/plugin-user-agent-enrichment-browser/src/typings/user-agent-enrichment-plugin.ts
new file mode 100644
index 000000000..0385293c3
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/src/typings/user-agent-enrichment-plugin.ts
@@ -0,0 +1,13 @@
+/* eslint-disable @typescript-eslint/no-empty-interface */
+import { EnrichmentPlugin } from '@amplitude/analytics-types';
+
+export interface Options {
+ osName?: boolean;
+ osVersion?: boolean;
+ deviceManufacturer?: boolean;
+ deviceModel?: boolean;
+}
+
+export interface CreateUserAgentEnrichmentPlugin {
+ (options?: Options): EnrichmentPlugin;
+}
diff --git a/packages/plugin-user-agent-enrichment-browser/src/user-agent-enrichment-plugin.ts b/packages/plugin-user-agent-enrichment-browser/src/user-agent-enrichment-plugin.ts
new file mode 100644
index 000000000..de7fd34aa
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/src/user-agent-enrichment-plugin.ts
@@ -0,0 +1,43 @@
+import { BrowserConfig, EnrichmentPlugin, Event } from '@amplitude/analytics-types';
+import { CreateUserAgentEnrichmentPlugin, Options } from './typings/user-agent-enrichment-plugin';
+import UAParser from '@amplitude/ua-parser-js';
+
+export const userAgentEnrichmentPlugin: CreateUserAgentEnrichmentPlugin = function (options: Options = {}) {
+ const { osName = true, osVersion = true, deviceManufacturer = true, deviceModel = true } = options;
+
+ let uaResult: UAParser.IResult;
+
+ const plugin: EnrichmentPlugin = {
+ name: '@amplitude/plugin-user-agent-enrichment-browser',
+ type: 'enrichment',
+
+ setup: async function (config: BrowserConfig) {
+ let userAgent: string | undefined;
+ /* istanbul ignore else */
+ if (typeof navigator !== 'undefined') {
+ userAgent = navigator.userAgent;
+ }
+
+ uaResult = new UAParser(userAgent).getResult();
+
+ config.loggerProvider.log('Installing @amplitude/plugin-user-agent-enrichment-browser.');
+ },
+
+ execute: async (event: Event) => {
+ const uaOsName = uaResult.browser.name;
+ const UaOsVersion = uaResult.browser.version;
+ const UaDeviceModel = uaResult.device.model || uaResult.os.name;
+ const UaDeviceVendor = uaResult.device.vendor;
+
+ return {
+ ...event,
+ ...(osName && { os_name: uaOsName }),
+ ...(osVersion && { os_version: UaOsVersion }),
+ ...(deviceManufacturer && { device_manufacturer: UaDeviceVendor }),
+ ...(deviceModel && { device_model: UaDeviceModel }),
+ };
+ },
+ };
+
+ return plugin;
+};
diff --git a/packages/plugin-user-agent-enrichment-browser/test/user-agent.test.ts b/packages/plugin-user-agent-enrichment-browser/test/user-agent.test.ts
new file mode 100644
index 000000000..814bcfc9a
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/test/user-agent.test.ts
@@ -0,0 +1,120 @@
+import { createInstance } from '@amplitude/analytics-browser';
+import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
+import { userAgentEnrichmentPlugin } from '../src/user-agent-enrichment-plugin';
+import { BaseEvent, BrowserConfig, LogLevel } from '@amplitude/analytics-types';
+import { Logger, UUID } from '@amplitude/analytics-core';
+
+describe('uaParserPlugin', () => {
+ let event: BaseEvent;
+
+ const mockConfig: BrowserConfig = {
+ apiKey: UUID(),
+ flushIntervalMillis: 0,
+ flushMaxRetries: 0,
+ flushQueueSize: 0,
+ logLevel: LogLevel.None,
+ loggerProvider: new Logger(),
+ optOut: false,
+ serverUrl: undefined,
+ transportProvider: new FetchTransport(),
+ useBatch: false,
+ cookieOptions: {
+ domain: '.amplitude.com',
+ expiration: 365,
+ sameSite: 'Lax',
+ secure: false,
+ upgrade: true,
+ },
+ cookieStorage: new CookieStorage(),
+ sessionTimeout: 30 * 60 * 1000,
+ trackingOptions: {
+ ipAddress: true,
+ language: true,
+ platform: true,
+ },
+ };
+
+ beforeEach(() => {
+ event = {
+ event_type: 'event_type',
+ };
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('execute', () => {
+ test('should overwirte all devices info without option', async () => {
+ const amplitude = createInstance();
+
+ const plugin = userAgentEnrichmentPlugin();
+ const executeSpy = jest.spyOn(plugin, 'execute');
+
+ await plugin.setup?.(mockConfig, amplitude);
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(executeSpy).toHaveBeenCalledWith(event);
+ expect(enrichedEvent).toHaveProperty('os_name');
+ expect(enrichedEvent).toHaveProperty('os_version');
+ expect(enrichedEvent).toHaveProperty('os_name');
+ expect(enrichedEvent).toHaveProperty('device_model');
+ });
+
+ test('should escape the optOut devices info with disabled options 1', async () => {
+ const amplitude = createInstance();
+
+ const plugin = userAgentEnrichmentPlugin({
+ osVersion: false,
+ });
+
+ await plugin.setup?.(mockConfig, amplitude);
+
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(enrichedEvent).toHaveProperty('device_model');
+ expect(enrichedEvent).toHaveProperty('os_name');
+ expect(enrichedEvent).not.toHaveProperty('os_version');
+ expect(enrichedEvent).toHaveProperty('device_manufacturer');
+ });
+
+ test('should escape the optOut devices info with disabled options 2', async () => {
+ const amplitude = createInstance();
+
+ const plugin = userAgentEnrichmentPlugin({
+ osName: false,
+ deviceManufacturer: true,
+ });
+
+ await plugin.setup?.(mockConfig, amplitude);
+
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(enrichedEvent).toHaveProperty('device_model');
+ expect(enrichedEvent).not.toHaveProperty('os_name');
+ expect(enrichedEvent).toHaveProperty('os_version');
+ expect(enrichedEvent).toHaveProperty('device_manufacturer');
+ });
+
+ test('should overwrite the opted in devices info', async () => {
+ const amplitude = createInstance();
+
+ const plugin = userAgentEnrichmentPlugin({
+ osName: true,
+ osVersion: true,
+ deviceManufacturer: false,
+ deviceModel: false,
+ });
+
+ await plugin.setup?.(mockConfig, amplitude);
+
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(enrichedEvent).toHaveProperty('os_name');
+ expect(enrichedEvent).toHaveProperty('os_version');
+
+ expect(enrichedEvent).not.toHaveProperty('device_manufacturer');
+ expect(enrichedEvent).not.toHaveProperty('device_model');
+ });
+ });
+});
diff --git a/packages/plugin-user-agent-enrichment-browser/tsconfig.es5.json b/packages/plugin-user-agent-enrichment-browser/tsconfig.es5.json
new file mode 100644
index 000000000..77e041d3f
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/tsconfig.es5.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "module": "commonjs",
+ "noEmit": false,
+ "outDir": "lib/cjs",
+ "rootDir": "./src"
+ }
+}
diff --git a/packages/plugin-user-agent-enrichment-browser/tsconfig.esm.json b/packages/plugin-user-agent-enrichment-browser/tsconfig.esm.json
new file mode 100644
index 000000000..bec981eee
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/tsconfig.esm.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "module": "es6",
+ "noEmit": false,
+ "outDir": "lib/esm",
+ "rootDir": "./src"
+ }
+}
diff --git a/packages/plugin-user-agent-enrichment-browser/tsconfig.json b/packages/plugin-user-agent-enrichment-browser/tsconfig.json
new file mode 100644
index 000000000..bd2a4cdb7
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src/**/*", "test/**/*"],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "esModuleInterop": true,
+ "lib": ["dom"],
+ "noEmit": true,
+ "rootDir": ".",
+ }
+}
\ No newline at end of file
From 7fb6059716a09fae3c62a238d100fecc06861e67 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 26 Jul 2023 11:01:31 -0700
Subject: [PATCH 063/214] fix(plugins): stop recording when document.hasFocus
is false
---
.../src/session-replay.ts | 9 +++++--
.../test/session-replay.test.ts | 27 +++++++++++++++++--
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 719a70a9b..8840f4352 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -16,8 +16,8 @@ import {
const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
-const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 200; // derived by JSON stringifying an example payload without events
-const MAX_EVENT_LIST_SIZE_IN_BYTES = 20 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 500; // derived by JSON stringifying an example payload without events
+const MAX_EVENT_LIST_SIZE_IN_BYTES = 10 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
const MIN_INTERVAL = 500; // 500 ms
const MAX_INTERVAL = 10 * 1000; // 10 seconds
@@ -132,6 +132,11 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
recordEvents() {
this.stopRecordingEvents = record({
emit: (event) => {
+ const GlobalScope = getGlobalScope();
+ if (GlobalScope && GlobalScope.document && !GlobalScope.document.hasFocus()) {
+ this.stopRecordingEvents && this.stopRecordingEvents();
+ return;
+ }
const eventString = JSON.stringify(event);
const shouldSplit = this.shouldSplitEventsList(eventString);
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 01e412277..98eb79cbe 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -89,14 +89,17 @@ describe('SessionReplayPlugin', () => {
}),
) as jest.Mock;
addEventListenerMock = jest.fn() as typeof addEventListenerMock;
- jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValueOnce({
+ jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
window: {
addEventListener: addEventListenerMock,
} as unknown as Window,
+ document: {
+ hasFocus: () => true,
+ },
} as Window & typeof globalThis);
});
afterEach(() => {
- jest.clearAllMocks();
+ jest.resetAllMocks();
global.fetch = originalFetch;
});
afterAll(() => {
@@ -438,6 +441,26 @@ describe('SessionReplayPlugin', () => {
},
});
});
+
+ test('should return early if document is not in focus', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.recordEvents();
+ sessionReplay.stopRecordingEvents = jest.fn();
+ expect(sessionReplay.events).toEqual([]);
+ jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
+ document: {
+ hasFocus: () => false,
+ },
+ } as typeof globalThis);
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ const sendEventsListMock = jest.spyOn(sessionReplay, 'sendEventsList').mockImplementationOnce(() => {});
+ const recordArg = record.mock.calls[0][0];
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(sendEventsListMock).toHaveBeenCalledTimes(0);
+ expect(sessionReplay.stopRecordingEvents).toHaveBeenCalled();
+ expect(sessionReplay.events).toEqual([]);
+ });
});
describe('addToQueue', () => {
From cf93eaf295645232e839e4625cfbbeaa933bfefc Mon Sep 17 00:00:00 2001
From: Brian Giori
Date: Wed, 26 Jul 2023 13:44:01 -0700
Subject: [PATCH 064/214] fix: react-native fix context plugin to include app
version on mobile (#507)
---
.../src/plugins/context.ts | 2 +-
.../test/plugins/context.test.ts | 17 +++++++++++++++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/packages/analytics-react-native/src/plugins/context.ts b/packages/analytics-react-native/src/plugins/context.ts
index 8c4414a44..20486ddff 100644
--- a/packages/analytics-react-native/src/plugins/context.ts
+++ b/packages/analytics-react-native/src/plugins/context.ts
@@ -81,7 +81,7 @@ export class Context implements BeforePlugin {
device_id: this.config.deviceId,
session_id: this.config.sessionId,
time,
- ...(this.config.appVersion && { app_version: appVersion }),
+ ...(appVersion && { app_version: appVersion }),
...(this.config.trackingOptions.platform && { platform: platform }),
...(this.config.trackingOptions.osName && { os_name: osName }),
...(this.config.trackingOptions.osVersion && { os_version: osVersion }),
diff --git a/packages/analytics-react-native/test/plugins/context.test.ts b/packages/analytics-react-native/test/plugins/context.test.ts
index 65e6ae06b..6cea0fdf5 100644
--- a/packages/analytics-react-native/test/plugins/context.test.ts
+++ b/packages/analytics-react-native/test/plugins/context.test.ts
@@ -131,6 +131,23 @@ describe('context', () => {
expect(secondContextEvent.insert_id).not.toEqual(firstContextEvent.insert_id);
});
+ test('should contain app version from native module', async () => {
+ const context = new Context();
+ const config = useDefaultConfig({
+ deviceId: 'deviceId',
+ sessionId: 1,
+ userId: 'user@amplitude.com',
+ });
+ await context.setup(config);
+
+ const event = {
+ event_type: 'event_type',
+ };
+ const firstContextEvent = await context.execute(event);
+
+ expect(firstContextEvent.app_version).toEqual(isWeb() ? undefined : '1.0.0');
+ });
+
describe('ingestionMetadata config', () => {
test('should include ingestion metadata', async () => {
const sourceName = 'ampli';
From 6d9cf5f1a05293815917f2136914493a67a9256f Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 26 Jul 2023 20:51:04 +0000
Subject: [PATCH 065/214] chore(release): publish
- @amplitude/analytics-react-native@1.3.2
- @amplitude/plugin-session-replay-browser@0.2.1
- @amplitude/plugin-user-agent-enrichment-browser@0.1.0
---
packages/analytics-react-native/CHANGELOG.md | 13 +++++++++++++
packages/analytics-react-native/package.json | 2 +-
packages/analytics-react-native/src/version.ts | 2 +-
packages/plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
.../CHANGELOG.md | 11 +++++++++++
.../package.json | 2 +-
7 files changed, 40 insertions(+), 4 deletions(-)
create mode 100644 packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
diff --git a/packages/analytics-react-native/CHANGELOG.md b/packages/analytics-react-native/CHANGELOG.md
index d373867a1..5159e4202 100644
--- a/packages/analytics-react-native/CHANGELOG.md
+++ b/packages/analytics-react-native/CHANGELOG.md
@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.3.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-react-native@1.3.1...@amplitude/analytics-react-native@1.3.2) (2023-07-26)
+
+### Bug Fixes
+
+- react-native fix context plugin to include app version on mobile
+ ([#507](https://github.com/amplitude/Amplitude-TypeScript/issues/507))
+ ([cf93eaf](https://github.com/amplitude/Amplitude-TypeScript/commit/cf93eaf295645232e839e4625cfbbeaa933bfefc))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.3.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-react-native@1.3.0...@amplitude/analytics-react-native@1.3.1) (2023-07-05)
**Note:** Version bump only for package @amplitude/analytics-react-native
diff --git a/packages/analytics-react-native/package.json b/packages/analytics-react-native/package.json
index c2b8cf914..b8d85189a 100644
--- a/packages/analytics-react-native/package.json
+++ b/packages/analytics-react-native/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-react-native",
- "version": "1.3.1",
+ "version": "1.3.2",
"description": "Official React Native SDK",
"keywords": [
"analytics",
diff --git a/packages/analytics-react-native/src/version.ts b/packages/analytics-react-native/src/version.ts
index 1ef6744a1..45f295c35 100644
--- a/packages/analytics-react-native/src/version.ts
+++ b/packages/analytics-react-native/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.3.1';
+export const VERSION = '1.3.2';
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 435418cf4..39e26d536 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.2.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.2.0...@amplitude/plugin-session-replay-browser@0.2.1) (2023-07-26)
+
+### Bug Fixes
+
+- **plugins:** stop recording when document.hasFocus is false
+ ([7fb6059](https://github.com/amplitude/Amplitude-TypeScript/commit/7fb6059716a09fae3c62a238d100fecc06861e67))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [0.2.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.1.3...@amplitude/plugin-session-replay-browser@0.2.0) (2023-07-20)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 27fcc3b5f..f2a16b50c 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
diff --git a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
new file mode 100644
index 000000000..649bb0b8f
--- /dev/null
+++ b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# 0.1.0 (2023-07-26)
+
+### Features
+
+- user agent plugin ([#503](https://github.com/amplitude/Amplitude-TypeScript/issues/503))
+ ([d85e230](https://github.com/amplitude/Amplitude-TypeScript/commit/d85e230193b0c4286586fb23a8e020413051c595))
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
index 73bffb4e0..a801a263a 100644
--- a/packages/plugin-user-agent-enrichment-browser/package.json
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-user-agent-enrichment-browser",
- "version": "0.0.0",
+ "version": "0.1.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 6aeb511e5a7db760ebaa86aee0d7756fb85e9020 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 10 Jul 2023 10:33:05 -0400
Subject: [PATCH 066/214] feat(plugins): add default tracking of sessions to
session replay plugin
---
.../src/session-replay.ts | 16 +++++++++
.../src/typings/session-replay.ts | 1 +
.../test/session-replay.test.ts | 36 +++++++++++++++++++
3 files changed, 53 insertions(+)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 8840f4352..10fafa8c9 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -42,6 +42,22 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ if (typeof config.defaultTracking === 'boolean') {
+ if (config.defaultTracking === false) {
+ config.defaultTracking = {
+ pageViews: false,
+ formInteractions: false,
+ fileDownloads: false,
+ sessions: true,
+ };
+ }
+ } else {
+ config.defaultTracking = {
+ ...config.defaultTracking,
+ sessions: true,
+ };
+ }
+
this.config = config;
this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
await this.initialize(true);
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 823999f7d..a77c9f90c 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -32,6 +32,7 @@ export interface IDBStore {
};
}
export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
+ setup: (config: BrowserConfig) => Promise;
config: BrowserConfig;
storageKey: string;
retryTimeout: number;
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 98eb79cbe..553c69c49 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -293,6 +293,42 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.initialize();
expect(record).toHaveBeenCalledTimes(1);
});
+ describe('defaultTracking', () => {
+ test('should not change defaultTracking if its set to true', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ await sessionReplay.setup({
+ ...mockConfig,
+ defaultTracking: true,
+ });
+ expect(sessionReplay.config.defaultTracking).toBe(true);
+ });
+ test('should modify defaultTracking to enable sessions if its set to false', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ await sessionReplay.setup({
+ ...mockConfig,
+ defaultTracking: false,
+ });
+ expect(sessionReplay.config.defaultTracking).toEqual({
+ pageViews: false,
+ formInteractions: false,
+ fileDownloads: false,
+ sessions: true,
+ });
+ });
+ test('should modify defaultTracking to enable sessions if it is an object', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ await sessionReplay.setup({
+ ...mockConfig,
+ defaultTracking: {
+ pageViews: false,
+ },
+ });
+ expect(sessionReplay.config.defaultTracking).toEqual({
+ pageViews: false,
+ sessions: true,
+ });
+ });
+ });
});
describe('execute', () => {
From bcabfa4ea784187edc85a85bce9a2c68dde411e5 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 10 Jul 2023 11:50:01 -0400
Subject: [PATCH 067/214] feat(plugins): add a configuration option for
sampling rate
---
.../plugin-session-replay-browser/README.md | 20 +++++--
.../src/session-replay.ts | 22 +++++++-
.../src/typings/session-replay.ts | 11 ++--
.../test/session-replay.test.ts | 52 +++++++++++++++++++
4 files changed, 94 insertions(+), 11 deletions(-)
diff --git a/packages/plugin-session-replay-browser/README.md b/packages/plugin-session-replay-browser/README.md
index 5f546afe4..2a3e9a11f 100644
--- a/packages/plugin-session-replay-browser/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -35,12 +35,26 @@ import * as amplitude from '@amplitude/analytics-browser';
import { sessionReplayPlugin } from '@amplitude/plugin-session-replay-browser';
```
-### 2. Instantiate session replay plugin and install plugin to Amplitude SDK
+### 2. Instantiate Session Replay plugin
-The plugin must be registered with the amplitude instance via the following code:
+The plugin must be registered with the amplitude instance via the following code. The plugin accepts an optional parameter which is an `Object` to configure the plugin based on your use case.
```typescript
amplitude.init(API_KEY);
-const sessionReplayTracking = sessionReplayPlugin();
+const sessionReplayTracking = sessionReplayPlugin({
+ sampleRate: undefined
+});
+```
+
+
+#### Options
+
+|Name|Type|Default|Description|
+|-|-|-|-|
+|`sampleRate`|`number`|`undefined`|Use this option to control how many sessions will be selected for recording. The number should be a decimal between 0 and 1, ie `0.4`, representing the fraction of sessions you would like to have randomly selected for recording. Over a large number of sessions, `0.4` would select `40%` of those sessions.|
+
+### 3. Install plugin to Amplitude SDK
+
+```typescript
amplitude.add(sessionReplayTracking);
```
\ No newline at end of file
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 10fafa8c9..525152d40 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -11,6 +11,7 @@ import {
RecordingStatus,
SessionReplayContext,
SessionReplayEnrichmentPlugin,
+ SessionReplayOptions,
SessionReplayPlugin,
} from './typings/session-replay';
@@ -38,9 +39,22 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
maxPersistedEventsSize = MAX_EVENT_LIST_SIZE_IN_BYTES;
interval = MIN_INTERVAL;
timeAtLastSend: number | null = null;
+ options: SessionReplayOptions;
+ shouldRecordEvents = true;
+
+ constructor(options?: SessionReplayOptions) {
+ this.options = { ...options };
+ if (options?.sampleRate) {
+ this.shouldRecordEvents = Math.random() < options.sampleRate;
+ }
+ }
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ if (!this.shouldRecordEvents) {
+ config.loggerProvider.log('Opting session out of recording due to lack of inclusion in sample.');
+ return;
+ }
if (typeof config.defaultTracking === 'boolean') {
if (config.defaultTracking === false) {
@@ -75,6 +89,10 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
async execute(event: Event) {
+ if (!this.shouldRecordEvents) {
+ return Promise.resolve(event);
+ }
+
event.event_properties = {
...event.event_properties,
[DEFAULT_SESSION_REPLAY_PROPERTY]: true,
@@ -405,6 +423,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
}
-export const sessionReplayPlugin: SessionReplayPlugin = () => {
- return new SessionReplay();
+export const sessionReplayPlugin: SessionReplayPlugin = (options?: SessionReplayOptions) => {
+ return new SessionReplay(options);
};
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index a77c9f90c..354995682 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -1,8 +1,10 @@
-import { BrowserClient, BrowserConfig, EnrichmentPlugin } from '@amplitude/analytics-types';
+import { BrowserConfig, EnrichmentPlugin } from '@amplitude/analytics-types';
import { record } from 'rrweb';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface Options {}
+export interface SessionReplayOptions {
+ sampleRate?: number;
+}
export type Events = string[];
@@ -76,8 +78,5 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
}
export interface SessionReplayPlugin {
- (client: BrowserClient, options?: Options): SessionReplayEnrichmentPlugin;
- (options?: Options): SessionReplayEnrichmentPlugin;
+ (options?: SessionReplayOptions): SessionReplayEnrichmentPlugin;
}
-
-export type SessionReplayPluginParameters = [BrowserClient, Options?] | [Options?];
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 553c69c49..378dde624 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -748,6 +748,58 @@ describe('SessionReplayPlugin', () => {
});
describe('module level integration', () => {
+ describe('with a sample rate', () => {
+ test('should not record session if excluded due to sampling', async () => {
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ const sessionReplay = sessionReplayPlugin({
+ sampleRate: 0.2,
+ });
+ await sessionReplay.setup(mockConfig);
+ await sessionReplay.execute({
+ event_type: 'session_end',
+ session_id: 456,
+ });
+ await runScheduleTimers();
+ expect(record).not.toHaveBeenCalled();
+ expect(fetch).not.toHaveBeenCalled();
+ expect(update).not.toHaveBeenCalled();
+ });
+ test('should record session if included due to sampling', async () => {
+ (fetch as jest.Mock).mockImplementationOnce(() => {
+ return Promise.resolve({
+ status: 200,
+ });
+ });
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ const sessionReplay = sessionReplayPlugin({
+ sampleRate: 0.8,
+ });
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ await sessionReplay.setup(config);
+ // Log is called from setup, but that's not what we're testing here
+ mockLoggerProvider.log.mockClear();
+ await sessionReplay.execute({
+ event_type: 'session_start',
+ session_id: 456,
+ });
+ expect(record).toHaveBeenCalled();
+ const recordArg = record.mock.calls[0][0];
+ recordArg?.emit && recordArg?.emit(mockEvent);
+ expect(update).toHaveBeenCalledTimes(1);
+ await sessionReplay.execute({
+ event_type: 'session_end',
+ session_id: 456,
+ });
+ await runScheduleTimers();
+ expect(fetch).toHaveBeenCalledTimes(1);
+ expect(mockLoggerProvider.log).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(SUCCESS_MESSAGE);
+ });
+ });
test('should handle unexpected error', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
From 25bd516849c9a7fc346c6a868c664e70cdeb5cba Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 10 Jul 2023 12:22:58 -0400
Subject: [PATCH 068/214] feat(plugins): abide by global opt out for session
recording
---
.../src/session-replay.ts | 6 +++-
.../test/session-replay.test.ts | 30 +++++++++++++++++--
2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 525152d40..9a4b475ec 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -51,8 +51,12 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
+ if (config.optOut) {
+ this.shouldRecordEvents = false;
+ }
+
if (!this.shouldRecordEvents) {
- config.loggerProvider.log('Opting session out of recording due to lack of inclusion in sample.');
+ config.loggerProvider.log('Opting session out of recording.');
return;
}
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 378dde624..c2cc372a2 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -755,14 +755,18 @@ describe('SessionReplayPlugin', () => {
sampleRate: 0.2,
});
await sessionReplay.setup(mockConfig);
+ await sessionReplay.execute({
+ event_type: 'session_start',
+ session_id: 456,
+ });
+ expect(record).not.toHaveBeenCalled();
+ expect(update).not.toHaveBeenCalled();
await sessionReplay.execute({
event_type: 'session_end',
session_id: 456,
});
await runScheduleTimers();
- expect(record).not.toHaveBeenCalled();
expect(fetch).not.toHaveBeenCalled();
- expect(update).not.toHaveBeenCalled();
});
test('should record session if included due to sampling', async () => {
(fetch as jest.Mock).mockImplementationOnce(() => {
@@ -800,6 +804,28 @@ describe('SessionReplayPlugin', () => {
expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(SUCCESS_MESSAGE);
});
});
+
+ describe('with optOut in config', () => {
+ test('should not record session if excluded due to optOut', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ await sessionReplay.setup({
+ ...mockConfig,
+ optOut: true,
+ });
+ await sessionReplay.execute({
+ event_type: 'session_start',
+ session_id: 456,
+ });
+ expect(record).not.toHaveBeenCalled();
+ expect(update).not.toHaveBeenCalled();
+ await sessionReplay.execute({
+ event_type: 'session_end',
+ session_id: 456,
+ });
+ await runScheduleTimers();
+ expect(fetch).not.toHaveBeenCalled();
+ });
+ });
test('should handle unexpected error', async () => {
const sessionReplay = sessionReplayPlugin();
const config = {
From 5c04c3cc0e8ef287898f2571f8c2a3e9e00311be Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 25 Jul 2023 19:35:03 -0700
Subject: [PATCH 069/214] feat(plugins): update config additions to store in
idb
---
.../src/messages.ts | 3 +-
.../src/session-replay.ts | 88 +++--
.../src/typings/session-replay.ts | 16 +-
.../test/session-replay.test.ts | 303 ++++++++++++++++--
4 files changed, 340 insertions(+), 70 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/messages.ts b/packages/plugin-session-replay-browser/src/messages.ts
index 8b93b23da..dd1de2e45 100644
--- a/packages/plugin-session-replay-browser/src/messages.ts
+++ b/packages/plugin-session-replay-browser/src/messages.ts
@@ -1,4 +1,5 @@
-export const SUCCESS_MESSAGE = 'Session replay event batch tracked successfully';
+export const getSuccessMessage = (sessionId: number) =>
+ `Session replay event batch tracked successfully for session id ${sessionId}`;
export const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error occurred';
export const MAX_RETRIES_EXCEEDED_MESSAGE = 'Session replay event batch rejected due to exceeded retry count';
export const STORAGE_FAILURE = 'Failed to store session replay events in IndexedDB';
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 9a4b475ec..a34c47c3f 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -4,10 +4,11 @@ import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
-import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from './messages';
+import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from './messages';
import {
Events,
IDBStore,
+ IDBStoreSession,
RecordingStatus,
SessionReplayContext,
SessionReplayEnrichmentPlugin,
@@ -21,7 +22,11 @@ const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 500; // derived by JSON s
const MAX_EVENT_LIST_SIZE_IN_BYTES = 10 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
const MIN_INTERVAL = 500; // 500 ms
const MAX_INTERVAL = 10 * 1000; // 10 seconds
-
+const defaultSessionStore: IDBStoreSession = {
+ shouldRecord: true,
+ currentSequenceId: 0,
+ sessionSequences: {},
+};
class SessionReplay implements SessionReplayEnrichmentPlugin {
name = '@amplitude/plugin-session-replay-browser';
type = 'enrichment' as const;
@@ -40,25 +45,18 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
interval = MIN_INTERVAL;
timeAtLastSend: number | null = null;
options: SessionReplayOptions;
- shouldRecordEvents = true;
+ shouldRecord = true;
constructor(options?: SessionReplayOptions) {
this.options = { ...options };
- if (options?.sampleRate) {
- this.shouldRecordEvents = Math.random() < options.sampleRate;
- }
}
async setup(config: BrowserConfig) {
config.loggerProvider.log('Installing @amplitude/plugin-session-replay.');
- if (config.optOut) {
- this.shouldRecordEvents = false;
- }
- if (!this.shouldRecordEvents) {
- config.loggerProvider.log('Opting session out of recording.');
- return;
- }
+ this.config = config;
+ this.config.sessionId = config.sessionId;
+ this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
if (typeof config.defaultTracking === 'boolean') {
if (config.defaultTracking === false) {
@@ -76,8 +74,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
};
}
- this.config = config;
- this.storageKey = `${STORAGE_PREFIX}_${this.config.apiKey.substring(0, 10)}`;
await this.initialize(true);
const GlobalScope = getGlobalScope();
@@ -93,18 +89,17 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
async execute(event: Event) {
- if (!this.shouldRecordEvents) {
- return Promise.resolve(event);
+ if (this.shouldRecord) {
+ event.event_properties = {
+ ...event.event_properties,
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
+ };
}
-
- event.event_properties = {
- ...event.event_properties,
- [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
- };
if (event.event_type === DEFAULT_SESSION_START_EVENT && !this.stopRecordingEvents) {
+ this.setShouldRecord();
this.recordEvents();
} else if (event.event_type === DEFAULT_SESSION_END_EVENT) {
- if (event.session_id) {
+ if (event.session_id && this.events.length) {
this.sendEventsList({
events: this.events,
sequenceId: this.currentSequenceId,
@@ -129,15 +124,16 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
if (storedReplaySessions && storedSequencesForSession && storedSequencesForSession.sessionSequences) {
const storedSeqId = storedSequencesForSession.currentSequenceId;
const lastSequence = storedSequencesForSession.sessionSequences[storedSeqId];
- if (lastSequence.status !== RecordingStatus.RECORDING) {
+ if (lastSequence && lastSequence.status !== RecordingStatus.RECORDING) {
this.currentSequenceId = storedSeqId + 1;
this.events = [];
} else {
// Pick up recording where it was left off in another tab or window
this.currentSequenceId = storedSeqId;
- this.events = lastSequence.events;
+ this.events = lastSequence?.events || [];
}
}
+ this.setShouldRecord(storedSequencesForSession);
if (shouldSendStoredEvents && storedReplaySessions) {
this.sendStoredEvents(storedReplaySessions);
}
@@ -146,6 +142,20 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
}
+ setShouldRecord(sessionStore?: IDBStoreSession) {
+ if (sessionStore?.shouldRecord === false) {
+ this.shouldRecord = false;
+ } else if (this.options && this.options.sampleRate) {
+ this.shouldRecord = Math.random() < this.options.sampleRate;
+ } else if (this.config.optOut) {
+ this.shouldRecord = false;
+ }
+ this.config.sessionId && void this.storeShouldRecordForSession(this.config.sessionId, this.shouldRecord);
+ if (!this.shouldRecord && this.config.sessionId) {
+ this.config.loggerProvider.log(`Opting session ${this.config.sessionId} out of recording.`);
+ }
+ }
+
sendStoredEvents(storedReplaySessions: IDBStore) {
for (const sessionId in storedReplaySessions) {
const storedSequences = storedReplaySessions[sessionId].sessionSequences;
@@ -168,6 +178,9 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
recordEvents() {
+ if (!this.shouldRecord) {
+ return;
+ }
this.stopRecordingEvents = record({
emit: (event) => {
const GlobalScope = getGlobalScope();
@@ -330,7 +343,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
handleSuccessResponse(context: SessionReplayContext) {
- this.completeRequest({ context, success: SUCCESS_MESSAGE });
+ this.completeRequest({ context, success: getSuccessMessage(context.sessionId) });
}
handleOtherResponse(context: SessionReplayContext) {
@@ -354,10 +367,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
async storeEventsForSession(events: Events, sequenceId: number, sessionId: number) {
try {
await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
- const session = sessionMap[sessionId] || {
- currentSequenceId: 0,
- sessionSequences: [],
- };
+ const session: IDBStoreSession = sessionMap[sessionId] || { ...defaultSessionStore };
session.currentSequenceId = sequenceId;
const currentSequence = (session.sessionSequences && session.sessionSequences[sequenceId]) || {};
@@ -381,10 +391,26 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
}
+ async storeShouldRecordForSession(sessionId: number, shouldRecord: boolean) {
+ try {
+ await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
+ const session: IDBStoreSession = sessionMap[sessionId] || { ...defaultSessionStore };
+ session.shouldRecord = shouldRecord;
+
+ return {
+ ...sessionMap,
+ [sessionId]: session,
+ };
+ });
+ } catch (e) {
+ this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
+ }
+ }
+
async cleanUpSessionEventsStore(sessionId: number, sequenceId: number) {
try {
await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
- const session = sessionMap[sessionId];
+ const session: IDBStoreSession = sessionMap[sessionId];
const sequenceToUpdate = session?.sessionSequences && session.sessionSequences[sequenceId];
if (!sequenceToUpdate) {
return sessionMap;
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 354995682..84f959410 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -27,12 +27,17 @@ export interface IDBStoreSequence {
status: RecordingStatus;
}
-export interface IDBStore {
- [sessionId: number]: {
- currentSequenceId: number;
- sessionSequences: { [sequenceId: number]: IDBStoreSequence };
+export interface IDBStoreSession {
+ shouldRecord: boolean;
+ currentSequenceId: number;
+ sessionSequences: {
+ [sequenceId: number]: IDBStoreSequence;
};
}
+
+export interface IDBStore {
+ [sessionId: number]: IDBStoreSession;
+}
export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
setup: (config: BrowserConfig) => Promise;
config: BrowserConfig;
@@ -41,11 +46,13 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
events: Events;
currentSequenceId: number;
interval: number;
+ shouldRecord: boolean;
queue: SessionReplayContext[];
timeAtLastSend: number | null;
stopRecordingEvents: ReturnType | null;
maxPersistedEventsSize: number;
initialize: (shouldSendStoredEvents?: boolean) => Promise;
+ setShouldRecord: (sessionStore?: IDBStoreSession) => void;
recordEvents: () => void;
shouldSplitEventsList: (nextEventString: string) => boolean;
sendEventsList: ({
@@ -74,6 +81,7 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
}): void;
getAllSessionEventsFromStore: () => Promise;
storeEventsForSession: (events: Events, sequenceId: number, sessionId: number) => Promise;
+ storeShouldRecordForSession: (sessionId: number, shouldRecord: boolean) => Promise;
cleanUpSessionEventsStore: (sessionId: number, sequenceId: number) => Promise;
}
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index c2cc372a2..ccdffd601 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -1,10 +1,10 @@
/* eslint-disable jest/expect-expect */
import * as AnalyticsClientCommon from '@amplitude/analytics-client-common';
import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
-import { BrowserConfig, LogLevel } from '@amplitude/analytics-types';
+import { BrowserConfig, LogLevel, Logger } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
-import { SUCCESS_MESSAGE, UNEXPECTED_ERROR_MESSAGE } from '../src/messages';
+import { UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from '../src/messages';
import { sessionReplayPlugin } from '../src/session-replay';
import { IDBStore, RecordingStatus } from '../src/typings/session-replay';
@@ -14,6 +14,8 @@ type MockedIDBKeyVal = jest.Mocked;
jest.mock('rrweb');
type MockedRRWeb = jest.Mocked;
+type MockedLogger = jest.Mocked;
+
const mockEvent = {
type: 4,
data: { href: 'https://analytics.amplitude.com/', width: 1728, height: 154 },
@@ -30,20 +32,11 @@ async function runScheduleTimers() {
jest.runAllTimers();
}
-const mockLoggerProvider = {
- error: jest.fn(),
- log: jest.fn(),
- logLevel: 1,
- disable: jest.fn(),
- enable: jest.fn(),
- warn: jest.fn(),
- debug: jest.fn(),
-};
-
describe('SessionReplayPlugin', () => {
const { get, update } = IDBKeyVal as MockedIDBKeyVal;
const { record } = RRWeb as MockedRRWeb;
let originalFetch: typeof global.fetch;
+ let mockLoggerProvider: MockedLogger;
let addEventListenerMock: jest.Mock;
const mockConfig: BrowserConfig = {
apiKey: 'static_key',
@@ -78,10 +71,8 @@ describe('SessionReplayPlugin', () => {
platform: true,
},
};
- beforeAll(() => {
- jest.useFakeTimers();
- });
beforeEach(() => {
+ jest.useFakeTimers();
originalFetch = global.fetch;
global.fetch = jest.fn(() =>
Promise.resolve({
@@ -97,12 +88,19 @@ describe('SessionReplayPlugin', () => {
hasFocus: () => true,
},
} as Window & typeof globalThis);
+ mockLoggerProvider = {
+ error: jest.fn(),
+ log: jest.fn(),
+ disable: jest.fn(),
+ enable: jest.fn(),
+ warn: jest.fn(),
+ debug: jest.fn(),
+ };
});
afterEach(() => {
jest.resetAllMocks();
+ jest.spyOn(global.Math, 'random').mockRestore();
global.fetch = originalFetch;
- });
- afterAll(() => {
jest.useRealTimers();
});
describe('setup', () => {
@@ -165,6 +163,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = config;
const mockGetResolution: Promise = Promise.resolve({
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -174,6 +173,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -243,6 +243,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockGetResolution: Promise = Promise.resolve({
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -262,6 +263,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockGetResolution: Promise = Promise.resolve({
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -285,6 +287,21 @@ describe('SessionReplayPlugin', () => {
expect(sessionReplay.currentSequenceId).toBe(0);
expect(sessionReplay.events).toEqual([]);
});
+ test('should handle no stored sequences for session', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockGetResolution = Promise.resolve({
+ 123: {
+ shouldRecord: true,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ });
+ get.mockReturnValueOnce(mockGetResolution);
+ await sessionReplay.initialize();
+ expect(sessionReplay.currentSequenceId).toBe(0);
+ expect(sessionReplay.events).toEqual([]);
+ });
test('should record events', async () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
@@ -331,6 +348,47 @@ describe('SessionReplayPlugin', () => {
});
});
+ describe('setShouldRecord', () => {
+ test('should set record as false if false in session store', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const sessionStore = {
+ shouldRecord: false,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ };
+ expect(sessionReplay.shouldRecord).toBe(true);
+ sessionReplay.setShouldRecord(sessionStore);
+ expect(sessionReplay.shouldRecord).toBe(false);
+ });
+ test('should not set record as false if no options', () => {
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.setShouldRecord();
+ expect(sessionReplay.shouldRecord).toBe(true);
+ });
+ test('should set record as false if session not included in sample rate', () => {
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ const sessionReplay = sessionReplayPlugin({
+ sampleRate: 0.2,
+ });
+ sessionReplay.config = mockConfig;
+ expect(sessionReplay.shouldRecord).toBe(true);
+ sessionReplay.setShouldRecord();
+ expect(sessionReplay.shouldRecord).toBe(false);
+ });
+ test('should set record as false if opt out in config', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = {
+ ...mockConfig,
+ optOut: true,
+ };
+ sessionReplay.setShouldRecord();
+ expect(sessionReplay.shouldRecord).toBe(false);
+ });
+ });
+
describe('execute', () => {
test('should add event property for [Amplitude] Session Recorded', async () => {
const sessionReplay = sessionReplayPlugin();
@@ -406,6 +464,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {
0: {
@@ -420,16 +479,16 @@ describe('SessionReplayPlugin', () => {
test('should split the events list at an increasing interval and send', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
+ sessionReplay.timeAtLastSend = new Date('2023-07-31 08:30:00').getTime();
sessionReplay.recordEvents();
- sessionReplay.timeAtLastSend = 1;
- const dateNowMock = jest.spyOn(Date, 'now').mockReturnValue(1);
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:30:00').getTime());
const sendEventsList = jest.spyOn(sessionReplay, 'sendEventsList');
const recordArg = record.mock.calls[0][0];
// Emit first event, which is not sent immediately
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(0);
// Emit second event and advance timers to interval
- dateNowMock.mockReturnValue(1002);
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:31:00').getTime());
recordArg?.emit && recordArg?.emit(mockEvent);
expect(sendEventsList).toHaveBeenCalledTimes(1);
expect(sendEventsList).toHaveBeenCalledWith({
@@ -439,7 +498,6 @@ describe('SessionReplayPlugin', () => {
});
expect(sessionReplay.events).toEqual([mockEventString]);
expect(sessionReplay.currentSequenceId).toEqual(1);
- dateNowMock.mockClear();
});
test('should split the events list at max size and send', () => {
@@ -467,6 +525,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -755,19 +814,93 @@ describe('SessionReplayPlugin', () => {
sampleRate: 0.2,
});
await sessionReplay.setup(mockConfig);
+ expect(update).toHaveBeenCalled();
+ expect(update.mock.calls[0][1]({})).toEqual({
+ 123: {
+ shouldRecord: false,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ });
+ update.mockClear();
await sessionReplay.execute({
- event_type: 'session_start',
- session_id: 456,
+ event_type: 'my_event',
+ session_id: 123,
});
expect(record).not.toHaveBeenCalled();
expect(update).not.toHaveBeenCalled();
await sessionReplay.execute({
event_type: 'session_end',
- session_id: 456,
+ session_id: 123,
+ });
+ await runScheduleTimers();
+ expect(fetch).not.toHaveBeenCalled();
+ });
+ test('should recalculate whether to exclude session due to sample rate when start session fires', async () => {
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.1);
+ const sessionReplay = sessionReplayPlugin({
+ sampleRate: 0.2,
+ });
+ await sessionReplay.setup(mockConfig);
+ expect(update).toHaveBeenCalled();
+ expect(update.mock.calls[0][1]({})).toEqual({
+ 123: {
+ shouldRecord: true,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ });
+ update.mockClear();
+ // Record is called in setup, but we're not interested in that now
+ record.mockClear();
+ // This will exclude session from sample rate
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ await sessionReplay.execute({
+ event_type: 'session_start',
+ session_id: 123,
+ });
+ expect(record).not.toHaveBeenCalled();
+ expect(update).toHaveBeenCalled();
+ expect(update.mock.calls[0][1]({})).toEqual({
+ 123: {
+ shouldRecord: false,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ });
+ await sessionReplay.execute({
+ event_type: 'session_end',
+ session_id: 123,
});
await runScheduleTimers();
expect(fetch).not.toHaveBeenCalled();
});
+ test('should fetch whether to record session from IDB', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const mockIDBStore = {
+ 123: {
+ shouldRecord: false,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ };
+ get.mockImplementationOnce(() => Promise.resolve(mockIDBStore));
+ await sessionReplay.setup(mockConfig);
+ expect(record).not.toHaveBeenCalled();
+ expect(update).toHaveBeenCalled();
+ expect(update.mock.calls[0][1]({})).toEqual({
+ 123: {
+ shouldRecord: false,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ });
+ await sessionReplay.execute({
+ event_type: 'session_start',
+ session_id: 123,
+ });
+ expect(record).not.toHaveBeenCalled();
+ });
test('should record session if included due to sampling', async () => {
(fetch as jest.Mock).mockImplementationOnce(() => {
return Promise.resolve({
@@ -783,25 +916,25 @@ describe('SessionReplayPlugin', () => {
loggerProvider: mockLoggerProvider,
};
await sessionReplay.setup(config);
- // Log is called from setup, but that's not what we're testing here
- mockLoggerProvider.log.mockClear();
await sessionReplay.execute({
event_type: 'session_start',
session_id: 456,
});
+ // Log is called from setup, but that's not what we're testing here
+ mockLoggerProvider.log.mockClear();
expect(record).toHaveBeenCalled();
const recordArg = record.mock.calls[0][0];
recordArg?.emit && recordArg?.emit(mockEvent);
- expect(update).toHaveBeenCalledTimes(1);
await sessionReplay.execute({
event_type: 'session_end',
session_id: 456,
});
await runScheduleTimers();
expect(fetch).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.log).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(SUCCESS_MESSAGE);
+ expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(getSuccessMessage(456));
});
});
@@ -817,7 +950,6 @@ describe('SessionReplayPlugin', () => {
session_id: 456,
});
expect(record).not.toHaveBeenCalled();
- expect(update).not.toHaveBeenCalled();
await sessionReplay.execute({
event_type: 'session_end',
session_id: 456,
@@ -834,12 +966,14 @@ describe('SessionReplayPlugin', () => {
};
(fetch as jest.Mock).mockImplementationOnce(() => Promise.reject('API Failure'));
await sessionReplay.setup(config);
+ sessionReplay.events = [mockEventString];
await sessionReplay.execute({
event_type: 'session_end',
session_id: 456,
});
await runScheduleTimers();
expect(fetch).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual('API Failure');
@@ -866,16 +1000,19 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.setup(config);
// Log is called from setup, but that's not what we're testing here
mockLoggerProvider.log.mockClear();
+ sessionReplay.events = [mockEventString];
await sessionReplay.execute({
event_type: 'session_end',
session_id: 456,
});
await runScheduleTimers();
expect(fetch).toHaveBeenCalledTimes(2);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(0);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.log).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(SUCCESS_MESSAGE);
+ expect(mockLoggerProvider.log.mock.calls[0][0]).toEqual(getSuccessMessage(456));
});
test('should handle retry for 413 error with flushQueueSize of 1', async () => {
(fetch as jest.Mock)
@@ -895,6 +1032,7 @@ describe('SessionReplayPlugin', () => {
flushMaxRetries: 2,
};
await sessionReplay.setup(config);
+ sessionReplay.events = [mockEventString];
const event = {
event_type: 'session_end',
session_id: 456,
@@ -921,6 +1059,7 @@ describe('SessionReplayPlugin', () => {
flushMaxRetries: 2,
};
await sessionReplay.setup(config);
+ sessionReplay.events = [mockEventString];
const event = {
event_type: 'session_end',
session_id: 456,
@@ -947,6 +1086,7 @@ describe('SessionReplayPlugin', () => {
flushMaxRetries: 2,
};
await sessionReplay.setup(config);
+ sessionReplay.events = [mockEventString];
const event = {
event_type: 'session_end',
session_id: 456,
@@ -966,6 +1106,7 @@ describe('SessionReplayPlugin', () => {
loggerProvider: mockLoggerProvider,
};
await sessionReplay.setup(config);
+ sessionReplay.events = [mockEventString];
const event = {
event_type: 'session_end',
session_id: 456,
@@ -973,6 +1114,7 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.execute(event);
await runScheduleTimers();
expect(fetch).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(UNEXPECTED_ERROR_MESSAGE);
@@ -989,6 +1131,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = config;
get.mockImplementationOnce(() => Promise.reject('error'));
await sessionReplay.getAllSessionEventsFromStore();
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
@@ -996,6 +1139,75 @@ describe('SessionReplayPlugin', () => {
);
});
});
+ describe('storeShouldRecordForSession', () => {
+ test('should store if should record session', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ 123: {
+ shouldRecord: true,
+ currentSequenceId: 3,
+ sessionSequences: {
+ 2: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ shouldRecord: true,
+ currentSequenceId: 1,
+ sessionSequences: {
+ 1: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ },
+ },
+ };
+ await sessionReplay.storeShouldRecordForSession(456, false);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ ...mockIDBStore,
+ 456: {
+ ...mockIDBStore[456],
+ shouldRecord: false,
+ },
+ });
+ });
+ test('should catch errors', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ update.mockImplementationOnce(() => Promise.reject('error'));
+ await sessionReplay.storeShouldRecordForSession(123, true);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
+ 'Failed to store session replay events in IndexedDB: error',
+ );
+ });
+ test('should handle an undefined store', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ loggerProvider: mockLoggerProvider,
+ };
+ sessionReplay.config = config;
+ update.mockImplementationOnce(() => Promise.resolve());
+ await sessionReplay.storeShouldRecordForSession(123, true);
+ expect(update.mock.calls[0][1](undefined)).toEqual({
+ 123: {
+ shouldRecord: true,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ },
+ });
+ });
+ });
describe('cleanUpSessionEventsStore', () => {
test('should update events and status for current session', async () => {
@@ -1003,6 +1215,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
2: {
@@ -1016,6 +1229,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1030,6 +1244,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
2: {
@@ -1043,6 +1258,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1059,6 +1275,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
2: {
@@ -1072,6 +1289,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1086,6 +1304,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -1095,6 +1314,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1114,6 +1334,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = config;
update.mockImplementationOnce(() => Promise.reject('error'));
await sessionReplay.cleanUpSessionEventsStore(123, 1);
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
@@ -1139,6 +1360,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
+ shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1148,6 +1370,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1162,6 +1385,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1171,6 +1395,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1186,6 +1411,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1195,6 +1421,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1209,6 +1436,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
1: {
@@ -1222,6 +1450,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1237,6 +1466,7 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
+ shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1251,6 +1481,7 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
+ shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1260,6 +1491,7 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
+ shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {
0: {
@@ -1279,6 +1511,8 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = config;
update.mockImplementationOnce(() => Promise.reject('error'));
await sessionReplay.storeEventsForSession([mockEventString], 0, config.sessionId as number);
+
+ // eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
@@ -1296,6 +1530,7 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.storeEventsForSession([mockEventString], 0, 456);
expect(update.mock.calls[0][1](undefined)).toEqual({
456: {
+ shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {
0: {
@@ -1338,17 +1573,17 @@ describe('SessionReplayPlugin', () => {
});
test('should return false if it has not been long enough since last send', () => {
const sessionReplay = sessionReplayPlugin();
- sessionReplay.timeAtLastSend = 1;
- jest.spyOn(Date, 'now').mockReturnValue(2);
+ sessionReplay.timeAtLastSend = new Date('2023-07-31 08:30:00').getTime();
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:30:00').getTime());
const nextEvent = 'a';
const result = sessionReplay.shouldSplitEventsList(nextEvent);
expect(result).toBe(false);
});
test('should return true if it has been long enough since last send and events have been emitted', () => {
const sessionReplay = sessionReplayPlugin();
- sessionReplay.timeAtLastSend = 1;
sessionReplay.events = [mockEventString];
- jest.spyOn(Date, 'now').mockReturnValue(100002);
+ sessionReplay.timeAtLastSend = new Date('2023-07-31 08:30:00').getTime();
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:33:00').getTime());
const nextEvent = 'a';
const result = sessionReplay.shouldSplitEventsList(nextEvent);
expect(result).toBe(true);
From 5438a438ec062b3011234b0fea82b54351a12864 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 26 Jul 2023 14:10:11 -0700
Subject: [PATCH 070/214] fix(plugins): ensure config.optout takes priority
over sample rate
---
.../plugin-session-replay-browser/README.md | 2 +-
.../src/session-replay.ts | 5 +++--
.../test/session-replay.test.ts | 22 ++++++++++++++++++-
3 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/packages/plugin-session-replay-browser/README.md b/packages/plugin-session-replay-browser/README.md
index 2a3e9a11f..9b0fdfcf5 100644
--- a/packages/plugin-session-replay-browser/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -51,7 +51,7 @@ const sessionReplayTracking = sessionReplayPlugin({
|Name|Type|Default|Description|
|-|-|-|-|
-|`sampleRate`|`number`|`undefined`|Use this option to control how many sessions will be selected for recording. The number should be a decimal between 0 and 1, ie `0.4`, representing the fraction of sessions you would like to have randomly selected for recording. Over a large number of sessions, `0.4` would select `40%` of those sessions.|
+|`sampleRate`|`number`|`undefined`|Use this option to control how many sessions will be selected for recording. A selected session will be recorded, while sessions that are not selected will not be recorded. The number should be a decimal between 0 and 1, ie `0.4`, representing the fraction of sessions you would like to have randomly selected for recording. Over a large number of sessions, `0.4` would select `40%` of those sessions.|
### 3. Install plugin to Amplitude SDK
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index a34c47c3f..b6f2f8cea 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -145,11 +145,12 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
setShouldRecord(sessionStore?: IDBStoreSession) {
if (sessionStore?.shouldRecord === false) {
this.shouldRecord = false;
- } else if (this.options && this.options.sampleRate) {
- this.shouldRecord = Math.random() < this.options.sampleRate;
} else if (this.config.optOut) {
this.shouldRecord = false;
+ } else if (this.options && this.options.sampleRate) {
+ this.shouldRecord = Math.random() < this.options.sampleRate;
}
+
this.config.sessionId && void this.storeShouldRecordForSession(this.config.sessionId, this.shouldRecord);
if (!this.shouldRecord && this.config.sessionId) {
this.config.loggerProvider.log(`Opting session ${this.config.sessionId} out of recording.`);
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index ccdffd601..3c9615ef1 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -362,7 +362,6 @@ describe('SessionReplayPlugin', () => {
expect(sessionReplay.shouldRecord).toBe(false);
});
test('should not set record as false if no options', () => {
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.setShouldRecord();
@@ -378,6 +377,15 @@ describe('SessionReplayPlugin', () => {
sessionReplay.setShouldRecord();
expect(sessionReplay.shouldRecord).toBe(false);
});
+ test('should set record as true if session is included in sample rate', () => {
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ const sessionReplay = sessionReplayPlugin({
+ sampleRate: 0.8,
+ });
+ sessionReplay.config = mockConfig;
+ sessionReplay.setShouldRecord();
+ expect(sessionReplay.shouldRecord).toBe(true);
+ });
test('should set record as false if opt out in config', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = {
@@ -387,6 +395,18 @@ describe('SessionReplayPlugin', () => {
sessionReplay.setShouldRecord();
expect(sessionReplay.shouldRecord).toBe(false);
});
+ test('opt out in config should override the sample rate', () => {
+ jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ const sessionReplay = sessionReplayPlugin({
+ sampleRate: 0.8,
+ });
+ sessionReplay.config = {
+ ...mockConfig,
+ optOut: true,
+ };
+ sessionReplay.setShouldRecord();
+ expect(sessionReplay.shouldRecord).toBe(false);
+ });
});
describe('execute', () => {
From ed20cf22180b33f29fd71a1439de26706d6ec4ff Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 26 Jul 2023 14:53:28 -0700
Subject: [PATCH 071/214] fix(plugins): update README for session replay
---
packages/plugin-session-replay-browser/README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/plugin-session-replay-browser/README.md b/packages/plugin-session-replay-browser/README.md
index 9b0fdfcf5..0ada5f891 100644
--- a/packages/plugin-session-replay-browser/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -25,6 +25,8 @@ yarn add @amplitude/plugin-session-replay-browser
This plugin works on top of Amplitude Browser SDK and adds session replay features to built-in features. To use this plugin, you need to install `@amplitude/analytics-browser` version `v1.0.0` or later.
+This plugin requires that default tracking for sessions is enabled. If default tracking for sessions is not enabled in the config, the plugin will automatically enable it.
+
### 1. Import Amplitude packages
* `@amplitude/analytics-browser`
From e46a42e430c7560a4e9da322b10238fdbf4f1bc5 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Wed, 26 Jul 2023 17:13:03 -0700
Subject: [PATCH 072/214] feat(plugins): add masking controls to session replay
and reorganize constants and helper fns
---
.../plugin-session-replay-browser/README.md | 12 +-
.../package.json | 2 +-
.../src/constants.ts | 17 +++
.../src/helpers.ts | 8 ++
.../src/session-replay.ts | 30 ++---
.../src/typings/session-replay.ts | 1 -
.../test/helpers.test.ts | 24 ++++
yarn.lock | 103 ++++++++++++++----
8 files changed, 158 insertions(+), 39 deletions(-)
create mode 100644 packages/plugin-session-replay-browser/src/helpers.ts
create mode 100644 packages/plugin-session-replay-browser/test/helpers.test.ts
diff --git a/packages/plugin-session-replay-browser/README.md b/packages/plugin-session-replay-browser/README.md
index 0ada5f891..66dd0b95d 100644
--- a/packages/plugin-session-replay-browser/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -59,4 +59,14 @@ const sessionReplayTracking = sessionReplayPlugin({
```typescript
amplitude.add(sessionReplayTracking);
-```
\ No newline at end of file
+```
+
+## Privacy
+By default, the session recording will mask all inputs, meaning the text in inputs will appear in a session replay as asterisks: `***`. You may require more specific masking controls based on your use case, so we offer the following controls:
+
+#### 1. Unmask inputs
+In your application code, add the class `.amp-unmask` to any __input__ whose text you'd like to have unmasked in the recording. In the replay of a recorded session, it will be possible to read the exact text entered into an input with this class, the text will not be converted to asterisks.
+
+#### 2. Mask non-input elements
+In your application code, add the class `.amp-mask` to any __non-input element__ whose text you'd like to have masked from the recording. The text in the element, as well as it's children, will all be converted to asterisks.
+
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index f2a16b50c..0d0902019 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -39,7 +39,7 @@
"dependencies": {
"@amplitude/analytics-types": ">=1 <3",
"idb-keyval": "^6.2.1",
- "rrweb": "^2.0.0-alpha.4",
+ "rrweb": "^2.0.0-alpha.9",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/plugin-session-replay-browser/src/constants.ts b/packages/plugin-session-replay-browser/src/constants.ts
index 2ca6feab9..d726dbc10 100644
--- a/packages/plugin-session-replay-browser/src/constants.ts
+++ b/packages/plugin-session-replay-browser/src/constants.ts
@@ -1,5 +1,22 @@
+import { AMPLITUDE_PREFIX } from '@amplitude/analytics-core';
+import { IDBStoreSession } from './typings/session-replay';
+
export const DEFAULT_EVENT_PROPERTY_PREFIX = '[Amplitude]';
export const DEFAULT_SESSION_REPLAY_PROPERTY = `${DEFAULT_EVENT_PROPERTY_PREFIX} Session Recorded`;
export const DEFAULT_SESSION_START_EVENT = 'session_start';
export const DEFAULT_SESSION_END_EVENT = 'session_end';
+
+export const MASK_TEXT_CLASS = 'amp-mask';
+export const UNMASK_TEXT_CLASS = 'amp-unmask';
+export const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
+export const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
+const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 500; // derived by JSON stringifying an example payload without events
+export const MAX_EVENT_LIST_SIZE_IN_BYTES = 10 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
+export const MIN_INTERVAL = 500; // 500 ms
+export const MAX_INTERVAL = 10 * 1000; // 10 seconds
+export const defaultSessionStore: IDBStoreSession = {
+ shouldRecord: true,
+ currentSequenceId: 0,
+ sessionSequences: {},
+};
diff --git a/packages/plugin-session-replay-browser/src/helpers.ts b/packages/plugin-session-replay-browser/src/helpers.ts
new file mode 100644
index 000000000..3aeaaaf12
--- /dev/null
+++ b/packages/plugin-session-replay-browser/src/helpers.ts
@@ -0,0 +1,8 @@
+import { UNMASK_TEXT_CLASS } from './constants';
+
+export const maskInputFn = (text: string, element: HTMLElement) => {
+ if (element.classList?.contains(UNMASK_TEXT_CLASS)) {
+ return text;
+ }
+ return '*'.repeat(text.length);
+};
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index b6f2f8cea..31b62be68 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -1,9 +1,21 @@
import { getGlobalScope } from '@amplitude/analytics-client-common';
-import { AMPLITUDE_PREFIX, BaseTransport } from '@amplitude/analytics-core';
+import { BaseTransport } from '@amplitude/analytics-core';
import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
-import { DEFAULT_SESSION_END_EVENT, DEFAULT_SESSION_REPLAY_PROPERTY, DEFAULT_SESSION_START_EVENT } from './constants';
+import {
+ DEFAULT_SESSION_END_EVENT,
+ DEFAULT_SESSION_REPLAY_PROPERTY,
+ DEFAULT_SESSION_START_EVENT,
+ MASK_TEXT_CLASS,
+ MAX_EVENT_LIST_SIZE_IN_BYTES,
+ MAX_INTERVAL,
+ MIN_INTERVAL,
+ SESSION_REPLAY_SERVER_URL,
+ STORAGE_PREFIX,
+ defaultSessionStore,
+} from './constants';
+import { maskInputFn } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from './messages';
import {
Events,
@@ -15,18 +27,6 @@ import {
SessionReplayOptions,
SessionReplayPlugin,
} from './typings/session-replay';
-
-const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
-const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
-const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 500; // derived by JSON stringifying an example payload without events
-const MAX_EVENT_LIST_SIZE_IN_BYTES = 10 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
-const MIN_INTERVAL = 500; // 500 ms
-const MAX_INTERVAL = 10 * 1000; // 10 seconds
-const defaultSessionStore: IDBStoreSession = {
- shouldRecord: true,
- currentSequenceId: 0,
- sessionSequences: {},
-};
class SessionReplay implements SessionReplayEnrichmentPlugin {
name = '@amplitude/plugin-session-replay-browser';
type = 'enrichment' as const;
@@ -206,6 +206,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
},
packFn: pack,
maskAllInputs: true,
+ maskTextClass: MASK_TEXT_CLASS,
+ maskInputFn,
});
}
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 84f959410..d0b5ed359 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -1,7 +1,6 @@
import { BrowserConfig, EnrichmentPlugin } from '@amplitude/analytics-types';
import { record } from 'rrweb';
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface SessionReplayOptions {
sampleRate?: number;
}
diff --git a/packages/plugin-session-replay-browser/test/helpers.test.ts b/packages/plugin-session-replay-browser/test/helpers.test.ts
new file mode 100644
index 000000000..e2283874e
--- /dev/null
+++ b/packages/plugin-session-replay-browser/test/helpers.test.ts
@@ -0,0 +1,24 @@
+import { UNMASK_TEXT_CLASS } from '../src/constants';
+import { maskInputFn } from '../src/helpers';
+
+describe('SessionReplayPlugin helpers', () => {
+ describe('maskInputFn', () => {
+ test('should not mask an element whose class list has amp-unmask in it', () => {
+ const htmlElement = document.createElement('div');
+ htmlElement.classList.add(UNMASK_TEXT_CLASS);
+ const result = maskInputFn('some text', htmlElement);
+ expect(result).toEqual('some text');
+ });
+ test('should mask any other element', () => {
+ const htmlElement = document.createElement('div');
+ htmlElement.classList.add('another-class');
+ const result = maskInputFn('some text', htmlElement);
+ expect(result).toEqual('*********');
+ });
+ test('should handle an element without a class list', () => {
+ const htmlElement = {} as unknown as HTMLElement;
+ const result = maskInputFn('some text', htmlElement);
+ expect(result).toEqual('*********');
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 0a43256b5..b0e54f68f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,16 +2,75 @@
# yarn lockfile v1
-"@amplitude/analytics-connector@^1.4.5":
+"@amplitude/analytics-browser@^2.1.2":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@amplitude/analytics-browser/-/analytics-browser-2.1.2.tgz#fadd74c56acfa163443db83b60ba2795294a6c31"
+ integrity sha512-1XoWJmLgGrszxupz61yCjen53bq7+kJN2gedu+kSrq/Ze9ow5R5QI0YfLyuzi1FHchzmSVWd2PheXOVYC4K/Cw==
+ dependencies:
+ "@amplitude/analytics-client-common" "^2.0.4"
+ "@amplitude/analytics-core" "^2.0.3"
+ "@amplitude/analytics-types" "^2.1.1"
+ "@amplitude/plugin-page-view-tracking-browser" "^2.0.4"
+ "@amplitude/plugin-web-attribution-browser" "^2.0.4"
+ tslib "^2.4.1"
+
+"@amplitude/analytics-client-common@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@amplitude/analytics-client-common/-/analytics-client-common-2.0.4.tgz#59c9a86c68bdd53cc925afe17d295ab47bd9d7b3"
+ integrity sha512-X0+zE8sODcQ2ioj9ZB98Gr/9FCRDiJuSixefaLrfng/4x5VwHK0if8biCqqBHXu6HlMpeMFrCyiABUTDT87QVA==
+ dependencies:
+ "@amplitude/analytics-connector" "^1.4.8"
+ "@amplitude/analytics-core" "^2.0.3"
+ "@amplitude/analytics-types" "^2.1.1"
+ tslib "^2.4.1"
+
+"@amplitude/analytics-connector@^1.4.5", "@amplitude/analytics-connector@^1.4.8":
version "1.4.8"
resolved "https://registry.yarnpkg.com/@amplitude/analytics-connector/-/analytics-connector-1.4.8.tgz#dd801303db2662bc51be7e0194eeb8bd72267c42"
integrity sha512-dFW7c7Wb6Ng7vbmzwbaXZSpqfBx37ukamJV9ErFYYS8vGZK/Hkbt3M7fZHBI4WFU6CCwakr2ZXPme11uGPYWkQ==
+"@amplitude/analytics-core@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@amplitude/analytics-core/-/analytics-core-2.0.3.tgz#491d29707765899abbe7da06d9d3a3677a8a9193"
+ integrity sha512-tpD1gCmPpzPNPumQT1ecOJtuan5OsQdKp9AX8YKc7t1/K3xHzGo3FH3JvdaAJVYYWeZV40bp/JL6wJiYIzyZjA==
+ dependencies:
+ "@amplitude/analytics-types" "^2.1.1"
+ tslib "^2.4.1"
+
+"@amplitude/analytics-types@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@amplitude/analytics-types/-/analytics-types-2.1.1.tgz#89ef85397c4e6bc3a6aeaae4f3d4354ebb7de297"
+ integrity sha512-H3vebPR9onRdp0WzAZmI/4qmAE903uLOd2ZfMeHsVc1zaFTTCk46SoCuV4IrlF+VILrDw9Fy6gC9yl5N2PZcJQ==
+
+"@amplitude/plugin-page-view-tracking-browser@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.4.tgz#294c050f66810619816caa9027cfe6c61567e9b6"
+ integrity sha512-XQlgydCmfUb+mkXjD8NoOU80eO/578m2AupRcV0dPKwk+VN5D7D1s+xXYM6Bg7l1HSJx2mt0Qwa5xsdSCFzF2g==
+ dependencies:
+ "@amplitude/analytics-client-common" "^2.0.4"
+ "@amplitude/analytics-types" "^2.1.1"
+ tslib "^2.4.1"
+
+"@amplitude/plugin-web-attribution-browser@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.4.tgz#30498aaff7c385cb14ebbb3976d6cc8df1794766"
+ integrity sha512-y/VQjDH7tnevRlKO9IWzELF9L87MwiR+y7WCRxR5/wRGKDxKS5Ol33vHYWe1RaOzkC/7wO0px7pZGRZkj/X+1Q==
+ dependencies:
+ "@amplitude/analytics-client-common" "^2.0.4"
+ "@amplitude/analytics-core" "^2.0.3"
+ "@amplitude/analytics-types" "^2.1.1"
+ tslib "^2.4.1"
+
"@amplitude/ua-parser-js@^0.7.31":
version "0.7.31"
resolved "https://registry.yarnpkg.com/@amplitude/ua-parser-js/-/ua-parser-js-0.7.31.tgz#749bf7cb633cfcc7ff3c10805bad7c5f6fbdbc61"
integrity sha512-+z8UGRaj13Pt5NDzOnkTBy49HE2CX64jeL0ArB86HAtilpnfkPB7oqkigN7Lf2LxscMg4QhFD7mmCfedh3rqTg==
+"@amplitude/ua-parser-js@^0.7.33":
+ version "0.7.33"
+ resolved "https://registry.yarnpkg.com/@amplitude/ua-parser-js/-/ua-parser-js-0.7.33.tgz#26441a0fb2e956a64e4ede50fb80b848179bb5db"
+ integrity sha512-wKEtVR4vXuPT9cVEIJkYWnlF++Gx3BdLatPBM+SZ1ztVIvnhdGBZR/mn9x/PzyrMcRlZmyi6L56I2J3doVBnjA==
+
"@ampproject/remapping@^2.1.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
@@ -3778,12 +3837,12 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
-"@rrweb/types@^2.0.0-alpha.4":
- version "2.0.0-alpha.8"
- resolved "https://registry.yarnpkg.com/@rrweb/types/-/types-2.0.0-alpha.8.tgz#29a6550dd833364a459af4be4ce3b233003fb78b"
- integrity sha512-yAr6ZQrgmr7+qZU5DMGqYXnVsolC5epftmZtkOtgFD/bbvCWflNnl09M32hUjttlKCV1ohhmQGioXkCQ37IF7A==
+"@rrweb/types@^2.0.0-alpha.9":
+ version "2.0.0-alpha.9"
+ resolved "https://registry.yarnpkg.com/@rrweb/types/-/types-2.0.0-alpha.9.tgz#960cd224f7de7d0ba86852db0c04783320f92846"
+ integrity sha512-yS2KghLSmSSxo6H7tHrJ6u+nWJA9zCXaKFyc79rUSX8RHHSImRqocTqJ8jz794kCIWA90rvaQayRONdHO+vB0Q==
dependencies:
- rrweb-snapshot "^2.0.0-alpha.8"
+ rrweb-snapshot "^2.0.0-alpha.9"
"@sideway/address@^4.1.3":
version "4.1.4"
@@ -11017,31 +11076,31 @@ rollup@^2.79.1:
optionalDependencies:
fsevents "~2.3.2"
-rrdom@^0.1.7:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/rrdom/-/rrdom-0.1.7.tgz#f2f49bfd01b59291bb7b0d981371a5e02a18e2aa"
- integrity sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==
+rrdom@^2.0.0-alpha.9:
+ version "2.0.0-alpha.9"
+ resolved "https://registry.yarnpkg.com/rrdom/-/rrdom-2.0.0-alpha.9.tgz#a326d4176cec7eb8c9374ed8bb7da784252f09ef"
+ integrity sha512-jfaZ8tHi098P4GpPEtkOwnkucyKA5eGanAVHGPklzCqAeEq1Yx+9/y8AeOtF3yiobqKKkW8lLvFH2KrBH1CZlQ==
dependencies:
- rrweb-snapshot "^2.0.0-alpha.4"
+ rrweb-snapshot "^2.0.0-alpha.9"
-rrweb-snapshot@^2.0.0-alpha.4, rrweb-snapshot@^2.0.0-alpha.8:
- version "2.0.0-alpha.8"
- resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.8.tgz#5f93f8ab7d78b3de26f6a64e993ff68edd54e0cf"
- integrity sha512-3Rb7c+mnDEADQ8N9qn9SDH5PzCyHlZ1cwZC932qRyt9O8kJWLM11JLYqqEyQCa2FZVQbzH2iAaCgnyM7A32p7A==
+rrweb-snapshot@^2.0.0-alpha.9:
+ version "2.0.0-alpha.9"
+ resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.9.tgz#4f504a4f266f0cb075ecbfa35aaf687d00483ca5"
+ integrity sha512-mHg1uUE2iUf0MXLE//4r5cMynkbduwmaOEis4gC7EuqkUAC1pYoLpcYYVt9lD6dgYIF6BmK6dgLLzMpD/tTyyA==
-rrweb@^2.0.0-alpha.4:
- version "2.0.0-alpha.4"
- resolved "https://registry.yarnpkg.com/rrweb/-/rrweb-2.0.0-alpha.4.tgz#3c7cf2f1bcf44f7a88dd3fad00ee8d6dd711f258"
- integrity sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==
+rrweb@^2.0.0-alpha.9:
+ version "2.0.0-alpha.9"
+ resolved "https://registry.yarnpkg.com/rrweb/-/rrweb-2.0.0-alpha.9.tgz#bce02ae1024251ece9d981bc7ebfe6a2eb6bbf9e"
+ integrity sha512-8E2yiLY7IrFjDcVUZ7AcQtdBNFuTIsBrlCMpbyLua6X64dGRhOZ+IUDXLnAbNj5oymZgFtZu2UERG9rmV2VAng==
dependencies:
- "@rrweb/types" "^2.0.0-alpha.4"
+ "@rrweb/types" "^2.0.0-alpha.9"
"@types/css-font-loading-module" "0.0.7"
"@xstate/fsm" "^1.4.0"
base64-arraybuffer "^1.0.1"
fflate "^0.4.4"
mitt "^3.0.0"
- rrdom "^0.1.7"
- rrweb-snapshot "^2.0.0-alpha.4"
+ rrdom "^2.0.0-alpha.9"
+ rrweb-snapshot "^2.0.0-alpha.9"
run-async@^2.4.0:
version "2.4.1"
From 7587aac1c8f277a724aae09b8b1041923bee1d1d Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Thu, 27 Jul 2023 02:07:13 +0000
Subject: [PATCH 073/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.3.0
---
.../CHANGELOG.md | 27 +++++++++++++++++++
.../package.json | 2 +-
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 39e26d536..e1c4d4b45 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.3.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.2.1...@amplitude/plugin-session-replay-browser@0.3.0) (2023-07-27)
+
+### Bug Fixes
+
+- **plugins:** ensure config.optout takes priority over sample rate
+ ([5438a43](https://github.com/amplitude/Amplitude-TypeScript/commit/5438a438ec062b3011234b0fea82b54351a12864))
+- **plugins:** update README for session replay
+ ([ed20cf2](https://github.com/amplitude/Amplitude-TypeScript/commit/ed20cf22180b33f29fd71a1439de26706d6ec4ff))
+
+### Features
+
+- **plugins:** abide by global opt out for session recording
+ ([25bd516](https://github.com/amplitude/Amplitude-TypeScript/commit/25bd516849c9a7fc346c6a868c664e70cdeb5cba))
+- **plugins:** add a configuration option for sampling rate
+ ([bcabfa4](https://github.com/amplitude/Amplitude-TypeScript/commit/bcabfa4ea784187edc85a85bce9a2c68dde411e5))
+- **plugins:** add default tracking of sessions to session replay plugin
+ ([6aeb511](https://github.com/amplitude/Amplitude-TypeScript/commit/6aeb511e5a7db760ebaa86aee0d7756fb85e9020))
+- **plugins:** add masking controls to session replay and reorganize constants and helper fns
+ ([e46a42e](https://github.com/amplitude/Amplitude-TypeScript/commit/e46a42e430c7560a4e9da322b10238fdbf4f1bc5))
+- **plugins:** update config additions to store in idb
+ ([5c04c3c](https://github.com/amplitude/Amplitude-TypeScript/commit/5c04c3cc0e8ef287898f2571f8c2a3e9e00311be))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.2.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.2.0...@amplitude/plugin-session-replay-browser@0.2.1) (2023-07-26)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 0d0902019..02212c532 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.2.1",
+ "version": "0.3.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From ed034358065d8f3dc6da4be9a880de5af001d6c2 Mon Sep 17 00:00:00 2001
From: Andrey Sokolov
Date: Thu, 27 Jul 2023 22:00:02 +0400
Subject: [PATCH 074/214] fix: updated session logic and tests (#447)
* fix: updated session logic and tests
* fix: reverted session start logic in init
* fix: fix appStateChange logic and tests
* fix: do not update config.lastSessionTime for events with assigned session_id
---
.../src/react-native-client.ts | 72 ++-
.../test/react-native-client.test.ts | 43 +-
.../test/react-native-sessions.test.ts | 608 ++++++++++++++++++
3 files changed, 678 insertions(+), 45 deletions(-)
create mode 100644 packages/analytics-react-native/test/react-native-sessions.test.ts
diff --git a/packages/analytics-react-native/src/react-native-client.ts b/packages/analytics-react-native/src/react-native-client.ts
index d10f72d31..5444fb705 100644
--- a/packages/analytics-react-native/src/react-native-client.ts
+++ b/packages/analytics-react-native/src/react-native-client.ts
@@ -84,7 +84,7 @@ export class AmplitudeReactNative extends AmplitudeCore {
// Step 4: Manage session
this.appState = AppState.currentState;
- const isNewSession = this.startNewSessionIfNeeded();
+ const isNewSession = this.startNewSessionIfNeeded(this.currentTimeMillis());
this.config.loggerProvider?.log(
`Init: startNewSessionIfNeeded = ${isNewSession ? 'yes' : 'no'}, sessionId = ${
this.getSessionId() ?? 'undefined'
@@ -230,19 +230,24 @@ export class AmplitudeReactNative extends AmplitudeCore {
event = { ...event, time: eventTime };
}
- if (event.event_type != START_SESSION_EVENT && event.event_type != END_SESSION_EVENT) {
- if (this.appState !== 'active') {
- this.startNewSessionIfNeeded(eventTime);
+ const isSessionEvent = event.event_type === START_SESSION_EVENT || event.event_type === END_SESSION_EVENT;
+ const isCustomEventSessionId =
+ !isSessionEvent && event.session_id != undefined && event.session_id !== this.getSessionId();
+ if (!isCustomEventSessionId) {
+ if (!isSessionEvent) {
+ if (this.appState !== 'active') {
+ this.startNewSessionIfNeeded(eventTime);
+ }
}
+ this.config.lastEventTime = eventTime;
}
- this.config.lastEventTime = eventTime;
if (event.session_id == undefined) {
event.session_id = this.getSessionId();
}
if (event.event_id === undefined) {
- const eventId = (this.config.lastEventId ?? -1) + 1;
+ const eventId = (this.config.lastEventId ?? 0) + 1;
event = { ...event, event_id: eventId };
this.config.lastEventId = eventId;
}
@@ -255,25 +260,31 @@ export class AmplitudeReactNative extends AmplitudeCore {
return Date.now();
}
- private startNewSessionIfNeeded(eventTime?: number): boolean {
- eventTime = eventTime ?? this.currentTimeMillis();
- const sessionId = this.explicitSessionId ?? eventTime;
-
- if (
- this.inSession() &&
- (this.explicitSessionId === this.config.sessionId ||
- (this.explicitSessionId === undefined && this.isWithinMinTimeBetweenSessions(sessionId)))
- ) {
- this.config.lastEventTime = eventTime;
- return false;
+ private startNewSessionIfNeeded(timestamp: number): boolean {
+ const sessionId = this.explicitSessionId ?? timestamp;
+
+ const shouldStartNewSession = this.shouldStartNewSession(timestamp);
+ if (shouldStartNewSession) {
+ this.setSessionIdInternal(sessionId, timestamp);
+ } else {
+ this.config.lastEventTime = timestamp;
}
- this.setSessionIdInternal(sessionId, eventTime);
- return true;
+ return shouldStartNewSession;
+ }
+
+ private shouldStartNewSession(timestamp: number): boolean {
+ const sessionId = this.explicitSessionId ?? timestamp;
+
+ return (
+ !this.inSession() ||
+ (this.explicitSessionId !== this.config.sessionId &&
+ (this.explicitSessionId !== undefined || !this.isWithinMinTimeBetweenSessions(sessionId)))
+ );
}
- private isWithinMinTimeBetweenSessions(eventTime: number) {
- return eventTime - (this.config.lastEventTime ?? 0) < this.config.sessionTimeout;
+ private isWithinMinTimeBetweenSessions(timestamp: number) {
+ return timestamp - (this.config.lastEventTime ?? 0) < this.config.sessionTimeout;
}
private inSession() {
@@ -283,11 +294,24 @@ export class AmplitudeReactNative extends AmplitudeCore {
private readonly handleAppStateChange = (nextAppState: AppStateStatus) => {
const currentAppState = this.appState;
this.appState = nextAppState;
- if (currentAppState !== nextAppState && nextAppState === 'active') {
- this.config.loggerProvider?.log('App Activated');
- this.startNewSessionIfNeeded();
+ if (currentAppState !== nextAppState) {
+ const timestamp = this.currentTimeMillis();
+ if (nextAppState == 'active') {
+ this.enterForeground(timestamp);
+ } else {
+ this.exitForeground(timestamp);
+ }
}
};
+
+ private enterForeground(timestamp: number) {
+ this.config.loggerProvider?.log('App Activated');
+ return this.startNewSessionIfNeeded(timestamp);
+ }
+
+ private exitForeground(timestamp: number) {
+ this.config.lastEventTime = timestamp;
+ }
}
export const createInstance = (): ReactNativeClient => {
diff --git a/packages/analytics-react-native/test/react-native-client.test.ts b/packages/analytics-react-native/test/react-native-client.test.ts
index dde1d017b..8feb641c6 100644
--- a/packages/analytics-react-native/test/react-native-client.test.ts
+++ b/packages/analytics-react-native/test/react-native-client.test.ts
@@ -462,7 +462,8 @@ describe('react-native-client', () => {
this.handleAppStateChange('active');
}
- setBackground() {
+ setBackground(timestamp: number) {
+ this.currentTime = timestamp;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.handleAppStateChange('background');
@@ -500,46 +501,46 @@ describe('react-native-client', () => {
const send = jest.fn().mockReturnValue(sendResponse);
const cookieStorage = new core.MemoryStorage();
- const client1 = new AmplitudeReactNativeTest(500);
+ const client1 = new AmplitudeReactNativeTest(950);
await client1.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
client1.setActive(1000);
- expect(client1.config.sessionId).toEqual(1000);
+ expect(client1.config.sessionId).toEqual(950);
expect(client1.config.lastEventTime).toEqual(1000);
- expect(client1.config.lastEventId).toEqual(2);
+ expect(client1.config.lastEventId).toEqual(1);
void client1.track({ event_type: 'event-1', time: 1200 });
- expect(client1.config.sessionId).toEqual(1000);
+ expect(client1.config.sessionId).toEqual(950);
expect(client1.config.lastEventTime).toEqual(1200);
- expect(client1.config.lastEventId).toEqual(3);
+ expect(client1.config.lastEventId).toEqual(2);
const client2 = new AmplitudeReactNativeTest(1250);
await client2.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
- expect(client2.config.sessionId).toEqual(1000);
+ expect(client2.config.sessionId).toEqual(950);
expect(client2.config.lastEventTime).toEqual(1250);
- expect(client2.config.lastEventId).toEqual(3);
+ expect(client2.config.lastEventId).toEqual(2);
void client2.track({ event_type: 'event-2', time: 1270 });
- expect(client2.config.sessionId).toEqual(1000);
+ expect(client2.config.sessionId).toEqual(950);
expect(client2.config.lastEventTime).toEqual(1270);
- expect(client2.config.lastEventId).toEqual(4);
+ expect(client2.config.lastEventId).toEqual(3);
const client3 = new AmplitudeReactNativeTest(1300);
await client3.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
- expect(client3.config.sessionId).toEqual(1000);
+ expect(client3.config.sessionId).toEqual(950);
expect(client3.config.lastEventTime).toEqual(1300);
- expect(client3.config.lastEventId).toEqual(4);
+ expect(client3.config.lastEventId).toEqual(3);
client3.setActive(1500);
expect(client3.config.sessionId).toEqual(1500);
expect(client3.config.lastEventTime).toEqual(1500);
- expect(client3.config.lastEventId).toEqual(6);
+ expect(client3.config.lastEventId).toEqual(5);
});
describe('track session events', () => {
@@ -560,7 +561,7 @@ describe('react-native-client', () => {
void client.track({ event_type: 'event-5', time: 1700 });
- client.setBackground();
+ client.setBackground(1730);
void client.track({ event_type: 'event-6', time: 1750 });
void client.track({ event_type: 'event-7', time: 2000 });
@@ -572,7 +573,7 @@ describe('react-native-client', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
expect(events.length).toEqual(18);
- events.forEach((event, i) => expect(event.event_id).toEqual(i));
+ events.forEach((event, i) => expect(event.event_id).toEqual(i + 1));
expect(events[0].event_type).toEqual('session_end');
expect(events[0].session_id).toEqual(500);
@@ -671,7 +672,7 @@ describe('react-native-client', () => {
client.currentTime = 1720;
client.setSessionId(5050);
- client.setBackground();
+ client.setBackground(1730);
void client.track({ event_type: 'event-6', time: 1750 });
void client.track({ event_type: 'event-7', time: 2000 });
@@ -685,7 +686,7 @@ describe('react-native-client', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
expect(events.length).toEqual(14);
- events.forEach((event, i) => expect(event.event_id).toEqual(i));
+ events.forEach((event, i) => expect(event.event_id).toEqual(i + 1));
expect(events[0].event_type).toEqual('session_end');
expect(events[0].session_id).toEqual(500);
@@ -763,7 +764,7 @@ describe('react-native-client', () => {
void client.track({ event_type: 'event-5', time: 1700 });
- client.setBackground();
+ client.setBackground(1730);
void client.track({ event_type: 'event-6', time: 1750 });
void client.track({ event_type: 'event-7', time: 2000 });
@@ -775,7 +776,7 @@ describe('react-native-client', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
expect(events.length).toEqual(8);
- events.forEach((event, i) => expect(event.event_id).toEqual(i));
+ events.forEach((event, i) => expect(event.event_id).toEqual(i + 1));
expect(events[0].event_type).toEqual('event-1');
expect(events[0].session_id).toEqual(950);
@@ -834,7 +835,7 @@ describe('react-native-client', () => {
client.currentTime = 1720;
client.setSessionId(5050);
- client.setBackground();
+ client.setBackground(1730);
void client.track({ event_type: 'event-6', time: 1750 });
void client.track({ event_type: 'event-7', time: 2000 });
@@ -848,7 +849,7 @@ describe('react-native-client', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
expect(events.length).toEqual(8);
- events.forEach((event, i) => expect(event.event_id).toEqual(i));
+ events.forEach((event, i) => expect(event.event_id).toEqual(i + 1));
expect(events[0].event_type).toEqual('event-1');
expect(events[0].session_id).toEqual(1000);
diff --git a/packages/analytics-react-native/test/react-native-sessions.test.ts b/packages/analytics-react-native/test/react-native-sessions.test.ts
new file mode 100644
index 000000000..aaed17526
--- /dev/null
+++ b/packages/analytics-react-native/test/react-native-sessions.test.ts
@@ -0,0 +1,608 @@
+import { AmplitudeReactNative } from '../src/react-native-client';
+import * as core from '@amplitude/analytics-core';
+import { Status, UserSession, Event } from '@amplitude/analytics-types';
+import { isWeb } from '../src/utils/platform';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+describe('react-native-session', () => {
+ const API_KEY = 'API_KEY';
+ const attributionConfig = {
+ attribution: {
+ disabled: true,
+ },
+ };
+
+ afterEach(async () => {
+ // clean up cookies
+ // due to jest env, cookies are always preset and needs to be cleaned up
+ document.cookie = 'AMP_API_KEY=null; expires=-1';
+ if (!isWeb()) {
+ await AsyncStorage.clear();
+ }
+ });
+
+ class AmplitudeReactNativeTest extends AmplitudeReactNative {
+ currentTime: number;
+
+ constructor(currentTime: number) {
+ super();
+ this.currentTime = currentTime;
+ }
+
+ currentTimeMillis(): number {
+ return this.currentTime;
+ }
+
+ setForeground(timestamp: number) {
+ this.currentTime = timestamp;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ this.handleAppStateChange('active');
+ }
+
+ setBackground(timestamp: number) {
+ this.currentTime = timestamp;
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ this.handleAppStateChange('background');
+ }
+ }
+
+ const sendResponse = {
+ status: Status.Success,
+ statusCode: 200,
+ body: {
+ eventsIngested: 1,
+ payloadSizeBytes: 1,
+ serverUploadTime: 1,
+ },
+ };
+
+ const clientOptions = (
+ send: any,
+ cookieStorage: core.MemoryStorage,
+ trackingSessionEvents: boolean,
+ sessionId?: number,
+ ) => ({
+ transportProvider: {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ send,
+ },
+ cookieStorage,
+ sessionTimeout: 100,
+ sessionId,
+ trackingSessionEvents,
+ ...attributionConfig,
+ });
+
+ const createEvent = (time: number, eventType: string, sessionId?: number): Event => {
+ return {
+ event_type: eventType,
+ time: time,
+ session_id: sessionId,
+ user_id: 'user',
+ };
+ };
+
+ test('close background events should not start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.track(createEvent(1000, 'event-1'));
+ client.track(createEvent(1050, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(3);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(1050);
+ });
+
+ test('distant background events should start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.track(createEvent(1000, 'event-1'));
+ client.track(createEvent(2000, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(5);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('session_end');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(1001);
+
+ expect(events[3].event_type).toEqual('session_start');
+ expect(events[3].session_id).toEqual(2000);
+ expect(events[3].time).toEqual(2000);
+
+ expect(events[4].event_type).toEqual('event-2');
+ expect(events[4].session_id).toEqual(2000);
+ expect(events[4].time).toEqual(2000);
+ });
+
+ test('foreground events should not start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.setForeground(1000);
+ client.track(createEvent(1050, 'event-1'));
+ client.track(createEvent(2000, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(3);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1050);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(2000);
+ });
+
+ test('close background and foreground events should not start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.track(createEvent(1000, 'event-1'));
+ client.setForeground(1050);
+ client.track(createEvent(2000, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(3);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(2000);
+ });
+
+ test('distant background and foreground events should start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.track(createEvent(1000, 'event-1'));
+ client.setForeground(2000);
+ client.track(createEvent(3000, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(5);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('session_end');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(1001);
+
+ expect(events[3].event_type).toEqual('session_start');
+ expect(events[3].session_id).toEqual(2000);
+ expect(events[3].time).toEqual(2000);
+
+ expect(events[4].event_type).toEqual('event-2');
+ expect(events[4].session_id).toEqual(2000);
+ expect(events[4].time).toEqual(3000);
+ });
+
+ test('close foreground and background events should not start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.setForeground(1000);
+ client.track(createEvent(1500, 'event-1'));
+ client.setBackground(2000);
+ client.track(createEvent(2050, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(3);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1500);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(2050);
+ });
+
+ test('distant foreground and background events should start new session', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.setForeground(1000);
+ client.track(createEvent(1500, 'event-1'));
+ client.setBackground(2000);
+ client.track(createEvent(3000, 'event-2'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(5);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1500);
+
+ expect(events[2].event_type).toEqual('session_end');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(2001);
+
+ expect(events[3].event_type).toEqual('session_start');
+ expect(events[3].session_id).toEqual(3000);
+ expect(events[3].time).toEqual(3000);
+
+ expect(events[4].event_type).toEqual('event-2');
+ expect(events[4].session_id).toEqual(3000);
+ expect(events[4].time).toEqual(3000);
+ });
+
+ test('session data should be persisted', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client1 = new AmplitudeReactNativeTest(950);
+ await client1.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client1.setForeground(1000);
+
+ expect(client1.getSessionId()).toEqual(950);
+ expect(client1.config.sessionId).toEqual(950);
+ expect(client1.config.lastEventTime).toEqual(1000);
+ expect(client1.config.lastEventId).toEqual(1);
+
+ client1.track(createEvent(1200, 'event-1'));
+
+ expect(client1.getSessionId()).toEqual(950);
+ expect(client1.config.sessionId).toEqual(950);
+ expect(client1.config.lastEventTime).toEqual(1200);
+ expect(client1.config.lastEventId).toEqual(2);
+
+ const client2 = new AmplitudeReactNativeTest(1250);
+ await client2.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ expect(client2.getSessionId()).toEqual(950);
+ expect(client2.config.sessionId).toEqual(950);
+ expect(client2.config.lastEventTime).toEqual(1250);
+ expect(client1.config.lastEventId).toEqual(2);
+ });
+
+ test('explicit session for event should be preserved and do not update config.lastEventTime', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ expect(client.config.lastEventTime).toEqual(950);
+ client.track(createEvent(1000, 'event-1'));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1050, 'event-2', 3000));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1100, 'event-3'));
+ expect(client.config.lastEventTime).toEqual(1100);
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(6);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(3000);
+ expect(events[2].time).toEqual(1050);
+
+ expect(events[3].event_type).toEqual('session_end');
+ expect(events[3].session_id).toEqual(950);
+ expect(events[3].time).toEqual(1001);
+
+ expect(events[4].event_type).toEqual('session_start');
+ expect(events[4].session_id).toEqual(1100);
+ expect(events[4].time).toEqual(1100);
+
+ expect(events[5].event_type).toEqual('event-3');
+ expect(events[5].session_id).toEqual(1100);
+ expect(events[5].time).toEqual(1100);
+ });
+
+ test('explicit no session for event should be preserved and do not update config.lastEventTime', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ expect(client.config.lastEventTime).toEqual(950);
+ client.track(createEvent(1000, 'event-1'));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1050, 'event-2', -1));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1100, 'event-3'));
+ expect(client.config.lastEventTime).toEqual(1100);
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(6);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(-1);
+ expect(events[2].time).toEqual(1050);
+
+ expect(events[3].event_type).toEqual('session_end');
+ expect(events[3].session_id).toEqual(950);
+ expect(events[3].time).toEqual(1001);
+
+ expect(events[4].event_type).toEqual('session_start');
+ expect(events[4].session_id).toEqual(1100);
+ expect(events[4].time).toEqual(1100);
+
+ expect(events[5].event_type).toEqual('event-3');
+ expect(events[5].session_id).toEqual(1100);
+ expect(events[5].time).toEqual(1100);
+ });
+
+ test('explicit session for event (equal to current session) should be preserved and update config.lastEventTime', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ expect(client.config.lastEventTime).toEqual(950);
+ client.track(createEvent(1000, 'event-1'));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1050, 'event-2', 950));
+ expect(client.config.lastEventTime).toEqual(1050);
+ client.track(createEvent(1100, 'event-3'));
+ expect(client.config.lastEventTime).toEqual(1100);
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(4);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(950);
+ expect(events[2].time).toEqual(1050);
+
+ expect(events[3].event_type).toEqual('event-3');
+ expect(events[3].session_id).toEqual(950);
+ expect(events[3].time).toEqual(1100);
+ });
+
+ describe('explicit global session', () => {
+ test('explicit session should be used', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(5000);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.setSessionId(5000);
+ client.track(createEvent(1000, 'event-1'));
+ client.track(createEvent(1050, 'event-2'));
+ client.currentTime = 1070;
+ client.setSessionId(6000);
+ client.track(createEvent(1100, 'event-3'));
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(6);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(5000);
+ expect(events[0].time).toEqual(5000);
+
+ expect(events[1].event_type).toEqual('event-1');
+ expect(events[1].session_id).toEqual(5000);
+ expect(events[1].time).toEqual(1000);
+
+ expect(events[2].event_type).toEqual('event-2');
+ expect(events[2].session_id).toEqual(5000);
+ expect(events[2].time).toEqual(1050);
+
+ expect(events[3].event_type).toEqual('session_end');
+ expect(events[3].session_id).toEqual(5000);
+ expect(events[3].time).toEqual(1051);
+
+ expect(events[4].event_type).toEqual('session_start');
+ expect(events[4].session_id).toEqual(6000);
+ expect(events[4].time).toEqual(1070);
+
+ expect(events[5].event_type).toEqual('event-3');
+ expect(events[5].session_id).toEqual(6000);
+ expect(events[5].time).toEqual(1100);
+ });
+
+ test('explicit session for event should be preserved', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.setSessionId(5000);
+ expect(client.config.lastEventTime).toEqual(950);
+ client.track(createEvent(1000, 'event-1'));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1050, 'event-2', 3000));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1100, 'event-3'));
+ expect(client.config.lastEventTime).toEqual(1100);
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(6);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('session_end');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(951);
+
+ expect(events[2].event_type).toEqual('session_start');
+ expect(events[2].session_id).toEqual(5000);
+ expect(events[2].time).toEqual(950);
+
+ expect(events[3].event_type).toEqual('event-1');
+ expect(events[3].session_id).toEqual(5000);
+ expect(events[3].time).toEqual(1000);
+
+ expect(events[4].event_type).toEqual('event-2');
+ expect(events[4].session_id).toEqual(3000);
+ expect(events[4].time).toEqual(1050);
+
+ expect(events[5].event_type).toEqual('event-3');
+ expect(events[5].session_id).toEqual(5000);
+ expect(events[5].time).toEqual(1100);
+ });
+
+ test('explicit no session for event should be preserved', async () => {
+ const send = jest.fn().mockReturnValue(sendResponse);
+ const cookieStorage = new core.MemoryStorage();
+
+ const client = new AmplitudeReactNativeTest(950);
+ await client.init(API_KEY, undefined, clientOptions(send, cookieStorage, true)).promise;
+
+ client.setSessionId(5000);
+ expect(client.config.lastEventTime).toEqual(950);
+ client.track(createEvent(1000, 'event-1'));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1050, 'event-2', -1));
+ expect(client.config.lastEventTime).toEqual(1000);
+ client.track(createEvent(1100, 'event-3'));
+ expect(client.config.lastEventTime).toEqual(1100);
+ await client.flush().promise;
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const events = send.mock.calls.flatMap((call) => call[1].events as Event[]);
+ expect(events.length).toEqual(6);
+
+ expect(events[0].event_type).toEqual('session_start');
+ expect(events[0].session_id).toEqual(950);
+ expect(events[0].time).toEqual(950);
+
+ expect(events[1].event_type).toEqual('session_end');
+ expect(events[1].session_id).toEqual(950);
+ expect(events[1].time).toEqual(951);
+
+ expect(events[2].event_type).toEqual('session_start');
+ expect(events[2].session_id).toEqual(5000);
+ expect(events[2].time).toEqual(950);
+
+ expect(events[3].event_type).toEqual('event-1');
+ expect(events[3].session_id).toEqual(5000);
+ expect(events[3].time).toEqual(1000);
+
+ expect(events[4].event_type).toEqual('event-2');
+ expect(events[4].session_id).toEqual(-1);
+ expect(events[4].time).toEqual(1050);
+
+ expect(events[5].event_type).toEqual('event-3');
+ expect(events[5].session_id).toEqual(5000);
+ expect(events[5].time).toEqual(1100);
+ });
+ });
+});
From fb261b89db96e707de6509ccbf57f319c696ef27 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 27 Jul 2023 13:14:47 -0700
Subject: [PATCH 075/214] feat(plugins): add option to block elements based on
amp-block class in session replay
---
packages/plugin-session-replay-browser/README.md | 2 ++
packages/plugin-session-replay-browser/src/constants.ts | 1 +
packages/plugin-session-replay-browser/src/session-replay.ts | 2 ++
3 files changed, 5 insertions(+)
diff --git a/packages/plugin-session-replay-browser/README.md b/packages/plugin-session-replay-browser/README.md
index 66dd0b95d..cf1a789c3 100644
--- a/packages/plugin-session-replay-browser/README.md
+++ b/packages/plugin-session-replay-browser/README.md
@@ -70,3 +70,5 @@ In your application code, add the class `.amp-unmask` to any __input__ whose tex
#### 2. Mask non-input elements
In your application code, add the class `.amp-mask` to any __non-input element__ whose text you'd like to have masked from the recording. The text in the element, as well as it's children, will all be converted to asterisks.
+#### 3. Block non-text elements
+In your application code, add the class `.amp-block` to any element you would like to have blocked from the recording. The element will appear in the replay as a placeholder with the same dimensions.
diff --git a/packages/plugin-session-replay-browser/src/constants.ts b/packages/plugin-session-replay-browser/src/constants.ts
index d726dbc10..55936ce9e 100644
--- a/packages/plugin-session-replay-browser/src/constants.ts
+++ b/packages/plugin-session-replay-browser/src/constants.ts
@@ -7,6 +7,7 @@ export const DEFAULT_SESSION_REPLAY_PROPERTY = `${DEFAULT_EVENT_PROPERTY_PREFIX}
export const DEFAULT_SESSION_START_EVENT = 'session_start';
export const DEFAULT_SESSION_END_EVENT = 'session_end';
+export const BLOCK_CLASS = 'amp-block';
export const MASK_TEXT_CLASS = 'amp-mask';
export const UNMASK_TEXT_CLASS = 'amp-unmask';
export const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 31b62be68..f25573665 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -4,6 +4,7 @@ import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
import {
+ BLOCK_CLASS,
DEFAULT_SESSION_END_EVENT,
DEFAULT_SESSION_REPLAY_PROPERTY,
DEFAULT_SESSION_START_EVENT,
@@ -207,6 +208,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
packFn: pack,
maskAllInputs: true,
maskTextClass: MASK_TEXT_CLASS,
+ blockClass: BLOCK_CLASS,
maskInputFn,
});
}
From 66d8395c7bac0a60252a80ddf03e037301150b86 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Thu, 27 Jul 2023 21:09:55 +0000
Subject: [PATCH 076/214] chore(release): publish
- @amplitude/analytics-react-native@1.3.3
- @amplitude/plugin-session-replay-browser@0.4.0
---
packages/analytics-react-native/CHANGELOG.md | 12 ++++++++++++
packages/analytics-react-native/package.json | 2 +-
packages/analytics-react-native/src/version.ts | 2 +-
packages/plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
5 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/packages/analytics-react-native/CHANGELOG.md b/packages/analytics-react-native/CHANGELOG.md
index 5159e4202..1110778e2 100644
--- a/packages/analytics-react-native/CHANGELOG.md
+++ b/packages/analytics-react-native/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.3.3](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-react-native@1.3.2...@amplitude/analytics-react-native@1.3.3) (2023-07-27)
+
+### Bug Fixes
+
+- updated session logic and tests ([#447](https://github.com/amplitude/Amplitude-TypeScript/issues/447))
+ ([ed03435](https://github.com/amplitude/Amplitude-TypeScript/commit/ed034358065d8f3dc6da4be9a880de5af001d6c2))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.3.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/analytics-react-native@1.3.1...@amplitude/analytics-react-native@1.3.2) (2023-07-26)
### Bug Fixes
diff --git a/packages/analytics-react-native/package.json b/packages/analytics-react-native/package.json
index b8d85189a..d48e80130 100644
--- a/packages/analytics-react-native/package.json
+++ b/packages/analytics-react-native/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/analytics-react-native",
- "version": "1.3.2",
+ "version": "1.3.3",
"description": "Official React Native SDK",
"keywords": [
"analytics",
diff --git a/packages/analytics-react-native/src/version.ts b/packages/analytics-react-native/src/version.ts
index 45f295c35..181c51b5a 100644
--- a/packages/analytics-react-native/src/version.ts
+++ b/packages/analytics-react-native/src/version.ts
@@ -1 +1 @@
-export const VERSION = '1.3.2';
+export const VERSION = '1.3.3';
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index e1c4d4b45..098898744 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.4.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.3.0...@amplitude/plugin-session-replay-browser@0.4.0) (2023-07-27)
+
+### Features
+
+- **plugins:** add option to block elements based on amp-block class in session replay
+ ([fb261b8](https://github.com/amplitude/Amplitude-TypeScript/commit/fb261b89db96e707de6509ccbf57f319c696ef27))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [0.3.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.2.1...@amplitude/plugin-session-replay-browser@0.3.0) (2023-07-27)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 02212c532..36645d533 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.3.0",
+ "version": "0.4.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 675cb82d248f5eb564150be479228469f12d4fc8 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 27 Jul 2023 15:48:13 -0700
Subject: [PATCH 077/214] fix(plugins): prevent any activity from occurring if
document does not have focus
---
.../src/session-replay.ts | 11 +++++--
.../test/session-replay.test.ts | 29 +++++++++++++++++++
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index f25573665..f2509105a 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -75,8 +75,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
};
}
- await this.initialize(true);
-
const GlobalScope = getGlobalScope();
if (GlobalScope && GlobalScope.window) {
GlobalScope.window.addEventListener('blur', () => {
@@ -87,9 +85,18 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
void this.initialize();
});
}
+
+ if (GlobalScope && GlobalScope.document && GlobalScope.document.hasFocus()) {
+ await this.initialize(true);
+ }
}
async execute(event: Event) {
+ const GlobalScope = getGlobalScope();
+ if (GlobalScope && GlobalScope.document && !GlobalScope.document.hasFocus()) {
+ return Promise.resolve(event);
+ }
+
if (this.shouldRecord) {
event.event_properties = {
...event.event_properties,
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 3c9615ef1..27840a4aa 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -151,6 +151,16 @@ describe('SessionReplayPlugin', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(initialize.mock.calls[0]).toEqual([]);
});
+ test('it should not call initialize if the document does not have focus', () => {
+ const sessionReplay = sessionReplayPlugin();
+ const initialize = jest.spyOn(sessionReplay, 'initialize').mockReturnValueOnce(Promise.resolve());
+ jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
+ document: {
+ hasFocus: () => false,
+ },
+ } as typeof globalThis);
+ expect(initialize).not.toHaveBeenCalled();
+ });
});
describe('initalize', () => {
@@ -410,6 +420,25 @@ describe('SessionReplayPlugin', () => {
});
describe('execute', () => {
+ test('it should return event if document does not have focus', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
+ document: {
+ hasFocus: () => false,
+ },
+ } as typeof globalThis);
+ await sessionReplay.setup(mockConfig);
+ const event = {
+ event_type: 'event_type',
+ event_properties: {
+ property_a: true,
+ property_b: 123,
+ },
+ };
+
+ const executedEvent = await sessionReplay.execute(event);
+ expect(executedEvent).toEqual(event);
+ });
test('should add event property for [Amplitude] Session Recorded', async () => {
const sessionReplay = sessionReplayPlugin();
await sessionReplay.setup(mockConfig);
From 856f91dc11c7f0336a800e299838ef2e2b8556df Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Thu, 27 Jul 2023 23:15:02 +0000
Subject: [PATCH 078/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.4.1
---
packages/plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 098898744..086db4822 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.4.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.4.0...@amplitude/plugin-session-replay-browser@0.4.1) (2023-07-27)
+
+### Bug Fixes
+
+- **plugins:** prevent any activity from occurring if document does not have focus
+ ([675cb82](https://github.com/amplitude/Amplitude-TypeScript/commit/675cb82d248f5eb564150be479228469f12d4fc8))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [0.4.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.3.0...@amplitude/plugin-session-replay-browser@0.4.0) (2023-07-27)
### Features
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 36645d533..5874170a3 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.4.0",
+ "version": "0.4.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 2d4362f845148e7f45411e1e7db8482cd71366ab Mon Sep 17 00:00:00 2001
From: Kevin Pagtakhan
Date: Sun, 30 Jul 2023 23:11:30 -0700
Subject: [PATCH 079/214] chore:
plugin-user-agent-enrichment-browser@1.0.0-beta.0 (#513)
---
packages/plugin-user-agent-enrichment-browser/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
index a801a263a..9367f675a 100644
--- a/packages/plugin-user-agent-enrichment-browser/package.json
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-user-agent-enrichment-browser",
- "version": "0.1.0",
+ "version": "1.0.0-beta.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From d7c22fca2eda201f427d6a730abb5e82fb7e66dc Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Mon, 31 Jul 2023 06:31:05 +0000
Subject: [PATCH 080/214] chore(release): publish
- @amplitude/plugin-user-agent-enrichment-browser@1.0.0-beta.1
---
.../plugin-user-agent-enrichment-browser/CHANGELOG.md | 9 +++++++++
.../plugin-user-agent-enrichment-browser/package.json | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
index 649bb0b8f..87379728b 100644
--- a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
+++ b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.0.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-user-agent-enrichment-browser@0.1.0...@amplitude/plugin-user-agent-enrichment-browser@1.0.0-beta.1) (2023-07-31)
+
+**Note:** Version bump only for package @amplitude/plugin-user-agent-enrichment-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# 0.1.0 (2023-07-26)
### Features
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
index 9367f675a..5cd62c93b 100644
--- a/packages/plugin-user-agent-enrichment-browser/package.json
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-user-agent-enrichment-browser",
- "version": "1.0.0-beta.0",
+ "version": "1.0.0-beta.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 1e8317b64d8868bd4b5e584f50ffec4ca54dcadc Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 31 Jul 2023 13:25:33 -0400
Subject: [PATCH 081/214] fix(plugins): correctly fetch shouldRecord from store
and clean up old store entries
---
.../src/constants.ts | 1 +
.../src/session-replay.ts | 32 +++---
.../test/session-replay.test.ts | 100 +++++++++++-------
3 files changed, 79 insertions(+), 54 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/constants.ts b/packages/plugin-session-replay-browser/src/constants.ts
index 55936ce9e..77fda5a2e 100644
--- a/packages/plugin-session-replay-browser/src/constants.ts
+++ b/packages/plugin-session-replay-browser/src/constants.ts
@@ -21,3 +21,4 @@ export const defaultSessionStore: IDBStoreSession = {
currentSequenceId: 0,
sessionSequences: {},
};
+export const MAX_IDB_STORAGE_LENGTH = 1000 * 60 * 60 * 24 * 3; // 3 days
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index f2509105a..a236bf4e4 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -10,6 +10,7 @@ import {
DEFAULT_SESSION_START_EVENT,
MASK_TEXT_CLASS,
MAX_EVENT_LIST_SIZE_IN_BYTES,
+ MAX_IDB_STORAGE_LENGTH,
MAX_INTERVAL,
MIN_INTERVAL,
SESSION_REPLAY_SERVER_URL,
@@ -151,8 +152,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
setShouldRecord(sessionStore?: IDBStoreSession) {
- if (sessionStore?.shouldRecord === false) {
- this.shouldRecord = false;
+ if (sessionStore && typeof sessionStore.shouldRecord === 'boolean') {
+ this.shouldRecord = sessionStore.shouldRecord;
} else if (this.config.optOut) {
this.shouldRecord = false;
} else if (this.options && this.options.sampleRate) {
@@ -324,7 +325,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
};
const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
if (res === null) {
- this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE, removeEvents: false });
+ this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE });
return;
}
if (!useRetry) {
@@ -339,7 +340,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.handleReponse(res.status, context);
}
} catch (e) {
- this.completeRequest({ context, err: e as string, removeEvents: false });
+ this.completeRequest({ context, err: e as string });
}
}
@@ -431,6 +432,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
sequenceToUpdate.events = [];
sequenceToUpdate.status = RecordingStatus.SENT;
+ // Delete sent sequences for current session
Object.entries(session.sessionSequences).forEach(([storedSeqId, sequence]) => {
const numericStoredSeqId = parseInt(storedSeqId, 10);
if (sequence.status === RecordingStatus.SENT && sequenceId !== numericStoredSeqId) {
@@ -438,6 +440,14 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
});
+ // Delete any sessions that are older than 3 days
+ Object.keys(sessionMap).forEach((sessionId: string) => {
+ const numericSessionId = parseInt(sessionId, 10);
+ if (Date.now() - numericSessionId >= MAX_IDB_STORAGE_LENGTH) {
+ delete sessionMap[numericSessionId];
+ }
+ });
+
return sessionMap;
});
} catch (e) {
@@ -445,18 +455,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
}
- completeRequest({
- context,
- err,
- success,
- removeEvents = true,
- }: {
- context: SessionReplayContext;
- err?: string;
- success?: string;
- removeEvents?: boolean;
- }) {
- removeEvents && context.sessionId && this.cleanUpSessionEventsStore(context.sessionId, context.sequenceId);
+ completeRequest({ context, err, success }: { context: SessionReplayContext; err?: string; success?: string }) {
+ context.sessionId && this.cleanUpSessionEventsStore(context.sessionId, context.sequenceId);
if (err) {
this.config.loggerProvider.error(err);
} else if (success) {
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 27840a4aa..0af535de0 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -371,6 +371,18 @@ describe('SessionReplayPlugin', () => {
sessionReplay.setShouldRecord(sessionStore);
expect(sessionReplay.shouldRecord).toBe(false);
});
+ test('should set record as true if true in session store', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ sessionReplay.shouldRecord = false; // Set to false to be sure the setShouldRecord changes the value
+ const sessionStore = {
+ shouldRecord: true,
+ currentSequenceId: 0,
+ sessionSequences: {},
+ };
+ sessionReplay.setShouldRecord(sessionStore);
+ expect(sessionReplay.shouldRecord).toBe(true);
+ });
test('should not set record as false if no options', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
@@ -782,7 +794,7 @@ describe('SessionReplayPlugin', () => {
expect(cleanUpSessionEventsStore).toHaveBeenCalledTimes(1);
expect(cleanUpSessionEventsStore.mock.calls[0]).toEqual([123, 1]);
});
- test('should not remove session events from IDB store upon failure', async () => {
+ test('should remove session events from IDB store upon failure', async () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const context = {
@@ -792,12 +804,15 @@ describe('SessionReplayPlugin', () => {
attempts: 0,
timeout: 0,
};
+ const cleanUpSessionEventsStore = jest
+ .spyOn(sessionReplay, 'cleanUpSessionEventsStore')
+ .mockReturnValueOnce(Promise.resolve());
(global.fetch as jest.Mock).mockImplementationOnce(() => Promise.reject());
await sessionReplay.send(context);
jest.runAllTimers();
-
- expect(update).not.toHaveBeenCalled();
+ expect(cleanUpSessionEventsStore).toHaveBeenCalledTimes(1);
+ expect(cleanUpSessionEventsStore.mock.calls[0]).toEqual([123, 1]);
});
test('should retry if retry param is true', async () => {
@@ -1260,10 +1275,12 @@ describe('SessionReplayPlugin', () => {
describe('cleanUpSessionEventsStore', () => {
test('should update events and status for current session', async () => {
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:30:00').getTime());
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
+ const currentSessionId = new Date('2023-07-31 07:30:00').getTime();
const mockIDBStore: IDBStore = {
- 123: {
+ [currentSessionId]: {
shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
@@ -1277,22 +1294,12 @@ describe('SessionReplayPlugin', () => {
},
},
},
- 456: {
- shouldRecord: true,
- currentSequenceId: 1,
- sessionSequences: {
- 1: {
- events: [],
- status: RecordingStatus.SENT,
- },
- },
- },
};
- await sessionReplay.cleanUpSessionEventsStore(123, 3);
+ await sessionReplay.cleanUpSessionEventsStore(currentSessionId, 3);
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
- 123: {
+ [currentSessionId]: {
shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
@@ -1306,24 +1313,16 @@ describe('SessionReplayPlugin', () => {
},
},
},
- 456: {
- shouldRecord: true,
- currentSequenceId: 1,
- sessionSequences: {
- 1: {
- events: [],
- status: RecordingStatus.SENT,
- },
- },
- },
});
});
test('should delete sent sequences for current session', async () => {
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:30:00').getTime());
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
+ const currentSessionId = new Date('2023-07-31 07:30:00').getTime();
const mockIDBStore: IDBStore = {
- 123: {
+ [currentSessionId]: {
shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
@@ -1337,22 +1336,42 @@ describe('SessionReplayPlugin', () => {
},
},
},
- 456: {
+ };
+ await sessionReplay.cleanUpSessionEventsStore(currentSessionId, 3);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ [currentSessionId]: {
shouldRecord: true,
- currentSequenceId: 1,
+ currentSequenceId: 3,
sessionSequences: {
- 1: {
+ 3: {
events: [],
status: RecordingStatus.SENT,
},
},
},
- };
- await sessionReplay.cleanUpSessionEventsStore(123, 3);
+ });
+ });
- expect(update).toHaveBeenCalledTimes(1);
- expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
- 123: {
+ test('should keep sessions that are less than 3 days old, and delete sessions that are more than 3 days old', async () => {
+ jest.useFakeTimers().setSystemTime(new Date('2023-07-31 08:30:00').getTime());
+ const fourDayOldSessionId = new Date('2023-07-27 07:30:00').getTime();
+ const oneDayOldSessionId = new Date('2023-07-30 07:30:00').getTime();
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ const mockIDBStore: IDBStore = {
+ [oneDayOldSessionId]: {
+ shouldRecord: true,
+ currentSequenceId: 3,
+ sessionSequences: {
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.SENDING,
+ },
+ },
+ },
+ [fourDayOldSessionId]: {
shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
@@ -1362,11 +1381,16 @@ describe('SessionReplayPlugin', () => {
},
},
},
- 456: {
+ };
+ await sessionReplay.cleanUpSessionEventsStore(oneDayOldSessionId, 3);
+
+ expect(update).toHaveBeenCalledTimes(1);
+ expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
+ [oneDayOldSessionId]: {
shouldRecord: true,
- currentSequenceId: 1,
+ currentSequenceId: 3,
sessionSequences: {
- 1: {
+ 3: {
events: [],
status: RecordingStatus.SENT,
},
From b279f0df007ae0c8e7f8a97e12530af795c473bc Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Mon, 31 Jul 2023 17:41:31 +0000
Subject: [PATCH 082/214] chore(release): publish
- @amplitude/plugin-user-agent-enrichment-browser@1.0.0
---
.../plugin-user-agent-enrichment-browser/CHANGELOG.md | 9 +++++++++
.../plugin-user-agent-enrichment-browser/package.json | 2 +-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
index 87379728b..cffa9af53 100644
--- a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
+++ b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-user-agent-enrichment-browser@1.0.0-beta.1...@amplitude/plugin-user-agent-enrichment-browser@1.0.0) (2023-07-31)
+
+**Note:** Version bump only for package @amplitude/plugin-user-agent-enrichment-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.0.0-beta.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-user-agent-enrichment-browser@0.1.0...@amplitude/plugin-user-agent-enrichment-browser@1.0.0-beta.1) (2023-07-31)
**Note:** Version bump only for package @amplitude/plugin-user-agent-enrichment-browser
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
index 5cd62c93b..f842130ad 100644
--- a/packages/plugin-user-agent-enrichment-browser/package.json
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-user-agent-enrichment-browser",
- "version": "1.0.0-beta.1",
+ "version": "1.0.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 016865335d05371849738403817410e2afc0ca57 Mon Sep 17 00:00:00 2001
From: Jackson Huang
Date: Mon, 31 Jul 2023 15:21:49 -0700
Subject: [PATCH 083/214] feat(session replay eu): route rraffic to eu based on
config
---
.../src/constants.ts | 1 +
.../src/session-replay.ts | 13 ++++++--
.../test/session-replay.test.ts | 30 ++++++++++++++++++-
3 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/constants.ts b/packages/plugin-session-replay-browser/src/constants.ts
index 55936ce9e..c014410bf 100644
--- a/packages/plugin-session-replay-browser/src/constants.ts
+++ b/packages/plugin-session-replay-browser/src/constants.ts
@@ -11,6 +11,7 @@ export const BLOCK_CLASS = 'amp-block';
export const MASK_TEXT_CLASS = 'amp-mask';
export const UNMASK_TEXT_CLASS = 'amp-unmask';
export const SESSION_REPLAY_SERVER_URL = 'https://api-secure.amplitude.com/sessions/track';
+export const SESSION_REPLAY_EU_URL = 'https://api.eu.amplitude.com/sessions/track';
export const STORAGE_PREFIX = `${AMPLITUDE_PREFIX}_replay_unsent`;
const PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS = 500; // derived by JSON stringifying an example payload without events
export const MAX_EVENT_LIST_SIZE_IN_BYTES = 10 * 1000000 - PAYLOAD_ESTIMATED_SIZE_IN_BYTES_WITHOUT_EVENTS;
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index f2509105a..5073f7b4d 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -1,6 +1,6 @@
import { getGlobalScope } from '@amplitude/analytics-client-common';
import { BaseTransport } from '@amplitude/analytics-core';
-import { BrowserConfig, Event, Status } from '@amplitude/analytics-types';
+import { BrowserConfig, Event, ServerZone, Status } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import { pack, record } from 'rrweb';
import {
@@ -15,6 +15,7 @@ import {
SESSION_REPLAY_SERVER_URL,
STORAGE_PREFIX,
defaultSessionStore,
+ SESSION_REPLAY_EU_URL as SESSION_REPLAY_EU_SERVER_URL,
} from './constants';
import { maskInputFn } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from './messages';
@@ -301,6 +302,13 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
await Promise.all(list.map((context) => this.send(context, useRetry)));
}
+ getServerUrl() {
+ if (this.config.serverZone === ServerZone.EU) {
+ return SESSION_REPLAY_EU_SERVER_URL;
+ }
+ return SESSION_REPLAY_SERVER_URL;
+ }
+
async send(context: SessionReplayContext, useRetry = true) {
const payload = {
api_key: this.config.apiKey,
@@ -322,7 +330,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
body: JSON.stringify(payload),
method: 'POST',
};
- const res = await fetch(SESSION_REPLAY_SERVER_URL, options);
+ const server_url = this.getServerUrl();
+ const res = await fetch(server_url, options);
if (res === null) {
this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE, removeEvents: false });
return;
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 27840a4aa..dc2fa1b66 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -1,7 +1,7 @@
/* eslint-disable jest/expect-expect */
import * as AnalyticsClientCommon from '@amplitude/analytics-client-common';
import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
-import { BrowserConfig, LogLevel, Logger } from '@amplitude/analytics-types';
+import { BrowserConfig, LogLevel, Logger, ServerZone } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
import { UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from '../src/messages';
@@ -764,6 +764,34 @@ describe('SessionReplayPlugin', () => {
method: 'POST',
});
});
+ test('should make a request to eu', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = {
+ ...mockConfig,
+ serverZone: ServerZone.EU,
+ };
+
+ const context = {
+ events: [mockEventString],
+ sequenceId: 1,
+ sessionId: 123,
+ attempts: 0,
+ timeout: 0,
+ };
+
+ await sessionReplay.send(context);
+ expect(fetch).toHaveBeenCalledTimes(1);
+ expect(fetch).toHaveBeenCalledWith('https://api.eu.amplitude.com/sessions/track', {
+ body: JSON.stringify({
+ api_key: 'static_key',
+ session_id: 123,
+ start_timestamp: 123,
+ events_batch: { version: 1, events: [mockEventString], seq_number: 1 },
+ }),
+ headers: { Accept: '*/*', 'Content-Type': 'application/json' },
+ method: 'POST',
+ });
+ });
test('should update IDB store upon success', async () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
From e641477064763d7dedf41bcd870aa149cccf1832 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 1 Aug 2023 16:19:38 +0000
Subject: [PATCH 084/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.4.2
---
packages/plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 086db4822..988ab159a 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.4.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.4.1...@amplitude/plugin-session-replay-browser@0.4.2) (2023-08-01)
+
+### Bug Fixes
+
+- **plugins:** correctly fetch shouldRecord from store and clean up old store entries
+ ([1e8317b](https://github.com/amplitude/Amplitude-TypeScript/commit/1e8317b64d8868bd4b5e584f50ffec4ca54dcadc))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.4.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.4.0...@amplitude/plugin-session-replay-browser@0.4.1) (2023-07-27)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 5874170a3..9145d117d 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.4.1",
+ "version": "0.4.2",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 2857038310110b9bae57ad45295107895586a847 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Tue, 1 Aug 2023 16:31:27 -0400
Subject: [PATCH 085/214] fix(plugins): fix edge case when opening tab in
background
---
.../src/session-replay.ts | 11 +++++---
.../test/session-replay.test.ts | 25 +++++++++++++++++++
2 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 76e358aaa..fe18419f4 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -130,6 +130,12 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
return;
}
const storedReplaySessions = await this.getAllSessionEventsFromStore();
+ // This resolves a timing issue when focus is fired multiple times in short succession,
+ // we only want the rest of this function to run once. We can be sure that initialize has
+ // already been called if this.stopRecordingEvents is defined
+ if (this.stopRecordingEvents) {
+ return;
+ }
const storedSequencesForSession = storedReplaySessions && storedReplaySessions[this.config.sessionId];
if (storedReplaySessions && storedSequencesForSession && storedSequencesForSession.sessionSequences) {
const storedSeqId = storedSequencesForSession.currentSequenceId;
@@ -147,9 +153,8 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
if (shouldSendStoredEvents && storedReplaySessions) {
this.sendStoredEvents(storedReplaySessions);
}
- if (!this.stopRecordingEvents) {
- this.recordEvents();
- }
+
+ this.recordEvents();
}
setShouldRecord(sessionStore?: IDBStoreSession) {
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 920580e6d..a61465e58 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -248,6 +248,31 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.initialize();
expect(getAllSessionEventsFromStore).not.toHaveBeenCalled();
});
+ test('should return early if stopRecordingEvents is already defined, signaling that recording is already in progress', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = mockConfig;
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ sessionReplay.stopRecordingEvents = () => {};
+ const getAllSessionEventsFromStore = jest
+ .spyOn(sessionReplay, 'getAllSessionEventsFromStore')
+ .mockReturnValueOnce(
+ Promise.resolve({
+ 123: {
+ shouldRecord: true,
+ currentSequenceId: 3,
+ sessionSequences: {
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ }),
+ );
+ await sessionReplay.initialize();
+ expect(getAllSessionEventsFromStore).toHaveBeenCalled();
+ expect(sessionReplay.events).toEqual([]); // events should not be updated to match what is in the store
+ });
test('should configure current sequence id and events correctly if last sequence was sending', async () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
From a9293cb635a4c088ec928f2205f2d19cc47ec4e1 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 2 Aug 2023 00:38:08 +0000
Subject: [PATCH 086/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.5.0
---
.../plugin-session-replay-browser/CHANGELOG.md | 17 +++++++++++++++++
.../plugin-session-replay-browser/package.json | 2 +-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index 988ab159a..fadc4cf41 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.5.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.4.2...@amplitude/plugin-session-replay-browser@0.5.0) (2023-08-02)
+
+### Bug Fixes
+
+- **plugins:** fix edge case when opening tab in background
+ ([2857038](https://github.com/amplitude/Amplitude-TypeScript/commit/2857038310110b9bae57ad45295107895586a847))
+
+### Features
+
+- **session replay eu:** route rraffic to eu based on config
+ ([0168653](https://github.com/amplitude/Amplitude-TypeScript/commit/016865335d05371849738403817410e2afc0ca57))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.4.2](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.4.1...@amplitude/plugin-session-replay-browser@0.4.2) (2023-08-01)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 9145d117d..46fb83ded 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.4.2",
+ "version": "0.5.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 4c7e5791b02114caf5ae95d7612fef0f339fbfee Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Thu, 3 Aug 2023 16:20:56 -0400
Subject: [PATCH 087/214] fix(plugins): use hash to calculate is session is
included in sample
---
.../src/constants.ts | 1 -
.../src/helpers.ts | 19 ++
.../src/session-replay.ts | 62 ++---
.../src/typings/session-replay.ts | 5 +-
.../test/helpers.test.ts | 28 +-
.../test/session-replay.test.ts | 263 ++++--------------
6 files changed, 118 insertions(+), 260 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/constants.ts b/packages/plugin-session-replay-browser/src/constants.ts
index 2b1f5046c..8e02f45a8 100644
--- a/packages/plugin-session-replay-browser/src/constants.ts
+++ b/packages/plugin-session-replay-browser/src/constants.ts
@@ -18,7 +18,6 @@ export const MAX_EVENT_LIST_SIZE_IN_BYTES = 10 * 1000000 - PAYLOAD_ESTIMATED_SIZ
export const MIN_INTERVAL = 500; // 500 ms
export const MAX_INTERVAL = 10 * 1000; // 10 seconds
export const defaultSessionStore: IDBStoreSession = {
- shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {},
};
diff --git a/packages/plugin-session-replay-browser/src/helpers.ts b/packages/plugin-session-replay-browser/src/helpers.ts
index 3aeaaaf12..3070fb1ca 100644
--- a/packages/plugin-session-replay-browser/src/helpers.ts
+++ b/packages/plugin-session-replay-browser/src/helpers.ts
@@ -6,3 +6,22 @@ export const maskInputFn = (text: string, element: HTMLElement) => {
}
return '*'.repeat(text.length);
};
+
+export const generateHashCode = function (str: string) {
+ let hash = 0;
+ if (str.length === 0) return hash;
+ for (let i = 0; i < str.length; i++) {
+ const chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr;
+ hash |= 0;
+ }
+ return hash;
+};
+
+export const isSessionInSample = function (sessionId: number, sampleRate: number) {
+ const hashNumber = generateHashCode(sessionId.toString());
+ const absHash = Math.abs(hashNumber);
+ const absHashMultiply = absHash * 31;
+ const mod = absHashMultiply % 100;
+ return mod / 100 < sampleRate;
+};
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index fe18419f4..781062278 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -13,12 +13,12 @@ import {
MAX_IDB_STORAGE_LENGTH,
MAX_INTERVAL,
MIN_INTERVAL,
+ SESSION_REPLAY_EU_URL as SESSION_REPLAY_EU_SERVER_URL,
SESSION_REPLAY_SERVER_URL,
STORAGE_PREFIX,
defaultSessionStore,
- SESSION_REPLAY_EU_URL as SESSION_REPLAY_EU_SERVER_URL,
} from './constants';
-import { maskInputFn } from './helpers';
+import { isSessionInSample, maskInputFn } from './helpers';
import { MAX_RETRIES_EXCEEDED_MESSAGE, STORAGE_FAILURE, UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from './messages';
import {
Events,
@@ -48,7 +48,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
interval = MIN_INTERVAL;
timeAtLastSend: number | null = null;
options: SessionReplayOptions;
- shouldRecord = true;
constructor(options?: SessionReplayOptions) {
this.options = { ...options };
@@ -98,15 +97,7 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
if (GlobalScope && GlobalScope.document && !GlobalScope.document.hasFocus()) {
return Promise.resolve(event);
}
-
- if (this.shouldRecord) {
- event.event_properties = {
- ...event.event_properties,
- [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
- };
- }
if (event.event_type === DEFAULT_SESSION_START_EVENT && !this.stopRecordingEvents) {
- this.setShouldRecord();
this.recordEvents();
} else if (event.event_type === DEFAULT_SESSION_END_EVENT) {
if (event.session_id && this.events.length) {
@@ -121,6 +112,15 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.events = [];
this.currentSequenceId = 0;
}
+
+ const shouldRecord = this.getShouldRecord();
+ if (shouldRecord) {
+ event.event_properties = {
+ ...event.event_properties,
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
+ };
+ }
+
return Promise.resolve(event);
}
@@ -149,27 +149,21 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.events = lastSequence?.events || [];
}
}
- this.setShouldRecord(storedSequencesForSession);
if (shouldSendStoredEvents && storedReplaySessions) {
this.sendStoredEvents(storedReplaySessions);
}
-
this.recordEvents();
}
- setShouldRecord(sessionStore?: IDBStoreSession) {
- if (sessionStore && typeof sessionStore.shouldRecord === 'boolean') {
- this.shouldRecord = sessionStore.shouldRecord;
- } else if (this.config.optOut) {
- this.shouldRecord = false;
+ getShouldRecord() {
+ if (this.config.optOut) {
+ return false;
+ } else if (!this.config.sessionId) {
+ return false;
} else if (this.options && this.options.sampleRate) {
- this.shouldRecord = Math.random() < this.options.sampleRate;
- }
-
- this.config.sessionId && void this.storeShouldRecordForSession(this.config.sessionId, this.shouldRecord);
- if (!this.shouldRecord && this.config.sessionId) {
- this.config.loggerProvider.log(`Opting session ${this.config.sessionId} out of recording.`);
+ return isSessionInSample(this.config.sessionId, this.options.sampleRate);
}
+ return true;
}
sendStoredEvents(storedReplaySessions: IDBStore) {
@@ -194,7 +188,9 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
recordEvents() {
- if (!this.shouldRecord) {
+ const shouldRecord = this.getShouldRecord();
+ if (!shouldRecord && this.config.sessionId) {
+ this.config.loggerProvider.log(`Opting session ${this.config.sessionId} out of recording.`);
return;
}
this.stopRecordingEvents = record({
@@ -418,22 +414,6 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
}
- async storeShouldRecordForSession(sessionId: number, shouldRecord: boolean) {
- try {
- await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
- const session: IDBStoreSession = sessionMap[sessionId] || { ...defaultSessionStore };
- session.shouldRecord = shouldRecord;
-
- return {
- ...sessionMap,
- [sessionId]: session,
- };
- });
- } catch (e) {
- this.config.loggerProvider.error(`${STORAGE_FAILURE}: ${e as string}`);
- }
- }
-
async cleanUpSessionEventsStore(sessionId: number, sequenceId: number) {
try {
await IDBKeyVal.update(this.storageKey, (sessionMap: IDBStore = {}): IDBStore => {
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index d0b5ed359..5e2924a90 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -27,7 +27,6 @@ export interface IDBStoreSequence {
}
export interface IDBStoreSession {
- shouldRecord: boolean;
currentSequenceId: number;
sessionSequences: {
[sequenceId: number]: IDBStoreSequence;
@@ -45,13 +44,12 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
events: Events;
currentSequenceId: number;
interval: number;
- shouldRecord: boolean;
queue: SessionReplayContext[];
timeAtLastSend: number | null;
stopRecordingEvents: ReturnType | null;
maxPersistedEventsSize: number;
initialize: (shouldSendStoredEvents?: boolean) => Promise;
- setShouldRecord: (sessionStore?: IDBStoreSession) => void;
+ getShouldRecord: () => boolean;
recordEvents: () => void;
shouldSplitEventsList: (nextEventString: string) => boolean;
sendEventsList: ({
@@ -80,7 +78,6 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
}): void;
getAllSessionEventsFromStore: () => Promise;
storeEventsForSession: (events: Events, sequenceId: number, sessionId: number) => Promise;
- storeShouldRecordForSession: (sessionId: number, shouldRecord: boolean) => Promise;
cleanUpSessionEventsStore: (sessionId: number, sequenceId: number) => Promise;
}
diff --git a/packages/plugin-session-replay-browser/test/helpers.test.ts b/packages/plugin-session-replay-browser/test/helpers.test.ts
index e2283874e..a51e63faa 100644
--- a/packages/plugin-session-replay-browser/test/helpers.test.ts
+++ b/packages/plugin-session-replay-browser/test/helpers.test.ts
@@ -1,5 +1,5 @@
import { UNMASK_TEXT_CLASS } from '../src/constants';
-import { maskInputFn } from '../src/helpers';
+import { generateHashCode, isSessionInSample, maskInputFn } from '../src/helpers';
describe('SessionReplayPlugin helpers', () => {
describe('maskInputFn', () => {
@@ -21,4 +21,30 @@ describe('SessionReplayPlugin helpers', () => {
expect(result).toEqual('*********');
});
});
+
+ describe('generateHashCode', () => {
+ test('should return 0 if string length is 0', () => {
+ const hashCode = generateHashCode('');
+ expect(hashCode).toEqual(0);
+ });
+ test('should return hash for numeric string', () => {
+ const hashCode = generateHashCode('1691093770366');
+ expect(hashCode).toEqual(139812688);
+ });
+ test('should return hash for alphabetic string', () => {
+ const hashCode = generateHashCode('my_session_identifier');
+ expect(hashCode).toEqual(989939557);
+ });
+ });
+
+ describe('isSessionInSample', () => {
+ test('should deterministically return true if calculation puts session id below sample rate', () => {
+ const result = isSessionInSample(1691092433788, 0.5);
+ expect(result).toEqual(true);
+ });
+ test('should deterministically return false if calculation puts session id above sample rate', () => {
+ const result = isSessionInSample(1691092416403, 0.5);
+ expect(result).toEqual(false);
+ });
+ });
});
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index a61465e58..297f8cf8c 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -4,6 +4,8 @@ import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-commo
import { BrowserConfig, LogLevel, Logger, ServerZone } from '@amplitude/analytics-types';
import * as IDBKeyVal from 'idb-keyval';
import * as RRWeb from 'rrweb';
+import { DEFAULT_SESSION_REPLAY_PROPERTY } from '../src/constants';
+import * as Helpers from '../src/helpers';
import { UNEXPECTED_ERROR_MESSAGE, getSuccessMessage } from '../src/messages';
import { sessionReplayPlugin } from '../src/session-replay';
import { IDBStore, RecordingStatus } from '../src/typings/session-replay';
@@ -173,7 +175,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = config;
const mockGetResolution: Promise = Promise.resolve({
123: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -183,7 +184,6 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -278,7 +278,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockGetResolution: Promise = Promise.resolve({
123: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -298,7 +297,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockGetResolution: Promise = Promise.resolve({
123: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -327,7 +325,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockGetResolution = Promise.resolve({
123: {
- shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {},
},
@@ -383,55 +380,30 @@ describe('SessionReplayPlugin', () => {
});
});
- describe('setShouldRecord', () => {
- test('should set record as false if false in session store', () => {
+ describe('getShouldRecord', () => {
+ test('should return true if no options', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
- const sessionStore = {
- shouldRecord: false,
- currentSequenceId: 0,
- sessionSequences: {},
- };
- expect(sessionReplay.shouldRecord).toBe(true);
- sessionReplay.setShouldRecord(sessionStore);
- expect(sessionReplay.shouldRecord).toBe(false);
- });
- test('should set record as true if true in session store', () => {
- const sessionReplay = sessionReplayPlugin();
- sessionReplay.config = mockConfig;
- sessionReplay.shouldRecord = false; // Set to false to be sure the setShouldRecord changes the value
- const sessionStore = {
- shouldRecord: true,
- currentSequenceId: 0,
- sessionSequences: {},
- };
- sessionReplay.setShouldRecord(sessionStore);
- expect(sessionReplay.shouldRecord).toBe(true);
- });
- test('should not set record as false if no options', () => {
- const sessionReplay = sessionReplayPlugin();
- sessionReplay.config = mockConfig;
- sessionReplay.setShouldRecord();
- expect(sessionReplay.shouldRecord).toBe(true);
+ const shouldRecord = sessionReplay.getShouldRecord();
+ expect(shouldRecord).toBe(true);
});
- test('should set record as false if session not included in sample rate', () => {
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ test('should return false if session not included in sample rate', () => {
+ jest.spyOn(Helpers, 'isSessionInSample').mockImplementationOnce(() => false);
const sessionReplay = sessionReplayPlugin({
sampleRate: 0.2,
});
sessionReplay.config = mockConfig;
- expect(sessionReplay.shouldRecord).toBe(true);
- sessionReplay.setShouldRecord();
- expect(sessionReplay.shouldRecord).toBe(false);
+ const shouldRecord = sessionReplay.getShouldRecord();
+ expect(shouldRecord).toBe(false);
});
test('should set record as true if session is included in sample rate', () => {
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ jest.spyOn(Helpers, 'isSessionInSample').mockImplementationOnce(() => true);
const sessionReplay = sessionReplayPlugin({
sampleRate: 0.8,
});
sessionReplay.config = mockConfig;
- sessionReplay.setShouldRecord();
- expect(sessionReplay.shouldRecord).toBe(true);
+ const shouldRecord = sessionReplay.getShouldRecord();
+ expect(shouldRecord).toBe(true);
});
test('should set record as false if opt out in config', () => {
const sessionReplay = sessionReplayPlugin();
@@ -439,8 +411,17 @@ describe('SessionReplayPlugin', () => {
...mockConfig,
optOut: true,
};
- sessionReplay.setShouldRecord();
- expect(sessionReplay.shouldRecord).toBe(false);
+ const shouldRecord = sessionReplay.getShouldRecord();
+ expect(shouldRecord).toBe(false);
+ });
+ test('should set record as false if no session id', () => {
+ const sessionReplay = sessionReplayPlugin();
+ sessionReplay.config = {
+ ...mockConfig,
+ sessionId: undefined,
+ };
+ const shouldRecord = sessionReplay.getShouldRecord();
+ expect(shouldRecord).toBe(false);
});
test('opt out in config should override the sample rate', () => {
jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
@@ -451,8 +432,8 @@ describe('SessionReplayPlugin', () => {
...mockConfig,
optOut: true,
};
- sessionReplay.setShouldRecord();
- expect(sessionReplay.shouldRecord).toBe(false);
+ const shouldRecord = sessionReplay.getShouldRecord();
+ expect(shouldRecord).toBe(false);
});
});
@@ -550,7 +531,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
123: {
- shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {
0: {
@@ -611,7 +591,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1]({})).toEqual({
123: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -926,105 +905,43 @@ describe('SessionReplayPlugin', () => {
describe('module level integration', () => {
describe('with a sample rate', () => {
test('should not record session if excluded due to sampling', async () => {
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
+ jest.spyOn(Helpers, 'isSessionInSample').mockImplementation(() => false);
const sessionReplay = sessionReplayPlugin({
sampleRate: 0.2,
});
await sessionReplay.setup(mockConfig);
- expect(update).toHaveBeenCalled();
- expect(update.mock.calls[0][1]({})).toEqual({
- 123: {
- shouldRecord: false,
- currentSequenceId: 0,
- sessionSequences: {},
- },
- });
- update.mockClear();
- await sessionReplay.execute({
+ const event = await sessionReplay.execute({
event_type: 'my_event',
session_id: 123,
});
+ // Confirm Amplitude event is not tagged with Session Recorded
+ expect(event).not.toMatchObject({
+ event_properties: {
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
+ },
+ });
expect(record).not.toHaveBeenCalled();
expect(update).not.toHaveBeenCalled();
- await sessionReplay.execute({
+ const endEvent = await sessionReplay.execute({
event_type: 'session_end',
session_id: 123,
});
- await runScheduleTimers();
- expect(fetch).not.toHaveBeenCalled();
- });
- test('should recalculate whether to exclude session due to sample rate when start session fires', async () => {
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.1);
- const sessionReplay = sessionReplayPlugin({
- sampleRate: 0.2,
- });
- await sessionReplay.setup(mockConfig);
- expect(update).toHaveBeenCalled();
- expect(update.mock.calls[0][1]({})).toEqual({
- 123: {
- shouldRecord: true,
- currentSequenceId: 0,
- sessionSequences: {},
- },
- });
- update.mockClear();
- // Record is called in setup, but we're not interested in that now
- record.mockClear();
- // This will exclude session from sample rate
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
- await sessionReplay.execute({
- event_type: 'session_start',
- session_id: 123,
- });
- expect(record).not.toHaveBeenCalled();
- expect(update).toHaveBeenCalled();
- expect(update.mock.calls[0][1]({})).toEqual({
- 123: {
- shouldRecord: false,
- currentSequenceId: 0,
- sessionSequences: {},
+ // Confirm Amplitude event is not tagged with Session Recorded
+ expect(endEvent).not.toMatchObject({
+ event_properties: {
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
},
});
- await sessionReplay.execute({
- event_type: 'session_end',
- session_id: 123,
- });
await runScheduleTimers();
expect(fetch).not.toHaveBeenCalled();
});
- test('should fetch whether to record session from IDB', async () => {
- const sessionReplay = sessionReplayPlugin();
- const mockIDBStore = {
- 123: {
- shouldRecord: false,
- currentSequenceId: 0,
- sessionSequences: {},
- },
- };
- get.mockImplementationOnce(() => Promise.resolve(mockIDBStore));
- await sessionReplay.setup(mockConfig);
- expect(record).not.toHaveBeenCalled();
- expect(update).toHaveBeenCalled();
- expect(update.mock.calls[0][1]({})).toEqual({
- 123: {
- shouldRecord: false,
- currentSequenceId: 0,
- sessionSequences: {},
- },
- });
- await sessionReplay.execute({
- event_type: 'session_start',
- session_id: 123,
- });
- expect(record).not.toHaveBeenCalled();
- });
test('should record session if included due to sampling', async () => {
+ jest.spyOn(Helpers, 'isSessionInSample').mockImplementation(() => true);
(fetch as jest.Mock).mockImplementationOnce(() => {
return Promise.resolve({
status: 200,
});
});
- jest.spyOn(Math, 'random').mockImplementationOnce(() => 0.7);
const sessionReplay = sessionReplayPlugin({
sampleRate: 0.8,
});
@@ -1033,19 +950,27 @@ describe('SessionReplayPlugin', () => {
loggerProvider: mockLoggerProvider,
};
await sessionReplay.setup(config);
- await sessionReplay.execute({
+ const startEvent = await sessionReplay.execute({
event_type: 'session_start',
session_id: 456,
});
+ // Confirm Amplitude event is tagged with Session Recorded
+ expect(startEvent?.event_properties).toMatchObject({
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
+ });
// Log is called from setup, but that's not what we're testing here
mockLoggerProvider.log.mockClear();
expect(record).toHaveBeenCalled();
const recordArg = record.mock.calls[0][0];
recordArg?.emit && recordArg?.emit(mockEvent);
- await sessionReplay.execute({
+ const endEvent = await sessionReplay.execute({
event_type: 'session_end',
session_id: 456,
});
+ // Confirm Amplitude event is tagged with Session Recorded
+ expect(endEvent?.event_properties).toMatchObject({
+ [DEFAULT_SESSION_REPLAY_PROPERTY]: true,
+ });
await runScheduleTimers();
expect(fetch).toHaveBeenCalledTimes(1);
// eslint-disable-next-line @typescript-eslint/unbound-method
@@ -1256,75 +1181,6 @@ describe('SessionReplayPlugin', () => {
);
});
});
- describe('storeShouldRecordForSession', () => {
- test('should store if should record session', async () => {
- const sessionReplay = sessionReplayPlugin();
- sessionReplay.config = mockConfig;
- const mockIDBStore: IDBStore = {
- 123: {
- shouldRecord: true,
- currentSequenceId: 3,
- sessionSequences: {
- 2: {
- events: [mockEventString],
- status: RecordingStatus.RECORDING,
- },
- },
- },
- 456: {
- shouldRecord: true,
- currentSequenceId: 1,
- sessionSequences: {
- 1: {
- events: [],
- status: RecordingStatus.SENT,
- },
- },
- },
- };
- await sessionReplay.storeShouldRecordForSession(456, false);
- expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
- ...mockIDBStore,
- 456: {
- ...mockIDBStore[456],
- shouldRecord: false,
- },
- });
- });
- test('should catch errors', async () => {
- const sessionReplay = sessionReplayPlugin();
- const config = {
- ...mockConfig,
- loggerProvider: mockLoggerProvider,
- };
- sessionReplay.config = config;
- update.mockImplementationOnce(() => Promise.reject('error'));
- await sessionReplay.storeShouldRecordForSession(123, true);
- // eslint-disable-next-line @typescript-eslint/unbound-method
- expect(mockLoggerProvider.error).toHaveBeenCalledTimes(1);
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- expect(mockLoggerProvider.error.mock.calls[0][0]).toEqual(
- 'Failed to store session replay events in IndexedDB: error',
- );
- });
- test('should handle an undefined store', async () => {
- const sessionReplay = sessionReplayPlugin();
- const config = {
- ...mockConfig,
- loggerProvider: mockLoggerProvider,
- };
- sessionReplay.config = config;
- update.mockImplementationOnce(() => Promise.resolve());
- await sessionReplay.storeShouldRecordForSession(123, true);
- expect(update.mock.calls[0][1](undefined)).toEqual({
- 123: {
- shouldRecord: true,
- currentSequenceId: 0,
- sessionSequences: {},
- },
- });
- });
- });
describe('cleanUpSessionEventsStore', () => {
test('should update events and status for current session', async () => {
@@ -1334,7 +1190,6 @@ describe('SessionReplayPlugin', () => {
const currentSessionId = new Date('2023-07-31 07:30:00').getTime();
const mockIDBStore: IDBStore = {
[currentSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
2: {
@@ -1353,7 +1208,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
[currentSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
2: {
@@ -1376,7 +1230,6 @@ describe('SessionReplayPlugin', () => {
const currentSessionId = new Date('2023-07-31 07:30:00').getTime();
const mockIDBStore: IDBStore = {
[currentSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
2: {
@@ -1395,7 +1248,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
[currentSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -1415,7 +1267,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
[oneDayOldSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -1425,7 +1276,6 @@ describe('SessionReplayPlugin', () => {
},
},
[fourDayOldSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -1440,7 +1290,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
[oneDayOldSessionId]: {
- shouldRecord: true,
currentSequenceId: 3,
sessionSequences: {
3: {
@@ -1486,7 +1335,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
- shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1496,7 +1344,6 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1511,7 +1358,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
- shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1521,7 +1367,6 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1537,7 +1382,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1547,7 +1391,6 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1562,7 +1405,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
- shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
1: {
@@ -1576,7 +1418,6 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
- shouldRecord: true,
currentSequenceId: 1,
sessionSequences: {
1: {
@@ -1592,7 +1433,6 @@ describe('SessionReplayPlugin', () => {
sessionReplay.config = mockConfig;
const mockIDBStore: IDBStore = {
123: {
- shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1607,7 +1447,6 @@ describe('SessionReplayPlugin', () => {
expect(update).toHaveBeenCalledTimes(1);
expect(update.mock.calls[0][1](mockIDBStore)).toEqual({
123: {
- shouldRecord: true,
currentSequenceId: 2,
sessionSequences: {
2: {
@@ -1617,7 +1456,6 @@ describe('SessionReplayPlugin', () => {
},
},
456: {
- shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {
0: {
@@ -1656,7 +1494,6 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.storeEventsForSession([mockEventString], 0, 456);
expect(update.mock.calls[0][1](undefined)).toEqual({
456: {
- shouldRecord: true,
currentSequenceId: 0,
sessionSequences: {
0: {
From ca2bca168a8982724cde32abd12cb40821ef69c6 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Fri, 4 Aug 2023 15:03:26 +0000
Subject: [PATCH 088/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.5.1
---
packages/plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index fadc4cf41..e0517e05d 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.5.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.5.0...@amplitude/plugin-session-replay-browser@0.5.1) (2023-08-04)
+
+### Bug Fixes
+
+- **plugins:** use hash to calculate is session is included in sample
+ ([4c7e579](https://github.com/amplitude/Amplitude-TypeScript/commit/4c7e5791b02114caf5ae95d7612fef0f339fbfee))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [0.5.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.4.2...@amplitude/plugin-session-replay-browser@0.5.0) (2023-08-02)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 46fb83ded..8e8e9c408 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.5.0",
+ "version": "0.5.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 589991733621042a10e62d1996ccb6eab9fc6f33 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Fri, 4 Aug 2023 13:31:25 -0400
Subject: [PATCH 089/214] test(plugins): add test for sending events that are
not current sessions current sequence
---
.../src/typings/session-replay.ts | 2 +-
.../test/session-replay.test.ts | 71 ++++++++++++++++++-
2 files changed, 69 insertions(+), 4 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
index 5e2924a90..dec1b8d8e 100644
--- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts
@@ -17,7 +17,6 @@ export interface SessionReplayContext {
export enum RecordingStatus {
RECORDING = 'recording',
- SENDING = 'sending',
SENT = 'sent',
}
@@ -49,6 +48,7 @@ export interface SessionReplayEnrichmentPlugin extends EnrichmentPlugin {
stopRecordingEvents: ReturnType | null;
maxPersistedEventsSize: number;
initialize: (shouldSendStoredEvents?: boolean) => Promise;
+ sendStoredEvents: (storedReplaySessions: IDBStore) => void;
getShouldRecord: () => boolean;
recordEvents: () => void;
shouldSplitEventsList: (nextEventString: string) => boolean;
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 297f8cf8c..2c6d559e9 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -273,7 +273,7 @@ describe('SessionReplayPlugin', () => {
expect(getAllSessionEventsFromStore).toHaveBeenCalled();
expect(sessionReplay.events).toEqual([]); // events should not be updated to match what is in the store
});
- test('should configure current sequence id and events correctly if last sequence was sending', async () => {
+ test('should configure current sequence id and events correctly if last sequence was sent', async () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
const mockGetResolution: Promise = Promise.resolve({
@@ -282,7 +282,7 @@ describe('SessionReplayPlugin', () => {
sessionSequences: {
3: {
events: [mockEventString],
- status: RecordingStatus.SENDING,
+ status: RecordingStatus.SENT,
},
},
},
@@ -342,6 +342,71 @@ describe('SessionReplayPlugin', () => {
await sessionReplay.initialize();
expect(record).toHaveBeenCalledTimes(1);
});
+ describe('sendStoredEvents', () => {
+ test('should send all recording sequences except the current sequence for the current session', () => {
+ const sessionReplay = sessionReplayPlugin();
+ const config = {
+ ...mockConfig,
+ sessionId: 456,
+ };
+ sessionReplay.config = config;
+ sessionReplay.currentSequenceId = 3;
+ const store: IDBStore = {
+ 123: {
+ currentSequenceId: 5,
+ sessionSequences: {
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ 4: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ 5: {
+ events: [mockEventString, mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ 456: {
+ currentSequenceId: 3,
+ sessionSequences: {
+ 1: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ 2: {
+ events: [],
+ status: RecordingStatus.SENT,
+ },
+ 3: {
+ events: [mockEventString],
+ status: RecordingStatus.RECORDING,
+ },
+ },
+ },
+ };
+ const sendEventsList = jest.spyOn(sessionReplay, 'sendEventsList');
+ sessionReplay.sendStoredEvents(store);
+ expect(sendEventsList).toHaveBeenCalledTimes(3);
+ expect(sendEventsList.mock.calls[0][0]).toEqual({
+ events: [mockEventString],
+ sequenceId: 3,
+ sessionId: 123,
+ });
+ expect(sendEventsList.mock.calls[1][0]).toEqual({
+ events: [mockEventString, mockEventString],
+ sequenceId: 5,
+ sessionId: 123,
+ });
+ expect(sendEventsList.mock.calls[2][0]).toEqual({
+ events: [mockEventString],
+ sequenceId: 1,
+ sessionId: 456,
+ });
+ });
+ });
describe('defaultTracking', () => {
test('should not change defaultTracking if its set to true', async () => {
const sessionReplay = sessionReplayPlugin();
@@ -1271,7 +1336,7 @@ describe('SessionReplayPlugin', () => {
sessionSequences: {
3: {
events: [mockEventString],
- status: RecordingStatus.SENDING,
+ status: RecordingStatus.RECORDING,
},
},
},
From 8d4afd64a2e90604f5c990a9a3df73082850b963 Mon Sep 17 00:00:00 2001
From: Kelly Wallach
Date: Mon, 7 Aug 2023 12:22:30 -0400
Subject: [PATCH 090/214] feat(plugins): add teardown functionality to session
replay plugin
---
.../src/session-replay.ts | 65 ++++++++++++-------
.../test/session-replay.test.ts | 63 +++++++++++++++---
2 files changed, 94 insertions(+), 34 deletions(-)
diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts
index 781062278..9ba6ee4ba 100644
--- a/packages/plugin-session-replay-browser/src/session-replay.ts
+++ b/packages/plugin-session-replay-browser/src/session-replay.ts
@@ -76,39 +76,46 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
};
}
- const GlobalScope = getGlobalScope();
- if (GlobalScope && GlobalScope.window) {
- GlobalScope.window.addEventListener('blur', () => {
- this.stopRecordingEvents && this.stopRecordingEvents();
- this.stopRecordingEvents = null;
- });
- GlobalScope.window.addEventListener('focus', () => {
- void this.initialize();
- });
+ const globalScope = getGlobalScope();
+ if (globalScope) {
+ globalScope.addEventListener('blur', this.blurListener);
+ globalScope.addEventListener('focus', this.focusListener);
}
- if (GlobalScope && GlobalScope.document && GlobalScope.document.hasFocus()) {
+ if (globalScope && globalScope.document && globalScope.document.hasFocus()) {
await this.initialize(true);
}
}
+ blurListener = () => {
+ this.stopRecordingAndSendEvents();
+ };
+ focusListener = () => {
+ void this.initialize();
+ };
+
+ stopRecordingAndSendEvents(sessionId?: number) {
+ this.stopRecordingEvents && this.stopRecordingEvents();
+ this.stopRecordingEvents = null;
+ const sessionIdToSend = sessionId || this.config.sessionId;
+ if (this.events.length && sessionIdToSend) {
+ this.sendEventsList({
+ events: this.events,
+ sequenceId: this.currentSequenceId,
+ sessionId: sessionIdToSend,
+ });
+ }
+ }
+
async execute(event: Event) {
- const GlobalScope = getGlobalScope();
- if (GlobalScope && GlobalScope.document && !GlobalScope.document.hasFocus()) {
+ const globalScope = getGlobalScope();
+ if (globalScope && globalScope.document && !globalScope.document.hasFocus()) {
return Promise.resolve(event);
}
if (event.event_type === DEFAULT_SESSION_START_EVENT && !this.stopRecordingEvents) {
this.recordEvents();
} else if (event.event_type === DEFAULT_SESSION_END_EVENT) {
- if (event.session_id && this.events.length) {
- this.sendEventsList({
- events: this.events,
- sequenceId: this.currentSequenceId,
- sessionId: event.session_id,
- });
- }
- this.stopRecordingEvents && this.stopRecordingEvents();
- this.stopRecordingEvents = null;
+ this.stopRecordingAndSendEvents(event.session_id);
this.events = [];
this.currentSequenceId = 0;
}
@@ -195,9 +202,9 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
}
this.stopRecordingEvents = record({
emit: (event) => {
- const GlobalScope = getGlobalScope();
- if (GlobalScope && GlobalScope.document && !GlobalScope.document.hasFocus()) {
- this.stopRecordingEvents && this.stopRecordingEvents();
+ const globalScope = getGlobalScope();
+ if (globalScope && globalScope.document && !globalScope.document.hasFocus()) {
+ this.stopRecordingAndSendEvents();
return;
}
const eventString = JSON.stringify(event);
@@ -457,6 +464,16 @@ class SessionReplay implements SessionReplayEnrichmentPlugin {
this.config.loggerProvider.log(success);
}
}
+
+ async teardown() {
+ const globalScope = getGlobalScope();
+ if (globalScope) {
+ globalScope.removeEventListener('blur', this.blurListener);
+ globalScope.removeEventListener('focus', this.focusListener);
+ }
+
+ this.stopRecordingAndSendEvents();
+ }
}
export const sessionReplayPlugin: SessionReplayPlugin = (options?: SessionReplayOptions) => {
diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts
index 2c6d559e9..0eac71017 100644
--- a/packages/plugin-session-replay-browser/test/session-replay.test.ts
+++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts
@@ -40,6 +40,7 @@ describe('SessionReplayPlugin', () => {
let originalFetch: typeof global.fetch;
let mockLoggerProvider: MockedLogger;
let addEventListenerMock: jest.Mock;
+ let removeEventListenerMock: jest.Mock;
const mockConfig: BrowserConfig = {
apiKey: 'static_key',
flushIntervalMillis: 0,
@@ -82,14 +83,14 @@ describe('SessionReplayPlugin', () => {
}),
) as jest.Mock;
addEventListenerMock = jest.fn() as typeof addEventListenerMock;
+ removeEventListenerMock = jest.fn() as typeof removeEventListenerMock;
jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
- window: {
- addEventListener: addEventListenerMock,
- } as unknown as Window,
+ addEventListener: addEventListenerMock,
+ removeEventListener: removeEventListenerMock,
document: {
hasFocus: () => true,
},
- } as Window & typeof globalThis);
+ } as unknown as typeof globalThis);
mockLoggerProvider = {
error: jest.fn(),
log: jest.fn(),
@@ -506,10 +507,11 @@ describe('SessionReplayPlugin', () => {
test('it should return event if document does not have focus', async () => {
const sessionReplay = sessionReplayPlugin();
jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
+ addEventListener: addEventListenerMock,
document: {
hasFocus: () => false,
},
- } as typeof globalThis);
+ } as unknown as typeof globalThis);
await sessionReplay.setup(mockConfig);
const event = {
event_type: 'event_type',
@@ -667,12 +669,14 @@ describe('SessionReplayPlugin', () => {
});
});
- test('should return early if document is not in focus', () => {
+ test('should stop recording and send events if document is not in focus', () => {
const sessionReplay = sessionReplayPlugin();
sessionReplay.config = mockConfig;
sessionReplay.recordEvents();
- sessionReplay.stopRecordingEvents = jest.fn();
+ const stopRecordingMock = jest.fn();
+ sessionReplay.stopRecordingEvents = stopRecordingMock;
expect(sessionReplay.events).toEqual([]);
+ sessionReplay.events = [mockEventString]; // Add one event to list to trigger sending in stopRecordingAndSendEvents
jest.spyOn(AnalyticsClientCommon, 'getGlobalScope').mockReturnValue({
document: {
hasFocus: () => false,
@@ -682,9 +686,15 @@ describe('SessionReplayPlugin', () => {
const sendEventsListMock = jest.spyOn(sessionReplay, 'sendEventsList').mockImplementationOnce(() => {});
const recordArg = record.mock.calls[0][0];
recordArg?.emit && recordArg?.emit(mockEvent);
- expect(sendEventsListMock).toHaveBeenCalledTimes(0);
- expect(sessionReplay.stopRecordingEvents).toHaveBeenCalled();
- expect(sessionReplay.events).toEqual([]);
+ expect(sendEventsListMock).toHaveBeenCalledTimes(1);
+ expect(sendEventsListMock).toHaveBeenCalledWith({
+ events: [mockEventString],
+ sequenceId: 0,
+ sessionId: 123,
+ });
+ expect(stopRecordingMock).toHaveBeenCalled();
+ expect(sessionReplay.stopRecordingEvents).toEqual(null);
+ expect(sessionReplay.events).toEqual([mockEventString]); // events should not change, emmitted event should be ignored
});
});
@@ -1618,4 +1628,37 @@ describe('SessionReplayPlugin', () => {
});
});
});
+
+ describe('teardown', () => {
+ test('should remove event listeners', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ await sessionReplay.setup(mockConfig);
+ await sessionReplay.teardown?.();
+ expect(removeEventListenerMock).toHaveBeenCalledTimes(2);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(removeEventListenerMock.mock.calls[0][0]).toEqual('blur');
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(removeEventListenerMock.mock.calls[1][0]).toEqual('focus');
+ });
+
+ test('should stop recording and send any events in queue', async () => {
+ const sessionReplay = sessionReplayPlugin();
+ await sessionReplay.setup(mockConfig);
+ const stopRecordingMock = jest.fn();
+ sessionReplay.stopRecordingEvents = stopRecordingMock;
+ sessionReplay.events = [mockEventString];
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ const sendEventsListMock = jest.spyOn(sessionReplay, 'sendEventsList').mockImplementationOnce(() => {});
+ await sessionReplay.teardown?.();
+ expect(stopRecordingMock).toHaveBeenCalled();
+ expect(sessionReplay.stopRecordingEvents).toBe(null);
+ expect(sendEventsListMock).toHaveBeenCalled();
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ expect(sendEventsListMock.mock.calls[0][0]).toEqual({
+ events: [mockEventString],
+ sequenceId: 0,
+ sessionId: 123,
+ });
+ });
+ });
});
From 5b8d427f86450335d1d7d564fc44d36537ec1d7e Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Mon, 7 Aug 2023 17:52:40 +0000
Subject: [PATCH 091/214] chore(release): publish
- @amplitude/plugin-session-replay-browser@0.6.0
---
packages/plugin-session-replay-browser/CHANGELOG.md | 12 ++++++++++++
packages/plugin-session-replay-browser/package.json | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/packages/plugin-session-replay-browser/CHANGELOG.md b/packages/plugin-session-replay-browser/CHANGELOG.md
index e0517e05d..f672b31b3 100644
--- a/packages/plugin-session-replay-browser/CHANGELOG.md
+++ b/packages/plugin-session-replay-browser/CHANGELOG.md
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [0.6.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.5.1...@amplitude/plugin-session-replay-browser@0.6.0) (2023-08-07)
+
+### Features
+
+- **plugins:** add teardown functionality to session replay plugin
+ ([8d4afd6](https://github.com/amplitude/Amplitude-TypeScript/commit/8d4afd64a2e90604f5c990a9a3df73082850b963))
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [0.5.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-session-replay-browser@0.5.0...@amplitude/plugin-session-replay-browser@0.5.1) (2023-08-04)
### Bug Fixes
diff --git a/packages/plugin-session-replay-browser/package.json b/packages/plugin-session-replay-browser/package.json
index 8e8e9c408..8411f5918 100644
--- a/packages/plugin-session-replay-browser/package.json
+++ b/packages/plugin-session-replay-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-session-replay-browser",
- "version": "0.5.1",
+ "version": "0.6.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 31d5c353f38c04bd183f01fb8d00258d50e34f8b Mon Sep 17 00:00:00 2001
From: Alyssa Yu
Date: Tue, 8 Aug 2023 15:27:37 -0700
Subject: [PATCH 092/214] feat: page view v1 enrichment plugin (#524)
---
.../plugin-page-view-v1-enrichment/README.md | 66 ++++++++
.../jest.config.js | 10 ++
.../package.json | 56 +++++++
.../rollup.config.js | 6 +
.../src/index.ts | 2 +
.../src/page-view-v1-enrichment.ts | 51 ++++++
.../typings/page-view-v1-enrichment-plugin.ts | 6 +
.../test/page-view-v1-enrichment.test.ts | 149 ++++++++++++++++++
.../tsconfig.es5.json | 10 ++
.../tsconfig.esm.json | 10 ++
.../tsconfig.json | 11 ++
11 files changed, 377 insertions(+)
create mode 100644 packages/plugin-page-view-v1-enrichment/README.md
create mode 100644 packages/plugin-page-view-v1-enrichment/jest.config.js
create mode 100644 packages/plugin-page-view-v1-enrichment/package.json
create mode 100644 packages/plugin-page-view-v1-enrichment/rollup.config.js
create mode 100644 packages/plugin-page-view-v1-enrichment/src/index.ts
create mode 100644 packages/plugin-page-view-v1-enrichment/src/page-view-v1-enrichment.ts
create mode 100644 packages/plugin-page-view-v1-enrichment/src/typings/page-view-v1-enrichment-plugin.ts
create mode 100644 packages/plugin-page-view-v1-enrichment/test/page-view-v1-enrichment.test.ts
create mode 100644 packages/plugin-page-view-v1-enrichment/tsconfig.es5.json
create mode 100644 packages/plugin-page-view-v1-enrichment/tsconfig.esm.json
create mode 100644 packages/plugin-page-view-v1-enrichment/tsconfig.json
diff --git a/packages/plugin-page-view-v1-enrichment/README.md b/packages/plugin-page-view-v1-enrichment/README.md
new file mode 100644
index 000000000..130c5b063
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/README.md
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+# @amplitude/@amplitude/plugin-page-view-v1-enrichment-browser
+
+Official Browser SDK plugin for making page view event compatible with browser v1.
+
+## Installation
+
+This package is published on NPM registry and is available to be installed using npm and yarn.
+
+```sh
+# npm
+npm install @amplitude/plugin-page-view-v1-enrichment-browser@^1.0.0
+
+# yarn
+yarn add @amplitude/plugin-page-view-v1-enrichment-browser@^1.0.0
+```
+
+## Usage
+
+This plugin works on top of the Amplitude Browser SDK. It's used for updating the page view event_type and event_properties the same as Browser v1.
+In Browser v2.x, we have enriched the page view event_type and event_propertis. You can use this plugin to maintain consistency with page view tracking from earlier SDK versions.
+
+### 1. Import Amplitude packages
+
+* `@amplitude/analytics-browser`
+* `@amplitude/plugin-page-view-v1-enrichment-browser`
+
+```typescript
+import * as amplitude from '@amplitude/analytics-browser';
+import { pageViewV1EnrichmentPlugin } from '@amplitude/plugin-page-view-v1-enrichment-browser';
+```
+
+### 2. Instantiate page view v1 enrichment plugin
+
+```typescript
+const pageViewPlugin = pageViewV1EnrichmentPlugin();
+```
+
+### 3. Install plugin to Amplitude SDK
+
+```typescript
+amplitude.add(pageViewPlugin);
+```
+
+### 4. Initialize Amplitude SDK
+
+```typescript
+amplitude.init('API_KEY');
+```
+
+## Resulting on page view event
+
+| property |[Browser SDK 2.0](../) | With this plugin |
+| --- | --- | --- |
+| `Event Type` | `[Amplitude] Page Viewed` | `Page View` |
+| `Event Properties` | `page_domain` | `[Amplitude] Page Domain` |
+| | `page_location` | `[Amplitude] Page Location` |
+| | `page_path` | `[Amplitude] Page Path` |
+| | `page_title` | `[Amplitude] Page Title` |
+| | `page_url` | `[Amplitude] Page URL`|
diff --git a/packages/plugin-page-view-v1-enrichment/jest.config.js b/packages/plugin-page-view-v1-enrichment/jest.config.js
new file mode 100644
index 000000000..a0ef09957
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/jest.config.js
@@ -0,0 +1,10 @@
+const baseConfig = require('../../jest.config.js');
+const package = require('./package');
+
+module.exports = {
+ ...baseConfig,
+ displayName: package.name,
+ rootDir: '.',
+ testEnvironment: 'jsdom',
+ coveragePathIgnorePatterns: ['index.ts'],
+};
\ No newline at end of file
diff --git a/packages/plugin-page-view-v1-enrichment/package.json b/packages/plugin-page-view-v1-enrichment/package.json
new file mode 100644
index 000000000..e54733d9c
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "@amplitude/plugin-page-view-v1-enrichment-browser",
+ "version": "0.0.0",
+ "description": "",
+ "author": "Amplitude Inc",
+ "homepage": "https://github.com/amplitude/Amplitude-TypeScript",
+ "license": "MIT",
+ "main": "lib/cjs/index.js",
+ "module": "lib/esm/index.js",
+ "types": "lib/esm/index.d.ts",
+ "sideEffects": false,
+ "publishConfig": {
+ "access": "public",
+ "tag": "latest"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/amplitude/Amplitude-TypeScript.git"
+ },
+ "scripts": {
+ "build": "yarn bundle && yarn build:es5 && yarn build:esm",
+ "bundle": "rollup --config rollup.config.js",
+ "build:es5": "tsc -p ./tsconfig.es5.json",
+ "build:esm": "tsc -p ./tsconfig.esm.json",
+ "clean": "rimraf node_modules lib coverage",
+ "fix": "yarn fix:eslint & yarn fix:prettier",
+ "fix:eslint": "eslint '{src,test}/**/*.ts' --fix",
+ "fix:prettier": "prettier --write \"{src,test}/**/*.ts\"",
+ "lint": "yarn lint:eslint & yarn lint:prettier",
+ "lint:eslint": "eslint '{src,test}/**/*.ts'",
+ "lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
+ "test": "jest",
+ "typecheck": "tsc -p ./tsconfig.json"
+ },
+ "bugs": {
+ "url": "https://github.com/amplitude/Amplitude-TypeScript/issues"
+ },
+ "dependencies": {
+ "@amplitude/analytics-client-common": "^2.0.4",
+ "@amplitude/analytics-types": "^2.1.1",
+ "tslib": "^2.4.1"
+ },
+ "devDependencies": {
+ "@amplitude/analytics-browser": "^2.1.2",
+ "@rollup/plugin-commonjs": "^23.0.4",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-typescript": "^10.0.1",
+ "rollup": "^2.79.1",
+ "rollup-plugin-execute": "^1.1.1",
+ "rollup-plugin-gzip": "^3.1.0",
+ "rollup-plugin-terser": "^7.0.2"
+ },
+ "files": [
+ "lib"
+ ]
+}
diff --git a/packages/plugin-page-view-v1-enrichment/rollup.config.js b/packages/plugin-page-view-v1-enrichment/rollup.config.js
new file mode 100644
index 000000000..0f6642c86
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/rollup.config.js
@@ -0,0 +1,6 @@
+import { iife, umd } from '../../scripts/build/rollup.config';
+
+iife.input = umd.input;
+iife.output.name = 'amplitudePageViewV1EnrichmentPlugin';
+
+export default [umd, iife];
diff --git a/packages/plugin-page-view-v1-enrichment/src/index.ts b/packages/plugin-page-view-v1-enrichment/src/index.ts
new file mode 100644
index 000000000..bf1863a2c
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/src/index.ts
@@ -0,0 +1,2 @@
+export { pageViewV1EnrichmentPlugin } from './page-view-v1-enrichment';
+export { pageViewV1EnrichmentPlugin as plugin } from './page-view-v1-enrichment';
diff --git a/packages/plugin-page-view-v1-enrichment/src/page-view-v1-enrichment.ts b/packages/plugin-page-view-v1-enrichment/src/page-view-v1-enrichment.ts
new file mode 100644
index 000000000..262ba3b48
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/src/page-view-v1-enrichment.ts
@@ -0,0 +1,51 @@
+import { BrowserConfig, EnrichmentPlugin, Event } from '@amplitude/analytics-types';
+import { CreatePageViewV1EnrichmentPlugin } from './typings/page-view-v1-enrichment-plugin';
+
+export const pageViewV1EnrichmentPlugin: CreatePageViewV1EnrichmentPlugin = function () {
+ const plugin: EnrichmentPlugin = {
+ name: '@amplitude/plugin-page-view-v1-enrichment-browser',
+ type: 'enrichment',
+
+ setup: async function (config: BrowserConfig) {
+ config.loggerProvider.log('Installing @amplitude/plugin-page-view-v1-enrichment-browser.');
+ },
+
+ execute: async (event: Event) => {
+ if (event.event_type == '[Amplitude] Page Viewed') {
+ const {
+ '[Amplitude] Page Domain': page_domain,
+ '[Amplitude] Page Location': page_location,
+ '[Amplitude] Page Path': page_path,
+ '[Amplitude] Page Title': page_title,
+ '[Amplitude] Page URL': page_url,
+ } = event.event_properties || {};
+
+ event = {
+ ...event,
+ event_type: 'Page View',
+ event_properties: {
+ ...event.event_properties,
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
+ page_domain: page_domain ?? '',
+ page_location: page_location ?? '',
+ page_path: page_path ?? '',
+ page_title: page_title ?? '',
+ page_url: page_url ?? '',
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
+ },
+ };
+
+ if (event.event_properties) {
+ delete event.event_properties['[Amplitude] Page Domain'];
+ delete event.event_properties['[Amplitude] Page Location'];
+ delete event.event_properties['[Amplitude] Page Path'];
+ delete event.event_properties['[Amplitude] Page Title'];
+ delete event.event_properties['[Amplitude] Page URL'];
+ }
+ }
+ return event;
+ },
+ };
+
+ return plugin;
+};
diff --git a/packages/plugin-page-view-v1-enrichment/src/typings/page-view-v1-enrichment-plugin.ts b/packages/plugin-page-view-v1-enrichment/src/typings/page-view-v1-enrichment-plugin.ts
new file mode 100644
index 000000000..bb9ca6969
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/src/typings/page-view-v1-enrichment-plugin.ts
@@ -0,0 +1,6 @@
+/* eslint-disable @typescript-eslint/no-empty-interface */
+import { EnrichmentPlugin } from '@amplitude/analytics-types';
+
+export interface CreatePageViewV1EnrichmentPlugin {
+ (): EnrichmentPlugin;
+}
diff --git a/packages/plugin-page-view-v1-enrichment/test/page-view-v1-enrichment.test.ts b/packages/plugin-page-view-v1-enrichment/test/page-view-v1-enrichment.test.ts
new file mode 100644
index 000000000..9dca41bea
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/test/page-view-v1-enrichment.test.ts
@@ -0,0 +1,149 @@
+import { createInstance } from '@amplitude/analytics-browser';
+import { CookieStorage, FetchTransport } from '@amplitude/analytics-client-common';
+import { pageViewV1EnrichmentPlugin } from '../src/page-view-v1-enrichment';
+import { BaseEvent, BrowserConfig, EnrichmentPlugin, LogLevel } from '@amplitude/analytics-types';
+import { Logger, UUID } from '@amplitude/analytics-core';
+
+describe('page-view-v1-enrichment', () => {
+ let plugin: EnrichmentPlugin;
+
+ const mockConfig: BrowserConfig = {
+ apiKey: UUID(),
+ flushIntervalMillis: 0,
+ flushMaxRetries: 0,
+ flushQueueSize: 0,
+ logLevel: LogLevel.None,
+ loggerProvider: new Logger(),
+ optOut: false,
+ serverUrl: undefined,
+ transportProvider: new FetchTransport(),
+ useBatch: false,
+ cookieOptions: {
+ domain: '.amplitude.com',
+ expiration: 365,
+ sameSite: 'Lax',
+ secure: false,
+ upgrade: true,
+ },
+ cookieStorage: new CookieStorage(),
+ sessionTimeout: 30 * 60 * 1000,
+ trackingOptions: {
+ ipAddress: true,
+ language: true,
+ platform: true,
+ },
+ };
+
+ beforeEach(() => {
+ plugin = pageViewV1EnrichmentPlugin();
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('execute', () => {
+ test('should overwirte the browser v2 page view keys to v1 page view keys', async () => {
+ const event: BaseEvent = {
+ event_type: '[Amplitude] Page Viewed',
+ event_properties: {
+ '[Amplitude] Page Domain': 'page_domain_v2',
+ '[Amplitude] Page Path': 'page_path_v2',
+ '[Amplitude] Page Title': 'page_title_v2',
+ '[Amplitude] Page URL': 'page_url_v2',
+ '[Amplitude] Page Location': 'page_location_v2',
+ },
+ user_properties: {
+ org: 'engineer',
+ },
+ };
+
+ const amplitude = createInstance();
+
+ const executeSpy = jest.spyOn(plugin, 'execute');
+
+ await plugin.setup?.(mockConfig, amplitude);
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(executeSpy).toHaveBeenCalledWith(event);
+ expect(enrichedEvent?.event_type).toEqual('Page View');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_domain');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_location');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_path');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_title');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_url');
+ expect(enrichedEvent?.event_properties).toEqual({
+ page_domain: 'page_domain_v2',
+ page_location: 'page_location_v2',
+ page_path: 'page_path_v2',
+ page_title: 'page_title_v2',
+ page_url: 'page_url_v2',
+ });
+
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Domain');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Location');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Path');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Title');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page URL');
+
+ expect(enrichedEvent?.user_properties).toEqual({
+ org: 'engineer',
+ });
+ });
+
+ test('should not throw error with imcomplete browser v2 page view', async () => {
+ const event = {
+ event_type: '[Amplitude] Page Viewed',
+ };
+
+ const amplitude = createInstance();
+
+ const executeSpy = jest.spyOn(plugin, 'execute');
+
+ await plugin.setup?.(mockConfig, amplitude);
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(executeSpy).toHaveBeenCalledWith(event);
+ expect(enrichedEvent?.event_type).toEqual('Page View');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_domain');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_path');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_title');
+ expect(enrichedEvent?.event_properties).toHaveProperty('page_url');
+
+ expect(enrichedEvent?.event_properties).toEqual({
+ page_domain: '',
+ page_location: '',
+ page_path: '',
+ page_title: '',
+ page_url: '',
+ });
+
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Domain');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Location');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Path');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page Title');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('[Amplitude] Page URL');
+ });
+
+ test('should not overwirte with no page view event', async () => {
+ const event = {
+ event_type: 'event_type',
+ event_properties: {},
+ };
+
+ const amplitude = createInstance();
+
+ const executeSpy = jest.spyOn(plugin, 'execute');
+
+ await plugin.setup?.(mockConfig, amplitude);
+ const enrichedEvent = await plugin.execute?.(event);
+
+ expect(executeSpy).toHaveBeenCalledWith(event);
+ expect(enrichedEvent?.event_type).toEqual('event_type');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('page_domain');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('page_path');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('page_title');
+ expect(enrichedEvent?.event_properties).not.toHaveProperty('page_url');
+ });
+ });
+});
diff --git a/packages/plugin-page-view-v1-enrichment/tsconfig.es5.json b/packages/plugin-page-view-v1-enrichment/tsconfig.es5.json
new file mode 100644
index 000000000..77e041d3f
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/tsconfig.es5.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "module": "commonjs",
+ "noEmit": false,
+ "outDir": "lib/cjs",
+ "rootDir": "./src"
+ }
+}
diff --git a/packages/plugin-page-view-v1-enrichment/tsconfig.esm.json b/packages/plugin-page-view-v1-enrichment/tsconfig.esm.json
new file mode 100644
index 000000000..bec981eee
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/tsconfig.esm.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "module": "es6",
+ "noEmit": false,
+ "outDir": "lib/esm",
+ "rootDir": "./src"
+ }
+}
diff --git a/packages/plugin-page-view-v1-enrichment/tsconfig.json b/packages/plugin-page-view-v1-enrichment/tsconfig.json
new file mode 100644
index 000000000..bd2a4cdb7
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src/**/*", "test/**/*"],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "esModuleInterop": true,
+ "lib": ["dom"],
+ "noEmit": true,
+ "rootDir": ".",
+ }
+}
\ No newline at end of file
From f9bb21699dd3d44dbca3318141249c3a57ce1000 Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Tue, 8 Aug 2023 23:35:17 +0000
Subject: [PATCH 093/214] chore(release): publish
- @amplitude/plugin-page-view-v1-enrichment-browser@0.1.0
---
packages/plugin-page-view-v1-enrichment/CHANGELOG.md | 11 +++++++++++
packages/plugin-page-view-v1-enrichment/package.json | 2 +-
2 files changed, 12 insertions(+), 1 deletion(-)
create mode 100644 packages/plugin-page-view-v1-enrichment/CHANGELOG.md
diff --git a/packages/plugin-page-view-v1-enrichment/CHANGELOG.md b/packages/plugin-page-view-v1-enrichment/CHANGELOG.md
new file mode 100644
index 000000000..5e4bcb485
--- /dev/null
+++ b/packages/plugin-page-view-v1-enrichment/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# 0.1.0 (2023-08-08)
+
+### Features
+
+- page view v1 enrichment plugin ([#524](https://github.com/amplitude/Amplitude-TypeScript/issues/524))
+ ([31d5c35](https://github.com/amplitude/Amplitude-TypeScript/commit/31d5c353f38c04bd183f01fb8d00258d50e34f8b))
diff --git a/packages/plugin-page-view-v1-enrichment/package.json b/packages/plugin-page-view-v1-enrichment/package.json
index e54733d9c..ed478fd54 100644
--- a/packages/plugin-page-view-v1-enrichment/package.json
+++ b/packages/plugin-page-view-v1-enrichment/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-v1-enrichment-browser",
- "version": "0.0.0",
+ "version": "0.1.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From bb746e8b8da28180ab9750acc1356e182886d5ac Mon Sep 17 00:00:00 2001
From: Alyssa Yu
Date: Wed, 9 Aug 2023 13:02:20 -0700
Subject: [PATCH 094/214] chore: add publish script to plugins to upload the
script into cdn (#527)
---
packages/plugin-page-view-v1-enrichment/package.json | 1 +
packages/plugin-user-agent-enrichment-browser/package.json | 1 +
2 files changed, 2 insertions(+)
diff --git a/packages/plugin-page-view-v1-enrichment/package.json b/packages/plugin-page-view-v1-enrichment/package.json
index ed478fd54..82091597f 100644
--- a/packages/plugin-page-view-v1-enrichment/package.json
+++ b/packages/plugin-page-view-v1-enrichment/package.json
@@ -29,6 +29,7 @@
"lint": "yarn lint:eslint & yarn lint:prettier",
"lint:eslint": "eslint '{src,test}/**/*.ts'",
"lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
+ "publish": "node ../../scripts/publish/upload-to-s3.js",
"test": "jest",
"typecheck": "tsc -p ./tsconfig.json"
},
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
index f842130ad..0ebe4d58a 100644
--- a/packages/plugin-user-agent-enrichment-browser/package.json
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -29,6 +29,7 @@
"lint": "yarn lint:eslint & yarn lint:prettier",
"lint:eslint": "eslint '{src,test}/**/*.ts'",
"lint:prettier": "prettier --check \"{src,test}/**/*.ts\"",
+ "publish": "node ../../scripts/publish/upload-to-s3.js",
"test": "jest",
"typecheck": "tsc -p ./tsconfig.json"
},
From a95285028064aa75053b8db84137a2acf91a018f Mon Sep 17 00:00:00 2001
From: Alyssa Yu
Date: Wed, 9 Aug 2023 13:06:47 -0700
Subject: [PATCH 095/214] chore: plugin-page-view-v1-enrichment@1.0.0-beta.0
(#526)
---
packages/plugin-page-view-v1-enrichment/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/plugin-page-view-v1-enrichment/package.json b/packages/plugin-page-view-v1-enrichment/package.json
index 82091597f..bb0d13b2b 100644
--- a/packages/plugin-page-view-v1-enrichment/package.json
+++ b/packages/plugin-page-view-v1-enrichment/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-v1-enrichment-browser",
- "version": "0.1.0",
+ "version": "1.0.0-beta.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 2f2b22af6d29531292ef3b331ce816196095bb2b Mon Sep 17 00:00:00 2001
From: amplitude-sdk-bot
Date: Wed, 9 Aug 2023 21:05:47 +0000
Subject: [PATCH 096/214] chore(release): publish
- @amplitude/plugin-page-view-v1-enrichment-browser@1.0.0
- @amplitude/plugin-user-agent-enrichment-browser@1.0.1
---
packages/plugin-page-view-v1-enrichment/CHANGELOG.md | 9 +++++++++
packages/plugin-page-view-v1-enrichment/package.json | 2 +-
.../plugin-user-agent-enrichment-browser/CHANGELOG.md | 9 +++++++++
.../plugin-user-agent-enrichment-browser/package.json | 2 +-
4 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/packages/plugin-page-view-v1-enrichment/CHANGELOG.md b/packages/plugin-page-view-v1-enrichment/CHANGELOG.md
index 5e4bcb485..b79ee917b 100644
--- a/packages/plugin-page-view-v1-enrichment/CHANGELOG.md
+++ b/packages/plugin-page-view-v1-enrichment/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-page-view-v1-enrichment-browser@0.1.0...@amplitude/plugin-page-view-v1-enrichment-browser@1.0.0) (2023-08-09)
+
+**Note:** Version bump only for package @amplitude/plugin-page-view-v1-enrichment-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# 0.1.0 (2023-08-08)
### Features
diff --git a/packages/plugin-page-view-v1-enrichment/package.json b/packages/plugin-page-view-v1-enrichment/package.json
index bb0d13b2b..7a6be5ce6 100644
--- a/packages/plugin-page-view-v1-enrichment/package.json
+++ b/packages/plugin-page-view-v1-enrichment/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-page-view-v1-enrichment-browser",
- "version": "1.0.0-beta.0",
+ "version": "1.0.0",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
diff --git a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
index cffa9af53..8ae3a6575 100644
--- a/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
+++ b/packages/plugin-user-agent-enrichment-browser/CHANGELOG.md
@@ -3,6 +3,15 @@
All notable changes to this project will be documented in this file. See
[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [1.0.1](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-user-agent-enrichment-browser@1.0.0...@amplitude/plugin-user-agent-enrichment-browser@1.0.1) (2023-08-09)
+
+**Note:** Version bump only for package @amplitude/plugin-user-agent-enrichment-browser
+
+# Change Log
+
+All notable changes to this project will be documented in this file. See
+[Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
# [1.0.0](https://github.com/amplitude/Amplitude-TypeScript/compare/@amplitude/plugin-user-agent-enrichment-browser@1.0.0-beta.1...@amplitude/plugin-user-agent-enrichment-browser@1.0.0) (2023-07-31)
**Note:** Version bump only for package @amplitude/plugin-user-agent-enrichment-browser
diff --git a/packages/plugin-user-agent-enrichment-browser/package.json b/packages/plugin-user-agent-enrichment-browser/package.json
index 0ebe4d58a..3c704e971 100644
--- a/packages/plugin-user-agent-enrichment-browser/package.json
+++ b/packages/plugin-user-agent-enrichment-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@amplitude/plugin-user-agent-enrichment-browser",
- "version": "1.0.0",
+ "version": "1.0.1",
"description": "",
"author": "Amplitude Inc",
"homepage": "https://github.com/amplitude/Amplitude-TypeScript",
From 586cbc59dca0da88dd1564b93cb8cc5c8ac7796d Mon Sep 17 00:00:00 2001
From: Andrey Sokolov
Date: Thu, 10 Aug 2023 23:53:31 +0400
Subject: [PATCH 097/214] fix: migrate legacy data (#486)
* fix: migrate legacy data (Android)
* chore: fix tests
* fix: do not move legacy identifies if not first run since upgrade
* chore: eslint
* fix: get max event id from legacy events (for ios)
* fix: put session data (Android)
* fix: legacy database storage and native 'get' methods for iOS
* fix: native 'remove' methods for iOS
* fix: native 'remove' methods for Android
* fix: remove legacy events after migration
* fix: squash native methods
* fix: changes in Android part
* fix: fixes for iOS
* chore: increase test coverage
* fix: catch/log native Android exceptions
* fix: example application to test legacy data migration (Android)
* fix: example application to test legacy data migration (iOS)
* fix: fix the example application (Android)
* fix: review fixes
* fix: fix getLastEventId() logic for iOS
* chore: fix event prefixes in check messages
---
.../legacyDataMigration/.bundle/config | 2 +
.../legacyDataMigration/.eslintrc.js | 4 +
.../legacyDataMigration/.gitignore | 66 +
.../legacyDataMigration/.prettierrc.js | 7 +
.../legacyDataMigration/.watchmanconfig | 1 +
.../react-native/legacyDataMigration/App.tsx | 99 +
.../react-native/legacyDataMigration/Gemfile | 6 +
.../legacyDataMigration/MigrationChecker.ts | 133 +
.../legacyDataMigration/README.md | 79 +
.../__tests__/App.test.tsx | 17 +
.../android/app/build.gradle | 125 +
.../android/app/debug.keystore | Bin 0 -> 2257 bytes
.../android/app/proguard-rules.pro | 10 +
.../android/app/src/debug/AndroidManifest.xml | 13 +
.../ReactNativeFlipper.java | 75 +
.../android/app/src/main/AndroidManifest.xml | 25 +
.../java/com/legacydatamigration/LegacyV3.kt | 369 +
.../java/com/legacydatamigration/LegacyV4.kt | 405 +
.../com/legacydatamigration/MainActivity.kt | 28 +
.../legacydatamigration/MainApplication.kt | 47 +
.../com/legacydatamigration/MainPackage.kt | 18 +
.../legacydatamigration/MigrationModule.kt | 29 +
.../res/drawable/rn_edit_text_material.xml | 36 +
.../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes
.../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes
.../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes
.../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes
.../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes
.../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes
.../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes
.../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes
.../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes
.../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes
.../app/src/main/res/values/strings.xml | 3 +
.../app/src/main/res/values/styles.xml | 9 +
.../ReactNativeFlipper.java | 20 +
.../legacyDataMigration/android/build.gradle | 23 +
.../android/gradle.properties | 44 +
.../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61574 bytes
.../gradle/wrapper/gradle-wrapper.properties | 6 +
.../legacyDataMigration/android/gradlew | 244 +
.../legacyDataMigration/android/gradlew.bat | 92 +
.../android/settings.gradle | 4 +
.../react-native/legacyDataMigration/app.json | 4 +
.../legacyDataMigration/babel.config.js | 3 +
.../react-native/legacyDataMigration/index.js | 9 +
.../legacyDataMigration/ios/.xcode.env | 11 +
.../legacyDataMigration/ios/LegacyV3.swift | 332 +
.../legacyDataMigration/ios/LegacyV4.swift | 332 +
.../legacyDataMigration/ios/MigrationModule.m | 6 +
.../ios/MigrationModule.swift | 59 +
.../legacyDataMigration/ios/Podfile | 62 +
.../legacyDataMigration/ios/Podfile.lock | 725 ++
.../ios/legacyDataMigration-Bridging-Header.h | 1 +
.../project.pbxproj | 728 ++
.../xcschemes/legacyDataMigration.xcscheme | 88 +
.../ios/legacyDataMigration/AppDelegate.h | 6 +
.../ios/legacyDataMigration/AppDelegate.mm | 26 +
.../AppIcon.appiconset/Contents.json | 53 +
.../Images.xcassets/Contents.json | 6 +
.../ios/legacyDataMigration/Info.plist | 55 +
.../LaunchScreen.storyboard | 47 +
.../ios/legacyDataMigration/main.m | 10 +
.../ios/legacyDataMigrationTests/Info.plist | 24 +
.../legacyDataMigrationTests.m | 66 +
.../legacyDataMigration/jest.config.js | 3 +
.../legacyDataMigration/metro.config.js | 11 +
.../legacyDataMigration/package.json | 39 +
.../legacyDataMigration/tsconfig.json | 3 +
.../legacyDataMigration/yarn.lock | 6481 +++++++++++++++++
packages/analytics-core/src/index.ts | 2 +-
packages/analytics-core/test/index.test.ts | 2 +
.../reactnative/AmplitudeReactNativeModule.kt | 101 +
.../reactnative/LegacyDatabaseStorage.kt | 313 +
.../ios/AmplitudeReactNative.m | 3 +
.../ios/AmplitudeReactNative.swift | 74 +
.../project.pbxproj | 26 +-
.../ios/LegacyDatabaseStorage.swift | 224 +
packages/analytics-react-native/src/config.ts | 33 +-
.../src/migration/remnant-data-migration.ts | 168 +
.../src/react-native-client.ts | 4 +-
.../migration/remnant-data-migration.test.ts | 240 +
.../test/mock/setup-mobile.ts | 4 +
.../test/react-native-client.test.ts | 6 +-
.../src/config/react-native.ts | 1 +
85 files changed, 12400 insertions(+), 30 deletions(-)
create mode 100644 examples/react-native/legacyDataMigration/.bundle/config
create mode 100644 examples/react-native/legacyDataMigration/.eslintrc.js
create mode 100644 examples/react-native/legacyDataMigration/.gitignore
create mode 100644 examples/react-native/legacyDataMigration/.prettierrc.js
create mode 100644 examples/react-native/legacyDataMigration/.watchmanconfig
create mode 100644 examples/react-native/legacyDataMigration/App.tsx
create mode 100644 examples/react-native/legacyDataMigration/Gemfile
create mode 100644 examples/react-native/legacyDataMigration/MigrationChecker.ts
create mode 100644 examples/react-native/legacyDataMigration/README.md
create mode 100644 examples/react-native/legacyDataMigration/__tests__/App.test.tsx
create mode 100644 examples/react-native/legacyDataMigration/android/app/build.gradle
create mode 100644 examples/react-native/legacyDataMigration/android/app/debug.keystore
create mode 100644 examples/react-native/legacyDataMigration/android/app/proguard-rules.pro
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/debug/AndroidManifest.xml
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/debug/java/com/legacydatamigration/ReactNativeFlipper.java
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/AndroidManifest.xml
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/java/com/legacydatamigration/LegacyV3.kt
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/java/com/legacydatamigration/LegacyV4.kt
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/java/com/legacydatamigration/MainActivity.kt
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/java/com/legacydatamigration/MainApplication.kt
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/java/com/legacydatamigration/MainPackage.kt
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/java/com/legacydatamigration/MigrationModule.kt
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/drawable/rn_edit_text_material.xml
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/values/strings.xml
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/main/res/values/styles.xml
create mode 100644 examples/react-native/legacyDataMigration/android/app/src/release/java/com/legacydatamigration/ReactNativeFlipper.java
create mode 100644 examples/react-native/legacyDataMigration/android/build.gradle
create mode 100644 examples/react-native/legacyDataMigration/android/gradle.properties
create mode 100644 examples/react-native/legacyDataMigration/android/gradle/wrapper/gradle-wrapper.jar
create mode 100644 examples/react-native/legacyDataMigration/android/gradle/wrapper/gradle-wrapper.properties
create mode 100755 examples/react-native/legacyDataMigration/android/gradlew
create mode 100644 examples/react-native/legacyDataMigration/android/gradlew.bat
create mode 100644 examples/react-native/legacyDataMigration/android/settings.gradle
create mode 100644 examples/react-native/legacyDataMigration/app.json
create mode 100644 examples/react-native/legacyDataMigration/babel.config.js
create mode 100644 examples/react-native/legacyDataMigration/index.js
create mode 100644 examples/react-native/legacyDataMigration/ios/.xcode.env
create mode 100644 examples/react-native/legacyDataMigration/ios/LegacyV3.swift
create mode 100644 examples/react-native/legacyDataMigration/ios/LegacyV4.swift
create mode 100644 examples/react-native/legacyDataMigration/ios/MigrationModule.m
create mode 100644 examples/react-native/legacyDataMigration/ios/MigrationModule.swift
create mode 100644 examples/react-native/legacyDataMigration/ios/Podfile
create mode 100644 examples/react-native/legacyDataMigration/ios/Podfile.lock
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration-Bridging-Header.h
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration.xcodeproj/project.pbxproj
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration.xcodeproj/xcshareddata/xcschemes/legacyDataMigration.xcscheme
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/AppDelegate.h
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/AppDelegate.mm
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/Images.xcassets/AppIcon.appiconset/Contents.json
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/Images.xcassets/Contents.json
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/Info.plist
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/LaunchScreen.storyboard
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigration/main.m
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigrationTests/Info.plist
create mode 100644 examples/react-native/legacyDataMigration/ios/legacyDataMigrationTests/legacyDataMigrationTests.m
create mode 100644 examples/react-native/legacyDataMigration/jest.config.js
create mode 100644 examples/react-native/legacyDataMigration/metro.config.js
create mode 100644 examples/react-native/legacyDataMigration/package.json
create mode 100644 examples/react-native/legacyDataMigration/tsconfig.json
create mode 100644 examples/react-native/legacyDataMigration/yarn.lock
create mode 100644 packages/analytics-react-native/android/src/main/java/com/amplitude/reactnative/LegacyDatabaseStorage.kt
create mode 100644 packages/analytics-react-native/ios/LegacyDatabaseStorage.swift
create mode 100644 packages/analytics-react-native/src/migration/remnant-data-migration.ts
create mode 100644 packages/analytics-react-native/test/migration/remnant-data-migration.test.ts
diff --git a/examples/react-native/legacyDataMigration/.bundle/config b/examples/react-native/legacyDataMigration/.bundle/config
new file mode 100644
index 000000000..848943bb5
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/.bundle/config
@@ -0,0 +1,2 @@
+BUNDLE_PATH: "vendor/bundle"
+BUNDLE_FORCE_RUBY_PLATFORM: 1
diff --git a/examples/react-native/legacyDataMigration/.eslintrc.js b/examples/react-native/legacyDataMigration/.eslintrc.js
new file mode 100644
index 000000000..187894b6a
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/.eslintrc.js
@@ -0,0 +1,4 @@
+module.exports = {
+ root: true,
+ extends: '@react-native',
+};
diff --git a/examples/react-native/legacyDataMigration/.gitignore b/examples/react-native/legacyDataMigration/.gitignore
new file mode 100644
index 000000000..0cab2ac6f
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/.gitignore
@@ -0,0 +1,66 @@
+# OSX
+#
+.DS_Store
+
+# Xcode
+#
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+ios/.xcode.env.local
+
+# Android/IntelliJ
+#
+build/
+.idea
+.gradle
+local.properties
+*.iml
+*.hprof
+.cxx/
+*.keystore
+!debug.keystore
+
+# node.js
+#
+node_modules/
+npm-debug.log
+yarn-error.log
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
+# screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/
+
+**/fastlane/report.xml
+**/fastlane/Preview.html
+**/fastlane/screenshots
+**/fastlane/test_output
+
+# Bundle artifact
+*.jsbundle
+
+# Ruby / CocoaPods
+/ios/Pods/
+/vendor/bundle/
+
+# Temporary files created by Metro to check the health of the file watcher
+.metro-health-check*
+
+# testing
+/coverage
diff --git a/examples/react-native/legacyDataMigration/.prettierrc.js b/examples/react-native/legacyDataMigration/.prettierrc.js
new file mode 100644
index 000000000..2b540746a
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/.prettierrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ arrowParens: 'avoid',
+ bracketSameLine: true,
+ bracketSpacing: false,
+ singleQuote: true,
+ trailingComma: 'all',
+};
diff --git a/examples/react-native/legacyDataMigration/.watchmanconfig b/examples/react-native/legacyDataMigration/.watchmanconfig
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/.watchmanconfig
@@ -0,0 +1 @@
+{}
diff --git a/examples/react-native/legacyDataMigration/App.tsx b/examples/react-native/legacyDataMigration/App.tsx
new file mode 100644
index 000000000..269e6842c
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/App.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import {Alert, NativeModules} from 'react-native';
+import {Button, SafeAreaView, StyleSheet} from 'react-native';
+import {createInstance} from '@amplitude/analytics-react-native';
+import {getCookieName} from '@amplitude/analytics-client-common';
+import {UUID, MemoryStorage, STORAGE_PREFIX} from '@amplitude/analytics-core';
+import {Event, UserSession} from '@amplitude/analytics-types';
+import MigrationChecker from './MigrationChecker';
+
+interface MigrationModule {
+ prepareLegacyDatabase(
+ instanceName: string | undefined,
+ version: string,
+ ): Promise;
+}
+
+const migrationModule = NativeModules.MigrationModule as MigrationModule;
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#fff',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 10,
+ },
+});
+
+function App(): JSX.Element {
+ return (
+
+ migrateLegacyData('v4')}
+ />
+ migrateLegacyData('v3')}
+ />
+ migrateLegacyData('vMissing')}
+ />
+
+ );
+}
+
+async function migrateLegacyData(version: 'v4' | 'v3' | 'vMissing') {
+ let instanceName = UUID();
+ let apiKey = UUID();
+ let cookieName = getCookieName(apiKey);
+ let eventsKey = `${STORAGE_PREFIX}_${apiKey.substring(0, 10)}`;
+ await migrationModule.prepareLegacyDatabase(instanceName, version);
+
+ const cookieStorage1 = new MemoryStorage();
+ const storageProvider1 = new MemoryStorage();
+ const amplitude1 = createInstance();
+ await amplitude1.init(apiKey, undefined, {
+ instanceName,
+ cookieStorage: cookieStorage1,
+ storageProvider: storageProvider1,
+ optOut: true,
+ trackingSessionEvents: false,
+ }).promise;
+
+ let checker1 = new MigrationChecker(version);
+ let userSession1 = await cookieStorage1.get(cookieName);
+ const events1 = await storageProvider1.get(eventsKey);
+ checker1.checkUserSession(userSession1);
+ checker1.checkEvents(events1);
+
+ const cookieStorage2 = new MemoryStorage();
+ const storageProvider2 = new MemoryStorage();
+ const amplitude2 = createInstance();
+ await amplitude2.init(apiKey, undefined, {
+ instanceName,
+ cookieStorage: cookieStorage2,
+ storageProvider: storageProvider2,
+ optOut: true,
+ trackingSessionEvents: false,
+ }).promise;
+
+ let checker2 = new MigrationChecker(version);
+ let userSession2 = await cookieStorage2.get(cookieName);
+ const events2 = await storageProvider2.get(eventsKey);
+ checker2.checkUserSession(userSession2);
+ checker2.check(events2?.length === 0, 'events');
+
+ Alert.alert(`\
+First migration: ${
+ checker1.errors.length === 0 ? 'Success' : checker1.errors.join(', ')
+ }
+Second migration: ${
+ checker2.errors.length === 0 ? 'Success' : checker2.errors.join(', ')
+ }
+`);
+}
+
+export default App;
diff --git a/examples/react-native/legacyDataMigration/Gemfile b/examples/react-native/legacyDataMigration/Gemfile
new file mode 100644
index 000000000..1fa2c2e1a
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/Gemfile
@@ -0,0 +1,6 @@
+source 'https://rubygems.org'
+
+# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
+ruby ">= 2.6.10"
+
+gem 'cocoapods', '~> 1.12'
diff --git a/examples/react-native/legacyDataMigration/MigrationChecker.ts b/examples/react-native/legacyDataMigration/MigrationChecker.ts
new file mode 100644
index 000000000..eb9de2da5
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/MigrationChecker.ts
@@ -0,0 +1,133 @@
+import {Event, UserSession} from '@amplitude/analytics-types';
+
+type LegacyEvent = {
+ event_id: number;
+ event_type: string;
+ time: number;
+ insert_id: string;
+};
+
+const legacyDeviceId = '22833898-c487-4536-b213-40f207abdce0R';
+const legacyUserId = 'react-native-user-legacy';
+const legacySessionId = 3684219150343;
+const legacyEvents: LegacyEvent[] = [
+ {
+ event_id: 1,
+ event_type: '$identify',
+ time: 1684219150343,
+ insert_id: 'be09ecba-83f7-444a-aba0-fe1f529a3716',
+ },
+ {
+ event_id: 2,
+ event_type: '$identify',
+ time: 1684219150344,
+ insert_id: '0894387e-e923-423b-9feb-086ba8cb2cfa',
+ },
+ {
+ event_id: 1,
+ event_type: '$identify',
+ time: 1684219150358,
+ insert_id: '1a14d057-8a12-40bb-8217-2d62dd08a525',
+ },
+ {
+ event_id: 2,
+ event_type: '$identify',
+ time: 1684219150359,
+ insert_id: 'b115a299-4cc6-495b-8e4e-c2ce6f244be9',
+ },
+ {
+ event_id: 1,
+ event_type: 'legacy event 1',
+ time: 1684219150354,
+ insert_id: 'd6eff10b-9cd4-45d7-85cb-c81cb6cb8b2e',
+ },
+ {
+ event_id: 2,
+ event_type: 'legacy event 2',
+ time: 1684219150355,
+ insert_id: '7b4c5c13-6fdc-4931-9ba1-e4efdf346ee0',
+ },
+];
+
+export default class MigrationChecker {
+ errors: string[] = [];
+
+ constructor(private readonly version: 'v4' | 'v3' | 'vMissing') {}
+
+ checkUserSession(userSession: UserSession | undefined) {
+ if (userSession === undefined) {
+ this.check(false, 'userSession');
+ return;
+ }
+
+ if (this.version === 'v4' || this.version === 'v3') {
+ this.check(userSession.deviceId === legacyDeviceId, 'deviceId');
+ this.check(userSession.userId === legacyUserId, 'userId');
+ this.check(userSession.sessionId === legacySessionId, 'sessionId');
+ this.check(userSession.lastEventId === 2, 'lastEventId');
+ } else {
+ this.check(userSession.deviceId !== legacyDeviceId, 'deviceId');
+ this.check(userSession.userId === undefined, 'userId');
+ this.check(userSession.sessionId !== legacySessionId, 'sessionId');
+ this.check(userSession.lastEventId === undefined, 'lastEventId');
+ }
+ }
+
+ checkEvents(events: Event[] | undefined) {
+ if (events === undefined) {
+ this.check(false, 'events');
+ return;
+ }
+
+ if (this.version === 'v4') {
+ this.compareEvents(events, legacyEvents);
+ } else if (this.version === 'v3') {
+ this.compareEvents(events, [
+ ...legacyEvents.slice(0, 2),
+ ...legacyEvents.slice(4),
+ ]);
+ } else {
+ this.compareEvents(events, []);
+ }
+ }
+
+ private compareEvents(events: Event[], expectedEvents: LegacyEvent[]) {
+ if (events.length !== expectedEvents.length) {
+ this.check(false, 'events.length');
+ return;
+ }
+
+ events.forEach((event, i) => {
+ const eventPrefix = `events[${i}]`;
+
+ this.check(
+ event.event_id === expectedEvents[i].event_id,
+ `${eventPrefix}.event_id`,
+ );
+ this.check(
+ event.event_type === expectedEvents[i].event_type,
+ `${eventPrefix}.event_type`,
+ );
+ this.check(event.time === expectedEvents[i].time, `${eventPrefix}.time`);
+ this.check(
+ event.insert_id === expectedEvents[i].insert_id,
+ `${eventPrefix}.insert_id`,
+ );
+ this.check(
+ event.library === 'amplitude-react-native/2.17.1',
+ `${eventPrefix}.library`,
+ );
+ this.check(
+ event.device_id === legacyDeviceId,
+ `${eventPrefix}.device_id`,
+ );
+ this.check(event.user_id === legacyUserId, `${eventPrefix}.user_id`);
+ });
+ }
+
+ check(assert: boolean, error: string) {
+ if (!assert) {
+ this.errors.push(error);
+ }
+ }
+}
diff --git a/examples/react-native/legacyDataMigration/README.md b/examples/react-native/legacyDataMigration/README.md
new file mode 100644
index 000000000..12470c30e
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/README.md
@@ -0,0 +1,79 @@
+This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
+
+# Getting Started
+
+>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
+
+## Step 1: Start the Metro Server
+
+First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native.
+
+To start Metro, run the following command from the _root_ of your React Native project:
+
+```bash
+# using npm
+npm start
+
+# OR using Yarn
+yarn start
+```
+
+## Step 2: Start your Application
+
+Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app:
+
+### For Android
+
+```bash
+# using npm
+npm run android
+
+# OR using Yarn
+yarn android
+```
+
+### For iOS
+
+```bash
+# using npm
+npm run ios
+
+# OR using Yarn
+yarn ios
+```
+
+If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
+
+This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively.
+
+## Step 3: Modifying your App
+
+Now that you have successfully run the app, let's modify it.
+
+1. Open `App.tsx` in your text editor of choice and edit some lines.
+2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes!
+
+ For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes!
+
+## Congratulations! :tada:
+
+You've successfully run and modified your React Native App. :partying_face:
+
+### Now what?
+
+- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
+- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started).
+
+# Troubleshooting
+
+If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
+
+# Learn More
+
+To learn more about React Native, take a look at the following resources:
+
+- [React Native Website](https://reactnative.dev) - learn more about React Native.
+- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
+- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
+- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
+- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.
diff --git a/examples/react-native/legacyDataMigration/__tests__/App.test.tsx b/examples/react-native/legacyDataMigration/__tests__/App.test.tsx
new file mode 100644
index 000000000..3413ac1c4
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/__tests__/App.test.tsx
@@ -0,0 +1,17 @@
+/**
+ * @format
+ */
+
+import 'react-native';
+import React from 'react';
+import App from '../App';
+
+// Note: import explicitly to use the types shiped with jest.
+import {it} from '@jest/globals';
+
+// Note: test renderer must be required after react-native.
+import renderer from 'react-test-renderer';
+
+it('renders correctly', () => {
+ renderer.create( );
+});
diff --git a/examples/react-native/legacyDataMigration/android/app/build.gradle b/examples/react-native/legacyDataMigration/android/app/build.gradle
new file mode 100644
index 000000000..12f5ba3ac
--- /dev/null
+++ b/examples/react-native/legacyDataMigration/android/app/build.gradle
@@ -0,0 +1,125 @@
+apply plugin: "com.android.application"
+apply plugin: "com.facebook.react"
+apply plugin: "kotlin-android"
+apply plugin: "kotlin-android-extensions"
+
+/**
+ * This is the configuration block to customize your React Native Android app.
+ * By default you don't need to apply any configuration, just uncomment the lines you need.
+ */
+react {
+ /* Folders */
+ // The root of your project, i.e. where "package.json" lives. Default is '..'
+ // root = file("../")
+ // The folder where the react-native NPM package is. Default is ../node_modules/react-native
+ // reactNativeDir = file("../node_modules/react-native")
+ // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
+ // codegenDir = file("../node_modules/@react-native/codegen")
+ // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
+ // cliFile = file("../node_modules/react-native/cli.js")
+
+ /* Variants */
+ // The list of variants to that are debuggable. For those we're going to
+ // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
+ // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
+ // debuggableVariants = ["liteDebug", "prodDebug"]
+
+ /* Bundling */
+ // A list containing the node command and its flags. Default is just 'node'.
+ // nodeExecutableAndArgs = ["node"]
+ //
+ // The command to run when bundling. By default is 'bundle'
+ // bundleCommand = "ram-bundle"
+ //
+ // The path to the CLI configuration file. Default is empty.
+ // bundleConfig = file(../rn-cli.config.js)
+ //
+ // The name of the generated asset file containing your JS bundle
+ // bundleAssetName = "MyApplication.android.bundle"
+ //
+ // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
+ // entryFile = file("../js/MyApplication.android.js")
+ //
+ // A list of extra flags to pass to the 'bundle' commands.
+ // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
+ // extraPackagerArgs = []
+
+ /* Hermes Commands */
+ // The hermes compiler command to run. By default it is 'hermesc'
+ // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
+ //
+ // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
+ // hermesFlags = ["-O", "-output-source-map"]
+}
+
+/**
+ * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
+ */
+def enableProguardInReleaseBuilds = false
+
+/**
+ * The preferred build flavor of JavaScriptCore (JSC)
+ *
+ * For example, to use the international variant, you can use:
+ * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
+ *
+ * The international variant includes ICU i18n library and necessary data
+ * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
+ * give correct results when using with locales other than en-US. Note that
+ * this variant is about 6MiB larger per architecture than default.
+ */
+def jscFlavor = 'org.webkit:android-jsc:+'
+
+android {
+ ndkVersion rootProject.ext.ndkVersion
+
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ namespace "com.legacydatamigration"
+ defaultConfig {
+ applicationId "com.legacydatamigration"
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ versionCode 1
+ versionName "1.0"
+ }
+ signingConfigs {
+ debug {
+ storeFile file('debug.keystore')
+ storePassword 'android'
+ keyAlias 'androiddebugkey'
+ keyPassword 'android'
+ }
+ }
+ buildTypes {
+ debug {
+ signingConfig signingConfigs.debug
+ }
+ release {
+ // Caution! In production, you need to generate your own keystore file.
+ // see https://reactnative.dev/docs/signed-apk-android.
+ signingConfig signingConfigs.debug
+ minifyEnabled enableProguardInReleaseBuilds
+ proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
+ }
+ }
+}
+
+dependencies {
+ // The version of react-native is set by the React Native Gradle Plugin
+ implementation("com.facebook.react:react-android")
+
+ debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
+ debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
+ exclude group:'com.squareup.okhttp3', module:'okhttp'
+ }
+
+ debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
+ if (hermesEnabled.toBoolean()) {
+ implementation("com.facebook.react:hermes-android")
+ } else {
+ implementation jscFlavor
+ }
+}
+
+apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
diff --git a/examples/react-native/legacyDataMigration/android/app/debug.keystore b/examples/react-native/legacyDataMigration/android/app/debug.keystore
new file mode 100644
index 0000000000000000000000000000000000000000..364e105ed39fbfd62001429a68140672b06ec0de
GIT binary patch
literal 2257
zcmchYXEfYt8;7T1^dLH$VOTZ%2NOdOH5j5LYLtZ0q7x-V8_6gU5)#7dkq{HTmsfNq
zB3ZqcAxeY^G10@?efK?Q&)M(qInVv!xjx+IKEL}p*K@LYvIzo#AZG>st5|P)KF1_Z;y){W{<7K{nl!CPuE
z_^(!C(Ol0n8
zK13*rzAtW>(wULKPRYLd7G18F8#1P`V*9`(Poj26eOXYyBVZPno~Cvvhx7vPjAuZo
zF?VD!zB~QG(!zbw#qsxT8%BSpqMZ4f70ZPn-3y$L8{EVbbN9$H`B&Z1quk9tgp5FM
zuxp3pJ0b8u|3+#5bkJ4SRnCF2l7#DyLYXYY8*?OuAwK4E6J{0N=O3QNVzQ$L#FKkR
zi-c@&!nDvezOV$i$Lr}iF$XEcwnybQ6WZrMKuw8gCL^U#D;q3t&HpTbqyD%vG=TeDlzCT~MXUPC|Leb-Uk+
z=vnMd(|>ld?Fh>V8poP;q;;nc@en$|rnP0ytzD&fFkCeUE^kG9Kx4wUh!!rpjwKDP
zyw_e|a^x_w3E
zP}}@$g>*LLJ4i0`Gx)qltL}@;mDv}D*xR^oeWcWdPkW@Uu)B^X&4W1$p6}ze!zudJ
zyiLg@uggoMIArBr*27EZV7djDg@W1MaL+rcZ-lrANJQ%%>u8)ZMWU@R2qtnmG(acP
z0d_^!t>}5W
zpT`*2NR+0+SpTHb+6Js4b;%LJB;B_-ChhnU5py}iJtku*hm5F0!iql8Hrpcy1aYbT
z1*dKC5ua6pMX@@iO