| 123456789101112131415161718192021222324252627 |
- import { ErrorHandler, Injectable } from '@angular/core';
- import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
- import { catchError, throwError } from 'rxjs';
- export const DIAGNOSTIC_VERSION = '2026-10-13-local-review-v1';
- interface Diagnostic {time:string;version:string;kind:'http'|'runtime';requestId:string;status:number;}
- const key='xiaoshu.safe-diagnostics.v1';
- export function recordDiagnostic(kind:Diagnostic['kind'],requestId='',status=0):void {
- try {const previous=JSON.parse(sessionStorage.getItem(key)||'[]');const rows=Array.isArray(previous)?previous.slice(-49):[];
- rows.push({time:new Date().toISOString(),version:DIAGNOSTIC_VERSION,kind,requestId:/^[a-z0-9-]{1,64}$/i.test(requestId)?requestId:'',status:Number(status)||0});sessionStorage.setItem(key,JSON.stringify(rows));
- }catch{/* Diagnostics must not break the original interaction. */}
- }
- export function downloadDiagnostics():void {
- const blob=new Blob([sessionStorage.getItem(key)||'[]'],{type:'application/json'}),url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download='xiaoshu-diagnostics.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
- }
- @Injectable()
- export class SafeErrorHandler implements ErrorHandler {
- handleError(_error:unknown):void {recordDiagnostic('runtime');if(document.getElementById('runtime-notice'))return;
- const box=document.createElement('aside');box.id='runtime-notice';box.setAttribute('role','alert');box.style.cssText='position:fixed;bottom:16px;left:16px;right:16px;z-index:99999;background:#fff4dc;color:#573b00;padding:16px;border:1px solid #d9b45b;border-radius:12px';
- const text=document.createElement('span');text.textContent='页面遇到问题,请刷新后重试。';const button=document.createElement('button');button.textContent='下载诊断';button.onclick=downloadDiagnostics;const close=document.createElement('button');close.textContent='关闭提示';close.onclick=()=>box.remove();box.append(text,button,close);document.body.append(box);
- }
- }
- export const diagnosticInterceptor:HttpInterceptorFn=(request,next)=>{
- const id=crypto.randomUUID(),body=request.body;
- // Carry only the correlation ID to our gateways; do not add CORS headers or log request bodies.
- const correlated=body&&typeof body==='object'&&'params' in body&&body.params&&typeof body.params==='object'?request.clone({body:{...body,params:{...body.params,requestId:id}}}):request;
- return next(correlated).pipe(catchError(error=>{recordDiagnostic('http',id,error instanceof HttpErrorResponse?error.status:0);return throwError(()=>error);}));
- };
|