mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
fix(monitoring): route Umami events to the endpoint the tracker was built with
The tracker posts to `${data-host-url}${collect path}`, and the collect
path is compiled into the operator's Umami build: /api/send by default,
anything else when the instance sets COLLECT_API_ENDPOINT. The tag
pointed data-host-url at the app origin and the proxy listened only on
/api/send, so a renamed endpoint sent every page view and event to an
app path that does not exist and got a full 404 page back each time.
Point data-host-url at /api/monitoring/umami and answer every path under
it with one proxy route. The route reads the collect path from the
configured tracker (cached for an hour), forwards only that path to the
Umami host, and answers anything else, or an unreadable tracker, with an
empty response. The admin test event posts to the same learnt path. The
send-URL resolver lives in one module instead of two copies.
This commit is contained in:
1 parent
2e150e97cf
commit
1b1da652d3
12 files changed
+494
-275
No files matched your search
@@ -4,6 +4,10 @@ import { apiHandler, requireAdmin } from "@/lib/api-handler";
|
||||
import { annotate, getEvent } from "@/lib/logging/context";
|
||||
import { apiError, apiSuccess } from "@/lib/api-response";
|
||||
import { getPublicMonitoringSettings } from "@/lib/monitoring-settings";
|
||||
import {
|
||||
resolveUmamiCollectPath,
|
||||
resolveUmamiSendUrls,
|
||||
} from "@/lib/monitoring/umami";
|
||||
import { safeFetch } from "@/lib/safe-fetch";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -12,30 +16,6 @@ function redact(text: string): string {
|
||||
return text.replace(/https?:\/\/\S+/gi, "[url]");
|
||||
}
|
||||
|
||||
function resolveUmamiSendUrls(scriptUrl: string | null): string[] {
|
||||
if (!scriptUrl) return [];
|
||||
try {
|
||||
const parsed = new URL(scriptUrl);
|
||||
const origin = parsed.origin;
|
||||
const pathSegments = parsed.pathname.split("/").filter(Boolean);
|
||||
const segments = [...pathSegments];
|
||||
|
||||
if (segments.length > 0 && segments[segments.length - 1]?.includes(".")) {
|
||||
segments.pop();
|
||||
}
|
||||
|
||||
const prefix = segments.length > 0 ? `/${segments.join("/")}` : "";
|
||||
const candidates = [
|
||||
`${origin}${prefix}/api/send`,
|
||||
`${origin}/api/send`,
|
||||
`${origin}/umami/api/send`,
|
||||
];
|
||||
return Array.from(new Set(candidates));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function resolveAppUrl(request: NextRequest): URL {
|
||||
const configured =
|
||||
envValue("APP_URL") ??
|
||||
@@ -63,7 +43,13 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
return apiError("Umami script URL and website ID must be configured", 422);
|
||||
}
|
||||
|
||||
const targetUrls = resolveUmamiSendUrls(settings.umamiScriptUrl);
|
||||
// The collect path is compiled into the operator's tracker build
|
||||
// (`COLLECT_API_ENDPOINT`), so the test posts where the tracker would.
|
||||
const collectPath = await resolveUmamiCollectPath(settings.umamiScriptUrl);
|
||||
if (collectPath === null) {
|
||||
return apiError("Umami script could not be fetched", 502);
|
||||
}
|
||||
const targetUrls = resolveUmamiSendUrls(settings.umamiScriptUrl, collectPath);
|
||||
if (targetUrls.length === 0) {
|
||||
return apiError("Umami script URL is invalid", 422);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user