diff --git a/src/routes/onboarding/index.tsx b/src/routes/onboarding/index.tsx index 8d35309f..3930a14a 100644 --- a/src/routes/onboarding/index.tsx +++ b/src/routes/onboarding/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useWizard, Wizard } from "react-use-wizard"; import clsx from "clsx"; @@ -14,6 +14,7 @@ import SelectLanguage from "../../components/Language"; import { useGetAutoTunnelInfoQuery, useGetServerVersionQuery } from "@/app/services/server"; import { useAppSelector } from "@/app/store"; import { compareVersion } from "@/utils"; +import { trackUmamiEvent } from "@/utils/umami"; import { shallowEqual } from "react-redux"; const TUNNEL_MIN_VERSION = "0.5.19"; @@ -58,6 +59,12 @@ const Navigator = ({ showTunnelStep }: { showTunnelStep: boolean }) => { export default function OnboardingPage() { const { t } = useTranslation("welcome"); const [serverName, setServerName] = useState(""); + + // Load Umami and fire "installed" once when the onboarding page mounts. + // forceLoad=true so the script is fetched here and only here. + useEffect(() => { + trackUmamiEvent("installed"); + }, []); const { isLoading: versionLoading } = useGetServerVersionQuery(); const currentVersion = useAppSelector((store) => store.server.version, shallowEqual); const versionOk = !!currentVersion && compareVersion(currentVersion, TUNNEL_MIN_VERSION) >= 0; diff --git a/src/routes/onboarding/steps/get-public-domain.tsx b/src/routes/onboarding/steps/get-public-domain.tsx index e06a626d..9e7a9011 100644 --- a/src/routes/onboarding/steps/get-public-domain.tsx +++ b/src/routes/onboarding/steps/get-public-domain.tsx @@ -8,6 +8,7 @@ import { BASE_ORIGIN, tokenHeader } from "@/app/config"; import { getLocalAuthData, compareVersion } from "@/utils"; import { useAppSelector } from "@/app/store"; import { useGetAutoTunnelInfoQuery } from "@/app/services/server"; +import { trackUmamiEvent } from "@/utils/umami"; const REQUIRED_VERSION = "0.5.19"; @@ -66,6 +67,7 @@ export default function GetPublicDomain() { } const handleYes = async () => { + trackUmamiEvent("create_tunnel"); setPhase("starting"); setLogs([]); setErrorMsg(null); diff --git a/src/routes/setting/Cloudflared/index.tsx b/src/routes/setting/Cloudflared/index.tsx index d45832e9..6e96fa41 100644 --- a/src/routes/setting/Cloudflared/index.tsx +++ b/src/routes/setting/Cloudflared/index.tsx @@ -9,6 +9,7 @@ import ServerVersionChecker from "@/components/ServerVersionChecker"; import Button from "@/components/styled/Button"; import { getLocalAuthData, compareVersion } from "@/utils"; import { useAppSelector } from "@/app/store"; +import { trackUmamiEvent } from "@/utils/umami"; const TUNNEL_MIN_VERSION = "0.5.19"; @@ -80,6 +81,7 @@ export default function Cloudflared() { const startTunnel = async () => { if (streaming) return; + trackUmamiEvent("create_tunnel"); setStreaming(true); setStatus("starting"); setLogs([]); diff --git a/src/utils/umami.ts b/src/utils/umami.ts new file mode 100644 index 00000000..08122cc1 --- /dev/null +++ b/src/utils/umami.ts @@ -0,0 +1,68 @@ +/** + * Umami analytics — lazy loader. + * + * The script is injected into
at most once per page lifetime, + * loaded on demand the first time trackUmamiEvent() is called. + * Automatic pageview tracking is disabled; all events are explicit. + */ + +const UMAMI_SCRIPT_URL = "https://install.voce.chat/script.js"; +const UMAMI_WEBSITE_ID = "121f1469-bdce-4326-8fa3-7b820837c916"; + +type UmamiWindow = Window & { umami?: { track: (event: string, data?: Record