Skip to content

Instantly share code, notes, and snippets.

@sperand-io
Last active October 31, 2023 10:05
Show Gist options
  • Save sperand-io/4725e248a35d5005d68d810d8a8f7b29 to your computer and use it in GitHub Desktop.
Save sperand-io/4725e248a35d5005d68d810d8a8f7b29 to your computer and use it in GitHub Desktop.
Example of using analytics.js conditional loading with TrustArc Consent Manager
// To get started, make sure you're using the latest version of the analytics.js snippet (4.1.0 or above)
// and remove the `analytics.load("YOUR_WRITE_KEY")` call
// this is a standalone script for modern browsers. if you'd like to target older browsers, include
// a fetch polyfill and use that instead of window.fetch. This would require that you build and package the
// script somehow (rollup, webpack, browserify, etc)
// This script is configured to make all collection opt-in (rather than opt-out) for all users.
// If you want to conditionally require whether or not to block data collection before affirmative consent, use something
// like inEU below and pass that function into `conditionallyLoadAnalytics`. If you want to collect data until the user
// opts out, just change OPT_IN to false.
// import fetch from 'isomorphic-fetch'
// import inEU from '@segment/in-eu'
// CONFIG — EDIT ME!
const OPT_IN = true // false = only disable after opt out, can replace with a function such as inEU above
const YOUR_DOMAIN = 'domain.com' // the hostname of your website
const WEBSITE_WRITE_KEY = 'writeKey' // your segment website source write key
const OTHER_WRITE_KEYS = [] // any other sources w destinations that you want to include in config
// gets enabled destination configurations from across your source (and optionally other sources in workspace)
const destinations = await fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS])
// KEY LOGIC HERE
// eg. could instead use trustArc's other APIs such as getConsentCategories
// and map between the returned vendor domains to specific Segment Integrationss, etc
// review this logic carefully, and edit per your business requirements
//
// in this case, we go with all when customer provides full consent
// remove Advertising tools when the customer provides functional
// and mark Segment as required (for data to flow to warehouse etc— if you make that clear in your policy)
// note that though Segment will receive data we wont forward (even server side) to other sources because we
// decorate the calls with the full set of integrations you have enabled with "false."
//
// no preference here goes with all on — you may want to flip that!
const { consentDecision } = truste.cma.callApi("getConsentDecision", YOUR_DOMAIN);
const destinationPreferences = destinations.map(function (dest) {
if (consentDecision === 3) return { [dest.id]: true };
if (consentDecision === 2) return { [dest.id]: dest.categories.includes('Advertising') ? false : true };
if (consentDecision === 1) return { [dest.id]: dest.name === 'Segment.io' ? true : false }; // segment only for require (warehouses etc, note messages will still be de)
if (consentDecision === 0) return { [dest.id]: true } // IMPORTANT - this is default value for "no preference set"
})
conditionallyLoadAnalytics({
WEBSITE_WRITE_KEY,
destinations,
destinationPreferences,
OPT_IN
})
// registers a listener for consent changes, will reload window if they've denied to reset tracking
window.top.postMessage(JSON.stringify({
PrivacyManagerAPI: {
action: "getConsent",
timestamp: new Date().getTime(),
self: YOUR_DOMAIN
}
}), "*");
window.addEventListener("message", function reload(e) {
const response = JSON.parse(e.data)
if (response.PrivacyManagerAPI.consent === "denied") {
return window.location.reload();
}
// otherwise approved... carry on!
}, false);
// helper functions below...
function conditionallyLoadAnalytics({
writeKey,
destinations,
destinationPreferences,
isConsentRequired,
shouldReload = true // change if you dont want to reload on consent changes
}) {
const integrations = {All: false, 'Segment.io': true}
let isAnythingEnabled = false
if (!destinationPreferences) {
if (isConsentRequired) {
return
}
// Load a.js normally when consent isn't required and there's no preferences
if (!window.analytics.initialized) {
window.analytics.load(writeKey)
}
return
}
for (const destination of destinations) {
const isEnabled = Boolean(destinationPreferences[destination.id])
if (isEnabled) {
isAnythingEnabled = true
}
integrations[destination.id] = isEnabled
}
// Reload the page if the trackers have already been initialised so that
// the user's new preferences can take affect
if (window.analytics.initialized) {
if (shouldReload) {
window.location.reload()
}
return
}
// Don't load a.js at all if nothing has been enabled
if (isAnythingEnabled) {
window.analytics.load(writeKey, {integrations})
}
}
async function fetchDestinationForWriteKey(writeKey) {
const res = await window.fetch(
`https://cdn.segment.com/v1/projects/${writeKey}/integrations`
)
if (!res.ok) {
throw new Error(
`Failed to fetch integrations for write key ${writeKey}: HTTP ${
res.status
} ${res.statusText}`
)
}
const destinations = await res.json()
// Rename creationName to id to abstract the weird data model
for (const destination of destinations) {
destination.id = destination.creationName
delete destination.creationName
}
return destinations
}
async function fetchDestinations(...writeKeys) {
const destinationsRequests = []
for (const writeKey of writeKeys) {
destinationsRequests.push(fetchDestinationForWriteKey(writeKey))
}
let destinations = await Promise.all(destinationsRequests)
// unique list of destination across all sources
destinations = [...destinations
.reduce((a, b) => a.concat(b), []) // flatten multi-d array
.filter(d => d.id !== 'Repeater') // remove repeater
.reduce((map, item) => {
map.has(item['id']) || map.set(item['id'], item)
return map;
}, new Map()).values()]
return destinations
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment