import Taro from '@tarojs/taro'; import { CLIENT_APP, getToken, API_BASE } from './api'; export type ClientErrorLevel = 'fatal' | 'error' | 'warn'; export type ClientErrorCategory = | 'js_error' | 'unhandled_rejection' | 'api_error' | 'network' | 'render' | 'bridge' | 'other'; export type ClientErrorPayload = { level: ClientErrorLevel; category: ClientErrorCategory; message: string; stack?: string; pagePath?: string; extra?: Record; }; function currentPagePath(): string | undefined { try { const pages = Taro.getCurrentPages(); const cur = pages[pages.length - 1] as { route?: string; $taroPath?: string } | undefined; return cur?.$taroPath || cur?.route || undefined; } catch { return undefined; } } /** 上报客户端错误(失败静默,避免递归) */ export function reportClientError(payload: ClientErrorPayload): void { const body = { level: payload.level, category: payload.category, message: String(payload.message || 'unknown').slice(0, 1000), stack: payload.stack ? String(payload.stack).slice(0, 4000) : undefined, pagePath: payload.pagePath || currentPagePath(), clientApp: CLIENT_APP, extra: payload.extra, }; const header: Record = { 'Content-Type': 'application/json', 'X-Client-App': CLIENT_APP, }; const token = getToken(); if (token) header.Authorization = `Bearer ${token}`; void Taro.request({ url: `${API_BASE}/common/client-errors`, method: 'POST', data: body, header, }).catch(() => {}); } let installed = false; /** 安装小程序/H5 全局未捕获错误钩子(幂等) */ export function installClientErrorReporting(): void { if (installed) return; installed = true; try { Taro.onError?.((msg) => { reportClientError({ level: 'fatal', category: 'js_error', message: typeof msg === 'string' ? msg : String(msg), }); }); } catch { /* ignore */ } try { Taro.onUnhandledRejection?.((res) => { const reason = (res as { reason?: unknown })?.reason; const message = reason instanceof Error ? reason.message : typeof reason === 'string' ? reason : JSON.stringify(reason); const stack = reason instanceof Error ? reason.stack : undefined; reportClientError({ level: 'error', category: 'unhandled_rejection', message: message || 'UnhandledRejection', stack, }); }); } catch { /* ignore */ } if (typeof window !== 'undefined') { window.addEventListener('error', (ev) => { reportClientError({ level: 'fatal', category: 'js_error', message: ev.message || 'window.error', stack: ev.error instanceof Error ? ev.error.stack : undefined, extra: { filename: ev.filename, lineno: ev.lineno, colno: ev.colno }, }); }); window.addEventListener('unhandledrejection', (ev) => { const reason = ev.reason; reportClientError({ level: 'error', category: 'unhandled_rejection', message: reason instanceof Error ? reason.message : typeof reason === 'string' ? reason : 'unhandledrejection', stack: reason instanceof Error ? reason.stack : undefined, }); }); } }