Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions web/public/consent/dg-consent.js

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions web/src/consent/SOURCE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"note": "Vendored verbatim from deepgram/deepgram-docs so cli.deepgram.com matches developers.deepgram.com. Do not edit by hand; re-copy both files from the same docs commit.",
"repo": "https://github.com/deepgram/deepgram-docs",
"commit": "b807ab4613f9b138c761a7f21339975108c2e00b",
"files": {
"web/src/consent/ketch.js": "fern/assets/ketch.js",
"web/public/consent/dg-consent.js": "fern/assets/dg-consent.js (built from tools/consent-banner)"
},
"sha256": {
"ketch.js": "00348a71e084e22ca2096d50f5d9179f572b7a753220436203c2c2590224f2b0",
"dg-consent.js": "a9ba240426d3299fde1bc365897c677dfde1cf7403ac11e5ae5a56fb6d30eee0"
}
}
96 changes: 96 additions & 0 deletions web/src/consent/ketch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Ketch consent manager loader. Must run before Google Tag Manager and every
// other tracking script, so it is the first `beforeInteractive` entry in
// docs.yml and GTM is loaded from ./gtm.js right after it (not from Fern's
// built-in `analytics.gtm`, which Fern always renders ahead of custom JS).
// The line below is Ketch's snippet verbatim, minus the <script> wrapper.
!function(){window.semaphore=window.semaphore||[],window.ketch=function(){window.semaphore.push(arguments)};var e=document.createElement("script");e.type="text/javascript",e.src="https://global.ketchcdn.com/web/v3/config/deepgram/deepgram_marketing_site/boot.js",e.defer=e.async=!0,document.getElementsByTagName("head")[0].appendChild(e)}();

// Consent helpers shared by gtm.js, component.js (Heap) and sentry.js (Sentry).
// One `consent` subscription keeps Ketch's latest answer. Ketch fires
// `consent` when it resolves the visitor's choice and again whenever the
// visitor changes it, including a withdrawal, and replays the current value to
// late subscribers. Purpose codes come from the deepgram_marketing_site
// property: `analytics` and `targeted_advertising` (opt-in under gdpr, where
// unconsented purposes are left out of the payload; granted by default
// elsewhere). If Ketch never loads (blocked, offline), nothing counts as
// granted: trackers fail closed.
(function (w) {
var granted = {};
var waiting = [];
var revokers = [];

function hasAll(purposes) {
for (var i = 0; i < purposes.length; i++) {
if (granted[purposes[i]] !== true) return false;
}
return true;
}

function run(cb) {
// A throwing callback must not stop the others; rethrow it on its own so
// Sentry still sees it. It is not retried: a callback that failed partway
// could load GTM or add Replay twice if run again, which is worse than
// leaving that one tracker off until the next page load.
try {
cb();
} catch (err) {
setTimeout(function () {
throw err;
});
}
}

w.ketch("on", "consent", function (consent) {
granted = (consent && consent.purposes) || {};
var ready = [];
var still = [];
for (var i = 0; i < waiting.length; i++) {
(hasAll(waiting[i].purposes) ? ready : still).push(waiting[i]);
}
waiting = still;
for (var j = 0; j < ready.length; j++) run(ready[j].cb);
for (var k = 0; k < revokers.length; k++) {
var now = hasAll(revokers[k].purposes);
if (revokers[k].had && !now) run(revokers[k].cb);
revokers[k].had = now;
}
});

// True only while Ketch's latest answer grants every purpose in `purposes`.
// Check it at the moment data would leave the page, so a withdrawal stops
// anything that forwards data per event.
w.dgHasConsent = function (purposes) {
return hasAll(purposes);
};

// Run `cb` once, the first time every purpose in `purposes` is granted, so a
// later opt-in still runs it. To stop a tracker on withdrawal, pair it with
// dgOnRevoke; anything that can't pause stops on the next full page load.
w.dgOnConsent = function (purposes, cb) {
if (hasAll(purposes)) run(cb);
else waiting.push({ purposes: purposes, cb: cb });
};

// Run `cb` each time a visitor withdraws any purpose in `purposes` after
// granting all of them, so a tracker that can pause (Heap, Sentry Replay)
// stops collecting on the same page. A tracker stopped this way stays off
// if the visitor grants consent again on the same page, until the next
// page load. GTM tags rely on the container's own Ketch consent settings
// to stop on withdrawal.
w.dgOnRevoke = function (purposes, cb) {
revokers.push({ purposes: purposes, cb: cb, had: hasAll(purposes) });
};

// Ketch's `needsConsent` verdict for this page load: true while consent is
// missing or incomplete, false once the visitor has chosen, null until Ketch
// says. Ketch emits it once, before `consent`, and does not replay it to late
// subscribers. dg-consent.js subscribes after hydration, after Ketch has
// emitted it, so it reads the verdict recorded here to decide auto-open.
var needsConsent = null;
w.ketch("on", "needsConsent", function (value) {
if (typeof value === "boolean") needsConsent = value;
});
w.dgKetchNeedsConsent = function () {
return needsConsent;
};
})(window);
51 changes: 51 additions & 0 deletions web/src/consent/trackers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Heap and Google Analytics 4 for cli.deepgram.com, held until Ketch consent
// through the dgOnConsent / dgOnRevoke helpers in ./ketch.js, as on
// developers.deepgram.com (fern/assets/component.js). Both wait for
// `targeted_advertising`: the Ketch config for deepgram_marketing_site lists
// Heap's _hp2_* and GA's _ga / _ga_* cookies under that purpose. If Ketch
// never loads (blocked, offline), neither starts.
(function (w, d) {
if (typeof w.dgOnConsent !== "function") return;
var PURPOSES = ["targeted_advertising"];
var GA_ID = "G-TYPC1TBCKT";

function insertHeap() {
if (d.getElementById("heap-script")) return;
var script = d.createElement("script");
script.id = "heap-script";
script.text =
'window.heapReadyCb=window.heapReadyCb||[],window.heap=window.heap||[],heap.load=function(e,t){window.heap.envId=e,window.heap.clientConfig=t=t||{},window.heap.clientConfig.shouldFetchServerConfig=!1;var a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src="https://cdn.us.heap-api.com/config/"+e+"/heap_config.js";var r=document.getElementsByTagName("script")[0];r.parentNode.insertBefore(a,r);var n=["init","startTracking","stopTracking","track","resetIdentity","identify","getSessionId","getUserId","getIdentity","addUserProperties","addEventProperties","removeEventProperty","clearEventProperties","addAccountProperties","addAdapter","addTransformer","addTransformerFn","onReady","addPageviewProperties","removePageviewProperty","clearPageviewProperties","trackPageview"],i=function(e){return function(){var t=Array.prototype.slice.call(arguments,0);window.heapReadyCb.push({name:e,fn:function(){heap[e]&&heap[e].apply(heap,t)}})}};for(var p=0;p<n.length;p++)heap[n[p]]=i(n[p])};' +
'heap.load("765739241");';
d.head.appendChild(script);
}

function stopHeap() {
if (w.heap && typeof w.heap.stopTracking === "function") w.heap.stopTracking();
}

function insertGa() {
if (d.getElementById("ga4-script")) return;
w.dataLayer = w.dataLayer || [];
w.gtag = function () {
w.dataLayer.push(arguments);
};
w.gtag("js", new Date());
w.gtag("config", GA_ID);
var script = d.createElement("script");
script.id = "ga4-script";
script.async = true;
script.src = "https://www.googletagmanager.com/gtag/js?id=" + GA_ID;
d.head.appendChild(script);
}

// Google's documented per-property opt-out; the page also reloads on a
// withdrawal (dg-consent.js), so nothing restarts until consent is granted.
function stopGa() {
w["ga-disable-" + GA_ID] = true;
}

w.dgOnConsent(PURPOSES, insertHeap);
w.dgOnConsent(PURPOSES, insertGa);
w.dgOnRevoke(PURPOSES, stopHeap);
w.dgOnRevoke(PURPOSES, stopGa);
})(window, document);
30 changes: 15 additions & 15 deletions web/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,20 @@ const commandCards: CommandCard[] = [
desc: 'Sits in front of a stream so you can see exactly what reaches Deepgram.',
lines: ['dg debug probe'] },
];
import ketchJs from '../consent/ketch.js?raw';
import consentTrackersJs from '../consent/trackers.js?raw';
---

<html lang="en" prefix="og: https://ogp.me/ns#">
<html lang="en" class="dark" prefix="og: https://ogp.me/ns#">
<head>
<!-- ── Essentials ──────────────────────────────────────── -->
<meta charset="utf-8" />
<!--
Ketch consent manager plus the dgOnConsent / dgOnRevoke helpers, vendored
from developers.deepgram.com (src/consent/SOURCE.json). Must stay the
FIRST script in <head>: Heap and GA4 below wait for its consent.
-->
<script is:inline set:html={ketchJs}></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
Expand Down Expand Up @@ -438,20 +446,8 @@ const commandCards: CommandCard[] = [
]
})} />

<!-- Heap Analytics — product analytics for cli.deepgram.com -->
<script is:inline>
window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.us.heap-api.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o<p.length;o++)heap[o]=n(p[o])};
heap.load("765739241");
</script>

<!-- Google Analytics 4 — *.deepgram.com property (266000242 / 372400484) -->
<script is:inline async src="https://www.googletagmanager.com/gtag/js?id=G-TYPC1TBCKT"></script>
<script is:inline>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-TYPC1TBCKT');
</script>
<!-- Heap and Google Analytics 4 (G-TYPC1TBCKT), started only after Ketch grants targeted_advertising -->
<script is:inline set:html={consentTrackersJs}></script>
</head>
<body class="bg-dg-black text-dg-text font-sans overflow-x-hidden" style="font-feature-settings: 'cv02','cv03','cv04','cv11';">

Expand Down Expand Up @@ -913,10 +909,14 @@ const commandCards: CommandCard[] = [
</div>
<div class="pt-6 text-[11px] text-dg-pebble flex flex-col sm:flex-row justify-between gap-2" style="border-top: 1px solid #2c2c3330;">
<span>© {new Date().getFullYear()} Deepgram, Inc. All rights reserved.</span>
<!-- dg-consent.js mounts the "Your Privacy Choices" control here (Privacy Policy if Ketch is unavailable). -->
<div id="fern-footer"></div>
</div>
</div>
</footer>

<!-- Cookie consent banner and footer control, vendored from developers.deepgram.com. Loads no tracker. -->
<script is:inline src="/consent/dg-consent.js" defer></script>
</body>
</html>

Expand Down
Loading