___TERMS_OF_SERVICE___ By creating or modifying this file you agree to Google Tag Manager's Community Template Gallery Developer Terms of Service available at https://developers.google.com/tag-manager/gallery-tos (or such other URL as Google may provide), as modified from time to time. ___INFO___ { "type": "TAG", "id": "cvt_temp_public_id", "version": 1, "securityGroups": [], "displayName": "Amazon Advertising Tag - Server Side", "brand": { "id": "brand_dummy", "displayName": "Amazon Advertising", "thumbnail": "data:image/png;base64,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" }, "categories": [ "ADVERTISING", "ATTRIBUTION", "CONVERSIONS" ], "description": "The server-side tag template to send event from your tagging server to Amazon Ad Tag. Version 1.5", "containerContexts": [ "SERVER" ] } ___TEMPLATE_PARAMETERS___ [ { "type": "SIMPLE_TABLE", "name": "tagIds", "displayName": "", "simpleTableColumns": [ { "defaultValue": "", "displayName": "Tags", "name": "TagIds", "type": "TEXT", "isUnique": true, "valueHint": "Tag Id from Amazon DSP", "valueValidators": [ { "type": "NON_EMPTY" } ] } ], "newRowButtonText": "Add Tag", "valueValidators": [ { "type": "NON_EMPTY" } ], "alwaysInSummary": true }, { "type": "RADIO", "name": "eventName", "displayName": "Event Name", "radioItems": [ { "value": "standard", "displayValue": "Standard", "subParams": [ { "type": "SELECT", "name": "standardEventName", "macrosInSelect": false, "selectItems": [ { "displayValue": "AddToShoppingCart", "value": "AddToShoppingCart" }, { "displayValue": "Contact", "value": "Contact" }, { "displayValue": "Checkout", "value": "Checkout" }, { "displayValue": "PageView", "value": "PageView" }, { "displayValue": "Search", "value": "Search" }, { "displayValue": "Signup", "value": "Signup" }, { "displayValue": "Application", "value": "Application" }, { "displayValue": "Subscribe", "value": "Subscribe" }, { "value": "Other", "displayValue": "Other" }, { "value": "Lead", "displayValue": "Lead" }, { "value": "Off-AmazonPurchases", "displayValue": "Off-AmazonPurchases" } ], "simpleValueType": true, "defaultValue": "PageView" } ] }, { "value": "custom", "displayValue": "Custom", "subParams": [ { "type": "TEXT", "name": "customEventName", "displayName": "", "simpleValueType": true, "defaultValue": "default" } ] } ], "simpleValueType": true }, { "type": "RADIO", "name": "tagRegion", "displayName": "Region", "radioItems": [ { "value": "NA", "displayValue": "North America, South America, Japan and Australia" }, { "value": "EU", "displayValue": "Europe" } ], "simpleValueType": true, "defaultValue": "NA" }, { "type": "CHECKBOX", "name": "advancedMatching", "checkboxText": "Advanced Matching for User Data", "simpleValueType": true }, { "type": "GROUP", "name": "advancedMatchingGroup", "displayName": "Customer Information Data Parameters", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "SIMPLE_TABLE", "name": "advancedMatchingList", "displayName": "User Data", "simpleTableColumns": [ { "defaultValue": "Email", "displayName": "Parameter name", "name": "paramName", "type": "SELECT", "selectItems": [ { "value": "email", "displayValue": "Email" }, { "value": "phone", "displayValue": "Phone Number" }, { "value": "MATCH_ID", "displayValue": "Match ID" } ], "valueValidators": [], "isUnique": true }, { "defaultValue": "", "displayName": "Parameter value", "name": "paramValue", "type": "TEXT", "valueValidators": [ { "type": "NON_EMPTY" } ] } ], "valueValidators": [ { "type": "NON_EMPTY", "enablingConditions": [] } ], "newRowButtonText": "Add parameter" }, { "type": "TEXT", "name": "ttl", "displayName": "TTL", "simpleValueType": true, "enablingConditions": [ { "paramName": "advancedMatching", "paramValue": true, "type": "EQUALS" } ] } ], "enablingConditions": [ { "paramName": "advancedMatching", "paramValue": true, "type": "EQUALS" } ] }, { "type": "GROUP", "name": "OffAmazonPurchaseAttributesField", "displayName": "Off-AmazonPurchase Attributes", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "SIMPLE_TABLE", "name": "offAmazonPurchaseAttributes", "displayName": "", "simpleTableColumns": [ { "defaultValue": "", "displayName": "Attribute", "name": "Attribute", "type": "SELECT", "selectItems": [ { "value": "currencyCode", "displayValue": "currencyCode" }, { "value": "unitsSold", "displayValue": "unitsSold" }, { "value": "value", "displayValue": "value" }, { "value": "brand", "displayValue": "brand" }, { "value": "category", "displayValue": "category" }, { "value": "productId", "displayValue": "productId" }, { "value": "attr1", "displayValue": "attr1" }, { "value": "attr2", "displayValue": "attr2" }, { "value": "attr3", "displayValue": "attr3" }, { "value": "attr4", "displayValue": "attr4" }, { "value": "attr5", "displayValue": "attr5" }, { "value": "attr6", "displayValue": "attr6" }, { "value": "attr7", "displayValue": "attr7" }, { "value": "attr8", "displayValue": "attr8" }, { "value": "attr9", "displayValue": "attr9" }, { "value": "attr10", "displayValue": "attr10" } ] }, { "defaultValue": "", "displayName": "Value", "name": "value", "type": "TEXT" } ], "newRowButtonText": "Add Attribute", "enablingConditions": [] } ], "enablingConditions": [ { "paramName": "standardEventName", "paramValue": "Off-AmazonPurchases", "type": "EQUALS" } ] }, { "type": "GROUP", "name": "DefaultAttributesField", "displayName": "Default Attributes", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "SIMPLE_TABLE", "name": "defaultAttributes", "displayName": "", "simpleTableColumns": [ { "defaultValue": "", "displayName": "Attribute", "name": "Attribute", "type": "SELECT", "selectItems": [ { "value": "value", "displayValue": "value" }, { "value": "brand", "displayValue": "brand" }, { "value": "category", "displayValue": "category" }, { "value": "productId", "displayValue": "productId" }, { "value": "attr1", "displayValue": "attr1" }, { "value": "attr2", "displayValue": "attr2" }, { "value": "attr3", "displayValue": "attr3" }, { "value": "attr4", "displayValue": "attr4" }, { "value": "attr5", "displayValue": "attr5" }, { "value": "attr6", "displayValue": "attr6" }, { "value": "attr7", "displayValue": "attr7" }, { "value": "attr8", "displayValue": "attr8" }, { "value": "attr9", "displayValue": "attr9" }, { "value": "attr10", "displayValue": "attr10" } ] }, { "defaultValue": "", "displayName": "Value", "name": "value", "type": "TEXT" } ], "newRowButtonText": "Add Attribute", "enablingConditions": [] } ], "enablingConditions": [ { "paramName": "standardEventName", "paramValue": "Off-AmazonPurchases", "type": "NOT_EQUALS" } ] }, { "type": "GROUP", "name": "CustomAttributesField", "displayName": "Custom Attributes", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "SIMPLE_TABLE", "name": "customAttributes", "displayName": "", "simpleTableColumns": [ { "defaultValue": "key", "displayName": "Attribute", "name": "Attribute", "type": "TEXT" }, { "defaultValue": "value", "displayName": "Value", "name": "value", "type": "TEXT" } ], "newRowButtonText": "Add Attribute" } ] }, { "type": "GROUP", "name": "TCFv2", "displayName": "TCFv2", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "CHECKBOX", "name": "includeTcf", "checkboxText": "Include TCFv2", "simpleValueType": true, "defaultValue": false }, { "type": "SELECT", "name": "gdpr", "displayName": "GDPR consent", "macrosInSelect": false, "selectItems": [ { "value": 0, "displayValue": "GDPR does not apply (0)" }, { "value": 1, "displayValue": "GDPR applies (1)" }, { "value": 2, "displayValue": "GDPR unknown (null)" } ], "simpleValueType": true, "enablingConditions": [ { "paramName": "includeTcf", "paramValue": true, "type": "EQUALS" } ], "defaultValue": -1 }, { "type": "TEXT", "name": "gdprConsent", "displayName": "TCFv2 consent string", "simpleValueType": true, "enablingConditions": [ { "paramName": "includeTcf", "paramValue": true, "type": "EQUALS" } ], "defaultValue": "" }, { "type": "SELECT", "name": "gdprPd", "displayName": "GDPR personal data", "macrosInSelect": false, "selectItems": [ { "value": 0, "displayValue": "Does not contain personal data (0)" }, { "value": 1, "displayValue": "Contains personal data (1)" }, { "value": -1, "displayValue": "unknown (null)" } ], "simpleValueType": true, "enablingConditions": [ { "paramName": "includeTcf", "paramValue": true, "type": "EQUALS" } ], "defaultValue": -1 } ] }, { "type": "GROUP", "name": "amazonConsent", "displayName": "Amazon Consent", "groupStyle": "ZIPPY_OPEN", "subParams": [ { "type": "GROUP", "name": "geo", "displayName": "Geo Attributes", "groupStyle": "ZIPPY_OPEN", "subParams": [ { "type": "TEXT", "name": "ipAddress", "displayName": "IP Address", "simpleValueType": true }, { "type": "TEXT", "name": "countryCode", "displayName": "Country Code", "simpleValueType": true, "help": "The country where the event originates from. e.g. US This value is based on ISO 3166-1 alpha-2, two-letter country codes defined in ISO 3166-1, part of the ISO 3166 standard[1] published by the International Organization for Standardization (ISO), to represent countries, dependent territories, and special areas of geographical interest.", "valueValidators": [ { "type": "NON_EMPTY" }, { "type": "STRING_LENGTH", "args": [ 2, 2 ] } ] } ] }, { "type": "GROUP", "name": "amazonConsentFormat", "displayName": "Amazon Consent Format", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "TEXT", "name": "amznAdStorage", "displayName": "Amazon Ad Storage (GRANTED or DENIED)", "simpleValueType": true }, { "type": "TEXT", "name": "amznUserData", "displayName": "Amazon User Data (GRANTED or DENIED)", "simpleValueType": true } ] }, { "type": "TEXT", "name": "gpp", "displayName": "Global Privacy Platform string", "simpleValueType": true } ] } ] ___SANDBOXED_JS_FOR_SERVER___ const version = "1.5"; const encodeUriComponent = require('encodeUriComponent'); const getEventData = require('getEventData'); const log = require('logToConsole'); const makeString = require('makeString'); const sendHttpGet = require('sendHttpGet'); const makeTableMap = require('makeTableMap'); const makeInteger = require('makeInteger'); const sendHttpRequest = require('sendHttpRequest'); const sha256Sync = require('sha256Sync'); const JSON = require('JSON'); const Object = require('Object'); const getCookieValues = require('getCookieValues'); const setCookie = require('setCookie'); const parseUrl = require('parseUrl'); const getTimestampMillis = require('getTimestampMillis'); const AIPES_RESPONSE_TOKEN_FIELD_NAME = 'AIPToken'; const constants = { nameLength: 256, valueLength: 1000, eventNameLengthWarning: 'Event name is longer than 256 characters.', parameterNameLengthWarning: 'Length of parameter name exceeds 256 characters.', parameterValueLengthWarning: 'Length of parameter value exceeds 1000 characters.', parameterKeyLengthWarning: 'Length of parameter key exceeds 256 characters.', AIP_TOKEN_URL_QUERY_PARAM_NAME: 'amznToken', NO_CONSENT_COOKIE_NAME: 'AMZN-NoCookieConsent', AMAZON_CONSENT_AIPES_ENABLED: false, AIPES_AMAZON_CONSENT_STRING_FIELD_NAME: 'amazonConsentString', AIPES_AMAZON_CONSENT_GEO_FIELD_NAME: 'geo', AIPES_AMAZON_CONSENT_CONSENT_FIELD_NAME: 'consent', AIPES_AMAZON_CONSENT_AMAZON_CONSENT_FIELD_NAME: 'amazonConsentFormat', AIPES_AMAZON_CONSENT_AD_STORAGE_FIELD_NAME: 'amzn_ad_storage', AIPES_AMAZON_CONSENT_USER_DATA_FIELD_NAME: 'amzn_user_data', AIPES_AMAZON_CONSENT_TCF_FIELD_NAME: 'tcf', AIPES_AMAZON_CONSENT_GPP_FIELD_NAME: 'gpp', AIPES_AMAZON_CONSENT_COUNTRY_CODE_FIELD_NAME: 'countryCode', AIPES_AMAZON_CONSENT_IP_ADDRESS_FIELD_NAME: 'ipAddress', MT_LP_QUERY_PARAM: 'aref', MTS_EVENT_ATTRIBUTE: 'arefs', MEASUREMENT_TOKEN_COOKIE_NAME: 'amznAref', MEASUREMENT_TOKEN_TIMESTAMP_PAIR_DELIMITER: '|', MEASUREMENT_TIMESTAMP_SEPARATOR: '.', NUM_MAX_MEASUREMENT_TOKENS: 147, MEASUREMENT_TOKEN_TTL_IN_MS: 2592000000, // 30 days in milliseconds, REPORTING_ATTRIBUTES: [ 'brand', 'category', 'productid', 'attr1', 'attr2', 'attr3', 'attr4', 'attr5', 'attr6', 'attr7', 'attr8', 'attr9', 'attr10', ], REPORTING_ATTRIBUTE_MAX_LENGTH: 256, REPORTING_ATTRIBUTE_MAX_LENGTH_WARNING: 'Length of attribute must be between 1 and 256 characters.', REPORTING_ATTRIBUTE_PROHIBITED_CHARACTERS_WARNING: 'Attribute may only contain letters, numbers, and the underscore character.', REPORTING_ATTRIBUTE_VALUE_NOT_STRING_WARNING: 'Attribute must be a string and cannot be an object or array.', }; function replaceNonAlphanumeric(inputString) { if (!inputString) return ''; let result = ''; let lastWasUnderscore = false; for (let i = 0; i < inputString.length; i++) { const char = inputString.charAt(i); // Check if character is alphanumeric using comparison operators const isAlphanumeric = (char >= '0' && char <= '9') || // 0-9 (char >= 'A' && char <= 'Z') || // A-Z (char >= 'a' && char <= 'z'); // a-z if (isAlphanumeric) { result += char; lastWasUnderscore = false; } else if (!lastWasUnderscore) { result += '_'; lastWasUnderscore = true; } // Skip consecutive non-alphanumeric characters } return result; } const validateCustomAttributesForReporting = (attribute, internalEventAttributes) => { let reportingAttributeWarnings; const attributeValue = internalEventAttributes[attribute]; let newAttributeValue = attributeValue; const attributeName = attribute.toLowerCase(); if (constants.REPORTING_ATTRIBUTES.indexOf(attributeName) != -1) { reportingAttributeWarnings = {}; reportingAttributeWarnings[attributeName] = { messages: [] }; if (attributeValue.length > constants.REPORTING_ATTRIBUTE_MAX_LENGTH || attributeValue.length < 1) { reportingAttributeWarnings[attributeName].messages.push(constants.REPORTING_ATTRIBUTE_MAX_LENGTH_WARNING); } if (typeof attributeValue === 'string') { newAttributeValue = replaceNonAlphanumeric(newAttributeValue.trim()); } else { reportingAttributeWarnings[attributeName].messages.push(constants.REPORTING_ATTRIBUTE_VALUE_NOT_STRING_WARNING); } if (reportingAttributeWarnings[attributeName].messages.length === 0) { reportingAttributeWarnings = undefined; } else { reportingAttributeWarnings[attributeName].value = newAttributeValue; newAttributeValue = undefined; } } return { eventReportingAttributeWarnings: reportingAttributeWarnings, newAttributeValue: newAttributeValue }; }; const pixelUrls = { NA: 'https://s.amazon-adsystem.com/iu3', EU: 'https://aax-eu.amazon-adsystem.com/s/iu3', FE: 'https://aax-fe.amazon-adsystem.com/s/iu3', }; // Helper methods const mergeObj = (obj, obj2) => { for (let key in obj2) { if (obj2.hasOwnProperty(key)) { obj[key] = obj2[key]; } } return obj; }; const fail = msg => { log(msg); data.gtmOnFailure(); }; const processAmazonConsentGeo = (geo) => { const geoAttribute = {}; if (geo.countryCode !== undefined) { geoAttribute[constants.AIPES_AMAZON_CONSENT_COUNTRY_CODE_FIELD_NAME] = geo.countryCode; } if (geo.ipAddress !== undefined) { geoAttribute[constants.AIPES_AMAZON_CONSENT_IP_ADDRESS_FIELD_NAME] = geo.ipAddress; } return geoAttribute; }; const processAmazonConsentFormat = (amazonConsent) => { const amazonConsentFormat = {}; if (amazonConsent.amznAdStorage !== undefined) { amazonConsentFormat[constants.AIPES_AMAZON_CONSENT_AD_STORAGE_FIELD_NAME] = amazonConsent.amznAdStorage; } if (amazonConsent.amznUserData !== undefined) { amazonConsentFormat[constants.AIPES_AMAZON_CONSENT_USER_DATA_FIELD_NAME] = amazonConsent.amznUserData; } return amazonConsentFormat; }; const globalAmazonConsent = { geo: { countryCode: data.countryCode, ipAddress: data.ipAddress }, gpp: data.gpp, amazonConsentFormat: { amznAdStorage: data.amznAdStorage, amznUserData: data.amznUserData } }; const formatAmazonConsent = (amazonConsent) => { const consentString = {}; const consentObject = {}; if (globalAmazonConsent.amazonConsentFormat.amznAdStorage === undefined && globalAmazonConsent.amazonConsentFormat.amznUserData === undefined && globalAmazonConsent.gpp === undefined) { globalAmazonConsent.amazonConsentFormat = { amznAdStorage: "GRANTED", amznUserData: "GRANTED", }; } log("consent = ", globalAmazonConsent); if (globalAmazonConsent.geo !== undefined) { consentString[constants.AIPES_AMAZON_CONSENT_GEO_FIELD_NAME] = processAmazonConsentGeo(globalAmazonConsent.geo); } if (globalAmazonConsent.amazonConsentFormat.amznAdStorage !== undefined || globalAmazonConsent.amazonConsentFormat.amznUserData !== undefined) { consentObject[constants.AIPES_AMAZON_CONSENT_AMAZON_CONSENT_FIELD_NAME] = processAmazonConsentFormat( globalAmazonConsent.amazonConsentFormat ); } if (globalAmazonConsent.tcf !== undefined) { consentObject[constants.AIPES_AMAZON_CONSENT_TCF_FIELD_NAME] = globalAmazonConsent.tcf; } if (globalAmazonConsent.gpp !== undefined) { consentObject[constants.AIPES_AMAZON_CONSENT_GPP_FIELD_NAME] = globalAmazonConsent.gpp; } consentString[constants.AIPES_AMAZON_CONSENT_CONSENT_FIELD_NAME] = consentObject; return consentString; }; // Field processing const tagIds = data.tagIds .map(item => item.TagIds) .filter( item => item ); if (tagIds.length === 0) { return fail("No Amazon Ad Tag IDs configured"); } let eventName = data.standardEventName; if (data.eventName === "custom") { eventName = data.customEventName; } if (!eventName) { return fail("Event Name is not defined"); } const region = data.tagRegion; function measurementTokenTimestampPairsNestedArrToMeasurementTokenCookie(measurementTokenTimestampPairs) { return measurementTokenTimestampPairs .map(function(pair) { return pair.join(constants.MEASUREMENT_TIMESTAMP_SEPARATOR); }) .join(constants.MEASUREMENT_TOKEN_TIMESTAMP_PAIR_DELIMITER); } let MEASUREMENT_TOKEN_VALUE = ''; function measurementTokenCookieToMeasurementTokenTimestampPairsNestedArr(measurementTokenCookieValue) { if (!measurementTokenCookieValue) return []; return measurementTokenCookieValue.split(constants.MEASUREMENT_TOKEN_TIMESTAMP_PAIR_DELIMITER).map(function(tokenTsPairStr) { var parts = tokenTsPairStr.split(constants.MEASUREMENT_TIMESTAMP_SEPARATOR); var token = parts[0]; var timestampStr = parts[1]; return [token, makeInteger(timestampStr)]; }); } function saveMeasurementTokenInURLToCookieIfPresent(data) { const url = parseUrl(data.page_location || ''); const region = data.tagRegion || ''; if (region.toUpperCase() !== 'NA') { return; } if (!url || !url.searchParams) { return; } const currURLMeasurementToken = url.searchParams.get(constants.MT_LP_QUERY_PARAM); if (!currURLMeasurementToken) { return; } let currMeasurementTokenCookieValue = getCookieValues(constants.MEASUREMENT_TOKEN_COOKIE_NAME)[0] || ''; if (currMeasurementTokenCookieValue.split(constants.MEASUREMENT_TOKEN_TIMESTAMP_PAIR_DELIMITER).length >= constants.NUM_MAX_MEASUREMENT_TOKENS) { currMeasurementTokenCookieValue = measurementTokenTimestampPairsNestedArrToMeasurementTokenCookie( measurementTokenCookieToMeasurementTokenTimestampPairsNestedArr(currMeasurementTokenCookieValue).slice(0, -1) ); } const newMeasurementTokenTimestamp = getTimestampMillis(); const newMeasurementTokenCookieValue = measurementTokenTimestampPairsNestedArrToMeasurementTokenCookie( [[currURLMeasurementToken, newMeasurementTokenTimestamp]].concat( measurementTokenCookieToMeasurementTokenTimestampPairsNestedArr(currMeasurementTokenCookieValue) ) ); MEASUREMENT_TOKEN_VALUE = newMeasurementTokenCookieValue; const newMeasurementTokenCookieExpiration = makeInteger(newMeasurementTokenTimestamp) + constants.MEASUREMENT_TOKEN_TTL_IN_MS; setCookie(constants.MEASUREMENT_TOKEN_COOKIE_NAME, newMeasurementTokenCookieValue, { domain: 'auto', sameSite: 'strict', path: '/', expires: newMeasurementTokenCookieExpiration, secure: true, httpOnly: true }); } function removeAnyExpiredMeasurementTokens() { var measurementTokenCookieValue = getCookieValues(constants.MEASUREMENT_TOKEN_COOKIE_NAME)[0]; if (!measurementTokenCookieValue) { return; } var MEASUREMENT_TOKEN_TTL_AGO = getTimestampMillis() - constants.MEASUREMENT_TOKEN_TTL_IN_MS; var unexpiredMeasurementTokenTimestampPairs = measurementTokenCookieToMeasurementTokenTimestampPairsNestedArr( measurementTokenCookieValue ).filter(function(pair) { return pair[constants.MT_TS_PAIR_TS_INDEX] > MEASUREMENT_TOKEN_TTL_AGO; }); if (unexpiredMeasurementTokenTimestampPairs.length === 0) { setCookie(constants.MEASUREMENT_TOKEN_COOKIE_NAME, '', { 'max-age': 0, path: '/', domain: 'auto' }); return; } var newMeasurementTokenCookieValue = measurementTokenTimestampPairsNestedArrToMeasurementTokenCookie( unexpiredMeasurementTokenTimestampPairs ); var newMeasurementTokenCookieExpirationTimestamp = unexpiredMeasurementTokenTimestampPairs[constants.NEWEST_MEASUREMENT_TOKEN_INDEX][constants.MT_TS_PAIR_TS_INDEX] + constants.MEASUREMENT_TOKEN_TTL_IN_MS; setCookie(constants.MEASUREMENT_TOKEN_COOKIE_NAME, newMeasurementTokenCookieValue, { 'max-age': makeInteger((newMeasurementTokenCookieExpirationTimestamp - getTimestampMillis()) / 1000), path: '/', domain: 'auto', secure: true, httpOnly: false }); } removeAnyExpiredMeasurementTokens(); saveMeasurementTokenInURLToCookieIfPresent(data); let attributes = {}; if (eventName === "Off-AmazonPurchases") { attributes = data.offAmazonPurchaseAttributes ? makeTableMap(data.offAmazonPurchaseAttributes, 'Attribute', 'value') : {}; } else { attributes = data.defaultAttributes ? makeTableMap(data.defaultAttributes, 'Attribute', 'value') : {}; } const customAttributes = data.customAttributes ? makeTableMap(data.customAttributes, 'Attribute', 'value') : {}; const finalAttributes = mergeObj(attributes, customAttributes); finalAttributes.gtmVersion = version; for (const key in finalAttributes) { if (!key) { return fail("Attribute is not defined - " + key); } } const gdprAttributes = {}; let ttl = null; if (data.includeTcf) { const possibleGDPRvalues = [0, 1, 2]; if (possibleGDPRvalues.indexOf(data.gdpr) !== -1) gdprAttributes.gdpr = data.gdpr; const possibleGDPRPDvalues = [-1, 0, 1]; if (possibleGDPRPDvalues.indexOf(data.gdprPd) !== -1) gdprAttributes.gdprPd = data.gdprPd; if (data.gdprConsent) gdprAttributes.gdprConsent = data.gdprConsent; } const tokenConfig = { gdpr: { enabled: false, // GDPR is mandatory for EU region token request. consent: '', // Valid IAB consent string, v1 or v2 (required if `enabled` is `true`) }, hashedRecords: [], ttl: 9600, // token TTL in seconds }; //If advanced matching for user data is enabled //removed return statement if (data.advancedMatchingList) { let ttl = null; if (data.ttl !== undefined && makeInteger(data.ttl) >= 0) { ttl = makeInteger(data.ttl); } data.advancedMatchingList.forEach((e) => { if (e.paramName && e.paramValue) { const paramVal = makeString(e.paramValue); if (e.paramName === "email" && paramVal.length > 0) { tokenConfig.hashedRecords.push({ type: 'email', record: sha256Sync(paramVal, { outputEncoding: 'hex' }) }); } if (e.paramName === "phone" && paramVal.length > 0) { tokenConfig.hashedRecords.push({ type: 'phonenumber', record: sha256Sync(paramVal, { outputEncoding: 'hex' }) }); } if (e.paramName === "MATCH_ID" && paramVal.length > 0) { finalAttributes["MATCH_ID"] = paramVal; } } }); if (gdprAttributes.gdpr) { tokenConfig.gdpr.enabled = !!gdprAttributes.gdpr; } if (gdprAttributes.gdprConsent) { tokenConfig.gdpr.consent = gdprAttributes.gdprConsent; } if (tokenConfig.gdpr.enabled && !tokenConfig.gdpr.consent) { return fail("GDPR consent string must be set if GDPR enabled"); } if (ttl) { tokenConfig.ttl = ttl; } if (globalAmazonConsent) { tokenConfig.consentString = formatAmazonConsent(globalAmazonConsent); } log("token config =", tokenConfig); log("amazon consent = ", data); } else { const userData = getEventData('user_data'); if (tokenConfig.email === '' && tokenConfig.phonenumber === '') { if (userData !== undefined) { if (userData.email !== undefined && userData.email.length > 0) { tokenConfig.hashedRecords.push({ type: 'email', record: sha256Sync(userData.email, { outputEncoding: 'hex' }) }); } if (userData.phone !== undefined && userData.phone.length > 0) { tokenConfig.hashedRecords.push({ type: 'phonenumber', record: sha256Sync(userData.phone, { outputEncoding: 'hex' }) }); } } } let ttl = null; if (data.ttl !== undefined && makeInteger(data.ttl) >= 0) ttl = makeInteger(data.ttl); if (ttl) { tokenConfig.ttl = ttl; } log("token config =", tokenConfig); } const hasHashedRecords = tokenConfig.hashedRecords.length != 0; function formatRequestBody(tokenConfig) { const body = {}; if (tokenConfig.gdpr !== null) { body.gdpr = tokenConfig.gdpr.enabled ? 1 : 0; if (tokenConfig.gdpr.consent !== null) { body.gdprConsent = tokenConfig.gdpr.consent; } } body.hashedRecords = tokenConfig.hashedRecords; if (tokenConfig.ttl !== null) { body.ttl = tokenConfig.ttl; } if (tokenConfig.consentString !== undefined) { body[constants.AIPES_AMAZON_CONSENT_STRING_FIELD_NAME] = tokenConfig.consentString; } return JSON.stringify(body); } function trackEvent(pixelUrl, token, testTagId, event, params, consent) { let url = ''+pixelUrl+'?pid='+testTagId+'&event='+event; const gdprValues = ['gdpr', 'gdpr_pd', 'gdpr_consent']; const warningArray = []; if (event.length > constants.nameLength) { warningArray.push(constants.eventNameLengthWarning+','+event); } if (params.length != undefined) { params.forEach(function (param) { if (Object.keys(param)[0].length > constants.nameLength) { warningArray.push(constants.parameterNameLengthWarning+','+ Object.keys(param)[0]); } if (param[Object.keys(param)[0]].length > constants.valueLength) { warningArray.push(constants.parameterValueLengthWarning+','+param[Object.keys(param)[0]]); } }); url += '&items='+encodeUriComponent(JSON.stringify(params)); url += '&items='+encodeUriComponent(JSON.stringify(params)); } else if (params && Object.keys(params)) { Object.keys(params) .filter(function (param) { return gdprValues.filter((item) => item == param).length == 0; }) .forEach(function (param) { if (param.length > constants.nameLength) { warningArray.push(constants.parameterNameLengthWarning+', '+param); } let value; value = params[param]; if (value || value === '') { if (value.length > constants.valueLength) { warningArray.push(constants.parameterValueLengthWarning+','+ value); } else if (Object.keys(value) && Object.keys(value)[0]) { Object.keys(value).forEach(function (entry) { if (entry.length > constants.nameLength) { warningArray.push(constants.parameterKeyLengthWarning+','+ entry); } if (value[entry].length > constants.valueLength) { warningArray.push(constants.parameterValueLengthWarning+','+ value[entry]); } }); } if (typeof value === 'object') { value = encodeUriComponent(JSON.stringify(value)); } const customAttributesData = validateCustomAttributesForReporting(param, params); log(customAttributesData); if (customAttributesData.newAttributeValue !== undefined){ url += '&'+param+'='+customAttributesData.newAttributeValue; } else { log(customAttributesData.eventReportingAttributeWarnings); } } else { log('Key '+param + 'has no value'); } }); } if (warningArray.length > 0) { log('Event has '+warningArray.length+ 'validation errors.'); log(warningArray); return false; } if (consent) { gdprValues.forEach(function (signal) { if (consent[signal]) { url += '&'+signal+'='+consent[signal]; } }); } if (token != undefined && token != null && token.length > 0) { url += '&'+constants.AIP_TOKEN_URL_QUERY_PARAM_NAME+'='+token; } if (MEASUREMENT_TOKEN_VALUE.length > 0) { url += '&'+constants.MTS_EVENT_ATTRIBUTE+'='+MEASUREMENT_TOKEN_VALUE; } log(url); sendHttpGet(url).then((eventRes) => { log(eventRes); if (eventRes.statusCode == 302) { log(eventRes.headers.location); sendHttpGet(eventRes.headers.location, { headers: {'Cookie': eventRes.headers['set-cookie'][0].split(';')[0]} }); } }); } // Modified section for token management // Check for existing token in 1p cookie let existingToken = getCookieValues('aatToken')[0]; // Assuming 'aatToken' is the name of your 1p cookie if (!existingToken && hasHashedRecords) { // No existing token found, proceed with token generation and storage sendHttpRequest('https://tk.amazon-adsystem.com/envelope', { method: 'POST', headers: {'Content-Type': 'application/json'}, timeout: 1000 }, formatRequestBody(tokenConfig)).then((res) => { const body = JSON.parse(res.body); log(res); log(body); if (body[AIPES_RESPONSE_TOKEN_FIELD_NAME] && body[AIPES_RESPONSE_TOKEN_FIELD_NAME] !== constants.NO_CONSENT_COOKIE_NAME) { const token = body[AIPES_RESPONSE_TOKEN_FIELD_NAME]; // Store the new token in a 1p cookie setCookie('aatToken', token, {'max-age': 3600 * 24 * 7, path: '/', httpOnly: true, secure: true, }); existingToken = token; // Update the existingToken variable with the new token tagIds.forEach((tag) => trackEvent(pixelUrls[region], existingToken, tag, eventName, finalAttributes, gdprAttributes)); data.gtmOnSuccess(); } }).catch((error) => { log('Error fetching token:', error); data.gtmOnFailure(); }); } else { log('Using existing token from 1p cookie:', existingToken); tagIds.forEach((tag) => trackEvent(pixelUrls[region], existingToken, tag, eventName, finalAttributes, gdprAttributes)); data.gtmOnSuccess(); } ___SERVER_PERMISSIONS___ [ { "instance": { "key": { "publicId": "read_event_data", "versionId": "1" }, "param": [ { "key": "eventDataAccess", "value": { "type": 1, "string": "any" } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "send_http", "versionId": "1" }, "param": [ { "key": "allowedUrls", "value": { "type": 1, "string": "specific" } }, { "key": "urls", "value": { "type": 2, "listItem": [ { "type": 1, "string": "https://s.amazon-adsystem.com/iu3*" }, { "type": 1, "string": "https://aax-eu.amazon-adsystem.com/s/iu3*" }, { "type": 1, "string": "https://aax-fe.amazon-adsystem.com/s/iu3*" }, { "type": 1, "string": "https://tk.amazon-adsystem.com/envelope" } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "logging", "versionId": "1" }, "param": [ { "key": "environments", "value": { "type": 1, "string": "debug" } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "get_cookies", "versionId": "1" }, "param": [ { "key": "cookieAccess", "value": { "type": 1, "string": "specific" } }, { "key": "cookieNames", "value": { "type": 2, "listItem": [ { "type": 1, "string": "aatToken" }, { "type": 1, "string": "amznAref" } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "set_cookies", "versionId": "1" }, "param": [ { "key": "allowedCookies", "value": { "type": 2, "listItem": [ { "type": 3, "mapKey": [ { "type": 1, "string": "name" }, { "type": 1, "string": "domain" }, { "type": 1, "string": "path" }, { "type": 1, "string": "secure" }, { "type": 1, "string": "session" } ], "mapValue": [ { "type": 1, "string": "aatToken" }, { "type": 1, "string": "*" }, { "type": 1, "string": "*" }, { "type": 1, "string": "any" }, { "type": 1, "string": "any" } ] } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true } ] ___TESTS___ scenarios: - name: Library is injected code: | runCode(mockData); - name: GTM Generates the AIP Token when user data is present code: "// Mock user data\nconst mockUserData = {\n email: \"user@example.com\"\ ,\n phone: \"1234567890\"\n};\n\n const gtmOnSuccess = function() {\n \ \ successCalled = true;\n };\n\n const gtmOnFailure = function() {\n\ \ failureCalled = true;\n };\n\nconst mockTokenConfig = {\n gdpr:\ \ {\n enabled: true,\n consent: \"mockConsentString\"\n },\n\ \ hashedRecords: [\n // Simulating hashed records for email and phone\n\ \ { type: 'email', record: 'mockHashedEmail' },\n { type: 'phonenumber',\ \ record: 'mockHashedPhone' }\n ],\n ttl: 9600,\n};\n\nfunction mockSendHttpRequest(url,\ \ options, callback) {\n const requestBody = options.body;\n\n let mockResponse\ \ = {};\n if (requestBody === mockTokenConfig) { \n mockResponse = {\n\ \ body: { AIPToken: 'mockToken123' }, \n statusCode: 200\n\ \ };\n } else {\n mockResponse = {\n body: { error:\ \ 'Invalid tokenConfig' },\n statusCode: 400\n };\n }\n\n\ \ callback(mockResponse);\n}\n\n// Adjust getTokenAndVerify to return a result\ \ message or status based on the test outcome\nfunction getTokenAndVerify(successCallback,\ \ failureCallback) {\n const requestOptions = {\n method: 'POST',\n\ \ headers: {'Content-Type': 'application/json'},\n body: mockTokenConfig\ \ // Directly use the object, as JSON parsing/stringifying is restricted\n \ \ };\n\n // Call the mock function\n mockSendHttpRequest('https://tk.amazon-adsystem.com/envelope',\ \ requestOptions, (res) => {\n if (res.body && res.body.AIPToken === 'mockToken123')\ \ {\n successCallback();\n } else {\n failureCallback();\n\ \ }\n });\n\n if (successCalled) return \"Test Passed: Success callback\ \ was called.\";\n if (failureCalled) return \"Test Failed: Failure callback\ \ was called.\";\n return \"Test Inconclusive: No callback was called.\";\n\ }\n\nlet testResult = getTokenAndVerify(gtmOnSuccess, gtmOnFailure);\n\nassertThat(testResult,\ \ \"Test Passed: Success callback was called.\");\n" - name: GTM Generates the AIP Token for only email address if present code: "// Mock user data\nconst mockUserData = {\n email: \"user@example.com\"\ ,\n};\n\n // Define success and failure functions directly inside the scope\ \ where they're used\n const gtmOnSuccess = function() {\n successCalled\ \ = true;\n };\n\n const gtmOnFailure = function() {\n failureCalled\ \ = true;\n };\n\n// Simulated tokenConfig based on the mockUserData\nconst\ \ mockTokenConfig = {\n gdpr: {\n enabled: true,\n consent: \"\ mockConsentString\"\n },\n hashedRecords: [\n // Simulating hashed\ \ records for email and phone\n { type: 'email', record: 'mockHashedEmail'\ \ },\n { type: 'phonenumber', record: 'mockHashedPhone' }\n ],\n \ \ ttl: 9600,\n};\nlet url = 'https://tk.amazon-adsystem.com/envelope';\nfunction\ \ mockSendHttpRequest(url, options, callback) {\n const requestBody = options.body;\n\ \n let mockResponse = {};\n if (requestBody === mockTokenConfig) { \n \ \ mockResponse = {\n body: { AIPToken: 'mockToken123' }, \n \ \ statusCode: 200\n };\n } else {\n // Simulate an error\ \ response\n mockResponse = {\n body: { error: 'Invalid tokenConfig'\ \ },\n statusCode: 400\n };\n }\n\n callback(mockResponse);\n\ }\n\n// Adjust getTokenAndVerify to return a result message or status based on\ \ the test outcome\nfunction getTokenAndVerify(successCallback, failureCallback)\ \ {\n const requestOptions = {\n method: 'POST',\n headers: {'Content-Type':\ \ 'application/json'},\n body: mockTokenConfig\n };\n\n // Call the\ \ mock function\n mockSendHttpRequest('https://tk.amazon-adsystem.com/envelope',\ \ requestOptions, (res) => {\n if (res.body && res.body.AIPToken === 'mockToken123')\ \ {\n successCallback();\n } else {\n failureCallback();\n\ \ }\n });\n\n if (successCalled) return \"Test Passed: Success callback\ \ was called.\";\n if (failureCalled) return \"Test Failed: Failure callback\ \ was called.\";\n return \"Test Inconclusive: No callback was called.\";\n\ }\n\nlet testResult = getTokenAndVerify(gtmOnSuccess, gtmOnFailure);\n\nassertThat(testResult,\ \ \"Test Passed: Success callback was called.\");\n" - name: GTM Generates the AIP Token for only phonenumber if present code: "// Mock user data\nconst mockUserData = {\n phone: \"1234567890\",\n\ };\n\n // Define success and failure functions directly inside the scope where\ \ they're used\n const gtmOnSuccess = function() {\n successCalled =\ \ true;\n };\n\n const gtmOnFailure = function() {\n failureCalled\ \ = true;\n };\n\n// Simulated tokenConfig based on the mockUserData\nconst\ \ mockTokenConfig = {\n gdpr: {\n enabled: true,\n consent: \"\ mockConsentString\"\n },\n hashedRecords: [\n // Simulating hashed\ \ records for email and phone\n { type: 'email', record: 'mockHashedEmail'\ \ },\n { type: 'phonenumber', record: 'mockHashedPhone' }\n ],\n \ \ ttl: 9600,\n};\n\nfunction mockSendHttpRequest(url, options, callback) {\n \ \ const requestBody = options.body;\n\n let mockResponse = {};\n if (requestBody\ \ === mockTokenConfig) {\n mockResponse = {\n body: { AIPToken:\ \ 'mockToken123' }, \n statusCode: 200\n };\n } else {\n\ \ mockResponse = {\n body: { error: 'Invalid tokenConfig' },\n\ \ statusCode: 400\n };\n }\n\n callback(mockResponse);\n\ }\n\nfunction getTokenAndVerify(successCallback, failureCallback) {\n const\ \ requestOptions = {\n method: 'POST',\n headers: {'Content-Type':\ \ 'application/json'},\n body: mockTokenConfig // Directly use the object,\ \ as JSON parsing/stringifying is restricted\n };\n\n mockSendHttpRequest('https://tk.amazon-adsystem.com/envelope',\ \ requestOptions, (res) => {\n if (res.body && res.body.AIPToken === 'mockToken123')\ \ {\n successCallback();\n } else {\n failureCallback();\n\ \ }\n });\n\n if (successCalled) return \"Test Passed: Success callback\ \ was called.\";\n if (failureCalled) return \"Test Failed: Failure callback\ \ was called.\";\n return \"Test Inconclusive: No callback was called.\";\n\ }\n\nlet testResult = getTokenAndVerify(gtmOnSuccess, gtmOnFailure);\n\nassertThat(testResult,\ \ \"Test Passed: Success callback was called.\");\n" - name: GTM sets the token inside the cookie once the token is retrieved code: "let cookies = {}; // Simulate browser cookies storage\n\n// Mock setCookie\ \ function\nfunction setCookie(name, value, options) {\n // Store the cookie\ \ as a key-value pair, omitting logging.\n cookies[name] = value;\n}\n\n//\ \ Mock getCookieValues function\nfunction getCookieValues(cookieName) {\n return\ \ cookies[cookieName] ? [cookies[cookieName]] : [];\n}\n\nfunction testTokenStorage()\ \ {\n const expectedToken = \"mockToken123\"; \n\n setCookie('aatToken',\ \ expectedToken, {'max-age': 3600 * 24 * 7, path: '/', httpOnly: true, secure:\ \ true});\n\n const storedToken = getCookieValues('aatToken')[0];\n\n assertThat(storedToken\ \ === expectedToken, \"The token stored in the aatToken cookie does not match\ \ the expected value.\");\n}\n\ntestTokenStorage();\n" - name: GTM can handle Amazon Consent code: "// Mock user data\nconst mockUserData = {\n email: \"user@example.com\"\ ,\n phone: \"1234567890\",\n amazonConsent: {\n enabled: true,\n \ \ geo: {\n countryCode: \"US\"\n }\n }\n};\n const gtmOnSuccess\ \ = function() {\n successCalled = true;\n };\n\n const gtmOnFailure\ \ = function() {\n failureCalled = true;\n };\n\nconst mockTokenConfig\ \ = {\n gdpr: {\n enabled: true,\n consent: \"mockConsentString\"\ \n },\n hashedRecords: [\n // Simulating hashed records for email\ \ and phone\n { type: 'email', record: 'mockHashedEmail' },\n {\ \ type: 'phonenumber', record: 'mockHashedPhone' }\n ],\n ttl: 9600,\n \ \ Amazon_ConsentString: {\n Geo: {\n CountryCode: 'US' \n \ \ }\n }\n};\n\nfunction mockSendHttpRequest(url, options, callback) {\n\ \ const requestBody = options.body;\n\n let mockResponse = {};\n if (requestBody\ \ === mockTokenConfig) { \n mockResponse = {\n body: { AIPToken:\ \ 'mockToken123' }, \n statusCode: 200\n };\n } else {\n\ \ mockResponse = {\n body: { error: 'Invalid tokenConfig' },\n\ \ statusCode: 400\n };\n }\n\n callback(mockResponse);\n\ }\n\n// Adjust getTokenAndVerify to return a result message or status based on\ \ the test outcome\nfunction getTokenAndVerify(successCallback, failureCallback)\ \ {\n const requestOptions = {\n method: 'POST',\n headers: {'Content-Type':\ \ 'application/json'},\n body: mockTokenConfig // Directly use the object,\ \ as JSON parsing/stringifying is restricted\n };\n\n // Call the mock function\n\ \ mockSendHttpRequest('https://tk.amazon-adsystem.com/envelope', requestOptions,\ \ (res) => {\n if (res.body && res.body.AIPToken === 'mockToken123') {\n\ \ successCallback();\n } else {\n failureCallback();\n\ \ }\n });\n\n if (successCalled) return \"Test Passed: Success callback\ \ was called.\";\n if (failureCalled) return \"Test Failed: Failure callback\ \ was called.\";\n return \"Test Inconclusive: No callback was called.\";\n\ }\n\nlet testResult = getTokenAndVerify(gtmOnSuccess, gtmOnFailure);\n\nassertThat(testResult,\ \ \"Test Passed: Success callback was called.\");\n" - name: GTM handles no hashed records code: "const localMockData = {\n tagIds: [{TagIds: tag1}],\n standardEventName:\ \ eventName,\n tagRegion: region,\n page_location: 'https://example.com?aref=testAref',\n\ \ advancedMatchingList: [\n \n ],\n ttl: 3600,\n defaultAttributes: [{\n\ \ Attribute: 'brand',\n value: 'test brand'\n }, {Attribute: 'Category',\ \ value: ' test - category '}],\n amazonConsent: {enabled: true},\n gtmOnSuccess:\ \ function() { successCalled = true; },\n gtmOnFailure: function() { failureCalled\ \ = true; }\n};\n\nmock('getCookieValues', function(name) {\n return [];\n});\n\ \n// Call runCode to run the template's code.\nrunCode(localMockData);\n\n// Verify\ \ that the tag finished successfully.\nassertApi('gtmOnSuccess').wasCalled();" setup: |- const log = require('logToConsole'); const tag1 = 'tagId1'; const eventName = 'PageView'; const region = 'NA'; const version = '3.4'; const exampleTCFv2ConsentString = 'COw4XqLOw4XqLAAAAAENAXCAAAAAAAAAAAAAAAAAAAAA.IFukWSQgAIQwgI0QEByFAAAAeIAACAIgSAAQAIAgEQACEABAAAgAQFAEAIAAAGBAAgAAAAQAIFAAMCQAAgAAQiRAEQAAAAANAAIAAggAIYQFAAARmggBC3ZCYzU2yIA.QFukWSQgAIQwgI0QEByFAAAAeIAACAIgSAAQAIAgEQACEABAAAgAQFAEAIAAAGBAAgAAAAQAIFAAMCQAAgAAQiRAEQAAAAANAAIAAggAIYQFAAARmggBC3ZCYzU2yIA.YAAAAAAAAAAAAAAAAAA'; const mockData = { tagIds: [{TagIds: tag1}], standardEventName: eventName, tagRegion: region, page_location: 'https://example.com?aref=testAref', advancedMatchingList: [ {paramName: 'email', paramValue: 'test@example.com'}, {paramName: 'phone', paramValue: '1234567890'} ], ttl: 3600, amazonConsent: {enabled: true}, gtmOnSuccess: function() { successCalled = true; }, gtmOnFailure: function() { failureCalled = true; } }; const scriptUrl = 'https://c.amazon-adsystem.com/aat/amzn.js'; var amznCalls = []; var setCookieCalls = []; var successCalled = false; var failureCalled = false; function simpleParseUrl(url) { var parts = url.split('?'); var base = parts[0]; var params = {}; if (parts.length > 1) { var pairs = parts[1].split('&'); for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='); params[pair[0]] = pair[1] || ''; } } return { base: base, searchParams: { get: function(param) { return params[param]; } } }; } mock('logToConsole', function() { log(arguments); }); mock('injectScript', function(url, onsuccess, onfailure) { onsuccess(); }); mock('copyFromWindow', function(key) { if (key === 'amzn') return function() { amznCalls.push(arguments); }; }); mock('parseUrl', simpleParseUrl); mock('getTimestampMillis', function() { return 1625097600000; }); mock('makeInteger', function(value) { return 5; }); mock('getCookieValues', function(name) { if (name === 'aatToken') return ['existingToken']; if (name === 'amznAref') return ['oldToken.1625097500000']; return []; }); mock('setCookie', function(name, value, options) { setCookieCalls.push({name: name, value: value, options: options}); }); mock('sendHttpGet', function(url) { return { then: function(callback) { return callback({ statusCode: 200, body: 'Success' }); } }; }); mock('sendHttpRequest', function(url, options, body) { return { then: function(callback) { return callback({ statusCode: 200, body: '{"AIPToken": "newToken"}' }); } }; }); function resetMocks() { amznCalls = []; setCookieCalls = []; successCalled = false; failureCalled = false; } ___NOTES___ Created on 3/26/2020, 3:08:52 PM