mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-06 05:26:54 +08:00
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:
parent
98f1273398
commit
90b3d7362c
8 changed files with 149 additions and 85 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
42
frontend/src/ts/cookies.ts
Normal file
42
frontend/src/ts/cookies.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue