1/** 2 * DataGrail Consent Management â minimal integration. 3 * 4 * DataGrail's embedded deployment is fully integrated with GTM: it sets GCM defaults, 5 * pushes consent updates, manages page reloads, and drives GTM triggers directly. 6 * Everything that used to live here for those concerns has been moved to DataGrail's 7 * side. This file's only responsibilities now are: 8 * 1. Defang the current history entry (see comment below) so DataGrail's reload â 9 * and any other future reload â can't replay a POST submission. 10 * 2. Load the DataGrail consent-loader.js script. 11 * 3. Expose window.addConsentListener so other in-repo callers can run code once 12 * consent has been resolved. 13 * 4. Expose isAdvertisingCookieAllowed / isFunctionalCookieAllowed for cookie/storage 14 * gating across the codebase. 15 * 5. Expose applyWistiaConsent to keep Wistia's Privacy Mode in sync with consent. 16 */ 17 18const DATAGRAIL_CONSENT_LOADER_URL = window.location.hostname === 'www.bamboostage.com' 19 ? 'https://api.consentjs.datagrail.io/3cce3d81-3af7-4fb3-9dfe-f0c84a46b32a/7066733c-614d-403b-bd13-ed131302c2e7/consent-loader.js' 20 : 'https://api.consentjs.datagrail.io/3cce3d81-3af7-4fb3-9dfe-f0c84a46b32a/6a9023dc-ceab-4544-b52b-7c97cebe67d9/consent-loader.js'; 21 22// --------------------------------------------------------------------------- 23// Defang POST-loaded history entries 24// --------------------------------------------------------------------------- 25// This site embeds Marketo forms (and a few other POST-submitting integrations) 26// that can land users on pages the browser remembers as having been submitted 27// via POST. Any subsequent window.location.reload() against such an entry â 28// including DataGrail's own reload when consent changes, our own reloads, GTM 29// tags, or app code â would repeat the POST, triggering "Confirm Form 30// Resubmission" prompts or silently re-submitting the form. 31// 32// history.replaceState with the current URL replaces the active session-history 33// entry. The new entry has no associated form data, so the browser treats it 34// like any other GET navigation: reload() becomes a plain GET. This lets us 35// keep DataGrail's reload behavior unmodified rather than blocking it and 36// reimplementing the logic ourselves. 37// 38// We pass window.history.state through so any state object set by previous 39// scripts survives. The call is a no-op on entries that were already GET-loaded 40// and costs effectively nothing, so we run it unconditionally â there's no JS 41// API that exposes the original request method to ask first. 42window.history.replaceState(window.history.state, '', window.location.href); 43 44// --------------------------------------------------------------------------- 45// addConsentListener queue 46// --------------------------------------------------------------------------- 47const consentListeners = []; 48let consentResolved = false; 49 50/** 51 * Registers a callback to run once DataGrail has resolved consent. If consent has 52 * already been resolved when this is called, the callback fires immediately. 53 */ 54window.addConsentListener = (callback) => { 55 if (consentResolved) { 56 callback(); 57 return; 58 } 59 consentListeners.push(callback); 60}; 61 62const flushConsentListeners = () => { 63 consentResolved = true; 64 consentListeners.splice(0).forEach((callback) => callback()); 65}; 66 67// --------------------------------------------------------------------------- 68// DataGrail callbacks 69// --------------------------------------------------------------------------- 70// DataGrail consumes window.dgEvent as a queue. Pushing before consent-loader.js 71// loads is safe â registrations are processed once the loader initializes. 72window.dgEvent = window.dgEvent || []; 73 74// Fires as soon as the page loads with any pre-existing consent. 75window.dgEvent.push({ 76 event: 'initial_preference_callback', 77 params: () => flushConsentListeners(), 78}); 79 80// Fires after the visitor selects an action via the banner. DataGrail handles 81// its own page reload when prefs actually change; we just notify in-page 82// listeners (e.g. Wistia) so they can re-apply consent state. The reload that 83// DataGrail issues is safe because the history.replaceState call at the top of 84// this file already converted any POST-loaded entry to a GET-equivalent one. 85// 86// Two signals fire here: a `consent-prefs-set` dataLayer event for GTM tags to 87// trigger on, and a matching DOM CustomEvent for in-page listeners (e.g. Wistia). 88window.dgEvent.push({ 89 event: 'preference_callback', 90 params: () => { 91 window.dataLayer = window.dataLayer || []; 92 window.dataLayer.push({ event: 'consent-prefs-set' }); 93 window.dispatchEvent(new CustomEvent('consent-prefs-set')); 94 flushConsentListeners(); 95 }, 96}); 97 98// --------------------------------------------------------------------------- 99// Inject custom banner CSS 100// --------------------------------------------------------------------------- 101// DataGrail's loader clones <style id="dg-consent-custom-style"> contents into 102// the banner's shadow DOM. Shadow encapsulation blocks <link rel="stylesheet">, 103// so we fetch the CSS file and inject as inline <style>. Fire-and-forget â a 104// same-origin static fetch almost always beats the async cross-origin 105// DataGrail loader. 106fetch('/styles/datagrail-banner.css') 107 .then((resp) => (resp.ok ? resp.text() : '')) 108 .then((css) => { 109 if (!css) return; 110 const style = document.createElement('style'); 111 style.id = 'dg-consent-custom-style'; 112 style.textContent = css; 113 document.head.appendChild(style); 114 }) 115 .catch(() => {}); 116 117// --------------------------------------------------------------------------- 118// Load the DataGrail consent-loader script 119// --------------------------------------------------------------------------- 120const cmpScript = document.createElement('script'); 121cmpScript.type = 'text/javascript'; 122cmpScript.src = DATAGRAIL_CONSENT_LOADER_URL; 123cmpScript.async = true; 124document.head.appendChild(cmpScript); 125 126// ---------------------------------------------------------------------------
127// Consent utility exports â source of truth for all consent checks across the codebase 128// --------------------------------------------------------------------------- 129 130/** 131 * Returns true if the given DataGrail category is currently granted. Before DataGrail 132 * finishes loading there's no consent state to read, so the safe default is deny. 133 * Most callers wrap their reads in addConsentListener anyway, so DG_BANNER_API will be 134 * available by the time they run. 135 */ 136const isCategoryGranted = (category) => { 137 if (window.DG_BANNER_API && typeof window.DG_BANNER_API.categoryEnabled === 'function') { 138 return !!window.DG_BANNER_API.categoryEnabled(category); 139 } 140 return false; 141}; 142 143/** Returns true if advertising/marketing cookies are permitted. */ 144export function isAdvertisingCookieAllowed() { 145 if (window.location.href === 'about:srcdoc') return true; 146 return isCategoryGranted('marketing'); 147} 148 149/** Returns true if functional cookies are permitted. */ 150export function isFunctionalCookieAllowed() { 151 if (window.location.href === 'about:srcdoc') return true; 152 return isCategoryGranted('functional'); 153} 154 155// --------------------------------------------------------------------------- 156// Wistia Privacy Mode helper 157// --------------------------------------------------------------------------- 158let wistiaConsentApplied = false; 159 160/** 161 * Pushes the current Wistia Player Privacy Mode value (W.consent) onto window._wq. 162 * Re-invoked on the 'consent-prefs-set' event so opt-out submissions (which don't 163 * trigger DataGrail's full-page reload) still propagate preference flips to the 164 * player without a navigation. 165 */ 166const pushWistiaConsent = () => { 167 // eslint-disable-next-line no-underscore-dangle 168 window._wq = window._wq || []; 169 const consentGranted = isFunctionalCookieAllowed() && isAdvertisingCookieAllowed(); 170 // eslint-disable-next-line no-underscore-dangle 171 window._wq.push((W) => { W.consent(consentGranted); }); 172}; 173 174/** 175 * Wires Wistia's Privacy Mode (W.consent) to our consent state. Idempotent â safe to 176 * call from every Wistia block decorator; subsequent calls bail early. 177 * 178 * Wistia's tracking on bamboohr.com is implemented via localStorage and XHR beacons â 179 * not cookies. Its Privacy Mode is a single binary switch with no way to enable 180 * cookies/storage selectively. To respect both consent buckets simultaneously without 181 * leaking data into a category the visitor denied, Privacy Mode is disabled (tracking 182 * allowed) only when BOTH functional AND advertising consent are granted. Any denial 183 * of either category puts Wistia into Privacy Mode. 184 */ 185export function applyWistiaConsent() { 186 if (wistiaConsentApplied) return; 187 wistiaConsentApplied = true; 188 189 window.addConsentListener(() => pushWistiaConsent()); 190 window.addEventListener('consent-prefs-set', () => pushWistiaConsent()); 191}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.