@sailfish-ai/recorder 1.7.6 → 1.7.7
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/errorInterceptor.js +113 -0
- package/dist/eventCache.js +1 -1
- package/dist/sailfish-recorder.cjs.js +1 -1
- package/dist/sailfish-recorder.cjs.js.br +0 -0
- package/dist/sailfish-recorder.cjs.js.gz +0 -0
- package/dist/sailfish-recorder.es.js +1 -1
- package/dist/sailfish-recorder.es.js.br +0 -0
- package/dist/sailfish-recorder.es.js.gz +0 -0
- package/dist/sailfish-recorder.umd.js +1 -1
- package/dist/sailfish-recorder.umd.js.br +0 -0
- package/dist/sailfish-recorder.umd.js.gz +0 -0
- package/dist/types/errorInterceptor.d.ts +10 -0
- package/package.json +2 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { SourceMapConsumer } from "source-map-js";
|
|
2
|
+
import { sendMessage } from "./websocket";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves stack traces using source maps.
|
|
5
|
+
* @param stackTrace - The minified stack trace.
|
|
6
|
+
* @returns The mapped stack trace with original file/line/column.
|
|
7
|
+
*/
|
|
8
|
+
export async function resolveStackTrace(stackTrace) {
|
|
9
|
+
if (!stackTrace)
|
|
10
|
+
return ["No stack trace available"];
|
|
11
|
+
const traceLines = Array.isArray(stackTrace) ? stackTrace : stackTrace.split("\n");
|
|
12
|
+
const mappedStack = [];
|
|
13
|
+
for (const line of traceLines) {
|
|
14
|
+
const match = line.match(/\/assets\/([^\/]+\.js):(\d+):(\d+)/);
|
|
15
|
+
if (!match) {
|
|
16
|
+
mappedStack.push(line); // Keep non-matching lines
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const [, fileUrl, lineNum, colNum] = match;
|
|
20
|
+
const fileName = fileUrl.split("/").pop();
|
|
21
|
+
const sourceMapUrl = `/assets/${fileName}.map`;
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(sourceMapUrl);
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
const rawSourceMap = await response.json();
|
|
28
|
+
if (!rawSourceMap.sources || !Array.isArray(rawSourceMap.sources)) {
|
|
29
|
+
mappedStack.push(line);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const consumer = await new SourceMapConsumer(rawSourceMap);
|
|
33
|
+
const original = consumer.originalPositionFor({
|
|
34
|
+
line: parseInt(lineNum, 10) - 1,
|
|
35
|
+
column: parseInt(colNum, 10),
|
|
36
|
+
});
|
|
37
|
+
if (original.source && original.line) {
|
|
38
|
+
// Get index of original source in rawSourceMap.sources
|
|
39
|
+
const sourceIndex = rawSourceMap.sources.indexOf(original.source);
|
|
40
|
+
let contextSnippet = [];
|
|
41
|
+
if (sourceIndex !== -1 &&
|
|
42
|
+
rawSourceMap.sourcesContent &&
|
|
43
|
+
rawSourceMap.sourcesContent[sourceIndex]) {
|
|
44
|
+
const lines = rawSourceMap.sourcesContent[sourceIndex].split("\n");
|
|
45
|
+
const errorLine = original.line || 1;
|
|
46
|
+
// Define range (clamp within file bounds)
|
|
47
|
+
const start = Math.max(errorLine - 6, 0); // -6 because index is 0-based
|
|
48
|
+
const end = Math.min(errorLine + 4, lines.length); // +4 to get 5 lines after
|
|
49
|
+
// Extract and trim context lines
|
|
50
|
+
contextSnippet = lines.slice(start, end).map((line, i) => {
|
|
51
|
+
const lineNumber = start + i + 1;
|
|
52
|
+
const prefix = lineNumber === errorLine ? "👉" : " ";
|
|
53
|
+
return `${prefix} ${lineNumber.toString().padStart(4)} | ${line.trim()}`;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
mappedStack.push(`${original.source}:${original.line}:${original.column} (${original.name || "anonymous"})`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
mappedStack.push(line);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
mappedStack.push(line);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return mappedStack;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Captures full error details and resolves the stack trace.
|
|
70
|
+
*/
|
|
71
|
+
async function captureError(error, isPromiseRejection = false) {
|
|
72
|
+
let errorMessage;
|
|
73
|
+
let stack;
|
|
74
|
+
if (error instanceof Error) {
|
|
75
|
+
errorMessage = error.message;
|
|
76
|
+
stack = error.stack || "No stack trace";
|
|
77
|
+
}
|
|
78
|
+
else if (typeof error === "string") {
|
|
79
|
+
errorMessage = error;
|
|
80
|
+
stack = "No stack trace available";
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
errorMessage = "Unknown error occurred";
|
|
84
|
+
stack = "No stack trace available";
|
|
85
|
+
}
|
|
86
|
+
const mappedStack = await resolveStackTrace(stack);
|
|
87
|
+
const filteredStack = mappedStack.filter((line) => !line.includes("chunk-") && !line.includes("react-dom"));
|
|
88
|
+
const errorDetails = {
|
|
89
|
+
message: errorMessage,
|
|
90
|
+
stack,
|
|
91
|
+
filteredStack,
|
|
92
|
+
userAgent: navigator.userAgent,
|
|
93
|
+
url: window.location.href,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
level: "error",
|
|
96
|
+
};
|
|
97
|
+
// Sends the mapped error details to the backend.
|
|
98
|
+
sendMessage({
|
|
99
|
+
type: "frontendError",
|
|
100
|
+
data: errorDetails,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Initializes the error interceptor globally.
|
|
105
|
+
*/
|
|
106
|
+
export function initializeErrorInterceptor() {
|
|
107
|
+
window.addEventListener("error", (event) => {
|
|
108
|
+
captureError(event.error || event.message);
|
|
109
|
+
});
|
|
110
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
111
|
+
captureError(event.reason, true);
|
|
112
|
+
});
|
|
113
|
+
}
|
package/dist/eventCache.js
CHANGED
|
@@ -16,7 +16,7 @@ export function sendRecordingEvents(webSocket) {
|
|
|
16
16
|
let batchSize = 0;
|
|
17
17
|
const sendBatch = () => {
|
|
18
18
|
if (batch.length > 0) {
|
|
19
|
-
const message = JSON.stringify({ type: "events", events: batch });
|
|
19
|
+
const message = JSON.stringify({ type: "events", events: batch, mapUuid: window.sfMapUuid });
|
|
20
20
|
webSocket.send(message);
|
|
21
21
|
batch = [];
|
|
22
22
|
batchSize = 0;
|