PageSourceSearch

https://www.bamboohr.com/scripts/consent.js

js bamboohr.com collected 2026-10-01 07:05:13 UTC 8,884 bytes, 191 lines download raw bytes

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.