mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 01:48:12 +00:00
152 lines
4.3 KiB
TypeScript
152 lines
4.3 KiB
TypeScript
/**
|
|
* PostHog wiring — session replay, exception capture, console logs.
|
|
*
|
|
* NOTE: `backoffice/src/lib/posthog.ts` is the twin of this file. The init
|
|
* config below (masking rules) and the PII allowlist in `useIdentify` MUST be
|
|
* kept identical in both — a change made here alone silently leaks customer
|
|
* data into the other app's replays.
|
|
*
|
|
* This is instrumentation, not analytics: autocapture is off and no product
|
|
* events are sent.
|
|
*/
|
|
import posthog from "posthog-js";
|
|
import { useEffect } from "react";
|
|
|
|
import type { AuthUser } from "@/types/auth";
|
|
|
|
const APP = "freight-portal";
|
|
|
|
const TOKEN = import.meta.env.VITE_POSTHOG_KEY;
|
|
const HOST = import.meta.env.VITE_POSTHOG_HOST;
|
|
|
|
/**
|
|
* Whether init actually ran. Without a token every export below is a no-op, so
|
|
* local dev and any environment whose env file lacks the vars keeps working —
|
|
* a missing observability token must never break the app.
|
|
*/
|
|
let enabled = false;
|
|
|
|
export function initPostHog(): void {
|
|
if (enabled || !TOKEN || !HOST) return;
|
|
|
|
posthog.init(TOKEN, {
|
|
api_host: HOST,
|
|
defaults: "2026-05-30",
|
|
|
|
// Debuggability, not analytics.
|
|
autocapture: false,
|
|
capture_pageview: true,
|
|
capture_pageleave: true,
|
|
disable_surveys: true,
|
|
person_profiles: "identified_only",
|
|
|
|
capture_exceptions: {
|
|
capture_unhandled_errors: true,
|
|
capture_unhandled_rejections: true,
|
|
capture_console_errors: true,
|
|
},
|
|
|
|
// Off by default; this is half the point of the integration.
|
|
enable_recording_console_log: true,
|
|
|
|
// Inputs are masked; rendered text stays visible so replays are readable.
|
|
// Wrap sensitive elements in `ph-no-capture` to blank them individually.
|
|
session_recording: { maskAllInputs: true },
|
|
});
|
|
|
|
// Portal and backoffice share one PostHog project — filter by this.
|
|
posthog.register({ app: APP });
|
|
|
|
enabled = true;
|
|
}
|
|
|
|
export function captureException(
|
|
error: unknown,
|
|
properties?: Record<string, unknown>,
|
|
): void {
|
|
if (!enabled) return;
|
|
posthog.captureException(error, properties);
|
|
}
|
|
|
|
/**
|
|
* Report a failed API call.
|
|
*
|
|
* Called from the axios interceptor rather than from `emitApiError`, which
|
|
* early-returns on suppressed paths (warehouse / first-mile / onboarding /
|
|
* auth) — hooking there would drop errors on exactly those pages.
|
|
*
|
|
* 5xx and network failures are real defects and go to Error tracking. 4xx is
|
|
* usually the server correctly rejecting input, so it is recorded as a plain
|
|
* event to keep the issue list signal-heavy.
|
|
*/
|
|
export function captureApiError(error: unknown): void {
|
|
if (!enabled) return;
|
|
|
|
const err = error as {
|
|
message?: string;
|
|
config?: { method?: string; url?: string };
|
|
response?: { status?: number };
|
|
};
|
|
|
|
const status = err.response?.status;
|
|
const properties = {
|
|
api_status: status ?? null,
|
|
api_method: err.config?.method?.toUpperCase() ?? null,
|
|
api_path: err.config?.url ?? null,
|
|
};
|
|
|
|
if (status && status < 500) {
|
|
posthog.capture("api_error", properties);
|
|
return;
|
|
}
|
|
|
|
posthog.captureException(error, properties);
|
|
}
|
|
|
|
/** Company context, as returned by `useAuth().company`. */
|
|
interface IdentifyCompany {
|
|
company?: { id?: string; type?: string | null; status?: string | null } | null;
|
|
profile?: { activeProfileType?: string | null } | null;
|
|
}
|
|
|
|
/**
|
|
* Identify the current user to PostHog so replays and exceptions are
|
|
* attributable.
|
|
*
|
|
* Deliberately sends NO contact details. `AuthUser` carries email, phoneNumber,
|
|
* name and username; these are real customers, and debugging a replay never
|
|
* requires knowing how to phone the person in it.
|
|
*/
|
|
export function useIdentify(
|
|
user: AuthUser | null,
|
|
company?: IdentifyCompany | null,
|
|
): void {
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
|
|
if (!user?.id) {
|
|
posthog.reset();
|
|
return;
|
|
}
|
|
|
|
posthog.identify(user.id, {
|
|
roles: user.roles,
|
|
status: user.status,
|
|
user_type: user.userType,
|
|
company_id: company?.company?.id,
|
|
company_type: company?.company?.type,
|
|
company_status: company?.company?.status,
|
|
active_profile_type: company?.profile?.activeProfileType,
|
|
});
|
|
}, [
|
|
user?.id,
|
|
user?.roles,
|
|
user?.status,
|
|
user?.userType,
|
|
company?.company?.id,
|
|
company?.company?.type,
|
|
company?.company?.status,
|
|
company?.profile?.activeProfileType,
|
|
]);
|
|
}
|