Skip to content

Instantly share code, notes, and snippets.

@sperand-io
Last active October 31, 2023 10:05

Revisions

  1. sperand-io revised this gist Jun 10, 2019. 2 changed files with 189 additions and 1 deletion.
    188 changes: 188 additions & 0 deletions granularDecision.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,188 @@
    // 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. then drop this script below the snippet.

    // 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). You may also want to transpile it to ES5 w eg Babel.

    // 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!
    // 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 = "sNOks4InIbuBaPcSQa76ny0neogp6yDf"; // your segment website source write key
    const SEGMENT_MAPPING = 1;
    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)
    fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then(
    destinations => {
    // 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(
    "getGDPRConsentDecision",
    YOUR_DOMAIN
    );

    const destinationPreferences = destinations
    .map(function(dest) {
    if (consentDecision.includes(3)) return { [dest.id]: true };
    if (consentDecision.includes(2)) return { [dest.id]: dest.category === "Advertising" ? false : true };
    if (consentDecision.includes(1)) return { [dest.id]: false };
    if (consentDecision.includes(0)) return { [dest.id]: !OPT_IN };
    })
    .reduce(
    (acc, val) => {
    return {
    ...val,
    ...acc
    };
    },
    { "Segment.io": consentDecision.some(d => d === SEGMENT_MAPPING) }
    );

    conditionallyLoadAnalytics({
    writeKey: WEBSITE_WRITE_KEY,
    destinations,
    destinationPreferences,
    isConsentRequired: OPT_IN
    });
    }
    );

    // instructs trustarc to notify on changes to consent
    window.top.postMessage(JSON.stringify({
    PrivacyManagerAPI: {
    action: "getConsent",
    timestamp: new Date().getTime(),
    self: YOUR_DOMAIN
    }
    }), "*");

    // registers listener for consent changes.
    // some care has been taken here to handle messages safely.
    // will reload window if they've "denied"... to reset tracking.
    window.addEventListener("message", function reload(e) {
    let data = e.data;
    if (typeof data === "string") {
    try {
    data = JSON.parse(data);
    } catch (e) { /* weird message, bail */}
    }
    if (
    data &&
    data.PrivacyManagerAPI &&
    data.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
    }) {
    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 destinationPreferences) {
    const isEnabled = destinationPreferences[destination];
    if (isEnabled) {
    isAnythingEnabled = true;
    }
    }

    // Reload the page if the trackers have already been initialized so that
    // the user's new preferences can take effect
    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: destinationPreferences });
    }
    }

    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
    .reduce((map, item) => {
    if (d.id === "Repeater") return map; // remove repeater
    map.has(item["id"]) || map.set(item["id"], item);
    return map;
    }, new Map()) // return object
    .values()
    ];

    return destinations;
    }
    2 changes: 1 addition & 1 deletion index.js
    Original file line number Diff line number Diff line change
    @@ -129,7 +129,7 @@ function conditionallyLoadAnalytics({
    // the user's new preferences can take affect
    if (window.analytics.initialized) {
    if (shouldReload) {
    // window.location.reload();
    window.location.reload();
    }
    return;
    }
  2. sperand-io revised this gist Jun 7, 2019. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -104,7 +104,6 @@ function conditionallyLoadAnalytics({
    isConsentRequired,
    shouldReload = true // change if you dont want to reload on consent changes
    }) {
    const integrations = { "Segment.io": true };
    let isAnythingEnabled = false;

    if (!destinationPreferences) {
    @@ -124,7 +123,6 @@ function conditionallyLoadAnalytics({
    if (isEnabled) {
    isAnythingEnabled = true;
    }
    integrations[destination.id] = isEnabled;
    }

    // Reload the page if the trackers have already been initialised so that
    @@ -138,7 +136,7 @@ function conditionallyLoadAnalytics({

    // Don't load a.js at all if nothing has been enabled
    if (isAnythingEnabled) {
    window.analytics.load(writeKey, { destinationPreferences });
    window.analytics.load(writeKey, { integrations: destinationPreferences });
    }
    }

  3. sperand-io revised this gist Jun 7, 2019. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -44,8 +44,7 @@ fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then(
    const destinationPreferences = destinations
    .map(function(dest) {
    if (consentDecision === 3) return { [dest.id]: true };
    if (consentDecision === 2)
    return { [dest.id]: dest.category === "Advertising" ? false : true };
    if (consentDecision === 2) return { [dest.id]: dest.category === "Advertising" ? false : true };
    if (consentDecision === 1) return { [dest.id]: false };
    if (consentDecision === 0) return { [dest.id]: !OPT_IN };
    })
  4. sperand-io revised this gist Jun 7, 2019. 1 changed file with 9 additions and 2 deletions.
    11 changes: 9 additions & 2 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -68,7 +68,7 @@ fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then(
    }
    );

    // registers a listener for consent changes, will reload window if they've denied to reset tracking
    // instructs trustarc to notify on changes to consent
    window.top.postMessage(JSON.stringify({
    PrivacyManagerAPI: {
    action: "getConsent",
    @@ -77,9 +77,16 @@ window.top.postMessage(JSON.stringify({
    }
    }), "*");

    // registers listener for consent changes.
    // some care has been taken here to handle messages safely.
    // will reload window if they've "denied"... to reset tracking.
    window.addEventListener("message", function reload(e) {
    let data = e.data;
    if (typeof data === "string") data = JSON.parse(data);
    if (typeof data === "string") {
    try {
    data = JSON.parse(data);
    } catch (e) { /* weird message, bail */}
    }
    if (
    data &&
    data.PrivacyManagerAPI &&
  5. sperand-io revised this gist Jun 7, 2019. 1 changed file with 14 additions and 9 deletions.
    23 changes: 14 additions & 9 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -70,18 +70,23 @@ fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then(

    // 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
    }
    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();
    }
    let data = e.data;
    if (typeof data === "string") data = JSON.parse(data);
    if (
    data &&
    data.PrivacyManagerAPI &&
    data.PrivacyManagerAPI.consent === "denied"
    ) {
    return window.location.reload();
    }
    // otherwise approved... carry on!
    }, false);

  6. sperand-io revised this gist Jun 7, 2019. 2 changed files with 88 additions and 80 deletions.
    158 changes: 88 additions & 70 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -14,52 +14,67 @@
    // import inEU from '@segment/in-eu'

    // CONFIG — EDIT ME!
    const REQUIRE_CONSENT = false // 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
    const SEGMENT_MAPPING = 1 // functional
    // 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 = "sNOks4InIbuBaPcSQa76ny0neogp6yDf"; // your segment website source write key
    const SEGMENT_MAPPING = 1;
    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)
    fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then((destinations) => {
    // 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 === 'Advertising' ? false : true }
    if (consentDecision === 1) return { [dest.id]: false }
    if (consentDecision === 0) return { [dest.id]: true } // IMPORTANT - this is default value for "no preference set"
    }).reduce((acc, val) => ({ ...val, ...acc }), {
    'Segment.io': consentDecision >= SEGMENT_MAPPING
    })

    conditionallyLoadAnalytics({
    WEBSITE_WRITE_KEY,
    destinations,
    destinationPreferences,
    OPT_IN
    })
    })
    fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then(
    destinations => {
    // 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.category === "Advertising" ? false : true };
    if (consentDecision === 1) return { [dest.id]: false };
    if (consentDecision === 0) return { [dest.id]: !OPT_IN };
    })
    .reduce(
    (acc, val) => {
    return {
    ...val,
    ...acc
    };
    },
    { "Segment.io": consentDecision >= SEGMENT_MAPPING }
    );

    conditionallyLoadAnalytics({
    writeKey: WEBSITE_WRITE_KEY,
    destinations,
    destinationPreferences,
    isConsentRequired: OPT_IN
    });
    }
    );

    // registers a listener for consent changes, will reload window if they've denied to reset tracking
    window.top.postMessage(JSON.stringify({
    PrivacyManagerAPI: {
    PrivacyManagerAPI: {
    action: "getConsent",
    timestamp: new Date().getTime(),
    self: YOUR_DOMAIN
    }
    }
    }), "*");

    window.addEventListener("message", function reload(e) {
    @@ -68,7 +83,7 @@ window.addEventListener("message", function reload(e) {
    return window.location.reload();
    }
    // otherwise approved... carry on!
    }, false);
    }, false);

    // helper functions below...
    function conditionallyLoadAnalytics({
    @@ -78,84 +93,87 @@ function conditionallyLoadAnalytics({
    isConsentRequired,
    shouldReload = true // change if you dont want to reload on consent changes
    }) {
    const integrations = {All: false, 'Segment.io': true}
    let isAnythingEnabled = false
    const integrations = { "Segment.io": true };
    let isAnythingEnabled = false;

    if (!destinationPreferences) {
    if (isConsentRequired) {
    return
    return;
    }

    // Load a.js normally when consent isn't required and there's no preferences
    if (!window.analytics.initialized) {
    window.analytics.load(writeKey)
    window.analytics.load(writeKey);
    }
    return
    return;
    }

    for (const destination of destinations) {
    const isEnabled = Boolean(destinationPreferences[destination.id])
    for (const destination of destinationPreferences) {
    const isEnabled = destinationPreferences[destination];
    if (isEnabled) {
    isAnythingEnabled = true
    isAnythingEnabled = true;
    }
    integrations[destination.id] = isEnabled
    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()
    // window.location.reload();
    }
    return
    return;
    }

    // Don't load a.js at all if nothing has been enabled
    if (isAnythingEnabled) {
    window.analytics.load(writeKey, {integrations})
    window.analytics.load(writeKey, { destinationPreferences });
    }
    }

    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()
    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
    destination.id = destination.creationName;
    delete destination.creationName;
    }

    return destinations
    return destinations;
    }

    async function fetchDestinations(...writeKeys) {
    const destinationsRequests = []
    const destinationsRequests = [];
    for (const writeKey of writeKeys) {
    destinationsRequests.push(fetchDestinationForWriteKey(writeKey))
    destinationsRequests.push(fetchDestinationForWriteKey(writeKey));
    }
    let destinations = await Promise.all(destinationsRequests)

    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
    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;
    }
    10 changes: 0 additions & 10 deletions truste.js
    Original file line number Diff line number Diff line change
    @@ -1,10 +0,0 @@
    // test stub

    // truste.cma.callApi("getConsentDecision", YOUR_DOMAIN);

    const truste = {
    cma: {
    callApi: (x, _) => truste[x]()
    },
    getConsentDecision: () => {consentDecision: 3}
    }
  7. sperand-io revised this gist Jun 5, 2019. 1 changed file with 32 additions and 28 deletions.
    60 changes: 32 additions & 28 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -14,39 +14,43 @@
    // 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 REQUIRE_CONSENT = false // 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
    const SEGMENT_MAPPING = 1 // functional

    // 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
    fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS]).then((destinations) => {
    // 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 === 'Advertising' ? false : true }
    if (consentDecision === 1) return { [dest.id]: false }
    if (consentDecision === 0) return { [dest.id]: true } // IMPORTANT - this is default value for "no preference set"
    }).reduce((acc, val) => ({ ...val, ...acc }), {
    'Segment.io': consentDecision >= SEGMENT_MAPPING
    })

    conditionallyLoadAnalytics({
    WEBSITE_WRITE_KEY,
    destinations,
    destinationPreferences,
    OPT_IN
    })
    })

    // registers a listener for consent changes, will reload window if they've denied to reset tracking
  8. sperand-io revised this gist Jun 5, 2019. 2 changed files with 2 additions and 7 deletions.
    7 changes: 1 addition & 6 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -40,12 +40,7 @@ const destinationPreferences = destinations.map(function (dest) {
    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"
    }).reduce((acc, val) => {
    return {
    ...val,
    ...acc
    }
    }, {})
    })

    conditionallyLoadAnalytics({
    WEBSITE_WRITE_KEY,
    2 changes: 1 addition & 1 deletion truste.js
    Original file line number Diff line number Diff line change
    @@ -6,5 +6,5 @@ const truste = {
    cma: {
    callApi: (x, _) => truste[x]()
    },
    getConsentDecision: () => ({ consentDecision: 3 })
    getConsentDecision: () => {consentDecision: 3}
    }
  9. sperand-io revised this gist Jun 5, 2019. 2 changed files with 7 additions and 2 deletions.
    7 changes: 6 additions & 1 deletion index.js
    Original file line number Diff line number Diff line change
    @@ -40,7 +40,12 @@ const destinationPreferences = destinations.map(function (dest) {
    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"
    })
    }).reduce((acc, val) => {
    return {
    ...val,
    ...acc
    }
    }, {})

    conditionallyLoadAnalytics({
    WEBSITE_WRITE_KEY,
    2 changes: 1 addition & 1 deletion truste.js
    Original file line number Diff line number Diff line change
    @@ -6,5 +6,5 @@ const truste = {
    cma: {
    callApi: (x, _) => truste[x]()
    },
    getConsentDecision: () => 3
    getConsentDecision: () => ({ consentDecision: 3 })
    }
  10. sperand-io revised this gist Jun 5, 2019. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions truste.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    // test stub

    // truste.cma.callApi("getConsentDecision", YOUR_DOMAIN);

    const truste = {
    cma: {
    callApi: (x, _) => truste[x]()
    },
    getConsentDecision: () => 3
    }
  11. sperand-io revised this gist Jun 3, 2019. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    // 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
    // and remove the `analytics.load("YOUR_WRITE_KEY")` call. then drop this script below the snippet.

    // 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)
    // script somehow (rollup, webpack, browserify, etc). You may also want to transpile it to ES5 w eg Babel.

    // 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
  12. sperand-io revised this gist Jun 3, 2019. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -15,9 +15,9 @@

    // 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'
    const WEBSITE_WRITE_KEY = 'writeKey'
    const OTHER_WRITE_KEYS = [] // any other ones you want to load integrations from
    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])
  13. sperand-io revised this gist Jun 3, 2019. 1 changed file with 6 additions and 2 deletions.
    8 changes: 6 additions & 2 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -5,12 +5,16 @@
    // 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)

    // if you want to conditionally require
    // 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'
    const WEBSITE_WRITE_KEY = 'writeKey'
    const OTHER_WRITE_KEYS = [] // any other ones you want to load integrations from
    @@ -42,7 +46,7 @@ conditionallyLoadAnalytics({
    WEBSITE_WRITE_KEY,
    destinations,
    destinationPreferences,
    true // or replace with custom isConsentRequired func, eg. inEU() from above!
    OPT_IN
    })

    // registers a listener for consent changes, will reload window if they've denied to reset tracking
  14. sperand-io revised this gist Jun 3, 2019. 1 changed file with 25 additions and 18 deletions.
    43 changes: 25 additions & 18 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,14 @@
    // 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 (so that you can conditionally manage the loading process).
    // and remove the `analytics.load("YOUR_WRITE_KEY")` call

    // then "build" this file with something like rollup, parcel, browserify etc targeting whatever browsers you need
    // and include just below the snippet
    // 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)

    import fetch from 'isomorphic-fetch'
    import {flatten, sortedUniqBy, sortBy} from 'lodash'
    import inEU from '@segment/in-eu'
    // if you want to conditionally require

    // import fetch from 'isomorphic-fetch'
    // import inEU from '@segment/in-eu'

    // CONFIG — EDIT ME!
    const YOUR_DOMAIN = 'domain.com'
    @@ -30,10 +32,10 @@ const destinations = await fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_
    // 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.name]: true };
    if (consentDecision === 2) return { [dest.name]: dest.categories.includes('Advertising') ? false : true };
    if (consentDecision === 1) return { [dest.name]: dest.name === 'Segment.io' ? true : false }; // segment only for require (warehouses etc, note messages will still be de)
    if (consentDecision === 0) return { [dest.name]: true } // IMPORTANT - this is default value for "no preference set"
    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({
    @@ -107,7 +109,7 @@ function conditionallyLoadAnalytics({
    }

    async function fetchDestinationForWriteKey(writeKey) {
    const res = await fetch(
    const res = await window.fetch(
    `https://cdn.segment.com/v1/projects/${writeKey}/integrations`
    )

    @@ -130,17 +132,22 @@ async function fetchDestinationForWriteKey(writeKey) {
    return destinations
    }

    async function fetchDestinations(writeKeys) {
    async function fetchDestinations(...writeKeys) {
    const destinationsRequests = []
    for (const writeKey of writeKeys) {
    destinationsRequests.push(fetchDestinationForWriteKey(writeKey))
    }

    let destinations = flatten(await Promise.all(destinationsRequests))
    // Remove the dummy Repeater destination
    destinations = destinations.filter(d => d.id !== 'Repeater')
    destinations = sortBy(destinations, ['id'])
    destinations = sortedUniqBy(destinations, 'id')

    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
    }
  15. sperand-io revised this gist Jun 1, 2019. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -60,6 +60,7 @@ window.addEventListener("message", function reload(e) {
    // otherwise approved... carry on!
    }, false);

    // helper functions below...
    function conditionallyLoadAnalytics({
    writeKey,
    destinations,
  16. sperand-io revised this gist Jun 1, 2019. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion index.js
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,8 @@
    // 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 (so that you can conditionally manage the loading process).

    // then "build" this file with something like rollup, parcel, browserify etc targeting whatever browsers you need!
    // then "build" this file with something like rollup, parcel, browserify etc targeting whatever browsers you need
    // and include just below the snippet

    import fetch from 'isomorphic-fetch'
    import {flatten, sortedUniqBy, sortBy} from 'lodash'
  17. sperand-io revised this gist Jun 1, 2019. 1 changed file with 16 additions and 8 deletions.
    24 changes: 16 additions & 8 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -1,29 +1,37 @@
    // 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 (so that you can conditionally manage the loading process).


    // optional, requires build step:
    // then "build" this file with something like rollup, parcel, browserify etc targeting whatever browsers you need!

    import fetch from 'isomorphic-fetch'
    import {flatten, sortedUniqBy, sortBy} from 'lodash'
    import inEU from '@segment/in-eu'

    // CONFIG
    // CONFIG — EDIT ME!
    const YOUR_DOMAIN = 'domain.com'
    const WEBSITE_WRITE_KEY = 'writeKey'
    const OTHER_WRITE_KEYS = [] // any other ones you want to load integrations from

    // gets enabled destinations across your source (and other sources in workspace)
    // 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 getConsentCategories and map between the cookie domains to specific Segment Ints, etc
    // 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.name]: true };
    if (consentDecision === 2) return { [dest.name]: dest.categories.includes('Advertising') ? false : true }; // non-advertising only
    if (consentDecision === 1) return { [dest.name]: dest.name === 'Segment.io' ? true : false }; // segment only
    if (consentDecision === 2) return { [dest.name]: dest.categories.includes('Advertising') ? false : true };
    if (consentDecision === 1) return { [dest.name]: dest.name === 'Segment.io' ? true : false }; // segment only for require (warehouses etc, note messages will still be de)
    if (consentDecision === 0) return { [dest.name]: true } // IMPORTANT - this is default value for "no preference set"
    })

    @@ -34,7 +42,7 @@ conditionallyLoadAnalytics({
    true // or replace with custom isConsentRequired func, eg. inEU() from above!
    })

    // registers a listener for consent changes, will reload too
    // registers a listener for consent changes, will reload window if they've denied to reset tracking
    window.top.postMessage(JSON.stringify({
    PrivacyManagerAPI: {
    action: "getConsent",
  18. sperand-io revised this gist Jun 1, 2019. 2 changed files with 132 additions and 57 deletions.
    145 changes: 132 additions & 13 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -1,17 +1,136 @@
    // 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 (so that you can conditionally manage the loading process).

    const [isConsentRequired, destinations] = await Promise.all([
    shouldRequireConsent(),
    fetchDestinations([writeKey, ...otherWriteKeys])
    ])

    const newDestinations = getNewDestinations(
    destinations,
    destinationPreferences
    // optional, requires build step:

    import fetch from 'isomorphic-fetch'
    import {flatten, sortedUniqBy, sortBy} from 'lodash'
    import inEU from '@segment/in-eu'

    // CONFIG
    const YOUR_DOMAIN = 'domain.com'
    const WEBSITE_WRITE_KEY = 'writeKey'
    const OTHER_WRITE_KEYS = [] // any other ones you want to load integrations from

    // gets enabled destinations across your source (and other sources in workspace)
    const destinations = await fetchDestinations([WEBSITE_WRITE_KEY, ...OTHER_WRITE_KEYS])

    // KEY LOGIC HERE
    // eg. could instead use getConsentCategories and map between the cookie domains to specific Segment Ints, etc
    // review this logic carefully, and edit per your business requirements
    const { consentDecision } = truste.cma.callApi("getConsentDecision", YOUR_DOMAIN);
    const destinationPreferences = destinations.map(function (dest) {
    if (consentDecision === 3) return { [dest.name]: true };
    if (consentDecision === 2) return { [dest.name]: dest.categories.includes('Advertising') ? false : true }; // non-advertising only
    if (consentDecision === 1) return { [dest.name]: dest.name === 'Segment.io' ? true : false }; // segment only
    if (consentDecision === 0) return { [dest.name]: true } // IMPORTANT - this is default value for "no preference set"
    })

    conditionallyLoadAnalytics({
    WEBSITE_WRITE_KEY,
    destinations,
    destinationPreferences,
    true // or replace with custom isConsentRequired func, eg. inEU() from above!
    })

    // registers a listener for consent changes, will reload too
    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);

    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 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 = flatten(await Promise.all(destinationsRequests))
    // Remove the dummy Repeater destination
    destinations = destinations.filter(d => d.id !== 'Repeater')
    destinations = sortBy(destinations, ['id'])
    destinations = sortedUniqBy(destinations, 'id')

    conditionallyLoadAnalytics({
    writeKey,
    destinations,
    destinationPreferences,
    isConsentRequired
    })
    return destinations
    }
    44 changes: 0 additions & 44 deletions loadAnalytics.js
    Original file line number Diff line number Diff line change
    @@ -1,44 +0,0 @@
    export default function conditionallyLoadAnalytics({
    writeKey,
    destinations,
    destinationPreferences,
    isConsentRequired,
    shouldReload = true
    }) {
    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})
    }
    }
  19. sperand-io created this gist Jun 1, 2019.
    17 changes: 17 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@

    const [isConsentRequired, destinations] = await Promise.all([
    shouldRequireConsent(),
    fetchDestinations([writeKey, ...otherWriteKeys])
    ])

    const newDestinations = getNewDestinations(
    destinations,
    destinationPreferences
    )

    conditionallyLoadAnalytics({
    writeKey,
    destinations,
    destinationPreferences,
    isConsentRequired
    })
    44 changes: 44 additions & 0 deletions loadAnalytics.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,44 @@
    export default function conditionallyLoadAnalytics({
    writeKey,
    destinations,
    destinationPreferences,
    isConsentRequired,
    shouldReload = true
    }) {
    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})
    }
    }