___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": "CLIENT", "id": "cvt_temp_public_id", "version": 1, "securityGroups": [], "displayName": "Data Client", "brand": { "id": "brand_dummy", "displayName": "Stape", "thumbnail": "data:image/png;base64,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" }, "description": "Use this Client as a mapper from Request Data to Event Data.", "containerContexts": [ "SERVER" ] } ___TEMPLATE_PARAMETERS___ [ { "type": "CHECKBOX", "name": "exposeFPIDCookie", "checkboxText": "Expose FPID Cookie", "simpleValueType": true, "help": "If enabled, the server only accessible FPID cookie, generated by UA/GA4 client, will be duplicated to FPIDP cookie, which will be accessible from the client JS. Highly recommend using this option only in case it is necessary." }, { "type": "CHECKBOX", "name": "generateClientId", "checkboxText": "Always generate client_id parameter", "simpleValueType": true, "help": "If enabled, even if the `client_id` parameter will not be determined from the request, it will still be generated. The `client_id` parameter is required by UA/GA4 tags.", "defaultValue": true }, { "type": "CHECKBOX", "name": "prolongCookies", "checkboxText": "Automatically prolong Data Tag cookies", "simpleValueType": true, "help": "If enabled, cookies generated by Data tag will be reseated from the server with an expiration time of two years. Useful if you use Data tag store functionality.", "defaultValue": true }, { "type": "CHECKBOX", "name": "acceptMultipleEvents", "checkboxText": "Accept Multiple Events", "simpleValueType": true, "help": "When the Accept Multiple Events is set to true, the Data Client will parse an array of objects, in the request body, as separate events.\nExample:\n\u003cbr /\u003e\n[\n {\"event\":\"page_view\"},\n {\"event\":\"view_item\"}\n]", "defaultValue": false }, { "type": "GROUP", "name": "responseSettings", "displayName": "Response Settings", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "SELECT", "name": "responseStatusCode", "displayName": "Response Status Code", "selectItems": [ { "value": 200, "displayValue": "200" }, { "value": 201, "displayValue": "201" }, { "value": 301, "displayValue": "301" }, { "value": 302, "displayValue": "302" }, { "value": 403, "displayValue": "403" }, { "value": 404, "displayValue": "404" } ], "simpleValueType": true, "defaultValue": 200 }, { "type": "GROUP", "name": "regularResponseSettings", "groupStyle": "NO_ZIPPY", "subParams": [ { "type": "SELECT", "name": "responseBody", "displayName": "Response Body", "macrosInSelect": false, "selectItems": [ { "value": "timestamp", "displayValue": "JSON Object with timestamp (recommended)" }, { "value": "eventData", "displayValue": "JSON Object with Event Data" }, { "value": "empty", "displayValue": "Empty" } ], "simpleValueType": true, "defaultValue": "timestamp" }, { "type": "CHECKBOX", "name": "responseBodyGet", "checkboxText": "Send Response Body for GET request", "simpleValueType": true, "help": "By default, for the GET request type, the answer is image pixel. \u003ca target\u003d\"_blank\" href\u003d\"https://developers.google.com/tag-manager/serverside/api#setpixelresponse\"\u003eMore Info\u003c/a\u003e." } ], "enablingConditions": [ { "paramName": "responseStatusCode", "paramValue": 200, "type": "EQUALS" }, { "paramName": "responseStatusCode", "paramValue": 201, "type": "EQUALS" } ] }, { "type": "GROUP", "name": "redirectResponseSettings", "groupStyle": "NO_ZIPPY", "subParams": [ { "type": "TEXT", "name": "redirectTo", "displayName": "Redirect To", "simpleValueType": true, "valueValidators": [ { "type": "NON_EMPTY" }, { "type": "REGEX", "args": [ "^https?:\\/\\/.*" ] } ] }, { "type": "CHECKBOX", "name": "lookupForRedirectToParam", "checkboxText": "Try to find redirect destination in query params", "simpleValueType": true, "help": "Override destination URL with query param from request url" }, { "type": "TEXT", "name": "redirectToQueryParamName", "displayName": "Query Param Name", "simpleValueType": true, "enablingConditions": [ { "paramName": "lookupForRedirectToParam", "paramValue": true, "type": "EQUALS" } ], "valueValidators": [ { "type": "NON_EMPTY" } ] } ], "enablingConditions": [ { "paramName": "responseStatusCode", "paramValue": 301, "type": "EQUALS" }, { "paramName": "responseStatusCode", "paramValue": 302, "type": "EQUALS" } ] }, { "type": "GROUP", "name": "clientErrorResponseSettings", "groupStyle": "NO_ZIPPY", "subParams": [ { "type": "TEXT", "name": "clientErrorResponseMessage", "displayName": "Client Error Response Message", "simpleValueType": true, "valueValidators": [ { "type": "NON_EMPTY" } ] } ], "enablingConditions": [ { "paramName": "responseStatusCode", "paramValue": 403, "type": "EQUALS" }, { "paramName": "responseStatusCode", "paramValue": 404, "type": "EQUALS" } ] } ] }, { "type": "GROUP", "name": "pathSettings", "displayName": "Accepted Path Settings", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "SIMPLE_TABLE", "name": "path", "displayName": "Type additional paths that will be claimed by this client", "simpleTableColumns": [ { "defaultValue": "", "displayName": "For example: /callback", "name": "path", "type": "TEXT", "isUnique": true, "valueValidators": [ { "type": "NON_EMPTY" } ] } ], "newRowButtonText": "Add path", "help": "By default path \u003cb\u003e/data\u003c/b\u003e will be claimed. But you can add more paths that will be claimed by this client." } ] } ] ___SANDBOXED_JS_FOR_SERVER___ const returnResponse = require('returnResponse'); const runContainer = require('runContainer'); const setResponseHeader = require('setResponseHeader'); const setResponseStatus = require('setResponseStatus'); const setResponseBody = require('setResponseBody'); const JSON = require('JSON'); const fromBase64 = require('fromBase64'); const getTimestampMillis = require('getTimestampMillis'); const getCookieValues = require('getCookieValues'); const getRequestBody = require('getRequestBody'); const getRequestMethod = require('getRequestMethod'); const getRequestHeader = require('getRequestHeader'); const getRequestPath = require('getRequestPath'); const getRequestQueryParameters = require('getRequestQueryParameters'); const makeInteger = require('makeInteger'); const getRemoteAddress = require('getRemoteAddress'); const setCookie = require('setCookie'); const setPixelResponse = require('setPixelResponse'); const generateRandom = require('generateRandom'); const computeEffectiveTldPlusOne = require('computeEffectiveTldPlusOne'); const getRequestQueryParameter = require('getRequestQueryParameter'); const getType = require('getType'); const Promise = require('Promise'); const decodeUriComponent = require('decodeUriComponent'); const createRegex = require('createRegex'); const makeString = require('makeString'); const requestMethod = getRequestMethod(); const path = getRequestPath(); let isClientUsed = false; let isEventModelsWrappedInArray = false; if (path === '/data') { runClient(); } if (data.path && !isClientUsed) { for (let key in data.path) { if (!isClientUsed && data.path[key].path === path) { runClient(); } } } function runClient() { isClientUsed = true; require('claimRequest')(); if (requestMethod === 'OPTIONS') { setResponseHeaders(200); returnResponse(); return; } const baseEventModel = getBaseEventModelWithQueryParameters(); let eventModels = getEventModels(baseEventModel); const clientId = getClientId(eventModels); eventModels = eventModels.map((eventModel) => { eventModel = addRequiredParametersToEventModel(eventModel); eventModel = addCommonParametersToEventModel(eventModel); eventModel = addClientIdToEventModel(eventModel, clientId); return eventModel; }); storeClientId(eventModels[0]); exposeFPIDCookie(eventModels[0]); prolongDataTagCookies(eventModels[0]); const responseStatusCode = makeInteger(data.responseStatusCode); setResponseHeaders(responseStatusCode); Promise.all( eventModels.map((eventModel) => { return Promise.create((resolve) => { runContainer(eventModel, resolve); }); }) ).then(() => { switch (responseStatusCode) { case 200: case 201: if (requestMethod === 'POST' || data.responseBodyGet) { prepareResponseBody(eventModels); } else { setPixelResponse(); } break; case 301: case 302: setRedirectLocation(); break; case 403: case 404: setClientErrorResponseMessage(); break; } returnResponse(); }); } function addCommonParametersToEventModel(eventModel) { if (!eventModel.ip_override) { if (eventModel.ip) eventModel.ip_override = eventModel.ip; else if (eventModel.ipOverride) eventModel.ip_override = eventModel.ipOverride; else eventModel.ip_override = getRemoteAddress(); } if (!eventModel.user_agent) { if (eventModel.userAgent) eventModel.user_agent = eventModel.userAgent; else if (getRequestHeader('User-Agent')) eventModel.user_agent = getRequestHeader('User-Agent'); } if (!eventModel.language) { const acceptLanguageHeader = getRequestHeader('Accept-Language'); if (acceptLanguageHeader) { eventModel.language = acceptLanguageHeader .split(';')[0] .substring(0, 2) .toLowerCase(); } } if (!eventModel.page_hostname) { if (eventModel.pageHostname) eventModel.page_hostname = eventModel.pageHostname; else if (eventModel.hostname) eventModel.page_hostname = eventModel.hostname; } if (!eventModel.page_location) { if (eventModel.pageLocation) eventModel.page_location = eventModel.pageLocation; else if (eventModel.url) eventModel.page_location = eventModel.url; else if (eventModel.href) eventModel.page_location = eventModel.href; } if (!eventModel.page_referrer) { if (eventModel.pageReferrer) eventModel.page_referrer = eventModel.pageReferrer; else if (eventModel.referrer) eventModel.page_referrer = eventModel.referrer; else if (eventModel.urlref) eventModel.page_referrer = eventModel.urlref; } if (!eventModel.value && eventModel.e_v) eventModel.value = eventModel.e_v; if (getType(eventModel.items) === 'array' && eventModel.items.length) { const firstItem = eventModel.items[0]; if (!eventModel.currency && firstItem.currency) eventModel.currency = firstItem.currency; if (eventModel.items.length === 1) { if (!eventModel.item_id && firstItem.item_id) eventModel.item_id = firstItem.item_id; if (!eventModel.item_name && firstItem.item_name) eventModel.item_name = firstItem.item_name; if (!eventModel.item_brand && firstItem.item_brand) eventModel.item_brand = firstItem.item_brand; if (!eventModel.item_quantity && firstItem.quantity) eventModel.item_quantity = firstItem.quantity; if (!eventModel.item_category && firstItem.item_category) eventModel.item_category = firstItem.item_category; if (!eventModel.item_price && firstItem.price) eventModel.item_price = firstItem.price; } if (!eventModel.value) { const valueFromItems = eventModel.items.reduce((acc, item) => { if (!item.price) return acc; const quantity = item.quantity ? item.quantity : 1; return acc + quantity * item.price; }, 0); if (valueFromItems) eventModel.value = valueFromItems; } } const ecommerceAction = getEcommerceAction(eventModel); if (ecommerceAction) { if (!eventModel['x-ga-mp1-pa']) eventModel['x-ga-mp1-pa'] = ecommerceAction; if ( ecommerceAction === 'purchase' && eventModel.ecommerce.purchase.actionField ) { if (!eventModel['x-ga-mp1-tr']) eventModel['x-ga-mp1-tr'] = eventModel.ecommerce.purchase.actionField.revenue; if (!eventModel.revenue) eventModel.revenue = eventModel.ecommerce.purchase.actionField.revenue; if (!eventModel.affiliation) eventModel.affiliation = eventModel.ecommerce.purchase.actionField.affiliation; if (!eventModel.tax) eventModel.tax = eventModel.ecommerce.purchase.actionField.tax; if (!eventModel.shipping) eventModel.shipping = eventModel.ecommerce.purchase.actionField.shipping; if (!eventModel.coupon) eventModel.coupon = eventModel.ecommerce.purchase.actionField.coupon; if (!eventModel.transaction_id) eventModel.transaction_id = eventModel.ecommerce.purchase.actionField.id; } } if (!eventModel.page_encoding && eventModel.pageEncoding) eventModel.page_encoding = eventModel.pageEncoding; if (!eventModel.page_path && eventModel.pagePath) eventModel.page_path = eventModel.pagePath; if (!eventModel.page_title && eventModel.pageTitle) eventModel.page_title = eventModel.pageTitle; if (!eventModel.screen_resolution && eventModel.screenResolution) eventModel.screen_resolution = eventModel.screenResolution; if (!eventModel.viewport_size && eventModel.viewportSize) eventModel.viewport_size = eventModel.viewportSize; if (!eventModel.user_id && eventModel.userId) eventModel.user_id = eventModel.userId; if (!eventModel.user_data) { let userData = {}; let userAddressData = {}; if (!userData.email_address) { if (eventModel.userEmail) userData.email_address = eventModel.userEmail; else if (eventModel.email_address) userData.email_address = eventModel.email_address; else if (eventModel.email) userData.email_address = eventModel.email; else if (eventModel.mail) userData.email_address = eventModel.mail; } if (!userData.phone_number) { if (eventModel.userPhoneNumber) userData.phone_number = eventModel.userPhoneNumber; else if (eventModel.phone_number) userData.phone_number = eventModel.phone_number; else if (eventModel.phoneNumber) userData.phone_number = eventModel.phoneNumber; else if (eventModel.phone) userData.phone_number = eventModel.phone; } if (!userAddressData.street && eventModel.street) userAddressData.street = eventModel.street; if (!userAddressData.city && eventModel.city) userAddressData.city = eventModel.city; if (!userAddressData.region && eventModel.region) userAddressData.region = eventModel.region; if (!userAddressData.country && eventModel.country) userAddressData.country = eventModel.country; if (!userAddressData.first_name) { if (eventModel.userFirstName) userAddressData.first_name = eventModel.userFirstName; else if (eventModel.first_name) userAddressData.first_name = eventModel.first_name; else if (eventModel.firstName) userAddressData.first_name = eventModel.firstName; else if (eventModel.name) userAddressData.first_name = eventModel.name; } if (!userAddressData.last_name) { if (eventModel.userLastName) userAddressData.last_name = eventModel.userLastName; else if (eventModel.last_name) userAddressData.last_name = eventModel.last_name; else if (eventModel.lastName) userAddressData.last_name = eventModel.lastName; else if (eventModel.surname) userAddressData.last_name = eventModel.surname; else if (eventModel.family_name) userAddressData.last_name = eventModel.family_name; else if (eventModel.familyName) userAddressData.last_name = eventModel.familyName; } if (!userAddressData.region) { if (eventModel.region) userAddressData.region = eventModel.region; else if (eventModel.state) userAddressData.region = eventModel.state; } if (!userAddressData.postal_code) { if (eventModel.postal_code) userAddressData.postal_code = eventModel.postal_code; else if (eventModel.postalCode) userAddressData.postal_code = eventModel.postalCode; else if (eventModel.zip) userAddressData.postal_code = eventModel.zip; } if (getObjectLength(userAddressData) !== 0) { userData.address = userAddressData; } if (!eventModel.user_data && getObjectLength(userData) !== 0) { eventModel.user_data = userData; } } return eventModel; } function getBaseEventModelWithQueryParameters() { const requestQueryParameters = getRequestQueryParameters(); const eventModel = {}; if (requestQueryParameters) { for (let queryParameterKey in requestQueryParameters) { if ( (queryParameterKey === 'dtcd' || queryParameterKey === 'dtdc') && requestMethod === 'GET' ) { let dt = queryParameterKey === 'dtcd' ? JSON.parse(requestQueryParameters[queryParameterKey]) : JSON.parse(fromBase64(requestQueryParameters[queryParameterKey])); for (let dtKey in dt) { eventModel[dtKey] = dt[dtKey]; } } else { eventModel[queryParameterKey] = requestQueryParameters[queryParameterKey]; } } } return eventModel; } function addClientIdToEventModel(eventModel, clientId) { eventModel.client_id = clientId; return eventModel; } function prolongDataTagCookies(eventModel) { if (data.prolongCookies) { let stapeData = getCookieValues('stape'); if (stapeData.length) { setCookie('stape', stapeData[0], { domain: 'auto', path: '/', samesite: getCookieType(eventModel), secure: true, 'max-age': 63072000, // 2 years httpOnly: false, }); } } } function addRequiredParametersToEventModel(eventModel) { if (!eventModel.event_name) { let eventName = 'Data'; if (eventModel.eventName) eventName = eventModel.eventName; else if (eventModel.event) eventName = eventModel.event; else if (eventModel.e_n) eventName = eventModel.e_n; eventModel.event_name = eventName; } return eventModel; } function exposeFPIDCookie(eventModel) { if (data.exposeFPIDCookie) { let fpid = getCookieValues('FPID'); if (fpid.length) { setCookie('FPIDP', fpid[0], { domain: 'auto', path: '/', samesite: getCookieType(eventModel), secure: true, 'max-age': 63072000, // 2 years httpOnly: false, }); } } } function storeClientId(eventModel) { if (data.generateClientId) { setCookie('_dcid', eventModel.client_id, { domain: 'auto', path: '/', samesite: getCookieType(eventModel), secure: true, 'max-age': 63072000, // 2 years httpOnly: false, }); } } function getObjectLength(object) { let length = 0; for (let key in object) { if (object.hasOwnProperty(key)) { ++length; } } return length; } function setResponseHeaders(statusCode) { setResponseHeader('Access-Control-Max-Age', '600'); setResponseHeader('Access-Control-Allow-Origin', getRequestHeader('origin')); setResponseHeader( 'Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS' ); setResponseHeader( 'Access-Control-Allow-Headers', 'content-type,set-cookie,x-robots-tag,x-gtm-server-preview,x-stape-preview' ); setResponseHeader('Access-Control-Allow-Credentials', 'true'); setResponseStatus(statusCode); } function getCookieType(eventModel) { if (!eventModel.page_location) { return 'Lax'; } const host = getRequestHeader('host'); const effectiveTldPlusOne = computeEffectiveTldPlusOne( eventModel.page_location ); if (!host || !effectiveTldPlusOne) { return 'Lax'; } if (host && host.indexOf(effectiveTldPlusOne) !== -1) { return 'Lax'; } return 'None'; } function prepareResponseBody(eventModels) { if (data.responseBody === 'empty') { return; } const responseModel = isEventModelsWrappedInArray ? eventModels[0] : eventModels; setResponseHeader('Content-Type', 'application/json'); if (data.responseBody === 'eventData') { setResponseBody(JSON.stringify(responseModel)); return; } if (isEventModelsWrappedInArray) { setResponseBody( JSON.stringify({ timestamp: responseModel.timestamp, unique_event_id: responseModel.unique_event_id, }) ); return; } setResponseBody( JSON.stringify( eventModels.map((eventModel) => { return { timestamp: eventModel.timestamp, unique_event_id: eventModel.unique_event_id, }; }) ) ); } function getEcommerceAction(eventModel) { if (eventModel.ecommerce) { const actions = [ 'detail', 'click', 'add', 'remove', 'checkout', 'checkout_option', 'purchase', 'refund', ]; for (let index = 0; index < actions.length; ++index) { const action = actions[index]; if (eventModel.ecommerce[action]) { return action; } } } return null; } function setRedirectLocation() { let location = data.redirectTo; if (data.lookupForRedirectToParam && data.redirectToQueryParamName) { const param = getRequestQueryParameter(data.redirectToQueryParamName); if (param && param.startsWith('http')) { location = param; } } setResponseHeaders('location', location); } function setClientErrorResponseMessage() { if (data.clientErrorResponseMessage) { setResponseBody(data.clientErrorResponseMessage); } } function getEventModels(baseEventModel) { const body = getRequestBody(); if (body) { const contentType = getRequestHeader('content-type'); const isFormUrlEncoded = !!contentType && contentType.indexOf('application/x-www-form-urlencoded') !== -1; let bodyJson = isFormUrlEncoded ? parseUrlEncoded(body) : JSON.parse(body); if (bodyJson) { const bodyType = getType(bodyJson); const shouldUseOriginalBody = data.acceptMultipleEvents && bodyType === 'array'; if (!shouldUseOriginalBody) { bodyJson = [bodyJson]; isEventModelsWrappedInArray = true; } return bodyJson.map((bodyItem) => { const eventModel = assign({}, baseEventModel, { timestamp: makeInteger(getTimestampMillis() / 1000), unique_event_id: getTimestampMillis() + '_' + generateRandom(100000000, 999999999), }); for (let bodyItemKey in bodyItem) { eventModel[bodyItemKey] = bodyItem[bodyItemKey]; } return eventModel; }); } } return [ assign({}, baseEventModel, { timestamp: makeInteger(getTimestampMillis() / 1000), unique_event_id: getTimestampMillis() + '_' + generateRandom(100000000, 999999999), }), ]; } function getClientId(eventModels) { for (let i = 0; i < eventModels.length; i++) { const eventModel = eventModels[i]; const clientId = eventModel.client_id || eventModel.data_client_id || eventModel._dcid; if (clientId) return clientId; } const dcid = getCookieValues('_dcid'); if (dcid && dcid[0]) return dcid[0]; if (data.generateClientId) { return ( 'dcid.1.' + getTimestampMillis() + '.' + generateRandom(100000000, 999999999) ); } return ''; } function assign() { const target = arguments[0]; for (let i = 1; i < arguments.length; i++) { for (let key in arguments[i]) { target[key] = arguments[i][key]; } } return target; } function parseUrlEncoded(data) { const pairs = data.split('&'); const parsedData = {}; const regex = createRegex('\\+', 'g'); for (const pair of pairs) { const pairValue = pair.split('='); const key = pairValue[0]; const value = pairValue[1]; const keys = key .split('.') .map((k) => decodeUriComponent(k.replace(regex, ' '))); let currentObject = parsedData; for (let i = 0; i < keys.length - 1; i++) { const currentKey = keys[i]; if (!currentObject[currentKey]) { const nextKey = keys[i + 1]; const nextKeyIsNumber = makeString(makeInteger(nextKey)) === nextKey; currentObject[currentKey] = nextKeyIsNumber ? [] : {}; } currentObject = currentObject[currentKey]; } const lastKey = keys[keys.length - 1]; const decodedValue = decodeUriComponent(value.replace(regex, ' ')); const parsedValue = JSON.parse(decodedValue) || decodedValue; if (getType(currentObject) === 'array') { currentObject.push(parsedValue); } else { currentObject[lastKey] = parsedValue; } } return parsedData; } ___SERVER_PERMISSIONS___ [ { "instance": { "key": { "publicId": "return_response", "versionId": "1" }, "param": [] }, "isRequired": true }, { "instance": { "key": { "publicId": "access_response", "versionId": "1" }, "param": [ { "key": "writeResponseAccess", "value": { "type": 1, "string": "any" } }, { "key": "writeHeaderAccess", "value": { "type": 1, "string": "specific" } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "run_container", "versionId": "1" }, "param": [] }, "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": "stape" }, { "type": 1, "string": "_dcid" }, { "type": 1, "string": "FPIDP" }, { "type": 1, "string": "FPID" } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "read_request", "versionId": "1" }, "param": [ { "key": "requestAccess", "value": { "type": 1, "string": "any" } }, { "key": "headerAccess", "value": { "type": 1, "string": "any" } }, { "key": "queryParameterAccess", "value": { "type": 1, "string": "any" } } ] }, "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": "stape" }, { "type": 1, "string": "*" }, { "type": 1, "string": "*" }, { "type": 1, "string": "any" }, { "type": 1, "string": "any" } ] }, { "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": "_dcid" }, { "type": 1, "string": "*" }, { "type": 1, "string": "*" }, { "type": 1, "string": "any" }, { "type": 1, "string": "any" } ] }, { "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": "FPIDP" }, { "type": 1, "string": "*" }, { "type": 1, "string": "*" }, { "type": 1, "string": "any" }, { "type": 1, "string": "any" } ] } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true } ] ___TESTS___ scenarios: - name: Quick Test code: runCode(); setup: '' ___NOTES___ Created on 21/03/2021, 11:24:30