| 1 | import { z } from "zod"; |
| 2 | |
| 3 | const technicalString = (max: number) => z.string().max(max).refine((value) => !/[\u0000-\u001f\u007f]/.test(value)); |
| 4 | const runtimeBucket = z.string().trim().min(1).max(64).regex(/^[a-z0-9_]+$/); |
| 5 | |
| 6 | const Device = z.object({ |
| 7 | osVersion: technicalString(128), |
| 8 | osBuild: z.number().int().min(0).max(1_000_000), |
| 9 | osRevision: z.number().int().min(0).max(1_000_000), |
| 10 | distroId: technicalString(64), |
| 11 | distroVersion: technicalString(64), |
| 12 | kernelVersion: technicalString(128), |
| 13 | sessionType: z.enum(["wayland", "x11", "remote", "unknown"]), |
| 14 | cpu: technicalString(128), |
| 15 | cores: z.number().int().min(0).max(4096), |
| 16 | ramGb: z.number().min(0).max(65536), |
| 17 | }).partial(); |
| 18 | |
| 19 | const WebView2Diagnostic = z.object({ |
| 20 | kind: runtimeBucket, |
| 21 | reason: runtimeBucket, |
| 22 | exitCode: z.number().int().min(-2147483648).max(2147483647).optional(), |
| 23 | processDescription: technicalString(255).optional(), |
| 24 | failureSourceModule: z.string().max(255).refine((value) => !/[\\/]/.test(value)).optional(), |
| 25 | runtimeVersion: technicalString(128), |
| 26 | gpuDisabled: z.boolean(), |
| 27 | recovery: z.enum(["not_applicable", "reload_succeeded", "reload_failed"]), |
| 28 | }); |
| 29 | |
| 30 | export const WebRuntimeDiagnostic = z.object({ |
| 31 | engine: z.enum(["webview2", "webkitgtk"]), |
| 32 | kind: runtimeBucket, |
| 33 | reason: runtimeBucket, |
| 34 | exitCode: z.number().int().min(-2147483648).max(2147483647).optional(), |
| 35 | processDescription: technicalString(255).optional(), |
| 36 | failureSourceModule: technicalString(255).optional(), |
| 37 | runtimeVersion: technicalString(128), |
| 38 | gpuMode: z.enum(["enabled", "disabled", "always", "on_demand", "unknown"]), |
| 39 | recovery: z.enum(["not_applicable", "reload_succeeded", "reload_failed"]), |
| 40 | }); |
| 41 | |
| 42 | export const Diagnostics = z |
| 43 | .object({ |
| 44 | subjectVersion: technicalString(64).optional(), |
| 45 | subjectBuildCommit: technicalString(64).optional(), |
| 46 | subjectChannel: technicalString(32).optional(), |
| 47 | observerVersion: technicalString(64).optional(), |
| 48 | observerBuildCommit: technicalString(64).optional(), |
| 49 | runId: technicalString(64).optional(), |
| 50 | incidentId: technicalString(64).optional(), |
| 51 | processRole: z.enum(["service", "shell", "renderer", "unknown"]).optional(), |
| 52 | lastPhase: runtimeBucket.optional(), |
| 53 | lastPhaseAt: technicalString(64).optional(), |
| 54 | observedAt: technicalString(64).optional(), |
| 55 | terminationReason: z.enum(["user_quit", "update_restart", "startup_failure", "connection_lost", "system_signal", "unknown"]).optional(), |
| 56 | cleanupOutcome: z.enum(["not_started", "in_progress", "success", "failed", "interrupted", "unknown"]).optional(), |
| 57 | exitCode: z.number().int().min(-2147483648).max(2147483647).optional(), |
| 58 | signal: technicalString(32).optional(), |
| 59 | evidence: z.enum(["confirmed", "observed", "unknown"]).optional(), |
| 60 | category: z.enum(["crash", "startup_failure", "unclean_exit", "historical_record"]).optional(), |
| 61 | legacyParsed: z.boolean().optional(), |
| 62 | }) |
| 63 | .passthrough(); |
| 64 | |
| 65 | export const Report = z.object({ |
| 66 | eventId: z.string().regex(/^[0-9a-f]{32}$/).optional(), |
| 67 | dedupKey: z.string().regex(/^[0-9a-f]{64}$/).optional(), |
| 68 | installId: z.string().regex(/^[0-9a-f]{32}$/).optional(), |
| 69 | kind: z.enum(["crash", "exception", "feedback", "performance", "bot"]), |
| 70 | version: z.string().min(1).max(64), |
| 71 | os: z.string().min(1).max(32), |
| 72 | arch: z.string().min(1).max(32), |
| 73 | message: z.string().min(1).max(16 * 1024), |
| 74 | device: Device.optional(), |
| 75 | schemaVersion: z.number().int().min(1).max(10).optional(), |
| 76 | source: z.string().trim().min(1).max(32).regex(/^[a-z0-9_.-]+$/).optional(), |
| 77 | label: z.string().max(64).optional(), |
| 78 | errorType: z.string().max(128).optional(), |
| 79 | errorMessage: z.string().max(4 * 1024).optional(), |
| 80 | errorFamily: z |
| 81 | .string() |
| 82 | .max(128) |
| 83 | .regex(/^[a-z0-9_.-]+$/) |
| 84 | .optional(), |
| 85 | stack: z.string().max(16 * 1024).optional(), |
| 86 | componentStack: z.string().max(16 * 1024).optional(), |
| 87 | topFrame: z.string().max(300).optional(), |
| 88 | fingerprintHint: z.string().max(300).optional(), |
| 89 | buildCommit: z.string().max(64).optional(), |
| 90 | channel: z.string().max(32).optional(), |
| 91 | language: z.string().max(64).optional(), |
| 92 | view: z.string().max(200).optional(), |
| 93 | breadcrumbs: z.array(z.object({ |
| 94 | t: z.number().int().optional(), |
| 95 | cat: z.string().max(64).optional(), |
| 96 | msg: z.string().max(240).optional(), |
| 97 | })).max(30).optional(), |
| 98 | occurredAt: z.string().max(64).optional(), |
| 99 | diagnostics: Diagnostics.optional(), |
| 100 | webRuntime: WebRuntimeDiagnostic.optional(), |
| 101 | webview2: WebView2Diagnostic.optional(), |
| 102 | }); |
| 103 | |
| 104 | export type ReportPayload = z.infer<typeof Report>; |
| 105 |