FAQ

await transcend.showConsentManager();

This could be happening for a couple of different reasons:

  1. You are not in a region with applicable data laws. For California/US residents, users are opted in by default, and websites are not required to obtain consent/show a banner. For EU citizens, users are opted out by default, which means websites must ask for consent with a banner.

💡 Solution: Use our userscript here to simulate accessing the site from a given region (EU or US).

  1. You have already confirmed your consent. To clear out your consent, type the following into your dev console:
addEventListener('click', airgap.reset);
// click anywhere on the page

See Creating your own UI.

The airgap.getConsent() API returns an object in this format:

{
"purposes": {
// consent to individual tracking purposes
[TrackingPurpose in string]: boolean;
},
// Was tracking consent confirmed by the user?
// If this is false, the consent was resolved from defaults & is not yet confirmed
"confirmed": boolean;
// Consent update/sync timestamp (ISO 8601 format)
"timestamp": string; // e.g. "2021-06-29T19:16:13.964Z"
}

Example:

await airgap.sync();
const { purposes } = airgap.getConsent();
console.log(JSON.stringify(purposes, null, 2));
// example log message: { Functional: true, Advertising: true, Analytics: true }

Absolutely! See the following example:

airgap.addEventListener(
'consent-change',
({ detail: { consent, oldConsent, changes } }) => {
console.log('old consent:', oldConsent);
console.log('new consent:', consent);
console.log('consent changes:', changes);
}
);

See Syncing consent to your backend.

See Restoring consent to users from your backend.

Consent changes require proper authorization. See consent authorization to learn more.

  • Option 1: Set specific tracking preferences airgap.js onload

    <script
    src=".../airgap.js"
    onLoad="airgap.setConsent(event, { Functional: true, ... })"
    ></script>

    HTML

    <button onClick="airgap.setConsent(event, { Functional: true, ... })">
    Save tracking preferences
    </button>

    JavaScript

    saveConsentButton.addEventListener('click', (event) => {
    airgap.setConsent(event, { Functional: true /* ... */ });
    });
  • Option 2: Opt-in or opt-out to all tracking preferences (airgap.optIn() and airgap.optOut()) HTML

    <button onclick="airgap.optOut(event)">Opt-out</button>
    <button onclick="airgap.optIn(event)">Opt-in</button>

    JavaScript:

    optOutButton.addEventListener('click', airgap.optOut);
    optInButton.addEventListener('click', airgap.optIn);
  • Set "Do not sell my personal information" tracking preference

    // "Do not sell my personal information" consent (CCPA/CPRA)
    saleOfInfoOptOutButton.addEventListener('click', (interaction) => {
    airgap.setConsent(interaction, { SaleOfInfo: false });
    });
    saleOfInfoOptInButton.addEventListener('click', (interaction) => {
    airgap.setConsent(interaction, { SaleOfInfo: true });
    });

If you are concerned about the ePrivacy Directive and use of local storage, you can limit our replay quarantine feature to use a non-persistent in-memory queue by setting data-replay="mutations" on your airgap.js script tag. Our quarantine can also be completely disabled with data-replay="off".

The default behavior is to persist the replay quarantine between pageviews.

Note that setting the quarantine to use non-persistent storage will cause the queue to be cleared on each page load.

You may load airgap.js asynchronously, though you should be aware that airgap.js can not regulate any scripts that load before it. Asynchronous loading is recommended for single-page applications in order to further reduce page load time.

// Call `await getAirgap()` to load airgap.js
// Get your bundle ID at https://app.transcend.io/consent-manager/developer-settings
// Bundle ID is in this format: https://cdn.transcend.io/cm/{bundle-id}/airgap.js
const BUNDLE_ID = 'your-bundle-id-here';
let airgapAPI;
const getAirgap = () =>
(airgapAPI ??= new Promise((resolve, reject) => {
// Stub airgap.js ready queue
if (!self?.airgap?.ready) {
self.airgap = {
readyQueue: [],
ready(callback) {
this.readyQueue.push(callback);
},
...self?.airgap,
};
}
// Wait for airgap.js to be ready
self.airgap.ready((airgap) => {
resolve(airgap);
});
const script = document.createElement('script');
// Reject promise if airgap.js fails to load
script.addEventListener('error', (evt) => {
reject(evt);
});
// Specify load options:
// e.g.
// script.dataset.lazyLoadUi = 'on';
// script.dataset.prompt = 'auto';
// script.dataset.dismissedViewState = 'Closed';
// Load airgap.js script asynchronously
script.async = script.defer = true;
script.src = `https://cdn.transcend.io/cm/${BUNDLE_ID}/airgap.js`;
document.documentElement?.appendChild?.(script);
}));

Cross-domain consent sync works without any special configuration. In order to also get cross-domain quarantine sync, you need to host your own first-party sync endpoint.

This is possible through our cross-domain iframe postMessage interface, Transcend XDI.

This API is automatically scoped to only allow the domains in your domain list to get consent and quarantine data. By default, private quarantine data synchronized through this API never leaves the browser.

// Get your bundle ID at https://app.transcend.io/consent-manager/developer-settings
// Bundle ID is in this format: https://cdn.transcend.io/cm/{bundle-id}/airgap.js
const BUNDLE_ID = 'your-bundle-id-here';
let xdiAPI;
const getXDI = () =>
(xdiAPI ??= new Promise((resolve, reject) => {
// Stub Transcend XDI ready queue
if (!self?.transcend?.xdi?.ready) {
self.transcend = {
...self?.transcend,
xdi: {
readyQueue: [],
ready(callback) {
this.readyQueue.push(callback);
},
...self?.transcend?.xdi,
},
};
}
// Wait for Transcend XDI to be ready
self.transcend.xdi.ready((xdi) => {
resolve(xdi);
});
const script = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'script'
);
script.addEventListener('error', (evt) => {
reject(evt);
});
// Load Transcend XDI script asynchronously
script.async = script.defer = true;
script.src = `https://cdn.transcend.io/cm/${BUNDLE_ID}/xdi.js`;
document.documentElement.appendChild(script);
}));
let xdiChannel;
const connect = async () =>
(xdiChannel ??= await new Promise(async (resolve) => {
const xdi = await getXDI();
resolve(
await xdi.connect(
`https://sync.transcend.io/consent-manager/${BUNDLE_ID}`
)
);
}));
const getConsent = async () => {
const channel = await connect();
const { consent } = await channel.run('ConsentManager:Sync', {
sync: ['consent'],
});
return consent;
};
console.log(await getConsent());

Absolutely. With this snippet, you're ready to go!

// Get your bundle ID at https://app.transcend.io/consent-manager/developer-settings
// Bundle ID is in this format: https://cdn.transcend.io/cm/{bundle-id}/airgap.js
const isCaliforniaResident =
self.Intl &&
new Intl.DateTimeFormat().resolvedOptions().timeZone ===
'America/Los_Angeles';
const doSomethingThatRequiresFacebookLDUOverrides = async () => {
if (isCaliforniaResident) {
// Wait for airgap.js to init
// This automatically starts applying our global LDU/etc. overrides
await getAirgap();
}
// loadFacebookPixel(...)
};

See the airgap.js API reference for more API information.

We fully regulate almost every API used for tracking in modern browsers. Notably absent from our first-class regulatory coverage are the following technologies, all of which are on our roadmap with clear paths forward to add regulation support:

  • CSS / CSSOM
  • SVG-namespaced subresources
  • Realms
    • Same-origin frame (w/o our Consent Manager) subresources
    • ECMAScript ShadowRealms API
  • Automated page navigation (e.g. loading a new page without responding to a user gesture)
  • Trusted Types override/quarantine functionality — Trusted Types-originating requests can be allowed/denied but not yet overridden or quarantined in a seamless manner.

Note: All of these technologies (except automated page navigation) are regulated with blocking when using a Content Security Policy. You can enable our dynamic Content Security Policy feature in your Consent Manager Admin Dashboard.

Transcend Consent was designed to load quickly, operate efficiently during website usage and speed up the website experience. Check out Transcend's blog post on Architecting Transcend Consent for performance for more information on how airgap.js was built with speed and efficiency in mind.

The following performance metrics were measured on an Apple M1 Pro laptop under light-medium load, using the transcend.io bundle configuration:

  • Request processing overhead
    • Average (hot JIT): ~35μs/request
    • Worst case (cold JIT): ~350μs/request
    • This is the entire overhead for 'pure network APIs' (e.g. fetch, XMLHttpRequest, etc.)
  • 'HTML string' DOM mutation processing overhead: ~65μs/request-causing node (in addition to request processing overhead)
    • HTML string consumers include innerHTML, outerHTML, iframe.srcdoc, and document.write

As a stress test, we benchmarked re-processing transcend.io's entire homepage (equivalent to calling document.documentElement.innerHTML = document.documentElement.innerHTML) and measured an average total latency of 24.6ms while processing ~250 request-causing elements per call.

Note: Performance overhead scales linearly with the size of your bundle configuration.

Currently, as of February 17, 2022 our uncompressed sizes are as follows:

  • airgap.js core: 35.9 KB compressed / 88 KB uncompressed (can be loaded asynchronously)
  • UI: 69.6 KB compressed / 248 KB uncompressed (async and optionally lazy-loaded)
  • Private cross-domain sync host (XDI): 10.9 KB compressed / 23.4 KB uncompressed (lazy-loaded)

📏 Size changes are coming soon! The following changes will happen later in development.

These are estimates of the size changes that will happen with our upcoming changes:

  • +5-10 KB (uncompressed) to airgap.js core to regulate the remaining technologies on our Regulation Roadmap
  • -7-10 KB (uncompressed) from all of airgap.js core and XDI through clearly attainable optimizations in common components

Note: Size figures do not include additional embedded configuration data.

We currently do not configure any browser-caching for our Consent JS files (https://cdn.transcend.io/cm/...). The only caching that happens is DNS caching + CDN caching. This simplifies publishing changes to your consent manager bundle, but could result in slower load times for users on low bandwidth.

Going forward, our plan is to configure browser-caching for 4 hours by default, and allow each customer to configure this time period from the Admin Dashboard. When initially setting up the bundle, customers can set this time period to 0 (disabling browser cache), and then increase the time period as the Consent bundle stabilizes and no longer need to be updated frequently.

Even with CDN caching enabled, it is possible to generate a dynamic query parameter that can be used to cache-bust according to your own set of criteria. For example, you could create a cache-buster to re-fetch the script once per hour, even if the CDN caching was configured to 4 hours.

function roundToHour(date) {
p = 60 * 60 * 1000; // milliseconds in an hour
return new Date(Math.round(date.getTime() / p) * p);
}
const cacheString = roundToHour(new Date()).toISOString();
const script = document.createElement('script');
script.setAttribute(
'src',
'https://cdn.transcend.io/cm/ee571c7f-030a-41b2-affa-70df8a47b57b/airgap.js?=' +
cacheString
);
document.head.appendChild(script);

An alternative method that we can also support is to have separate paths, one for versioned files and one that always has the latest version. When using the latest version/static URL, you can cache for some short period of time (or have no cache). When using the versioned URL, you can cache indefinitely and “cache-bust” by updating the URL to point to the newer hash/version.

The safest/recommended approach is still to include the script directly into the HTML header. But if that is not feasible, then you can also use Tag Managers to inject the script. When using a tag manager, it is important to configure the airgap.js script to be the first script injected. This allows the airgap.js script to regulate subsequent scripts that get injected.

Read on for more specific instructions.

The GTM script itself should be safe to load without having any tracking side effects. You will need to configure the airgap.js script as the first script to load by using the “Consent Initialization” trigger. This is loaded before all other page view triggers.

References:

The initial request to fetch the adobe script manager should not be tracking user data as a side effect. You will need to configure the tag rule event for the airgap.js script to be the "Library Loaded (Page Top)" event and also specify "0" as the rule ordering priority.

References:

The initial request to fetch the Tealium script manager should not be tracking user data as a side effect. To have Transcend Consent load as early as possible, we recommend adding it to the utag.sync.js script. The script is placed in the <head> section of your page code and loads synchronously to comply with the most common vendor requirements. Instructions for how to do this can be found here. You'll want to modify the utag.sync.js script to load our Consent script as follows:

// Get your bundle ID at https://app.transcend.io/consent-manager/developer-settings
// Bundle ID is in this format: https://cdn.transcend.io/cm/{bundle-id}/airgap.js
const script = document.createElement('script');
// Specify load options:
// e.g.
// script.dataset.lazyLoadUi = 'on';
// script.dataset.prompt = 'auto';
// script.dataset.dismissedViewState = 'Closed';
// Load airgap.js script synchronously
script.src = `https://cdn.transcend.io/cm/${BUNDLE_ID}/airgap.js`;
document.documentElement?.appendChild?.(script);

References: