chore: add telemetry
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useWizard, Wizard } from "react-use-wizard";
|
import { useWizard, Wizard } from "react-use-wizard";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@@ -14,6 +14,7 @@ import SelectLanguage from "../../components/Language";
|
|||||||
import { useGetAutoTunnelInfoQuery, useGetServerVersionQuery } from "@/app/services/server";
|
import { useGetAutoTunnelInfoQuery, useGetServerVersionQuery } from "@/app/services/server";
|
||||||
import { useAppSelector } from "@/app/store";
|
import { useAppSelector } from "@/app/store";
|
||||||
import { compareVersion } from "@/utils";
|
import { compareVersion } from "@/utils";
|
||||||
|
import { trackUmamiEvent } from "@/utils/umami";
|
||||||
import { shallowEqual } from "react-redux";
|
import { shallowEqual } from "react-redux";
|
||||||
|
|
||||||
const TUNNEL_MIN_VERSION = "0.5.19";
|
const TUNNEL_MIN_VERSION = "0.5.19";
|
||||||
@@ -58,6 +59,12 @@ const Navigator = ({ showTunnelStep }: { showTunnelStep: boolean }) => {
|
|||||||
export default function OnboardingPage() {
|
export default function OnboardingPage() {
|
||||||
const { t } = useTranslation("welcome");
|
const { t } = useTranslation("welcome");
|
||||||
const [serverName, setServerName] = useState("");
|
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 { isLoading: versionLoading } = useGetServerVersionQuery();
|
||||||
const currentVersion = useAppSelector((store) => store.server.version, shallowEqual);
|
const currentVersion = useAppSelector((store) => store.server.version, shallowEqual);
|
||||||
const versionOk = !!currentVersion && compareVersion(currentVersion, TUNNEL_MIN_VERSION) >= 0;
|
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 { getLocalAuthData, compareVersion } from "@/utils";
|
||||||
import { useAppSelector } from "@/app/store";
|
import { useAppSelector } from "@/app/store";
|
||||||
import { useGetAutoTunnelInfoQuery } from "@/app/services/server";
|
import { useGetAutoTunnelInfoQuery } from "@/app/services/server";
|
||||||
|
import { trackUmamiEvent } from "@/utils/umami";
|
||||||
|
|
||||||
const REQUIRED_VERSION = "0.5.19";
|
const REQUIRED_VERSION = "0.5.19";
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ export default function GetPublicDomain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleYes = async () => {
|
const handleYes = async () => {
|
||||||
|
trackUmamiEvent("create_tunnel");
|
||||||
setPhase("starting");
|
setPhase("starting");
|
||||||
setLogs([]);
|
setLogs([]);
|
||||||
setErrorMsg(null);
|
setErrorMsg(null);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ServerVersionChecker from "@/components/ServerVersionChecker";
|
|||||||
import Button from "@/components/styled/Button";
|
import Button from "@/components/styled/Button";
|
||||||
import { getLocalAuthData, compareVersion } from "@/utils";
|
import { getLocalAuthData, compareVersion } from "@/utils";
|
||||||
import { useAppSelector } from "@/app/store";
|
import { useAppSelector } from "@/app/store";
|
||||||
|
import { trackUmamiEvent } from "@/utils/umami";
|
||||||
|
|
||||||
const TUNNEL_MIN_VERSION = "0.5.19";
|
const TUNNEL_MIN_VERSION = "0.5.19";
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ export default function Cloudflared() {
|
|||||||
|
|
||||||
const startTunnel = async () => {
|
const startTunnel = async () => {
|
||||||
if (streaming) return;
|
if (streaming) return;
|
||||||
|
trackUmamiEvent("create_tunnel");
|
||||||
setStreaming(true);
|
setStreaming(true);
|
||||||
setStatus("starting");
|
setStatus("starting");
|
||||||
setLogs([]);
|
setLogs([]);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user