chore: add telemetry

This commit is contained in:
haorwen
2026-07-01 23:09:32 +08:00
parent 09b3e895b4
commit 67ac7b8d79
4 changed files with 80 additions and 1 deletions
+8 -1
View File
@@ -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;
@@ -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);
+2
View File
@@ -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([]);
+68
View File
@@ -0,0 +1,68 @@
/**
* Umami analytics — lazy loader.
*
* The script is injected into <head> 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<string, unknown>) => void } };
let loaded = false;
let loading = false;
const queue: Array<() => void> = [];
function loadScript(): Promise<void> {
return new Promise((resolve) => {
if (loaded) {
resolve();
return;
}
queue.push(resolve);
if (loading) return;
loading = true;
const script = document.createElement("script");
script.async = true;
script.defer = true;
script.src = UMAMI_SCRIPT_URL;
script.setAttribute("data-website-id", UMAMI_WEBSITE_ID);
script.setAttribute("data-auto-track", "false"); // disable automatic pageview tracking
script.onload = () => {
loaded = true;
loading = false;
queue.splice(0).forEach((cb) => cb());
};
script.onerror = () => {
// Don't block callers on network failure; just drain the queue
loading = false;
queue.splice(0).forEach((cb) => cb());
};
document.head.appendChild(script);
});
}
/**
* Load the Umami script (if not already loaded) then fire a named event.
* The script is fetched on first call and reused for all subsequent calls.
* Safe to call from anywhere — silently no-ops if the script fails to load.
*
* @param eventName The event name shown in the Umami dashboard.
* @param data Optional key/value payload.
*/
export async function trackUmamiEvent(
eventName: string,
data?: Record<string, unknown>
): Promise<void> {
await loadScript();
const umami = (window as UmamiWindow).umami;
if (umami?.track) {
umami.track(eventName, data);
}
}