diff --git a/apps/web/public/an.js b/apps/web/public/an.js
deleted file mode 100644
index 8a6b8c5e9..000000000
--- a/apps/web/public/an.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(){"use strict";var t=function(t,e,n){var a=t[e];return function(){for(var e=[],i=arguments.length;i--;)e[i]=arguments[i];return n.apply(null,e),a.apply(t,e)}},e=function(){var t=window.doNotTrack,e=window.navigator,n=window.external,a=t||e.doNotTrack||e.msDoNotTrack||n&&"function"==typeof n.msTrackingProtectionEnabled&&n.msTrackingProtectionEnabled();return!0===a||1===a||"yes"===a||"1"===a};!function(n){var a=n.screen,i=a.width,r=a.height,o=n.navigator.language,c=n.location,s=c.hostname,u=c.pathname,l=c.search,d=n.localStorage,f=n.sessionStorage,v=n.document,p=n.history,m=v.querySelector("script[data-website-id]");if(m){var h,g=function(t){return m&&m.getAttribute(t)},w=g("data-website-id"),y=g("data-host-url"),S="false"!==g("data-auto-track"),E=g("data-do-not-track"),k=g("data-cache"),b=g("data-domains"),T=d.getItem("umami.disabled")||E&&e()||b&&!b.split(",").map((function(t){return t.trim()})).includes(s),N=y?(h=y)&&h.length>1&&h.endsWith("/")?h.slice(0,-1):h:m.src.split("/").slice(0,-1).join("/"),j=i+"x"+r,q=[],I=""+u+l,L=v.referrer,O=function(t,e,n){if(!T){var a="umami.cache",i={website:n,hostname:s,screen:j,language:o,cache:k&&f.getItem(a)};e&&Object.keys(e).forEach((function(t){i[t]=e[t]})),function(t,e,n){var a=new XMLHttpRequest;a.open("POST",t,!0),a.setRequestHeader("Content-Type","application/json"),a.onreadystatechange=function(){4===a.readyState&&n&&n(a.response)},a.send(JSON.stringify(e))}(N+"/api/collect",{type:t,payload:i},(function(t){return k&&f.setItem(a,t)}))}},P=function(t,e,n){return void 0===t&&(t=I),void 0===e&&(e=L),void 0===n&&(n=w),O("pageview",{url:t,referrer:e},n)},x=function(t,e,n,a){return void 0===e&&(e="custom"),void 0===n&&(n=I),void 0===a&&(a=w),O("event",{event_type:e,event_value:t,url:n},a)},A=function(){v.querySelectorAll("[class*='umami--']").forEach((function(t){t.className.split(" ").forEach((function(e){if(/^umami--([a-z]+)--([\w]+[\w-]*)$/.test(e)){var n=e.split("--"),a=n[1],i=n[2],r=function(){return x(i,a)};q.push([t,a,r]),t.addEventListener(a,r,!0)}}))}))},H=function(){q.forEach((function(t){var e=t[0],n=t[1],a=t[2];e&&e.removeEventListener(n,a,!0)})),q.length=0},R=function(t,e,n){if(n){H(),L=I;var a=n.toString();(I="http"===a.substring(0,4)?"/"+a.split("/").splice(3).join("/"):a)!==L&&P(I,L),setTimeout(A,300)}};if(!n.umami){var _=function(t){return x(t)};_.trackView=P,_.trackEvent=x,_.addEvents=A,_.removeEvents=H,n.umami=_}S&&!T&&(p.pushState=t(p,"pushState",R),p.replaceState=t(p,"replaceState",R),P(I,L),A())}}(window)}();
\ No newline at end of file
diff --git a/apps/web/src/components/announcements/body.js b/apps/web/src/components/announcements/body.js
index f313b15bd..dc2fb2a8b 100644
--- a/apps/web/src/components/announcements/body.js
+++ b/apps/web/src/components/announcements/body.js
@@ -298,7 +298,7 @@ function CalltoAction({ action, variant, sx, removeAnnouncement }) {
onClick={async () => {
if (removeAnnouncement) removeAnnouncement();
closeOpenedDialog();
- trackEvent(ANALYTICS_EVENTS.announcementCta, action.data);
+ trackEvent(ANALYTICS_EVENTS.announcementCta, action);
switch (action.type) {
case "link": {
const url = new URL(action.data);
diff --git a/apps/web/src/components/announcements/index.js b/apps/web/src/components/announcements/index.js
index 150aef2e3..d6a6a8a2c 100644
--- a/apps/web/src/components/announcements/index.js
+++ b/apps/web/src/components/announcements/index.js
@@ -51,10 +51,7 @@ function Announcements({ announcements, removeAnnouncement }) {
}}
title="Dismiss announcement"
onClick={() => {
- trackEvent(
- ANALYTICS_EVENTS.announcementDismissed,
- announcement.title
- );
+ trackEvent(ANALYTICS_EVENTS.announcementDismissed, announcement);
removeAnnouncement && removeAnnouncement(announcement.id);
}}
>
diff --git a/apps/web/src/components/dialogs/onboarding-dialog.js b/apps/web/src/components/dialogs/onboarding-dialog.js
index fb5498971..1742e056b 100644
--- a/apps/web/src/components/dialogs/onboarding-dialog.js
+++ b/apps/web/src/components/dialogs/onboarding-dialog.js
@@ -41,6 +41,7 @@ import { useCallback, useState } from "react";
import Config from "../../utils/config";
import { getAllAccents } from "@notesnook/theme";
import { isMacStoreApp } from "../../utils/platform";
+import { doNotTrack } from "../../utils/telemetry";
const newUserSteps = [
{
@@ -48,7 +49,7 @@ const newUserSteps = [
subtitle: "Write with freedom. Never compromise on privacy again.",
buttonText: "Get started",
image: ,
- component: TrackingConsent
+ component: doNotTrack() ? null : TrackingConsent
},
{
title: "Choose your style",
diff --git a/apps/web/src/components/toggle/index.js b/apps/web/src/components/toggle/index.js
index 2f241b88b..751a7d647 100644
--- a/apps/web/src/components/toggle/index.js
+++ b/apps/web/src/components/toggle/index.js
@@ -33,7 +33,8 @@ function Toggle(props) {
onToggled,
onlyIf,
premium,
- testId
+ testId,
+ disabled
} = props;
const onClick = useCallback(async () => {
if (isUserPremium() || !premium || isToggled) onToggled();
@@ -45,10 +46,11 @@ function Toggle(props) {
if (onlyIf === false) return null;
return (
-
+
);
}
diff --git a/apps/web/src/hooks/use-database.js b/apps/web/src/hooks/use-database.js
index a0616d6a4..e555c9327 100644
--- a/apps/web/src/hooks/use-database.js
+++ b/apps/web/src/hooks/use-database.js
@@ -19,10 +19,9 @@ along with this program. If not, see .
import { useEffect, useState } from "react";
import { initializeDatabase } from "../common/db";
-import { loadTrackerScript } from "../utils/analytics";
+import "../utils/analytics";
if (process.env.NODE_ENV === "production") {
- loadTrackerScript();
console.log = () => {};
}
diff --git a/apps/web/src/hooks/use-telemetry.ts b/apps/web/src/hooks/use-telemetry.ts
new file mode 100644
index 000000000..9a2ef2fd1
--- /dev/null
+++ b/apps/web/src/hooks/use-telemetry.ts
@@ -0,0 +1,35 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { useEffect, useState } from "react";
+import {
+ doNotTrack,
+ isTelemetryEnabled,
+ setTelemetry
+} from "../utils/telemetry";
+
+export function useTelemetry() {
+ const [enabled, setEnabled] = useState(isTelemetryEnabled());
+
+ useEffect(() => {
+ setTelemetry(enabled);
+ }, [enabled]);
+
+ return [enabled, setEnabled, doNotTrack()] as const;
+}
diff --git a/apps/web/src/utils/analytics.ts b/apps/web/src/utils/analytics.ts
index 0ebc102c7..3d4fdbdc2 100644
--- a/apps/web/src/utils/analytics.ts
+++ b/apps/web/src/utils/analytics.ts
@@ -17,42 +17,21 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-import Config from "./config";
import { getPlatform } from "./platform";
+import { isTelemetryEnabled } from "./telemetry";
import { appVersion } from "./version";
-declare global {
- interface Window {
- umami?: {
- trackEvent: (
- value: string,
- type: string,
- url?: string,
- websiteId?: string
- ) => void;
- trackView: (url: string, referrer?: string, websiteId?: string) => void;
- };
- }
-}
+type PageView = {
+ type: "pageview";
+ referrer: string;
+ url?: string;
+};
-export function loadTrackerScript() {
- if (Config.get("telemetry") === "false") return;
- const script = document.createElement("script");
- script.src = "/an.js";
- script.async = true;
- script.dataset.websiteId = "f16c07d9-c77b-4781-bfbd-f58e95640002";
-
- if (process.env.REACT_APP_PLATFORM !== "desktop")
- script.dataset.domains = "app.notesnook.com";
- script.dataset.autoTrack = "false";
- script.dataset.doNotTrack = "true";
- script.dataset.hostUrl = "https://analytics.streetwriters.co";
- const firstScriptElement = document.getElementsByTagName("script")[0];
- script.onload = function () {
- trackVisit();
- };
- firstScriptElement.parentNode?.insertBefore(script, firstScriptElement);
-}
+type Event = {
+ type: "event";
+ event_name: string;
+ event_data: Record;
+};
type TrackerEvent = {
name: string;
@@ -104,22 +83,86 @@ export const ANALYTICS_EVENTS = {
}
} as const;
-export function trackEvent(event: TrackerEvent, eventMessage?: string) {
- if (Config.get("telemetry") === "false") return;
- if (!window.umami) return;
+const baseUrl = `https://analytics.streetwriters.co/api/collect`;
+
+async function trackUmamiView(url: string, referrer?: string) {
+ return collect({
+ type: "pageview",
+ referrer: referrer || window.document.referrer,
+ url
+ });
+}
+
+function trackUmamiEvent(name: string, data: Record) {
+ return collect({
+ type: "event",
+ event_name: name,
+ event_data: data
+ });
+}
+
+async function collect(event: PageView | Event) {
+ const {
+ screen: { width, height },
+ navigator: { language },
+ location: { hostname, pathname, search }
+ } = window;
+
+ const screen = `${width}x${height}`;
+ const currentUrl =
+ (event.type === "pageview" && event.url) || `${pathname}${search}`;
+
+ const body = {
+ payload: {
+ website: `f16c07d9-c77b-4781-bfbd-f58e95640002`,
+ hostname,
+ screen,
+ language,
+ url: currentUrl,
+ ...event
+ },
+ type: event.type
+ };
+
+ try {
+ await fetch(baseUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(body),
+ keepalive: true
+ });
+ } catch (e) {
+ console.error(e);
+ }
+}
+
+export function trackEvent(
+ event: TrackerEvent,
+ data?: Record
+) {
+ if (isTelemetryEnabled()) return;
if (event.type === "view") trackVisit(event.name);
- else if (eventMessage) window.umami.trackEvent(eventMessage, event.name);
+ else if (data) trackUmamiEvent(event.name, data);
}
export function trackVisit(url = "/") {
- if (Config.get("telemetry") === "false") return;
- const platform = getPlatform();
- if (!window.umami || !platform) return;
+ if (isTelemetryEnabled()) return;
- window.umami.trackView(url, window.document.referrer);
+ const platform = getPlatform();
+ if (!platform) return;
+
+ trackUmamiView(window.document.referrer);
if (url === "/")
- trackEvent(
- ANALYTICS_EVENTS.version,
- `${appVersion.formatted}-${platform.toLowerCase()}`
- );
+ trackEvent(ANALYTICS_EVENTS.version, {
+ version: appVersion.formatted,
+ platform
+ });
+}
+
+if (isTelemetryEnabled()) {
+ document.addEventListener("readystatechange", () => {
+ trackVisit();
+ });
}
diff --git a/apps/web/src/utils/telemetry.ts b/apps/web/src/utils/telemetry.ts
new file mode 100644
index 000000000..b2b766d79
--- /dev/null
+++ b/apps/web/src/utils/telemetry.ts
@@ -0,0 +1,50 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import Config from "./config";
+
+export function isTelemetryEnabled() {
+ return !doNotTrack() && Config.get("telemetry", true);
+}
+
+export function setTelemetry(state: boolean) {
+ Config.set("telemetry", state);
+}
+
+export function doNotTrack() {
+ const { navigator, external } = window;
+
+ const msTrackProtection = "msTrackingProtectionEnabled";
+ const msTracking = () => {
+ return (
+ external &&
+ msTrackProtection in external &&
+ typeof external[msTrackProtection] === "function" &&
+ external[msTrackProtection]()
+ );
+ };
+
+ const dnt =
+ ("doNotTrack" in window && window.doNotTrack) ||
+ navigator.doNotTrack ||
+ ("msDoNotTrack" in navigator && navigator.doNotTrack) ||
+ msTracking();
+
+ return dnt == "1" || dnt === "yes";
+}
diff --git a/apps/web/src/views/email-confirmed.js b/apps/web/src/views/email-confirmed.js
index 289059ead..d5880c9a9 100644
--- a/apps/web/src/views/email-confirmed.js
+++ b/apps/web/src/views/email-confirmed.js
@@ -156,7 +156,7 @@ function BlogPromoBanner() {
title={account.title}
onClick={() => {
window.open(account.link, "_blank");
- trackEvent(ANALYTICS_EVENTS.socialLink, account.title);
+ trackEvent(ANALYTICS_EVENTS.socialLink, { link: account.link });
}}
size={30}
sx={{ mr: 1, cursor: "pointer" }}
diff --git a/apps/web/src/views/settings.js b/apps/web/src/views/settings.js
index 8ec586a01..ad5491bd9 100644
--- a/apps/web/src/views/settings.js
+++ b/apps/web/src/views/settings.js
@@ -74,6 +74,7 @@ import { clearLogs, downloadLogs } from "../utils/logger";
import { exportNotes } from "../common/export";
import { scheduleBackups } from "../common/reminders";
import usePrivacyMode from "../hooks/use-privacy-mode";
+import { useTelemetry } from "../hooks/use-telemetry";
function subscriptionStatusToString(user) {
const status = user?.subscription?.type;
@@ -188,10 +189,8 @@ function Settings() {
"backupStorageLocation",
PATHS.backupsDirectory
);
- const [enableTelemetry, setEnableTelemetry] = usePersistentState(
- "telemetry",
- true
- );
+ const [enableTelemetry, setEnableTelemetry, isTelemetryDisabled] =
+ useTelemetry();
const [privacyMode, setPrivacyMode] = usePrivacyMode();
const [showReminderNotifications, setShowReminderNotifications] =
usePersistentState("reminderNotifications", true);
@@ -824,11 +823,16 @@ function Settings() {
{
setEnableTelemetry(!enableTelemetry);
}}
isToggled={enableTelemetry}
+ disabled={isTelemetryDisabled}
/>