@abbox/inspector 0.4.16 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/client-bridge.d.ts.map +1 -1
- package/dist/host/client-bridge.js +7 -0
- package/dist/host/host-bridge.d.ts +4 -1
- package/dist/host/host-bridge.d.ts.map +1 -1
- package/dist/host/host-bridge.js +6 -0
- package/dist/protocol.d.ts +27 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/runtime/recorder.d.ts +15 -0
- package/dist/runtime/recorder.d.ts.map +1 -0
- package/dist/runtime/recorder.js +245 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-bridge.d.ts","sourceRoot":"","sources":["../../src/host/client-bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-bridge.d.ts","sourceRoot":"","sources":["../../src/host/client-bridge.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CAmFjB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ABBBOX_INSPECTOR_CHANNEL, PROTOCOL_VERSION, } from "../protocol.js";
|
|
2
2
|
import { createInspectorRuntime } from "../runtime/inspector.js";
|
|
3
|
+
import { createRecorder } from "../runtime/recorder.js";
|
|
3
4
|
import { withLocalhostAliases } from "./origin-aliases.js";
|
|
4
5
|
import { validateIncomingMessage } from "./validate.js";
|
|
5
6
|
export function createInspectorClient(options) {
|
|
@@ -45,6 +46,8 @@ export function createInspectorClient(options) {
|
|
|
45
46
|
send({ ...base(), type: "ROUTE_CHANGED", path });
|
|
46
47
|
},
|
|
47
48
|
});
|
|
49
|
+
// The last minute of activity, for bug reports from the Viewer.
|
|
50
|
+
const recorder = createRecorder(win, doc);
|
|
48
51
|
const onMessage = (event) => {
|
|
49
52
|
const msg = validateIncomingMessage(event.data, event.origin, {
|
|
50
53
|
sessionId: options.sessionId,
|
|
@@ -61,6 +64,9 @@ export function createInspectorClient(options) {
|
|
|
61
64
|
if (msg.type === "APPLY_CHANGE_MARKERS") {
|
|
62
65
|
runtime.setChangeMarkers(msg.markers);
|
|
63
66
|
}
|
|
67
|
+
if (msg.type === "REQUEST_RECORDING") {
|
|
68
|
+
send({ ...base(), type: "RECORDING", steps: recorder.snapshot() });
|
|
69
|
+
}
|
|
64
70
|
};
|
|
65
71
|
return {
|
|
66
72
|
start: () => {
|
|
@@ -75,6 +81,7 @@ export function createInspectorClient(options) {
|
|
|
75
81
|
},
|
|
76
82
|
stop: () => {
|
|
77
83
|
win.removeEventListener("message", onMessage);
|
|
84
|
+
recorder.stop();
|
|
78
85
|
runtime.destroy();
|
|
79
86
|
},
|
|
80
87
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChangeMarker, type ClientToHostMessage } from "../protocol.js";
|
|
1
|
+
import { type ChangeMarker, type ClientToHostMessage, type RecordedStep } from "../protocol.js";
|
|
2
2
|
export type InspectorHostOptions = {
|
|
3
3
|
sessionId: string;
|
|
4
4
|
/** Origin of the embedded preview (iframe src origin). */
|
|
@@ -20,6 +20,7 @@ export type InspectorHostHandlers = {
|
|
|
20
20
|
onCleared?: () => void;
|
|
21
21
|
onChangeMarkerSelected?: (changeId: string) => void;
|
|
22
22
|
onRouteChange?: (path: string) => void;
|
|
23
|
+
onRecording?: (steps: RecordedStep[]) => void;
|
|
23
24
|
onError?: (code: string, message: string) => void;
|
|
24
25
|
};
|
|
25
26
|
export type InspectorHost = {
|
|
@@ -30,6 +31,8 @@ export type InspectorHost = {
|
|
|
30
31
|
applyChangeMarkers: (markers: ChangeMarker[]) => void;
|
|
31
32
|
/** Ask the preview to resend INSPECTOR_READY (call after iframe load). */
|
|
32
33
|
pingPreview: () => void;
|
|
34
|
+
/** Ask the preview for its last minute of activity (answered via onRecording). */
|
|
35
|
+
requestRecording: () => void;
|
|
33
36
|
};
|
|
34
37
|
export declare function createInspectorHost(options: InspectorHostOptions, handlers: InspectorHostHandlers): InspectorHost;
|
|
35
38
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../src/host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"host-bridge.d.ts","sourceRoot":"","sources":["../../src/host/host-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAGxB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAIxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACvC,CAAC;AAEF,KAAK,YAAY,GAAG,OAAO,CACzB,mBAAmB,EACnB;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAC5B,CAAC,SAAS,CAAC,CAAC;AACb,KAAK,aAAa,GAAG,OAAO,CAC1B,mBAAmB,EACnB;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,CAC7B,CAAC,SAAS,CAAC,CAAC;AAEb,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,kBAAkB,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;IACtD,0EAA0E;IAC1E,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,qBAAqB,GAC9B,aAAa,CAwEf"}
|
package/dist/host/host-bridge.js
CHANGED
|
@@ -38,6 +38,9 @@ export function createInspectorHost(options, handlers) {
|
|
|
38
38
|
if (msg.type === "ROUTE_CHANGED") {
|
|
39
39
|
handlers.onRouteChange?.(msg.path);
|
|
40
40
|
}
|
|
41
|
+
if (msg.type === "RECORDING") {
|
|
42
|
+
handlers.onRecording?.(msg.steps);
|
|
43
|
+
}
|
|
41
44
|
if (msg.type === "INSPECTOR_ERROR") {
|
|
42
45
|
handlers.onError?.(msg.code, msg.message);
|
|
43
46
|
}
|
|
@@ -58,6 +61,9 @@ export function createInspectorHost(options, handlers) {
|
|
|
58
61
|
applyChangeMarkers: (markers) => {
|
|
59
62
|
send({ ...base(), type: "APPLY_CHANGE_MARKERS", markers });
|
|
60
63
|
},
|
|
64
|
+
requestRecording: () => {
|
|
65
|
+
send({ ...base(), type: "REQUEST_RECORDING" });
|
|
66
|
+
},
|
|
61
67
|
pingPreview: () => {
|
|
62
68
|
send({
|
|
63
69
|
...base(),
|
package/dist/protocol.d.ts
CHANGED
|
@@ -126,8 +126,33 @@ export type ApplyChangeMarkersMessage = InspectorMessageBase & {
|
|
|
126
126
|
type: "APPLY_CHANGE_MARKERS";
|
|
127
127
|
markers: ChangeMarker[];
|
|
128
128
|
};
|
|
129
|
-
|
|
130
|
-
export type
|
|
129
|
+
/** One step from the preview's last minute, for bug reports. Never holds field values. */
|
|
130
|
+
export type RecordedStep = {
|
|
131
|
+
/** Epoch milliseconds. */
|
|
132
|
+
t: number;
|
|
133
|
+
kind: "click" | "submit" | "input" | "route" | "scroll" | "error" | "console-error" | "request";
|
|
134
|
+
route: string;
|
|
135
|
+
label?: string;
|
|
136
|
+
/** `data-abbox-src` of the element, linking the step to code. */
|
|
137
|
+
source?: string;
|
|
138
|
+
tag?: string;
|
|
139
|
+
detail?: string;
|
|
140
|
+
/** Script location of an error, when the browser reports one. */
|
|
141
|
+
location?: string;
|
|
142
|
+
method?: string;
|
|
143
|
+
url?: string;
|
|
144
|
+
status?: number;
|
|
145
|
+
durationMs?: number;
|
|
146
|
+
};
|
|
147
|
+
export type RequestRecordingMessage = InspectorMessageBase & {
|
|
148
|
+
type: "REQUEST_RECORDING";
|
|
149
|
+
};
|
|
150
|
+
export type RecordingMessage = InspectorMessageBase & {
|
|
151
|
+
type: "RECORDING";
|
|
152
|
+
steps: RecordedStep[];
|
|
153
|
+
};
|
|
154
|
+
export type HostToClientMessage = InspectorEnableMessage | InspectorDisableMessage | InspectorHostHelloMessage | ApplyChangeMarkersMessage | RequestRecordingMessage;
|
|
155
|
+
export type ClientToHostMessage = InspectorReadyMessage | ElementHoveredMessage | ElementSelectedMessage | ElementClearedMessage | ChangeMarkerSelectedMessage | RouteChangedMessage | RecordingMessage | InspectorErrorMessage;
|
|
131
156
|
export type InspectorMessage = HostToClientMessage | ClientToHostMessage;
|
|
132
157
|
export declare function isInspectorMessage(value: unknown): value is InspectorMessage;
|
|
133
158
|
//# sourceMappingURL=protocol.d.ts.map
|
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RecordedStep } from "../protocol.js";
|
|
2
|
+
/** Like a dashcam: only the last minute is kept. */
|
|
3
|
+
export declare const RECORDING_WINDOW_MS = 60000;
|
|
4
|
+
export type Recorder = {
|
|
5
|
+
snapshot: () => RecordedStep[];
|
|
6
|
+
stop: () => void;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Records the last minute of what happened in the page so a bug report can
|
|
10
|
+
* show the steps that led to it. Field values, request bodies and query strings
|
|
11
|
+
* are never stored. Lives only in this tab's sessionStorage, so it survives a
|
|
12
|
+
* reload and never leaves the page until the Viewer asks for it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createRecorder(win: Window, doc: Document): Recorder;
|
|
15
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/runtime/recorder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAK1C,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,MAAM,YAAY,EAAE,CAAC;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AA+EF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,QAAQ,CAuLnE"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { ABBBOX_SOURCE_ATTR } from "../instrument/constants.js";
|
|
2
|
+
/** Like a dashcam: only the last minute is kept. */
|
|
3
|
+
export const RECORDING_WINDOW_MS = 60_000;
|
|
4
|
+
const STORAGE_KEY = "abbox:recording";
|
|
5
|
+
const MAX_STEPS = 400;
|
|
6
|
+
const LABEL_LIMIT = 80;
|
|
7
|
+
function readStored(win) {
|
|
8
|
+
try {
|
|
9
|
+
const raw = win.sessionStorage.getItem(STORAGE_KEY);
|
|
10
|
+
return raw ? JSON.parse(raw) : [];
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function labelOf(el) {
|
|
17
|
+
// Prefer what a person would call the control: its label, then its hint or name.
|
|
18
|
+
const field = el instanceof HTMLInputElement ||
|
|
19
|
+
el instanceof HTMLTextAreaElement ||
|
|
20
|
+
el instanceof HTMLSelectElement
|
|
21
|
+
? el
|
|
22
|
+
: null;
|
|
23
|
+
const fieldHint = field instanceof HTMLSelectElement
|
|
24
|
+
? field.name
|
|
25
|
+
: field
|
|
26
|
+
? field.placeholder || field.name
|
|
27
|
+
: null;
|
|
28
|
+
const text = el.getAttribute("aria-label") ??
|
|
29
|
+
field?.labels?.[0]?.textContent ??
|
|
30
|
+
fieldHint ??
|
|
31
|
+
el.textContent;
|
|
32
|
+
const trimmed = text?.replace(/\s+/g, " ").trim();
|
|
33
|
+
return trimmed ? trimmed.slice(0, LABEL_LIMIT) : undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* What was entered, without the content: enough for a diagnosis to tell a
|
|
37
|
+
* single word from a phrase, or an empty field from a filled one.
|
|
38
|
+
*/
|
|
39
|
+
function shapeOf(el) {
|
|
40
|
+
if (el instanceof HTMLSelectElement) {
|
|
41
|
+
return `selected option ${el.selectedIndex + 1} of ${el.options.length}`;
|
|
42
|
+
}
|
|
43
|
+
if (el instanceof HTMLInputElement &&
|
|
44
|
+
(el.type === "checkbox" || el.type === "radio")) {
|
|
45
|
+
return el.checked ? "checked" : "unchecked";
|
|
46
|
+
}
|
|
47
|
+
const text = el.value.trim();
|
|
48
|
+
if (text === "")
|
|
49
|
+
return "cleared";
|
|
50
|
+
const words = text.split(/\s+/).length;
|
|
51
|
+
const kind = el instanceof HTMLInputElement && !["text", "search"].includes(el.type)
|
|
52
|
+
? `${el.type}, `
|
|
53
|
+
: "";
|
|
54
|
+
return `filled: ${kind}${words} word${words === 1 ? "" : "s"}, ${text.length} characters`;
|
|
55
|
+
}
|
|
56
|
+
function sourceOf(el) {
|
|
57
|
+
return (el.closest(`[${ABBBOX_SOURCE_ATTR}]`)?.getAttribute(ABBBOX_SOURCE_ATTR) ??
|
|
58
|
+
undefined);
|
|
59
|
+
}
|
|
60
|
+
/** Path only: query strings and fragments can carry tokens or personal data. */
|
|
61
|
+
function pathOf(url, base) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = new URL(url, base);
|
|
64
|
+
return parsed.origin === new URL(base).origin
|
|
65
|
+
? parsed.pathname
|
|
66
|
+
: `${parsed.origin}${parsed.pathname}`;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return url.split(/[?#]/)[0] ?? url;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Records the last minute of what happened in the page so a bug report can
|
|
74
|
+
* show the steps that led to it. Field values, request bodies and query strings
|
|
75
|
+
* are never stored. Lives only in this tab's sessionStorage, so it survives a
|
|
76
|
+
* reload and never leaves the page until the Viewer asks for it.
|
|
77
|
+
*/
|
|
78
|
+
export function createRecorder(win, doc) {
|
|
79
|
+
let steps = readStored(win);
|
|
80
|
+
const prune = (now) => {
|
|
81
|
+
steps = steps
|
|
82
|
+
.filter((step) => now - step.t <= RECORDING_WINDOW_MS)
|
|
83
|
+
.slice(-MAX_STEPS);
|
|
84
|
+
};
|
|
85
|
+
const push = (step) => {
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
steps.push({ t: now, route: win.location.pathname, ...step });
|
|
88
|
+
prune(now);
|
|
89
|
+
try {
|
|
90
|
+
win.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(steps));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Storage full or blocked: the in-memory copy still works for this page.
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const onClick = (event) => {
|
|
97
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
98
|
+
const el = target?.closest("button, a, [role=button], input, select, label") ??
|
|
99
|
+
target;
|
|
100
|
+
if (!el)
|
|
101
|
+
return;
|
|
102
|
+
push({
|
|
103
|
+
kind: "click",
|
|
104
|
+
label: labelOf(el),
|
|
105
|
+
source: sourceOf(el),
|
|
106
|
+
tag: el.tagName.toLowerCase(),
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
const onSubmit = (event) => {
|
|
110
|
+
const form = event.target instanceof Element ? event.target : null;
|
|
111
|
+
if (!form)
|
|
112
|
+
return;
|
|
113
|
+
const submitter = event.submitter;
|
|
114
|
+
push({
|
|
115
|
+
kind: "submit",
|
|
116
|
+
label: submitter ? labelOf(submitter) : labelOf(form),
|
|
117
|
+
source: sourceOf(form),
|
|
118
|
+
tag: "form",
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
const onChange = (event) => {
|
|
122
|
+
const el = event.target;
|
|
123
|
+
if (!(el instanceof HTMLInputElement ||
|
|
124
|
+
el instanceof HTMLTextAreaElement ||
|
|
125
|
+
el instanceof HTMLSelectElement))
|
|
126
|
+
return;
|
|
127
|
+
if (el instanceof HTMLInputElement && el.type === "password")
|
|
128
|
+
return;
|
|
129
|
+
push({
|
|
130
|
+
kind: "input",
|
|
131
|
+
label: labelOf(el) ?? el.name ?? undefined,
|
|
132
|
+
source: sourceOf(el),
|
|
133
|
+
tag: el.tagName.toLowerCase(),
|
|
134
|
+
detail: shapeOf(el),
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
let lastScroll = 0;
|
|
138
|
+
const onScroll = () => {
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
if (now - lastScroll < 1000)
|
|
141
|
+
return;
|
|
142
|
+
lastScroll = now;
|
|
143
|
+
const max = doc.documentElement.scrollHeight - win.innerHeight;
|
|
144
|
+
const pct = max > 0 ? Math.round((win.scrollY / max) * 100) : 0;
|
|
145
|
+
push({ kind: "scroll", detail: `${pct}% down` });
|
|
146
|
+
};
|
|
147
|
+
let lastPath = win.location.pathname;
|
|
148
|
+
const routeTimer = win.setInterval(() => {
|
|
149
|
+
if (win.location.pathname === lastPath)
|
|
150
|
+
return;
|
|
151
|
+
lastPath = win.location.pathname;
|
|
152
|
+
push({ kind: "route", detail: lastPath });
|
|
153
|
+
}, 300);
|
|
154
|
+
const onError = (event) => {
|
|
155
|
+
push({
|
|
156
|
+
kind: "error",
|
|
157
|
+
detail: String(event.message).slice(0, 300),
|
|
158
|
+
...(event.filename
|
|
159
|
+
? {
|
|
160
|
+
location: `${pathOf(event.filename, win.location.href)}:${event.lineno}`,
|
|
161
|
+
}
|
|
162
|
+
: {}),
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
const onRejection = (event) => {
|
|
166
|
+
const reason = event.reason instanceof Error
|
|
167
|
+
? event.reason.message
|
|
168
|
+
: String(event.reason);
|
|
169
|
+
push({
|
|
170
|
+
kind: "error",
|
|
171
|
+
detail: `Unhandled rejection: ${reason}`.slice(0, 300),
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
const originalConsoleError = console.error;
|
|
175
|
+
console.error = (...args) => {
|
|
176
|
+
push({
|
|
177
|
+
kind: "console-error",
|
|
178
|
+
detail: args
|
|
179
|
+
.map((a) => a instanceof Error ? a.message : typeof a === "string" ? a : "")
|
|
180
|
+
.join(" ")
|
|
181
|
+
.trim()
|
|
182
|
+
.slice(0, 300),
|
|
183
|
+
});
|
|
184
|
+
originalConsoleError.apply(console, args);
|
|
185
|
+
};
|
|
186
|
+
const originalFetch = win.fetch;
|
|
187
|
+
win.fetch = async (input, init) => {
|
|
188
|
+
const started = Date.now();
|
|
189
|
+
const url = typeof input === "string"
|
|
190
|
+
? input
|
|
191
|
+
: input instanceof URL
|
|
192
|
+
? input.href
|
|
193
|
+
: input.url;
|
|
194
|
+
// Framework dev tooling (Next.js overlay, HMR) is not the app's activity.
|
|
195
|
+
if (/\/(__nextjs|_next\/)/.test(url)) {
|
|
196
|
+
return originalFetch.call(win, input, init);
|
|
197
|
+
}
|
|
198
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
199
|
+
try {
|
|
200
|
+
const response = await originalFetch.call(win, input, init);
|
|
201
|
+
push({
|
|
202
|
+
kind: "request",
|
|
203
|
+
method,
|
|
204
|
+
url: pathOf(url, win.location.href),
|
|
205
|
+
status: response.status,
|
|
206
|
+
durationMs: Date.now() - started,
|
|
207
|
+
});
|
|
208
|
+
return response;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
push({
|
|
212
|
+
kind: "request",
|
|
213
|
+
method,
|
|
214
|
+
url: pathOf(url, win.location.href),
|
|
215
|
+
status: 0,
|
|
216
|
+
detail: error instanceof Error ? error.message : "Network error",
|
|
217
|
+
durationMs: Date.now() - started,
|
|
218
|
+
});
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
doc.addEventListener("click", onClick, true);
|
|
223
|
+
doc.addEventListener("submit", onSubmit, true);
|
|
224
|
+
doc.addEventListener("change", onChange, true);
|
|
225
|
+
win.addEventListener("scroll", onScroll, { passive: true });
|
|
226
|
+
win.addEventListener("error", onError);
|
|
227
|
+
win.addEventListener("unhandledrejection", onRejection);
|
|
228
|
+
return {
|
|
229
|
+
snapshot: () => {
|
|
230
|
+
prune(Date.now());
|
|
231
|
+
return [...steps];
|
|
232
|
+
},
|
|
233
|
+
stop: () => {
|
|
234
|
+
doc.removeEventListener("click", onClick, true);
|
|
235
|
+
doc.removeEventListener("submit", onSubmit, true);
|
|
236
|
+
doc.removeEventListener("change", onChange, true);
|
|
237
|
+
win.removeEventListener("scroll", onScroll);
|
|
238
|
+
win.removeEventListener("error", onError);
|
|
239
|
+
win.removeEventListener("unhandledrejection", onRejection);
|
|
240
|
+
win.clearInterval(routeTimer);
|
|
241
|
+
console.error = originalConsoleError;
|
|
242
|
+
win.fetch = originalFetch;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|