From caca7a75de391a8a78a420226ccb363f67ae02af Mon Sep 17 00:00:00 2001 From: Kiko Ruiz Date: Wed, 18 Sep 2024 13:01:38 +0200 Subject: [PATCH] feat(packages/sui-segment-wrapper): add segment wrapper package --- packages/sui-segment-wrapper/README.md | 251 ++++++ packages/sui-segment-wrapper/package.json | 26 + packages/sui-segment-wrapper/src-umd/index.js | 9 + .../src/adobeRepository.js | 72 ++ .../src/adobeVisitorApi.js | 12 + .../src/checkAnonymousId.js | 10 + packages/sui-segment-wrapper/src/config.js | 24 + packages/sui-segment-wrapper/src/index.js | 39 + .../destination/optimizelySiteAttribute.js | 22 + .../source/defaultContextProperties.js | 15 + .../middlewares/source/optimizelyUserId.js | 20 + .../src/middlewares/source/pageReferrer.js | 80 ++ .../src/middlewares/source/userScreenInfo.js | 11 + .../src/middlewares/source/userTraits.js | 41 + .../sui-segment-wrapper/src/patchAnalytics.js | 32 + .../sui-segment-wrapper/src/segmentWrapper.js | 228 ++++++ packages/sui-segment-wrapper/src/tcf.js | 260 +++++++ .../sui-segment-wrapper/src/universalId.js | 42 + .../sui-segment-wrapper/src/utils/cookies.js | 56 ++ .../src/utils/hashEmail.js | 33 + .../sui-segment-wrapper/src/utils/storage.js | 18 + .../src/xandrRepository.js | 48 ++ .../test/checkAnonymousIdSpec.js | 43 + .../defaultContextPropertiesMiddlewareSpec.js | 46 ++ .../optimizelyMiddlewaresIntegrationSpec.js | 154 ++++ .../optimizelySiteAttributeSpec.js | 47 ++ .../optimizelyUserIdMiddlewareSpec.js | 48 ++ .../middlewares/pageReferrerMiddlewareSpec.js | 42 + .../userScreenInfoMiddlewareSpec.js | 38 + .../test/segmentWrapperSpec.js | 732 ++++++++++++++++++ packages/sui-segment-wrapper/test/stubs.js | 175 +++++ packages/sui-segment-wrapper/test/tcf.js | 39 + .../test/universalIdSpec.js | 73 ++ packages/sui-segment-wrapper/test/utils.js | 42 + .../test/xandrRepositorySpec.js | 62 ++ packages/sui-segment-wrapper/umd/index.js | 9 + 36 files changed, 2899 insertions(+) create mode 100644 packages/sui-segment-wrapper/README.md create mode 100644 packages/sui-segment-wrapper/package.json create mode 100644 packages/sui-segment-wrapper/src-umd/index.js create mode 100644 packages/sui-segment-wrapper/src/adobeRepository.js create mode 100644 packages/sui-segment-wrapper/src/adobeVisitorApi.js create mode 100644 packages/sui-segment-wrapper/src/checkAnonymousId.js create mode 100644 packages/sui-segment-wrapper/src/config.js create mode 100644 packages/sui-segment-wrapper/src/index.js create mode 100644 packages/sui-segment-wrapper/src/middlewares/destination/optimizelySiteAttribute.js create mode 100644 packages/sui-segment-wrapper/src/middlewares/source/defaultContextProperties.js create mode 100644 packages/sui-segment-wrapper/src/middlewares/source/optimizelyUserId.js create mode 100644 packages/sui-segment-wrapper/src/middlewares/source/pageReferrer.js create mode 100644 packages/sui-segment-wrapper/src/middlewares/source/userScreenInfo.js create mode 100644 packages/sui-segment-wrapper/src/middlewares/source/userTraits.js create mode 100644 packages/sui-segment-wrapper/src/patchAnalytics.js create mode 100644 packages/sui-segment-wrapper/src/segmentWrapper.js create mode 100644 packages/sui-segment-wrapper/src/tcf.js create mode 100644 packages/sui-segment-wrapper/src/universalId.js create mode 100644 packages/sui-segment-wrapper/src/utils/cookies.js create mode 100644 packages/sui-segment-wrapper/src/utils/hashEmail.js create mode 100644 packages/sui-segment-wrapper/src/utils/storage.js create mode 100644 packages/sui-segment-wrapper/src/xandrRepository.js create mode 100644 packages/sui-segment-wrapper/test/checkAnonymousIdSpec.js create mode 100644 packages/sui-segment-wrapper/test/middlewares/defaultContextPropertiesMiddlewareSpec.js create mode 100644 packages/sui-segment-wrapper/test/middlewares/optimizelyMiddlewaresIntegrationSpec.js create mode 100644 packages/sui-segment-wrapper/test/middlewares/optimizelySiteAttributeSpec.js create mode 100644 packages/sui-segment-wrapper/test/middlewares/optimizelyUserIdMiddlewareSpec.js create mode 100644 packages/sui-segment-wrapper/test/middlewares/pageReferrerMiddlewareSpec.js create mode 100644 packages/sui-segment-wrapper/test/middlewares/userScreenInfoMiddlewareSpec.js create mode 100644 packages/sui-segment-wrapper/test/segmentWrapperSpec.js create mode 100644 packages/sui-segment-wrapper/test/stubs.js create mode 100644 packages/sui-segment-wrapper/test/tcf.js create mode 100644 packages/sui-segment-wrapper/test/universalIdSpec.js create mode 100644 packages/sui-segment-wrapper/test/utils.js create mode 100644 packages/sui-segment-wrapper/test/xandrRepositorySpec.js create mode 100644 packages/sui-segment-wrapper/umd/index.js diff --git a/packages/sui-segment-wrapper/README.md b/packages/sui-segment-wrapper/README.md new file mode 100644 index 000000000..8096f8bcd --- /dev/null +++ b/packages/sui-segment-wrapper/README.md @@ -0,0 +1,251 @@ +# Segment Wrapper + +This package adds an abstraction layer on top of [segment.com](https://segment.com/)'s [javascript library](https://segment.com/docs/sources/website/analytics.js/) with some useful integrations and fixes: + +**Data Quality 📈** + +- [x] Add `page` method that internally uses `track` but with the correct `referrer` property. +- [x] Send `user.id` and `anonymousId` on every track. +- [x] Send anonymous data to AWS to be able to check data integrity with Adobe. + +**Adobe Marketing Cloud Visitor Id ☁️** + +- [x] Load *Adobe Visitor API* when needed (if flag `importAdobeVisitorId` is set to `true`, otherwise you should load `Visitor API` by your own to get the `mcvid`). +- [x] Fetch `marketingCloudVisitorId` and put in integrations object for every track. +- [x] Monkey patch `track` native Segment method to send `marketingCloudVisitorId` inside `context.integrations`. + +**Consent Management Platform 🐾** + +- [x] Automatic tracking of Consent Management Platform usage. +- [x] Send `gdpr_privacy` on `context` for for all tracking events. Expect values: `unknown`, `accepted` and `declined`. +- [x] Send `gdpr_privacy_advertising` on `context` for for all tracking events. Expect values: `unknown`, `accepted` and `declined`. +- [x] `gdpr_privacy` is accepted when consents [1, 8, 10] are accepted and `gdpr_privacy_advertising` is accepted when consents [3] is accepted. +- [x] Any track is sent if the user has not accepted/rejected any consent yet. + +**Developer Experience 👩‍💻** + +- [x] Always send `platform` property as `web`. +- [x] Allow to configure `window.__mpi.defaultContext` to send these properties on all tracks inside the context object. +- [x] Allow to configure `window.__mpi.defaultProperties` to send these properties on all tracks. + +**Segment Middlewares 🖖** + +- [x] Optimizely Full Stack middleware to use Segment's anonymousId as Optimizely's userId, more info [here](#optimizelys-userid) + +## Usage + +After adding your Segment snippet into your html, you'll need to include this package in order to be able to fire events. + +`analytics` will be an object with the methods described [here](#events) + +### Step 1: Copy the Snippet in your HTML + +```html + +``` + +### Step 2: Use + +#### In your modern and beautiful JavaScript... + +```js +import analytics from '@s-ui/segment-wrapper' +``` + +### In your monolithic JavaScript... + +```js +// First load the UMD module. + + +``` + +### Step 3: Configure mandatory Segment Wrapper attributes: + +The following configuration parameters are required and must be set for the system to function correctly: + - `ADOBE_ORG_ID`: This parameter is the Adobe Organization ID, required for integration with Adobe services. Please make sure that you replace the example value with your actual Adobe Org ID. + - `TRACKING_SERVER`: This specifies the tracking server URL that will be used for sending data and handling tracking requests. + + These parameters need to be defined in the `window._SEGMENT_WRAPPER` object as follows: + +```js + window.__SEGMENT_WRAPPER = { + ADOBE_ORG_ID: '012345678@AdobeOrg', // Mandatory! + TRACKING_SERVER: 'mycompany.test.net', // Mandatory! + } +``` + +Configure both values correctly before running the application to ensure proper tracking and data integration. + +### Step 4: Configure Segment Wrapper (optional) + +You could put a special config in a the `window.__mpi` to change some behaviour of the wrapper. This config MUST somewhere before using the Segment Wrapper. + +- `defaultContext`: _(optional)_ If set, properties will be merged and sent with every `track` and `page` in the **context object**. It's the ideal place to put the `site` and `vertical` info to make sure that static info will be sent along with all the tracking. +- `defaultProperties`: _(optional)_ If set, properties will be merged and sent with every `track` and `page`. +- `getCustomAdobeVisitorId`: _(optional)_ If set, the output of this function will be used as `marketingCloudVisitorId` in Adobe Analytics' integration. It must return a promise. +- `importAdobeVisitorId` _(optional)_ If set and `true`, Adobe Visitor API will be fetched from Segment Wrapper instead of relying on being loaded before from Tealium or other services. Right now, by default, this is `false` but in the next major this configuration will be `true` by default. If `getCustomAdobeVisitorId` is being used this will be ignored. +- `tcfTrackDefaultProperties` _(optional)_ If set, this property will be merged together with the default properties set to send with every tcf track event +- `universalId`: _(optional)_ If set this value will be used for the Visitor API and other services. +- `hashedUserEmail`: _(optional)_ If set and not `universalId` is set this value will be used for the Visitor API and other services. +- `userEmail`: _(optional)_ If set and not `universalId` is available, this value will be hashed with SHA-256 and used for the Visitor API and other services. +- `isUserTraitsEnabled`: _(optional)_ If set context traits will be populated with all user traits. + +Example: + +```js + window.__mpi = { + segmentWrapper: { + universalId: '7ab9ddf3281d5d5458a29e8b3ae2864', + defaultContext: { + site: 'comprocasa', + vertical: 'realestate' + }, + getCustomAdobeVisitorId: () => { + const visitorId = // get your visitorId + return Promise.resolve(visitorId) + }, + tcfTrackDefaultProperties: { + tcfSpecialProp: 'anyvalue' + } + } + } +``` + +### It also provides additional information such as: + +- window.__mpi.isFirstVisit: boolean - true if the user hasn't interacted with the tcf modal yet + +## Events + +### Track - [docs](https://segment.com/docs/spec/track/) + +```js +import analytics from '@s-ui/segment-wrapper' + +analytics.track('CTA Clicked') + +analytics.track('Registered', { + plan: 'Pro Annual', + accountType: 'Facebook' +}) +``` + +### Page + +Internally uses `Track` but changes the `referrer` everytime is called. + +```js +import analytics from '@s-ui/segment-wrapper' + +analytics.page('Home Viewed') + +analytics.page('List Viewed', { + property: 'HOUSE', + transaction: 'SELL' +}) +``` + +### Identify - [docs](https://segment.com/docs/spec/identify/) + +```js +import analytics from '@s-ui/segment-wrapper' + +analytics.identify('97980cfea0067', { + name: 'Peter Gibbons', + email: 'peter@initech.com', + plan: 'premium', + logins: 5 +}) +``` + +### Reset - [docs](https://segment.com/docs/sources/website/analytics.js/#reset-logout) + +```js +import analytics from '@s-ui/segment-wrapper' + +analytics.reset() +``` + +## UniversalID + +*Segment Wrapper* is handling all about the UniversalID, an ID to identify the user across different sites by using a hashed email. If you want, you could subscribe yourself to an event in order to retrieve this info: + +```js +document.addEventListener(USER_DATA_READY_EVENT, e => { + const {universalId} = e.detail +}) +``` + +Also, you could check directly if the `universalId` is already available on the window: + +```js +const {universalId} = window.__mpi.segmentWrapper +``` + +Or use both systems: + +```js +let {universalId, universalIdInitialized} = window.__mpi.segmentWrapper + +if (!universalId && !universalIdInitialized) { + document.addEventListener(USER_DATA_READY_EVENT, e => { + universalId = e.detail.universalId + doSomethingWithUniversalId(universalId) + }) +} + +console.log(universalId) +``` + +### Send xandrId as externalIds + +To not send the ```xandrId``` put this flag as configuration: ```window.__mpi.segmentWrapper.sendXandrId = false``` + +By default, all xandrId will be sent. + + +## Middlewares + +You can find info about segment's middleware [here](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/middleware/) + +### Optimizely's userId + +Will use segment's anonymousId as optimizely's userId + +#### How to + +```js +import {optimizelyUserId} from '@s-ui/segment-wrapper/lib/middlewares/source/optimizelyUserId' + +window.analytics.ready(() => { + window.analytics.addSourceMiddleware(optimizelyUserId) +}) +``` + +### Optimizely's site attribute + +Will add the site property as optimizely attribute + +#### How to + +```js +import {optimizelySiteAttributeMiddleware} from '@s-ui/segment-wrapper/lib/middlewares/destination/optimizelySiteAttribute' + +window.analytics.ready(() => { + window.analytics.addDestinationMiddleware('Optimizely', [ + optimizelySiteAttributeMiddleware + ]) +}) +``` diff --git a/packages/sui-segment-wrapper/package.json b/packages/sui-segment-wrapper/package.json new file mode 100644 index 000000000..3d93a51f6 --- /dev/null +++ b/packages/sui-segment-wrapper/package.json @@ -0,0 +1,26 @@ +{ + "name": "@s-ui/segment-wrapper", + "version": "4.0.0", + "description": "Abstraction layer on top of the Segment library.", + "main": "lib/index.js", + "scripts": { + "lib": "sui-js-compiler", + "prepublishOnly": "npm run umd && npm run lib", + "test:client:watch": "npm run test:client -- --watch", + "test:client": "sui-test browser --src-pattern=src/index.js -H", + "test": "npm run test:client", + "test:umd": "npm run umd && npx servor ./umd", + "umd": "sui-bundler lib src-umd/index.js -o umd/ -p --root" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@s-ui/js": "2", + "tiny-hashes": "1.0.1" + }, + "devDependencies": { + "@s-ui/bundler": "9", + "@s-ui/js-compiler": "1", + "@s-ui/test": "8" + } +} diff --git a/packages/sui-segment-wrapper/src-umd/index.js b/packages/sui-segment-wrapper/src-umd/index.js new file mode 100644 index 000000000..ca97f5cf6 --- /dev/null +++ b/packages/sui-segment-wrapper/src-umd/index.js @@ -0,0 +1,9 @@ +import {getAdobeMCVisitorID, getAdobeVisitorData} from '../src/adobeRepository.js' +import analytics from '../src/index.js' + +const w = window + +w.sui = w.sui || {} +w.sui.analytics = w.sui.analytics || analytics +w.sui.analytics.getAdobeVisitorData = w.sui.analytics.getAdobeVisitorData || getAdobeVisitorData +w.sui.analytics.getAdobeMCVisitorID = w.sui.analytics.getAdobeMCVisitorID || getAdobeMCVisitorID diff --git a/packages/sui-segment-wrapper/src/adobeRepository.js b/packages/sui-segment-wrapper/src/adobeRepository.js new file mode 100644 index 000000000..801df3fe0 --- /dev/null +++ b/packages/sui-segment-wrapper/src/adobeRepository.js @@ -0,0 +1,72 @@ +import {getConfig} from './config.js' + +let mcvid + +const getGlobalConfig = () => { + return { + ADOBE_ORG_ID: window.__SEGMENT_WRAPPER?.ADOBE_ORG_ID, + DEFAULT_DEMDEX_VERSION: window.__SEGMENT_WRAPPER?.DEFAULT_DEMDEX_VERSION ?? '3.3.0', + TIME_BETWEEN_RETRIES: window.__SEGMENT_WRAPPER?.TIME_BETWEEN_RETRIES ?? 15, + TIMES_TO_RETRY: window.__SEGMENT_WRAPPER?.TIMES_TO_RETRY ?? 80, + SERVERS: { + trackingServer: window.__SEGMENT_WRAPPER?.TRACKING_SERVER, + trackingServerSecure: window.__SEGMENT_WRAPPER?.TRACKING_SERVER + } + } +} + +const getDemdex = () => { + const config = getGlobalConfig() + + return window.Visitor && window.Visitor.getInstance(config.ADOBE_ORG_ID, config.SERVERS) +} + +const getMarketingCloudVisitorID = demdex => { + const mcvid = demdex && demdex.getMarketingCloudVisitorID() + return mcvid +} + +const getAdobeVisitorData = () => { + const demdex = getDemdex() || {} + const config = getGlobalConfig() + const {version = config.DEFAULT_DEMDEX_VERSION} = demdex + const {trackingServer} = config.SERVERS + + return Promise.resolve({trackingServer, version}) +} + +export const getAdobeMarketingCloudVisitorIdFromWindow = () => { + if (mcvid) return Promise.resolve(mcvid) + + const config = getGlobalConfig() + + return new Promise(resolve => { + function retry(retries) { + if (retries === 0) return resolve('') + + const demdex = getDemdex() + mcvid = getMarketingCloudVisitorID(demdex) + return mcvid ? resolve(mcvid) : window.setTimeout(() => retry(--retries), config.TIME_BETWEEN_RETRIES) + } + retry(config.TIMES_TO_RETRY) + }) +} + +const importVisitorApiAndGetAdobeMCVisitorID = () => + import('./adobeVisitorApi.js').then(() => { + mcvid = getAdobeMarketingCloudVisitorIdFromWindow() + return mcvid + }) + +const getAdobeMCVisitorID = () => { + const getCustomAdobeVisitorId = getConfig('getCustomAdobeVisitorId') + if (typeof getCustomAdobeVisitorId === 'function') { + return getCustomAdobeVisitorId() + } + + return getConfig('importAdobeVisitorId') === true + ? importVisitorApiAndGetAdobeMCVisitorID() + : getAdobeMarketingCloudVisitorIdFromWindow() +} + +export {getAdobeVisitorData, getAdobeMCVisitorID} diff --git a/packages/sui-segment-wrapper/src/adobeVisitorApi.js b/packages/sui-segment-wrapper/src/adobeVisitorApi.js new file mode 100644 index 000000000..219fd6d31 --- /dev/null +++ b/packages/sui-segment-wrapper/src/adobeVisitorApi.js @@ -0,0 +1,12 @@ +/* eslint-disable */ +/* prettier-ignore */ +/* istanbul ignore file */ + +/** + * @license + * Adobe Visitor API for JavaScript version: 4.6.0 + * Copyright 2020 Adobe, Inc. All Rights Reserved + * More info available at https://marketing.adobe.com/resources/help/en_US/mcvid/ + */ +var e=function(){"use strict";function e(t){"@babel/helpers - typeof";return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function n(){return{callbacks:{},add:function(e,t){this.callbacks[e]=this.callbacks[e]||[];var n=this.callbacks[e].push(t)-1,i=this;return function(){i.callbacks[e].splice(n,1)}},execute:function(e,t){if(this.callbacks[e]){t=void 0===t?[]:t,t=t instanceof Array?t:[t];try{for(;this.callbacks[e].length;){var n=this.callbacks[e].shift();"function"==typeof n?n.apply(null,t):n instanceof Array&&n[1].apply(n[0],t)}delete this.callbacks[e]}catch(e){}}},executeAll:function(e,t){(t||e&&!j.isObjectEmpty(e))&&Object.keys(this.callbacks).forEach(function(t){var n=void 0!==e[t]?e[t]:"";this.execute(t,n)},this)},hasCallbacks:function(){return Boolean(Object.keys(this.callbacks).length)}}}function i(e,t,n){var i=null==e?void 0:e[t];return void 0===i?n:i}function r(e){for(var t=/^\d+$/,n=0,i=e.length;nr)return 1;if(r>i)return-1}return 0}function s(e,t){if(e===t)return 0;var n=e.toString().split("."),i=t.toString().split(".");return r(n.concat(i))?(a(n,i),o(n,i)):NaN}function l(e){return e===Object(e)&&0===Object.keys(e).length}function c(e){return"function"==typeof e||e instanceof Array&&e.length}function u(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){return!0};this.log=_e("log",e,t),this.warn=_e("warn",e,t),this.error=_e("error",e,t)}function d(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.isEnabled,n=e.cookieName,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=i.cookies;return t&&n&&r?{remove:function(){r.remove(n)},get:function(){var e=r.get(n),t={};try{t=JSON.parse(e)}catch(e){t={}}return t},set:function(e,t){t=t||{},r.set(n,JSON.stringify(e),{domain:t.optInCookieDomain||"",cookieLifetime:t.optInStorageExpiry||3419e4,expires:!0})}}:{get:Pe,set:Pe,remove:Pe}}function f(e){this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}function p(){function e(e,t){var n=De(e);return n.length?n.every(function(e){return!!t[e]}):Se(t)}function t(){M(b),O(ce.COMPLETE),_(h.status,h.permissions),m.set(h.permissions,{optInCookieDomain:l,optInStorageExpiry:c}),C.execute(xe)}function n(e){return function(n,i){if(!Ae(n))throw new Error("[OptIn] Invalid category(-ies). Please use the `OptIn.Categories` enum.");return O(ce.CHANGED),Object.assign(b,ye(De(n),e)),i||t(),h}}var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=i.doesOptInApply,a=i.previousPermissions,o=i.preOptInApprovals,s=i.isOptInStorageEnabled,l=i.optInCookieDomain,c=i.optInStorageExpiry,u=i.isIabContext,f=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},p=f.cookies,g=Le(a);Re(g,"Invalid `previousPermissions`!"),Re(o,"Invalid `preOptInApprovals`!");var m=d({isEnabled:!!s,cookieName:"adobeujs-optin"},{cookies:p}),h=this,_=le(h),C=ge(),I=Me(g),v=Me(o),D=m.get(),S={},A=function(e,t){return ke(e)||t&&ke(t)?ce.COMPLETE:ce.PENDING}(I,D),y=function(e,t,n){var i=ye(pe,!r);return r?Object.assign({},i,e,t,n):i}(v,I,D),b=be(y),O=function(e){return A=e},M=function(e){return y=e};h.deny=n(!1),h.approve=n(!0),h.denyAll=h.deny.bind(h,pe),h.approveAll=h.approve.bind(h,pe),h.isApproved=function(t){return e(t,h.permissions)},h.isPreApproved=function(t){return e(t,v)},h.fetchPermissions=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t?h.on(ce.COMPLETE,e):Pe;return!r||r&&h.isComplete||!!o?e(h.permissions):t||C.add(xe,function(){return e(h.permissions)}),n},h.complete=function(){h.status===ce.CHANGED&&t()},h.registerPlugin=function(e){if(!e||!e.name||"function"!=typeof e.onRegister)throw new Error(je);S[e.name]||(S[e.name]=e,e.onRegister.call(e,h))},h.execute=Ne(S),Object.defineProperties(h,{permissions:{get:function(){return y}},status:{get:function(){return A}},Categories:{get:function(){return ue}},doesOptInApply:{get:function(){return!!r}},isPending:{get:function(){return h.status===ce.PENDING}},isComplete:{get:function(){return h.status===ce.COMPLETE}},__plugins:{get:function(){return Object.keys(S)}},isIabContext:{get:function(){return u}}})}function g(e,t){function n(){r=null,e.call(e,new f("The call took longer than you wanted!"))}function i(){r&&(clearTimeout(r),e.apply(e,arguments))}if(void 0===t)return e;var r=setTimeout(n,t);return i}function m(){if(window.__cmp)return window.__cmp;var e=window;if(e===window.top)return void Ie.error("__cmp not found");for(var t;!t;){e=e.parent;try{e.frames.__cmpLocator&&(t=e)}catch(e){}if(e===window.top)break}if(!t)return void Ie.error("__cmp not found");var n={};return window.__cmp=function(e,i,r){var a=Math.random()+"",o={__cmpCall:{command:e,parameter:i,callId:a}};n[a]=r,t.postMessage(o,"*")},window.addEventListener("message",function(e){var t=e.data;if("string"==typeof t)try{t=JSON.parse(e.data)}catch(e){}if(t.__cmpReturn){var i=t.__cmpReturn;n[i.callId]&&(n[i.callId](i.returnValue,i.success),delete n[i.callId])}},!1),window.__cmp}function h(){var e=this;e.name="iabPlugin",e.version="0.0.1";var t=ge(),n={allConsentData:null},i=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return n[e]=t};e.fetchConsentData=function(e){var t=e.callback,n=e.timeout,i=g(t,n);r({callback:i})},e.isApproved=function(e){var t=e.callback,i=e.category,a=e.timeout;if(n.allConsentData)return t(null,s(i,n.allConsentData.vendorConsents,n.allConsentData.purposeConsents));var o=g(function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.vendorConsents,a=n.purposeConsents;t(e,s(i,r,a))},a);r({category:i,callback:o})},e.onRegister=function(t){var n=Object.keys(de),i=function(e){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=i.purposeConsents,a=i.gdprApplies,o=i.vendorConsents;!e&&a&&o&&r&&(n.forEach(function(e){var n=s(e,o,r);t[n?"approve":"deny"](e,!0)}),t.complete())};e.fetchConsentData({callback:i})};var r=function(e){var r=e.callback;if(n.allConsentData)return r(null,n.allConsentData);t.add("FETCH_CONSENT_DATA",r);var s={};o(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=e.purposeConsents,o=e.gdprApplies,l=e.vendorConsents;(arguments.length>1?arguments[1]:void 0)&&(s={purposeConsents:r,gdprApplies:o,vendorConsents:l},i("allConsentData",s)),a(function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};(arguments.length>1?arguments[1]:void 0)&&(s.consentString=e.consentData,i("allConsentData",s)),t.execute("FETCH_CONSENT_DATA",[null,n.allConsentData])})})},a=function(e){var t=m();t&&t("getConsentData",null,e)},o=function(e){var t=Fe(de),n=m();n&&n("getVendorConsents",t,e)},s=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=!!t[de[e]];return i&&function(){return fe[e].every(function(e){return n[e]})}()}}var _="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};Object.assign=Object.assign||function(e){for(var t,n,i=1;i=0||t.indexOf("Trident/")>=0&&t.indexOf("Windows NT 6")>=0};n.getIeVersion=function(){return document.documentMode?document.documentMode:i()?7:null},n.encodeAndBuildRequest=function(e,t){return e.map(encodeURIComponent).join(t)},n.isObject=function(t){return null!==t&&"object"===e(t)&&!1===Array.isArray(t)},n.defineGlobalNamespace=function(){return window.adobe=n.isObject(window.adobe)?window.adobe:{},window.adobe},n.pluck=function(e,t){return t.reduce(function(t,n){return e[n]&&(t[n]=e[n]),t},Object.create(null))},n.parseOptOut=function(e,t,n){t||(t=n,e.d_optout&&e.d_optout instanceof Array&&(t=e.d_optout.join(",")));var i=parseInt(e.d_ottl,10);return isNaN(i)&&(i=7200),{optOut:t,d_ottl:i}},n.normalizeBoolean=function(e){var t=e;return"true"===e?t=!0:"false"===e&&(t=!1),t}}),V=(j.isObjectEmpty,j.isValueEmpty,j.getIeVersion,j.encodeAndBuildRequest,j.isObject,j.defineGlobalNamespace,j.pluck,j.parseOptOut,j.normalizeBoolean,n),U=E.MESSAGES,H={0:"prefix",1:"orgID",2:"state"},B=function(e,t){this.parse=function(e){try{var t={};return e.data.split("|").forEach(function(e,n){if(void 0!==e){t[H[n]]=2!==n?e:JSON.parse(e)}}),t}catch(e){}},this.isInvalid=function(n){var i=this.parse(n);if(!i||Object.keys(i).length<2)return!0;var r=e!==i.orgID,a=!t||n.origin!==t,o=-1===Object.keys(U).indexOf(i.prefix);return r||a||o},this.send=function(n,i,r){var a=i+"|"+e;r&&r===Object(r)&&(a+="|"+JSON.stringify(r));try{n.postMessage(a,t)}catch(e){}}},G=E.MESSAGES,Y=function(e,t,n,i){function r(e){Object.assign(p,e)}function a(e){Object.assign(p.state,e),Object.assign(p.state.ALLFIELDS,e),p.callbackRegistry.executeAll(p.state)}function o(e){if(!h.isInvalid(e)){m=!1;var t=h.parse(e);p.setStateAndPublish(t.state)}}function s(e){!m&&g&&(m=!0,h.send(i,e))}function l(){r(new P(n._generateID)),p.getMarketingCloudVisitorID(),p.callbackRegistry.executeAll(p.state,!0),_.removeEventListener("message",c)}function c(e){if(!h.isInvalid(e)){var t=h.parse(e);m=!1,_.clearTimeout(p._handshakeTimeout),_.removeEventListener("message",c),r(new F(p)),_.addEventListener("message",o),p.setStateAndPublish(t.state),p.callbackRegistry.hasCallbacks()&&s(G.GETSTATE)}}function u(){g&&postMessage?(_.addEventListener("message",c),s(G.HANDSHAKE),p._handshakeTimeout=setTimeout(l,250)):l()}function d(){_.s_c_in||(_.s_c_il=[],_.s_c_in=0),p._c="Visitor",p._il=_.s_c_il,p._in=_.s_c_in,p._il[p._in]=p,_.s_c_in++}function f(){function e(e){0!==e.indexOf("_")&&"function"==typeof n[e]&&(p[e]=function(){})}Object.keys(n).forEach(e),p.getSupplementalDataID=n.getSupplementalDataID,p.isAllowed=function(){return!0}}var p=this,g=t.whitelistParentDomain;p.state={ALLFIELDS:{}},p.version=n.version,p.marketingCloudOrgID=e,p.cookieDomain=n.cookieDomain||"",p._instanceType="child";var m=!1,h=new B(e,g);p.callbackRegistry=V(),p.init=function(){d(),f(),r(new x(p)),u()},p.findField=function(e,t){if(void 0!==p.state[e])return t(p.state[e]),p.state[e]},p.messageParent=s,p.setStateAndPublish=a},q=E.MESSAGES,X=E.ALL_APIS,W=E.ASYNC_API_MAP,J=E.FIELDGROUP_TO_FIELD,K=function(e,t){function n(){var t={};return Object.keys(X).forEach(function(n){var i=X[n],r=e[i]();j.isValueEmpty(r)||(t[n]=r)}),t}function i(){var t=[];return e._loading&&Object.keys(e._loading).forEach(function(n){if(e._loading[n]){var i=J[n];t.push(i)}}),t.length?t:null}function r(t){return function n(r){var a=i();if(a){var o=W[a[0]];e[o](n,!0)}else t()}}function a(e,i){var r=n();t.send(e,i,r)}function o(e){l(e),a(e,q.HANDSHAKE)}function s(e){r(function(){a(e,q.PARENTSTATE)})()}function l(n){function i(i){r.call(e,i),t.send(n,q.PARENTSTATE,{CUSTOMERIDS:e.getCustomerIDs()})}var r=e.setCustomerIDs;e.setCustomerIDs=i}return function(e){if(!t.isInvalid(e)){(t.parse(e).prefix===q.HANDSHAKE?o:s)(e.source)}}},z=function(e,t){function n(e){return function(n){i[e]=n,r++,r===a&&t(i)}}var i={},r=0,a=Object.keys(e).length;Object.keys(e).forEach(function(t){var i=e[t];if(i.fn){var r=i.args||[];r.unshift(n(t)),i.fn.apply(i.context||null,r)}})},Q={get:function(e){e=encodeURIComponent(e);var t=(";"+document.cookie).split(" ").join(";"),n=t.indexOf(";"+e+"="),i=n<0?n:t.indexOf(";",n+1);return n<0?"":decodeURIComponent(t.substring(n+2+e.length,i<0?t.length:i))},set:function(e,t,n){var r=i(n,"cookieLifetime"),a=i(n,"expires"),o=i(n,"domain"),s=i(n,"secure"),l=s?"Secure":"";if(a&&"SESSION"!==r&&"NONE"!==r){var c=""!==t?parseInt(r||0,10):-60;if(c)a=new Date,a.setTime(a.getTime()+1e3*c);else if(1===a){a=new Date;var u=a.getYear();a.setYear(u+2+(u<1900?1900:0))}}else a=0;return e&&"NONE"!==r?(document.cookie=encodeURIComponent(e)+"="+encodeURIComponent(t)+"; path=/;"+(a?" expires="+a.toGMTString()+";":"")+(o?" domain="+o+";":"")+l,this.get(e)===t):0},remove:function(e,t){var n=i(t,"domain");n=n?" domain="+n+";":"",document.cookie=encodeURIComponent(e)+"=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"+n}},$=function(e){var t;!e&&_.location&&(e=_.location.hostname),t=e;var n,i=t.split(".");for(n=i.length-2;n>=0;n--)if(t=i.slice(n).join("."),Q.set("test","cookie",{domain:t}))return Q.remove("test",{domain:t}),t;return""},Z={compare:s,isLessThan:function(e,t){return s(e,t)<0},areVersionsDifferent:function(e,t){return 0!==s(e,t)},isGreaterThan:function(e,t){return s(e,t)>0},isEqual:function(e,t){return 0===s(e,t)}},ee=!!_.postMessage,te={postMessage:function(e,t,n){var i=1;t&&(ee?n.postMessage(e,t.replace(/([^:]+:\/\/[^\/]+).*/,"$1")):t&&(n.location=t.replace(/#.*$/,"")+"#"+ +new Date+i+++"&"+e))},receiveMessage:function(e,t){var n;try{ee&&(e&&(n=function(n){if("string"==typeof t&&n.origin!==t||"[object Function]"===Object.prototype.toString.call(t)&&!1===t(n.origin))return!1;e(n)}),_.addEventListener?_[e?"addEventListener":"removeEventListener"]("message",n):_[e?"attachEvent":"detachEvent"]("onmessage",n))}catch(e){}}},ne=function(e){var t,n,i="0123456789",r="",a="",o=8,s=10,l=10;if(1==e){for(i+="ABCDEF",t=0;16>t;t++)n=Math.floor(Math.random()*o),r+=i.substring(n,n+1),n=Math.floor(Math.random()*o),a+=i.substring(n,n+1),o=16;return r+"-"+a}for(t=0;19>t;t++)n=Math.floor(Math.random()*s),r+=i.substring(n,n+1),0===t&&9==n?s=3:(1==t||2==t)&&10!=s&&2>n?s=10:2n?l=10:20&&(t=!1)),{corsType:e,corsCookiesEnabled:t}}(),getCORSInstance:function(){return"none"===this.corsMetadata.corsType?null:new _[this.corsMetadata.corsType]},fireCORS:function(t,n,i){function r(e){var n;try{if((n=JSON.parse(e))!==Object(n))return void a.handleCORSError(t,null,"Response is not JSON")}catch(e){return void a.handleCORSError(t,e,"Error parsing response as JSON")}try{for(var i=t.callback,r=_,o=0;o=a&&(e.splice(r,1),r--);return{dataPresent:o,dataValid:s}},manageSyncsSize:function(e){if(e.join("*").length>this.MAX_SYNCS_LENGTH)for(e.sort(function(e,t){return parseInt(e.split("-")[1],10)-parseInt(t.split("-")[1],10)});e.join("*").length>this.MAX_SYNCS_LENGTH;)e.shift()},fireSync:function(t,n,i,r,a,o){var s=this;if(t){if("img"===n.tag){var l,c,u,d,f=n.url,p=e.loadSSL?"https:":"http:";for(l=0,c=f.length;lre.DAYS_BETWEEN_SYNC_ID_CALLS},attachIframeASAP:function(){function e(){t.startedAttachingIframe||(n.body?t.attachIframe():setTimeout(e,30))}var t=this;e()}}},oe={audienceManagerServer:{},audienceManagerServerSecure:{},cookieDomain:{},cookieLifetime:{},cookieName:{},doesOptInApply:{},disableThirdPartyCalls:{},discardTrackingServerECID:{},idSyncAfterIDCallResult:{},idSyncAttachIframeOnWindowLoad:{},idSyncContainerID:{},idSyncDisable3rdPartySyncing:{},disableThirdPartyCookies:{},idSyncDisableSyncs:{},disableIdSyncs:{},idSyncIDCallResult:{},idSyncSSLUseAkamai:{},isCoopSafe:{},isIabContext:{},isOptInStorageEnabled:{},loadSSL:{},loadTimeout:{},marketingCloudServer:{},marketingCloudServerSecure:{},optInCookieDomain:{},optInStorageExpiry:{},overwriteCrossDomainMCIDAndAID:{},preOptInApprovals:{},previousPermissions:{},resetBeforeVersion:{},sdidParamExpiry:{},serverState:{},sessionCookieName:{},secureCookie:{},takeTimeoutMetrics:{},trackingServer:{},trackingServerSecure:{},whitelistIframeDomains:{},whitelistParentDomain:{}},se={getConfigNames:function(){return Object.keys(oe)},getConfigs:function(){return oe},normalizeConfig:function(e){return"function"!=typeof e?e:e()}},le=function(e){var t={};return e.on=function(e,n,i){if(!n||"function"!=typeof n)throw new Error("[ON] Callback should be a function.");t.hasOwnProperty(e)||(t[e]=[]);var r=t[e].push({callback:n,context:i})-1;return function(){t[e].splice(r,1),t[e].length||delete t[e]}},e.off=function(e,n){t.hasOwnProperty(e)&&(t[e]=t[e].filter(function(e){if(e.callback!==n)return e}))},e.publish=function(e){if(t.hasOwnProperty(e)){var n=[].slice.call(arguments,1);t[e].slice(0).forEach(function(e){e.callback.apply(e.context,n)})}},e.publish},ce={PENDING:"pending",CHANGED:"changed",COMPLETE:"complete"},ue={AAM:"aam",ADCLOUD:"adcloud",ANALYTICS:"aa",CAMPAIGN:"campaign",ECID:"ecid",LIVEFYRE:"livefyre",TARGET:"target",MEDIA_ANALYTICS:"mediaaa"},de=(C={},t(C,ue.AAM,565),t(C,ue.ECID,565),C),fe=(I={},t(I,ue.AAM,[1,2,5]),t(I,ue.ECID,[1,2,5]),I),pe=function(e){return Object.keys(e).map(function(t){return e[t]})}(ue),ge=function(){var e={};return e.callbacks=Object.create(null),e.add=function(t,n){if(!c(n))throw new Error("[callbackRegistryFactory] Make sure callback is a function or an array of functions.");e.callbacks[t]=e.callbacks[t]||[];var i=e.callbacks[t].push(n)-1;return function(){e.callbacks[t].splice(i,1)}},e.execute=function(t,n){if(e.callbacks[t]){n=void 0===n?[]:n,n=n instanceof Array?n:[n];try{for(;e.callbacks[t].length;){var i=e.callbacks[t].shift();"function"==typeof i?i.apply(null,n):i instanceof Array&&i[1].apply(i[0],n)}delete e.callbacks[t]}catch(e){}}},e.executeAll=function(t,n){(n||t&&!l(t))&&Object.keys(e.callbacks).forEach(function(n){var i=void 0!==t[n]?t[n]:"";e.execute(n,i)},e)},e.hasCallbacks=function(){return Boolean(Object.keys(e.callbacks).length)},e},me=function(){},he=function(e){var t=window,n=t.console;return!!n&&"function"==typeof n[e]},_e=function(e,t,n){return n()?function(){if(he(e)){for(var n=arguments.length,i=new Array(n),r=0;r-1})},ye=function(e,t){return e.reduce(function(e,n){return e[n]=t,e},{})},be=function(e){return JSON.parse(JSON.stringify(e))},Oe=function(e){return"[object Array]"===Object.prototype.toString.call(e)&&!e.length},Me=function(e){if(Te(e))return e;try{return JSON.parse(e)}catch(e){return{}}},ke=function(e){return void 0===e||(Te(e)?Ae(Object.keys(e)):Ee(e))},Ee=function(e){try{var t=JSON.parse(e);return!!e&&ve(e,"string")&&Ae(Object.keys(t))}catch(e){return!1}},Te=function(e){return null!==e&&ve(e,"object")&&!1===Array.isArray(e)},Pe=function(){},Le=function(e){return ve(e,"function")?e():e},Re=function(e,t){ke(e)||Ie.error("".concat(t))},we=function(e){return Object.keys(e).map(function(t){return e[t]})},Fe=function(e){return we(e).filter(function(e,t,n){return n.indexOf(e)===t})},Ne=function(e){return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.command,i=t.params,r=void 0===i?{}:i,a=t.callback,o=void 0===a?Pe:a;if(!n||-1===n.indexOf("."))throw new Error("[OptIn.execute] Please provide a valid command.");try{var s=n.split("."),l=e[s[0]],c=s[1];if(!l||"function"!=typeof l[c])throw new Error("Make sure the plugin and API name exist.");var u=Object.assign(r,{callback:o});l[c].call(l,u)}catch(e){Ie.error("[execute] Something went wrong: "+e.message)}}};f.prototype=Object.create(Error.prototype),f.prototype.constructor=f;var xe="fetchPermissions",je="[OptIn#registerPlugin] Plugin is invalid.";p.Categories=ue,p.TimeoutError=f;var Ve=Object.freeze({OptIn:p,IabPlugin:h}),Ue=function(e,t){e.publishDestinations=function(n){var i=arguments[1],r=arguments[2];try{r="function"==typeof r?r:n.callback}catch(e){r=function(){}}var a=t;if(!a.readyToAttachIframePreliminary())return void r({error:"The destination publishing iframe is disabled in the Visitor library."});if("string"==typeof n){if(!n.length)return void r({error:"subdomain is not a populated string."});if(!(i instanceof Array&&i.length))return void r({error:"messages is not a populated array."});var o=!1 +;if(i.forEach(function(e){"string"==typeof e&&e.length&&(a.addMessage(e),o=!0)}),!o)return void r({error:"None of the messages are populated strings."})}else{if(!j.isObject(n))return void r({error:"Invalid parameters passed."});var s=n;if("string"!=typeof(n=s.subdomain)||!n.length)return void r({error:"config.subdomain is not a populated string."});var l=s.urlDestinations;if(!(l instanceof Array&&l.length))return void r({error:"config.urlDestinations is not a populated array."});var c=[];l.forEach(function(e){j.isObject(e)&&(e.hideReferrer?e.message&&a.addMessage(e.message):c.push(e))});!function e(){c.length&&setTimeout(function(){var t=new Image,n=c.shift();t.src=n.url,a.onPageDestinationsFired.push(n),e()},100)}()}a.iframe?(r({message:"The destination publishing iframe is already attached and loaded."}),a.requestToProcess()):!e.subdomain&&e._getField("MCMID")?(a.subdomain=n,a.doAttachIframe=!0,a.url=a.getUrl(),a.readyToAttachIframe()?(a.iframeLoadedCallbacks.push(function(e){r({message:"Attempted to attach and load the destination publishing iframe through this API call. Result: "+(e.message||"no result")})}),a.attachIframe()):r({error:"Encountered a problem in attempting to attach and load the destination publishing iframe through this API call."})):a.iframeLoadedCallbacks.push(function(e){r({message:"Attempted to attach and load the destination publishing iframe through normal Visitor API processing. Result: "+(e.message||"no result")})})}},He=function e(t){function n(e,t){return e>>>t|e<<32-t}for(var i,r,a=Math.pow,o=a(2,32),s="",l=[],c=8*t.length,u=e.h=e.h||[],d=e.k=e.k||[],f=d.length,p={},g=2;f<64;g++)if(!p[g]){for(i=0;i<313;i+=g)p[i]=g;u[f]=a(g,.5)*o|0,d[f++]=a(g,1/3)*o|0}for(t+="€";t.length%64-56;)t+="\0";for(i=0;i>8)return;l[i>>2]|=r<<(3-i)%4*8}for(l[l.length]=c/o|0,l[l.length]=c,r=0;r>>3)+m[i-7]+(n(C,17)^n(C,19)^C>>>10)|0);u=[D+((n(I,2)^n(I,13)^n(I,22))+(I&u[1]^I&u[2]^u[1]&u[2]))|0].concat(u),u[4]=u[4]+D|0}for(i=0;i<8;i++)u[i]=u[i]+h[i]|0}for(i=0;i<8;i++)for(r=3;r+1;r--){var S=u[i]>>8*r&255;s+=(S<16?0:"")+S.toString(16)}return s},Be=function(e,t){return"SHA-256"!==t&&"SHA256"!==t&&"sha256"!==t&&"sha-256"!==t||(e=He(e)),e},Ge=function(e){return String(e).trim().toLowerCase()},Ye=Ve.OptIn;j.defineGlobalNamespace(),window.adobe.OptInCategories=Ye.Categories;var qe=function(t,n,i){function r(e){var t=e;return function(e){var n=e||D.location.href;try{var i=m._extractParamFromUri(n,t);if(i)return N.parsePipeDelimetedKeyValues(i)}catch(e){}}}function a(e){function t(e,t,n){e&&e.match(re.VALID_VISITOR_ID_REGEX)&&(n===y&&(v=!0),t(e))}t(e[y],m.setMarketingCloudVisitorID,y),m._setFieldExpire(T,-1),t(e[M],m.setAnalyticsVisitorID)}function o(e){e=e||{},m._supplementalDataIDCurrent=e.supplementalDataIDCurrent||"",m._supplementalDataIDCurrentConsumed=e.supplementalDataIDCurrentConsumed||{},m._supplementalDataIDLast=e.supplementalDataIDLast||"",m._supplementalDataIDLastConsumed=e.supplementalDataIDLastConsumed||{}}function s(e){function t(e,t,n){return n=n?n+="|":n,n+=e+"="+encodeURIComponent(t)}function n(e,n){var i=n[0],r=n[1];return null!=r&&r!==P&&(e=t(i,r,e)),e}var i=e.reduce(n,"");return function(e){var t=N.getTimestampInSeconds();return e=e?e+="|":e,e+="TS="+t}(i)}function l(e){var t=e.minutesToLive,n="";return(m.idSyncDisableSyncs||m.disableIdSyncs)&&(n=n||"Error: id syncs have been disabled"),"string"==typeof e.dpid&&e.dpid.length||(n=n||"Error: config.dpid is empty"),"string"==typeof e.url&&e.url.length||(n=n||"Error: config.url is empty"),void 0===t?t=20160:(t=parseInt(t,10),(isNaN(t)||t<=0)&&(n=n||"Error: config.minutesToLive needs to be a positive number")),{error:n,ttl:t}}function c(){return!!m.configs.doesOptInApply&&!(h.optIn.isComplete&&u())}function u(){return m.configs.doesOptInApply&&m.configs.isIabContext?h.optIn.isApproved(h.optIn.Categories.ECID)&&I:h.optIn.isApproved(h.optIn.Categories.ECID)}function d(){[["getMarketingCloudVisitorID"],["setCustomerIDs",void 0],["getAnalyticsVisitorID"],["getAudienceManagerLocationHint"],["getLocationHint"],["getAudienceManagerBlob"]].forEach(function(e){var t=e[0],n=2===e.length?e[1]:"",i=m[t];m[t]=function(e){return u()&&m.isAllowed()?i.apply(m,arguments):("function"==typeof e&&m._callCallback(e,[n]),n)}})}function f(e,t){if(I=!0,e)throw new Error("[IAB plugin] : "+e);t.gdprApplies&&(C=t.consentString),m.init(),g()}function p(){h.optIn.isComplete&&(h.optIn.isApproved(h.optIn.Categories.ECID)?m.configs.isIabContext?h.optIn.execute({command:"iabPlugin.fetchConsentData",callback:f}):(m.init(),g()):(d(),g()))}function g(){h.optIn.off("complete",p)}if(!i||i.split("").reverse().join("")!==t)throw new Error("Please use `Visitor.getInstance` to instantiate Visitor.");var m=this,h=window.adobe,C="",I=!1,v=!1;m.version="4.6.0";var D=_,S=D.Visitor;S.version=m.version,S.AuthState=E.AUTH_STATE,S.OptOut=E.OPT_OUT,D.s_c_in||(D.s_c_il=[],D.s_c_in=0),m._c="Visitor",m._il=D.s_c_il,m._in=D.s_c_in,m._il[m._in]=m,D.s_c_in++,m._instanceType="regular",m._log={requests:[]},m.marketingCloudOrgID=t,m.cookieName="AMCV_"+t,m.sessionCookieName="AMCVS_"+t,m.cookieDomain=$(),m.loadSSL=!0,m.loadTimeout=3e4,m.CORSErrors=[],m.marketingCloudServer=m.audienceManagerServer="dpm.demdex.net",m.sdidParamExpiry=30;var A=null,y="MCMID",b="MCIDTS",O="A",M="MCAID",k="AAM",T="MCAAMB",P="NONE",L=function(e){return!Object.prototype[e]},R=ie(m);m.FIELDS=E.FIELDS,m.cookieRead=function(e){return Q.get(e)},m.cookieWrite=function(e,t,n){var i=m.cookieLifetime?(""+m.cookieLifetime).toUpperCase():"",r=!1;return m.configs&&m.configs.secureCookie&&"https:"===location.protocol&&(r=!0),Q.set(e,""+t,{expires:n,domain:m.cookieDomain,cookieLifetime:i,secure:r})},m.resetState=function(e){e?m._mergeServerState(e):o()},m._isAllowedDone=!1,m._isAllowedFlag=!1,m.isAllowed=function(){return m._isAllowedDone||(m._isAllowedDone=!0,(m.cookieRead(m.cookieName)||m.cookieWrite(m.cookieName,"T",1))&&(m._isAllowedFlag=!0)),"T"===m.cookieRead(m.cookieName)&&m._helpers.removeCookie(m.cookieName),m._isAllowedFlag},m.setMarketingCloudVisitorID=function(e){m._setMarketingCloudFields(e)},m._use1stPartyMarketingCloudServer=!1,m.getMarketingCloudVisitorID=function(e,t){m.marketingCloudServer&&m.marketingCloudServer.indexOf(".demdex.net")<0&&(m._use1stPartyMarketingCloudServer=!0);var n=m._getAudienceManagerURLData("_setMarketingCloudFields"),i=n.url;return m._getRemoteField(y,i,e,t,n)};var w=function(e,t){var n={};m.getMarketingCloudVisitorID(function(){t.forEach(function(e){n[e]=m._getField(e,!0)}),-1!==t.indexOf("MCOPTOUT")?m.isOptedOut(function(t){n.MCOPTOUT=t,e(n)},null,!0):e(n)},!0)};m.getVisitorValues=function(e,t){var n={MCMID:{fn:m.getMarketingCloudVisitorID,args:[!0],context:m},MCOPTOUT:{fn:m.isOptedOut,args:[void 0,!0],context:m},MCAID:{fn:m.getAnalyticsVisitorID,args:[!0],context:m},MCAAMLH:{fn:m.getAudienceManagerLocationHint,args:[!0],context:m},MCAAMB:{fn:m.getAudienceManagerBlob,args:[!0],context:m}},i=t&&t.length?j.pluck(n,t):n;t&&-1===t.indexOf("MCAID")?w(e,t):z(i,e)},m._currentCustomerIDs={},m._customerIDsHashChanged=!1,m._newCustomerIDsHash="",m.setCustomerIDs=function(t,n){function i(){m._customerIDsHashChanged=!1}if(!m.isOptedOut()&&t){if(!j.isObject(t)||j.isObjectEmpty(t))return!1;m._readVisitor();var r,a,o;for(r in t)if(L(r)&&(a=t[r],n=a.hasOwnProperty("hashType")?a.hashType:n,a))if("object"===e(a)){var s={};if(a.id){if(n){if(!(o=Be(Ge(a.id),n)))return;a.id=o,s.hashType=n}s.id=a.id}void 0!=a.authState&&(s.authState=a.authState),m._currentCustomerIDs[r]=s}else if(n){if(!(o=Be(Ge(a),n)))return;m._currentCustomerIDs[r]={id:o,hashType:n}}else m._currentCustomerIDs[r]={id:a};var l=m.getCustomerIDs(),c=m._getField("MCCIDH"),u="";c||(c=0);for(r in l)L(r)&&(a=l[r],u+=(u?"|":"")+r+"|"+(a.id?a.id:"")+(a.authState?a.authState:""));m._newCustomerIDsHash=String(m._hash(u)),m._newCustomerIDsHash!==c&&(m._customerIDsHashChanged=!0,m._mapCustomerIDs(i))}},m.getCustomerIDs=function(){m._readVisitor();var e,t,n={};for(e in m._currentCustomerIDs)L(e)&&(t=m._currentCustomerIDs[e],t.id&&(n[e]||(n[e]={}),n[e].id=t.id,void 0!=t.authState?n[e].authState=t.authState:n[e].authState=S.AuthState.UNKNOWN,t.hashType&&(n[e].hashType=t.hashType)));return n},m.setAnalyticsVisitorID=function(e){m._setAnalyticsFields(e)},m.getAnalyticsVisitorID=function(e,t,n){if(!N.isTrackingServerPopulated()&&!n)return m._callCallback(e,[""]),"";var i="";if(n||(i=m.getMarketingCloudVisitorID(function(t){m.getAnalyticsVisitorID(e,!0)})),i||n){var r=n?m.marketingCloudServer:m.trackingServer,a="";m.loadSSL&&(n?m.marketingCloudServerSecure&&(r=m.marketingCloudServerSecure):m.trackingServerSecure&&(r=m.trackingServerSecure));var o={};if(r){var s="http"+(m.loadSSL?"s":"")+"://"+r+"/id",l="d_visid_ver="+m.version+"&mcorgid="+encodeURIComponent(m.marketingCloudOrgID)+(i?"&mid="+encodeURIComponent(i):"")+(m.idSyncDisable3rdPartySyncing||m.disableThirdPartyCookies?"&d_coppa=true":""),c=["s_c_il",m._in,"_set"+(n?"MarketingCloud":"Analytics")+"Fields"];a=s+"?"+l+"&callback=s_c_il%5B"+m._in+"%5D._set"+(n?"MarketingCloud":"Analytics")+"Fields",o.corsUrl=s+"?"+l,o.callback=c}return o.url=a,m._getRemoteField(n?y:M,a,e,t,o)}return""},m.getAudienceManagerLocationHint=function(e,t){if(m.getMarketingCloudVisitorID(function(t){m.getAudienceManagerLocationHint(e,!0)})){var n=m._getField(M);if(!n&&N.isTrackingServerPopulated()&&(n=m.getAnalyticsVisitorID(function(t){m.getAudienceManagerLocationHint(e,!0)})),n||!N.isTrackingServerPopulated()){var i=m._getAudienceManagerURLData(),r=i.url;return m._getRemoteField("MCAAMLH",r,e,t,i)}}return""},m.getLocationHint=m.getAudienceManagerLocationHint,m.getAudienceManagerBlob=function(e,t){if(m.getMarketingCloudVisitorID(function(t){m.getAudienceManagerBlob(e,!0)})){var n=m._getField(M);if(!n&&N.isTrackingServerPopulated()&&(n=m.getAnalyticsVisitorID(function(t){m.getAudienceManagerBlob(e,!0)})),n||!N.isTrackingServerPopulated()){var i=m._getAudienceManagerURLData(),r=i.url;return m._customerIDsHashChanged&&m._setFieldExpire(T,-1),m._getRemoteField(T,r,e,t,i)}}return""},m._supplementalDataIDCurrent="",m._supplementalDataIDCurrentConsumed={},m._supplementalDataIDLast="",m._supplementalDataIDLastConsumed={},m.getSupplementalDataID=function(e,t){m._supplementalDataIDCurrent||t||(m._supplementalDataIDCurrent=m._generateID(1));var n=m._supplementalDataIDCurrent;return m._supplementalDataIDLast&&!m._supplementalDataIDLastConsumed[e]?(n=m._supplementalDataIDLast,m._supplementalDataIDLastConsumed[e]=!0):n&&(m._supplementalDataIDCurrentConsumed[e]&&(m._supplementalDataIDLast=m._supplementalDataIDCurrent,m._supplementalDataIDLastConsumed=m._supplementalDataIDCurrentConsumed,m._supplementalDataIDCurrent=n=t?"":m._generateID(1),m._supplementalDataIDCurrentConsumed={}),n&&(m._supplementalDataIDCurrentConsumed[e]=!0)),n};var F=!1;m._liberatedOptOut=null,m.getOptOut=function(e,t){var n=m._getAudienceManagerURLData("_setMarketingCloudFields"),i=n.url;if(u())return m._getRemoteField("MCOPTOUT",i,e,t,n);if(m._registerCallback("liberatedOptOut",e),null!==m._liberatedOptOut)return m._callAllCallbacks("liberatedOptOut",[m._liberatedOptOut]),F=!1,m._liberatedOptOut;if(F)return null;F=!0;var r="liberatedGetOptOut";return n.corsUrl=n.corsUrl.replace(/dpm\.demdex\.net\/id\?/,"dpm.demdex.net/optOutStatus?"),n.callback=[r],_[r]=function(e){if(e===Object(e)){var t,n,i=j.parseOptOut(e,t,P);t=i.optOut,n=1e3*i.d_ottl,m._liberatedOptOut=t,setTimeout(function(){m._liberatedOptOut=null},n)}m._callAllCallbacks("liberatedOptOut",[t]),F=!1},R.fireCORS(n),null},m.isOptedOut=function(e,t,n){t||(t=S.OptOut.GLOBAL);var i=m.getOptOut(function(n){var i=n===S.OptOut.GLOBAL||n.indexOf(t)>=0;m._callCallback(e,[i])},n);return i?i===S.OptOut.GLOBAL||i.indexOf(t)>=0:null},m._fields=null,m._fieldsExpired=null,m._hash=function(e){var t,n,i=0;if(e)for(t=0;t0;)m._callCallback(n.shift(),t)}},m._addQuerystringParam=function(e,t,n,i){var r=encodeURIComponent(t)+"="+encodeURIComponent(n),a=N.parseHash(e),o=N.hashlessUrl(e);if(-1===o.indexOf("?"))return o+"?"+r+a;var s=o.split("?"),l=s[0]+"?",c=s[1];return l+N.addQueryParamAtLocation(c,r,i)+a},m._extractParamFromUri=function(e,t){var n=new RegExp("[\\?&#]"+t+"=([^&#]*)"),i=n.exec(e);if(i&&i.length)return decodeURIComponent(i[1])},m._parseAdobeMcFromUrl=r(re.ADOBE_MC),m._parseAdobeMcSdidFromUrl=r(re.ADOBE_MC_SDID),m._attemptToPopulateSdidFromUrl=function(e){var n=m._parseAdobeMcSdidFromUrl(e),i=1e9;n&&n.TS&&(i=N.getTimestampInSeconds()-n.TS),n&&n.SDID&&n.MCORGID===t&&ire.ADOBE_MC_TTL_IN_MIN||e.MCORGID!==t)return;a(e)}},m._mergeServerState=function(e){if(e)try{if(e=function(e){return N.isObject(e)?e:JSON.parse(e)}(e),e[m.marketingCloudOrgID]){var t=e[m.marketingCloudOrgID];!function(e){N.isObject(e)&&m.setCustomerIDs(e)}(t.customerIDs),o(t.sdid)}}catch(e){throw new Error("`serverState` has an invalid format.")}},m._timeout=null,m._loadData=function(e,t,n,i){t=m._addQuerystringParam(t,"d_fieldgroup",e,1),i.url=m._addQuerystringParam(i.url,"d_fieldgroup",e,1),i.corsUrl=m._addQuerystringParam(i.corsUrl,"d_fieldgroup",e,1),V.fieldGroupObj[e]=!0,i===Object(i)&&i.corsUrl&&"XMLHttpRequest"===R.corsMetadata.corsType&&R.fireCORS(i,n,e)},m._clearTimeout=function(e){null!=m._timeout&&m._timeout[e]&&(clearTimeout(m._timeout[e]),m._timeout[e]=0)},m._settingsDigest=0,m._getSettingsDigest=function(){if(!m._settingsDigest){var e=m.version;m.audienceManagerServer&&(e+="|"+m.audienceManagerServer),m.audienceManagerServerSecure&&(e+="|"+m.audienceManagerServerSecure),m._settingsDigest=m._hash(e)}return m._settingsDigest},m._readVisitorDone=!1,m._readVisitor=function(){if(!m._readVisitorDone){m._readVisitorDone=!0;var e,t,n,i,r,a,o=m._getSettingsDigest(),s=!1,l=m.cookieRead(m.cookieName),c=new Date;if(l||v||m.discardTrackingServerECID||(l=m.cookieRead(re.FIRST_PARTY_SERVER_COOKIE)),null==m._fields&&(m._fields={}),l&&"T"!==l)for(l=l.split("|"),l[0].match(/^[\-0-9]+$/)&&(parseInt(l[0],10)!==o&&(s=!0),l.shift()),l.length%2==1&&l.pop(),e=0;e1?(r=parseInt(t[1],10),a=t[1].indexOf("s")>0):(r=0,a=!1),s&&("MCCIDH"===n&&(i=""),r>0&&(r=c.getTime()/1e3-60)),n&&i&&(m._setField(n,i,1),r>0&&(m._fields["expire"+n]=r+(a?"s":""),(c.getTime()>=1e3*r||a&&!m.cookieRead(m.sessionCookieName))&&(m._fieldsExpired||(m._fieldsExpired={}),m._fieldsExpired[n]=!0)));!m._getField(M)&&N.isTrackingServerPopulated()&&(l=m.cookieRead("s_vi"))&&(l=l.split("|"),l.length>1&&l[0].indexOf("v1")>=0&&(i=l[1],e=i.indexOf("["),e>=0&&(i=i.substring(0,e)),i&&i.match(re.VALID_VISITOR_ID_REGEX)&&m._setField(M,i)))}},m._appendVersionTo=function(e){var t="vVersion|"+m.version,n=e?m._getCookieVersion(e):null;return n?Z.areVersionsDifferent(n,m.version)&&(e=e.replace(re.VERSION_REGEX,t)):e+=(e?"|":"")+t,e},m._writeVisitor=function(){var e,t,n=m._getSettingsDigest();for(e in m._fields)L(e)&&m._fields[e]&&"expire"!==e.substring(0,6)&&(t=m._fields[e],n+=(n?"|":"")+e+(m._fields["expire"+e]?"-"+m._fields["expire"+e]:"")+"|"+t);n=m._appendVersionTo(n),m.cookieWrite(m.cookieName,n,1)},m._getField=function(e,t){return null==m._fields||!t&&m._fieldsExpired&&m._fieldsExpired[e]?null:m._fields[e]},m._setField=function(e,t,n){null==m._fields&&(m._fields={}),m._fields[e]=t,n||m._writeVisitor()},m._getFieldList=function(e,t){var n=m._getField(e,t);return n?n.split("*"):null},m._setFieldList=function(e,t,n){m._setField(e,t?t.join("*"):"",n)},m._getFieldMap=function(e,t){var n=m._getFieldList(e,t);if(n){var i,r={};for(i=0;i0?e.substr(t):""},hashlessUrl:function(e){var t=e.indexOf("#");return t>0?e.substr(0,t):e},addQueryParamAtLocation:function(e,t,n){var i=e.split("&");return n=null!=n?n:i.length,i.splice(n,0,t),i.join("&")},isFirstPartyAnalyticsVisitorIDCall:function(e,t,n){if(e!==M)return!1;var i;return t||(t=m.trackingServer),n||(n=m.trackingServerSecure),!("string"!=typeof(i=m.loadSSL?n:t)||!i.length)&&(i.indexOf("2o7.net")<0&&i.indexOf("omtrdc.net")<0)},isObject:function(e){return Boolean(e&&e===Object(e))},removeCookie:function(e){Q.remove(e,{domain:m.cookieDomain})},isTrackingServerPopulated:function(){return!!m.trackingServer||!!m.trackingServerSecure},getTimestampInSeconds:function(){return Math.round((new Date).getTime()/1e3)},parsePipeDelimetedKeyValues:function(e){return e.split("|").reduce(function(e,t){var n=t.split("=");return e[n[0]]=decodeURIComponent(n[1]),e},{})},generateRandomString:function(e){e=e||5;for(var t="",n="abcdefghijklmnopqrstuvwxyz0123456789";e--;)t+=n[Math.floor(Math.random()*n.length)];return t},normalizeBoolean:function(e){return"true"===e||"false"!==e&&e},parseBoolean:function(e){return"true"===e||"false"!==e&&null},replaceMethodsWithFunction:function(e,t){for(var n in e)e.hasOwnProperty(n)&&"function"==typeof e[n]&&(e[n]=t);return e}};m._helpers=N;var x=ae(m,S);m._destinationPublishing=x,m.timeoutMetricsLog=[];var V={isClientSideMarketingCloudVisitorID:null,MCIDCallTimedOut:null,AnalyticsIDCallTimedOut:null,AAMIDCallTimedOut:null,fieldGroupObj:{},setState:function(e,t){switch(e){case"MC":!1===t?!0!==this.MCIDCallTimedOut&&(this.MCIDCallTimedOut=!1):this.MCIDCallTimedOut=t;break;case O:!1===t?!0!==this.AnalyticsIDCallTimedOut&&(this.AnalyticsIDCallTimedOut=!1):this.AnalyticsIDCallTimedOut=t;break;case k:!1===t?!0!==this.AAMIDCallTimedOut&&(this.AAMIDCallTimedOut=!1):this.AAMIDCallTimedOut=t}}};m.isClientSideMarketingCloudVisitorID=function(){return V.isClientSideMarketingCloudVisitorID},m.MCIDCallTimedOut=function(){return V.MCIDCallTimedOut},m.AnalyticsIDCallTimedOut=function(){return V.AnalyticsIDCallTimedOut},m.AAMIDCallTimedOut=function(){return V.AAMIDCallTimedOut},m.idSyncGetOnPageSyncInfo=function(){return m._readVisitor(),m._getField("MCSYNCSOP")},m.idSyncByURL=function(e){if(!m.isOptedOut()){var t=l(e||{});if(t.error)return t.error;var n,i,r=e.url,a=encodeURIComponent,o=x;return r=r.replace(/^https:/,"").replace(/^http:/,""),n=j.encodeAndBuildRequest(["",e.dpid,e.dpuuid||""],","),i=["ibs",a(e.dpid),"img",a(r),t.ttl,"",n],o.addMessage(i.join("|")),o.requestToProcess(),"Successfully queued"}},m.idSyncByDataSource=function(e){if(!m.isOptedOut())return e===Object(e)&&"string"==typeof e.dpuuid&&e.dpuuid.length?(e.url="//dpm.demdex.net/ibs:dpid="+e.dpid+"&dpuuid="+e.dpuuid,m.idSyncByURL(e)):"Error: config or config.dpuuid is empty"},Ue(m,x),m._getCookieVersion=function(e){e=e||m.cookieRead(m.cookieName);var t=re.VERSION_REGEX.exec(e);return t&&t.length>1?t[1]:null},m._resetAmcvCookie=function(e){var t=m._getCookieVersion();t&&!Z.isLessThan(t,e)||N.removeCookie(m.cookieName)},m.setAsCoopSafe=function(){A=!0},m.setAsCoopUnsafe=function(){A=!1},function(){if(m.configs=Object.create(null),N.isObject(n))for(var e in n)L(e)&&(m[e]=n[e],m.configs[e]=n[e])}(),d();var U;m.init=function(){c()&&(h.optIn.fetchPermissions(p,!0),!h.optIn.isApproved(h.optIn.Categories.ECID))||U||(U=!0,function(){if(N.isObject(n)){m.idSyncContainerID=m.idSyncContainerID||0,A="boolean"==typeof m.isCoopSafe?m.isCoopSafe:N.parseBoolean(m.isCoopSafe),m.resetBeforeVersion&&m._resetAmcvCookie(m.resetBeforeVersion),m._attemptToPopulateIdsFromUrl(),m._attemptToPopulateSdidFromUrl(),m._readVisitor();var e=m._getField(b),t=Math.ceil((new Date).getTime()/re.MILLIS_PER_DAY);m.idSyncDisableSyncs||m.disableIdSyncs||!x.canMakeSyncIDCall(e,t)||(m._setFieldExpire(T,-1),m._setField(b,t)),m.getMarketingCloudVisitorID(),m.getAudienceManagerLocationHint(),m.getAudienceManagerBlob(),m._mergeServerState(m.serverState)}else m._attemptToPopulateIdsFromUrl(),m._attemptToPopulateSdidFromUrl()}(),function(){if(!m.idSyncDisableSyncs&&!m.disableIdSyncs){x.checkDPIframeSrc();var e=function(){var e=x;e.readyToAttachIframe()&&e.attachIframe()};D.addEventListener("load",function(){S.windowLoaded=!0,e()});try{te.receiveMessage(function(e){x.receiveMessage(e.data)},x.iframeHost)}catch(e){}}}(),function(){m.whitelistIframeDomains&&re.POST_MESSAGE_ENABLED&&(m.whitelistIframeDomains=m.whitelistIframeDomains instanceof Array?m.whitelistIframeDomains:[m.whitelistIframeDomains],m.whitelistIframeDomains.forEach(function(e){var n=new B(t,e),i=K(m,n);te.receiveMessage(i,e)}))}())}};qe.config=se,_.Visitor=qe;var Xe=qe,We=function(e){if(j.isObject(e))return Object.keys(e).filter(function(t){return""!==e[t]}).reduce(function(t,n){var i=se.normalizeConfig(e[n]),r=j.normalizeBoolean(i);return t[n]=r,t},Object.create(null))},Je=Ve.OptIn,Ke=Ve.IabPlugin;return Xe.getInstance=function(e,t){if(!e)throw new Error("Visitor requires Adobe Marketing Cloud Org ID.");e.indexOf("@")<0&&(e+="@AdobeOrg");var n=function(){var t=_.s_c_il;if(t)for(var n=0;n { + const SEGMENT_ID_USER_WITHOUT_CONSENTS = + window.__SEGMENT_WRAPPER?.SEGMENT_ID_USER_WITHOUT_CONSENTS ?? 'anonymous_user' + + const anonymousId = window.analytics.user?.()?.anonymousId() + + if (anonymousId === SEGMENT_ID_USER_WITHOUT_CONSENTS) { + window.analytics.setAnonymousId(null) + } +} diff --git a/packages/sui-segment-wrapper/src/config.js b/packages/sui-segment-wrapper/src/config.js new file mode 100644 index 000000000..3c7636e73 --- /dev/null +++ b/packages/sui-segment-wrapper/src/config.js @@ -0,0 +1,24 @@ +const MPI_CONFIG_KEY = '__mpi' + +export const isClient = typeof window !== 'undefined' + +/** + * Get the Segment Wrapper config from window + * @param {string=} key Key config to extract. If not provided, all the config will be returned + * @return {any} Config value or all the config if not key provided + */ +export const getConfig = key => { + const config = window?.[MPI_CONFIG_KEY]?.segmentWrapper || {} + return key ? config[key] : config +} + +/** + * Set a config value to the Segment Wrapper config + * @param {string} key Config key to update + * @param {boolean|string|number|object} value Value to set on the config key + */ +export const setConfig = (key, value) => { + window[MPI_CONFIG_KEY] = window[MPI_CONFIG_KEY] || {} + window[MPI_CONFIG_KEY].segmentWrapper = window[MPI_CONFIG_KEY].segmentWrapper || {} + window[MPI_CONFIG_KEY].segmentWrapper[key] = value +} diff --git a/packages/sui-segment-wrapper/src/index.js b/packages/sui-segment-wrapper/src/index.js new file mode 100644 index 000000000..0d8f6bcc5 --- /dev/null +++ b/packages/sui-segment-wrapper/src/index.js @@ -0,0 +1,39 @@ +import './patchAnalytics.js' + +import {defaultContextProperties} from './middlewares/source/defaultContextProperties.js' +import {pageReferrer} from './middlewares/source/pageReferrer.js' +import {userScreenInfo} from './middlewares/source/userScreenInfo.js' +import {userTraits} from './middlewares/source/userTraits.js' +import {checkAnonymousId} from './checkAnonymousId.js' +import {isClient} from './config.js' +import analytics from './segmentWrapper.js' +import initTcfTracking from './tcf.js' +import {getUserDataAndNotify} from './universalId.js' + +/* Initialize TCF Tracking with Segment */ +initTcfTracking() + +/* Generate UniversalId if available */ +try { + getUserDataAndNotify() +} catch (e) { + console.error(`[segment-wrapper] UniversalID couldn't be initialized`) // eslint-disable-line +} + +/* Initialize middlewares */ +const addMiddlewares = () => { + window.analytics.addSourceMiddleware(userTraits) + window.analytics.addSourceMiddleware(defaultContextProperties) + window.analytics.addSourceMiddleware(userScreenInfo) + window.analytics.addSourceMiddleware(pageReferrer) +} + +/* Initialize Segment on Client */ +if (isClient && window.analytics) { + window.analytics.ready(checkAnonymousId) + window.analytics.addSourceMiddleware ? addMiddlewares() : window.analytics.ready(addMiddlewares) +} + +export default analytics +export {getAdobeVisitorData, getAdobeMCVisitorID} from './adobeRepository.js' +export {getUniversalId} from './universalId.js' diff --git a/packages/sui-segment-wrapper/src/middlewares/destination/optimizelySiteAttribute.js b/packages/sui-segment-wrapper/src/middlewares/destination/optimizelySiteAttribute.js new file mode 100644 index 000000000..725bd6c48 --- /dev/null +++ b/packages/sui-segment-wrapper/src/middlewares/destination/optimizelySiteAttribute.js @@ -0,0 +1,22 @@ +export const optimizelySiteAttributeMiddleware = ({payload, next}) => { + const { + obj: {integrations, context} + } = payload + + if (!context.site) { + return next(payload) + } + + payload.obj.integrations = { + ...integrations, + Optimizely: { + ...integrations.Optimizely, + attributes: { + site: context.site, + ...integrations.Optimizely?.attributes + } + } + } + + next(payload) +} diff --git a/packages/sui-segment-wrapper/src/middlewares/source/defaultContextProperties.js b/packages/sui-segment-wrapper/src/middlewares/source/defaultContextProperties.js new file mode 100644 index 000000000..520bb4991 --- /dev/null +++ b/packages/sui-segment-wrapper/src/middlewares/source/defaultContextProperties.js @@ -0,0 +1,15 @@ +import {getConfig} from '../../config.js' + +const DEFAULT_CONTEXT = {platform: 'web'} + +export const defaultContextProperties = ({payload, next}) => { + const defaultContext = getConfig('defaultContext') || {} + + payload.obj.context = { + ...DEFAULT_CONTEXT, + ...defaultContext, + ...payload.obj.context + } + + next(payload) +} diff --git a/packages/sui-segment-wrapper/src/middlewares/source/optimizelyUserId.js b/packages/sui-segment-wrapper/src/middlewares/source/optimizelyUserId.js new file mode 100644 index 000000000..d6bfefca3 --- /dev/null +++ b/packages/sui-segment-wrapper/src/middlewares/source/optimizelyUserId.js @@ -0,0 +1,20 @@ +export const optimizelyUserId = ({payload, next}) => { + const { + obj: {integrations} + } = payload + + // when integrations.All equals false, destinations are deactivated + if (integrations.All === false) { + return next(payload) + } + + payload.obj.integrations = { + ...integrations, + Optimizely: { + userId: window.analytics.user().anonymousId(), + ...integrations.Optimizely + } + } + + next(payload) +} diff --git a/packages/sui-segment-wrapper/src/middlewares/source/pageReferrer.js b/packages/sui-segment-wrapper/src/middlewares/source/pageReferrer.js new file mode 100644 index 000000000..ccc96dde8 --- /dev/null +++ b/packages/sui-segment-wrapper/src/middlewares/source/pageReferrer.js @@ -0,0 +1,80 @@ +export const referrerState = { + spaReferrer: '', + referrer: '' +} + +/** + * Useful wrapper around document and window objects + */ +export const utils = { + /** + * @returns {string} The referrer of the document + */ + getDocumentReferrer: () => document.referrer, + /** + * @returns {string} The actual location with protocol, domain and pathname + */ + getActualLocation: () => { + const {origin, pathname} = window.location + return `${origin}${pathname}` + }, + /** + * @returns {string} The actual location with protocol, domain and pathname + */ + getActualQueryString: () => { + const {search} = window.location + return search + } +} + +/** + * Get the correct page referrer for SPA navigations + * @returns {string} referrer + */ +export const getPageReferrer = ({isPageTrack = false} = {}) => { + const {referrer, spaReferrer} = referrerState + // if we're a page, we should use the new referrer that was calculated with the previous location + // if we're a track, we should use the previous referrer, as the location hasn't changed yet + const referrerToUse = isPageTrack ? referrer : spaReferrer + // as a fallback for page and tracks, we must use always the document.referrer + // because some sites could not be using `page` or a `track` could be done + // even before the first page + return referrerToUse || utils.getDocumentReferrer() +} + +/** + * Update page referrer for SPA navigations + */ +export const updatePageReferrer = () => { + referrerState.spaReferrer = getPageReferrer({isPageTrack: true}) + // mutate actualReferrer with what will be the new referrer + referrerState.referrer = utils.getActualLocation() +} + +/** + * @param {object} params + * @param {{obj: {context: object }}} params.payload + * @param {function} params.next + * @returns {void} + */ +export const pageReferrer = ({payload, next}) => { + const { + obj: {context} + } = payload + + const {isPageTrack} = context + + const referrer = getPageReferrer({isPageTrack}) + + payload.obj.context = { + ...context, + page: { + ...context.page, + referrer + } + } + + // update page referrer only if it's a page event + isPageTrack && updatePageReferrer() + next(payload) +} diff --git a/packages/sui-segment-wrapper/src/middlewares/source/userScreenInfo.js b/packages/sui-segment-wrapper/src/middlewares/source/userScreenInfo.js new file mode 100644 index 000000000..c8452053d --- /dev/null +++ b/packages/sui-segment-wrapper/src/middlewares/source/userScreenInfo.js @@ -0,0 +1,11 @@ +export const userScreenInfo = ({payload, next}) => { + payload.obj.context = { + ...payload.obj.context, + screen: { + height: window.innerHeight, + width: window.innerWidth, + density: window.devicePixelRatio + } + } + next(payload) +} diff --git a/packages/sui-segment-wrapper/src/middlewares/source/userTraits.js b/packages/sui-segment-wrapper/src/middlewares/source/userTraits.js new file mode 100644 index 000000000..f3a22a42e --- /dev/null +++ b/packages/sui-segment-wrapper/src/middlewares/source/userTraits.js @@ -0,0 +1,41 @@ +import {getConfig} from '../../config.js' +import {checkAnalyticsGdprIsAccepted, getGdprPrivacyValue} from '../../tcf.js' + +/** + * Get user traits from global analytics object and put in the object + * @param {string} gdprPrivacyValue Determine if we have user consents + * @returns {object} User traits with to add + */ +const getUserTraits = gdprPrivacyValue => { + const isUserTraitsEnabled = getConfig('isUserTraitsEnabled') + const user = window.analytics.user() + const isGdprAccepted = checkAnalyticsGdprIsAccepted(gdprPrivacyValue) + const userId = user.id() + + return { + anonymousId: user.anonymousId(), + ...(userId && {userId}), + ...(isGdprAccepted && isUserTraitsEnabled && user.traits()) + } +} + +export const userTraits = async ({payload, next}) => { + const gdprPrivacyValue = await getGdprPrivacyValue() + + let userTraits + try { + userTraits = getUserTraits(gdprPrivacyValue) + } catch (error) { + console.error(error) // eslint-disable-line + } + + payload.obj.context = { + ...payload.obj.context, + traits: { + ...payload.obj.context.traits, + ...userTraits + } + } + + next(payload) +} diff --git a/packages/sui-segment-wrapper/src/patchAnalytics.js b/packages/sui-segment-wrapper/src/patchAnalytics.js new file mode 100644 index 000000000..c7b37c6b7 --- /dev/null +++ b/packages/sui-segment-wrapper/src/patchAnalytics.js @@ -0,0 +1,32 @@ +import {isClient} from './config.js' +import {decorateContextWithNeededData, getDefaultProperties} from './segmentWrapper.js' + +const MPI_PATCH_FIELD = '__mpi_patch' + +function monkeyPatchAnalyticsTrack() { + const {track: originalTrack} = window.analytics + window.analytics.track = (...args) => { + const [event, properties, contextFromArgs, fn] = args + const newProperties = { + ...getDefaultProperties(), + ...properties + } + decorateContextWithNeededData({ + context: contextFromArgs, + event + }).then(context => { + originalTrack.call(window.analytics, event, newProperties, context, fn) + }) + return window.analytics + } + // add a flag to the patched analytics so we don't patch this twice + window.analytics[MPI_PATCH_FIELD] = true +} + +if (isClient) { + if (!window.analytics) { + console.warn('Segment Analytics is not loaded so patch is not applied.') + } else if (!window.analytics[MPI_PATCH_FIELD]) { + window.analytics.initialized ? monkeyPatchAnalyticsTrack() : window.analytics.ready(monkeyPatchAnalyticsTrack) + } +} diff --git a/packages/sui-segment-wrapper/src/segmentWrapper.js b/packages/sui-segment-wrapper/src/segmentWrapper.js new file mode 100644 index 000000000..6bc86cdaf --- /dev/null +++ b/packages/sui-segment-wrapper/src/segmentWrapper.js @@ -0,0 +1,228 @@ +// @ts-check + +import {getAdobeMCVisitorID} from './adobeRepository.js' +import {getConfig} from './config.js' +import {checkAnalyticsGdprIsAccepted, getGdprPrivacyValue} from './tcf.js' +import {getXandrId} from './xandrRepository.js' + +/* Default properties to be sent on all trackings */ +const DEFAULT_PROPERTIES = {platform: 'web'} + +/* Disabled integrations when no GDPR Privacy Value is true */ +export const INTEGRATIONS_WHEN_NO_CONSENTS = { + All: false +} + +export const INTEGRATIONS_WHEN_NO_CONSENTS_CMP_SUBMITTED = { + All: true +} + +/** + * Get default properties using the constant and the window.__mpi object if available + * @returns {{[key:string]: any}} Default properties to attach to track + */ +export const getDefaultProperties = () => ({ + ...DEFAULT_PROPERTIES, + ...getConfig('defaultProperties') +}) + +/** + * Get all needed integrations depending on the gdprPrivacy value. + * One of them is the AdobeMarketingCloudVisitorId for Adobe Analytics integration. + * @param {object} param - Object with the gdprPrivacyValue and if it's a CMP Submitted event + */ +const getTrackIntegrations = async ({gdprPrivacyValue, event}) => { + const isGdprAccepted = checkAnalyticsGdprIsAccepted(gdprPrivacyValue) + let marketingCloudVisitorId + + if (isGdprAccepted) { + marketingCloudVisitorId = await getAdobeMCVisitorID() + } + + const restOfIntegrations = getRestOfIntegrations({isGdprAccepted, event}) + const adobeAnalyticsIntegration = marketingCloudVisitorId ? {marketingCloudVisitorId} : true + + // if we don't have the user consents we remove all the integrations but Adobe Analytics + return { + ...restOfIntegrations, + 'Adobe Analytics': adobeAnalyticsIntegration + } +} + +/** + * Get Rest of integrations depending on the gdprPrivacy value and if it's a CMP Submitted event + * @param {object} param - Object with the isGdprAccepted and if it's a CMP Submitted event + * @returns {object} integrations + */ +export const getRestOfIntegrations = ({isGdprAccepted, event}) => { + const isCMPSubmittedEvent = event === 'CMP Submitted' + + if (isCMPSubmittedEvent) { + return INTEGRATIONS_WHEN_NO_CONSENTS_CMP_SUBMITTED + } + return isGdprAccepted ? {} : INTEGRATIONS_WHEN_NO_CONSENTS +} + +/** + * It returns externalIds to add to context + * + * @param {Object} param + * @param {Object} param.context previous context + * @param {String} param.xandrId xandrId to be included + * @returns + */ +const getExternalIds = ({context, xandrId}) => { + const shouldSendXandrId = getConfig('sendXandrId') !== false + const isValidXandrId = xandrId && parseInt(xandrId) !== 0 + if (!shouldSendXandrId || !isValidXandrId) { + return {} + } + const SEGMENT_COLLECTION = 'users' + const SEGMENT_ENCODING = 'none' + const SEGMENT_TYPE = 'xandr_id' + const externalIds = [ + ...(context?.externalIds || []), + { + collection: SEGMENT_COLLECTION, + encoding: SEGMENT_ENCODING, + id: xandrId, + type: SEGMENT_TYPE + } + ] + + const uniqueExternalIds = externalIds.filter( + ({id: idFilter, type: typeFilter}, index) => + index === externalIds.findIndex(({id: idFind, type: typeFind}) => idFilter === idFind && typeFilter === typeFind) + ) + return {externalIds: uniqueExternalIds} +} + +/** + * Get data like traits and integrations to be added to the context object + * @param {object} context Context object with all the actual info + * @returns {Promise} New context with all the previous info and the new one + */ +export const decorateContextWithNeededData = async ({event = '', context = {}}) => { + const gdprPrivacyValue = await getGdprPrivacyValue() + const {analytics: gdprPrivacyValueAnalytics, advertising: gdprPrivacyValueAdvertising} = gdprPrivacyValue || {} + const isGdprAccepted = checkAnalyticsGdprIsAccepted(gdprPrivacyValue) + const [integrations, xandrId] = await Promise.all([ + getTrackIntegrations({gdprPrivacyValue, event}), + getXandrId({gdprPrivacyValueAdvertising}) + ]) + + if (!isGdprAccepted) { + context.integrations = { + ...(context.integrations ?? {}), + Personas: false, + Webhooks: true, + Webhook: true + } + } + + return { + ...context, + ...(!isGdprAccepted && {ip: '0.0.0.0'}), + ...getExternalIds({context, xandrId}), + gdpr_privacy: gdprPrivacyValueAnalytics, + gdpr_privacy_advertising: gdprPrivacyValueAdvertising, + integrations: { + ...context.integrations, + ...integrations + } + } +} + +/** + * The track method lets you record any actions your users perform. + * @param {string} event The name of the event you’re tracking + * @param {object} [properties] A dictionary of properties for the event. + * @param {object} [context] A dictionary of options. + * @param {function} [callback] A function executed after a short timeout, giving the browser time to make outbound requests first. + * @returns {Promise} + */ +const track = (event, properties, context = {}, callback) => + new Promise(resolve => { + const initTrack = async () => { + const newContext = await decorateContextWithNeededData({context, event}) + + /** + * @deprecated Now we use `defaultContextProperties` middleware + * and put the info on the context object + */ + const newProperties = { + ...getDefaultProperties(), + ...properties + } + + const newCallback = async (...args) => { + if (callback) callback(...args) // eslint-disable-line n/no-callback-literal + const [gdprPrivacyValue] = await Promise.all([getGdprPrivacyValue()]) + + if (checkAnalyticsGdprIsAccepted(gdprPrivacyValue)) { + resolve(...args) + } else { + resolve() + } + } + + window.analytics.track( + event, + newProperties, + { + ...newContext, + context: { + integrations: { + ...newContext.integrations + } + } + }, + newCallback + ) + } + + initTrack() + }) + +/** + * Associate your users and their actions to a recognizable userId and traits. + * @param {string} userId Id to identify the user. + * @param {object} traits A dictionary of traits you know about the user, like their email or name. + * @param {object} [options] A dictionary of options. + * @param {function} [callback] A function executed after a short timeout, giving the browser time to make outbound requests first. + * @returns {Promise} + */ +const identify = async (userId, traits, options, callback) => { + const gdprPrivacyValue = await getGdprPrivacyValue() + + return window.analytics.identify( + userId, + checkAnalyticsGdprIsAccepted(gdprPrivacyValue) ? traits : {}, + options, + callback + ) +} + +/** + * Record whenever a user sees a page of your website, along with any optional properties about the page. + * @param {string} event The name of the event you’re tracking + * @param {object=} properties A dictionary of properties for the event. + * @param {object} [context] A dictionary of options. + * @param {function} [callback] A function executed after a short timeout, giving the browser time to make outbound requests first. + * @returns {Promise} + */ +const page = (event, properties, context = {}, callback) => { + // we put a flag on context to know this track is a page + context.isPageTrack = true + // just call track again but the with the proper context + return track(event, properties, context, callback) +} + +/** + * Resets the id, including anonymousId, and clear traits for the currently identified user and group. + * NOTE: Only clears the cookies and localStorage set by analytics. + * @returns {Promise} + */ +const reset = () => Promise.resolve(window.analytics.reset()) + +export default {page, identify, track, reset} diff --git a/packages/sui-segment-wrapper/src/tcf.js b/packages/sui-segment-wrapper/src/tcf.js new file mode 100644 index 000000000..f8ff77c38 --- /dev/null +++ b/packages/sui-segment-wrapper/src/tcf.js @@ -0,0 +1,260 @@ +import {getConfig, isClient, setConfig} from './config.js' +import analytics from './segmentWrapper.js' + +/** + * Cookie to extract the already saved consents for the user + * @type {string} + */ +const TCF_COOKIE_KEY = 'borosTcf' +/** + * TCF Api Version to use + * @type {number} + */ +const TCF_API_VERSION = 2 + +/** + * Default properties to send with every TCF tracking event + */ +const TCF_TRACK_PROPERTIES = { + channel: 'GDPR' +} + +/** + * List of purpose ids needed to be able to track with all info + * @type {Array} + */ +const NEEDED_PURPOSES = { + analytics: [1, 8, 10], + advertising: [3] +} + +/** + * TCF events + */ +const TCF_EVENTS = { + // Event that determines that the tcData has been loaded + LOADED: 'tcloaded', + // Event that determines that the user has performed an action + USER_ACTION_COMPLETE: 'useractioncomplete' +} + +/** + * State of user according to GDPR regarding tracking + */ +export const USER_GDPR = { + ACCEPTED: 'accepted', + DECLINED: 'declined', + UNKNOWN: 'unknown' +} + +/** + * Define the user GDPR consents state. This value will be updated with new values + * when the consents of the users changes. + */ +const gdprState = { + listeners: [], + value: undefined, + addListener: callback => gdprState.listeners.push(callback), + get: () => gdprState.value, + set: value => { + const {analytics, advertising} = value || {} + gdprState.value = { + analytics, + advertising + } + gdprState.listeners.forEach(fn => fn(value)) + gdprState.listeners = [] + } +} + +/** + * Read cookie by using a key + * @returns {string} + */ +function readCookie(key) { + const re = new RegExp(key + '=([^;]+)') + const value = re.exec(document.cookie) + return value !== null ? unescape(value[1]) : null +} + +/** + * Check if we're on client and tcfApi is available on window object + * @returns {Boolean} + */ +const checkTcfIsAvailable = () => { + // if we're on the client, check we haven't already initialized it + if (getConfig('initialized')) return false + + // if we're on client, check if we have the tcfapi available + const isTcfApiAvailable = !!window.__tcfapi + !isTcfApiAvailable && + console.warn("[tcfTracking] window.__tcfapi is not available on client and TCF won't be tracked.") + return isTcfApiAvailable +} + +/** + * Check from a list of consents if user has accepted being tracked + * @param {{[purposeId: string]: boolean}} userConsents + * @returns {Boolean} + */ +const checkHasUserConsentedAnalytics = userConsents => + NEEDED_PURPOSES.analytics.every(purposeId => userConsents[`${purposeId}`]) + +const checkHasUserConsentedAdvertising = userConsents => + NEEDED_PURPOSES.advertising.every(purposeId => userConsents[`${purposeId}`]) + +/** + * Track a specific TCF event + * @param {object} params + * @param {string} params.eventId Event ID to be sent with the TCF Tracking + * @param {string} params.gdprPrivacy Send a string telling if the gdpr has been accepted or reject + * @return {Promise} + */ +const trackTcf = ({eventId, gdprPrivacy}) => + analytics.track( + 'CMP Submitted', + { + ...TCF_TRACK_PROPERTIES, + ...getConfig('tcfTrackDefaultProperties') + }, + { + gdpr_privacy: gdprPrivacy.analytics, + gdpr_privacy_advertising: gdprPrivacy.advertising + } + ) + +/** + * Get if we have user consents + * @return {Promise} + */ +export const getGdprPrivacyValue = () => { + // try to get the actual gdprPrivacyValue and just return it + const gdprPrivacyValue = gdprState.get() + if (gdprPrivacyValue !== undefined) return Promise.resolve(gdprPrivacyValue) + + // // if we don't have a gdprPrivacyValue, then subscribe to it until we have a value + return new Promise(resolve => { + gdprState.addListener(gdprPrivacyValue => resolve(gdprPrivacyValue)) + }) +} + +/** + * Check if gdprPrivacyValue is accepted + * @return {boolean} + */ +export const checkAnalyticsGdprIsAccepted = gdprPrivacyValue => { + return gdprPrivacyValue.analytics === USER_GDPR.ACCEPTED +} + +/** + * Set gdprState according to list of purpose consents + * @returns {string} + */ +const setGdprStateBy = purposeConsents => { + const hasAnalyticsConsents = checkHasUserConsentedAnalytics(purposeConsents) + const hasAdvertisingConsents = checkHasUserConsentedAdvertising(purposeConsents) + // get the state key according to the gdprPrivacyValue + const gdprAnalyticsStateKey = hasAnalyticsConsents ? USER_GDPR.ACCEPTED : USER_GDPR.DECLINED + + const gdprAdvertisingStateKey = hasAdvertisingConsents ? USER_GDPR.ACCEPTED : USER_GDPR.DECLINED + // update gdprState with the new value for user gdpr + gdprState.set({ + analytics: gdprAnalyticsStateKey, + advertising: gdprAdvertisingStateKey + }) + // return the new gdprState + return { + analytics: gdprAnalyticsStateKey, + advertising: gdprAdvertisingStateKey + } +} + +/** + * Read and decode the tcf cookie + */ +const getConsentsFromCookie = () => { + const cookieValue = readCookie(TCF_COOKIE_KEY) + if (!cookieValue) return + + try { + const {purpose} = JSON.parse(window.atob(cookieValue)) + const {consents} = purpose + return consents + } catch (e) { + console.error(e) + } +} + +/** + * Set the correct initial gdprState based on the consents cookie string + */ +const initializeGdprState = () => { + const consents = getConsentsFromCookie() + if (consents) setGdprStateBy(consents) +} + +/** + * Sets global isFirstVisit flag based on the consents cookie string + */ +const initializeIsFirstVisit = () => { + const consents = getConsentsFromCookie() + setConfig('isFirstVisit', !consents) +} + +/** + * Init TCF Tracking User Consents with Segment + */ +export default function initTcfTracking() { + // first check if we're on server, as this doesn't work on server-side + if (!isClient) return + // read the cookie and put the correct usergdprValue and isFirstVisit flag before listening events + initializeGdprState() + initializeIsFirstVisit() + // do some checks before initializing tcf tracking as we do that only if available + if (!checkTcfIsAvailable()) { + // if we don't have a gdpr state and tcf is not available + // we should assume we don't known if we have consents + const analyticsGdprState = gdprState.get()?.analytics + if (analyticsGdprState === undefined) + gdprState.set({ + analytics: USER_GDPR.UNKNOWN, + advertising: USER_GDPR.UNKNOWN + }) + // and we stop executing as we can't track tcf + return + } + // add a flag to the segmentWrapper config to know it's already initialized + setConfig('initialized', true) + + // listen events from tcf api + window.__tcfapi('addEventListener', TCF_API_VERSION, ({eventStatus, purpose}, success) => { + if (!success) return Promise.resolve() + + // if we've already user consents or the user is accepting or declining now + // we change the state of the GDPR to use in our tracking + if (eventStatus === TCF_EVENTS.USER_ACTION_COMPLETE || eventStatus === TCF_EVENTS.LOADED) { + const {consents} = purpose + const gdprStateKey = setGdprStateBy(consents) + // if it's a user action, then we will track it + if (eventStatus === TCF_EVENTS.USER_ACTION_COMPLETE) { + // extract the eventId and gdprPrivacy string to send with the track + const {analytics: analyticsStateKey, advertising: advertisingStateKey} = gdprStateKey + + const gdprPrivacy = { + analytics: analyticsStateKey, + advertising: advertisingStateKey + } + + // temporary during Didomi migration to avoid fake programmatically generated events where the users doesn't really interact + const MIGRATION_DIDOMI_SEGMENT_WRAPPER_FLAG = 'didomi-migration' + const isDidomiMigration = isClient && window.sessionStorage.getItem(MIGRATION_DIDOMI_SEGMENT_WRAPPER_FLAG) + return ( + !isDidomiMigration && + trackTcf({ + gdprPrivacy + }) + ) + } + } + }) +} diff --git a/packages/sui-segment-wrapper/src/universalId.js b/packages/sui-segment-wrapper/src/universalId.js new file mode 100644 index 000000000..3bc8a6f15 --- /dev/null +++ b/packages/sui-segment-wrapper/src/universalId.js @@ -0,0 +1,42 @@ +import {dispatchEvent} from '@s-ui/js/lib/events' + +import {createUniversalId} from './utils/hashEmail.js' +import {getConfig, isClient, setConfig} from './config.js' +const USER_DATA_READY_EVENT = 'USER_DATA_READY' + +export const getUniversalIdFromConfig = () => getConfig('universalId') + +export const getUniversalId = () => { + // 1. Try to get universalId from config + let universalId = getUniversalIdFromConfig() + if (universalId) { + setUniversalIdInitialized() + return universalId + } + + // 2. If not available, then we use the email and hash it + const userEmail = getConfig('userEmail') + if (userEmail) { + universalId = createUniversalId(userEmail) + setUniversalId(universalId) + setUniversalIdInitialized() + return universalId + } + + // 3. We don't have user email, so we don't have universalId + // but we've tried, so we set it as initialized + setUniversalIdInitialized() +} + +export const getUserDataAndNotify = () => { + const universalId = getUniversalId() + const userEmail = getConfig('userEmail') + isClient && dispatchEvent({eventName: USER_DATA_READY_EVENT, detail: {universalId, userEmail}}) + return {universalId, userEmail} +} + +export const setUniversalIdInitialized = () => { + setConfig('universalIdInitialized', true) +} + +export const setUniversalId = universalId => setConfig('universalId', universalId) diff --git a/packages/sui-segment-wrapper/src/utils/cookies.js b/packages/sui-segment-wrapper/src/utils/cookies.js new file mode 100644 index 000000000..74a37df77 --- /dev/null +++ b/packages/sui-segment-wrapper/src/utils/cookies.js @@ -0,0 +1,56 @@ +export function readCookie(cookieName) { + const re = new RegExp(cookieName + '=([^;]+)') + const value = re.exec(document.cookie) + return value !== null ? unescape(value[1]) : null +} + +const ONE_YEAR = 31_536_000 +const DEFAULT_PATH = '/' +const DEFAULT_SAME_SITE = 'Lax' +export function saveCookie( + cookieName, + data, + {maxAge = ONE_YEAR, path = DEFAULT_PATH, reduceDomain = true, sameSite = DEFAULT_SAME_SITE} = {} +) { + const domain = reduceDomain ? toCookieDomain() : window.location.hostname + const cookieValue = [ + `${cookieName}=${data}`, + `domain=${domain}`, + `path=${path}`, + `max-age=${maxAge}`, + `SameSite=${sameSite}` + ].join(';') + document.cookie = cookieValue +} + +export function removeCookie( + cookieName, + {path = DEFAULT_PATH, reduceDomain = true, sameSite = DEFAULT_SAME_SITE} = {} +) { + const domain = reduceDomain ? toCookieDomain() : window.location.hostname + document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain}` +} + +/** + * Reduces the domain to main domain name + * Examples: + * - www.fotocasa.es -> .fotocasa.es + * - www.motos.coches.net -> .coches.net + * - www.jobisjob.co.uk -> .jobisjob.co.uk + * + * @param {String} domain + * @returns {String} dot + main domain + */ +const toCookieDomain = (domain = window.location.hostname || '') => { + const DOT = '.' + const hostDomainParts = domain.split(DOT).reverse() + if (hostDomainParts.length === 1) { + return hostDomainParts[0] + } + const cookieDomainParts = [] + for (const part of hostDomainParts) { + cookieDomainParts.push(part) + if (part.length > 3) break + } + return `${DOT}${cookieDomainParts.reverse().join(DOT)}` +} diff --git a/packages/sui-segment-wrapper/src/utils/hashEmail.js b/packages/sui-segment-wrapper/src/utils/hashEmail.js new file mode 100644 index 000000000..ef2da1c0d --- /dev/null +++ b/packages/sui-segment-wrapper/src/utils/hashEmail.js @@ -0,0 +1,33 @@ +import md5 from 'tiny-hashes/md5' +import sha256 from 'tiny-hashes/sha256' + +const PLUS_AND_DOT = /\.|\+.*$/g + +/** + * Normalize email by lowering case and extracting + and . symbols for gmail + * @param {string} email + * @return {string} Normalized email. If not valid, returns and empty string + */ +export function normalizeEmail(email) { + if (typeof email !== 'string') return '' + + email = email.toLowerCase() + const emailParts = email.split(/@/) + + if (emailParts.length !== 2) return '' + + let [username, domain] = emailParts + username = username.replace(PLUS_AND_DOT, '') + + return `${username}@${domain}` +} + +export function createUniversalId(email) { + const normalizedEmail = normalizeEmail(email) + return normalizedEmail ? sha256(normalizedEmail) : '' +} + +export function hashEmail(email) { + const normalizedEmail = normalizeEmail(email) + return normalizedEmail ? md5(normalizedEmail) : '' +} diff --git a/packages/sui-segment-wrapper/src/utils/storage.js b/packages/sui-segment-wrapper/src/utils/storage.js new file mode 100644 index 000000000..8730f00fc --- /dev/null +++ b/packages/sui-segment-wrapper/src/utils/storage.js @@ -0,0 +1,18 @@ +// @ts-check + +/** + * Use this module to store and retrieve data from localStorage or sessionStorage + * @param {object} params + * @param {string} params.key + * @param {'getItem'|'removeItem'|'setItem'=} params.method + * @param {'localStorage'|'sessionStorage'=} params.type + * @param {string=} params.value + * @returns {string|void} + */ +export const storage = ({type = 'localStorage', method = 'getItem', key, value}) => { + try { + return window[type][method](key, value) + } catch (e) { + console.error(e) + } +} diff --git a/packages/sui-segment-wrapper/src/xandrRepository.js b/packages/sui-segment-wrapper/src/xandrRepository.js new file mode 100644 index 000000000..2a17890dd --- /dev/null +++ b/packages/sui-segment-wrapper/src/xandrRepository.js @@ -0,0 +1,48 @@ +import {readCookie, removeCookie, saveCookie} from './utils/cookies.js' +import {isClient, setConfig} from './config.js' +import {USER_GDPR} from './tcf.js' + +const XANDR_ID_SERVER_URL = 'https://secure.adnxs.com/getuidj' +const XANDR_ID_COOKIE = 'adit-xandr-id' + +const USER_OPTED_OUT_XANDR_ID_VALUE = 0 + +/** + * [Xandr API Docs]{@link https://docs.xandr.com/bundle/invest_invest-standard/page/topics/user-id-mapping-with-getuid-and-mapuid.html} + * @returns {String|null} a valid xandrId value + */ +export function getXandrId({gdprPrivacyValueAdvertising}) { + if (!isClient) return null + + if (gdprPrivacyValueAdvertising !== USER_GDPR.ACCEPTED) { + removeCookie(XANDR_ID_COOKIE) + return null + } + + // 0 is invalid. Negative numbers seems to be invalid too. + const checkValid = xandrId => xandrId && Number(xandrId) > USER_OPTED_OUT_XANDR_ID_VALUE + + const storedXandrId = readCookie(XANDR_ID_COOKIE) + const isValidXandrId = checkValid(storedXandrId) + + if (!isValidXandrId) { + getRemoteXandrId().then(xandrId => { + if (typeof xandrId === 'string') { + saveCookie(XANDR_ID_COOKIE, xandrId) + } + }) + } + setConfig('xandrId', storedXandrId) + return isValidXandrId ? storedXandrId : null +} + +export function getRemoteXandrId() { + if (!isClient) return Promise.resolve(null) + + return window + .fetch(XANDR_ID_SERVER_URL, { + credentials: 'include' + }) + .then(response => response.json()) + .then(json => json?.uid) +} diff --git a/packages/sui-segment-wrapper/test/checkAnonymousIdSpec.js b/packages/sui-segment-wrapper/test/checkAnonymousIdSpec.js new file mode 100644 index 000000000..dd8b3474b --- /dev/null +++ b/packages/sui-segment-wrapper/test/checkAnonymousIdSpec.js @@ -0,0 +1,43 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {checkAnonymousId} from '../src/checkAnonymousId.js' + +describe('checkAnonymousId', () => { + let anonymousId + let setAnonymousId + + beforeEach(() => { + anonymousId = sinon.stub() + setAnonymousId = sinon.stub() + + window.analytics = { + user: () => ({ + anonymousId + }), + setAnonymousId + } + }) + + afterEach(() => { + window.analytics = undefined + }) + + it('should check anonymous id and not reset it when the value is not anonymous_user', () => { + anonymousId.returns('22564340c4-440a-4bbe-aef8-d9cwd6de1') + + checkAnonymousId() + + expect(anonymousId.callCount).to.equal(1) + expect(setAnonymousId.callCount).to.equal(0) + }) + + it('should check anonymous id and reset it when the value is anonymous_user', () => { + anonymousId.returns('anonymous_user') + + checkAnonymousId() + + expect(anonymousId.callCount).to.equal(1) + expect(setAnonymousId.calledWith(null)).to.be.true + }) +}) diff --git a/packages/sui-segment-wrapper/test/middlewares/defaultContextPropertiesMiddlewareSpec.js b/packages/sui-segment-wrapper/test/middlewares/defaultContextPropertiesMiddlewareSpec.js new file mode 100644 index 000000000..ace8a45db --- /dev/null +++ b/packages/sui-segment-wrapper/test/middlewares/defaultContextPropertiesMiddlewareSpec.js @@ -0,0 +1,46 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {setConfig} from '../../src/config.js' +import {defaultContextProperties} from '../../src/middlewares/source/defaultContextProperties' + +describe('defaultContextPropertiesMiddleware', () => { + const fakePayloadFactory = () => ({ + obj: { + properties: {}, + context: {} + } + }) + + beforeEach(() => { + setConfig('defaultContext', undefined) + }) + + it('should add the context default properties if defined', () => { + const payload = fakePayloadFactory() + setConfig('defaultContext', { + site: 'fotocasa' + }) + + const spy = sinon.spy() + defaultContextProperties({payload, next: spy}) + const [{obj}] = spy.args[0] + + expect(obj.context).to.deep.equal({ + platform: 'web', + site: 'fotocasa' + }) + }) + + it('should add the context default properties if config not defined', () => { + const payload = fakePayloadFactory() + + const spy = sinon.spy() + defaultContextProperties({payload, next: spy}) + const [{obj}] = spy.args[0] + + expect(obj.context).to.deep.equal({ + platform: 'web' + }) + }) +}) diff --git a/packages/sui-segment-wrapper/test/middlewares/optimizelyMiddlewaresIntegrationSpec.js b/packages/sui-segment-wrapper/test/middlewares/optimizelyMiddlewaresIntegrationSpec.js new file mode 100644 index 000000000..806baff5d --- /dev/null +++ b/packages/sui-segment-wrapper/test/middlewares/optimizelyMiddlewaresIntegrationSpec.js @@ -0,0 +1,154 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {optimizelySiteAttributeMiddleware} from '../../src/middlewares/destination/optimizelySiteAttribute.js' +import {optimizelyUserId as optimizelyUserIdMiddleware} from '../../src/middlewares/source/optimizelyUserId.js' + +describe('optimizely middlewares integration', () => { + beforeEach(() => { + window.analytics = {} + window.analytics.user = () => ({ + anonymousId: () => 'anonymousId' + }) + }) + + describe('when no attribute param has been send through', () => { + let initialPayload, finalPayload + beforeEach(() => { + initialPayload = { + obj: { + context: { + site: 'fakesite.fake' + }, + integrations: { + 'Adobe Analytics': { + mcvid: 'fakeMcvid' + } + } + } + } + + finalPayload = { + obj: { + context: { + site: 'fakesite.fake' + }, + integrations: { + Optimizely: { + userId: 'anonymousId', + attributes: { + site: 'fakesite.fake' + } + }, + 'Adobe Analytics': { + mcvid: 'fakeMcvid' + } + } + } + } + }) + + afterEach(() => { + initialPayload = {} + finalPayload = {} + }) + + it('should add the userId and attributes to the optimizely integration', () => { + const nextSpy = sinon.spy() + + optimizelyUserIdMiddleware({payload: initialPayload, next: nextSpy}) + optimizelySiteAttributeMiddleware({ + payload: nextSpy.getCall(0).args[0], + next: nextSpy + }) + expect(nextSpy.getCall(1).args[0]).to.deep.equal(finalPayload) + }) + + it('should work even if changing the middleware execution order', () => { + const nextSpy = sinon.spy() + + optimizelySiteAttributeMiddleware({ + payload: initialPayload, + next: nextSpy + }) + optimizelyUserIdMiddleware({ + payload: nextSpy.getCall(0).args[0], + next: nextSpy + }) + expect(nextSpy.getCall(1).args[0]).to.deep.equal(finalPayload) + }) + }) + + describe('when attribute param has been send through', () => { + let initialPayload, finalPayload + beforeEach(() => { + initialPayload = { + obj: { + context: { + site: 'fakesite.fake' + }, + integrations: { + 'Adobe Analytics': { + mcvid: 'fakeMcvid' + }, + Optimizely: { + attributes: { + myAttribute: 'attributeValue' + } + } + } + } + } + + finalPayload = { + obj: { + context: { + site: 'fakesite.fake' + }, + integrations: { + 'Adobe Analytics': { + mcvid: 'fakeMcvid' + }, + Optimizely: { + attributes: { + site: 'fakesite.fake', + myAttribute: 'attributeValue' + }, + userId: 'anonymousId' + } + } + } + } + }) + + afterEach(() => { + initialPayload = {} + finalPayload = {} + }) + + it('should add the userId and attributes to the optimizely integration', () => { + const nextSpy = sinon.spy() + + optimizelyUserIdMiddleware({payload: initialPayload, next: nextSpy}) + optimizelySiteAttributeMiddleware({ + payload: nextSpy.getCall(0).args[0], + next: nextSpy + }) + expect(nextSpy.getCall(1).args[0]).to.deep.equal(finalPayload) + }) + + it('should work even if changing the middleware execution order', () => { + const nextSpy = sinon.spy() + + optimizelySiteAttributeMiddleware({ + payload: initialPayload, + next: nextSpy + }) + optimizelyUserIdMiddleware({ + payload: nextSpy.getCall(0).args[0], + next: nextSpy + }) + expect(nextSpy.getCall(1).args[0]).to.deep.equal(finalPayload) + }) + }) +}) diff --git a/packages/sui-segment-wrapper/test/middlewares/optimizelySiteAttributeSpec.js b/packages/sui-segment-wrapper/test/middlewares/optimizelySiteAttributeSpec.js new file mode 100644 index 000000000..f70a96a22 --- /dev/null +++ b/packages/sui-segment-wrapper/test/middlewares/optimizelySiteAttributeSpec.js @@ -0,0 +1,47 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {optimizelySiteAttributeMiddleware} from '../../src/middlewares/destination/optimizelySiteAttribute' + +describe('#optimizelySiteAttributeMiddleware', () => { + it('should add optimizelys site attribute from segments context', () => { + const payload = { + obj: { + context: { + site: 'fakesite.fake' + }, + integrations: { + Optimizely: { + userId: 'fakeUserId' + } + } + } + } + + const spy = sinon.spy() + optimizelySiteAttributeMiddleware({payload, next: spy}) + expect(spy.args[0][0].obj.integrations.Optimizely).to.deep.equal({ + userId: 'fakeUserId', + attributes: {site: 'fakesite.fake'} + }) + }) + + it('when no site context is there it should not add anything', () => { + const payload = { + obj: { + context: {}, + integrations: { + Optimizely: { + userId: 'fakeUserId' + } + } + } + } + + const spy = sinon.spy() + optimizelySiteAttributeMiddleware({payload, next: spy}) + expect(spy.args[0][0].obj.integrations.Optimizely).to.deep.equal({ + userId: 'fakeUserId' + }) + }) +}) diff --git a/packages/sui-segment-wrapper/test/middlewares/optimizelyUserIdMiddlewareSpec.js b/packages/sui-segment-wrapper/test/middlewares/optimizelyUserIdMiddlewareSpec.js new file mode 100644 index 000000000..aa33eea2f --- /dev/null +++ b/packages/sui-segment-wrapper/test/middlewares/optimizelyUserIdMiddlewareSpec.js @@ -0,0 +1,48 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {optimizelyUserId} from '../../src/middlewares/source/optimizelyUserId' + +describe('#optimizelyUserIdMiddleware', () => { + const fakePayloadFactory = () => ({ + obj: { + properties: {}, + integrations: {} + } + }) + + beforeEach(() => { + window.analytics = { + user: () => ({ + anonymousId: () => 'anonymousId' + }) + } + }) + + afterEach(() => { + window.analytics = null + delete window.analytics + }) + + it('should add the userId integration', () => { + const payload = fakePayloadFactory() + + const spy = sinon.spy() + optimizelyUserId({payload, next: spy}) + expect(spy.args[0][0].obj.integrations.Optimizely.userId).to.equal('anonymousId') + }) + + it('when user hasnt given its consents it shouldnt add the optimizely integration', () => { + const payload = { + ...fakePayloadFactory(), + obj: { + integrations: { + All: false + } + } + } + const spy = sinon.spy() + optimizelyUserId({payload, next: spy}) + expect(spy.args[0][0].obj.integrations.Optimizely).to.be.undefined + }) +}) diff --git a/packages/sui-segment-wrapper/test/middlewares/pageReferrerMiddlewareSpec.js b/packages/sui-segment-wrapper/test/middlewares/pageReferrerMiddlewareSpec.js new file mode 100644 index 000000000..a72a092bc --- /dev/null +++ b/packages/sui-segment-wrapper/test/middlewares/pageReferrerMiddlewareSpec.js @@ -0,0 +1,42 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {pageReferrer} from '../../src/middlewares/source/pageReferrer.js' +import {resetReferrerState, stubActualLocation, stubReferrer} from '../stubs.js' + +describe('#pageReferrerMiddleware', () => { + let referrerStub, locationStub + + const fakePayload = ({isPageTrack}) => ({ + obj: { + context: { + isPageTrack + } + } + }) + + beforeEach(() => { + resetReferrerState() + }) + + afterEach(() => { + referrerStub.restore() + locationStub.restore() + }) + + it('should add correct referrer to context when isPageTrack is true', () => { + const firstReferrer = 'https://external-page.com' + const initialInternalLocation = 'https://internal-page.com/search' + + locationStub = stubActualLocation(initialInternalLocation) + referrerStub = stubReferrer(firstReferrer, locationStub) + + const spy = sinon.spy() + pageReferrer({payload: fakePayload({isPageTrack: true}), next: spy}) + pageReferrer({payload: fakePayload({isPageTrack: true}), next: spy}) + + expect(spy.firstCall.firstArg.obj.context.page.referrer).to.equal(firstReferrer) + + expect(spy.secondCall.firstArg.obj.context.page.referrer).to.equal(initialInternalLocation) + }) +}) diff --git a/packages/sui-segment-wrapper/test/middlewares/userScreenInfoMiddlewareSpec.js b/packages/sui-segment-wrapper/test/middlewares/userScreenInfoMiddlewareSpec.js new file mode 100644 index 000000000..07b60a5ef --- /dev/null +++ b/packages/sui-segment-wrapper/test/middlewares/userScreenInfoMiddlewareSpec.js @@ -0,0 +1,38 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {userScreenInfo} from '../../src/middlewares/source/userScreenInfo.js' + +describe('userScreenInfoMiddleware', () => { + const fakePayload = () => ({ + obj: { + properties: {} + } + }) + + const fakeWindowScreen = ({width, height, pixelRatio}) => { + window.innerWidth = width + window.innerHeight = height + window.devicePixelRatio = pixelRatio + } + + it('should add the user screen info', () => { + fakeWindowScreen({ + width: 1920, + height: 800, + pixelRatio: 2 + }) + + const spy = sinon.spy() + userScreenInfo({payload: fakePayload(), next: spy}) + const [{obj}] = spy.args[0] + + expect(obj.context).to.deep.equal({ + screen: { + width: 1920, + height: 800, + density: 2 + } + }) + }) +}) diff --git a/packages/sui-segment-wrapper/test/segmentWrapperSpec.js b/packages/sui-segment-wrapper/test/segmentWrapperSpec.js new file mode 100644 index 000000000..4f5ac6e1a --- /dev/null +++ b/packages/sui-segment-wrapper/test/segmentWrapperSpec.js @@ -0,0 +1,732 @@ +import {expect} from 'chai' +import sinon from 'sinon' + +import {getAdobeVisitorData} from '../src/adobeRepository.js' +import {setConfig} from '../src/config.js' +import suiAnalytics from '../src/index.js' +import {defaultContextProperties} from '../src/middlewares/source/defaultContextProperties.js' +import {pageReferrer} from '../src/middlewares/source/pageReferrer.js' +import {userScreenInfo} from '../src/middlewares/source/userScreenInfo.js' +import {userTraits} from '../src/middlewares/source/userTraits.js' +import {INTEGRATIONS_WHEN_NO_CONSENTS} from '../src/segmentWrapper.js' +import initTcfTracking, {getGdprPrivacyValue, USER_GDPR} from '../src/tcf.js' +import { + cleanWindowStubs, + resetReferrerState, + stubActualLocation, + stubDocumentCookie, + stubReferrer, + stubTcfApi, + stubWindowObjects +} from './stubs.js' +import { + simulateUserAcceptAdvertisingConsents, + simulateUserAcceptAnalyticsConsents, + simulateUserAcceptConsents, + simulateUserDeclinedConsents +} from './tcf.js' +import {getDataFromLastTrack, waitUntil} from './utils.js' + +const ACCEPTED_BOROS_COOKIE_VALUE = + 'eyJwb2xpY3lWZXJzaW9uIjoyLCJjbXBWZXJzaW9uIjoxNSwicHVycG9zZSI6eyJjb25zZW50cyI6eyIxIjp0cnVlLCIyIjp0cnVlLCIzIjp0cnVlLCI0Ijp0cnVlLCI1Ijp0cnVlLCI2Ijp0cnVlLCI3Ijp0cnVlLCI4Ijp0cnVlLCI5Ijp0cnVlLCIxMCI6dHJ1ZX19LCJzcGVjaWFsRmVhdHVyZXMiOnsiMSI6dHJ1ZX19;' +const DECLINED_BOROS_COOKIE_VALUE = + 'eyJwb2xpY3lWZXJzaW9uIjoyLCJjbXBWZXJzaW9uIjoxNSwicHVycG9zZSI6eyJjb25zZW50cyI6eyIxIjpmYWxzZSwiMiI6ZmFsc2UsIjMiOmZhbHNlLCI0IjpmYWxzZSwiNSI6ZmFsc2UsIjYiOmZhbHNlLCI3IjpmYWxzZSwiOCI6ZmFsc2UsIjkiOmZhbHNlLCIxMCI6ZmFsc2V9fSwic3BlY2lhbEZlYXR1cmVzIjp7IjEiOmZhbHNlLCIyIjpmYWxzZX19;' + +describe('Segment Wrapper', function () { + this.timeout(16000) + + beforeEach(() => { + stubWindowObjects() + + window.__SEGMENT_WRAPPER = window.__SEGMENT_WRAPPER || {} + window.__SEGMENT_WRAPPER.ADOBE_ORG_ID = '012345678@AdobeOrg' + window.__SEGMENT_WRAPPER.TRACKING_SERVER = 'mycompany.test.net' + + window.analytics.addSourceMiddleware(userTraits) + window.analytics.addSourceMiddleware(defaultContextProperties) + window.analytics.addSourceMiddleware(userScreenInfo) + window.analytics.addSourceMiddleware(pageReferrer) + }) + + afterEach(() => cleanWindowStubs()) + + describe('should use correct page referrer for tracks', () => { + let referrerStub, locationStub + + beforeEach(() => { + resetReferrerState() + }) + + afterEach(() => { + referrerStub.restore() + locationStub.restore() + }) + + it('by waiting consents of the user and send correct referrers at once', async function () { + try { + const firstReferrer = 'https://external-page.com' + const initialInternalLocation = 'https://internal-page.com/search' + + locationStub = stubActualLocation(initialInternalLocation) + referrerStub = stubReferrer(firstReferrer, locationStub) + + const spy = sinon.stub() + + const trackBeforeConsents = Promise.all([ + suiAnalytics.page('Home Page', undefined, undefined, spy), + suiAnalytics.track('First Track', undefined, undefined, spy) + ]) + + await simulateUserAcceptConsents() + + await trackBeforeConsents + + await suiAnalytics.page('Search Page', undefined, undefined, spy) + await suiAnalytics.track('Second Track on Search Page', undefined, undefined, spy) + + expect(spy.callCount).to.equal(4) + + const {context: firstContext} = spy.getCall(0).firstArg.obj + const {context: secondPageContext} = spy.getCall(2).firstArg.obj + const {context: lastContext} = spy.getCall(3).firstArg.obj + + expect(firstContext.page.referrer).to.equal(firstReferrer) + expect(secondPageContext.page.referrer).to.equal(initialInternalLocation) + expect(lastContext.page.referrer).to.equal(initialInternalLocation) + } catch (e) { + console.error(e) // eslint-disable-line + } + }) + + it('without calling page event document.referrer should be used for tracks', async function () { + await simulateUserAcceptConsents() + + const firstReferrer = 'https://external-page.com' + const initialInternalLocation = 'https://internal-page.com/another' + + locationStub = stubActualLocation(initialInternalLocation) + referrerStub = stubReferrer(firstReferrer, locationStub) + + const spy = sinon.stub() + await suiAnalytics.track('First Track', undefined, undefined, spy) + + const {context: firstContext} = spy.firstCall.firstArg.obj + + expect(firstContext.page.referrer).to.equal(firstReferrer) + }) + + it('after calling page event more than once and the referrer is external', async function () { + const firstReferrer = 'https://external-page.com' + const initialInternalLocation = 'https://internal-page.com/first' + + locationStub = stubActualLocation(initialInternalLocation) + referrerStub = stubReferrer(firstReferrer, locationStub) + + const spy = sinon.stub() + + await suiAnalytics.page('Home Page', undefined, undefined, spy) + const {context: firstPageContext} = spy.lastCall.firstArg.obj + + await suiAnalytics.track('First Track', undefined, undefined, spy) + const {context: firstPageFirstTrackContext} = spy.lastCall.firstArg.obj + + await suiAnalytics.track('Second Track', undefined, undefined, spy) + const {context: firstPageSecondTrackContext} = spy.lastCall.firstArg.obj + + expect(firstPageContext.page.referrer).to.equal(firstReferrer) + expect(firstPageFirstTrackContext.page.referrer).to.equal(firstReferrer) + expect(firstPageSecondTrackContext.page.referrer).to.equal(firstReferrer) + + await suiAnalytics.page('Second Page', undefined, undefined, spy) + const {context: secondPageContext} = spy.lastCall.firstArg.obj + + await suiAnalytics.track('First Track on Second Page', undefined, undefined, spy) + const {context: secondTrackContext} = spy.lastCall.firstArg.obj + + expect(secondPageContext.page.referrer).to.equal(initialInternalLocation) + expect(secondTrackContext.page.referrer).to.equal(initialInternalLocation) + }) + + it('after calling page event more than once and not referrer set', async function () { + const firstReferrer = '' + const initialInternalLocation = 'https://internal-page.com/another' + + locationStub = stubActualLocation(initialInternalLocation) + referrerStub = stubReferrer(firstReferrer, locationStub) + + const spy = sinon.stub() + + await suiAnalytics.page('Home Page', undefined, undefined, spy) + const {context: firstPageContext} = spy.lastCall.firstArg.obj + + await suiAnalytics.track('First Track', undefined, undefined, spy) + const {context: firstTrackContext} = spy.lastCall.firstArg.obj + + expect(firstPageContext.page.referrer).to.equal(firstReferrer) + expect(firstTrackContext.page.referrer).to.equal(firstReferrer) + + await suiAnalytics.page('Second Page', undefined, undefined, spy) + const {context: secondPageContext} = spy.lastCall.firstArg.obj + + await suiAnalytics.track('First Track', undefined, undefined, spy) + const {context: secondTrackContext} = spy.lastCall.firstArg.obj + + expect(secondPageContext.page.referrer).to.equal(initialInternalLocation) + expect(secondTrackContext.page.referrer).to.equal(initialInternalLocation) + }) + }) + + describe('when the track event is called', () => { + it('should add anonymousId as options trait', async function () { + await simulateUserAcceptConsents() + + const spy = sinon.stub() + + await suiAnalytics.track('fakeEvent', {}, {}, spy) + const {context} = spy.firstCall.firstArg.obj + + expect(context.traits.anonymousId).to.deep.equal('fakeAnonymousId') + }) + + it('should send MarketingCloudId on Adobe Analytics integration', async () => { + await simulateUserAcceptAnalyticsConsents() + + window.Visitor = {} + window.Visitor.getInstance = sinon.stub().returns({ + getMarketingCloudVisitorID: sinon.stub().returns('fakeCloudId') + }) + + await suiAnalytics.track( + 'fakeEvent', + {}, + { + integrations: {fakeIntegrationKey: 'fakeIntegrationValue'} + } + ) + + const {context} = getDataFromLastTrack() + + expect(context.integrations).to.deep.includes({ + fakeIntegrationKey: 'fakeIntegrationValue', + 'Adobe Analytics': { + marketingCloudVisitorId: 'fakeCloudId' + } + }) + }) + + it('should add always the platform as web and the language', async () => { + await suiAnalytics.track('fakeEvent', {fakePropKey: 'fakePropValue'}) + const {properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + fakePropKey: 'fakePropValue', + platform: 'web' + }) + }) + + it('should send defaultProperties if provided', async () => { + setConfig('defaultProperties', {site: 'midudev', vertical: 'blog'}) + + await suiAnalytics.track('fakeEvent') + + const {properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + site: 'midudev', + vertical: 'blog', + platform: 'web' + }) + }) + + describe('TCF is handled', () => { + it('should reset the anonymousId when the user first declines and then accepts', async () => { + await simulateUserDeclinedConsents() + + const spy = sinon.stub() + + await suiAnalytics.track('fakeEvent', {}, {}, spy) + + expect(spy.firstCall.firstArg.obj.context.traits.anonymousId).to.equal('fakeAnonymousId') + + await simulateUserAcceptAnalyticsConsents() + const spySecondCall = sinon.stub() + + await suiAnalytics.track('fakeEvent', {}, {}, spySecondCall) + + expect(spySecondCall.firstCall.firstArg.obj.context.traits.anonymousId).to.equal('fakeAnonymousId') + }) + }) + }) + + describe('when the identify event is called', () => { + const DEFAULT_SEGMENT_CALLBACK_TIMEOUT = 350 + it('should call sdk identify of users that accepts consents', async function () { + await simulateUserAcceptConsents() + + const spy = sinon.stub() + + await suiAnalytics.identify('fakeEvent', {}, {}, spy) + await waitUntil(() => spy.callCount, { + timeout: DEFAULT_SEGMENT_CALLBACK_TIMEOUT + }) + expect(spy.callCount).to.equal(1) + }) + + it('should call sdk identify of user not accepts consents', async function () { + await simulateUserDeclinedConsents() + + const spy = sinon.stub() + + await suiAnalytics.identify('fakeEvent', {}, {}, spy) + await waitUntil(() => spy.callCount, { + timeout: DEFAULT_SEGMENT_CALLBACK_TIMEOUT + }).catch(() => null) + expect(spy.callCount).to.equal(1) + }) + }) + + describe('when TCF is present on the page', () => { + it('should track that CMP user action when declined tracking purposes', async () => { + await simulateUserDeclinedConsents() + + const {context, properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web' + }) + expect(context).to.deep.include({ + gdpr_privacy: 'declined', + gdpr_privacy_advertising: 'declined' + }) + }) + + it('should track that CMP user action when accepted tracking purposes', async () => { + await simulateUserAcceptAnalyticsConsents() + const {context, properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web' + }) + + expect(context).to.deep.include({ + gdpr_privacy: 'accepted', + gdpr_privacy_advertising: 'declined' + }) + }) + + it('should track that CMP user action when accepted advertising purposes', async () => { + await simulateUserAcceptAdvertisingConsents() + + const {context, properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web' + }) + expect(context).to.deep.include({ + gdpr_privacy: 'declined', + gdpr_privacy_advertising: 'accepted' + }) + }) + + it('should track that CMP user action when reject all purposes', async () => { + await simulateUserDeclinedConsents() + + const {context, properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web' + }) + + expect(context).to.deep.include({ + gdpr_privacy: 'declined', + gdpr_privacy_advertising: 'declined' + }) + }) + + it('should set integrations all to true when reject all purposes', async () => { + await simulateUserDeclinedConsents() + + const {context} = getDataFromLastTrack() + + expect(context.integrations).to.include({ + All: true + }) + }) + + it('should set integrations all to true when accept all purposes', async () => { + await simulateUserAcceptConsents() + + const {context} = getDataFromLastTrack() + + expect(context.integrations).to.include({ + All: true + }) + }) + + it('should track that CMP user action when accept all purposes', async () => { + await simulateUserAcceptConsents() + + const {context, properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web' + }) + + expect(context).to.deep.include({ + gdpr_privacy: 'accepted', + gdpr_privacy_advertising: 'accepted' + }) + }) + + it('should send the correct gdpr_privacy field on site events after accepting CMP', async () => { + await simulateUserAcceptAnalyticsConsents() + + await suiAnalytics.track('fakeEvent', {fakePropKey: 'fakePropValue'}) + const {context, properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + fakePropKey: 'fakePropValue', + platform: 'web' + }) + + expect(context).to.deep.include({ + gdpr_privacy: 'accepted' + }) + }) + + it('should disable integrations if consents are declined', async () => { + await simulateUserDeclinedConsents() + + await suiAnalytics.track('fakeEvent', {fakePropKey: 'fakePropValue'}) + const {context} = getDataFromLastTrack() + + expect(context.integrations).to.deep.include(INTEGRATIONS_WHEN_NO_CONSENTS) + }) + + describe('for recurrent users', () => { + let cookiesStub + + beforeEach(() => setConfig('initialized', false)) + afterEach(() => cookiesStub.restore()) + + it('read its tcf cookie with accepted purposes', async () => { + cookiesStub = stubDocumentCookie(`borosTcf=${ACCEPTED_BOROS_COOKIE_VALUE}`) + + initTcfTracking() + + const gdprPrivacyValue = await getGdprPrivacyValue() + + expect(gdprPrivacyValue.analytics).to.equal(USER_GDPR.ACCEPTED) + expect(gdprPrivacyValue.advertising).to.equal(USER_GDPR.ACCEPTED) + }) + + it('read its tcf cookie with declined purposes', async function () { + cookiesStub = stubDocumentCookie(`borosTcf=${DECLINED_BOROS_COOKIE_VALUE}`) + + initTcfTracking() + + const gdprPrivacyValue = await getGdprPrivacyValue() + + expect(gdprPrivacyValue.analytics).to.equal(USER_GDPR.DECLINED) + expect(gdprPrivacyValue.advertising).to.equal(USER_GDPR.DECLINED) + }) + }) + }) + + describe('when the MarketingCloudVisitorId is loaded via callback', () => { + before(() => { + stubWindowObjects() + + window.__mpi = { + segmentWrapper: {} + } + window.__mpi.segmentWrapper.getCustomAdobeVisitorId = () => Promise.resolve('myCustomCloudVisitorId') + }) + + it('should use the visitor id resolved by the defined async callback function', async () => { + await simulateUserAcceptAnalyticsConsents() // simulate already fire an analytics.track + + const {context} = getDataFromLastTrack() + + expect(context.integrations).to.deep.include({ + 'Adobe Analytics': { + marketingCloudVisitorId: 'myCustomCloudVisitorId' + } + }) + }) + }) + + describe('when the importAdobeVisitorId config is set', () => { + before(() => { + setConfig('importAdobeVisitorId', true) + }) + + it('should import local Visitor Api version and create a MarketingCloudVisitorId on consents accepted', async () => { + await simulateUserAcceptAnalyticsConsents() // simulate already fire an analytics.track + + const {version} = await getAdobeVisitorData() + const {context} = getDataFromLastTrack() + + expect(version).to.equal('4.6.0') + expect(context.integrations['Adobe Analytics'].marketingCloudVisitorId).to.be.a('string') + }) + + it('should define Adobe Analytics as true in integrations', async () => { + await simulateUserDeclinedConsents() // simulate already fire an analytics.track + + const {context} = getDataFromLastTrack() + + expect(context.integrations['Adobe Analytics']).to.be.true + }) + }) + + describe('when tcfTrackDefaultProperties config is set', () => { + beforeEach(() => { + stubWindowObjects() + // Adding a custom property to later check this is added for every next track + setConfig('tcfTrackDefaultProperties', {vertical: 'fakeVertical'}) + }) + + it('should add the defined custom property to the track event when CONSENTS are ACCEPTED', async () => { + await simulateUserAcceptAnalyticsConsents() + + const {properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web', + vertical: 'fakeVertical' + }) + }) + it('should add the defined custom property to the track event when CONSENTS are DECLINED', async () => { + await simulateUserDeclinedConsents() + + const {properties} = getDataFromLastTrack() + + expect(properties).to.deep.equal({ + channel: 'GDPR', + platform: 'web', + vertical: 'fakeVertical' + }) + }) + }) + + describe('context integrations', () => { + before(() => { + stubWindowObjects() + + window.__mpi = { + segmentWrapper: {} + } + window.__mpi.segmentWrapper.getCustomAdobeVisitorId = () => Promise.resolve('myCustomCloudVisitorId') + }) + + it('sends an event with the actual context and traits when the consents are declined', async () => { + const spy = sinon.stub() + + await simulateUserDeclinedConsents() + + await suiAnalytics.track( + 'fakeEvent', + {fakePropKey: 'fakePropValue'}, + { + anonymousId: '1a3bfbfc-9a89-437a-8f1c-87d786f2b6a', + userId: 'fakeId', + integrations: { + 'Midu Analytics': true + }, + protocols: { + event_version: 3 + } + }, + spy + ) + + const {context} = getDataFromLastTrack() + const integrations = { + 'Midu Analytics': true, + All: false, + 'Adobe Analytics': true, + Personas: false, + Webhooks: true, + Webhook: true + } + + const expectation = { + anonymousId: '1a3bfbfc-9a89-437a-8f1c-87d786f2b6a', + integrations, + ip: '0.0.0.0', + userId: 'fakeId', + protocols: {event_version: 3}, + gdpr_privacy: 'declined', + gdpr_privacy_advertising: 'declined', + context: { + integrations + } + } + + const {traits} = spy.getCall(0).firstArg.obj.context + + expect(context).to.deep.equal(expectation) + expect(traits).to.deep.equal({ + anonymousId: 'fakeAnonymousId', + userId: 'fakeId' + }) + }) + }) + + describe('isFirstVisit flag', () => { + let cookiesStub + + afterEach(() => cookiesStub?.restore()) + + describe('when the user hasnt interacted with the tcf modal', () => { + beforeEach(() => { + initTcfTracking() + }) + + it('should flag the visit as first visit', async () => { + expect(window.__mpi.segmentWrapper.isFirstVisit).to.equal(true) + }) + }) + + describe('when the user did accept the cookies before', () => { + beforeEach(() => { + cookiesStub = stubDocumentCookie(`borosTcf=${ACCEPTED_BOROS_COOKIE_VALUE}`) + initTcfTracking() + }) + + it('shouldnt flag the visit as first visit', async () => { + expect(window.__mpi.segmentWrapper.isFirstVisit).to.equal(false) + }) + }) + + describe('when the user did decline the cookies before', () => { + beforeEach(() => { + cookiesStub = stubDocumentCookie(`borosTcf=${DECLINED_BOROS_COOKIE_VALUE}`) + initTcfTracking() + }) + + it('shouldnt flag the visit as first visit', async () => { + expect(window.__mpi.segmentWrapper.isFirstVisit).to.equal(false) + }) + }) + }) + + describe.skip('User Email to Visitor API', () => { + let spySetCustomerIDs + + before(() => { + stubWindowObjects() + + spySetCustomerIDs = sinon.spy() + window.Visitor = {} + window.Visitor.getInstance = sinon.stub().returns({ + getMarketingCloudVisitorID: sinon.stub().returns('fakeCloudId'), + setCustomerIDs: spySetCustomerIDs + }) + }) + + it('is sent when userEmail config is set and CONSENTS are ACCEPTED', function (done) { + setConfig('userEmail', 'USER.name+go@gmail.com') // this should be like 'username@gmail.com' + + const actionPromise = stubTcfApi() + + initTcfTracking() + + actionPromise.then(() => { + const intervalId = setInterval(() => { + if (spySetCustomerIDs.callCount === 1) { + const [ + { + hashed_email: {authState, id} + } + ] = spySetCustomerIDs.getCall(0).args + expect(authState).to.equal(1) + expect(id).to.equal('761cd16b141770ecb0bbb8a4e5962d16') + clearInterval(intervalId) + done() + } + }, 500) + }) + }) + + it('when hashedUserEmail config property is set', function (done) { + setConfig('hashedUserEmail', '761cd16b141770ecb0bbb8a4e5962d16') // this should be like 'username@gmail.com' but hashed + + const actionPromise = stubTcfApi() + + initTcfTracking() + + actionPromise.then(() => { + const intervalId = setInterval(() => { + if (spySetCustomerIDs.callCount === 1) { + const [ + { + hashed_email: {authState, id} + } + ] = spySetCustomerIDs.getCall(0).args + expect(authState).to.equal(1) + expect(id).to.equal('761cd16b141770ecb0bbb8a4e5962d16') + clearInterval(intervalId) + done() + } + }, 500) + }) + }) + }) + + describe('xandr id to externalId', () => { + const XANDR_ID_COOKIE = 'adit-xandr-id' + + const givenXandrId = '9999' + beforeEach(() => { + stubDocumentCookie(`${XANDR_ID_COOKIE}=${givenXandrId}`) + }) + + it('should send the xandrId as externalId, that where stored in a cookie', async () => { + await simulateUserAcceptConsents() + + await suiAnalytics.track('fakeEvent') + + const { + context: {externalIds} + } = getDataFromLastTrack() + + expect(externalIds).to.be.instanceOf(Array) + expect(externalIds[0]).to.be.deep.equal({ + id: givenXandrId, + type: 'xandr_id', + encoding: 'none', + collection: 'users' + }) + }) + + it('should not send the xandrId if user not consented advertising', async () => { + await simulateUserAcceptAnalyticsConsents() + await suiAnalytics.track('fakeEvent') + + const { + context: {externalIds} + } = getDataFromLastTrack() + expect(externalIds).to.be.undefined + }) + + it('should not send the xandrId if configuration sendXandrId flag is false', async () => { + setConfig('sendXandrId', false) + stubDocumentCookie(`${XANDR_ID_COOKIE}=${givenXandrId}`) + await simulateUserAcceptAnalyticsConsents() + await suiAnalytics.track('fakeEvent') + + const { + context: {externalIds} + } = getDataFromLastTrack() + expect(externalIds).to.be.undefined + }) + }) +}) diff --git a/packages/sui-segment-wrapper/test/stubs.js b/packages/sui-segment-wrapper/test/stubs.js new file mode 100644 index 000000000..0d6d88b7c --- /dev/null +++ b/packages/sui-segment-wrapper/test/stubs.js @@ -0,0 +1,175 @@ +import sinon from 'sinon' + +import {referrerState, updatePageReferrer, utils as referrerUtils} from '../src/middlewares/source/pageReferrer.js' + +const IDENTIFY_TIMEOUT = 300 // from https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#identify + +export const cleanWindowStubs = () => { + delete window.analytics + delete window.Visitor + delete window.__borosTcf + delete window.__mpi + delete window.__tcfapi +} + +export const stubTcfApi = ({success = true, eventStatus = 'cmpuishown', consents = {}} = {}) => { + return new Promise(resolve => { + // mock tcf global api + window.__tcfapi = (action, tcfApiVersion, handleAction) => { + resolve(handleAction({eventStatus, purpose: {consents}}, success)) + } + }) +} + +export const stubFetch = ({responses = [{urlRe: /^http/, fetchResponse: {}}]} = {}) => { + const createSuccessResponse = response => { + const res = new window.Response(JSON.stringify(response), { + status: 200, + headers: { + 'Content-type': 'application/json' + } + }) + + return Promise.resolve(res) + } + + const createFailResponse = () => Promise.reject(new Error('forced error')) + + return sinon.stub(window, 'fetch').callsFake(url => { + const responseMatch = responses.find(({urlRe}) => urlRe.test(url)) + return responseMatch ? createSuccessResponse(responseMatch.fetchResponse) : createFailResponse() + }) +} + +export const stubWindowObjects = ({borosMock = true, borosSuccess = true, isDmpAccepted = true} = {}) => { + stubTcfApi() + + const _middlewares = [] + + function executeMiddlewares(payload, middlewares, done) { + if (middlewares.length === 0) return done() + + const copyMiddlewares = middlewares.slice() + const middleware = copyMiddlewares.shift() + + middleware({ + payload, + integrations: [], + next: payload => { + executeMiddlewares(payload, copyMiddlewares, done) + } + }) + } + + // Segment Analytics Stub + window.analytics = { + // saved locally so it mantains updated values when + // executing test successively which changes the value + _testAnonymousId: 'fakeAnonymousId', + + _stubUser: () => { + if (window.analytics.user) return + // simulating segments script tag, user is set once the script is loaded + window.analytics.user = () => ({ + anonymousId: () => window.analytics._testAnonymousId, + id: () => 'fakeId', + traits: {email: 'john.wick@continental.net'} + }) + }, + + ready: cb => { + window.analytics._stubUser() + cb() + }, + reset: () => { + window.analytics._testAnonymousId = 'resetAnonymousId' + }, + setAnonymousId: id => { + window.analytics._testAnonymousId = id + }, + addSourceMiddleware: middleware => { + _middlewares.push(middleware) + }, + track: sinon.stub().callsFake((...args) => { + const [event, properties, context, fn] = args + const payload = { + obj: { + event, + properties, + context + } + } + + window.analytics._stubUser() + + executeMiddlewares(payload, _middlewares, () => { + fn(payload) + }) + }), + identify: sinon.fake((userId, traits, options, callback) => setTimeout(callback, IDENTIFY_TIMEOUT)) + } + + // Boros TCF Stub + + const mockBorosApi = (_, handle) => handle({success: borosSuccess, value: isDmpAccepted}) + + window.__borosTcf = borosMock + ? { + push: cb => { + setTimeout(cb(mockBorosApi), 200) + } + } + : undefined + + const storageMock = (function () { + let store = {} + return { + getItem: key => store[key], + setItem: function (key, value) { + store[key] = value.toString() + }, + removeItem: function (key) { + store[key] = undefined + }, + __clear: function () { + store = {} + } + } + })() + + Object.defineProperty(window, 'localStorage', { + value: storageMock + }) +} + +export const stubDocumentCookie = (value = '') => { + let cookies = value + document.__defineGetter__('cookie', () => cookies) + document.__defineSetter__('cookie', value => (cookies = `${cookies}; ${value}`)) + return {restore: () => (cookies = '')} +} + +export const resetReferrerState = () => { + referrerState.spaReferrer = '' + referrerState.referrer = '' +} + +export const stubActualLocation = location => sinon.stub(referrerUtils, 'getActualLocation').returns(location) + +export const stubActualQueryString = queryString => + sinon.stub(referrerUtils, 'getActualQueryString').returns(queryString) + +export const stubReferrer = (referrer, stubLocation) => { + const stubDocumentReferrer = sinon.stub(referrerUtils, 'getDocumentReferrer').returns(referrer) + + return { + restore: () => { + // we're using stubLocation to reset the state of `internalLocation` variable + stubLocation.returns('') + // we update the page referrer with an empty referrer to put all to an internal state + resetReferrerState() + updatePageReferrer() + stubDocumentReferrer.restore() + } + } +} diff --git a/packages/sui-segment-wrapper/test/tcf.js b/packages/sui-segment-wrapper/test/tcf.js new file mode 100644 index 000000000..d97e9a2fe --- /dev/null +++ b/packages/sui-segment-wrapper/test/tcf.js @@ -0,0 +1,39 @@ +import {setConfig} from '../src/config.js' +import initTcfTracking from '../src/tcf.js' +import {stubTcfApi} from './stubs.js' + +const simulateConsents = tcfEventObject => { + setConfig('initialized', false) + const simulateTcfApi = stubTcfApi(tcfEventObject) + initTcfTracking() + return simulateTcfApi +} + +export const simulateUserDeclinedConsents = () => + simulateConsents({ + eventStatus: 'useractioncomplete' + }) + +export const simulateUserDeclinedAnalyticsConsentsAndAcceptedAdvertisingConsents = () => + simulateConsents({ + eventStatus: 'useractioncomplete', + consents: {3: true} + }) + +export const simulateUserAcceptAnalyticsConsents = () => + simulateConsents({ + eventStatus: 'useractioncomplete', + consents: {1: true, 8: true, 10: true} + }) + +export const simulateUserAcceptAdvertisingConsents = () => + simulateConsents({ + eventStatus: 'useractioncomplete', + consents: {3: true} + }) + +export const simulateUserAcceptConsents = () => + simulateConsents({ + eventStatus: 'useractioncomplete', + consents: {1: true, 3: true, 8: true, 10: true} + }) diff --git a/packages/sui-segment-wrapper/test/universalIdSpec.js b/packages/sui-segment-wrapper/test/universalIdSpec.js new file mode 100644 index 000000000..c95a224cf --- /dev/null +++ b/packages/sui-segment-wrapper/test/universalIdSpec.js @@ -0,0 +1,73 @@ +import {expect} from 'chai' + +import {getConfig, setConfig} from '../src/config.js' +import {getUniversalId, getUserDataAndNotify} from '../src/universalId.js' +import {cleanWindowStubs} from './stubs.js' + +const UNIVERSAL_ID_EXAMPLE = '7ab9ddf3281d5d5458a29e8b3ae2864335087f1272d41ba440bee23d6acb911b' +const USER_EMAIL_EXAMPLE = 'miduga@gmail.com' +const USER_DATA_READY_EVENT = 'USER_DATA_READY' + +describe('Universal Id', () => { + describe('when universalId config is set', () => { + beforeEach(() => { + setConfig('universalId', UNIVERSAL_ID_EXAMPLE) + }) + + afterEach(() => { + cleanWindowStubs() + }) + + it('could be retrieved as expected from getUniversalId', () => { + const universalId = getUniversalId() + expect(universalId).to.equal(UNIVERSAL_ID_EXAMPLE) + }) + }) + + describe('when universalId config is NOT set but userEmail is set', () => { + beforeEach(() => { + setConfig('userEmail', USER_EMAIL_EXAMPLE) + }) + + afterEach(() => { + cleanWindowStubs() + }) + + it('could be retrieved as expected from method getUniversalId and set to window', () => { + const universalId = getUniversalId() + expect(universalId).to.equal(UNIVERSAL_ID_EXAMPLE) + + expect(window.__mpi.segmentWrapper.universalId).to.equal(UNIVERSAL_ID_EXAMPLE) + }) + + it('send an event with universalId and userEmail when getUserDataAndNotify is used', () => { + document.addEventListener(USER_DATA_READY_EVENT, e => { + expect(e.detail.universalId).to.equal(UNIVERSAL_ID_EXAMPLE) + expect(e.detail.userEmail).to.equal(USER_EMAIL_EXAMPLE) + }) + + const {universalId, userEmail} = getUserDataAndNotify() + expect(universalId).to.equal(UNIVERSAL_ID_EXAMPLE) + expect(userEmail).to.equal(USER_EMAIL_EXAMPLE) + + expect(window.__mpi.segmentWrapper.universalId).to.equal(UNIVERSAL_ID_EXAMPLE) + }) + }) + + describe('when universalId config is NOT set NEITHER userEmail', () => { + beforeEach(() => { + setConfig('userEmail', '') + }) + + afterEach(() => { + cleanWindowStubs() + }) + + it('should set universalIdInitialized config accordingly', () => { + const universalId = getUniversalId() + + expect(universalId).to.equal(undefined) + expect(getConfig('universalIdInitialized')).to.be.true + }) + }) +}) diff --git a/packages/sui-segment-wrapper/test/utils.js b/packages/sui-segment-wrapper/test/utils.js new file mode 100644 index 000000000..007982535 --- /dev/null +++ b/packages/sui-segment-wrapper/test/utils.js @@ -0,0 +1,42 @@ +/** + * Extract easily the properties and context used for the track + * @return {{properties: object, context: object}} + */ +export const getDataFromCall = call => { + const [, properties, context] = call.args + return {properties, context} +} + +/** + * Extract easily the last properties and context used for the track + * @return {{properties: object, context: object}} + */ +export const getDataFromLastTrack = () => getDataFromCall(window.analytics.track.lastCall) + +/** + * Waits until condition is true, or timeout + * + * @param {Function} condition + * @param {Object} options + * @param {String} options.message + * @param {Number} options.timeout + * @returns + */ +export const waitUntil = (condition = () => false, {message = TIMEOUT_MESSAGE, timeout = DEFAULT_TIMEOUT_MS} = {}) => + new Promise((resolve, reject) => { + const iid = setInterval(() => { + if (condition()) { + clearTimeout(tid) + clearInterval(iid) + resolve() + } + }, 5) + const tid = setTimeout(() => { + clearTimeout(tid) + clearInterval(iid) + reject(new Error(message)) + }, timeout) + }) + +const DEFAULT_TIMEOUT_MS = 50 +export const TIMEOUT_MESSAGE = 'wait timeout' diff --git a/packages/sui-segment-wrapper/test/xandrRepositorySpec.js b/packages/sui-segment-wrapper/test/xandrRepositorySpec.js new file mode 100644 index 000000000..b6271935f --- /dev/null +++ b/packages/sui-segment-wrapper/test/xandrRepositorySpec.js @@ -0,0 +1,62 @@ +import {expect} from 'chai' + +import {USER_GDPR} from '../src/tcf.js' +import {getXandrId} from '../src/xandrRepository.js' +import {stubDocumentCookie, stubFetch} from './stubs.js' +import {waitUntil} from './utils.js' + +describe('xandrRepository', () => { + const XANDR_ID_SERVER_URL = 'https://secure.adnxs.com/getuidj' + const XANDR_ID_COOKIE = 'adit-xandr-id' + + const givenXandrId = 'someXandrId' + let fetchStub + beforeEach(() => { + fetchStub = stubFetch({ + responses: [ + { + urlRe: /adnxs/, + fetchResponse: xandrResponse(givenXandrId) + } + ] + }) + }) + afterEach(() => { + fetchStub.restore() + }) + + const xandrResponse = xandrId => ({uid: xandrId}) + + const ACCEPTED_ADVERTISING_CONSENTS = USER_GDPR.ACCEPTED + const DECLINED_ADVERTISING_CONSENTS = USER_GDPR.DECLINED + + it('should send the xandrId as externalId stored in cookie', () => { + const givenXandrId = '9999' + stubDocumentCookie(`${XANDR_ID_COOKIE}=${givenXandrId}`) + expect(getXandrId({gdprPrivacyValueAdvertising: ACCEPTED_ADVERTISING_CONSENTS})).to.be.equal(givenXandrId) + }) + + it('should call xandr api and store the xandrId in the proper cookie', async () => { + stubDocumentCookie() + const xandrId = getXandrId({ + gdprPrivacyValueAdvertising: ACCEPTED_ADVERTISING_CONSENTS + }) + expect(xandrId).to.be.null + expect(fetchStub.firstCall.firstArg).to.be.equal(XANDR_ID_SERVER_URL) + await waitUntil(() => document.cookie) + expect(fetchStub.calledOnce).to.be.true + expect(document.cookie).to.include(`${XANDR_ID_COOKIE}=${givenXandrId}`) + }) + + it('should not call xandr api', async () => { + const cookieStub = stubDocumentCookie() + const xandrId = getXandrId({ + gdprPrivacyValueAdvertising: DECLINED_ADVERTISING_CONSENTS + }) + expect(xandrId).to.be.null + cookieStub.restore() + await waitUntil(() => false, {timeout: 20}).catch(() => {}) + expect(fetchStub.called).to.be.false + // expect(document.cookie).to.be.empty // would be nice to check this + }) +}) diff --git a/packages/sui-segment-wrapper/umd/index.js b/packages/sui-segment-wrapper/umd/index.js new file mode 100644 index 000000000..b744e366b --- /dev/null +++ b/packages/sui-segment-wrapper/umd/index.js @@ -0,0 +1,9 @@ +(()=>{var e,t,n={920:(e,t,n)=>{ +/** + * @license + * Adobe Visitor API for JavaScript version: 4.6.0 + * Copyright 2020 Adobe, Inc. All Rights Reserved + * More info available at https://marketing.adobe.com/resources/help/en_US/mcvid/ + */ +!function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t,n){var r=null==e?void 0:e[t];return void 0===r?n:r}function i(e,t){if(e===t)return 0;var n=e.toString().split("."),r=t.toString().split(".");return function(e){for(var t=/^\d+$/,n=0,r=e.length;ni)return 1;if(i>r)return-1}return 0}(n,r)):NaN}function o(e){this.name=this.constructor.name,this.message=e,"function"==typeof Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error(e).stack}function a(){function e(e,t){var n=ue(e);return n.length?n.every((function(e){return!!t[e]})):le(t)}function t(){A(S),D($.COMPLETE),h(g.status,g.permissions),p.set(g.permissions,{optInCookieDomain:c,optInStorageExpiry:u}),m.execute(De)}function n(e){return function(n,r){if(!de(n))throw new Error("[OptIn] Invalid category(-ies). Please use the `OptIn.Categories` enum.");return D($.CHANGED),Object.assign(S,fe(ue(n),e)),r||t(),g}}var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=r.doesOptInApply,o=r.previousPermissions,a=r.preOptInApprovals,s=r.isOptInStorageEnabled,c=r.optInCookieDomain,u=r.optInStorageExpiry,l=r.isIabContext,d=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).cookies,f=Ce(o);Ie(f,"Invalid `previousPermissions`!"),Ie(a,"Invalid `preOptInApprovals`!");var p=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.isEnabled,n=e.cookieName,r=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).cookies;return t&&n&&r?{remove:function(){r.remove(n)},get:function(){var e=r.get(n),t={};try{t=JSON.parse(e)}catch(e){t={}}return t},set:function(e,t){t=t||{},r.set(n,JSON.stringify(e),{domain:t.optInCookieDomain||"",cookieLifetime:t.optInStorageExpiry||3419e4,expires:!0})}}:{get:ye,set:ye,remove:ye}}({isEnabled:!!s,cookieName:"adobeujs-optin"},{cookies:d}),g=this,h=Q(g),m=re(),v=he(f),_=he(a),y=p.get(),C={},I=function(e,t){return me(e)||t&&me(t)?$.COMPLETE:$.PENDING}(v,y),b=function(e,t,n){var r=fe(ne,!i);return i?Object.assign({},r,e,t,n):r}(_,v,y),S=pe(b),D=function(e){return I=e},A=function(e){return b=e};g.deny=n(!1),g.approve=n(!0),g.denyAll=g.deny.bind(g,ne),g.approveAll=g.approve.bind(g,ne),g.isApproved=function(t){return e(t,g.permissions)},g.isPreApproved=function(t){return e(t,_)},g.fetchPermissions=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=t?g.on($.COMPLETE,e):ye;return!i||i&&g.isComplete||a?e(g.permissions):t||m.add(De,(function(){return e(g.permissions)})),n},g.complete=function(){g.status===$.CHANGED&&t()},g.registerPlugin=function(e){if(!e||!e.name||"function"!=typeof e.onRegister)throw new Error(Ae);C[e.name]||(C[e.name]=e,e.onRegister.call(e,g))},g.execute=Se(C),Object.defineProperties(g,{permissions:{get:function(){return b}},status:{get:function(){return I}},Categories:{get:function(){return Z}},doesOptInApply:{get:function(){return!!i}},isPending:{get:function(){return g.status===$.PENDING}},isComplete:{get:function(){return g.status===$.COMPLETE}},__plugins:{get:function(){return Object.keys(C)}},isIabContext:{get:function(){return l}}})}function s(e,t){if(void 0===t)return e;var n=setTimeout((function(){n=null,e.call(e,new o("The call took longer than you wanted!"))}),t);return function(){n&&(clearTimeout(n),e.apply(e,arguments))}}function c(){if(window.__cmp)return window.__cmp;var e=window;if(e!==window.top){for(var t;!t;){e=e.parent;try{e.frames.__cmpLocator&&(t=e)}catch(e){}if(e===window.top)break}if(t){var n={};return window.__cmp=function(e,r,i){var o=Math.random()+"",a={__cmpCall:{command:e,parameter:r,callId:o}};n[o]=i,t.postMessage(a,"*")},window.addEventListener("message",(function(e){var t=e.data;if("string"==typeof t)try{t=JSON.parse(e.data)}catch(e){}if(t.__cmpReturn){var r=t.__cmpReturn;n[r.callId]&&(n[r.callId](r.returnValue,r.success),delete n[r.callId])}}),!1),window.__cmp}se.error("__cmp not found")}else se.error("__cmp not found")}var u="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:"undefined"!=typeof self?self:{};Object.assign=Object.assign||function(e){for(var t,n,r=1;r=0||t.indexOf("Trident/")>=0&&t.indexOf("Windows NT 6")>=0}()?7:null},n.encodeAndBuildRequest=function(e,t){return e.map(encodeURIComponent).join(t)},n.isObject=function(t){return null!==t&&"object"===e(t)&&!1===Array.isArray(t)},n.defineGlobalNamespace=function(){return window.adobe=n.isObject(window.adobe)?window.adobe:{},window.adobe},n.pluck=function(e,t){return t.reduce((function(t,n){return e[n]&&(t[n]=e[n]),t}),Object.create(null))},n.parseOptOut=function(e,t,n){t||(t=n,e.d_optout&&e.d_optout instanceof Array&&(t=e.d_optout.join(",")));var r=parseInt(e.d_ottl,10);return isNaN(r)&&(r=7200),{optOut:t,d_ottl:r}},n.normalizeBoolean=function(e){var t=e;return"true"===e?t=!0:"false"===e&&(t=!1),t}})),w=(M.isObjectEmpty,M.isValueEmpty,M.getIeVersion,M.encodeAndBuildRequest,M.isObject,M.defineGlobalNamespace,M.pluck,M.parseOptOut,M.normalizeBoolean,function(){return{callbacks:{},add:function(e,t){this.callbacks[e]=this.callbacks[e]||[];var n=this.callbacks[e].push(t)-1,r=this;return function(){r.callbacks[e].splice(n,1)}},execute:function(e,t){if(this.callbacks[e]){t=(t=void 0===t?[]:t)instanceof Array?t:[t];try{for(;this.callbacks[e].length;){var n=this.callbacks[e].shift();"function"==typeof n?n.apply(null,t):n instanceof Array&&n[1].apply(n[0],t)}delete this.callbacks[e]}catch(e){}}},executeAll:function(e,t){(t||e&&!M.isObjectEmpty(e))&&Object.keys(this.callbacks).forEach((function(t){var n=void 0!==e[t]?e[t]:"";this.execute(t,n)}),this)},hasCallbacks:function(){return Boolean(Object.keys(this.callbacks).length)}}}),E=f,k={0:"prefix",1:"orgID",2:"state"},T=function(e,t){this.parse=function(e){try{var t={};return e.data.split("|").forEach((function(e,n){void 0!==e&&(t[k[n]]=2!==n?e:JSON.parse(e))})),t}catch(e){}},this.isInvalid=function(n){var r=this.parse(n);if(!r||Object.keys(r).length<2)return!0;var i=e!==r.orgID,o=!t||n.origin!==t,a=-1===Object.keys(E).indexOf(r.prefix);return i||o||a},this.send=function(n,r,i){var o=r+"|"+e;i&&i===Object(i)&&(o+="|"+JSON.stringify(i));try{n.postMessage(o,t)}catch(e){}}},P=f,x=function(e,t,n,r){function i(e){Object.assign(l,e)}function o(e){if(!p.isInvalid(e)){f=!1;var t=p.parse(e);l.setStateAndPublish(t.state)}}function a(e){!f&&d&&(f=!0,p.send(r,e))}function s(){i(new C(n._generateID)),l.getMarketingCloudVisitorID(),l.callbackRegistry.executeAll(l.state,!0),u.removeEventListener("message",c)}function c(e){if(!p.isInvalid(e)){var t=p.parse(e);f=!1,u.clearTimeout(l._handshakeTimeout),u.removeEventListener("message",c),i(new D(l)),u.addEventListener("message",o),l.setStateAndPublish(t.state),l.callbackRegistry.hasCallbacks()&&a(P.GETSTATE)}}var l=this,d=t.whitelistParentDomain;l.state={ALLFIELDS:{}},l.version=n.version,l.marketingCloudOrgID=e,l.cookieDomain=n.cookieDomain||"",l._instanceType="child";var f=!1,p=new T(e,d);l.callbackRegistry=w(),l.init=function(){u.s_c_in||(u.s_c_il=[],u.s_c_in=0),l._c="Visitor",l._il=u.s_c_il,l._in=u.s_c_in,l._il[l._in]=l,u.s_c_in++,Object.keys(n).forEach((function(e){0!==e.indexOf("_")&&"function"==typeof n[e]&&(l[e]=function(){})})),l.getSupplementalDataID=n.getSupplementalDataID,l.isAllowed=function(){return!0},i(new O(l)),d&&postMessage?(u.addEventListener("message",c),a(P.HANDSHAKE),l._handshakeTimeout=setTimeout(s,250)):s()},l.findField=function(e,t){if(void 0!==l.state[e])return t(l.state[e]),l.state[e]},l.messageParent=a,l.setStateAndPublish=function(e){Object.assign(l.state,e),Object.assign(l.state.ALLFIELDS,e),l.callbackRegistry.executeAll(l.state)}},L=f,R=g,j=p,N=h,F=function(e,t){function n(){var t={};return Object.keys(R).forEach((function(n){var r=R[n],i=e[r]();M.isValueEmpty(i)||(t[n]=i)})),t}function r(t){return function n(r){var i=function(){var t=[];return e._loading&&Object.keys(e._loading).forEach((function(n){if(e._loading[n]){var r=N[n];t.push(r)}})),t.length?t:null}();if(i){var o=j[i[0]];e[o](n,!0)}else t()}}function i(e,r){var i=n();t.send(e,r,i)}function o(e){s(e),i(e,L.HANDSHAKE)}function a(e){r((function(){i(e,L.PARENTSTATE)}))()}function s(n){var r=e.setCustomerIDs;e.setCustomerIDs=function(i){r.call(e,i),t.send(n,L.PARENTSTATE,{CUSTOMERIDS:e.getCustomerIDs()})}}return function(e){t.isInvalid(e)||(t.parse(e).prefix===L.HANDSHAKE?o:a)(e.source)}},V={get:function(e){e=encodeURIComponent(e);var t=(";"+document.cookie).split(" ").join(";"),n=t.indexOf(";"+e+"="),r=n<0?n:t.indexOf(";",n+1);return n<0?"":decodeURIComponent(t.substring(n+2+e.length,r<0?t.length:r))},set:function(e,t,n){var i=r(n,"cookieLifetime"),o=r(n,"expires"),a=r(n,"domain"),s=r(n,"secure")?"Secure":"";if(o&&"SESSION"!==i&&"NONE"!==i){var c=""!==t?parseInt(i||0,10):-60;if(c)(o=new Date).setTime(o.getTime()+1e3*c);else if(1===o){var u=(o=new Date).getYear();o.setYear(u+2+(u<1900?1900:0))}}else o=0;return e&&"NONE"!==i?(document.cookie=encodeURIComponent(e)+"="+encodeURIComponent(t)+"; path=/;"+(o?" expires="+o.toGMTString()+";":"")+(a?" domain="+a+";":"")+s,this.get(e)===t):0},remove:function(e,t){var n=r(t,"domain");n=n?" domain="+n+";":"",document.cookie=encodeURIComponent(e)+"=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"+n}},U=function(e){var t;!e&&u.location&&(e=u.location.hostname);var n,r=(t=e).split(".");for(n=r.length-2;n>=0;n--)if(t=r.slice(n).join("."),V.set("test","cookie",{domain:t}))return V.remove("test",{domain:t}),t;return""},H=function(e,t){return i(e,t)<0},G=function(e,t){return 0!==i(e,t)},B=!!u.postMessage,W=function(e,t,n){var r=1;t&&(B?n.postMessage(e,t.replace(/([^:]+:\/\/[^\/]+).*/,"$1")):t&&(n.location=t.replace(/#.*$/,"")+"#"+ +new Date+r+++"&"+e))},q=function(e,t){var n;try{B&&(e&&(n=function(n){if("string"==typeof t&&n.origin!==t||"[object Function]"===Object.prototype.toString.call(t)&&!1===t(n.origin))return!1;e(n)}),u.addEventListener?u[e?"addEventListener":"removeEventListener"]("message",n):u[e?"attachEvent":"detachEvent"]("onmessage",n))}catch(e){}},Y=function(e){var t,n,r="0123456789",i="",o="",a=8,s=10,c=10;if(1==e){for(r+="ABCDEF",t=0;16>t;t++)n=Math.floor(Math.random()*a),i+=r.substring(n,n+1),n=Math.floor(Math.random()*a),o+=r.substring(n,n+1),a=16;return i+"-"+o}for(t=0;19>t;t++)n=Math.floor(Math.random()*s),i+=r.substring(n,n+1),0===t&&9==n?s=3:((1==t||2==t)&&10!=s&&2>n||2n||20&&(t=!1)),{corsType:e,corsCookiesEnabled:t}}(),getCORSInstance:function(){return"none"===this.corsMetadata.corsType?null:new u[this.corsMetadata.corsType]},fireCORS:function(t,n,r){var i=this;n&&(t.loadErrorHandler=n);try{var o=this.getCORSInstance();o.open("get",t.corsUrl+"&ts="+(new Date).getTime(),!0),"XMLHttpRequest"===this.corsMetadata.corsType&&(o.withCredentials=!0,o.timeout=e.loadTimeout,o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.onreadystatechange=function(){4===this.readyState&&200===this.status&&function(e){var n;try{if((n=JSON.parse(e))!==Object(n))return void i.handleCORSError(t,null,"Response is not JSON")}catch(e){return void i.handleCORSError(t,e,"Error parsing response as JSON")}try{for(var r=t.callback,o=u,a=0;a0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){return!0};this.log=oe("log",e,t),this.warn=oe("warn",e,t),this.error=oe("error",e,t)},se=new ae("[ADOBE OPT-IN]"),ce=function(t,n){return e(t)===n},ue=function(e,t){return e instanceof Array?e:ce(e,"string")?[e]:t||[]},le=function(e){var t=Object.keys(e);return!!t.length&&t.every((function(t){return!0===e[t]}))},de=function(e){return!(!e||ge(e))&&ue(e).every((function(e){return ne.indexOf(e)>-1}))},fe=function(e,t){return e.reduce((function(e,n){return e[n]=t,e}),{})},pe=function(e){return JSON.parse(JSON.stringify(e))},ge=function(e){return"[object Array]"===Object.prototype.toString.call(e)&&!e.length},he=function(e){if(_e(e))return e;try{return JSON.parse(e)}catch(e){return{}}},me=function(e){return void 0===e||(_e(e)?de(Object.keys(e)):ve(e))},ve=function(e){try{var t=JSON.parse(e);return!!e&&ce(e,"string")&&de(Object.keys(t))}catch(e){return!1}},_e=function(e){return null!==e&&ce(e,"object")&&!1===Array.isArray(e)},ye=function(){},Ce=function(e){return ce(e,"function")?e():e},Ie=function(e,t){me(e)||se.error("".concat(t))},be=function(e){return function(e){return Object.keys(e).map((function(t){return e[t]}))}(e).filter((function(e,t,n){return n.indexOf(e)===t}))},Se=function(e){return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.command,r=t.params,i=void 0===r?{}:r,o=t.callback,a=void 0===o?ye:o;if(!n||-1===n.indexOf("."))throw new Error("[OptIn.execute] Please provide a valid command.");try{var s=n.split("."),c=e[s[0]],u=s[1];if(!c||"function"!=typeof c[u])throw new Error("Make sure the plugin and API name exist.");var l=Object.assign(i,{callback:a});c[u].call(c,l)}catch(e){se.error("[execute] Something went wrong: "+e.message)}}};o.prototype=Object.create(Error.prototype),o.prototype.constructor=o;var De="fetchPermissions",Ae="[OptIn#registerPlugin] Plugin is invalid.";a.Categories=Z,a.TimeoutError=o;var Oe=Object.freeze({OptIn:a,IabPlugin:function(){var e=this;e.name="iabPlugin",e.version="0.0.1";var t=re(),n={allConsentData:null},r=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return n[e]=t};e.fetchConsentData=function(e){var t=s(e.callback,e.timeout);i({callback:t})},e.isApproved=function(e){var t=e.callback,r=e.category,o=e.timeout;if(n.allConsentData)return t(null,u(r,n.allConsentData.vendorConsents,n.allConsentData.purposeConsents));var a=s((function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=n.vendorConsents,o=n.purposeConsents;t(e,u(r,i,o))}),o);i({category:r,callback:a})},e.onRegister=function(t){var n=Object.keys(ee);e.fetchConsentData({callback:function(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=r.purposeConsents,o=r.gdprApplies,a=r.vendorConsents;!e&&o&&a&&i&&(n.forEach((function(e){var n=u(e,a,i);t[n?"approve":"deny"](e,!0)})),t.complete())}})};var i=function(e){var i=e.callback;if(n.allConsentData)return i(null,n.allConsentData);t.add("FETCH_CONSENT_DATA",i);var s={};a((function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=e.purposeConsents,a=e.gdprApplies,c=e.vendorConsents;(arguments.length>1?arguments[1]:void 0)&&r("allConsentData",s={purposeConsents:i,gdprApplies:a,vendorConsents:c}),o((function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};(arguments.length>1?arguments[1]:void 0)&&(s.consentString=e.consentData,r("allConsentData",s)),t.execute("FETCH_CONSENT_DATA",[null,n.allConsentData])}))}))},o=function(e){var t=c();t&&t("getConsentData",null,e)},a=function(e){var t=be(ee),n=c();n&&n("getVendorConsents",t,e)},u=function(e){var t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return!!(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{})[ee[e]]&&te[e].every((function(e){return t[e]}))}}}),Me=function e(t){function n(e,t){return e>>>t|e<<32-t}for(var r,i,o=Math.pow,a=o(2,32),s="",c=[],u=8*t.length,l=e.h=e.h||[],d=e.k=e.k||[],f=d.length,p={},g=2;f<64;g++)if(!p[g]){for(r=0;r<313;r+=g)p[r]=g;l[f]=o(g,.5)*a|0,d[f++]=o(g,1/3)*a|0}for(t+="€";t.length%64-56;)t+="\0";for(r=0;r>8)return;c[r>>2]|=i<<(3-r)%4*8}for(c[c.length]=u/a|0,c[c.length]=u,i=0;i>>3)+h[r-7]+(n(_,17)^n(_,19)^_>>>10)|0);(l=[I+((n(y,2)^n(y,13)^n(y,22))+(y&l[1]^y&l[2]^l[1]&l[2]))|0].concat(l))[4]=l[4]+I|0}for(r=0;r<8;r++)l[r]=l[r]+m[r]|0}for(r=0;r<8;r++)for(i=3;i+1;i--){var b=l[r]>>8*i&255;s+=(b<16?0:"")+b.toString(16)}return s},we=function(e,t){return"SHA-256"!==t&&"SHA256"!==t&&"sha256"!==t&&"sha-256"!==t||(e=Me(e)),e},Ee=function(e){return String(e).trim().toLowerCase()},ke=Oe.OptIn;M.defineGlobalNamespace(),window.adobe.OptInCategories=ke.Categories;var Te=function(t,n,r){function i(e){var t=e;return function(e){var n=e||I.location.href;try{var r=p._extractParamFromUri(n,t);if(r)return j.parsePipeDelimetedKeyValues(r)}catch(e){}}}function o(e){e=e||{},p._supplementalDataIDCurrent=e.supplementalDataIDCurrent||"",p._supplementalDataIDCurrentConsumed=e.supplementalDataIDCurrentConsumed||{},p._supplementalDataIDLast=e.supplementalDataIDLast||"",p._supplementalDataIDLastConsumed=e.supplementalDataIDLastConsumed||{}}function a(e){var t=e.reduce((function(e,t){var n=t[0],r=t[1];return null!=r&&r!==P&&(e=function(e,t,n){return(n=n?n+="|":n)+(e+"=")+encodeURIComponent(t)}(n,r,e)),e}),"");return function(e){return(e=e?e+="|":e)+"TS="+j.getTimestampInSeconds()}(t)}function s(){return p.configs.doesOptInApply&&p.configs.isIabContext?g.optIn.isApproved(g.optIn.Categories.ECID)&&y:g.optIn.isApproved(g.optIn.Categories.ECID)}function c(){[["getMarketingCloudVisitorID"],["setCustomerIDs",void 0],["getAnalyticsVisitorID"],["getAudienceManagerLocationHint"],["getLocationHint"],["getAudienceManagerBlob"]].forEach((function(e){var t=e[0],n=2===e.length?e[1]:"",r=p[t];p[t]=function(e){return s()&&p.isAllowed()?r.apply(p,arguments):("function"==typeof e&&p._callCallback(e,[n]),n)}}))}function l(e,t){if(y=!0,e)throw new Error("[IAB plugin] : "+e);t.gdprApplies&&(h=t.consentString),p.init(),f()}function d(){g.optIn.isComplete&&(g.optIn.isApproved(g.optIn.Categories.ECID)?p.configs.isIabContext?g.optIn.execute({command:"iabPlugin.fetchConsentData",callback:l}):(p.init(),f()):(c(),f()))}function f(){g.optIn.off("complete",d)}if(!r||r.split("").reverse().join("")!==t)throw new Error("Please use `Visitor.getInstance` to instantiate Visitor.");var p=this,g=window.adobe,h="",y=!1,C=!1;p.version="4.6.0";var I=u,b=I.Visitor;b.version=p.version,b.AuthState=v,b.OptOut=_,I.s_c_in||(I.s_c_il=[],I.s_c_in=0),p._c="Visitor",p._il=I.s_c_il,p._in=I.s_c_in,p._il[p._in]=p,I.s_c_in++,p._instanceType="regular",p._log={requests:[]},p.marketingCloudOrgID=t,p.cookieName="AMCV_"+t,p.sessionCookieName="AMCVS_"+t,p.cookieDomain=U(),p.loadSSL=!0,p.loadTimeout=3e4,p.CORSErrors=[],p.marketingCloudServer=p.audienceManagerServer="dpm.demdex.net",p.sdidParamExpiry=30;var S=null,D="MCMID",A="MCIDTS",O="A",w="MCAID",E="AAM",k="MCAAMB",P="NONE",x=function(e){return!Object.prototype[e]},L=X(p);p.FIELDS=m,p.cookieRead=function(e){return V.get(e)},p.cookieWrite=function(e,t,n){var r=p.cookieLifetime?(""+p.cookieLifetime).toUpperCase():"",i=!1;return p.configs&&p.configs.secureCookie&&"https:"===location.protocol&&(i=!0),V.set(e,""+t,{expires:n,domain:p.cookieDomain,cookieLifetime:r,secure:i})},p.resetState=function(e){e?p._mergeServerState(e):o()},p._isAllowedDone=!1,p._isAllowedFlag=!1,p.isAllowed=function(){return p._isAllowedDone||(p._isAllowedDone=!0,(p.cookieRead(p.cookieName)||p.cookieWrite(p.cookieName,"T",1))&&(p._isAllowedFlag=!0)),"T"===p.cookieRead(p.cookieName)&&p._helpers.removeCookie(p.cookieName),p._isAllowedFlag},p.setMarketingCloudVisitorID=function(e){p._setMarketingCloudFields(e)},p._use1stPartyMarketingCloudServer=!1,p.getMarketingCloudVisitorID=function(e,t){p.marketingCloudServer&&p.marketingCloudServer.indexOf(".demdex.net")<0&&(p._use1stPartyMarketingCloudServer=!0);var n=p._getAudienceManagerURLData("_setMarketingCloudFields"),r=n.url;return p._getRemoteField(D,r,e,t,n)};p.getVisitorValues=function(e,t){var n={MCMID:{fn:p.getMarketingCloudVisitorID,args:[!0],context:p},MCOPTOUT:{fn:p.isOptedOut,args:[void 0,!0],context:p},MCAID:{fn:p.getAnalyticsVisitorID,args:[!0],context:p},MCAAMLH:{fn:p.getAudienceManagerLocationHint,args:[!0],context:p},MCAAMB:{fn:p.getAudienceManagerBlob,args:[!0],context:p}},r=t&&t.length?M.pluck(n,t):n;t&&-1===t.indexOf("MCAID")?function(e,t){var n={};p.getMarketingCloudVisitorID((function(){t.forEach((function(e){n[e]=p._getField(e,!0)})),-1!==t.indexOf("MCOPTOUT")?p.isOptedOut((function(t){n.MCOPTOUT=t,e(n)}),null,!0):e(n)}),!0)}(e,t):function(e,t){function n(e){return function(n){r[e]=n,++i===o&&t(r)}}var r={},i=0,o=Object.keys(e).length;Object.keys(e).forEach((function(t){var r=e[t];if(r.fn){var i=r.args||[];i.unshift(n(t)),r.fn.apply(r.context||null,i)}}))}(r,e)},p._currentCustomerIDs={},p._customerIDsHashChanged=!1,p._newCustomerIDsHash="",p.setCustomerIDs=function(t,n){if(!p.isOptedOut()&&t){if(!M.isObject(t)||M.isObjectEmpty(t))return!1;var r,i,o;for(r in p._readVisitor(),t)if(x(r)&&(n=(i=t[r]).hasOwnProperty("hashType")?i.hashType:n,i))if("object"===e(i)){var a={};if(i.id){if(n){if(!(o=we(Ee(i.id),n)))return;i.id=o,a.hashType=n}a.id=i.id}null!=i.authState&&(a.authState=i.authState),p._currentCustomerIDs[r]=a}else if(n){if(!(o=we(Ee(i),n)))return;p._currentCustomerIDs[r]={id:o,hashType:n}}else p._currentCustomerIDs[r]={id:i};var s=p.getCustomerIDs(),c=p._getField("MCCIDH"),u="";for(r in c||(c=0),s)x(r)&&(u+=(u?"|":"")+r+"|"+((i=s[r]).id?i.id:"")+(i.authState?i.authState:""));p._newCustomerIDsHash=String(p._hash(u)),p._newCustomerIDsHash!==c&&(p._customerIDsHashChanged=!0,p._mapCustomerIDs((function(){p._customerIDsHashChanged=!1})))}},p.getCustomerIDs=function(){p._readVisitor();var e,t,n={};for(e in p._currentCustomerIDs)x(e)&&((t=p._currentCustomerIDs[e]).id&&(n[e]||(n[e]={}),n[e].id=t.id,null!=t.authState?n[e].authState=t.authState:n[e].authState=b.AuthState.UNKNOWN,t.hashType&&(n[e].hashType=t.hashType)));return n},p.setAnalyticsVisitorID=function(e){p._setAnalyticsFields(e)},p.getAnalyticsVisitorID=function(e,t,n){if(!j.isTrackingServerPopulated()&&!n)return p._callCallback(e,[""]),"";var r="";if(n||(r=p.getMarketingCloudVisitorID((function(t){p.getAnalyticsVisitorID(e,!0)}))),r||n){var i=n?p.marketingCloudServer:p.trackingServer,o="";p.loadSSL&&(n?p.marketingCloudServerSecure&&(i=p.marketingCloudServerSecure):p.trackingServerSecure&&(i=p.trackingServerSecure));var a={};if(i){var s="http"+(p.loadSSL?"s":"")+"://"+i+"/id",c="d_visid_ver="+p.version+"&mcorgid="+encodeURIComponent(p.marketingCloudOrgID)+(r?"&mid="+encodeURIComponent(r):"")+(p.idSyncDisable3rdPartySyncing||p.disableThirdPartyCookies?"&d_coppa=true":""),u=["s_c_il",p._in,"_set"+(n?"MarketingCloud":"Analytics")+"Fields"];o=s+"?"+c+"&callback=s_c_il%5B"+p._in+"%5D._set"+(n?"MarketingCloud":"Analytics")+"Fields",a.corsUrl=s+"?"+c,a.callback=u}return a.url=o,p._getRemoteField(n?D:w,o,e,t,a)}return""},p.getAudienceManagerLocationHint=function(e,t){if(p.getMarketingCloudVisitorID((function(t){p.getAudienceManagerLocationHint(e,!0)}))){var n=p._getField(w);if(!n&&j.isTrackingServerPopulated()&&(n=p.getAnalyticsVisitorID((function(t){p.getAudienceManagerLocationHint(e,!0)}))),n||!j.isTrackingServerPopulated()){var r=p._getAudienceManagerURLData(),i=r.url;return p._getRemoteField("MCAAMLH",i,e,t,r)}}return""},p.getLocationHint=p.getAudienceManagerLocationHint,p.getAudienceManagerBlob=function(e,t){if(p.getMarketingCloudVisitorID((function(t){p.getAudienceManagerBlob(e,!0)}))){var n=p._getField(w);if(!n&&j.isTrackingServerPopulated()&&(n=p.getAnalyticsVisitorID((function(t){p.getAudienceManagerBlob(e,!0)}))),n||!j.isTrackingServerPopulated()){var r=p._getAudienceManagerURLData(),i=r.url;return p._customerIDsHashChanged&&p._setFieldExpire(k,-1),p._getRemoteField(k,i,e,t,r)}}return""},p._supplementalDataIDCurrent="",p._supplementalDataIDCurrentConsumed={},p._supplementalDataIDLast="",p._supplementalDataIDLastConsumed={},p.getSupplementalDataID=function(e,t){p._supplementalDataIDCurrent||t||(p._supplementalDataIDCurrent=p._generateID(1));var n=p._supplementalDataIDCurrent;return p._supplementalDataIDLast&&!p._supplementalDataIDLastConsumed[e]?(n=p._supplementalDataIDLast,p._supplementalDataIDLastConsumed[e]=!0):n&&(p._supplementalDataIDCurrentConsumed[e]&&(p._supplementalDataIDLast=p._supplementalDataIDCurrent,p._supplementalDataIDLastConsumed=p._supplementalDataIDCurrentConsumed,p._supplementalDataIDCurrent=n=t?"":p._generateID(1),p._supplementalDataIDCurrentConsumed={}),n&&(p._supplementalDataIDCurrentConsumed[e]=!0)),n};var R=!1;p._liberatedOptOut=null,p.getOptOut=function(e,t){var n=p._getAudienceManagerURLData("_setMarketingCloudFields"),r=n.url;if(s())return p._getRemoteField("MCOPTOUT",r,e,t,n);if(p._registerCallback("liberatedOptOut",e),null!==p._liberatedOptOut)return p._callAllCallbacks("liberatedOptOut",[p._liberatedOptOut]),R=!1,p._liberatedOptOut;if(R)return null;R=!0;var i="liberatedGetOptOut";return n.corsUrl=n.corsUrl.replace(/dpm\.demdex\.net\/id\?/,"dpm.demdex.net/optOutStatus?"),n.callback=[i],u[i]=function(e){if(e===Object(e)){var t,n,r=M.parseOptOut(e,t,P);t=r.optOut,n=1e3*r.d_ottl,p._liberatedOptOut=t,setTimeout((function(){p._liberatedOptOut=null}),n)}p._callAllCallbacks("liberatedOptOut",[t]),R=!1},L.fireCORS(n),null},p.isOptedOut=function(e,t,n){t||(t=b.OptOut.GLOBAL);var r=p.getOptOut((function(n){var r=n===b.OptOut.GLOBAL||n.indexOf(t)>=0;p._callCallback(e,[r])}),n);return r?r===b.OptOut.GLOBAL||r.indexOf(t)>=0:null},p._fields=null,p._fieldsExpired=null,p._hash=function(e){var t,n=0;if(e)for(t=0;t0;)p._callCallback(n.shift(),t)}},p._addQuerystringParam=function(e,t,n,r){var i=encodeURIComponent(t)+"="+encodeURIComponent(n),o=j.parseHash(e),a=j.hashlessUrl(e);if(-1===a.indexOf("?"))return a+"?"+i+o;var s=a.split("?"),c=s[0]+"?",u=s[1];return c+j.addQueryParamAtLocation(u,i,r)+o},p._extractParamFromUri=function(e,t){var n=new RegExp("[\\?&#]"+t+"=([^&#]*)").exec(e);if(n&&n.length)return decodeURIComponent(n[1])},p._parseAdobeMcFromUrl=i(J.ADOBE_MC),p._parseAdobeMcSdidFromUrl=i(J.ADOBE_MC_SDID),p._attemptToPopulateSdidFromUrl=function(e){var n=p._parseAdobeMcSdidFromUrl(e),r=1e9;n&&n.TS&&(r=j.getTimestampInSeconds()-n.TS),n&&n.SDID&&n.MCORGID===t&&rJ.ADOBE_MC_TTL_IN_MIN||e.MCORGID!==t)return;!function(e){function t(e,t,n){e&&e.match(J.VALID_VISITOR_ID_REGEX)&&(n===D&&(C=!0),t(e))}t(e[D],p.setMarketingCloudVisitorID,D),p._setFieldExpire(k,-1),t(e[w],p.setAnalyticsVisitorID)}(e)}},p._mergeServerState=function(e){if(e)try{if((e=function(e){return j.isObject(e)?e:JSON.parse(e)}(e))[p.marketingCloudOrgID]){var t=e[p.marketingCloudOrgID];!function(e){j.isObject(e)&&p.setCustomerIDs(e)}(t.customerIDs),o(t.sdid)}}catch(e){throw new Error("`serverState` has an invalid format.")}},p._timeout=null,p._loadData=function(e,t,n,r){t=p._addQuerystringParam(t,"d_fieldgroup",e,1),r.url=p._addQuerystringParam(r.url,"d_fieldgroup",e,1),r.corsUrl=p._addQuerystringParam(r.corsUrl,"d_fieldgroup",e,1),K.fieldGroupObj[e]=!0,r===Object(r)&&r.corsUrl&&"XMLHttpRequest"===L.corsMetadata.corsType&&L.fireCORS(r,n,e)},p._clearTimeout=function(e){null!=p._timeout&&p._timeout[e]&&(clearTimeout(p._timeout[e]),p._timeout[e]=0)},p._settingsDigest=0,p._getSettingsDigest=function(){if(!p._settingsDigest){var e=p.version;p.audienceManagerServer&&(e+="|"+p.audienceManagerServer),p.audienceManagerServerSecure&&(e+="|"+p.audienceManagerServerSecure),p._settingsDigest=p._hash(e)}return p._settingsDigest},p._readVisitorDone=!1,p._readVisitor=function(){if(!p._readVisitorDone){p._readVisitorDone=!0;var e,t,n,r,i,o,a=p._getSettingsDigest(),s=!1,c=p.cookieRead(p.cookieName),u=new Date;if(c||C||p.discardTrackingServerECID||(c=p.cookieRead(J.FIRST_PARTY_SERVER_COOKIE)),null==p._fields&&(p._fields={}),c&&"T"!==c)for((c=c.split("|"))[0].match(/^[\-0-9]+$/)&&(parseInt(c[0],10)!==a&&(s=!0),c.shift()),c.length%2==1&&c.pop(),e=0;e1?(i=parseInt(t[1],10),o=t[1].indexOf("s")>0):(i=0,o=!1),s&&("MCCIDH"===n&&(r=""),i>0&&(i=u.getTime()/1e3-60)),n&&r&&(p._setField(n,r,1),i>0&&(p._fields["expire"+n]=i+(o?"s":""),(u.getTime()>=1e3*i||o&&!p.cookieRead(p.sessionCookieName))&&(p._fieldsExpired||(p._fieldsExpired={}),p._fieldsExpired[n]=!0)));!p._getField(w)&&j.isTrackingServerPopulated()&&(c=p.cookieRead("s_vi"))&&((c=c.split("|")).length>1&&c[0].indexOf("v1")>=0&&((e=(r=c[1]).indexOf("["))>=0&&(r=r.substring(0,e)),r&&r.match(J.VALID_VISITOR_ID_REGEX)&&p._setField(w,r)))}},p._appendVersionTo=function(e){var t="vVersion|"+p.version,n=e?p._getCookieVersion(e):null;return n?G(n,p.version)&&(e=e.replace(J.VERSION_REGEX,t)):e+=(e?"|":"")+t,e},p._writeVisitor=function(){var e,t,n=p._getSettingsDigest();for(e in p._fields)x(e)&&p._fields[e]&&"expire"!==e.substring(0,6)&&(t=p._fields[e],n+=(n?"|":"")+e+(p._fields["expire"+e]?"-"+p._fields["expire"+e]:"")+"|"+t);n=p._appendVersionTo(n),p.cookieWrite(p.cookieName,n,1)},p._getField=function(e,t){return null==p._fields||!t&&p._fieldsExpired&&p._fieldsExpired[e]?null:p._fields[e]},p._setField=function(e,t,n){null==p._fields&&(p._fields={}),p._fields[e]=t,n||p._writeVisitor()},p._getFieldList=function(e,t){var n=p._getField(e,t);return n?n.split("*"):null},p._setFieldList=function(e,t,n){p._setField(e,t?t.join("*"):"",n)},p._getFieldMap=function(e,t){var n=p._getFieldList(e,t);if(n){var r,i={};for(r=0;r0?e.substr(t):""},hashlessUrl:function(e){var t=e.indexOf("#");return t>0?e.substr(0,t):e},addQueryParamAtLocation:function(e,t,n){var r=e.split("&");return n=null!=n?n:r.length,r.splice(n,0,t),r.join("&")},isFirstPartyAnalyticsVisitorIDCall:function(e,t,n){return e===w&&(t||(t=p.trackingServer),n||(n=p.trackingServerSecure),!("string"!=typeof(r=p.loadSSL?n:t)||!r.length)&&r.indexOf("2o7.net")<0&&r.indexOf("omtrdc.net")<0);var r},isObject:function(e){return Boolean(e&&e===Object(e))},removeCookie:function(e){V.remove(e,{domain:p.cookieDomain})},isTrackingServerPopulated:function(){return!!p.trackingServer||!!p.trackingServerSecure},getTimestampInSeconds:function(){return Math.round((new Date).getTime()/1e3)},parsePipeDelimetedKeyValues:function(e){return e.split("|").reduce((function(e,t){var n=t.split("=");return e[n[0]]=decodeURIComponent(n[1]),e}),{})},generateRandomString:function(e){e=e||5;for(var t="",n="abcdefghijklmnopqrstuvwxyz0123456789";e--;)t+=n[Math.floor(36*Math.random())];return t},normalizeBoolean:function(e){return"true"===e||"false"!==e&&e},parseBoolean:function(e){return"true"===e||"false"!==e&&null},replaceMethodsWithFunction:function(e,t){for(var n in e)e.hasOwnProperty(n)&&"function"==typeof e[n]&&(e[n]=t);return e}};p._helpers=j;var N=function(e,t){var n=u.document;return{THROTTLE_START:3e4,MAX_SYNCS_LENGTH:649,throttleTimerSet:!1,id:null,onPagePixels:[],iframeHost:null,getIframeHost:function(e){if("string"==typeof e){var t=e.split("/");return t[0]+"//"+t[2]}},subdomain:null,url:null,getUrl:function(){var t,r="http://fast.",i="?d_nsid="+e.idSyncContainerID+"#"+encodeURIComponent(n.location.origin);return this.subdomain||(this.subdomain="nosubdomainreturned"),e.loadSSL&&(r=e.idSyncSSLUseAkamai?"https://fast.":"https://"),t=r+this.subdomain+".demdex.net/dest5.html"+i,this.iframeHost=this.getIframeHost(t),this.id="destination_publishing_iframe_"+this.subdomain+"_"+e.idSyncContainerID,t},checkDPIframeSrc:function(){var t="?d_nsid="+e.idSyncContainerID+"#"+encodeURIComponent(n.location.href);"string"==typeof e.dpIframeSrc&&e.dpIframeSrc.length&&(this.id="destination_publishing_iframe_"+(e._subdomain||this.subdomain||(new Date).getTime())+"_"+e.idSyncContainerID,this.iframeHost=this.getIframeHost(e.dpIframeSrc),this.url=e.dpIframeSrc+t)},idCallNotProcesssed:null,doAttachIframe:!1,startedAttachingIframe:!1,iframeHasLoaded:null,iframeIdChanged:null,newIframeCreated:null,originalIframeHasLoadedAlready:null,iframeLoadedCallbacks:[],regionChanged:!1,timesRegionChanged:0,sendingMessages:!1,messages:[],messagesPosted:[],messagesReceived:[],messageSendingInterval:J.POST_MESSAGE_ENABLED?null:100,onPageDestinationsFired:[],jsonForComparison:[],jsonDuplicates:[],jsonWaiting:[],jsonProcessed:[],canSetThirdPartyCookies:!0,receivedThirdPartyCookiesNotification:!1,readyToAttachIframePreliminary:function(){return!(e.idSyncDisableSyncs||e.disableIdSyncs||e.idSyncDisable3rdPartySyncing||e.disableThirdPartyCookies||e.disableThirdPartyCalls)},readyToAttachIframe:function(){return this.readyToAttachIframePreliminary()&&(this.doAttachIframe||e._doAttachIframe)&&(this.subdomain&&"nosubdomainreturned"!==this.subdomain||e._subdomain)&&this.url&&!this.startedAttachingIframe},attachIframe:function(){function e(){(i=n.createElement("iframe")).sandbox="allow-scripts allow-same-origin",i.title="Adobe ID Syncing iFrame",i.id=r.id,i.name=r.id+"_name",i.style.cssText="display: none; width: 0; height: 0;",i.src=r.url,r.newIframeCreated=!0,t(),n.body.appendChild(i)}function t(e){i.addEventListener("load",(function(){i.className="aamIframeLoaded",r.iframeHasLoaded=!0,r.fireIframeLoadedCallbacks(e),r.requestToProcess()}))}this.startedAttachingIframe=!0;var r=this,i=n.getElementById(this.id);i?"IFRAME"!==i.nodeName?(this.id+="_2",this.iframeIdChanged=!0,e()):(this.newIframeCreated=!1,"aamIframeLoaded"!==i.className?(this.originalIframeHasLoadedAlready=!1,t("The destination publishing iframe already exists from a different library, but hadn't loaded yet.")):(this.originalIframeHasLoadedAlready=!0,this.iframeHasLoaded=!0,this.iframe=i,this.fireIframeLoadedCallbacks("The destination publishing iframe already exists from a different library, and had loaded alresady."),this.requestToProcess())):e(),this.iframe=i},fireIframeLoadedCallbacks:function(e){this.iframeLoadedCallbacks.forEach((function(t){"function"==typeof t&&t({message:e||"The destination publishing iframe was attached and loaded successfully."})})),this.iframeLoadedCallbacks=[]},requestToProcess:function(t){function n(){i.jsonForComparison.push(t),i.jsonWaiting.push(t),i.processSyncOnPage(t)}var r,i=this;if(t===Object(t)&&t.ibs)if(r=JSON.stringify(t.ibs||[]),this.jsonForComparison.length){var o,a,s,c=!1;for(o=0,a=this.jsonForComparison.length;o=o&&(e.splice(i,1),i--);return{dataPresent:a,dataValid:s}},manageSyncsSize:function(e){if(e.join("*").length>this.MAX_SYNCS_LENGTH)for(e.sort((function(e,t){return parseInt(e.split("-")[1],10)-parseInt(t.split("-")[1],10)}));e.join("*").length>this.MAX_SYNCS_LENGTH;)e.shift()},fireSync:function(t,n,r,i,o,a){var s=this;if(t){if("img"===n.tag){var c,u,l,d,f=n.url,p=e.loadSSL?"https:":"http:";for(c=0,u=f.length;cJ.DAYS_BETWEEN_SYNC_ID_CALLS},attachIframeASAP:function(){var e=this;!function t(){e.startedAttachingIframe||(n.body?e.attachIframe():setTimeout(t,30))}()}}}(p,b);p._destinationPublishing=N,p.timeoutMetricsLog=[];var B,K={isClientSideMarketingCloudVisitorID:null,MCIDCallTimedOut:null,AnalyticsIDCallTimedOut:null,AAMIDCallTimedOut:null,fieldGroupObj:{},setState:function(e,t){switch(e){case"MC":!1===t?!0!==this.MCIDCallTimedOut&&(this.MCIDCallTimedOut=!1):this.MCIDCallTimedOut=t;break;case O:!1===t?!0!==this.AnalyticsIDCallTimedOut&&(this.AnalyticsIDCallTimedOut=!1):this.AnalyticsIDCallTimedOut=t;break;case E:!1===t?!0!==this.AAMIDCallTimedOut&&(this.AAMIDCallTimedOut=!1):this.AAMIDCallTimedOut=t}}};p.isClientSideMarketingCloudVisitorID=function(){return K.isClientSideMarketingCloudVisitorID},p.MCIDCallTimedOut=function(){return K.MCIDCallTimedOut},p.AnalyticsIDCallTimedOut=function(){return K.AnalyticsIDCallTimedOut},p.AAMIDCallTimedOut=function(){return K.AAMIDCallTimedOut},p.idSyncGetOnPageSyncInfo=function(){return p._readVisitor(),p._getField("MCSYNCSOP")},p.idSyncByURL=function(e){if(!p.isOptedOut()){var t=function(e){var t=e.minutesToLive,n="";return(p.idSyncDisableSyncs||p.disableIdSyncs)&&(n=n||"Error: id syncs have been disabled"),"string"==typeof e.dpid&&e.dpid.length||(n=n||"Error: config.dpid is empty"),"string"==typeof e.url&&e.url.length||(n=n||"Error: config.url is empty"),void 0===t?t=20160:(t=parseInt(t,10),(isNaN(t)||t<=0)&&(n=n||"Error: config.minutesToLive needs to be a positive number")),{error:n,ttl:t}}(e||{});if(t.error)return t.error;var n,r,i=e.url,o=encodeURIComponent,a=N;return i=i.replace(/^https:/,"").replace(/^http:/,""),n=M.encodeAndBuildRequest(["",e.dpid,e.dpuuid||""],","),r=["ibs",o(e.dpid),"img",o(i),t.ttl,"",n],a.addMessage(r.join("|")),a.requestToProcess(),"Successfully queued"}},p.idSyncByDataSource=function(e){if(!p.isOptedOut())return e===Object(e)&&"string"==typeof e.dpuuid&&e.dpuuid.length?(e.url="//dpm.demdex.net/ibs:dpid="+e.dpid+"&dpuuid="+e.dpuuid,p.idSyncByURL(e)):"Error: config or config.dpuuid is empty"},function(e,t){e.publishDestinations=function(n){var r=arguments[1],i=arguments[2];try{i="function"==typeof i?i:n.callback}catch(e){i=function(){}}var o=t;if(o.readyToAttachIframePreliminary()){if("string"==typeof n){if(!n.length)return void i({error:"subdomain is not a populated string."});if(!(r instanceof Array&&r.length))return void i({error:"messages is not a populated array."});var a=!1;if(r.forEach((function(e){"string"==typeof e&&e.length&&(o.addMessage(e),a=!0)})),!a)return void i({error:"None of the messages are populated strings."})}else{if(!M.isObject(n))return void i({error:"Invalid parameters passed."});var s=n;if("string"!=typeof(n=s.subdomain)||!n.length)return void i({error:"config.subdomain is not a populated string."});var c=s.urlDestinations;if(!(c instanceof Array&&c.length))return void i({error:"config.urlDestinations is not a populated array."});var u=[];c.forEach((function(e){M.isObject(e)&&(e.hideReferrer?e.message&&o.addMessage(e.message):u.push(e))})),function e(){u.length&&setTimeout((function(){var t=new Image,n=u.shift();t.src=n.url,o.onPageDestinationsFired.push(n),e()}),100)}()}o.iframe?(i({message:"The destination publishing iframe is already attached and loaded."}),o.requestToProcess()):!e.subdomain&&e._getField("MCMID")?(o.subdomain=n,o.doAttachIframe=!0,o.url=o.getUrl(),o.readyToAttachIframe()?(o.iframeLoadedCallbacks.push((function(e){i({message:"Attempted to attach and load the destination publishing iframe through this API call. Result: "+(e.message||"no result")})})),o.attachIframe()):i({error:"Encountered a problem in attempting to attach and load the destination publishing iframe through this API call."})):o.iframeLoadedCallbacks.push((function(e){i({message:"Attempted to attach and load the destination publishing iframe through normal Visitor API processing. Result: "+(e.message||"no result")})}))}else i({error:"The destination publishing iframe is disabled in the Visitor library."})}}(p,N),p._getCookieVersion=function(e){e=e||p.cookieRead(p.cookieName);var t=J.VERSION_REGEX.exec(e);return t&&t.length>1?t[1]:null},p._resetAmcvCookie=function(e){var t=p._getCookieVersion();t&&!H(t,e)||j.removeCookie(p.cookieName)},p.setAsCoopSafe=function(){S=!0},p.setAsCoopUnsafe=function(){S=!1},function(){if(p.configs=Object.create(null),j.isObject(n))for(var e in n)x(e)&&(p[e]=n[e],p.configs[e]=n[e])}(),c(),p.init=function(){!(!p.configs.doesOptInApply||g.optIn.isComplete&&s())&&(g.optIn.fetchPermissions(d,!0),!g.optIn.isApproved(g.optIn.Categories.ECID))||B||(B=!0,function(){if(j.isObject(n)){p.idSyncContainerID=p.idSyncContainerID||0,S="boolean"==typeof p.isCoopSafe?p.isCoopSafe:j.parseBoolean(p.isCoopSafe),p.resetBeforeVersion&&p._resetAmcvCookie(p.resetBeforeVersion),p._attemptToPopulateIdsFromUrl(),p._attemptToPopulateSdidFromUrl(),p._readVisitor();var e=p._getField(A),t=Math.ceil((new Date).getTime()/J.MILLIS_PER_DAY);p.idSyncDisableSyncs||p.disableIdSyncs||!N.canMakeSyncIDCall(e,t)||(p._setFieldExpire(k,-1),p._setField(A,t)),p.getMarketingCloudVisitorID(),p.getAudienceManagerLocationHint(),p.getAudienceManagerBlob(),p._mergeServerState(p.serverState)}else p._attemptToPopulateIdsFromUrl(),p._attemptToPopulateSdidFromUrl()}(),function(){if(!p.idSyncDisableSyncs&&!p.disableIdSyncs){N.checkDPIframeSrc();I.addEventListener("load",(function(){b.windowLoaded=!0,function(){var e=N;e.readyToAttachIframe()&&e.attachIframe()}()}));try{q((function(e){N.receiveMessage(e.data)}),N.iframeHost)}catch(e){}}}(),p.whitelistIframeDomains&&J.POST_MESSAGE_ENABLED&&(p.whitelistIframeDomains=p.whitelistIframeDomains instanceof Array?p.whitelistIframeDomains:[p.whitelistIframeDomains],p.whitelistIframeDomains.forEach((function(e){var n=new T(t,e),r=F(p,n);q(r,e)}))))}};Te.config=z,u.Visitor=Te;var Pe=Te,xe=Oe.OptIn,Le=Oe.IabPlugin;Pe.getInstance=function(e,t){if(!e)throw new Error("Visitor requires Adobe Marketing Cloud Org ID.");e.indexOf("@")<0&&(e+="@AdobeOrg");var n=function(){var t=u.s_c_il;if(t)for(var n=0;n{var r=n(874).default;function i(){"use strict"; +/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */e.exports=i=function(){return t},e.exports.__esModule=!0,e.exports.default=e.exports;var t={},n=Object.prototype,o=n.hasOwnProperty,a="function"==typeof Symbol?Symbol:{},s=a.iterator||"@@iterator",c=a.asyncIterator||"@@asyncIterator",u=a.toStringTag||"@@toStringTag";function l(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(e){l=function(e,t,n){return e[t]=n}}function d(e,t,n,r){var i=t&&t.prototype instanceof g?t:g,o=Object.create(i.prototype),a=new O(r||[]);return o._invoke=function(e,t,n){var r="suspendedStart";return function(i,o){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===i)throw o;return w()}for(n.method=i,n.arg=o;;){var a=n.delegate;if(a){var s=S(a,n);if(s){if(s===p)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var c=f(e,t,n);if("normal"===c.type){if(r=n.done?"completed":"suspendedYield",c.arg===p)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(r="completed",n.method="throw",n.arg=c.arg)}}}(e,n,a),o}function f(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(e){return{type:"throw",arg:e}}}t.wrap=d;var p={};function g(){}function h(){}function m(){}var v={};l(v,s,(function(){return this}));var _=Object.getPrototypeOf,y=_&&_(_(M([])));y&&y!==n&&o.call(y,s)&&(v=y);var C=m.prototype=g.prototype=Object.create(v);function I(e){["next","throw","return"].forEach((function(t){l(e,t,(function(e){return this._invoke(t,e)}))}))}function b(e,t){function n(i,a,s,c){var u=f(e[i],e,a);if("throw"!==u.type){var l=u.arg,d=l.value;return d&&"object"==r(d)&&o.call(d,"__await")?t.resolve(d.__await).then((function(e){n("next",e,s,c)}),(function(e){n("throw",e,s,c)})):t.resolve(d).then((function(e){l.value=e,s(l)}),(function(e){return n("throw",e,s,c)}))}c(u.arg)}var i;this._invoke=function(e,r){function o(){return new t((function(t,i){n(e,r,t,i)}))}return i=i?i.then(o,o):o()}}function S(e,t){var n=e.iterator[t.method];if(void 0===n){if(t.delegate=null,"throw"===t.method){if(e.iterator.return&&(t.method="return",t.arg=void 0,S(e,t),"throw"===t.method))return p;t.method="throw",t.arg=new TypeError("The iterator does not provide a 'throw' method")}return p}var r=f(n,e.iterator,t.arg);if("throw"===r.type)return t.method="throw",t.arg=r.arg,t.delegate=null,p;var i=r.arg;return i?i.done?(t[e.resultName]=i.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,p):i:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,p)}function D(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function A(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function O(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(D,this),this.reset(!0)}function M(e){if(e){var t=e[s];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,r=function t(){for(;++n=0;--r){var i=this.tryEntries[r],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=o.call(i,"catchLoc"),c=o.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--n){var r=this.tryEntries[n];if(r.tryLoc<=this.prev&&o.call(r,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),A(n),p}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var i=r.arg;A(n)}return i}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:M(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},t}e.exports=i,e.exports.__esModule=!0,e.exports.default=e.exports},874:e=>{function t(n){return e.exports=t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e.exports.__esModule=!0,e.exports.default=e.exports,t(n)}e.exports=t,e.exports.__esModule=!0,e.exports.default=e.exports},841:(e,t,n)=>{var r=n(425)();e.exports=r;try{regeneratorRuntime=r}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=r:Function("r","regeneratorRuntime = r")(r)}}},r={};function i(e){var t=r[e];if(void 0!==t)return t.exports;var o=r[e]={exports:{}};return n[e](o,o.exports,i),o.exports}i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,i.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var o=Object.create(null);i.r(o);var a={};e=e||[null,t({}),t([]),t(t)];for(var s=2&r&&n;"object"==typeof s&&!~e.indexOf(s);s=t(s))Object.getOwnPropertyNames(s).forEach((e=>a[e]=()=>n[e]));return a.default=()=>n,i.d(o,a),o},i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e,t,n,r,o,a,s,c,u,l,d,f=null!=(e=null==(t=window.__SEGMENT_WRAPPER)?void 0:t.MPI_CONFIG_KEY)?e:"__mpi",p="undefined"!=typeof window,g=function(e){var t,n,r=(null==(t=window)||null==(n=t[f])?void 0:n.segmentWrapper)||{};return e?r[e]:r},h=function(e,t){window[f]=window[f]||{},window[f].segmentWrapper=window[f].segmentWrapper||{},window[f].segmentWrapper[e]=t},m=null==(n=window.__SEGMENT_WRAPPER)?void 0:n.ADOBE_ORG_ID,v=null!=(r=null==(o=window.__SEGMENT_WRAPPER)?void 0:o.DEFAULT_DEMDEX_VERSION)?r:"3.3.0",_=null!=(a=null==(s=window.__SEGMENT_WRAPPER)?void 0:s.TIME_BETWEEN_RETRIES)?a:15,y=null!=(c=null==(u=window.__SEGMENT_WRAPPER)?void 0:u.TIMES_TO_RETRY)?c:80,C=null==(l=window.__SEGMENT_WRAPPER)?void 0:l.TRACKING_SERVER,I={trackingServer:C,trackingServerSecure:C},b=function(){return window.Visitor&&window.Visitor.getInstance(m,I)},S=function(){return d?Promise.resolve(d):new Promise((function(e){!function t(n){if(0===n)return e("");var r=b();return(d=function(e){return e&&e.getMarketingCloudVisitorID()}(r))?e(d):window.setTimeout((function(){return t(--n)}),_)}(y)}))},D=function(){var e=g("getCustomAdobeVisitorId");return"function"==typeof e?e():!0===g("importAdobeVisitorId")?Promise.resolve().then(i.t.bind(i,920,23)).then((function(){return d=S()})):S()};function A(){return A=Object.assign?Object.assign.bind():function(e){for(var t=1;t=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function B(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n3)break}return"."+r.reverse().join(".")},q="https://secure.adnxs.com/getuidj",Y="adit-xandr-id";function X(e){var t,n,r,i,o,a,s,c,u=e.gdprPrivacyValueAdvertising;if(!p)return null;if(u!==L)return t=Y,i=(r=void 0===n?{}:n).path,o=void 0===i?"/":i,a=r.reduceDomain,s=void 0===a||a,r.sameSite,c=s?W():window.location.hostname,document.cookie=t+"=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path="+o+"; domain="+c,null;var l,d=function(e){var t=new RegExp(e+"=([^;]+)").exec(document.cookie);return null!==t?unescape(t[1]):null}(Y),f=(l=d)&&Number(l)>0;return f||(p?window.fetch(q,{credentials:"include"}).then((function(e){return e.json()})).then((function(e){return null==e?void 0:e.uid})):Promise.resolve(null)).then((function(e){"string"==typeof e&&function(e,t,n){var r=void 0===n?{}:n,i=r.maxAge,o=void 0===i?31536e3:i,a=r.path,s=void 0===a?"/":a,c=r.reduceDomain,u=void 0===c||c,l=r.sameSite,d=void 0===l?"Lax":l,f=[e+"="+t,"domain="+(u?W():window.location.hostname),"path="+s,"max-age="+o,"SameSite="+d].join(";");document.cookie=f}(Y,e)})),h("xandrId",d),f?d:null}var J={platform:"web"},K={All:!1},z={All:!0},Q=function(){return A({},J,g("defaultProperties"))},$=function(){var e=M(E().mark((function e(t){var n,r,i,o,a,s;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(n=t.gdprPrivacyValue,r=t.event,!(i=V(n))){e.next=6;break}return e.next=5,D();case 5:o=e.sent;case 6:return a=Z({isGdprAccepted:i,event:r}),s=!o||{marketingCloudVisitorId:o},e.abrupt("return",A({},a,{"Adobe Analytics":s}));case 9:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),Z=function(e){var t=e.isGdprAccepted;return"CMP Submitted"===e.event?z:t?{}:K},ee=function(e){var t=e.context,n=e.xandrId,r=!1!==g("sendXandrId"),i=n&&0!==parseInt(n);if(!r||!i)return{};var o=[].concat((null==t?void 0:t.externalIds)||[],[{collection:"users",encoding:"none",id:n,type:"xandr_id"}]);return{externalIds:o.filter((function(e,t){var n=e.id,r=e.type;return t===o.findIndex((function(e){var t=e.id,i=e.type;return n===t&&r===i}))}))}},te=function(){var e=M(E().mark((function e(t){var n,r,i,o,a,s,c,u,l,d,f,p,g;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=t.event,r=void 0===n?"":n,i=t.context,o=void 0===i?{}:i,e.next=3,F();case 3:return a=e.sent,c=(s=a||{}).analytics,u=s.advertising,l=V(a),e.next=8,Promise.all([$({gdprPrivacyValue:a,event:r}),X({gdprPrivacyValueAdvertising:u})]);case 8:return d=e.sent,f=d[0],p=d[1],l||(o.integrations=A({},null!=(g=o.integrations)?g:{},{Personas:!1,Webhooks:!0,Webhook:!0})),e.abrupt("return",A({},o,!l&&{ip:"0.0.0.0"},ee({context:o,xandrId:p}),{gdpr_privacy:c,gdpr_privacy_advertising:u,integrations:A({},o.integrations,f)}));case 13:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}(),ne=function(e,t,n,r){return void 0===n&&(n={}),new Promise((function(i){var o=function(){var o=M(E().mark((function o(){var a,s,c;return E().wrap((function(o){for(;;)switch(o.prev=o.next){case 0:return o.next=2,te({context:n,event:e});case 2:a=o.sent,s=A({},Q(),t),c=function(){var e=M(E().mark((function e(){var t,n,o=arguments;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r&&r.apply(void 0,o),e.next=3,Promise.all([F()]);case 3:t=e.sent,n=t[0],V(n)?i.apply(void 0,o):i();case 6:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),window.analytics.track(e,s,A({},a,{context:{integrations:A({},a.integrations)}}),c);case 6:case"end":return o.stop()}}),o)})));return function(){return o.apply(this,arguments)}}();o()}))};const re={page:function(e,t,n,r){return void 0===n&&(n={}),n.isPageTrack=!0,ne(e,t,n,r)},identify:function(){var e=M(E().mark((function e(t,n,r,i){var o;return E().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,F();case 2:return o=e.sent,e.abrupt("return",window.analytics.identify(t,V(o)?n:{},r,i));case 4:case"end":return e.stop()}}),e)})));return function(t,n,r,i){return e.apply(this,arguments)}}(),track:ne,reset:function(){return Promise.resolve(window.analytics.reset())}};var ie,oe,ae=null!=(ie=null==(oe=window.__SEGMENT_WRAPPER)?void 0:oe.MPI_PATCH_FIELD)?ie:"__mpi_patch";function se(){var e=window.analytics.track;window.analytics.track=function(){for(var t=arguments.length,n=new Array(t),r=0;r1;De--)for(Se=De;Se<320;)Ae[Se+=De]=1;function Me(e,t){return 4294967296*Math.pow(e,1/t)|0}for(Se=0;Se<64;)Ae[++De]||(Oe[Se]=Me(De,2),Ae[Se++]=Me(De,3));function we(e,t){return e>>>t|e<<-t}var Ee=/\.|\+.*$/g;function ke(e){if("string"!=typeof e)return"";var t=(e=e.toLowerCase()).split(/@/);if(2!==t.length)return"";var n=t[0],r=t[1];return(n=n.replace(Ee,""))+"@"+r}function Te(e){var t=ke(e);return t?function(e){var t=Oe.slice(De=Se=0,8),n=[],r=unescape(encodeURI(e))+"€",i=r.length;for(n[e=--i/4+2|15]=8*i;~i;)n[i>>2]|=r.charCodeAt(i)<<8*~i--;for(i=[];De>>10)+i[Se-7]+(we(r=i[Se-15],7)^we(r,18)^r>>>3)+i[Se-16])+Me.pop()+(we(r=Me[4],6)^we(r,11)^we(r,25))+(r&Me[5]^~r&Me[6])+Ae[Se++];for(Se=8;Se;)t[--Se]+=Me[Se]}for(r="";Se<64;)r+=(t[Se>>3]>>4*(7-Se++)&15).toString(16);return r}(t):""}var Pe,xe,Le,Re,je,Ne,Fe,Ve,Ue,He=function(){var e=g("universalId");if(e)return Ge(),e;var t=g("userEmail");if(t)return e=Te(t),Be(e),Ge(),e;Ge()},Ge=function(){h("universalIdInitialized",!0)},Be=function(e){return h("universalId",e)};p&&((Pe=H())&&U(Pe),function(){var e=H();h("isFirstVisit",!e)}(),function(){if(g("initialized"))return!1;var e=!!window.__tcfapi;return!e&&console.warn("[tcfTracking] window.__tcfapi is not available on client and TCF won't be tracked."),e}()?(h("initialized",!0),window.__tcfapi("addEventListener",2,(function(e,t){var n=e.eventStatus,r=e.purpose;if(!t)return Promise.resolve();if(n===x||n===P){var i=r.consents,o=U(i);if(n===x){var a={analytics:o.analytics,advertising:o.advertising};return!(p&&window.sessionStorage.getItem("didomi-migration"))&&function(e){e.eventId;var t=e.gdprPrivacy;return re.track("CMP Submitted",A({},k,g("tcfTrackDefaultProperties")),{gdpr_privacy:t.analytics,gdpr_privacy_advertising:t.advertising})}({gdprPrivacy:a})}}}))):void 0===(null==(xe=N.get())?void 0:xe.analytics)&&N.set({analytics:j,advertising:j}));try{Ve=He(),Ue=g("userEmail"),p&&(je=(Le={eventName:"USER_DATA_READY",detail:{universalId:Ve,userEmail:Ue}}).eventName,Ne=Le.detail,Fe=void 0===Ne?{}:Ne,window.CustomEvent&&"function"==typeof window.CustomEvent?Re=new window.CustomEvent(je,{detail:Fe}):(Re=document.createEvent("CustomEvent")).initCustomEvent(je,!0,!0,Fe),window.document.dispatchEvent(Re))}catch(Me){console.error("[segment-wrapper] UniversalID couldn't be initialized")}var We=function(){window.analytics.addSourceMiddleware(ye),window.analytics.addSourceMiddleware(de),window.analytics.addSourceMiddleware(ve),window.analytics.addSourceMiddleware(me)};p&&window.analytics&&(window.analytics.ready((function(){var e;(null==window.analytics.user||null==(e=window.analytics.user())?void 0:e.anonymousId())===Ce&&window.analytics.setAnonymousId(null)})),window.analytics.addSourceMiddleware?We():window.analytics.ready(We));const qe=re;var Ye=window;Ye.sui=Ye.sui||{},Ye.sui.analytics=Ye.sui.analytics||qe,Ye.sui.analytics.getAdobeVisitorData=Ye.sui.analytics.getAdobeVisitorData||function(){var e=(b()||{}).version,t=void 0===e?v:e,n=I.trackingServer;return Promise.resolve({trackingServer:n,version:t})},Ye.sui.analytics.getAdobeMCVisitorID=Ye.sui.analytics.getAdobeMCVisitorID||D})()})(); \ No newline at end of file