diff --git a/src/constants/application-config.js b/src/constants/application-config.js index a72b306d3..6a747617f 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -31,6 +31,7 @@ const applicationConfig = { "https://outlook.office.com/calendar/deeplink/compose?path=/calendar/action/compose&rru=addevent", FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging", AFFILIATE_COOKIE_CONTAINING_NAME: "_Track", + COOKIE_NAME_LENGTH_MAX_LIMIT: 44, }; export { applicationConfig }; diff --git a/src/constants/cookie-names.js b/src/constants/cookie-names.js index d62934cf5..72e6168fb 100644 --- a/src/constants/cookie-names.js +++ b/src/constants/cookie-names.js @@ -10,6 +10,9 @@ const cookieNames = { DXDEV: "dxdev", SESSION_ID: "sid", SESSION_KEY: "skey", + + //Commission Junction Cookie + CJE: "cje", }; const cookieExpirations = { @@ -17,6 +20,8 @@ const cookieExpirations = { DXDEV: convertToSeconds({ years: 1 }), FUNNEL_USER_ID: convertToSeconds({ weeks: 1 }), FUNNEL_SESSION_KEY: convertToSeconds({ minutes: 30 }), + CJE: convertToSeconds({ days: 395 }), + NON_CJ: convertToSeconds({ days: 100000 }), }; export { cookieNames, cookieExpirations }; diff --git a/src/constants/query-strings.js b/src/constants/query-strings.js index a21d93200..4a5c47943 100644 --- a/src/constants/query-strings.js +++ b/src/constants/query-strings.js @@ -36,6 +36,13 @@ const queryStrings = { VIN_SELECTION: "vinselection", SERVICE_PACKAGE: "servicepackage", NUMBER_OF_CHIPS: "numberofchips", + UTM_SOURCE: "_source", + UTM_MEDIUM: "_medium", + UTM_CAMPAIGN: "_campaign", + CJ_EVENT: "cjevent", + CJUNCTION: "cjunction", + ORGANIC: "organic", + ORGANIC_SOCIAL: "organic_social", }; export { queryStrings }; diff --git a/src/helpers/heritage-integration/cookie-helper.js b/src/helpers/heritage-integration/cookie-helper.js index 1b7b551ca..0bd4ae563 100644 --- a/src/helpers/heritage-integration/cookie-helper.js +++ b/src/helpers/heritage-integration/cookie-helper.js @@ -1,7 +1,9 @@ import { cookieNames, cookieExpirations } from "@/constants/cookie-names"; import store from "@/store"; import { applicationConfig } from "@/constants/application-config"; - +import { getQuerystringParameter } from "@/helpers/querystring-helper"; +import { queryStrings } from "@/constants/query-strings"; +//import { cookieParameters } from "@/constants/cookie-parameters"; /* Will update the cookie if present, or create a new one if not. */ @@ -221,6 +223,79 @@ export function getAffiliateCookies() { return affiliateCookies; } +export function setupAdvertiserTracking() { + var utmSource = getQuerystringParameter(queryStrings.UTM_SOURCE); + var utmMedium = getQuerystringParameter(queryStrings.UTM_MEDIUM); + var utmCampaign = getQuerystringParameter(queryStrings.UTM_CAMPAIGN); + const cjEvent = getQuerystringParameter(queryStrings.CJ_EVENT); + // helper check for if an object is defined (but maybe falsey) + const isDefined = (x) => x !== null && x !== undefined; + if (isDefined(cjEvent) != "") { + const cjeCookieName = cookieNames.CJE; + if (!isCookieSet(cjeCookieName)) { + createCJEventCookie(cjEvent, cjeCookieName); + } + } + //Firstly, Can't continue without utm-source or utm-medium. + //Secondly, Ignore the advertiser for CJ-affiliate-click and Email-click. + if ( + !isDefined(utmSource) || + isDefined(utmSource) == "" || + !isDefined(utmMedium) || + isDefined(utmMedium) == "" || + utmSource?.toLowerCase() == queryStrings.CJUNCTION || + utmMedium?.toLowerCase() == queryStrings.EMAIL || + utmMedium?.toLowerCase() == queryStrings.ORGANIC || + utmMedium?.toLowerCase() == queryStrings.ORGANIC_SOCIAL + ) { + return; + } + //UTC date time format yyyy-MM-dd/HH:mm:ss + const currentDateTime = new Date().toISOString().replace(/T/, "/").replace(/\..+/, ""); + + //Format cookie, by removing spaces and special characters. + utmSource = FormatCookieElementName(utmSource); + utmMedium = FormatCookieElementName(utmMedium); + utmCampaign = FormatCookieElementName(utmCampaign); + + var cookieNameBuilder = utmSource + "_" + utmMedium; + //Since utmCampaign is optional, ignore the rest. + if (isDefined(utmCampaign) && utmCampaign != "") { + cookieNameBuilder = cookieNameBuilder + "_" + utmCampaign; + } + const cookieName = + (cookieNameBuilder.length > applicationConfig.COOKIE_NAME_LENGTH_MAX_LIMIT + ? cookieNameBuilder.substring(0, applicationConfig.COOKIE_NAME_LENGTH_MAX_LIMIT) + : cookieNameBuilder) + applicationConfig.AFFILIATE_COOKIE_CONTAINING_NAME; + + if (!isCookieSet(cookieName)) { + const values = [ + `utm_source= ${utmSource}`, + `utm_medium= ${utmMedium}`, + `utm_campaign= ${utmCampaign}`, + `timestamp= ${currentDateTime}`, + `tagEvent= ${cjEvent}`, + ]; + let valueString = values.join("&"); + createOrUpdateCookie(cookieName, valueString, { + useDefaultFunnelCookieAttributes: true, + maxAge: cookieExpirations.NON_CJ, + }); + } else { + const cookieValue = getCookieValueByName(cookieName); + if (cookieValue) { + let cookieValueObj = convertStringToObject(cookieValue); + cookieValueObj.timestamp = currentDateTime; // Update the property + // Set the updated cookie + const cookieValueString = objectToString(cookieValueObj); + createOrUpdateCookie(cookieName, cookieValueString, { + useDefaultFunnelCookieAttributes: true, + maxAge: cookieExpirations.NON_CJ, + }); + } + } +} + /* =========================== = PRIVATE FUNCTIONS = @@ -253,7 +328,7 @@ function setFunnelCookieProperties(properties) { function createOrUpdateCookie( key, value = "", - { useDefaultFunnelCookieAttributes = true, maxAge, isSecure = true } + { useDefaultFunnelCookieAttributes = true, maxAge, isSecure = true, crossSiteAccess = false } ) { let cookieToAdd = `${key}=${value}; `; @@ -266,6 +341,9 @@ function createOrUpdateCookie( if (!isNaN(maxAge)) { cookieToAdd += `max-age=${maxAge};`; } + if (crossSiteAccess && isSecure) { + cookieToAdd += `sameSite=None;`; + } document.cookie = cookieToAdd; } @@ -315,3 +393,42 @@ function getCookiesContainingName(name) { } return matchingCookies; } +function createCJEventCookie(cjEvent, cookieName) { + createOrUpdateCookie(cookieName, cjEvent, { + useDefaultFunnelCookieAttributes: true, + maxAge: cookieExpirations.CJE, + isSecure: true, + crossSiteAccess: true, + }); +} + +function FormatCookieElementName(cookie) { + // Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + const problemSymbols = '\\s\\(\\)<>@,;:\\\\/\\[\\]\\?=\\{}"'; + const otherSymbols = "!#$%^&+\\-*|~`"; + const symbolsToIgnore = new RegExp("[" + problemSymbols + otherSymbols + "]+", "g"); + + return (cookie || "").replace(symbolsToIgnore, "."); +} + +function convertStringToObject(str) { + let obj = {}; + let pairs = str.split("&"); + + pairs.forEach((pair) => { + let [key, value] = pair.split("="); + obj[key] = value; + }); + + return obj; +} +function objectToString(obj) { + let str = ""; + for (let key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + str += key + "=" + obj[key] + "&"; + } + } + // Remove the trailing '&' + return str.slice(0, -1); +} diff --git a/src/router/index.js b/src/router/index.js index 4ee7dd0c6..23b61282c 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -19,6 +19,7 @@ import { updateSessionIdCookie, deleteFunnelCookie, getAffiliateCookies, + setupAdvertiserTracking, } from "@/helpers/heritage-integration/cookie-helper"; import { loadSessionIfPresent, saveSession } from "@/helpers/heritage-integration/order-helper"; import { @@ -103,7 +104,7 @@ const routes = [ if (affiliateCookies != null && affiliateCookies.length > 0) { store.commit(storeMutations.UPDATE_AFFILIATE_COOKIES, affiliateCookies); } - + setupAdvertiserTracking(); const loadSessionResponse = await loadSessionIfPresent( to.query.isInsurance != null ? to.query.isInsurance == "true"