chore: update privacy policy to mention sentry (@miodec) (#6558)

Also refactors cookie modal and cokie local storage a little bit.

closes #6557
This commit is contained in:
Jack 2025-05-13 16:41:48 +02:00 committed by GitHub
parent 98f1273398
commit 90b3d7362c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 149 additions and 85 deletions

View file

@ -166,8 +166,19 @@
<label class="checkbox">
<div class="title">analytics</div>
<div class="description">
We use Google Analytics cookies to check how users interact with our
website and use this data to improve our site design.
We use Google Analytics to track the overall traffic and
demographics of our site.
</div>
<input type="checkbox" />
</label>
</div>
<div class="cookie sentry">
<label class="checkbox">
<div class="title">sentry</div>
<div class="description">
We use Sentry to track errors and performance issues on our site, as
well as record annonymized user sessions to help us debug issues and
improve our product.
</div>
<input type="checkbox" />
</label>

View file

@ -111,7 +111,7 @@
</p>
<p>Effective date: September 8, 2021</p>
<p>Last updated: May 20, 2024</p>
<p>Last updated: May 12, 2025</p>
<p>
Thanks for trusting Monkeytype ('Monkeytype', 'we', 'us', 'our') with
your personal information! We take our responsibility to you very
@ -274,13 +274,13 @@
</ul>
<!-- <p>If you make a request, we have one month to respond to you. If you would like to exercise any of these rights, please contact us at our email: support@monkeytype.com</p> -->
<h1 id="Log_Data">What log data do we collect?</h1>
<h1 id="Log_Data">Analytics</h1>
<p>
Like most websites, Monkeytype collects information that your browser
sends whenever you visit the website. This data may include internet
protocol (IP) addresses, browser type, Internet Service Provider
(ISP), date and time stamp, referring/exit pages, and time spent on
each page.
Like most websites, we use Google Analytics, which collects
information that your browser sends whenever you visit the website.
This data may include internet protocol (IP) addresses, browser type,
Internet Service Provider (ISP), date and time stamp, referring/exit
pages, and time spent on each page.
<b>
THIS DATA DOES NOT CONTAIN ANY PERSONALLY IDENTIFIABLE INFORMATION.
</b>
@ -288,7 +288,36 @@
tracking users' movement on the website, and gathering demographic
information.
</p>
<p>In our case, this service is provided by Google Analytics.</p>
<p>
For more information on Google Analytics' privacy policy, please
visit:
<a
href="https://support.google.com/analytics/answer/6004245?hl=en"
target="_blank"
rel="noopener noreferrer"
>
https://support.google.com/analytics/answer/6004245?hl=en
</a>
</p>
<h1 id="Sentry">Sentry</h1>
<p>
Sentry is a crash reporting service that helps us track errors and
crashes on the website. It collects information about your device,
browser, and the error that occurred. Sometimes it might also include
an annonymized replay of your session. This information is used to
track down bugs faster and improve our website.
</p>
<p>
For more information on Sentry's privacy policy, please visit:
<a
href="https://sentry.io/privacy/"
target="_blank"
rel="noopener noreferrer"
>
https://sentry.io/privacy/
</a>
</p>
<h1 id="Advertisements">Advertisements</h1>
<p>

View file

@ -6,18 +6,9 @@ import {
} from "firebase/analytics";
import { app as firebaseApp } from "../firebase";
import { createErrorMessage } from "../utils/misc";
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
import { z } from "zod";
let analytics: AnalyticsType;
const AcceptedCookiesSchema = z.object({
security: z.boolean(),
analytics: z.boolean(),
});
type AcceptedCookies = z.infer<typeof AcceptedCookiesSchema>;
export async function log(
eventName: string,
params?: Record<string, string>
@ -29,26 +20,12 @@ export async function log(
}
}
const lsString = localStorage.getItem("acceptedCookies");
let acceptedCookies: AcceptedCookies | null;
if (lsString !== undefined && lsString !== null && lsString !== "") {
try {
acceptedCookies = parseJsonWithSchema(lsString, AcceptedCookiesSchema);
} catch (e) {
console.error("Failed to parse accepted cookies:", e);
acceptedCookies = null;
}
} else {
acceptedCookies = null;
}
if (acceptedCookies !== null) {
if (acceptedCookies.analytics) {
activateAnalytics();
}
}
export function activateAnalytics(): void {
if (analytics !== undefined) {
console.warn("Analytics already activated");
return;
}
console.log("Activating Analytics");
try {
analytics = getAnalytics(firebaseApp);
setAnalyticsCollectionEnabled(analytics, true);

View file

@ -0,0 +1,42 @@
import { z } from "zod";
import { LocalStorageWithSchema } from "./utils/local-storage-with-schema";
import { activateAnalytics } from "./controllers/analytics-controller";
import { activateSentry } from "./sentry";
const AcceptedCookiesSchema = z
.object({
security: z.boolean(),
analytics: z.boolean(),
sentry: z.boolean(),
})
.strict()
.nullable();
export type AcceptedCookies = z.infer<typeof AcceptedCookiesSchema>;
export function getAcceptedCookies(): AcceptedCookies | null {
return cookies.get();
}
export function setAcceptedCookies(accepted: AcceptedCookies): void {
cookies.set(accepted);
activateWhatsAccepted();
}
const cookies = new LocalStorageWithSchema({
key: "acceptedCookies",
schema: AcceptedCookiesSchema,
fallback: null,
// no migration here, if cookies changed, we need to ask the user again
});
export function activateWhatsAccepted(): void {
const accepted = getAcceptedCookies();
if (accepted?.analytics) {
activateAnalytics();
}
if (accepted?.sentry) {
activateSentry();
}
}

View file

@ -29,6 +29,7 @@ import "./controllers/account-controller";
import { enable } from "./states/glarses-mode";
import "./test/caps-warning";
import "./modals/simple-modals";
import * as CookiesModal from "./modals/cookies";
import "./controllers/input-controller";
import "./ready";
import "./controllers/route-controller";
@ -46,6 +47,7 @@ import * as VersionButton from "./elements/version-button";
import * as Focus from "./test/focus";
import { getDevOptionsModal } from "./utils/async-modules";
import * as Sentry from "./sentry";
import * as Cookies from "./cookies";
function addToGlobal(items: Record<string, unknown>): void {
for (const [name, item] of Object.entries(items)) {
@ -57,7 +59,13 @@ function addToGlobal(items: Record<string, unknown>): void {
void loadFromLocalStorage();
void VersionButton.update();
Focus.set(true, true);
Sentry.init();
const accepted = Cookies.getAcceptedCookies();
if (accepted === null) {
CookiesModal.show();
} else {
Cookies.activateWhatsAccepted();
}
addToGlobal({
snapshot: DB.getSnapshot,

View file

@ -1,39 +1,20 @@
import { activateAnalytics } from "../controllers/analytics-controller";
import * as Notifications from "../elements/notifications";
import { isPopupVisible } from "../utils/misc";
import * as AdController from "../controllers/ad-controller";
import AnimatedModal from "../utils/animated-modal";
import { focusWords } from "../test/test-ui";
import { LocalStorageWithSchema } from "../utils/local-storage-with-schema";
import { z } from "zod";
const AcceptedSchema = z.object({
security: z.boolean(),
analytics: z.boolean(),
});
type Accepted = z.infer<typeof AcceptedSchema>;
const acceptedCookiesLS = new LocalStorageWithSchema({
key: "acceptedCookies",
schema: AcceptedSchema,
fallback: undefined,
});
function setAcceptedObject(obj: Accepted): void {
acceptedCookiesLS.set(obj);
}
export function check(): void {
if (acceptedCookiesLS.get() === undefined) {
show();
}
}
import {
AcceptedCookies,
getAcceptedCookies,
setAcceptedCookies,
} from "../cookies";
export function show(goToSettings?: boolean): void {
void modal.show({
beforeAnimation: async () => {
if (goToSettings) {
showSettings();
const currentAcceptedCookies = getAcceptedCookies();
showSettings(currentAcceptedCookies);
}
},
afterAnimation: async () => {
@ -44,9 +25,26 @@ export function show(goToSettings?: boolean): void {
});
}
function showSettings(): void {
function showSettings(currentAcceptedCookies?: AcceptedCookies): void {
modal.getModal().querySelector(".main")?.classList.add("hidden");
modal.getModal().querySelector(".settings")?.classList.remove("hidden");
if (currentAcceptedCookies) {
if (currentAcceptedCookies.analytics) {
(
modal
.getModal()
.querySelector(".cookie.analytics input") as HTMLInputElement
).checked = true;
}
if (currentAcceptedCookies.sentry) {
(
modal
.getModal()
.querySelector(".cookie.sentry input") as HTMLInputElement
).checked = true;
}
}
}
async function hide(): Promise<void> {
@ -57,14 +55,6 @@ async function hide(): Promise<void> {
});
}
// function verifyVisible(): void {
// if (!modal.isOpen()) return;
// if (!isPopupVisible("cookiePopup")) {
// //removed by cookie popup blocking extension
// modal.destroy();
// }
// }
const modal = new AnimatedModal({
dialogId: "cookiesModal",
customEscapeHandler: (): void => {
@ -78,17 +68,18 @@ const modal = new AnimatedModal({
const accepted = {
security: true,
analytics: true,
sentry: true,
};
setAcceptedObject(accepted);
activateAnalytics();
setAcceptedCookies(accepted);
void hide();
});
modalEl.querySelector(".rejectAll")?.addEventListener("click", () => {
const accepted = {
security: true,
analytics: false,
sentry: false,
};
setAcceptedObject(accepted);
setAcceptedCookies(accepted);
void hide();
});
modalEl.querySelector(".openSettings")?.addEventListener("click", () => {
@ -111,16 +102,16 @@ const modal = new AnimatedModal({
const analyticsChecked = (
modalEl.querySelector(".cookie.analytics input") as HTMLInputElement
).checked;
const sentryChecked = (
modalEl.querySelector(".cookie.sentry input") as HTMLInputElement
).checked;
const accepted = {
security: true,
analytics: analyticsChecked,
sentry: sentryChecked,
};
setAcceptedObject(accepted);
setAcceptedCookies(accepted);
void hide();
if (analyticsChecked) {
activateAnalytics();
}
});
},
});

View file

@ -1,7 +1,6 @@
import * as Misc from "./utils/misc";
import * as MonkeyPower from "./elements/monkey-power";
import * as MerchBanner from "./elements/merch-banner";
import * as CookiesModal from "./modals/cookies";
import * as ConnectionState from "./states/connection";
import * as AccountButton from "./elements/account-button";
//@ts-expect-error no types for this package
@ -12,7 +11,6 @@ import { loadPromise } from "./config";
$(async (): Promise<void> => {
await loadPromise;
CookiesModal.check();
//this line goes back to pretty much the beginning of the project and im pretty sure its here
//to make sure the initial theme application doesnt animate the background color

View file

@ -3,7 +3,15 @@ import { envConfig } from "./constants/env-config";
let debug = false;
export function init(): void {
let activated = false;
export function activateSentry(): void {
if (activated) {
console.warn("Sentry already activated");
return;
}
activated = true;
console.log("Activating Sentry");
Sentry.init({
release: envConfig.clientVersion,
dsn: "https://f50c25dc9dd75304a63776063896a39b@o4509236448133120.ingest.us.sentry.io/4509237217394688",