thankyou-gtms-tag / tagco / server.js
server.js
Raw
const getAllEventData = require("getAllEventData");
const getTimestampMillis = require("getTimestampMillis");
// Doesnt work atm so we made our own implementation
//const getCookieValues = require("getCookieValues");
const makeNumber = require("makeNumber");
const fromBase64 = require("fromBase64");
const Math = require("Math");
const JSON = require("JSON");
const parseUrl = require("parseUrl");
const sendHttpRequest = require("sendHttpRequest");
const decodeUriComponent = require("decodeUriComponent");

const CONVERSION_PRE_VALIDATION = data.conversion_pre_validation;
const COOKIE_NAME_EAD = data.cookie_name_ead;
const CAMPAIGN_EXPIRATION_IN_MINUTES = makeNumber(data.campaign_expiration_in_minutes || 43200);
const BLACKLIST_DOMAINS = parseDomains(data.blacklist_domains);
const COOKIE_NAME_SESSION = data.cookie_name_session;
const BROWSER_ID_VALUE = data.browser_id_value;
const COOKIE_NAME_DO_NOT_TRACK = data.cookie_name_do_not_track;
const REQUEST_URL = data.api_url;
const PAGE_VIEW_EVENT_NAME = data.page_view_event_name;
const CONSENT_EVENT_NAME = data.consent_event_name;

const eventData = getAllEventData();
const doNotTrack = getDoNotTrack();
const hasSessionCookie = !doNotTrack ? getSession() : false;
const browserId = getBrowserId();

let ead = getEad();

const isNewVisitGa = calculateNewVisitGa();
const isNewVisitAnalytics = calculateNewVisitAnalytics();

if (!doNotTrack) {
  ead = updateCurrentCampaign();
}

switch (eventData.event_name) {
  case PAGE_VIEW_EVENT_NAME:
  case CONSENT_EVENT_NAME:
    if (shouldSendData()) {
      const consentUpdated = isConsentUpdated();
      // eslint-disable-next-line no-case-declarations
      let eventRequestCasePageView = {
        id: browserId,
        currentCampaign: ead.currentCampaign || null,
        consentStatus: translateConsentStatus(data.consent_status),
        tagId: data.tag_id,
        target: eventData.context.page.url,
        referrer: eventData.context.page.referrer,
        userAgent: eventData.context.device.user_agent,
        type: consentUpdated ? "consentAttribution" : "entranceAttribution",
        viewport: eventData.context.page.viewport,
        isNewVisitAnalytics: consentUpdated ? true : isNewVisitAnalytics,
        isNewVisitGa: consentUpdated ? true : isNewVisitGa,
        ipAddress: eventData.context.device.ip,
      };

      if (parseAdditionalParams(data.additional_audience_params)) {
        for (let param of parseAdditionalParams(data.additional_audience_params)) {
          eventRequestCasePageView[param.parameter] = param.value;
        }
      }

      sendHttpRequest(
        REQUEST_URL,
        (statusCode) => {
          if (statusCode >= 200 && statusCode < 300) {
            data.gtmOnSuccess();
          } else {
            data.gtmOnFailure();
          }
        },
        { headers: { "content-type": "application/json" }, method: "POST" },
        JSON.stringify(eventRequestCasePageView)
      );
    } else {
      data.gtmOnSuccess();
    }
    break;

  case "purchase":
    // eslint-disable-next-line no-case-declarations
    let eventRequestPurchase;
    if (CONVERSION_PRE_VALIDATION === true) {
      eventRequestPurchase = {
        tagId: data.tag_id,
        orderId: eventData.id,
        amount: makeNumber(eventData.value),
        margin: eventData.margin ? makeNumber(eventData.margin) : null,
        voucher: eventData.coupon,
        type: "orderValidation",
        ipAddress: eventData.context.device.ip,
      };
    } else {
      eventRequestPurchase = {
        id: browserId,
        currentCampaign: ead.currentCampaign || null,
        orderId: eventData.id,
        amount: makeNumber(eventData.value),
        margin: eventData.margin ? makeNumber(eventData.margin) : null,
        voucher: eventData.coupon,
        consentStatus: translateConsentStatus(data.consent_status),
        tagId: data.tag_id,
        target: eventData.context.page.url,
        referrer: eventData.context.page.referrer,
        userAgent: eventData.context.device.user_agent,
        type: "orderAttribution",
        viewport: eventData.context.page.viewport,
        ipAddress: eventData.context.device.ip,
      };
    }

    if (eventData?.items?.length > 0) {
      eventRequestPurchase.items = eventData.items.map((item) => ({
        ...(item?.product || item),
        product_discount: item.discount,
      }));
    }

    if (parseAdditionalParams(data.additional_conversion_params)) {
      for (let param of parseAdditionalParams(data.additional_conversion_params)) {
        eventRequestPurchase[param.parameter] = param.value;
      }
    }

    if (parseAdditionalParams(data.additional_audience_params)) {
      for (let param of parseAdditionalParams(data.additional_audience_params)) {
        eventRequestPurchase[param.parameter] = param.value;
      }
    }

    sendHttpRequest(
      REQUEST_URL,
      (statusCode) => {
        if (statusCode >= 200 && statusCode < 300) {
          data.gtmOnSuccess();
        } else {
          data.gtmOnFailure();
        }
      },
      { headers: { "content-type": "application/json" }, method: "POST" },
      JSON.stringify(eventRequestPurchase)
    );

    break;

  default:
    data.gtmOnSuccess();
    break;
}

function isUrlInDomainsList(url) {
  const referrerObject = parseUrl(url);
  let foundInDomainsList = false;

  if (parseDomains(data.whitelist_domains)) {
    for (let item of parseDomains(data.whitelist_domains)) {
      if (item.domain.replace("www.", "") === referrerObject.host.replace("www.", "")) {
        foundInDomainsList = true;
        break;
      }

      // Case with wildcard "*" as subdomain
      if (item.domain.startsWith("*.")) {
        // Extract the main domain without the wildcard (*.example.com -> example.com)
        const domainWithoutWildcard = item.domain.substring(2);

        // Extract the parts of the referrer domain
        const referrerHostParts = referrerObject.host.replace("www.", "").split(".");
        const domainParts = domainWithoutWildcard.split(".");

        // If there are enough parts in the referrer domain
        if (referrerHostParts.length >= domainParts.length) {
          // Compare only the main domain and extension (the last parts)
          const referrerMainDomain = referrerHostParts.slice(-domainParts.length).join(".");

          if (referrerMainDomain === domainWithoutWildcard) {
            foundInDomainsList = true;
            break;
          }
        }
      }
    }
  }

  return foundInDomainsList;
}

function referrerNotExcludedFromGA() {
  if (!BLACKLIST_DOMAINS || BLACKLIST_DOMAINS.length <= 0) {
    return true;
  }

  const referralUrl = eventData.context.page.referrer
    ? parseUrl(eventData.context.page.referrer)
    : null;

  if (!referralUrl || !referralUrl.hostname) {
    return true;
  }

  let referrerNotExcludedFromGA = true;
  for (let item of BLACKLIST_DOMAINS) {
    if (referralUrl.host.replace("www.", "") === item.domain.replace("www.", "")) {
      referrerNotExcludedFromGA = false;
      break;
    }
  }
  return referrerNotExcludedFromGA;
}

function isDirectAccess() {
  const referralUrl = eventData.context.page.referrer
    ? parseUrl(eventData.context.page.referrer)
    : null;
  const targetUrl = eventData.context.page.url ? parseUrl(eventData.context.page.url) : null;
  return (
    (!referralUrl || (referralUrl && isUrlInDomainsList(eventData.context.page.referrer))) &&
    (!targetUrl ||
      (!searchParamFromUrl(targetUrl, "utm_source") &&
        !searchParamFromUrl(targetUrl, "utm_campaign") &&
        !searchParamFromUrl(targetUrl, "utm_keyword") &&
        !searchParamFromUrl(targetUrl, "utm_content") &&
        !searchParamFromUrl(targetUrl, "utm_medium") &&
        !searchParamFromUrl(targetUrl, "at_platform") &&
        !searchParamFromUrl(targetUrl, "at_channel") &&
        !searchParamFromUrl(targetUrl, "at_medium") &&
        !searchParamFromUrl(targetUrl, "at_campaign") &&
        !searchParamFromUrl(targetUrl, "at_creation") &&
        !searchParamFromUrl(targetUrl, "at_network") &&
        !searchParamFromUrl(targetUrl, "at_aff_type") &&
        !searchParamFromUrl(targetUrl, "at_variant") &&
        !searchParamFromUrl(targetUrl, "at_source") &&
        !searchParamFromUrl(targetUrl, "cmpid") &&
        !searchParamFromUrl(targetUrl, "gclid") &&
        !searchParamFromUrl(targetUrl, "fbclid") &&
        !searchParamFromUrl(targetUrl, "msclkid")))
  );
}

function shouldSendData() {
  // IF NO REFERRER
  if (!eventData.context.page.referrer) {
    return true;
  }

  // IF REFERRER IS NOT IN THE WHITELIST
  if (!isUrlInDomainsList(eventData.context.page.referrer)) {
    return true;
  }

  if (isNewVisitGa || isNewVisitAnalytics) {
    return true;
  }

  return false;
}

function isConsentUpdated() {
  if (eventData.consent_updated) {
    return true;
  }
  if (eventData.event_name === CONSENT_EVENT_NAME) {
    return true;
  }

  return false;
}

function calculateNewVisitAnalytics() {
  if (!ead || !hasSessionCookie) {
    return true;
  }
  return false;
}

function calculateNewVisitGa() {
  if (
    !ead ||
    !hasSessionCookie ||
    checkSameReferrerToOrganic(ead.target, ead.referrer) ||
    checkNewReferrer(ead.referrer) ||
    checkNewUtmParameters(ead.target) ||
    checkNewClickIdentifierParameters(ead.target)
  ) {
    return true;
  }
  return false;
}

function checkNewReferrer(referrer) {
  const referralUrl = eventData.context.page.referrer
    ? parseUrl(eventData.context.page.referrer)
    : null;
  const targetUrl = eventData.context.page.url ? parseUrl(eventData.context.page.url) : null;
  const lastTouchpointReferrer = referrer ? parseUrl(referrer) : null;

  if (
    referralUrl &&
    !isUrlInDomainsList(eventData.context.page.referrer) &&
    targetUrl.hostname !== referralUrl.hostname &&
    (!lastTouchpointReferrer || lastTouchpointReferrer.hostname !== referralUrl.hostname)
  ) {
    return true;
  }

  return false;
}

function checkSameReferrerToOrganic(target, referrer) {
  const referralUrl = eventData.context.page.referrer
    ? parseUrl(eventData.context.page.referrer)
    : null;
  const targetUrl = eventData.context.page.url ? parseUrl(eventData.context.page.url) : null;
  const lastTouchpointReferrer = referrer ? parseUrl(referrer) : null;
  const lastTouchpointTarget = target ? parseUrl(target) : null;

  if (
    referralUrl &&
    targetUrl &&
    lastTouchpointReferrer &&
    lastTouchpointTarget &&
    lastTouchpointReferrer.hostname === referralUrl.hostname &&
    !isUrlInDomainsList(eventData.context.page.referrer) &&
    !isUrlInDomainsList(referrer) &&
    (searchParamFromUrl(lastTouchpointTarget, "utm_source") ||
      searchParamFromUrl(lastTouchpointTarget, "utm_campaign") ||
      searchParamFromUrl(lastTouchpointTarget, "utm_term") ||
      searchParamFromUrl(lastTouchpointTarget, "utm_content") ||
      searchParamFromUrl(lastTouchpointTarget, "utm_medium") ||
      searchParamFromUrl(lastTouchpointTarget, "at_platform") ||
      searchParamFromUrl(lastTouchpointTarget, "at_channel") ||
      searchParamFromUrl(lastTouchpointTarget, "at_medium") ||
      searchParamFromUrl(lastTouchpointTarget, "at_campaign") ||
      searchParamFromUrl(lastTouchpointTarget, "at_creation") ||
      searchParamFromUrl(lastTouchpointTarget, "at_network") ||
      searchParamFromUrl(lastTouchpointTarget, "at_aff_type") ||
      searchParamFromUrl(lastTouchpointTarget, "at_variant") ||
      searchParamFromUrl(lastTouchpointTarget, "at_source") ||
      searchParamFromUrl(lastTouchpointTarget, "cmpid") ||
      searchParamFromUrl(lastTouchpointTarget, "gclid") ||
      searchParamFromUrl(lastTouchpointTarget, "fbclid") ||
      searchParamFromUrl(lastTouchpointTarget, "msclkid")) &&
    !searchParamFromUrl(targetUrl, "utm_source") &&
    !searchParamFromUrl(targetUrl, "utm_campaign") &&
    !searchParamFromUrl(targetUrl, "utm_term") &&
    !searchParamFromUrl(targetUrl, "utm_content") &&
    !searchParamFromUrl(targetUrl, "utm_medium") &&
    !searchParamFromUrl(targetUrl, "at_platform") &&
    !searchParamFromUrl(targetUrl, "at_channel") &&
    !searchParamFromUrl(targetUrl, "at_medium") &&
    !searchParamFromUrl(targetUrl, "at_campaign") &&
    !searchParamFromUrl(targetUrl, "at_creation") &&
    !searchParamFromUrl(targetUrl, "at_network") &&
    !searchParamFromUrl(targetUrl, "at_aff_type") &&
    !searchParamFromUrl(targetUrl, "at_variant") &&
    !searchParamFromUrl(targetUrl, "at_source") &&
    !searchParamFromUrl(targetUrl, "cmpid") &&
    !searchParamFromUrl(targetUrl, "gclid") &&
    !searchParamFromUrl(targetUrl, "fbclid") &&
    !searchParamFromUrl(targetUrl, "msclkid")
  ) {
    return true;
  }

  return false;
}

function searchParamFromUrl(url, attributName) {
  let paramValue = undefined;

  if (url && url.search && url.search !== "") {
    for (let item of url.search.replace("?", "").split("&")) {
      const [attribut, value] = item.split("=");
      if (attribut === attributName) {
        paramValue = value;
        break;
      }
    }

    /**
        for (let [attribut, value] of url.searchParams) {
            if (attribut === attributName) {
                paramValue = value;
                break; 
            }
            
        }
        */
  }

  return paramValue;
}

function checkNewUtmParameters(target) {
  const lastTouchpointTarget = target ? parseUrl(target) : null;
  const targetUrl = eventData.context.page.url ? parseUrl(eventData.context.page.url) : null;

  if (
    targetUrl &&
    lastTouchpointTarget &&
    (["source", "campaign", "term", "content", "medium"].some((utm) => {
      const currentUrlParam = searchParamFromUrl(targetUrl, "utm_" + utm);
      const lastTouchPointParam = searchParamFromUrl(lastTouchpointTarget, "utm_" + utm);
      return currentUrlParam && currentUrlParam !== lastTouchPointParam;
    }) ||
      [
        "platform",
        "channel",
        "medium",
        "campaign",
        "creation",
        "network",
        "aff_type",
        "variant",
        "source",
      ].some((at) => {
        const currentUrlParam = searchParamFromUrl(targetUrl, "at_" + at);
        const lastTouchPointParam = searchParamFromUrl(lastTouchpointTarget, "at_" + at);
        return currentUrlParam && currentUrlParam !== lastTouchPointParam;
      }))
  ) {
    return true;
  }

  return false;
}

function checkNewClickIdentifierParameters(target) {
  const lastTouchpointTarget = target ? parseUrl(target) : null;
  let targetUrl = eventData.context.page.url ? parseUrl(eventData.context.page.url) : null;

  if (
    targetUrl &&
    lastTouchpointTarget &&
    ["gclid", "fbclid", "msclkid"].some((tagId) => {
      const currentUrlParam = searchParamFromUrl(targetUrl, tagId);
      const lastTouchPointParam = searchParamFromUrl(lastTouchpointTarget, tagId);
      return currentUrlParam && currentUrlParam !== lastTouchPointParam;
    })
  ) {
    return true;
  }

  return false;
}

function translateConsentStatus(consent) {
  if (consent === "false" || consent === "0" || consent === 0) {
    return false;
  }

  if (consent === "true" || consent === "1" || consent === 1) {
    return true;
  }

  if (!consent || consent === "null") {
    return null;
  }

  return consent;
}

function getEad() {
  let ead = getCookieValues(COOKIE_NAME_EAD)[0] || undefined;

  if (ead) {
    ead = JSON.parse(fromBase64(decodeUriComponent(ead)));
  }

  return ead;
}

function updateCurrentCampaign() {
  let newEad = {
    currentCampaign: ead && ead.currentCampaign ? ead.currentCampaign : null,
  };

  if (!ead || isNewVisitGa) {
    newEad.referrer = eventData.context.page.referrer;
    newEad.target = eventData.context.page.url;
  } else {
    newEad.referrer = ead.referrer;
    newEad.target = ead.target;
  }

  if (
    !ead ||
    !ead.currentCampaign ||
    !ead.currentCampaign.target ||
    !ead.currentCampaign.date ||
    (!isDirectAccess() &&
      referrerNotExcludedFromGA() &&
      (checkSameReferrerToOrganic(ead.currentCampaign.target, ead.currentCampaign.referrer) ||
        checkNewReferrer(ead.currentCampaign.referrer) ||
        checkNewUtmParameters(ead.currentCampaign.target) ||
        checkNewClickIdentifierParameters(ead.currentCampaign.target))) ||
    (isDirectAccess() && checkDelay(ead.currentCampaign.date, CAMPAIGN_EXPIRATION_IN_MINUTES))
  ) {
    newEad.currentCampaign = {
      date: getTimestampMillis(),
      referrer: eventData.context.page.referrer || null,
      target: eventData.context.page.url,
    };
  }

  return newEad;
}

function checkDelay(timestampInMillis, periodInMinutes) {
  const differentInMinutes = Math.abs(
    Math.round((getTimestampMillis() - timestampInMillis) / 60000)
  );

  if (differentInMinutes > periodInMinutes) {
    return true;
  }

  return false;
}

function getBrowserId() {
  let browserId = BROWSER_ID_VALUE || eventData.user.tcId || undefined;
  return browserId;
}

function getSession() {
  let session = getCookieValues(COOKIE_NAME_SESSION)[0] || undefined;

  if (!session) {
    return false;
  }

  return true;
}

function getDoNotTrack() {
  let dnt = getCookieValues(COOKIE_NAME_DO_NOT_TRACK)[0] || undefined;

  if (dnt) {
    return true;
  }

  return false;
}

function getCookieValues(id) {
  if (!("cookie" in eventData.context.device)) {
    return [];
  }

  return eventData.context.device.cookie
    .split("; ")
    .map((cookie) => ({ name: cookie.split("=")[0], value: cookie.split("=")[1] }))
    .filter(({ name }) => name === id)
    .map(({ value }) => value);
}

// No table object atm in TagCo, so replace table inputs with simple text fields that looks like "name:value,name2:value2"
function parseAdditionalParams(params) {
  if (!params) {
    return null;
  }
  return params
    .split(",")
    .map((param) => ({ parameter: param.split(":")[0], value: param.split(":")[1] }));
}

// No table object atm in TagCo, so replace table inputs with simple text fields that looks like "domain1,domain2"
function parseDomains(domains) {
  return domains ? domains.split(",").map((domain) => ({ domain })) : [];
}