mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
web: use new umami events api for tracking
This commit is contained in:
committed by
Abdullah Atta
parent
3e71ad0b0b
commit
2da32e27ff
@@ -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)}();
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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: <Note width={120} />,
|
||||
component: TrackingConsent
|
||||
component: doNotTrack() ? null : TrackingConsent
|
||||
},
|
||||
{
|
||||
title: "Choose your style",
|
||||
|
||||
@@ -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 (
|
||||
<Flex
|
||||
onClick={onClick}
|
||||
onClick={disabled ? null : onClick}
|
||||
data-test-id={testId}
|
||||
py={2}
|
||||
sx={{
|
||||
opacity: disabled ? 0.7 : 1,
|
||||
cursor: "pointer",
|
||||
borderBottom: "1px solid",
|
||||
borderBottomColor: "border",
|
||||
@@ -58,7 +60,7 @@ function Toggle(props) {
|
||||
}}
|
||||
>
|
||||
<Tip text={title} tip={isToggled ? onTip : offTip} sx={{ mr: 2 }} />
|
||||
<Switch onClick={onClick} checked={isToggled} />
|
||||
<Switch onClick={disabled ? null : onClick} checked={isToggled} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,10 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 = () => {};
|
||||
}
|
||||
|
||||
|
||||
35
apps/web/src/hooks/use-telemetry.ts
Normal file
35
apps/web/src/hooks/use-telemetry.ts
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -17,42 +17,21 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<string, unknown>;
|
||||
};
|
||||
|
||||
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<string, unknown>) {
|
||||
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<string, unknown>
|
||||
) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
50
apps/web/src/utils/telemetry.ts
Normal file
50
apps/web/src/utils/telemetry.ts
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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";
|
||||
}
|
||||
@@ -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" }}
|
||||
|
||||
@@ -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() {
|
||||
<Toggle
|
||||
title="Enable telemetry"
|
||||
onTip="Usage data & crash reports will be sent to us (no 3rd party involved) for analytics. All data is anonymous as mentioned in our privacy policy."
|
||||
offTip="Do not collect any data or crash reports"
|
||||
offTip={
|
||||
isTelemetryDisabled
|
||||
? "All telemetry is disabled because of Do Not Track being enabled in your browser."
|
||||
: "Do not collect any data or crash reports"
|
||||
}
|
||||
onToggled={() => {
|
||||
setEnableTelemetry(!enableTelemetry);
|
||||
}}
|
||||
isToggled={enableTelemetry}
|
||||
disabled={isTelemetryDisabled}
|
||||
/>
|
||||
<Button variant="list" onClick={showTrackingDetailsDialog}>
|
||||
<Tip
|
||||
|
||||
Reference in New Issue
Block a user