From 2bcb587d2f578425f907e83ad01187b0a517ead7 Mon Sep 17 00:00:00 2001 From: Peter Perlepes Date: Fri, 12 Jan 2024 13:52:09 +0200 Subject: [PATCH] Extend cross-domain linking with more user/session information and optional configuration #1278 --- .bundlemonrc.json | 8 +- .../browser-tracker/browser-tracker.api.md | 13 + ...omain-linking-entity_2023-08-28-10-46.json | 10 + ...omain-linking-entity_2023-08-28-10-46.json | 10 + .../rush/browser-approved-packages.json | 4 + common/config/rush/pnpm-lock.yaml | 369 ++++++++++++++++++ common/config/rush/repo-state.json | 2 +- libraries/browser-tracker-core/package.json | 3 +- .../src/helpers/cross_domain.ts | 72 ++++ .../browser-tracker-core/src/helpers/index.ts | 1 + .../browser-tracker-core/src/tracker/index.ts | 83 ++-- .../browser-tracker-core/src/tracker/types.ts | 19 + .../test/tracker/cross_domain.test.ts | 147 +++++++ trackers/browser-tracker/src/api.ts | 6 +- 14 files changed, 698 insertions(+), 49 deletions(-) create mode 100644 common/changes/@snowplow/browser-tracker-core/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json create mode 100644 common/changes/@snowplow/browser-tracker/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json create mode 100644 libraries/browser-tracker-core/src/helpers/cross_domain.ts create mode 100644 libraries/browser-tracker-core/test/tracker/cross_domain.test.ts diff --git a/.bundlemonrc.json b/.bundlemonrc.json index 502cd2cd3..466289b96 100644 --- a/.bundlemonrc.json +++ b/.bundlemonrc.json @@ -7,22 +7,22 @@ }, { "path": "./trackers/browser-tracker/dist/index.umd.min.js", - "maxSize": "15kb", + "maxSize": "15.5kb", "maxPercentIncrease": 10 }, { "path": "./trackers/javascript-tracker/dist/sp.js", - "maxSize": "25kb", + "maxSize": "25.5kb", "maxPercentIncrease": 10 }, { "path": "./trackers/javascript-tracker/dist/sp.lite.js", - "maxSize": "15.5kb", + "maxSize": "16kb", "maxPercentIncrease": 10 }, { "path": "./libraries/browser-tracker-core/dist/index.module.js", - "maxSize": "26kb", + "maxSize": "27kb", "maxPercentIncrease": 10 }, { diff --git a/api-docs/docs/browser-tracker/browser-tracker.api.md b/api-docs/docs/browser-tracker/browser-tracker.api.md index a449cbe07..2936496d3 100644 --- a/api-docs/docs/browser-tracker/browser-tracker.api.md +++ b/api-docs/docs/browser-tracker/browser-tracker.api.md @@ -221,6 +221,18 @@ export type EventBatch = GetBatch | PostBatch; // @public (undocumented) export type EventMethod = "post" | "get" | "beacon"; +// @public (undocumented) +export type ExtendedCrossDomainLinkerAttributes = { + userId?: boolean; + sessionId?: boolean; + sourceId?: boolean; + sourcePlatform?: boolean; + reason?: boolean | ((evt: Event) => string); +}; + +// @public (undocumented) +export type ExtendedCrossDomainLinkerOptions = boolean | ExtendedCrossDomainLinkerAttributes; + // @public export type FilterProvider = [ ContextFilter, @@ -371,6 +383,7 @@ export type TrackerConfiguration = { useStm?: boolean; bufferSize?: number; crossDomainLinker?: (elt: HTMLAnchorElement | HTMLAreaElement) => boolean; + useExtendedCrossDomainLinker?: ExtendedCrossDomainLinkerOptions; maxPostBytes?: number; maxGetBytes?: number; discoverRootDomain?: boolean; diff --git a/common/changes/@snowplow/browser-tracker-core/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json b/common/changes/@snowplow/browser-tracker-core/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json new file mode 100644 index 000000000..cf3ab3d98 --- /dev/null +++ b/common/changes/@snowplow/browser-tracker-core/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker-core", + "comment": "Allow for extended cross domain linking information using the useExtendedCrossDomainLinker option", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker-core" +} \ No newline at end of file diff --git a/common/changes/@snowplow/browser-tracker/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json b/common/changes/@snowplow/browser-tracker/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json new file mode 100644 index 000000000..508727e2d --- /dev/null +++ b/common/changes/@snowplow/browser-tracker/feature-extend-cross-domain-linking-entity_2023-08-28-10-46.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@snowplow/browser-tracker", + "comment": "Add new useExtendedCrossDomainLinker option", + "type": "none" + } + ], + "packageName": "@snowplow/browser-tracker" +} \ No newline at end of file diff --git a/common/config/rush/browser-approved-packages.json b/common/config/rush/browser-approved-packages.json index 376c8af07..95e14afe7 100644 --- a/common/config/rush/browser-approved-packages.json +++ b/common/config/rush/browser-approved-packages.json @@ -138,6 +138,10 @@ "name": "@snowplow/tracker-core", "allowedCategories": [ "libraries", "plugins", "trackers" ] }, + { + "name": "@testing-library/dom", + "allowedCategories": [ "libraries" ] + }, { "name": "@types/dockerode", "allowedCategories": [ "trackers" ] diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 1f09a82f2..b85e817fe 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -11,6 +11,7 @@ importers: '@rollup/plugin-commonjs': ~21.0.2 '@rollup/plugin-node-resolve': ~13.1.3 '@snowplow/tracker-core': workspace:* + '@testing-library/dom': ~9.3.1 '@types/jest': ~27.4.1 '@types/jsdom': ~16.2.14 '@types/sha1': ~1.1.3 @@ -41,6 +42,7 @@ importers: '@ampproject/rollup-plugin-closure-compiler': 0.27.0_rollup@2.70.1 '@rollup/plugin-commonjs': 21.0.2_rollup@2.70.1 '@rollup/plugin-node-resolve': 13.1.3_rollup@2.70.1 + '@testing-library/dom': 9.3.4 '@types/jest': 27.4.1 '@types/jsdom': 16.2.14 '@types/sha1': 1.1.3 @@ -2846,6 +2848,20 @@ packages: resolution: {integrity: sha512-kIhULpw9TrGYnHp/8VfdcneIcxKnLixmADtukQRtJUmsVlMg0niMkwV0xZmi8hqa57xqilIHjWFA0GKvEjVU5g==} dev: true + /@testing-library/dom/9.3.4: + resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} + engines: {node: '>=14'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/runtime': 7.18.9 + '@types/aria-query': 5.0.4 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: true + /@tootallnate/once/1.1.2: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} @@ -2871,6 +2887,10 @@ packages: resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==} dev: true + /@types/aria-query/5.0.4: + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + dev: true + /@types/babel__core/7.1.18: resolution: {integrity: sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==} dependencies: @@ -3780,6 +3800,19 @@ packages: engines: {node: '>=6.0'} dev: true + /aria-query/5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + dependencies: + deep-equal: 2.2.3 + dev: true + + /array-buffer-byte-length/1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.5 + is-array-buffer: 3.0.2 + dev: true + /array-find-index/1.0.2: resolution: {integrity: sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=} engines: {node: '>=0.10.0'} @@ -3894,6 +3927,11 @@ packages: - supports-color dev: true + /available-typed-arrays/1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + /aws-sign2/0.7.0: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} dev: true @@ -4313,6 +4351,14 @@ packages: get-intrinsic: 1.0.2 dev: true + /call-bind/1.0.5: + resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + dependencies: + function-bind: 1.1.2 + get-intrinsic: 1.2.2 + set-function-length: 1.1.1 + dev: true + /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -5067,6 +5113,30 @@ packages: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true + /deep-equal/2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.5 + es-get-iterator: 1.1.3 + get-intrinsic: 1.2.2 + is-arguments: 1.1.1 + is-array-buffer: 3.0.2 + is-date-object: 1.0.5 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.1 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.13 + dev: true + /deep-is/0.1.3: resolution: {integrity: sha512-GtxAN4HvBachZzm4OnWqc45ESpUCMwkYcsjnsPs23FwJbsO+k4t0k9bQCgOmzIlpHO28+WPK/KRbRk0DDHuuDw==} dev: true @@ -5091,6 +5161,15 @@ packages: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} + /define-data-property/1.1.1: + resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + /define-properties/1.1.3: resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} engines: {node: '>= 0.4'} @@ -5098,6 +5177,15 @@ packages: object-keys: 1.1.1 dev: true + /define-properties/1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + has-property-descriptors: 1.0.1 + object-keys: 1.1.1 + dev: true + /degenerator/5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -5213,6 +5301,10 @@ packages: esutils: 2.0.3 dev: true + /dom-accessibility-api/0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dev: true + /domexception/2.0.1: resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==} engines: {node: '>=8'} @@ -5401,6 +5493,20 @@ packages: string.prototype.trimstart: 1.0.3 dev: true + /es-get-iterator/1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + stop-iteration-iterator: 1.0.0 + dev: true + /es-to-primitive/1.2.1: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} @@ -6039,6 +6145,12 @@ packages: optional: true dev: true + /for-each/0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.3 + dev: true + /foreground-child/3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} @@ -6171,10 +6283,18 @@ packages: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true + /function-bind/1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + dev: true + /functional-red-black-tree/1.0.1: resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} dev: true + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + /gauge/2.7.4: resolution: {integrity: sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==} dependencies: @@ -6231,6 +6351,15 @@ packages: has-symbols: 1.0.1 dev: true + /get-intrinsic/1.2.2: + resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + dependencies: + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + dev: true + /get-package-type/0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -6446,6 +6575,12 @@ packages: google-closure-compiler-windows: 20210808.0.0 dev: true + /gopd/1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + /got/11.8.5: resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==} engines: {node: '>=10.19.0'} @@ -6577,6 +6712,10 @@ packages: ansi-regex: 2.1.1 dev: true + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + /has-flag/3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -6587,6 +6726,17 @@ packages: engines: {node: '>=8'} dev: true + /has-property-descriptors/1.0.1: + resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /has-proto/1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + /has-symbol-support-x/1.4.2: resolution: {integrity: sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==} dev: true @@ -6596,12 +6746,24 @@ packages: engines: {node: '>= 0.4'} dev: true + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + /has-to-string-tag-x/1.4.1: resolution: {integrity: sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==} dependencies: has-symbol-support-x: 1.4.2 dev: true + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + /has-unicode/2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: true @@ -6620,6 +6782,13 @@ packages: minimalistic-assert: 1.0.1 dev: true + /hasown/2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: true + /header-case/2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} dependencies: @@ -6899,6 +7068,15 @@ packages: wrap-ansi: 6.2.0 dev: true + /internal-slot/1.0.6: + resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + hasown: 2.0.0 + side-channel: 1.0.4 + dev: true + /into-stream/3.1.0: resolution: {integrity: sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==} engines: {node: '>=4'} @@ -6930,10 +7108,32 @@ packages: engines: {node: '>=8'} dev: true + /is-arguments/1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-array-buffer/3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + is-typed-array: 1.1.12 + dev: true + /is-arrayish/0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + /is-binary-path/2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -6941,6 +7141,14 @@ packages: binary-extensions: 2.1.0 dev: true + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + /is-callable/1.2.3: resolution: {integrity: sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==} engines: {node: '>= 0.4'} @@ -6970,6 +7178,13 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-error/2.2.2: resolution: {integrity: sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==} dev: true @@ -7017,6 +7232,10 @@ packages: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: true + /is-map/2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + /is-module/1.0.0: resolution: {integrity: sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=} dev: true @@ -7030,6 +7249,13 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-number/7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -7086,11 +7312,29 @@ packages: has-symbols: 1.0.1 dev: true + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: true + /is-retry-allowed/1.2.0: resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} engines: {node: '>=0.10.0'} dev: true + /is-set/2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.5 + dev: true + /is-stream/1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} engines: {node: '>=0.10.0'} @@ -7106,6 +7350,13 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-symbol/1.0.3: resolution: {integrity: sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==} engines: {node: '>= 0.4'} @@ -7113,6 +7364,13 @@ packages: has-symbols: 1.0.1 dev: true + /is-typed-array/1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.13 + dev: true + /is-typedarray/1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} dev: true @@ -7140,6 +7398,17 @@ packages: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: true + /is-weakmap/2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + + /is-weakset/2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + dev: true + /is2/2.0.6: resolution: {integrity: sha512-+Z62OHOjA6k2sUDOKXoZI3EXv7Fb1K52jpTBLbkfx62bcUeSsrTBLhEquCRDKTx0XE5XbHcG/S2vrtE3lnEDsQ==} engines: {node: '>=v0.10.0'} @@ -7157,6 +7426,10 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true + /isarray/2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isbot/3.3.4: resolution: {integrity: sha512-a6o/e6nBMoRGvoovg5NT2r/N7S4398yCDXc6HgEOILdBAjYv05SX1MBhgc8SHnEJdRyLfOpAPqc10ezLWkj7rQ==} engines: {node: '>=12'} @@ -8323,6 +8596,11 @@ packages: engines: {node: '>=12'} dev: true + /lz-string/1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + dev: true + /magic-string/0.25.7: resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==} dependencies: @@ -8969,6 +9247,14 @@ packages: resolution: {integrity: sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==} dev: true + /object-is/1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.1.3 + dev: true + /object-keys/1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -8989,6 +9275,16 @@ packages: object-keys: 1.1.1 dev: true + /object.assign/4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + /on-finished/2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -9861,6 +10157,15 @@ packages: resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} dev: true + /regexp.prototype.flags/1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + define-properties: 1.2.1 + set-function-name: 2.0.1 + dev: true + /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} @@ -10286,6 +10591,25 @@ packages: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true + /set-function-length/1.1.1: + resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /set-function-name/2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.1 + dev: true + /setimmediate/1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} dev: true @@ -10329,6 +10653,14 @@ packages: resolution: {integrity: sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==} dev: true + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + object-inspect: 1.12.0 + dev: true + /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true @@ -10586,6 +10918,13 @@ packages: engines: {node: '>= 0.6'} dev: true + /stop-iteration-iterator/1.0.0: + resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} + engines: {node: '>= 0.4'} + dependencies: + internal-slot: 1.0.6 + dev: true + /stream-buffers/3.0.2: resolution: {integrity: sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==} engines: {node: '>= 0.10.0'} @@ -11608,6 +11947,36 @@ packages: webidl-conversions: 6.1.0 dev: true + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.3 + dev: true + + /which-collection/1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + + /which-typed-array/1.1.13: + resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.5 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + /which/1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true diff --git a/common/config/rush/repo-state.json b/common/config/rush/repo-state.json index d5834052d..4d226ab04 100644 --- a/common/config/rush/repo-state.json +++ b/common/config/rush/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "f22225c05d2747a95a11b5d6c6ee8827084cf800", + "pnpmShrinkwrapHash": "76374ca64642c7cf6282ba6e6c9417787e019665", "preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f" } diff --git a/libraries/browser-tracker-core/package.json b/libraries/browser-tracker-core/package.json index 9bda3600e..6f2bf998d 100644 --- a/libraries/browser-tracker-core/package.json +++ b/libraries/browser-tracker-core/package.json @@ -48,6 +48,7 @@ "rollup-plugin-terser": "~7.0.2", "rollup-plugin-ts": "~2.0.5", "ts-jest": "~27.1.3", - "typescript": "~4.6.2" + "typescript": "~4.6.2", + "@testing-library/dom": "~9.3.1" } } diff --git a/libraries/browser-tracker-core/src/helpers/cross_domain.ts b/libraries/browser-tracker-core/src/helpers/cross_domain.ts new file mode 100644 index 000000000..75fc8c088 --- /dev/null +++ b/libraries/browser-tracker-core/src/helpers/cross_domain.ts @@ -0,0 +1,72 @@ +import { ExtendedCrossDomainLinkerAttributes, Platform } from '../tracker/types'; + +const DEFAULT_CROSS_DOMAIN_LINKER_PARAMS: ExtendedCrossDomainLinkerAttributes = { + sessionId: true, + sourceId: true, + sourcePlatform: false, + userId: false, + reason: false, +}; + +interface ExtendedCrossDomainLinkerValues { + domainUserId?: string; + /* Timestamp of the cross-domain link click. */ + timestamp?: number; + /* Current user ID as set with setUserId(). */ + userId?: string; + /* Visitor ID. */ + sessionId?: string; + /* The app ID. */ + sourceId?: string; + sourcePlatform?: Platform; + /* Link text of the cross-domain link. */ + reason?: string; +} + +export function createCrossDomainParameterValue( + isExtendedFormat: boolean, + attributeConfiguration: ExtendedCrossDomainLinkerAttributes | undefined, + attributeValues: ExtendedCrossDomainLinkerValues & { + /* As `reason` might be a callback, we also need to pass the event to calculate the reason value. */ + event: Event; + } +): string { + let crossDomainParameterValue; + const timestamp = new Date().getTime(); + const config = { ...DEFAULT_CROSS_DOMAIN_LINKER_PARAMS, ...attributeConfiguration }; + const { domainUserId, userId, sessionId, sourceId, sourcePlatform, event } = attributeValues; + + const eventTarget = event.currentTarget as HTMLAnchorElement | HTMLAreaElement | null; + const reason = typeof config.reason === 'function' ? config.reason(event) : eventTarget?.textContent?.trim(); + + if (isExtendedFormat) { + /* Index is used by Enrich, so it should not be changed. */ + crossDomainParameterValue = [ + domainUserId, + timestamp, + config.sessionId && sessionId, + config.userId && urlSafeBase64Encode(userId || ''), + config.sourceId && urlSafeBase64Encode(sourceId || ''), + config.sourcePlatform && sourcePlatform, + config.reason && urlSafeBase64Encode(reason || ''), + ] + .map((attribute) => attribute || '') + .join('.') + // Remove trailing dots + .replace(/([.]*$)/, ''); + } else { + crossDomainParameterValue = attributeValues.domainUserId + '.' + timestamp; + } + + return crossDomainParameterValue; +} + +/** + * + * The url-safe variation emits - and _ instead of + and / characters. Also removes = sign padding. + * @param {string} str The string to encode in a URL safe manner + * @return {string} + */ +export function urlSafeBase64Encode(str: string) { + return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''); +} diff --git a/libraries/browser-tracker-core/src/helpers/index.ts b/libraries/browser-tracker-core/src/helpers/index.ts index b160157d4..817780a86 100755 --- a/libraries/browser-tracker-core/src/helpers/index.ts +++ b/libraries/browser-tracker-core/src/helpers/index.ts @@ -29,6 +29,7 @@ */ export * from './storage'; +export * from './cross_domain'; declare global { interface EventTarget { diff --git a/libraries/browser-tracker-core/src/tracker/index.ts b/libraries/browser-tracker-core/src/tracker/index.ts index e79d30484..3754376aa 100755 --- a/libraries/browser-tracker-core/src/tracker/index.ts +++ b/libraries/browser-tracker-core/src/tracker/index.ts @@ -1,33 +1,3 @@ -/* - * Copyright (c) 2022 Snowplow Analytics Ltd, 2010 Anthon Pang - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - import { trackerCore, buildPagePing, @@ -56,6 +26,7 @@ import { isInteger, attemptGetSessionStorage, attemptWriteSessionStorage, + createCrossDomainParameterValue, } from '../helpers'; import { BrowserPlugin } from '../plugins'; import { OutQueueManager } from './out_queue'; @@ -75,6 +46,7 @@ import { BrowserPluginConfiguration, ClearUserDataConfiguration, ClientSession, + ExtendedCrossDomainLinkerOptions, } from './types'; import { parseIdCookie, @@ -186,7 +158,17 @@ export function Tracker( }, getAnonymousTracking = (config: TrackerConfiguration) => !!config.anonymousTracking, isBrowserContextAvailable = trackerConfiguration?.contexts?.browser ?? false, - isWebPageContextAvailable = trackerConfiguration?.contexts?.webPage ?? true; + isWebPageContextAvailable = trackerConfiguration?.contexts?.webPage ?? true, + getExtendedCrossDomainTrackingConfiguration = (crossDomainTrackingConfig: ExtendedCrossDomainLinkerOptions) => { + if (typeof crossDomainTrackingConfig === 'boolean') { + return { useExtendedCrossDomainLinker: crossDomainTrackingConfig }; + } + + return { + useExtendedCrossDomainLinker: true, + collectCrossDomainAttributes: crossDomainTrackingConfig, + }; + }; // Get all injected plugins browserPlugins.push(getBrowserDataPlugin()); @@ -321,7 +303,10 @@ export function Tracker( configSessionContext = trackerConfiguration.contexts?.session ?? false, toOptoutByCookie: string | boolean, onSessionUpdateCallback = trackerConfiguration.onSessionUpdateCallback, - manualSessionUpdateCalled = false; + manualSessionUpdateCalled = false, + { useExtendedCrossDomainLinker, collectCrossDomainAttributes } = getExtendedCrossDomainTrackingConfiguration( + trackerConfiguration.useExtendedCrossDomainLinker || false + ); if (trackerConfiguration.hasOwnProperty('discoverRootDomain') && trackerConfiguration.discoverRootDomain) { configCookieDomain = findRootDomain(configCookieSameSite, configCookieSecure); @@ -369,31 +354,45 @@ export function Tracker( } /** - * Decorate the querystring of a single link + * Create link handler to decorate the querystring of a link (onClick/onMouseDown) * * @param event - e The event targeting the link */ - function linkDecorationHandler(evt: Event) { - const timestamp = new Date().getTime(); - const elt = evt.currentTarget as HTMLAnchorElement | HTMLAreaElement | null; - if (elt?.href) { - elt.href = decorateQuerystring(elt.href, '_sp', domainUserId + '.' + timestamp); - } + function addLinkDecorationHandler(extended: boolean): (evt: Event) => void { + const CROSS_DOMAIN_PARAMETER_NAME = '_sp'; + + return (evt) => { + const elt = evt.currentTarget as HTMLAnchorElement | HTMLAreaElement | null; + + const crossDomainParameterValue = createCrossDomainParameterValue(extended, collectCrossDomainAttributes, { + domainUserId, + userId: businessUserId || undefined, + sessionId: memorizedSessionId, + sourceId: configTrackerSiteId, + sourcePlatform: configPlatform, + event: evt, + }); + + if (elt?.href) { + elt.href = decorateQuerystring(elt.href, CROSS_DOMAIN_PARAMETER_NAME, crossDomainParameterValue); + } + }; } /** - * Enable querystring decoration for links pasing a filter + * Enable querystring decoration for links passing a filter * Whenever such a link is clicked on or navigated to via the keyboard, * add "_sp={{duid}}.{{timestamp}}" to its querystring * * @param crossDomainLinker - Function used to determine which links to decorate */ function decorateLinks(crossDomainLinker: (elt: HTMLAnchorElement | HTMLAreaElement) => boolean) { + const crossDomainLinkHandler = addLinkDecorationHandler(useExtendedCrossDomainLinker); for (let i = 0; i < document.links.length; i++) { const elt = document.links[i]; if (!(elt as any).spDecorationEnabled && crossDomainLinker(elt)) { - addEventListener(elt, 'click', linkDecorationHandler, true); - addEventListener(elt, 'mousedown', linkDecorationHandler, true); + elt.addEventListener('click', crossDomainLinkHandler, true); + elt.addEventListener('mousedown', crossDomainLinkHandler, true); // Don't add event listeners more than once (elt as any).spDecorationEnabled = true; diff --git a/libraries/browser-tracker-core/src/tracker/types.ts b/libraries/browser-tracker-core/src/tracker/types.ts index fa2105500..922e6af90 100755 --- a/libraries/browser-tracker-core/src/tracker/types.ts +++ b/libraries/browser-tracker-core/src/tracker/types.ts @@ -32,6 +32,20 @@ export type CookieSameSite = 'None' | 'Lax' | 'Strict'; /* The supported methods which events can be sent with */ export type EventMethod = 'post' | 'get' | 'beacon'; +/* Available configuration for the extended cross domain linker */ +export type ExtendedCrossDomainLinkerAttributes = { + userId?: boolean; + sessionId?: boolean; + sourceId?: boolean; + sourcePlatform?: boolean; + /** + * Allow for the collection of the link text when a cross-domain link is clicked. Can also accept a callback for customizing information collection. + */ + reason?: boolean | ((evt: Event) => string); +}; + +export type ExtendedCrossDomainLinkerOptions = boolean | ExtendedCrossDomainLinkerAttributes; + /** * The configuration object for initialising the tracker * @example @@ -126,6 +140,11 @@ export type TrackerConfiguration = { * links on the callback */ crossDomainLinker?: (elt: HTMLAnchorElement | HTMLAreaElement) => boolean; + /** + * Configure the cross domain linker to use the extended format, allowing for + * more user/session information to pass to the cross domain navigation. + */ + useExtendedCrossDomainLinker?: ExtendedCrossDomainLinkerOptions; /** * The max size a POST request can be before the tracker will force send it * @defaultValue 40000 diff --git a/libraries/browser-tracker-core/test/tracker/cross_domain.test.ts b/libraries/browser-tracker-core/test/tracker/cross_domain.test.ts new file mode 100644 index 000000000..cf7748e9f --- /dev/null +++ b/libraries/browser-tracker-core/test/tracker/cross_domain.test.ts @@ -0,0 +1,147 @@ +import * as uuid from 'uuid'; +jest.mock('uuid'); +const MOCK_UUID = '123456789'; +jest.spyOn(uuid, 'v4').mockReturnValue(MOCK_UUID); + +import { createTracker } from '../helpers'; +import { getByText, queryByText, waitFor } from '@testing-library/dom'; +import { urlSafeBase64Encode } from '../../src'; + +function getCrossDomainURLParam(url: string) { + const CROSS_DOMAIN_PARAMETER_NAME = '_sp'; + const urlParams = new URLSearchParams(new URL(url).search); + return urlParams.get(CROSS_DOMAIN_PARAMETER_NAME); +} + +function decodeExtendedCrossDomainLinkParam(crossDomainLinkValue: string) { + return crossDomainLinkValue.split('.'); +} + +describe('Cross-domain linking: ', () => { + const CROSS_DOMAIN_LINK_PARAMETERS_LENGTH = 7; + const standardDate = new Date(2023, 1, 1); + + beforeAll(() => { + jest.useFakeTimers('modern'); + jest.setSystemTime(standardDate); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + const CROSS_DOMAIN_LINK_TEXT = 'Cross-domain link'; + const appId = 'my-app'; + const userId = 'U1234'; + function getLinkDom() { + const div = document.createElement('div'); + div.innerHTML = ` + ${CROSS_DOMAIN_LINK_TEXT} + `; + return div; + } + + it('Adds the correct link decoration', async () => { + const container = getLinkDom(); + + const elem = getByText(container, CROSS_DOMAIN_LINK_TEXT); + // @ts-ignore + jest.spyOn(document, 'links', 'get').mockReturnValue([elem]); + createTracker({ crossDomainLinker: () => true }); + elem.click(); + await waitFor(() => { + const clickedLink = queryByText(container, CROSS_DOMAIN_LINK_TEXT)?.getAttribute('href') as string; + const crossDomainLinkParam = getCrossDomainURLParam(clickedLink); + const crossDomainParamParts = crossDomainLinkParam?.split('.') as string[]; + expect(crossDomainParamParts?.length).toEqual(2); + expect(crossDomainParamParts[0]).toEqual(MOCK_UUID); + expect(crossDomainParamParts[1]).toEqual(String(standardDate.getTime())); + }); + }); + + it('Adds the correct link decoration with default extended format', async () => { + const container = getLinkDom(); + const elem = getByText(container, CROSS_DOMAIN_LINK_TEXT); + // @ts-ignore + jest.spyOn(document, 'links', 'get').mockReturnValue([elem]); + const t = createTracker({ appId, crossDomainLinker: () => true, useExtendedCrossDomainLinker: true }); + t?.setUserId(userId); + elem.click(); + await waitFor(() => { + const clickedLink = queryByText(container, CROSS_DOMAIN_LINK_TEXT)?.getAttribute('href') as string; + const crossDomainLinkParam = getCrossDomainURLParam(clickedLink) as string; + const decodedCrossDomainLinkParam = decodeExtendedCrossDomainLinkParam(crossDomainLinkParam); + expect(decodedCrossDomainLinkParam.length).toBe(CROSS_DOMAIN_LINK_PARAMETERS_LENGTH - 2); + expect(decodedCrossDomainLinkParam).toStrictEqual([ + MOCK_UUID, + String(standardDate.getTime()), + MOCK_UUID, + '', + urlSafeBase64Encode(appId), + ]); + }); + }); + + it('Adds the correct link decoration with configurable extended format and link text collection', async () => { + const container = getLinkDom(); + + const elem = getByText(container, CROSS_DOMAIN_LINK_TEXT); + // @ts-ignore + jest.spyOn(document, 'links', 'get').mockReturnValue([elem]); + const t = createTracker({ + appId, + crossDomainLinker: () => true, + useExtendedCrossDomainLinker: { userId: true, reason: true, sourcePlatform: true }, + }); + t?.setUserId(userId); + elem.click(); + await waitFor(() => { + const clickedLink = queryByText(container, CROSS_DOMAIN_LINK_TEXT)?.getAttribute('href') as string; + const crossDomainLinkParam = getCrossDomainURLParam(clickedLink) as string; + const decodedCrossDomainLinkParam = decodeExtendedCrossDomainLinkParam(crossDomainLinkParam); + expect(decodedCrossDomainLinkParam.length).toBe(CROSS_DOMAIN_LINK_PARAMETERS_LENGTH); + expect(decodedCrossDomainLinkParam).toStrictEqual([ + MOCK_UUID, + String(standardDate.getTime()), + MOCK_UUID, + urlSafeBase64Encode(userId), + urlSafeBase64Encode(appId), + 'web', + urlSafeBase64Encode(CROSS_DOMAIN_LINK_TEXT), + ]); + }); + }); + + it('Adds the correct link decoration with configurable extended format and event callback', async () => { + const container = getLinkDom(); + + const elem = getByText(container, CROSS_DOMAIN_LINK_TEXT); + // @ts-ignore + jest.spyOn(document, 'links', 'get').mockReturnValue([elem]); + createTracker({ + appId, + crossDomainLinker: () => true, + useExtendedCrossDomainLinker: { reason: (evt) => evt.type }, + }); + elem.click(); + await waitFor(() => { + const clickedLink = queryByText(container, CROSS_DOMAIN_LINK_TEXT)?.getAttribute('href') as string; + const crossDomainLinkParam = getCrossDomainURLParam(clickedLink) as string; + const decodedCrossDomainLinkParam = decodeExtendedCrossDomainLinkParam(crossDomainLinkParam); + expect(decodedCrossDomainLinkParam.length).toBe(CROSS_DOMAIN_LINK_PARAMETERS_LENGTH); + expect(decodedCrossDomainLinkParam).toStrictEqual([ + MOCK_UUID, + String(standardDate.getTime()), + MOCK_UUID, + '', + urlSafeBase64Encode(appId), + '', + urlSafeBase64Encode('click'), + ]); + }); + }); +}); diff --git a/trackers/browser-tracker/src/api.ts b/trackers/browser-tracker/src/api.ts index e8eba06b6..1367ed841 100644 --- a/trackers/browser-tracker/src/api.ts +++ b/trackers/browser-tracker/src/api.ts @@ -43,6 +43,8 @@ import { FlushBufferConfiguration, PageViewEvent, ClearUserDataConfiguration, + ExtendedCrossDomainLinkerOptions, + ExtendedCrossDomainLinkerAttributes, } from '@snowplow/browser-tracker-core'; import { buildSelfDescribingEvent, @@ -87,6 +89,8 @@ export { ContextEvent, ContextFilter, RuleSet, + ExtendedCrossDomainLinkerOptions, + ExtendedCrossDomainLinkerAttributes, }; /** @@ -185,7 +189,7 @@ export function setVisitorCookieTimeout(timeout: number, trackers?: Array