TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1/** Client-side humanizers for audit codes, user agents and dates. No secrets, no side effects. */23const ACTION_LABELS: Record<string, string> = {4 login: "Signed in",5 logout: "Signed out",6 "account.created": "Account created",7 "account.deleted": "Account deleted",8 "account.export": "Account exported",9 "email.verified": "Email verified",10 "email.change_requested": "Email change requested",11 "email.changed": "Email changed",12 "password.reset_requested": "Password reset requested",13 "password.reset": "Password reset",14 "password.changed": "Password changed",15 "session.revoked": "Session revoked",16 "sessions.revoked": "All sessions revoked",17 "provider.key_added": "API key added",18 "provider.key_replaced": "API key replaced",19 "provider.key_removed": "API key removed",20 "provider.key_validated": "API key validated",21 "provider.key_invalid": "API key rejected",22 "onboarding.completed": "Onboarding completed",23};2425export function humanizeAction(code: string): string {26 if (ACTION_LABELS[code]) return ACTION_LABELS[code];27 // "provider.key_added" → "Provider key added"28 const words = code.replace(/[._-]+/g, " ").trim();29 return words.charAt(0).toUpperCase() + words.slice(1);30}3132export function actionTone(code: string): "default" | "success" | "warning" | "danger" | "info" {33 if (/deleted|invalid|revoked|rejected/.test(code)) return "danger";34 if (/reset|change_requested/.test(code)) return "warning";35 if (/added|validated|verified|created|changed/.test(code)) return "success";36 if (/login|export/.test(code)) return "info";37 return "default";38}3940export interface UaSummary {41 browser: string;42 os: string;43 device: "desktop" | "mobile" | "tablet" | "unknown";44 label: string;45}4647/** Best-effort user-agent summary — good enough for a sessions list, never authoritative. */48export function describeUserAgent(ua: string | null | undefined): UaSummary {49 if (!ua) return { browser: "Unknown browser", os: "Unknown OS", device: "unknown", label: "Unknown device" };50 const s = ua;51 let browser = "Browser";52 if (/Edg\//.test(s)) browser = "Edge";53 else if (/OPR\/|Opera/.test(s)) browser = "Opera";54 else if (/Arc\//.test(s)) browser = "Arc";55 else if (/Firefox\//.test(s)) browser = "Firefox";56 else if (/Chrome\//.test(s) && !/Chromium/.test(s)) browser = "Chrome";57 else if (/Chromium/.test(s)) browser = "Chromium";58 else if (/Safari\//.test(s) && /Version\//.test(s)) browser = "Safari";59 else if (/curl|node|python|okhttp|Go-http/i.test(s)) browser = "API client";6061 let os = "Unknown OS";62 if (/iPhone|iPad|iPod/.test(s)) os = "iOS";63 else if (/Android/.test(s)) os = "Android";64 else if (/Mac OS X|Macintosh/.test(s)) os = "macOS";65 else if (/Windows/.test(s)) os = "Windows";66 else if (/CrOS/.test(s)) os = "ChromeOS";67 else if (/Linux/.test(s)) os = "Linux";6869 let device: UaSummary["device"] = "desktop";70 if (/iPad|Tablet/.test(s)) device = "tablet";71 else if (/Mobi|iPhone|Android.*Mobile/.test(s)) device = "mobile";7273 return { browser, os, device, label: `${browser} on ${os}` };74}7576export function formatDateTime(date: string | Date | null | undefined, opts: Intl.DateTimeFormatOptions = {}): string {77 if (!date) return "—";78 const d = typeof date === "string" ? new Date(date) : date;79 if (Number.isNaN(d.getTime())) return "—";80 return d.toLocaleString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", ...opts });81}8283export function formatDate(date: string | Date | null | undefined): string {84 if (!date) return "—";85 const d = typeof date === "string" ? new Date(date) : date;86 if (Number.isNaN(d.getTime())) return "—";87 return d.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });88}8990/** formatRelative that also accepts Date objects returned by the auth client. */91export function formatRelativeSafe(date: string | Date | null | undefined): string {92 if (!date) return "—";93 const d = typeof date === "string" ? new Date(date) : date;94 if (Number.isNaN(d.getTime())) return "—";95 const s = Math.round((Date.now() - d.getTime()) / 1000);96 if (s < 45) return "just now";97 const m = Math.round(s / 60);98 if (m < 60) return `${m} min ago`;99 const h = Math.round(m / 60);100 if (h < 24) return `${h} h ago`;101 const days = Math.round(h / 24);102 if (days < 7) return `${days} d ago`;103 return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });104}105106export function formatPercent(n: number | null | undefined, digits = 1): string {107 if (n === null || n === undefined || Number.isNaN(n)) return "—";108 return `${(n * 100).toFixed(digits)}%`;109}110111/** Turn an API error (ClientApiError, Error, Better Auth {error}) into a short user-facing string. */112export function errorMessage(e: unknown, fallback = "Something went wrong"): string {113 if (!e) return fallback;114 if (typeof e === "string") return e;115 if (typeof e === "object") {116 const o = e as { message?: unknown; error?: { message?: unknown } };117 if (typeof o.message === "string" && o.message) return o.message;118 if (o.error && typeof o.error.message === "string") return o.error.message;119 }120 return fallback;121}122