| 1 | export interface PageDiagnostic { sequence: number; time: number; kind: "console" | "exception" | "network" | "navigation"; message: string; url?: string; status?: number } |
| 2 | |
| 3 | export function diagnosticURL(value: string): string { |
| 4 | try { const url = new URL(value); if (url.protocol !== "http:" && url.protocol !== "https:") return ""; url.username = ""; url.password = ""; url.search = ""; url.hash = ""; return url.href.slice(0, 1024); } |
| 5 | catch { return ""; } |
| 6 | } |
| 7 | export function diagnosticText(value: string): string { |
| 8 | return value.slice(0, 4096) |
| 9 | .replace(/https?:\/\/[^\s"'<>]+/gi, diagnosticURL) |
| 10 | .replace(/((?:authorization|set-cookie|cookie)["']?\s*[:=]\s*)[^\r\n]*/gi, "$1[redacted]") |
| 11 | .replace(/(password|secret|token|api[-_]?key)["']?\s*[:=]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;]+)/gi, "$1=[redacted]") |
| 12 | .replace(/Bearer\s+[^\s,;]+/gi, "Bearer [redacted]"); |
| 13 | } |
| 14 | |
| 15 | export class DiagnosticBuffer { |
| 16 | private entries: PageDiagnostic[] = []; |
| 17 | private sequence = 0; |
| 18 | private bytes = 0; |
| 19 | private readonly listeners = new Set<(entry: PageDiagnostic) => void>(); |
| 20 | subscribe(listener: (entry: PageDiagnostic) => void): () => void { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; } |
| 21 | add(entry: Omit<PageDiagnostic, "sequence" | "time">): void { |
| 22 | const item = { ...entry, sequence: ++this.sequence, time: Date.now(), message: diagnosticText(entry.message), url: entry.url ? diagnosticURL(entry.url) : undefined }; |
| 23 | this.entries.push(item); |
| 24 | this.bytes += Buffer.byteLength(JSON.stringify(item)); |
| 25 | while (this.entries.length > 200 || this.bytes > 256 * 1024) this.bytes -= Buffer.byteLength(JSON.stringify(this.entries.shift())); |
| 26 | for (const listener of this.listeners) listener(item); |
| 27 | } |
| 28 | read(after = 0, kind = "") { return { available: true, cursor: this.sequence, truncated: after < (this.entries[0]?.sequence ?? 1) - 1, entries: this.entries.filter(entry => entry.sequence > after && (!kind || entry.kind === kind)) }; } |
| 29 | } |
| 30 |