Skip to content

Commit 445e161

Browse files
committed
Remove Google Analytics and Gauges
The Google Analytics property is a Universal Analytics one (UA-), a kind Google has stopped processing data for. Drop both trackers, the consent notice and setting, and the hosts from the CSP. Users who had consented may still carry the vendors' cookies, so the settings migration now expires them, also for the whole domain, where Google Analytics usually sets _ga. The retired consent cookies are expired rather than taken in as settings.
1 parent fde566c commit 445e161

17 files changed

Lines changed: 33 additions & 257 deletions

File tree

‎.gitignore‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@ docs/**/*
1313
*.tar.bz2
1414
*.tar.gz
1515
*.zip
16-
assets/stylesheets/components/_environment.scss
1716
assets/stylesheets/global/_icons.scss
1817
node_modules

‎assets/javascripts/app/settings.js‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ import { $ } from "../lib/util.js";
2626
* @property {number} news When the changelog was last read, as a Unix timestamp.
2727
* @property {StoredFlag} manualUpdate
2828
* @property {number} schema The offline database's schema version.
29-
* @property {StoredFlag} analyticsConsent Written as 1 or 0 rather than deleted,
30-
* so that consent that was refused is remembered.
3129
* @property {string} theme `"auto"`, `"dark"` or `"default"`.
3230
* @property {number} spaceScroll How far space scrolls, as a fraction of the viewport.
3331
* @property {number | string} spaceTimeout How long after typing space stops
@@ -59,7 +57,6 @@ export class Settings {
5957
"manualUpdate",
6058
"fastScroll",
6159
"arrowScroll",
62-
"analyticsConsent",
6360
"docs",
6461
"dark", // legacy
6562
"theme",
@@ -91,7 +88,6 @@ export class Settings {
9188
news: 0,
9289
manualUpdate: false,
9390
schema: 1,
94-
analyticsConsent: false,
9591
theme: "auto",
9692
spaceScroll: 1,
9793
spaceTimeout: 0.5,

‎assets/javascripts/application.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// @ts-check
22

33
import { app } from "./app/app.js";
4-
import "./tracking.js";
54

65
/*
76
* Copyright 2013-2026 Thibaut Courouble and other contributors

‎assets/javascripts/globals.d.ts‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ declare global {
4444
type ViewKeyboardEvent = KeyboardEvent & { target: HTMLElement; currentTarget: HTMLElement };
4545
type ViewInputEvent = Event & { target: HTMLInputElement; currentTarget: HTMLElement };
4646

47-
// --- Analytics, loaded at runtime by tracking.js ---
48-
49-
/** Google Analytics, once analytics.js has loaded. */
50-
var ga: (...args: unknown[]) => void;
51-
52-
/** Gauges' command queue. */
53-
var _gauges: unknown[] | undefined;
54-
5547
// --- Augmentations ---
5648

5749
interface Window {
@@ -61,18 +53,10 @@ declare global {
6153
/** Set by vendor/mathml.js once it has probed for MathML support. */
6254
supportsMathML?: boolean;
6355

64-
/** Gauges' command queue. */
65-
_gauges?: unknown[];
66-
6756
/** debug.js — prints the view tree, with each view's activation state. */
6857
viewTree?: (view?: unknown, level?: number, visited?: unknown[]) => void;
6958
}
7059

71-
interface Navigator {
72-
/** Global Privacy Control. Not in lib.dom yet. */
73-
readonly globalPrivacyControl?: boolean;
74-
}
75-
7660
interface XMLHttpRequest {
7761
/** Set by lib/ajax.js so that the timeout can be cleared when it settles. */
7862
timer?: ReturnType<typeof setTimeout>;

‎assets/javascripts/lib/page.js‎

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import { app } from "../app/app.js";
2-
import { config } from "../app/config.js";
3-
import {
4-
SettingsStore,
5-
expireCookie,
6-
settingsStore,
7-
} from "./settings_store.js";
81
import { $ } from "./util.js";
9-
import { Notif } from "../views/misc/notif.js";
102

113
/*
124
* Based on github.com/visionmedia/page.js
@@ -76,7 +68,6 @@ import { Notif } from "../views/misc/notif.js";
7668
* @property {(context: Context) => string | undefined} dispatch Runs the context through the registered routes, returning a redirect.
7769
* @property {() => boolean} canGoBack
7870
* @property {() => boolean} canGoForward
79-
* @property {(fn: () => void) => void} track Registers an analytics callback, run on every navigation once consent is given.
8071
*/
8172

8273
let running = false;
@@ -145,7 +136,6 @@ page.show = function (path, state) {
145136
} else {
146137
context.pushState();
147138
updateCanonicalLink();
148-
track();
149139
}
150140
return context;
151141
};
@@ -166,9 +156,6 @@ page.replace = function (path, state, skipDispatch, init) {
166156
}
167157
context.replaceState();
168158
updateCanonicalLink();
169-
if (!skipDispatch) {
170-
track();
171-
}
172159
return context;
173160
};
174161

@@ -512,61 +499,3 @@ var updateCanonicalLink = function () {
512499
`https://${location.host}${location.pathname}`,
513500
);
514501
};
515-
516-
/** @type {Array<() => void>} */
517-
const trackers = [];
518-
519-
/** @param {() => void} fn */
520-
page.track = function (fn) {
521-
trackers.push(fn);
522-
};
523-
524-
var track = function () {
525-
if (config.env !== "production") {
526-
return;
527-
}
528-
if (navigator.doNotTrack === "1") {
529-
return;
530-
}
531-
if (navigator.globalPrivacyControl) {
532-
return;
533-
}
534-
535-
const consentGiven = settingsStore.get("analyticsConsent");
536-
537-
if (consentGiven === 1) {
538-
for (var tracker of trackers) {
539-
tracker.call(undefined);
540-
}
541-
} else if (consentGiven === undefined && !consentAsked()) {
542-
new Notif("AnalyticsConsent", { autoHide: null });
543-
}
544-
};
545-
546-
/**
547-
* @returns {boolean} Whether the user has been asked for consent already, and
548-
* marks them as asked if not. Kept in sessionStorage, so the question comes
549-
* back on the next visit but not on the next page load.
550-
*/
551-
var consentAsked = function () {
552-
try {
553-
if (sessionStorage.getItem(SettingsStore.ASKED_KEY)) {
554-
return true;
555-
}
556-
sessionStorage.setItem(SettingsStore.ASKED_KEY, "1");
557-
} catch (error) {}
558-
return false;
559-
};
560-
561-
/**
562-
* Expires the analytics cookies, which are the ones the vendors set with a
563-
* single leading `_`. The app has none of its own.
564-
*/
565-
export const resetAnalytics = function () {
566-
for (var cookie of document.cookie.split(/;\s?/)) {
567-
var name = cookie.split("=")[0];
568-
if (name[0] === "_" && name[1] !== "_") {
569-
expireCookie(name);
570-
}
571-
}
572-
};

‎assets/javascripts/lib/settings_store.js‎

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export class SettingsStore {
2626
static KEY = "settings";
2727

2828
/**
29-
* The sessionStorage key the once-a-session analytics prompt is tracked by.
30-
* Not a setting: it was a session cookie, and is meant to last a visit.
29+
* Cookies of an earlier version that aren't settings any more: the consent
30+
* to analytics, and the session-only flag the consent prompt was tracked by.
3131
*/
32-
static ASKED_KEY = "analyticsConsentAsked";
32+
static RETIRED_KEYS = ["analyticsConsent", "analyticsConsentAsked"];
3333

3434
/**
3535
* Hook called when a value read back after a write doesn't match what was
@@ -89,12 +89,9 @@ export class SettingsStore {
8989
this.storage.set(SettingsStore.KEY, settings);
9090
}
9191

92-
/** Clears every setting, and the flags that outlive them. */
92+
/** Clears every setting. */
9393
reset() {
9494
this.storage.del(SettingsStore.KEY);
95-
try {
96-
sessionStorage.removeItem(SettingsStore.ASKED_KEY);
97-
} catch (error) {}
9895
}
9996

10097
/**
@@ -113,9 +110,10 @@ export class SettingsStore {
113110
* expires them. Does nothing once they are gone.
114111
*
115112
* What is already stored wins, being the newer of the two. Cookies with a
116-
* single leading underscore belong to the analytics vendors, not to us.
117-
* Nothing is expired unless the write lands, or a browser that won't take
118-
* the settings would be left with no copy of them at all.
113+
* single leading underscore were set by the analytics vendors the app used
114+
* to load; nothing looks after them any more, so they are expired outright.
115+
* Nothing else is expired unless the write lands, or a browser that won't
116+
* take the settings would be left with no copy of them at all.
119117
*
120118
* Remove once the app has had a release or two to empty the jar out.
121119
*/
@@ -133,19 +131,16 @@ export class SettingsStore {
133131
const names = [];
134132

135133
for (var cookie of document.cookie.split(/;\s?/)) {
136-
if (cookie[0] === "_") {
134+
const [name, value] = cookie.split("=");
135+
if (name[0] === "_") {
136+
if (name[1] !== "_") {
137+
expireCookie(name);
138+
}
137139
continue;
138140
}
139-
const [name, value] = cookie.split("=");
140141
const key = decode(name);
141142

142-
if (key === SettingsStore.ASKED_KEY) {
143-
// It was a session cookie, and is sessionStorage now. Carried over, or
144-
// the prompt would come back on the visit that upgrades.
145-
try {
146-
sessionStorage.setItem(key, "1");
147-
} catch (error) {}
148-
} else if (!(key in settings)) {
143+
if (!SettingsStore.RETIRED_KEYS.includes(key) && !(key in settings)) {
149144
settings[key] = decode(value || "");
150145
}
151146
names.push(name);
@@ -166,12 +161,15 @@ export class SettingsStore {
166161
}
167162

168163
/**
169-
* Expires a cookie, whoever set it.
164+
* Expires a cookie, whoever set it: once as the app sets them, and once for
165+
* the whole domain, which is where Google Analytics put its own.
170166
*
171167
* @param {string} name
172168
*/
173-
export const expireCookie = (name) => {
174-
document.cookie = `${name}=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT`;
169+
const expireCookie = (name) => {
170+
const expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT";
171+
document.cookie = `${name}=;path=/;${expires}`;
172+
document.cookie = `${name}=;path=/;domain=${location.hostname};${expires}`;
175173
};
176174

177175
/**

‎assets/javascripts/templates/notif_tmpl.js‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,3 @@ export const notifUpdateDocs = () =>
130130
" Documentation updates available. ",
131131
' <a href="/offline">Install them</a> as soon as possible to avoid broken pages. ',
132132
);
133-
134-
export const notifAnalyticsConsent = () =>
135-
textNotif(
136-
" Tracking cookies ",
137-
` We would like to gather usage data about how DevDocs is used through Google Analytics and Gauges. We only collect anonymous traffic information.
138-
Please confirm if you accept our tracking cookies. You can always change your decision in the settings.
139-
<br><span class="_notif-right"><a href="#" data-behavior="accept-analytics">Accept</a> or <a href="#" data-behavior="decline-analytics">Decline</a></span> `,
140-
);

‎assets/javascripts/templates/pages/about_tmpl.js‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const aboutPage = function () {
6464
6565
<p><strong>Special thanks to:</strong>
6666
<ul>
67-
<li><a href="https://sentry.io/">Sentry</a> and <a href="https://get.gaug.es/?utm_source=devdocs&utm_medium=referral&utm_campaign=sponsorships" title="Real Time Web Analytics">Gauges</a> for offering a free account to DevDocs
67+
<li><a href="https://sentry.io/">Sentry</a> for offering a free account to DevDocs
6868
<li><a href="https://out.devdocs.io/s/maxcdn">MaxCDN</a>, <a href="https://out.devdocs.io/s/shopify">Shopify</a>, <a href="https://out.devdocs.io/s/jetbrains">JetBrains</a> and <a href="https://out.devdocs.io/s/code-school">Code School</a> for sponsoring DevDocs in the past
6969
<li><a href="https://www.heroku.com">Heroku</a> and <a href="https://newrelic.com/">New Relic</a> for providing awesome free service
7070
<li><a href="https://www.jeremykratz.com/">Jeremy Kratz</a> for the C/C++ logo
@@ -89,7 +89,6 @@ export const aboutPage = function () {
8989
<ul>
9090
<li><a href="https://devdocs.io">devdocs.io</a> ("App") is operated by <a href="https://www.freecodecamp.org/">freeCodeCamp</a> ("We").
9191
<li>We do not collect personal information through the app.
92-
<li>We use Google Analytics and Gauges to collect anonymous traffic information if you have given consent to this. You can change your decision in the <a href="/settings">settings</a>.
9392
<li>We use Sentry to collect crash data and improve the app.
9493
<li>The app uses local storage to store user preferences.
9594
<li>By using the app, you signify your acceptance of this policy. If you do not agree to this policy, please do not use the app.

‎assets/javascripts/templates/pages/settings_tmpl.js‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,6 @@ export const settingsPage = (settings) => `\
7979
}>Automatically switch to the latest version of a documentation
8080
<small>With this checked, enabling e.g. CMake 3.9 switches to CMake 3.10 once it becomes available.</small>
8181
</label>
82-
<label class="_settings-label _hide-in-development">
83-
<input type="checkbox" form="settings" name="analyticsConsent"${
84-
settings.analyticsConsent ? " checked" : ""
85-
}>Enable tracking cookies
86-
<small>With this checked, we enable Google Analytics and Gauges to collect anonymous traffic information.</small>
87-
</label>
8882
<label class="_settings-label _hide-on-mobile">
8983
<input type="checkbox" form="settings" name="noDocSpecificIcon"${
9084
settings.noDocSpecificIcon ? " checked" : ""

‎assets/javascripts/tracking.js‎

Lines changed: 0 additions & 66 deletions
This file was deleted.

0 commit comments

Comments
 (0)