diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e6e40..1652e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,18 @@ # Changelog +## 6.15.0 +### New +- [browser] New AV Insights method (`media.playbackKill`) to technically stop a playback session +- [browser] `browserId` can now be set from `pdl` +- [browser] Cookie testing (`_cookie_test`) skipped when cookie domain configured + +### Fixes +- [browser] Fixed empty campaign parameters sent with `undefined` value +- [browser] Fixed an issue with empty cookie domain when none configured +- [browser] Manage case where `PA` is not part of `consent.products` + +### Changes +- [dev] Configuration file does not contain the version by default. Version is now added during build + ## 6.14.2 ### Fixes - [react-native] Fixed android user-agent value to prevent default processing exclusion diff --git a/Gruntfile.js b/Gruntfile.js index a43aea8..fc8b74e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,3 +1,4 @@ +const fs = require('fs'); "use strict"; module.exports = function (grunt) { @@ -26,11 +27,22 @@ module.exports = function (grunt) { }, concat: { es5polyfills: { - src: ['polyfills/core-js-bundle-3.6.5.min.js', 'dist/browser/piano-analytics.es5.js'], + src: ['polyfills/core-js-bundle-3.6.4.min.js', 'dist/browser/piano-analytics.es5.js'], dest: 'dist/browser/piano-analytics.es5.js', }, }, }); + grunt.registerTask('get-version', 'Copy version to the configuration', function() { + try { + const packageJsonVersion = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; + let configFile = fs.readFileSync('./src/config.js', 'utf8'); + configFile = configFile.replace(/'version': '.*'/,`'version': '${packageJsonVersion}'`); + fs.writeFileSync('./src/config.js', configFile); + } catch (err) { + console.error(err); + process.exit(1); + } + }); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-contrib-concat'); }; diff --git a/package.json b/package.json index fe02205..f3a209d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "piano-analytics-js", "description": "JavaScript library for Piano Analytics", - "version": "6.14.2", + "version": "6.15.0", "main": "dist/browserless/piano-analytics.cjs.js", "module": "dist/browserless/piano-analytics.esm.js", "browser": "dist/browser/piano-analytics.umd.js", @@ -26,10 +26,10 @@ "prebuild:node": "grunt copy:http-node", "prebuild:react-native": "grunt copy:http-react-native", "clean": "grunt copy:http-clean", - "rollup:browser": "npm run prebuild:browser && rollup --config rollup.config-browser.js && npm run clean", - "rollup:browser-es5": "npm run prebuild:browser-es5 && rollup --config rollup.config-browser-es5.js && grunt concat:es5polyfills && npm run clean", - "rollup:node": "npm run prebuild:node && rollup --config rollup.config-node.js && npm run clean", - "rollup:react-native": "npm run prebuild:react-native && rollup --config rollup.config-react-native.js && npm run clean", + "rollup:browser": "grunt get-version && npm run prebuild:browser && rollup --config rollup.config-browser.js && npm run clean", + "rollup:browser-es5": "grunt get-version && npm run prebuild:browser-es5 && rollup --config rollup.config-browser-es5.js && grunt concat:es5polyfills && npm run clean", + "rollup:node": "grunt get-version && npm run prebuild:node && rollup --config rollup.config-node.js && npm run clean", + "rollup:react-native": "grunt get-version && npm run prebuild:react-native && rollup --config rollup.config-react-native.js && npm run clean", "build": "npm run rollup:browser --omit=dev && npm run rollup:browser-es5 --omit=dev && npm run rollup:node --omit=dev && npm run rollup:react-native --omit=dev", "test": "npm run test:browser && npm run test:browser-es5 && npm run test:node", "test:browser": "npm run rollup:browser && karma start", @@ -37,15 +37,15 @@ "test:node": "npm run rollup:node && node test/node.run.js" }, "devDependencies": { - "@babel/core": "7.24.7", - "@babel/preset-env": "7.24.7", + "@babel/core": "7.24.9", + "@babel/preset-env": "7.24.8", "@rollup/plugin-babel": "6.0.4", "chai": "4.3.8", - "eslint": "9.5.0", + "eslint": "8.57.0", "eslint-config-standard": "17.1.0", "eslint-plugin-import": "2.29.1", "eslint-plugin-node": "11.1.0", - "eslint-plugin-promise": "6.2.0", + "eslint-plugin-promise": "7.0.0", "grunt": "1.6.1", "grunt-contrib-concat": "^2.1.0", "grunt-contrib-copy": "^1.0.0", @@ -54,8 +54,8 @@ "karma-chrome-launcher": "3.2.0", "karma-mocha": "2.0.1", "load-grunt-tasks": "5.1.0", - "mocha": "10.4.0", - "puppeteer": "22.11.2", + "mocha": "10.7.0", + "puppeteer": "22.14.0", "rollup": "2.79.1", "rollup-plugin-eslint": "7.0.0", "rollup-plugin-replace": "2.2.0", diff --git a/polyfills/core-js-bundle-3.6.5.min.js b/polyfills/core-js-bundle-3.6.4.min.js similarity index 100% rename from polyfills/core-js-bundle-3.6.5.min.js rename to polyfills/core-js-bundle-3.6.4.min.js diff --git a/src/.eslintrc.json b/src/.eslintrc.json index 05048c3..77cf001 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -11,8 +11,6 @@ "business/ext/storage/storage.js", "business/ext/data-layer/data-layer.js", "business/ext/consent/consent.js", - "business/privacy/privacy.js", - "business/privacy/privacy-template.js", "business/avinsights.js", "utils/request/http.js", "utils/request/http-templates.js" diff --git a/src/business/avinsights.js b/src/business/avinsights.js index ee31062..13e6e4f 100644 --- a/src/business/avinsights.js +++ b/src/business/avinsights.js @@ -624,6 +624,17 @@ const AVInsights = function (pa) { _resetSession(); }; + _thisMedia.playbackKill = function () { + _timers.initBaseTime(); + _context.isPlaying = false; + _context.isPlaybackActivated = false; + _timers.stopHeartbeatTimer(false); + _timers.stopHeartbeatTimer(true); + _timers.resetProperties(); + _restoreDelayConfiguration(false); + _restoreDelayConfiguration(true); + _resetSession(); + }; _thisMedia.seek = function (oldCursorPosition, newCursorPosition, eventOptions, extraProps) { const processedOldPosition = _utility.value2Number(oldCursorPosition); diff --git a/src/business/content-properties.js b/src/business/content-properties.js new file mode 100644 index 0000000..8076b81 --- /dev/null +++ b/src/business/content-properties.js @@ -0,0 +1,46 @@ +import dataLayer from './ext/data-layer/data-layer'; + +const MAP_DL_PA = [ + ['createdAt', 'content_publication_date'], + ['tags', 'tags_array'] +]; + +function processContentProperties(model) { + const content = dataLayer.get('content'); + for (const propContent in content) { + if (Object.prototype.hasOwnProperty.call(content, propContent)) { + // searching for a DL property name and replacing it with its PA name + const datalayerPropFoundResult = MAP_DL_PA.find((items) => items[0] === propContent); + const propFinalName = datalayerPropFoundResult ? datalayerPropFoundResult[1] : _camelToSnake(`content_${propContent}`); + model.addEventsProperty(propFinalName, content[propContent]); + } + } +} + +function initContentProperties(pa) { + pa.setContentProperty = function (name, value) { + // searching for a PA property name and replacing it with its DL name + const pianoAnalyticsPropFound = MAP_DL_PA.find((items) => items[1] === name); + dataLayer.set('content', { + [pianoAnalyticsPropFound ? pianoAnalyticsPropFound[0] : name]: value + }); + }; + pa.setContentProperties = function (content) { + for (const prop in content) { + if (Object.prototype.hasOwnProperty.call(content, prop)) { + pa.setContentProperty(prop, content[prop]); + } + } + }; +} + +function _camelToSnake(string) { + return string.replace(/[\w]([A-Z])/g, function (m) { + return m[0] + '_' + m[1]; + }).toLowerCase(); +} + +export { + processContentProperties, + initContentProperties +}; diff --git a/src/business/ext/consent/consent.js b/src/business/ext/consent/consent.js index f3d3b30..b82ebad 100644 --- a/src/business/ext/consent/consent.js +++ b/src/business/ext/consent/consent.js @@ -38,7 +38,8 @@ var createBaseConsentStorage = function createBaseConsentStorage(storage2, itemT var checkProperty = createCheckConsentWrapper(config.dataLayer, { items: config.items, type: itemType, - getConsent: getConsent + getConsent: getConsent, + product: config.productName }); var _init = function _init() { var removeOnInit = config.checkConsentOnInit === void 0 ? config.enableAutoRemove : false; @@ -85,7 +86,8 @@ var createTTLChecker = function createTTLChecker(dataLayer) { var checker = createCheckConsentWrapper(config.dataLayer, { items: (_a = {}, _a[name] = "mandatory", _a), type: "localStorage", - getConsent: createConsentWrapper(config) + getConsent: createConsentWrapper(config), + product: config.productName }); return function () { var result = checker(name); @@ -158,7 +160,8 @@ var createCookie = function createCookie(config) { var checkProperty = createCheckConsentWrapper(config.dataLayer, { items: items, type: ITEM_TYPE, - getConsent: getConsent + getConsent: getConsent, + product: config.productName }); var _init = function _init() { var removeOnInit = !!(config.checkConsentOnInit === void 0 ? config.enableAutoRemove : false); @@ -205,7 +208,8 @@ var createBasePropertyWrapper = function createBasePropertyWrapper(itemType, con check: createCheckConsentWrapper(config.dataLayer, { items: config.items, type: itemType, - getConsent: createConsentWrapper(config) + getConsent: createConsentWrapper(config), + product: config.productName }) }; }; diff --git a/src/business/ext/data-layer/data-layer.js b/src/business/ext/data-layer/data-layer.js index 1556bb7..2e194a6 100644 --- a/src/business/ext/data-layer/data-layer.js +++ b/src/business/ext/data-layer/data-layer.js @@ -1,6 +1,6 @@ /** * @license - * Piano Browser SDK-DataLayer@2.9.5. + * Piano Browser SDK-DataLayer@2.11.0. * Copyright 2010-2022 Piano Software Inc. */ import { cookie } from '@piano-sdk/storage'; @@ -191,7 +191,7 @@ var GLOBAL_CONFIG_NAME = 'pdl'; // @ts-ignore var getGlobalConfig$1 = function () { return window[GLOBAL_CONFIG_NAME] || {}; }; -var generateInitProtectedValue = function (_prev, changeConfig) { +var generateInitProtectedValue$3 = function (_prev, changeConfig) { changeConfig({ protect: true // protect rewriting after init (page loading) }); @@ -203,35 +203,13 @@ var generateAnewProtectedValue = function (_prev, changeConfig) { }); return randomStringCxCompatible(); }; -var pageViewId = __assign(__assign({}, createStaticParam()), { init: generateInitProtectedValue, refresh: generateAnewProtectedValue, update: generateAnewProtectedValue, set: function (value, _prevValue, changeConfig) { +var pageViewId = __assign(__assign({}, createStaticParam()), { init: generateInitProtectedValue$3, refresh: generateAnewProtectedValue, update: generateAnewProtectedValue, set: function (value, _prevValue, changeConfig) { changeConfig({ protect: true // protect rewriting after change }); return value; } }); -var browserId = __assign(__assign({}, createBaseParam(null, '_pcid')), { init: function (_cookieInitValue, changeConfig) { - changeConfig({ - protect: true - }); - // Get the value from cookie. If there is no data, then generate a new random string - return _cookieInitValue || randomStringCxCompatible(); - }, - // Need to update value - generate a new value - update: function (_prev, changeConfig) { - changeConfig({ - protect: true - }); - return randomStringCxCompatible(); - }, - // Need to set a specific value - set: function (value, _prev, changeConfig) { - changeConfig({ - protect: true - }); - return value; - } }); - var RESERVED_PRODUCT = 'DL'; var PRODUCTS_LIST = ['PA', 'DMP', 'COMPOSER', 'ID', 'VX', 'ESP', 'SOCIAL_FLOW', RESERVED_PRODUCT]; var PRODUCTS = PRODUCTS_LIST.map(function (name, id) { return ({ @@ -246,11 +224,12 @@ var PRODUCTS_MAP = PRODUCTS.reduce(function (res, _a, index) { // support legacy value PRODUCTS_MAP['social flow'] = PRODUCTS_MAP.SOCIAL_FLOW; PRODUCTS_MAP['Social Flow'] = PRODUCTS_MAP.SOCIAL_FLOW; -var onChangeConfigProducts = onMemo(function () { var _a; return (_a = validateConsentMemo(getGlobalConfig$1().consent)) === null || _a === void 0 ? void 0 : _a.products; }); +var getPdlProductNames = function () { var _a; return (_a = validateConsentMemo(getGlobalConfig$1().consent)) === null || _a === void 0 ? void 0 : _a.products; }; +var onChangePdlProducts = onMemo(getPdlProductNames); var getProducts = (function () { var result = PRODUCTS; return function () { - onChangeConfigProducts(function (config) { + onChangePdlProducts(function (config) { if (config) { result = PRODUCTS.filter(function (product) { return config.includes(product.name) || product.name === RESERVED_PRODUCT; @@ -525,6 +504,10 @@ var getNotAcquiredConsent = function () { }; var actions = ['include', 'exclude', 'obfuscate']; +var validateBrowserId = function (val) { + var length = val && val.length; + return length === 16 || length === 36 ? val : null; +}; var oneOf = function (name, value) { return "\"".concat(name, "\" should be one of ").concat(value.join(', ')); }; // tslint:disable-next-line no-empty var emptyFn = function () { }; @@ -641,6 +624,41 @@ var validateMigration = function (migration, log) { }, {}); }; +var generateInitProtectedValue$2 = function (_cookieInitValue, changeConfig) { + changeConfig({ + protect: true // protect rewriting after init (page loading) + }); + // Get the value from cookie. If there is no data, then generate a new random string + return validateBrowserId(getGlobalConfig$1().browserId || null) || _cookieInitValue || randomStringCxCompatible(); +}; +var browserId = __assign(__assign({}, createBaseParam(null, '_pcid')), { init: generateInitProtectedValue$2, + // Need to update value - generate a new value + update: function (_prev, changeConfig) { + changeConfig({ + protect: true + }); + return randomStringCxCompatible(); + }, + // Need to set a specific value + set: function (value, _prev, changeConfig) { + changeConfig({ + protect: true + }); + return value; + } }); + +// https://developer.mozilla.org/en-US/docs/Web/API/Document/referrer +var generateInitProtectedValue$1 = function () { + return getGlobalConfig$1().referrer || document.referrer; +}; +var referrer = __assign(__assign({}, createStaticParam()), { init: generateInitProtectedValue$1 }); + +var generateInitProtectedValue = function () { + return getGlobalConfig$1().sessionReferrer || document.referrer; +}; +// The same as referrer, but holds initial value for user session +var sessionReferrer = __assign(__assign({}, createStaticParam()), { init: generateInitProtectedValue }); + // opt-in - 0 // essential - 1 // opt-out - 2 @@ -944,6 +962,24 @@ var appendLegacyComposer = function (prevValue) { append(prevValue, 'authors', name('author')); }; +function isArticle(ldJsonPart) { + return ldJsonPart && isString(ldJsonPart['@type']) && ldJsonPart['@type'].includes('Article'); +} +function findArticle(parsedLdJson) { + if (isArray(parsedLdJson)) { + return parsedLdJson.find(function (part) { return isArticle(part); }) || {}; + } + return {}; +} +function readLdJson() { + var ldJsonElements = document.querySelectorAll('script[type="application/ld+json"]'); + if (ldJsonElements) { + var parsedLdJsons = Array.from(ldJsonElements).flatMap(function (element) { return parseJSON(element.innerHTML); }); + return findArticle(parsedLdJsons); + } + return {}; +} + var monthNames = { januar: '01', january: '01', @@ -1087,14 +1123,21 @@ var publishTimeConfig = [ getContent: function (el) { return el.getAttribute('datetime'); } }), takeLast({ - selector: 'time[datetime]', + selector: 'time[itemprop="datePublished"][datetime]', getContent: function (el) { return el.getAttribute('datetime'); } }) ]; // cxenseinternal-modifiedtime -var modifiedTimeConfig = { - selector: 'meta[property="article:modified_time"]' // take first -}; +var modifiedTimeConfig = [ + takeLast({ + attr: ['name', 'property', 'itemprop'], + names: ['article:modified_time', 'datemodified'] + }), + takeLast({ + selector: 'time[itemprop="dateModified"][datetime]', + getContent: function (el) { return el.getAttribute('datetime'); } + }) +]; // cxenseinternal-author var authorConfig = [ { @@ -1150,12 +1193,38 @@ var internalTitle = [ names: ['og:title'] }) ]; +var appendLdJsonCrawler = function (data) { + var maybeArticle = readLdJson(); + if (isArticle(maybeArticle)) { + append(data, 'createdAt', function () { + var datePublished = maybeArticle.datePublished; + return datePublished ? anyDateToISODate(datePublished.toLowerCase()) : null; + }); + append(data, 'modifiedAt', function () { + var dateModified = maybeArticle.dateModified; + return dateModified ? anyDateToISODate(dateModified.toLowerCase()) : null; + }); + append(data, 'authors', function () { + var author = maybeArticle.author; + return author ? (isArray(author) ? author.map(function (a) { return a.name; }).join(', ') : author.name) : null; + }); + append(data, 'keywords', function () { + var keywords = maybeArticle.keywords; + return isString(keywords) && keywords.length <= 1024 ? keywords : null; + }); + append(data, 'title', function () { return maybeArticle.headline; }); + append(data, 'description', function () { return maybeArticle.description; }); + } +}; var appendLegacyCrawler = function (data) { append(data, 'createdAt', function () { var publishTime = readMetaValues(publishTimeConfig); return publishTime ? anyDateToISODate(publishTime.toLowerCase()) : null; }); - append(data, 'modifiedAt', modifiedTimeConfig); + append(data, 'modifiedAt', function () { + var modifiedTime = readMetaValues(modifiedTimeConfig); + return modifiedTime ? anyDateToISODate(modifiedTime.toLowerCase()) : null; + }); append(data, 'authors', authorConfig); append(data, 'keywords', function () { var keywords = readMetaValues(keywordsConfig); // check length 1024 @@ -1179,6 +1248,7 @@ var readMetaElements = function () { queryMetasMemo.refresh(); appendLegacyComposer(data); appendLegacyCrawler(data); + appendLdJsonCrawler(data); return data; }; var content = __assign(__assign({}, createStaticParam(null)), { init: function () { return readMetaElements(); }, refresh: function (prevValue) { @@ -1197,8 +1267,12 @@ var content = __assign(__assign({}, createStaticParam(null)), { init: function ( var proceedValue = function (filter, forEachCb) { keys(filterObjectValues(value, filter)).forEach(forEachCb); }; - proceedValue(isNotEmpty, function (val) { fixedSet.add(val); }); - proceedValue(isEmpty, function (val) { fixedSet.delete(val); }); + proceedValue(isNotEmpty, function (val) { + fixedSet.add(val); + }); + proceedValue(isEmpty, function (val) { + fixedSet.delete(val); + }); return filterObjectValues(__assign(__assign(__assign({}, prevValue), value), { _fixed_: Array.from(fixedSet.values()) }), isNotEmpty); }, get: memo(function (value) { var getValue = __assign({}, value); @@ -1208,7 +1282,7 @@ var content = __assign(__assign({}, createStaticParam(null)), { init: function ( var userSegments = __assign(__assign({}, createBaseParam(null, '_pcus')), { init: function (valueFromCookie) { if (valueFromCookie === void 0) { valueFromCookie = null; } - return valueFromCookie && filterObjectValues(valueFromCookie, function (val) { return isObject(val) && Array.isArray(val.segments); }); + return valueFromCookie && filterObjectValues(valueFromCookie, function (val) { return isObject(val) && isArray(val.segments); }); } }); var PropertiesMap = { @@ -1223,16 +1297,26 @@ var PropertiesMap = { consentModifiers: consentModifiers, purposes: purposes, content: content, - userSegments: userSegments + userSegments: userSegments, + referrer: referrer, + sessionReferrer: sessionReferrer }; var domainExceptions = ['pantheon.io', 'go-vip.net', 'go-vip.co']; +var defaultDomain = (function () { + var _a, _b; + var pdlDefaultDomain = (_b = (_a = getGlobalConfig$1()) === null || _a === void 0 ? void 0 : _a.cookieDefault) === null || _b === void 0 ? void 0 : _b.domain; + if (pdlDefaultDomain === undefined) { + return cookie.getTopLevelDomain(domainExceptions); + } + return pdlDefaultDomain; +})(); var DEFAULT_COOKIE_OPTIONS = { path: '/', expires: 395, samesite: 'lax', secure: window.location.protocol === 'https:', - domain: cookie.getTopLevelDomain(domainExceptions) + domain: defaultDomain }; var createDateByExpires = function (expires) { @@ -1721,6 +1805,64 @@ function _decompress(length, resetValue, getNextValue) { } switch ((bits)) { + case 0: + bits = 0; + maxpower = Math.pow(2, 8); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + c = f(bits); + break; + case 1: + bits = 0; + maxpower = Math.pow(2, 16); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + c = f(bits); + break; + case 2: + return ''; + } + dictionary[3] = c; + w = c; + result.push(c); + while (true) { + if (data.index > length) { + return ''; + } + + bits = 0; + maxpower = Math.pow(2, numBits); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + + switch ((c = bits)) { case 0: bits = 0; maxpower = Math.pow(2, 8); @@ -1735,7 +1877,10 @@ function _decompress(length, resetValue, getNextValue) { bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } - c = f(bits); + + dictionary[dictSize++] = f(bits); + c = dictSize - 1; + enlargeIn--; break; case 1: bits = 0; @@ -1751,73 +1896,12 @@ function _decompress(length, resetValue, getNextValue) { bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } - c = f(bits); + dictionary[dictSize++] = f(bits); + c = dictSize - 1; + enlargeIn--; break; case 2: - return ''; - } - dictionary[3] = c; - w = c; - result.push(c); - while (true) { - if (data.index > length) { - return ''; - } - - bits = 0; - maxpower = Math.pow(2, numBits); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - - switch ((c = bits)) { - case 0: - bits = 0; - maxpower = Math.pow(2, 8); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - - dictionary[dictSize++] = f(bits); - c = dictSize - 1; - enlargeIn--; - break; - case 1: - bits = 0; - maxpower = Math.pow(2, 16); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - dictionary[dictSize++] = f(bits); - c = dictSize - 1; - enlargeIn--; - break; - case 2: - return result.join(''); + return result.join(''); } if (enlargeIn == 0) { @@ -1970,7 +2054,7 @@ var createCookieAssociation = function () { wrapper: cookieByName === null || cookieByName === void 0 ? void 0 : cookieByName[cookieWrapperName], data: {}, update: false, - remove: true, + remove: true }; } groupedData[cookieWrapperName].data[fieldName] = fieldValue; @@ -2182,29 +2266,37 @@ var getConsentModifier = function (itemType, modifierNoStrict, log) { }; var checkMode = function (mode, config) { switch (mode) { - case OPT_IN_MODE: - return true; - case ESSENTIAL_MODE: - return config === ESSENTIAL_CONFIG || config === MANDATORY_CONFIG; - case OPT_OUT_MODE: - return config === MANDATORY_CONFIG; - default: - // TODO util debug console - return true; + case OPT_IN_MODE: + return true; + case ESSENTIAL_MODE: + return config === ESSENTIAL_CONFIG || config === MANDATORY_CONFIG; + case OPT_OUT_MODE: + return config === MANDATORY_CONFIG; + default: + // TODO util debug console + return true; } }; var checkAction = function (action) { switch (action) { - case 'include': - return true; - case 'exclude': - return false; - case 'obfuscate': - return true; + case 'include': + return true; + case 'exclude': + return false; + case 'obfuscate': + return true; } }; var getData = function (action, data) { return (action === 'obfuscate' ? data : null); }; +var checkProductDisabled = function (productName) { + if (productName && productName !== RESERVED_PRODUCT) { + var pdlProducts = getPdlProductNames(); + return !!(pdlProducts && !pdlProducts.includes(productName)); + } + return false; +}; var createCheckConsentWrapper = function (config) { + var productName = config.product || null; var items = Object.assign({}, config.items); var masks = itemsToMask(items); var getConfigByName = function (name) { return items[name] || getByMask(name, masks) || OPTIONAL_CONFIG; }; @@ -2216,7 +2308,7 @@ var createCheckConsentWrapper = function (config) { var getDefaultResult = function () { return names.map(function (cName) { return ({ name: cName, - allowed: !requireConsent + allowed: !requireConsent || checkProductDisabled(productName) }); }); }; var consent = consentValue || config.getConsent(); @@ -2270,10 +2362,6 @@ var localStorageGet = function (name) { return ttl && ttl <= Date.now() ? null : value; }; -var validateBrowserId = function (val) { - var length = val && val.length; - return length === 16 || length === 36 ? val : null; -}; var getMigrationValue = (function () { var cookies = { pa_vid: function (data) { return validateBrowserId(parseJSON(data || '', true) || data); }, @@ -2305,33 +2393,26 @@ var getMigrationValue = (function () { return null; }; })(); -var defaultMigration = [{ ls: '_cX_P' }, 'cX_P']; var migrationMaps = { PA: { - browserId: ['pa_vid', 'atuserid'].concat(defaultMigration) - }, - DMP: { - browserId: defaultMigration + browserId: ['pa_vid', 'atuserid'] } }; -var DEFAULT_MIGRATION = { - browserId: { source: 'DMP' } -}; var migrate = function (_private) { var _a; - var migrationData = __assign(__assign({}, DEFAULT_MIGRATION), validateMigration((_a = getGlobalConfig$1()) === null || _a === void 0 ? void 0 : _a.migration)); + var migrationData = __assign({}, validateMigration((_a = getGlobalConfig$1()) === null || _a === void 0 ? void 0 : _a.migration)); keys(migrationData).forEach(function (propName) { var _a, _b; var param = _private.params.get(propName); - var isDefault = migrationData[propName] === DEFAULT_MIGRATION[propName]; var product = (_a = migrationData[propName]) === null || _a === void 0 ? void 0 : _a.source; var configs = (product && ((_b = migrationMaps[product]) === null || _b === void 0 ? void 0 : _b[propName])) || []; if (param && configs.length) { + var fromGlobalConfig = getGlobalConfig$1()[propName]; var migrationValue = getMigrationValue(configs); - if (migrationValue) { + if (migrationValue && !fromGlobalConfig) { param.readonly = false; _private.updateValues(propName, migrationValue, true); // force update from migrated value - param.readonly = !isDefault; // prohibit overwriting value + param.readonly = true; // prohibit overwriting value } } }); @@ -2785,7 +2866,9 @@ var DataLayer = function (paramsArgs, cookiesArgs, onInit) { checkConsent: checkConsent$1, setConsent: setConsent, getConsent: getConsent, - notAcquiredConsent: getNotAcquiredConsent() + notAcquiredConsent: getNotAcquiredConsent(), + compressLz: compressToEncodedURIComponent, + decompressLz: decompressFromEncodedURIComponent }, get cookies() { return getCookieConfig(); @@ -2815,7 +2898,7 @@ var getCookieProhibition = function (con) { _pprv: !getGlobalConfig$1().requireConsent, _pctx: prohibitForPaProducts, _pcid: prohibitForPaProducts, - _pcus: prohibitForPaProducts, + _pcus: prohibitForPaProducts }; }; var checkConsent = function (_private) { diff --git a/src/business/pageview-id.js b/src/business/pageview-id.js new file mode 100644 index 0000000..68bba7b --- /dev/null +++ b/src/business/pageview-id.js @@ -0,0 +1,41 @@ +import dataLayer from './ext/data-layer/data-layer'; + +let pageviewidProp = 'pageview_id'; +let isManualPageRefresh = null; +let isNotFirstPageView = false; + +function processPageViewId(pa, model) { + for (const event of model.events) { + if (event.name === 'page.display') { + if (isManualPageRefresh === null && isNotFirstPageView) { + isManualPageRefresh = false; + } + if (pa.getConfiguration('enableAutomaticPageRefresh') && isManualPageRefresh === false && isNotFirstPageView) { + dataLayer.refresh(); + } + if (!isNotFirstPageView) { + isNotFirstPageView = true; + } + } + if (pa._privacy.call('isPropAllowed', pageviewidProp) && model.isPropertyAbsentForEvent(pageviewidProp, event)) { + event.data[pageviewidProp] = dataLayer.get('pageViewId'); + } + } +} + +function initPageViewId(pa) { + pa.refresh = function () { + if (isManualPageRefresh === null) { + isManualPageRefresh = true; + } + if (isManualPageRefresh) { + dataLayer.refresh(); + } + }; +} + + +export { + processPageViewId, + initPageViewId +}; diff --git a/src/business/privacy/at-privacy.js b/src/business/privacy/at-privacy.js index 9d668e6..9f01876 100644 --- a/src/business/privacy/at-privacy.js +++ b/src/business/privacy/at-privacy.js @@ -8,7 +8,7 @@ function AtPrivacy(pa) { this._storageKeys = Object.assign(config.legacyKeys, config.storageKeys); this.init = function () { - if (pa.getConfiguration('isLegacyPrivacy')) { + if (pa._privacy.isLegacyPrivacy) { if (BUILD_BROWSER) { window._pac = window._pac || {privacy: []}; preloadTagging(this, window._pac.privacy); @@ -26,9 +26,9 @@ function AtPrivacy(pa) { this.currentMode = mode; pa._storage.getItem(config.storageKey, (function (storedMode) { if (mode === 'optout' || mode === 'no-consent' || mode === 'no-storage') { - pa.setConfiguration('visitorId', this.modes[mode].visitorId); - } else if (pa.getConfiguration('visitorId') === 'OPT-OUT' || pa.getConfiguration('visitorId') === 'no-consent' || pa.getConfiguration('visitorId') === 'no-storage') { - pa.cfg.deleteProperty('visitorId'); + pa._visitorId.value = this.modes[mode].visitorId; + } else if (pa._visitorId.value === 'OPT-OUT' || pa._visitorId.value === 'no-consent' || pa._visitorId.value === 'no-storage') { + pa._visitorId.value = null; } this.filterProps(pa._properties); this.filterKeys(); diff --git a/src/business/privacy/dl-privacy.js b/src/business/privacy/dl-privacy.js index 692a40e..1da3215 100644 --- a/src/business/privacy/dl-privacy.js +++ b/src/business/privacy/dl-privacy.js @@ -40,6 +40,9 @@ function DlPrivacy(pa) { return window.pdl.requireConsent === 'v2'; }; + this.isPAConsentDisabled = function () { + return typeof dataLayer.get('consent')['PA'] === 'undefined'; + }; this.init = function () { this.consentItems = getConsentItems(); this.propertyConsent = consent.createProperty({ @@ -57,7 +60,7 @@ function DlPrivacy(pa) { productName: 'PA', items: this.consentItems.cookieItems }); - if (!pa.getConfiguration('isLegacyPrivacy')) { + if (!pa._privacy.isLegacyPrivacy) { this.initMode(); this.filterKeys(); } @@ -95,12 +98,12 @@ function DlPrivacy(pa) { this.modeMetadata['custom'].visitor_privacy_consent = consentValue; }; this.setAllPurposes = function (mode) { - if(isConsentv2()){ + if (isConsentv2()) { return dataLayer.utils.setConsent(mode); } }; this.setByPurpose = function (purpose, mode, products) { - if(isConsentv2()){ + if (isConsentv2()) { dataLayer.utils.setConsent(purpose, mode, products); } }; @@ -116,19 +119,19 @@ function DlPrivacy(pa) { return this.consentItems; }; this.isPropAllowed = (function (propertyName) { - if (pa.getConfiguration('enableExtendedOptout') === true && this.getMode() === 'opt-out') { + if (this.isPAConsentDisabled() || (pa.getConfiguration('enableExtendedOptout') === true && this.getMode() === 'opt-out')) { return true; } return this.propertyConsent.check(propertyName).allowed; }).bind(this); this.isEventAllowed = (function (eventName) { - if (pa.getConfiguration('enableExtendedOptout') === true && this.getMode() === 'opt-out') { + if (this.isPAConsentDisabled() || (pa.getConfiguration('enableExtendedOptout') === true && this.getMode() === 'opt-out')) { return true; } return this.eventConsent.check(eventName).allowed; }).bind(this); this.isKeyAllowed = (function (storageKey) { - return this.storageConsent.check(storageKey).allowed; + return this.isPAConsentDisabled() ? true : this.storageConsent.check(storageKey).allowed; }).bind(this); this.filterProps = function (properties) { diff --git a/src/business/privacy/index.js b/src/business/privacy/index.js deleted file mode 100644 index a9fa3cd..0000000 --- a/src/business/privacy/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export {DlPrivacy} from './dl-privacy'; -export {AtPrivacy} from './at-privacy'; -export {Privacy} from './privacy'; diff --git a/src/business/privacy/privacy.js b/src/business/privacy/privacy.js index 9c2ebbb..e67a978 100644 --- a/src/business/privacy/privacy.js +++ b/src/business/privacy/privacy.js @@ -1,15 +1,69 @@ +import {AtPrivacy} from './at-privacy'; +import {DlPrivacy} from './dl-privacy'; +import {dataLayer} from '../ext/data-layer/data-layer'; + function Privacy(pa) { + this.isLegacyPrivacy = true; this.modeEnum = { OPTOUT: (() => { - return pa.getConfiguration('isLegacyPrivacy') ? 'optout' : 'opt-out'; + return this.isLegacyPrivacy ? 'optout' : 'opt-out'; })() - } + }; this.call = function (method, ...paramsArray) { - const privacy = pa.getConfiguration('isLegacyPrivacy') ? 'privacy' : 'consent'; + const privacy = this.isLegacyPrivacy ? 'privacy' : 'consent'; return pa[privacy][method].apply(pa[privacy], paramsArray); + }; +} + +function initPrivacy(pa) { + // wrapper between legacy vs consent privacies for internal use + pa._privacy = new Privacy(pa); + if (BUILD_BROWSER) { + if (typeof window.pdl === 'undefined') { + window.pdl = { + migration: { + browserId: { + source: 'PA' + } + }, + cookies: { + storageMode: 'fixed' + } + }; + } else { + if (window.pdl.requireConsent) { + pa._privacy.isLegacyPrivacy = false; + } + if (typeof window.pdl.cookies === 'undefined') { + window.pdl.cookies = { + storageMode: 'fixed' + }; + } else if (window.pdl.cookies && typeof window.pdl.cookies.storageMode === 'undefined') { + window.pdl.cookies.storageMode = 'fixed'; + } + } + dataLayer.init({ + cookieDefault: { + domain: pa.getConfiguration('cookieDomain') || null, + secure: pa.getConfiguration('cookieSecure'), + path: pa.getConfiguration('cookiePath'), + samesite: pa.getConfiguration('cookieSameSite') + }, + cookies: { + _pcid: { + expires: pa.getConfiguration('storageLifetimeVisitor') + } + } + }); + } + // public privacy api (deprecated for browser tagging) + pa.privacy = new AtPrivacy(pa); + if (BUILD_BROWSER) { + // public consent api (new browser tagging for privacy) + pa.consent = new DlPrivacy(pa); } } export { - Privacy + initPrivacy }; diff --git a/src/business/user-agent.js b/src/business/user-agent.js new file mode 100644 index 0000000..4331b1b --- /dev/null +++ b/src/business/user-agent.js @@ -0,0 +1,91 @@ +function getUserAgent(pa, model) { + return new Promise((resolve) => { + try { + if (pa.getConfiguration('allowHighEntropyClientHints')) { + window.navigator.userAgentData.getHighEntropyValues([ + 'architecture', + 'bitness', + 'brands', + 'mobile', + 'model', + 'platform', + 'platformVersion', + 'uaFullVersion', + 'fullVersionList' + ]) + .then(function (userAgentData) { + _addUserAgentMetadata(model, userAgentData); + }) + .finally(function () { + resolve(); + }); + } else { + const ua = { + 'brands': window.navigator.userAgentData.brands, + 'platform': window.navigator.userAgentData.platform, + 'mobile': window.navigator.userAgentData.mobile + }; + _addUserAgentMetadata(model, ua); + resolve(); + } + } catch (e) { + resolve(); + } + }); +} + +function _addUserAgentMetadata(model, ua) { + const properties = [ + { + metric: 'brands', + property: 'ch_ua' + }, + { + metric: 'architecture', + property: 'ch_ua_arch' + }, + { + metric: 'bitness', + property: 'ch_ua_bitness' + }, + { + metric: 'fullVersionList', + property: 'ch_ua_full_version_list' + }, + { + metric: 'mobile', + property: 'ch_ua_mobile' + }, + { + metric: 'model', + property: 'ch_ua_model' + }, + { + metric: 'platform', + property: 'ch_ua_platform' + }, + { + metric: 'platformVersion', + property: 'ch_ua_platform_version' + }, + { + metric: 'uaFullVersion', + property: 'ch_ua_full_version' + } + ]; + if (_isDefined(ua)) { + for (let i = 0; i < properties.length; i++) { + if (_isDefined(ua[properties[i].metric])) { + model.addEventsProperty(properties[i].property, ua[properties[i].metric]); + } + } + } +} + +function _isDefined(variable) { + return typeof variable !== 'undefined'; +} + +export { + getUserAgent +}; diff --git a/src/business/user.js b/src/business/user.js index d26a93e..87c1749 100644 --- a/src/business/user.js +++ b/src/business/user.js @@ -1,6 +1,6 @@ function User(pa) { const storageUser = pa.getConfiguration('storageUser'); - this.setUser = function (id, category, enableStorage) { + pa.setUser = function (id, category, enableStorage) { const _user = { id: id, category: category @@ -18,7 +18,7 @@ function User(pa) { pa._privacy.call('setItem', storageUser, _user, expirationDate); } }; - this.getUser = function (callback) { + pa.getUser = function (callback) { pa._storage.getItem(storageUser, function (data) { let userData = data; if (!data && pa._properties['user_id']) { @@ -30,7 +30,7 @@ function User(pa) { callback && callback(userData); }); }; - this.deleteUser = function (callback) { + pa.deleteUser = function (callback) { pa.deleteProperty('user_id'); pa.deleteProperty('user_category'); pa.deleteProperty('user_recognition'); diff --git a/src/business/visitor-id.js b/src/business/visitor-id.js new file mode 100644 index 0000000..98bbfd5 --- /dev/null +++ b/src/business/visitor-id.js @@ -0,0 +1,40 @@ +import {dataLayer} from './ext/data-layer/data-layer'; + +function VisitorId(pa) { + this.value = null; + pa.getVisitorId = (function (callback) { + let forcedValue = this.value; + let result = null; + if (BUILD_BROWSER) { + result = _processCallbackIfPresent(forcedValue || dataLayer.get('browserId'), callback); + } else { + pa._storage.getItem(pa.getConfiguration('storageVisitor'), (function (storedValue) { + result = _processCallbackIfPresent(forcedValue || storedValue, callback); + }).bind(pa)); + } + if (typeof callback === 'undefined') { + return result; + } + }).bind(this); + pa.setVisitorId = (function (value) { + this.value = value; + const expirationDate = new Date(); + expirationDate.setTime(expirationDate.getTime() + (pa.getConfiguration('storageLifetimeVisitor') * 24 * 60 * 60 * 1000)); + pa._privacy.call('setItem', pa.getConfiguration('storageVisitor'), value, expirationDate, function () { + if (BUILD_BROWSER) { + dataLayer.updateMigration(); + } + }); + }).bind(this); +} + +function _processCallbackIfPresent(value, cb) { + if (cb) { + cb(value); + } + return value; +} + +export { + VisitorId +}; diff --git a/src/config.js b/src/config.js index 387fb5f..008a66f 100644 --- a/src/config.js +++ b/src/config.js @@ -23,7 +23,7 @@ export default { ], 'storageVisitor': 'pa_vid', 'storageUser': 'pa_user', - 'version': '6.14.2', + 'version': '6.15.0', 'minHeartbeat': 5, 'minBufferingHeartbeat': 1, 'queueVarName': '_paq', @@ -312,4 +312,4 @@ export default { } } } -}; \ No newline at end of file +}; diff --git a/src/core/PianoAnalytics.js b/src/core/PianoAnalytics.js index 052ddf8..ccf3265 100644 --- a/src/core/PianoAnalytics.js +++ b/src/core/PianoAnalytics.js @@ -7,12 +7,14 @@ import { propertiesStep, sendStep, userStep, visitorStep } from './steps/index'; import {Storage} from '../storage/storage'; -import {AtPrivacy, DlPrivacy, Privacy} from '../business/privacy/index'; import {User} from '../business/user'; import {cloneObject} from '../utils/index'; import {preloadTagging} from '../business/preload'; import {AVInsights} from '../business/avinsights'; -import {dataLayer} from '../business/ext/data-layer/data-layer'; +import {VisitorId} from '../business/visitor-id'; +import {initPageViewId} from '../business/pageview-id'; +import {initContentProperties} from '../business/content-properties'; +import {initPrivacy} from '../business/privacy/privacy'; function PianoAnalytics(configuration) { _initConfig(this, configuration); @@ -22,10 +24,13 @@ function PianoAnalytics(configuration) { this._sendEvent = _sendEvent; this._setProperty = _setProperty; this._deleteProperty = _deleteProperty; - _initPrivacy(this); + this._visitorId = new VisitorId(this); + initPrivacy(this); this.user = new User(this); AVInsights(this); if (BUILD_BROWSER) { + initPageViewId(this); + initContentProperties(this); _runAsyncTagging(this); } } @@ -46,56 +51,6 @@ function _initConfig(pa, configuration) { } } -function _initPrivacy(pa) { - pa.setConfiguration('isLegacyPrivacy', true); - if (BUILD_BROWSER) { - if (typeof window.pdl === 'undefined') { - window.pdl = { - migration: { - browserId: { - source: 'PA' - } - }, - cookies: { - storageMode: 'fixed' - } - }; - } else { - if (window.pdl.requireConsent) { - pa.setConfiguration('isLegacyPrivacy', false); - } - if (typeof window.pdl.cookies === 'undefined') { - window.pdl.cookies = { - storageMode: 'fixed' - }; - } else if (window.pdl.cookies && typeof window.pdl.cookies.storageMode === 'undefined') { - window.pdl.cookies.storageMode = 'fixed'; - } - } - dataLayer.init({ - cookieDefault: { - domain: pa.getConfiguration('cookieDomain'), - secure: pa.getConfiguration('cookieSecure'), - path: pa.getConfiguration('cookiePath'), - samesite: pa.getConfiguration('cookieSameSite') - }, - cookies: { - _pcid: { - expires: pa.getConfiguration('storageLifetimeVisitor') - } - } - }); - } - // public privacy api (deprecated for browser tagging) - pa.privacy = new AtPrivacy(pa); - if (BUILD_BROWSER) { - // public consent api (new browser tagging for privacy) - pa.consent = new DlPrivacy(pa); - } - // apis wrapper for internal use - pa._privacy = new Privacy(pa); -} - function _runAsyncTagging(pa) { const asyncName = pa.getConfiguration('queueVarName'); window[asyncName] = window[asyncName] || []; @@ -148,13 +103,6 @@ function _deleteProperty(pa, property) { pa._queue.next(); } -function _processCallbackIfPresent(value, cb) { - if (cb) { - cb(value); - } - return value; -} - PianoAnalytics.prototype.setProperty = function (property, value, options) { this._queue.push(['_setProperty', this, property, value, options]); }; @@ -174,70 +122,6 @@ PianoAnalytics.prototype.sendEvent = function (eventName, eventData, options) { PianoAnalytics.prototype.sendEvents = function (events, options) { this._queue.push(['_sendEvent', events, options]); }; -PianoAnalytics.prototype.getVisitorId = function (callback) { - let forcedValue = this.getConfiguration('visitorId') || null; - let result = null; - if (BUILD_BROWSER) { - result = _processCallbackIfPresent(forcedValue || dataLayer.get('browserId'), callback); - } else { - this._storage.getItem(this.getConfiguration('storageVisitor'), (function (storedValue) { - result = _processCallbackIfPresent(forcedValue || storedValue, callback); - }).bind(this)); - } - if (typeof callback === 'undefined') { - return result; - } -}; -PianoAnalytics.prototype.setVisitorId = function (value) { - this.setConfiguration('visitorId', value); - const expirationDate = new Date(); - expirationDate.setTime(expirationDate.getTime() + (this.getConfiguration('storageLifetimeVisitor') * 24 * 60 * 60 * 1000)); - this._privacy.call('setItem', this.getConfiguration('storageVisitor'), value, expirationDate, function () { - if (BUILD_BROWSER) { - dataLayer.updateMigration(); - } - }); -}; -PianoAnalytics.prototype.setUser = function (id, category, enableStorage) { - this.user.setUser(id, category, enableStorage); -}; -PianoAnalytics.prototype.getUser = function (callback) { - this.user.getUser(callback); -}; -PianoAnalytics.prototype.deleteUser = function () { - this.user.deleteUser(); -}; PianoAnalytics.prototype.PA = PianoAnalytics; -if (BUILD_BROWSER) { - PianoAnalytics.prototype.refresh = function () { - if (this.getConfiguration('isManualPageRefresh') === null) { - this.setConfiguration('isManualPageRefresh', true); - } - if (this.getConfiguration('isManualPageRefresh')) { - dataLayer.refresh(); - } - }; - PianoAnalytics.prototype.setContentProperty = function (name, value) { - const MAP_PA_DL = { - 'content_publication_date': 'createdAt', - 'tags_array': 'tags' - }; - const temp = {}; - if (name === 'content_publication_date' || name === 'tags_array') { - temp[MAP_PA_DL[name]] = value; - } else { - temp[name] = value; - } - dataLayer.set('content', temp); - }; - PianoAnalytics.prototype.setContentProperties = function (content) { - for (const prop in content) { - if (Object.prototype.hasOwnProperty.call(content, prop)) { - this.setContentProperty(prop, content[prop]); - } - } - }; -} - export default PianoAnalytics; diff --git a/src/core/steps/campaigns.step.js b/src/core/steps/campaigns.step.js index f03b3d3..409952b 100644 --- a/src/core/steps/campaigns.step.js +++ b/src/core/steps/campaigns.step.js @@ -6,7 +6,7 @@ function _getQueryStringParameters(prefix, str, destPrefix) { let match = regex.exec(str); while (match !== null) { if (match[1].indexOf(prefix) === 0) { - campaign[destPrefix + match[1].substring(prefix.length)] = window.decodeURIComponent(match[2]); + campaign[destPrefix + match[1].substring(prefix.length)] = match[2] === undefined ? '' : window.decodeURIComponent(match[2]); } match = regex.exec(str); } diff --git a/src/core/steps/metadata.step.js b/src/core/steps/metadata.step.js index cbb89b8..b78f852 100644 --- a/src/core/steps/metadata.step.js +++ b/src/core/steps/metadata.step.js @@ -1,21 +1,8 @@ import {nextStep} from './utils/index'; import {dataLayer} from '../../business/ext/data-layer/data-layer'; - -function _parseLanguage(language, separators) { - for (const separator of separators) { - if (language.indexOf(separator) > -1) { - const splitted = language.split(separator); - return [splitted[0], splitted.slice(1).join(separator)]; - } - } - return ['', '']; -} - -function _camelToSnake(string) { - return string.replace(/[\w]([A-Z])/g, function (m) { - return m[0] + '_' + m[1]; - }).toLowerCase(); -} +import {processPageViewId} from '../../business/pageview-id'; +import {processContentProperties} from '../../business/content-properties'; +import {getUserAgent} from '../../business/user-agent'; function metadataStep(pa, model, nextSteps) { model.addEventsProperty('event_collection_platform', BUILD_BROWSER ? 'js' : 'js-browserless'); @@ -24,62 +11,21 @@ function metadataStep(pa, model, nextSteps) { model.addEventsProperty('device_timestamp_utc', date.getTime()); model.addEventsProperty('device_local_hour', date.getTime()); model.addEventsProperty('device_hour', date.getHours()); - if (BUILD_BROWSER) { - const manualConfig = 'isManualPageRefresh', - pageviewidProp = 'pageview_id', - notFirstPageConfig = '_isNotFirstPageView'; - for (const event of model.events) { - if (event.name === 'page.display') { - if (pa.getConfiguration(manualConfig) === null && pa.getConfiguration(notFirstPageConfig)) { - pa.setConfiguration(manualConfig, false); - } - - if (pa.getConfiguration('enableAutomaticPageRefresh') && pa.getConfiguration(manualConfig) === false && pa.getConfiguration(notFirstPageConfig)) { - dataLayer.refresh(); - } - if (!pa.getConfiguration(notFirstPageConfig)) { - pa.setConfiguration(notFirstPageConfig, true); - } - - } - if (pa._privacy.call('isPropAllowed', pageviewidProp) && model.isPropertyAbsentForEvent(pageviewidProp, event)) { - event.data[pageviewidProp] = dataLayer.get('pageViewId'); - } - } - + processPageViewId(pa, model); + processContentProperties(model); try { const cookieCreationDate = new Date((new Date(dataLayer.cookies._pcid.fixedAt[0])).setUTCHours(0, 0, 0, 0)).toISOString(); model.addEventsProperty('cookie_creation_date', cookieCreationDate); - } catch (e) { /* empty */ - } - const content = dataLayer.get('content'); - for (const propContent in content) { - if (Object.prototype.hasOwnProperty.call(content, propContent)) { - const MAP_PA_DL = { - 'createdAt': 'content_publication_date', - 'tags': 'tags_array' - }; - const propFinalName = (propContent === 'createdAt' || propContent === 'tags') ? MAP_PA_DL[propContent] : _camelToSnake(`content_${propContent}`); - model.addEventsProperty(propFinalName, content[propContent]); - } + } catch (e) { + /* empty */ } - model.addEventsProperty('has_access', dataLayer.get('userStatus')); model.addEventsProperty('device_screen_width', window.screen.width); model.addEventsProperty('device_screen_height', window.screen.height); - model.addEventsProperty('device_display_width', - window.innerWidth || - document.documentElement && document.documentElement.clientWidth ? - document.documentElement.clientWidth : '' - ); - model.addEventsProperty('device_display_height', - window.innerHeight || - document.documentElement && document.documentElement.clientHeight ? - document.documentElement.clientHeight : '' - ); - const language = window.navigator ? (window.navigator.language || window.navigator.userLanguage) : ''; - const languageSplitted = _parseLanguage(language, ['-', '_']); + model.addEventsProperty('device_display_width', _getInnerOrClientSize('Width')); + model.addEventsProperty('device_display_height', _getInnerOrClientSize('Height')); + const languageSplitted = _parseLanguage(['-', '_']); model.addEventsProperty('browser_language', languageSplitted[0]); model.addEventsProperty('browser_language_local', languageSplitted[1]); model.addEventsProperty('previous_url', document.referrer || ''); @@ -90,93 +36,28 @@ function metadataStep(pa, model, nextSteps) { if (eventUrlWithQueryString || (model.getConfiguration('addEventURL') === 'withoutQS')) { model.addEventsProperty('event_url_full', eventUrlWithQueryString ? window.location.href.split('#')[0] : `${window.location.protocol}//${window.location.host}${window.location.pathname}`); } - - try { - if (pa.getConfiguration('allowHighEntropyClientHints')) { - window.navigator.userAgentData.getHighEntropyValues([ - 'architecture', - 'bitness', - 'brands', - 'mobile', - 'model', - 'platform', - 'platformVersion', - 'uaFullVersion', - 'fullVersionList' - ]) - .then(function (userAgentData) { - _addUserAgentMetadata(model, userAgentData); - }) - .finally(function () { - nextStep(pa, model, nextSteps); - }); - } else { - const ua = { - 'brands': window.navigator.userAgentData.brands, - 'platform': window.navigator.userAgentData.platform, - 'mobile': window.navigator.userAgentData.mobile - }; - _addUserAgentMetadata(model, ua); - nextStep(pa, model, nextSteps); - } - } catch (e) { + getUserAgent(pa, model).finally(() => { nextStep(pa, model, nextSteps); - } + }); } else { nextStep(pa, model, nextSteps); } } -function _isDefined(variable) { - return typeof variable !== 'undefined'; -} - -function _addUserAgentMetadata(model, ua) { - const properties = [ - { - metric: 'brands', - property: 'ch_ua' - }, - { - metric: 'architecture', - property: 'ch_ua_arch' - }, - { - metric: 'bitness', - property: 'ch_ua_bitness' - }, - { - metric: 'fullVersionList', - property: 'ch_ua_full_version_list' - }, - { - metric: 'mobile', - property: 'ch_ua_mobile' - }, - { - metric: 'model', - property: 'ch_ua_model' - }, - { - metric: 'platform', - property: 'ch_ua_platform' - }, - { - metric: 'platformVersion', - property: 'ch_ua_platform_version' - }, - { - metric: 'uaFullVersion', - property: 'ch_ua_full_version' - } - ]; - if (_isDefined(ua)) { - for (let i = 0; i < properties.length; i++) { - if (_isDefined(ua[properties[i].metric])) { - model.addEventsProperty(properties[i].property, ua[properties[i].metric]); - } +function _parseLanguage(separators) { + const language = window.navigator ? (window.navigator.language || window.navigator.userLanguage) : ''; + for (const separator of separators) { + if (language.indexOf(separator) > -1) { + const splitted = language.split(separator); + return [splitted[0], splitted.slice(1).join(separator)]; } } + return ['', '']; +} + +function _getInnerOrClientSize(side) { + return (window[`inner${side}`] || document.documentElement && document.documentElement[`client${side}`] ? document.documentElement[`client${side}`] : ''); } + export {metadataStep}; diff --git a/src/core/steps/visitor.step.js b/src/core/steps/visitor.step.js index 4f5e0e5..b828db5 100644 --- a/src/core/steps/visitor.step.js +++ b/src/core/steps/visitor.step.js @@ -5,13 +5,13 @@ import {dataLayer} from '../../business/ext/data-layer/data-layer'; function visitorStep(pa, model, nextSteps) { pa._storage.getItem(model.getConfiguration('storageVisitor'), function (storedValue) { if (model.getConfiguration('isVisitorClientSide')) { - model.visitorId = model.getConfiguration('visitorId') || (BUILD_BROWSER ? dataLayer.get('browserId') : (storedValue || uuid.v4())); + model.visitorId = pa._visitorId.value || (BUILD_BROWSER ? dataLayer.get('browserId') : (storedValue || uuid.v4())); if(BUILD_BROWSER){ - if(!model.getConfiguration('isLegacyPrivacy') && pa.consent.getMode() === 'opt-out'){ + if(!pa._privacy.isLegacyPrivacy && pa.consent.getMode() === 'opt-out'){ model.visitorId = 'OPT-OUT'; } } - const isNotForcedValue = model.visitorId !== 'OPT-OUT' && model.visitorId !== 'no-consent' && model.visitorId !== 'no-storage' && model.visitorId !== model.getConfiguration('visitorId'); + const isNotForcedValue = model.visitorId !== 'OPT-OUT' && model.visitorId !== 'no-consent' && model.visitorId !== 'no-storage' && model.visitorId !== pa._visitorId.value; if (BUILD_BROWSER) { if (model.visitorId !== dataLayer.get('browserId') && isNotForcedValue) { diff --git a/test/browser/campaigns.js b/test/browser/campaigns.js index d790c3f..45cf883 100644 --- a/test/browser/campaigns.js +++ b/test/browser/campaigns.js @@ -43,6 +43,32 @@ describe('Campaigns :', function () { } }); }); + it('Should add a campaign from querystring with empty values', function (done) { + window.location.href = baseURL + '&at_source=&at_medium=mymedium&at_campaign=mycampaign&at_platform=&at_creation=&at_variant=myvariant&at_network=&at_term=myterm'; + globalPA.sendEvent('toto', {}, { + onBeforeSend: function (pa, model) { + Utility.promiseThrowCatcher(done, function () { + expect(model.build.data.events[0].data['src_source']).to.equal(''); + expect(model.build.data.events[0].data['src_medium']).to.equal('mymedium'); + expect(model.build.data.events[0].data['src_campaign']).to.equal('mycampaign'); + expect(model.build.data.events[0].data['src_platform']).to.equal(''); + expect(model.build.data.events[0].data['src_creation']).to.equal(''); + expect(model.build.data.events[0].data['src_variant']).to.equal('myvariant'); + expect(model.build.data.events[0].data['src_network']).to.equal(''); + expect(model.build.data.events[0].data['src_term']).to.equal('myterm'); + expect(model.build.data.events[0].data['utm_source']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_medium']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_campaign']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_platform']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_creation']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_variant']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_network']).to.equal(undefined); + expect(model.build.data.events[0].data['utm_term']).to.equal(undefined); + done(); + }); + } + }); + }); it('Should add a campaign from querystring with uri encoded-decoded values', function (done) { window.location.href = baseURL + '&at_source=mysource%26%C3%A9%22(-%C3%A8_%C3%A7%C3%A0)%3D%7D%5D%40%5E%60%7C%5B%7B%23~%C2%B2%2C%3B%3A!%C3%B9*%5E%24%3F.%2F%C2%A7%25%C2%B5%C2%A8%C2%A3%2B%C2%B0encodedvalue&at_medium=mymedium&at_campaign=mycampaign%26%C3%A9%22(-%C3%A8_%C3%A7%C3%A0)%3D%7D%5D%40%5E%60%7C%5B%7B%23~%C2%B2%2C%3B%3A!%C3%B9*%5E%24%3F.%2F%C2%A7%25%C2%B5%C2%A8%C2%A3%2B%C2%B0encodedvalue&at_platform=myplatform&at_creation=mycreation&at_variant=myvariant&at_network=mynetwork&at_term=myterm%26%C3%A9%22(-%C3%A8_%C3%A7%C3%A0)%3D%7D%5D%40%5E%60%7C%5B%7B%23~%C2%B2%2C%3B%3A!%C3%B9*%5E%24%3F.%2F%C2%A7%25%C2%B5%C2%A8%C2%A3%2B%C2%B0encodedvalue'; globalPA.sendEvent('toto', {}, { @@ -109,16 +135,16 @@ describe('Campaigns :', function () { }); }); it('Should add an utm campaign from querystring', function (done) { - window.location.href = baseURL + '&utm_source=mysource&utm_medium=mymedium&utm_campaign=mycampaign&utm_platform=myplatform&utm_creation=mycreation&utm_variant=myvariant&utm_network=mynetwork&utm_term=myterm'; + window.location.href = baseURL + '&utm_source=mysource&utm_medium=mymedium&utm_campaign=mycampaign&utm_platform=&utm_creation=mycreation&utm_variant=&utm_network=mynetwork&utm_term=myterm'; globalPA.sendEvent('toto', {}, { onBeforeSend: function (pa, model) { Utility.promiseThrowCatcher(done, function () { expect(model.build.data.events[0].data['utm_source']).to.equal('mysource'); expect(model.build.data.events[0].data['utm_medium']).to.equal('mymedium'); expect(model.build.data.events[0].data['utm_campaign']).to.equal('mycampaign'); - expect(model.build.data.events[0].data['utm_platform']).to.equal('myplatform'); + expect(model.build.data.events[0].data['utm_platform']).to.equal(''); expect(model.build.data.events[0].data['utm_creation']).to.equal('mycreation'); - expect(model.build.data.events[0].data['utm_variant']).to.equal('myvariant'); + expect(model.build.data.events[0].data['utm_variant']).to.equal(''); expect(model.build.data.events[0].data['utm_network']).to.equal('mynetwork'); expect(model.build.data.events[0].data['utm_term']).to.equal('myterm'); expect(model.build.data.events[0].data['src_source']).to.equal(undefined); diff --git a/test/utils.js b/test/utils.js index 5ad3f92..c58f8f2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -33,6 +33,7 @@ } } }; + // this is to properly catch throwing expects happening while using browser promise window.navigator.userAgentData.getHighEntropyValues exports.promiseThrowCatcher = function (done, expects) { try { expects();