___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": "DigitalFingerprint", "categories": [ "ANALYTICS" ], "brand": { "id": "brand_dummy", "displayName": "DigitalFingerprint", "thumbnail": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDY0IDY0Ij48cmVjdCB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHJ4PSI4IiBmaWxsPSIjMEQxODI3Ii8+PHRleHQgeD0iMzIiIHk9IjM4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0ic3lzdGVtLXVpLHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMjAiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiNmZmYiPkRGPC90ZXh0Pjwvc3ZnPg==" }, "description": "Adds the DigitalFingerprint JS agent via Google Tag Manager so you can identify visitors and expose originId on the dataLayer. Visit https://digitalfingerprintjs.com/ for your public API key.", "containerContexts": [ "WEB" ] } ___TEMPLATE_PARAMETERS___ [ { "type": "SELECT", "name": "tagType", "displayName": "Tag type", "macrosInSelect": false, "selectItems": [ { "value": "default", "displayValue": "Start and identify" }, { "value": "start", "displayValue": "Start" }, { "value": "get", "displayValue": "Identify" } ], "simpleValueType": true }, { "type": "TEXT", "name": "apiKey", "simpleValueType": true, "displayName": "Public API key", "valueValidators": [ { "type": "NON_EMPTY" } ], "help": "Your public API key (pk_…) that authenticates the agent with the DigitalFingerprint API. Allowlist your website hostname on the key.", "enablingConditions": [ { "paramName": "tagType", "paramValue": "default", "type": "EQUALS" }, { "paramName": "tagType", "paramValue": "start", "type": "EQUALS" } ] }, { "type": "GROUP", "name": "additionalFields", "displayName": "Additional Settings", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "GROUP", "name": "Agent settings", "displayName": "", "groupStyle": "NO_ZIPPY", "subParams": [ { "type": "SIMPLE_TABLE", "name": "endpoints", "displayName": "endpoints", "simpleTableColumns": [ { "defaultValue": "", "displayName": "", "name": "endpoint", "type": "TEXT", "isUnique": true, "valueValidators": [ { "type": "NON_EMPTY" }, { "type": "REGEX", "args": [ "^https:\\/\\/[^\\s\\$.?#]+\\.[^\\s]*$" ], "errorMessage": "Enter a valid https:// URL. It must include a TLD suffix." } ] } ], "help": "Identify API endpoint used by the JS agent. If you use a custom subdomain or first-party proxy, add a row with that URL (for example https://metrics.yourapp.com/api/identify). Leave empty to use https://api.digitalfingerprintjs.com/api/identify. Only the first row is used in v1." } ], "enablingConditions": [ { "paramName": "tagType", "paramValue": "default", "type": "EQUALS" }, { "paramName": "tagType", "paramValue": "start", "type": "EQUALS" } ] }, { "type": "GROUP", "name": "Identification settings", "displayName": "", "groupStyle": "NO_ZIPPY", "subParams": [ { "type": "TEXT", "name": "tag", "displayName": "tag", "simpleValueType": true, "help": "Optional tag metadata saved with the identification event. Plain string becomes tags.tag; a JSON object string is mapped into tags key/value pairs." }, { "type": "TEXT", "name": "linkedId", "displayName": "linkedId", "simpleValueType": true, "help": "Optional custom identifier linking this identification event (for example account or order id)." }, { "type": "TEXT", "name": "resultCustomName", "displayName": "Result custom name", "simpleValueType": true, "help": "Name of the result variable emitted to the dataLayer", "defaultValue": "DigitalFingerprintResult", "valueValidators": [ { "type": "NON_EMPTY" } ] } ], "enablingConditions": [ { "paramName": "tagType", "paramValue": "default", "type": "EQUALS" }, { "paramName": "tagType", "paramValue": "get", "type": "EQUALS" } ] } ] } ] ___SANDBOXED_JS_FOR_WEB_TEMPLATE___ const log = require('logToConsole'); const injectScript = require('injectScript'); const queryPermission = require('queryPermission'); const callInWindow = require('callInWindow'); const createQueue = require('createQueue'); const getTimestampMillis = require('getTimestampMillis'); const url = 'https://cdn.digitalfingerprintjs.com/integrations/gtm/v1/adapter.iife.min.js'; log('data =', data); const tagType = data.tagType; const startOptions = { apiKey: data.apiKey, }; if (data.endpoints) { startOptions.endpoints = data.endpoints.map(function (param) { return param.endpoint; }); } const getOptions = {}; if (data.tag) { getOptions.tag = data.tag; } if (data.linkedId) { getOptions.linkedId = data.linkedId; } log('startOptions =', startOptions); log('getOptions=', getOptions); const dataLayerPush = createQueue('dataLayer'); let getStartTime; const onDfGet = function (result) { log('result', result); const getFinishTime = getTimestampMillis(); const event = {event: 'DigitalFingerprint.identified'}; result.timeToIdentifyMs = getFinishTime - getStartTime; event[data.resultCustomName || 'DigitalFingerprintResult'] = result; dataLayerPush(event); data.gtmOnSuccess(); }; const onDfGetFailure = function (reason) { log('DigitalFingerprint: Identification failed', reason); data.gtmOnFailure(); }; const doIdentification = function () { log('doIdentification'); getStartTime = getTimestampMillis(); if (queryPermission('access_globals', 'execute', 'DigitalFingerprintGTM.get')) { callInWindow('DigitalFingerprintGTM.get', getOptions, onDfGet, onDfGetFailure); } else { log('DigitalFingerprint: Identification failed due to permissions mismatch.'); data.gtmOnFailure(); } }; const onSuccess = function () { log('DigitalFingerprint: Script loaded successfully.'); callInWindow('DigitalFingerprintGTM.start', startOptions); const event = {event: 'DigitalFingerprint.started'}; dataLayerPush(event); log('started'); if (tagType === 'default') { doIdentification(); } else { data.gtmOnSuccess(); } }; const onFailure = function () { log('DigitalFingerprint: Script load failed.'); data.gtmOnFailure(); }; if (tagType === 'default' || tagType === 'start') { if (queryPermission('inject_script', url) && queryPermission('access_globals', 'execute', 'DigitalFingerprintGTM.start')) { log('try to load'); injectScript(url, onSuccess, onFailure); } else { log('DigitalFingerprint: Script load failed due to permissions mismatch.'); data.gtmOnFailure(); } } else { doIdentification(); } ___WEB_PERMISSIONS___ [ { "instance": { "key": { "publicId": "logging", "versionId": "1" }, "param": [ { "key": "environments", "value": { "type": 1, "string": "debug" } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "inject_script", "versionId": "1" }, "param": [ { "key": "urls", "value": { "type": 2, "listItem": [ { "type": 1, "string": "https://cdn.digitalfingerprintjs.com/integrations/gtm/v1/adapter.iife.min.js" } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "access_globals", "versionId": "1" }, "param": [ { "key": "keys", "value": { "type": 2, "listItem": [ { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "DigitalFingerprintGTM.start" }, { "type": 8, "boolean": false }, { "type": 8, "boolean": false }, { "type": 8, "boolean": true } ] }, { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "dataLayer" }, { "type": 8, "boolean": true }, { "type": 8, "boolean": true }, { "type": 8, "boolean": false } ] }, { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "DigitalFingerprintGTM.get" }, { "type": 8, "boolean": false }, { "type": 8, "boolean": false }, { "type": 8, "boolean": true } ] } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true } ] ___TESTS___ scenarios: - name: check loading default tag code: |- const mockData = { tagType: 'default', apiKey: 'pk_test_key', resultCustomName: 'DigitalFingerprintResult', }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, callback) { if(fname === 'DigitalFingerprintGTM.get') { callback({originId: 'oid_test', eventId: 'evt_test', timestamp: 1}); } }); runCode(mockData); assertApi('injectScript').wasCalled(); assertApi('createQueue').wasCalled(); assertApi('gtmOnSuccess').wasCalled(); - name: check loading start tag code: |- const mockData = { tagType: 'start', apiKey: 'pk_test_key', }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, callback) { if(fname === 'DigitalFingerprintGTM.get') { fail('Tag called get function'); } }); runCode(mockData); assertApi('injectScript').wasCalled(); assertApi('createQueue').wasCalled(); assertApi('gtmOnSuccess').wasCalled(); - name: check loading get tag code: |- const mockData = { tagType: 'get', apiKey: 'pk_test_key', resultCustomName: 'DigitalFingerprintResult', }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, callback) { if (fname === 'DigitalFingerprintGTM.start') { fail('Tag called start function'); } else if(fname === 'DigitalFingerprintGTM.get') { callback({originId: 'oid_test', eventId: 'evt_test', timestamp: 1}); } }); runCode(mockData); assertApi('injectScript').wasNotCalled(); assertApi('createQueue').wasCalled(); assertApi('gtmOnSuccess').wasCalled(); - name: check startOptions and getOptions with minimum params code: |- const mockData = { tagType: 'default', apiKey: 'pk_test_key', resultCustomName: 'DigitalFingerprintResult', }; const expectedStartOptions = { apiKey: 'pk_test_key', }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, callback) { if (fname === 'DigitalFingerprintGTM.start') { assertThat(options).isEqualTo(expectedStartOptions); } else if(fname === 'DigitalFingerprintGTM.get') { assertThat(options).isEqualTo({}); callback({originId: 'oid_test', eventId: 'evt_test', timestamp: 1}); } }); runCode(mockData); assertApi('gtmOnSuccess').wasCalled(); - name: check startOptions and getOptions with maximum params code: |- const mockData = { tagType: 'default', apiKey: 'pk_test_key', endpoints: [{ endpoint: 'https://metrics.example.com/api/identify' }, { endpoint: 'https://ignored.example/api/identify' }], tag: 'myTag', linkedId: 'some-id', resultCustomName: 'DigitalFingerprintResult', }; const expectedStartOptions = { apiKey: 'pk_test_key', endpoints: ['https://metrics.example.com/api/identify', 'https://ignored.example/api/identify'], }; const expectedGetOptions = { tag: 'myTag', linkedId: 'some-id', }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, callback) { if (fname === 'DigitalFingerprintGTM.start') { assertThat(options).isEqualTo(expectedStartOptions); } else if(fname === 'DigitalFingerprintGTM.get') { assertThat(options).isEqualTo(expectedGetOptions); callback({originId: 'oid_test', eventId: 'evt_test', timestamp: 1}); } }); runCode(mockData); assertApi('gtmOnSuccess').wasCalled(); - name: check resultCustomName field works code: |- const mockData = { tagType: 'get', apiKey: 'pk_test_key', resultCustomName: 'result' }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, callback) { if(fname === 'DigitalFingerprintGTM.get') { callback({originId: 'oid_test', eventId: 'evt_test', timestamp: 1}); } }); mock('createQueue', function(name) { return function(params) { const timeToIdentifyMs = params.result ? params.result.timeToIdentifyMs : undefined; assertThat(timeToIdentifyMs).isGreaterThanOrEqualTo(0); assertThat(params).isEqualTo({ event: 'DigitalFingerprint.identified', result: {originId: 'oid_test', eventId: 'evt_test', timestamp: 1, timeToIdentifyMs: timeToIdentifyMs }, }); }; }); runCode(mockData); assertApi('gtmOnSuccess').wasCalled(); - name: check get failure handling code: |- const mockData = { tagType: 'get', apiKey: 'pk_test_key', resultCustomName: 'DigitalFingerprintResult', }; mock('injectScript', function(url, onSuccess, onFail) { onSuccess(); }); mock('callInWindow', function(fname, options, onFulfilled, onRejected) { if(fname === 'DigitalFingerprintGTM.get') { onRejected({ name: 'Error', message: 'failed' }); } }); runCode(mockData); assertApi('gtmOnFailure').wasCalled(); ___NOTES___ DigitalFingerprint GTM integration v1.0.0 — adapter at https://cdn.digitalfingerprintjs.com/integrations/gtm/v1/adapter.iife.min.js