@watchforge/browser 0.1.15 → 0.1.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/package.json +1 -1
- package/src/client.js +16 -1
- package/src/index.d.ts +1 -0
- package/src/stacktrace.js +379 -25
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -25,6 +25,7 @@ let CAPTURE_CONSOLE_ERRORS = true;
|
|
|
25
25
|
let CAPTURE_RESOURCE_ERRORS = true;
|
|
26
26
|
let CAPTURE_FAILED_REQUESTS = true;
|
|
27
27
|
let CAPTURE_NODE_WARNINGS = false;
|
|
28
|
+
let CAPTURE_NODE_MULTIPLE_RESOLVES = false;
|
|
28
29
|
|
|
29
30
|
// Detect environment
|
|
30
31
|
const isBrowser = typeof window !== "undefined";
|
|
@@ -594,6 +595,7 @@ export function register({
|
|
|
594
595
|
captureResourceErrors = true,
|
|
595
596
|
captureFailedRequests = true,
|
|
596
597
|
captureNodeWarnings = false,
|
|
598
|
+
captureNodeMultipleResolves = false,
|
|
597
599
|
projectRoot = null,
|
|
598
600
|
}) {
|
|
599
601
|
const nextBrowserRegisterKey = isBrowser
|
|
@@ -626,6 +628,7 @@ export function register({
|
|
|
626
628
|
CAPTURE_RESOURCE_ERRORS = Boolean(captureResourceErrors);
|
|
627
629
|
CAPTURE_FAILED_REQUESTS = Boolean(captureFailedRequests);
|
|
628
630
|
CAPTURE_NODE_WARNINGS = Boolean(captureNodeWarnings);
|
|
631
|
+
CAPTURE_NODE_MULTIPLE_RESOLVES = Boolean(captureNodeMultipleResolves);
|
|
629
632
|
setProjectRoot(
|
|
630
633
|
projectRoot ||
|
|
631
634
|
(isNode ? process.env.WATCHFORGE_PROJECT_ROOT || process.env.INIT_CWD : null)
|
|
@@ -682,11 +685,23 @@ export function register({
|
|
|
682
685
|
});
|
|
683
686
|
|
|
684
687
|
process.on("multipleResolves", (type, promise, reason) => {
|
|
688
|
+
addBreadcrumb({
|
|
689
|
+
type: "error",
|
|
690
|
+
level: "warning",
|
|
691
|
+
category: "process.multipleResolves",
|
|
692
|
+
message: `Node.js promise multipleResolves: ${type}`,
|
|
693
|
+
data: {
|
|
694
|
+
type,
|
|
695
|
+
reason: reason?.message || String(reason || ""),
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
if (!CAPTURE_NODE_MULTIPLE_RESOLVES) return;
|
|
685
700
|
void captureException(
|
|
686
701
|
normalizeException(reason, `Node.js promise multipleResolves: ${type}`),
|
|
687
702
|
{
|
|
688
703
|
tags: {
|
|
689
|
-
handled:
|
|
704
|
+
handled: true,
|
|
690
705
|
mechanism: "process.multipleResolves",
|
|
691
706
|
type,
|
|
692
707
|
},
|
package/src/index.d.ts
CHANGED
package/src/stacktrace.js
CHANGED
|
@@ -122,6 +122,219 @@ function getUndefinedIdentifier(message) {
|
|
|
122
122
|
return match?.[1] || null;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
function getReadPropertyName(message) {
|
|
126
|
+
if (!message || typeof message !== "string") return null;
|
|
127
|
+
const match = message.match(/Cannot read properties of (?:null|undefined) \(reading ['"]([^'"]+)['"]\)/);
|
|
128
|
+
return match?.[1] || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isJsonParseError(message) {
|
|
132
|
+
if (!message || typeof message !== "string") return false;
|
|
133
|
+
return (
|
|
134
|
+
/in JSON at position/i.test(message) ||
|
|
135
|
+
/Unexpected token .* in JSON/i.test(message) ||
|
|
136
|
+
(/SyntaxError/i.test(message) && /JSON/i.test(message))
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isUriError(message) {
|
|
141
|
+
if (!message || typeof message !== "string") return false;
|
|
142
|
+
return /URI(?:Error| malformed)/i.test(message) || /decodeURIComponent/i.test(message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isInvalidArrayLengthError(message) {
|
|
146
|
+
if (!message || typeof message !== "string") return false;
|
|
147
|
+
return /Invalid array length/i.test(message);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function lineHasJsonParse(line) {
|
|
151
|
+
return Boolean(line && /JSON\.parse\s*\(/.test(line));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function lineHasDecodeUriComponent(line) {
|
|
155
|
+
return Boolean(line && /decodeURIComponent\s*\(/.test(line));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function lineHasNewArray(line) {
|
|
159
|
+
return Boolean(line && /new Array\s*\(/.test(line));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function lineHasInvalidArrayLiteral(line) {
|
|
163
|
+
return Boolean(line && /new Array\s*\(\s*-/.test(line));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isInsideSetTimeout(sourceLines, lineIndex, scopeStart = 0) {
|
|
167
|
+
for (let i = lineIndex; i >= Math.max(scopeStart, lineIndex - 12); i--) {
|
|
168
|
+
if (/setTimeout\s*\(/.test(sourceLines[i])) return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function lineLooksLikeJsxRender(line) {
|
|
174
|
+
if (!line || typeof line !== "string") return false;
|
|
175
|
+
const trimmed = line.trim();
|
|
176
|
+
return (
|
|
177
|
+
trimmed.startsWith("return (") ||
|
|
178
|
+
trimmed.startsWith("return <") ||
|
|
179
|
+
trimmed.startsWith("<") ||
|
|
180
|
+
/^<\//.test(trimmed)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getFunctionScopeStart(sourceLines, functionName) {
|
|
185
|
+
if (!sourceLines?.length || !functionName) return -1;
|
|
186
|
+
|
|
187
|
+
const normalized = String(functionName).replace(/\.useEffect$/, "");
|
|
188
|
+
const patterns = [];
|
|
189
|
+
|
|
190
|
+
if (functionName.includes("useEffect")) {
|
|
191
|
+
patterns.push(/useEffect\s*\(/);
|
|
192
|
+
}
|
|
193
|
+
if (normalized && normalized !== "<anonymous>") {
|
|
194
|
+
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
195
|
+
patterns.push(new RegExp(`\\b${escaped}\\s*=\\s*(?:async\\s*)?\\(`));
|
|
196
|
+
patterns.push(new RegExp(`function\\s+${escaped}\\s*\\(`));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const pattern of patterns) {
|
|
200
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
201
|
+
if (pattern.test(sourceLines[i])) return i;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return -1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getUseEffectBodyRange(sourceLines, scopeStart) {
|
|
209
|
+
if (scopeStart < 0 || !sourceLines?.length) return null;
|
|
210
|
+
|
|
211
|
+
for (let i = scopeStart + 1; i < sourceLines.length; i++) {
|
|
212
|
+
if (/^\s*\}\s*,\s*\[/.test(sourceLines[i])) {
|
|
213
|
+
return { start: scopeStart, end: i };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isInsideUseEffectBody(sourceLines, lineIndex, scopeStart) {
|
|
221
|
+
const range = getUseEffectBodyRange(sourceLines, scopeStart);
|
|
222
|
+
if (!range || lineIndex < 0) return scopeStart >= 0 ? lineIndex >= scopeStart : true;
|
|
223
|
+
return lineIndex >= range.start && lineIndex <= range.end;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function filterCandidatesToScope(sourceLines, candidates, scopeStart, functionName) {
|
|
227
|
+
if (!candidates.length) return candidates;
|
|
228
|
+
if (!functionName?.includes("useEffect") || scopeStart < 0) return candidates;
|
|
229
|
+
|
|
230
|
+
const inBody = candidates.filter((idx) =>
|
|
231
|
+
isInsideUseEffectBody(sourceLines, idx, scopeStart)
|
|
232
|
+
);
|
|
233
|
+
return inBody.length ? inBody : candidates;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function pickCandidateNearScope(sourceLines, candidates, scopeStart, functionName) {
|
|
237
|
+
if (!candidates.length) return null;
|
|
238
|
+
|
|
239
|
+
const scoped = filterCandidatesToScope(sourceLines, candidates, scopeStart, functionName);
|
|
240
|
+
if (scopeStart < 0) return scoped[0] + 1;
|
|
241
|
+
|
|
242
|
+
const afterStart = scoped.filter((idx) => idx >= scopeStart);
|
|
243
|
+
const pool = afterStart.length ? afterStart : scoped;
|
|
244
|
+
return pool[0] + 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function pickCandidatePreferringSetTimeout(sourceLines, candidates, scopeStart, functionName) {
|
|
248
|
+
if (!candidates.length) return null;
|
|
249
|
+
|
|
250
|
+
const scoped = filterCandidatesToScope(sourceLines, candidates, scopeStart, functionName);
|
|
251
|
+
const pool =
|
|
252
|
+
scopeStart >= 0
|
|
253
|
+
? scoped.filter((idx) => idx >= scopeStart).length
|
|
254
|
+
? scoped.filter((idx) => idx >= scopeStart)
|
|
255
|
+
: scoped
|
|
256
|
+
: scoped;
|
|
257
|
+
|
|
258
|
+
const inSetTimeout = pool.filter((idx) =>
|
|
259
|
+
isInsideSetTimeout(sourceLines, idx, scopeStart)
|
|
260
|
+
);
|
|
261
|
+
if (inSetTimeout.length) return inSetTimeout[0] + 1;
|
|
262
|
+
|
|
263
|
+
return pool[0] + 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function inferJsonParseLine(frame, sourceLines) {
|
|
267
|
+
if (!isJsonParseError(frame.error_message) || !sourceLines?.length) return null;
|
|
268
|
+
|
|
269
|
+
const candidates = [];
|
|
270
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
271
|
+
if (lineHasJsonParse(sourceLines[i])) candidates.push(i);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const scopeStart = getFunctionScopeStart(sourceLines, frame.function);
|
|
275
|
+
return pickCandidatePreferringSetTimeout(
|
|
276
|
+
sourceLines,
|
|
277
|
+
candidates,
|
|
278
|
+
scopeStart,
|
|
279
|
+
frame.function
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function inferUriErrorLine(frame, sourceLines) {
|
|
284
|
+
if (!isUriError(frame.error_message) || !sourceLines?.length) return null;
|
|
285
|
+
|
|
286
|
+
const candidates = [];
|
|
287
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
288
|
+
if (lineHasDecodeUriComponent(sourceLines[i])) candidates.push(i);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const scopeStart = getFunctionScopeStart(sourceLines, frame.function);
|
|
292
|
+
return pickCandidatePreferringSetTimeout(
|
|
293
|
+
sourceLines,
|
|
294
|
+
candidates,
|
|
295
|
+
scopeStart,
|
|
296
|
+
frame.function
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function inferInvalidArrayLengthLine(frame, sourceLines) {
|
|
301
|
+
if (!isInvalidArrayLengthError(frame.error_message) || !sourceLines?.length) return null;
|
|
302
|
+
|
|
303
|
+
const scopeStart = getFunctionScopeStart(sourceLines, frame.function);
|
|
304
|
+
const candidates = [];
|
|
305
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
306
|
+
if (lineHasNewArray(sourceLines[i])) candidates.push(i);
|
|
307
|
+
}
|
|
308
|
+
if (!candidates.length) return null;
|
|
309
|
+
|
|
310
|
+
const scoped = scopeStart >= 0 ? candidates.filter((idx) => idx >= scopeStart) : candidates;
|
|
311
|
+
const pool = scoped.length ? scoped : candidates;
|
|
312
|
+
|
|
313
|
+
const negativeLiteral = pool.filter((idx) =>
|
|
314
|
+
lineHasInvalidArrayLiteral(sourceLines[idx])
|
|
315
|
+
);
|
|
316
|
+
if (negativeLiteral.length) return negativeLiteral[0] + 1;
|
|
317
|
+
|
|
318
|
+
const inSetTimeout = pool.filter((idx) =>
|
|
319
|
+
isInsideSetTimeout(sourceLines, idx, scopeStart)
|
|
320
|
+
);
|
|
321
|
+
if (inSetTimeout.length) return inSetTimeout[0] + 1;
|
|
322
|
+
|
|
323
|
+
return pool[0] + 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function lineHasToken(line, token) {
|
|
327
|
+
if (!line || !token) return false;
|
|
328
|
+
const tokenPattern = new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
|
|
329
|
+
return tokenPattern.test(line);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function lineHasPropertyAccess(line, property) {
|
|
333
|
+
if (!line || !property) return false;
|
|
334
|
+
const escaped = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
335
|
+
return new RegExp(`(?:\\.${escaped}\\b|\\[['"]${escaped}['"]\\]|\\["${escaped}"\\])`).test(line);
|
|
336
|
+
}
|
|
337
|
+
|
|
125
338
|
function inferLineFromErrorMessage(frame, sourceLines) {
|
|
126
339
|
const identifier = getUndefinedIdentifier(frame.error_message);
|
|
127
340
|
if (!identifier || !sourceLines?.length) return null;
|
|
@@ -155,6 +368,100 @@ function inferLineFromErrorMessage(frame, sourceLines) {
|
|
|
155
368
|
return candidates[0] + 1;
|
|
156
369
|
}
|
|
157
370
|
|
|
371
|
+
function inferPropertyLineFromErrorMessage(frame, sourceLines) {
|
|
372
|
+
const property = getReadPropertyName(frame.error_message);
|
|
373
|
+
if (!property || !sourceLines?.length) return null;
|
|
374
|
+
|
|
375
|
+
for (let i = 0; i < sourceLines.length; i++) {
|
|
376
|
+
if (lineHasPropertyAccess(sourceLines[i], property)) {
|
|
377
|
+
return i + 1;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function setFrameLineFromSource(frame, sourceLines, lineno, tokenForColumn) {
|
|
385
|
+
if (!lineno || !sourceLines[lineno - 1]) return lineno;
|
|
386
|
+
|
|
387
|
+
frame.lineno = lineno;
|
|
388
|
+
if (tokenForColumn) {
|
|
389
|
+
const col = sourceLines[lineno - 1].indexOf(tokenForColumn);
|
|
390
|
+
if (col >= 0) frame.colno = col + 1;
|
|
391
|
+
}
|
|
392
|
+
return lineno;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function adjustLineFromErrorMessage(frame, sourceLines, lineno) {
|
|
396
|
+
if (!sourceLines?.length) return lineno;
|
|
397
|
+
|
|
398
|
+
const currentLine = lineno ? sourceLines[lineno - 1] : null;
|
|
399
|
+
const scopeStart = getFunctionScopeStart(sourceLines, frame.function);
|
|
400
|
+
const mappedOutsideUseEffect =
|
|
401
|
+
frame.function?.includes("useEffect") &&
|
|
402
|
+
lineno &&
|
|
403
|
+
!isInsideUseEffectBody(sourceLines, lineno - 1, scopeStart);
|
|
404
|
+
const mappedToRenderLine =
|
|
405
|
+
lineLooksLikeJsxRender(currentLine) &&
|
|
406
|
+
(frame.function?.includes("useEffect") || frame.function?.includes("setTimeout"));
|
|
407
|
+
const shouldReinfer = mappedToRenderLine || mappedOutsideUseEffect;
|
|
408
|
+
|
|
409
|
+
const identifier = getUndefinedIdentifier(frame.error_message);
|
|
410
|
+
if (identifier && (shouldReinfer || !lineHasToken(currentLine, identifier))) {
|
|
411
|
+
const inferredLine = inferLineFromErrorMessage(frame, sourceLines);
|
|
412
|
+
if (inferredLine) {
|
|
413
|
+
return setFrameLineFromSource(frame, sourceLines, inferredLine, identifier);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const property = getReadPropertyName(frame.error_message);
|
|
418
|
+
if (property && (shouldReinfer || !lineHasPropertyAccess(currentLine, property))) {
|
|
419
|
+
const inferredLine = inferPropertyLineFromErrorMessage(frame, sourceLines);
|
|
420
|
+
if (inferredLine) {
|
|
421
|
+
return setFrameLineFromSource(frame, sourceLines, inferredLine, `.${property}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (isJsonParseError(frame.error_message) && (shouldReinfer || !lineHasJsonParse(currentLine))) {
|
|
426
|
+
const inferredLine = inferJsonParseLine(frame, sourceLines);
|
|
427
|
+
if (inferredLine) {
|
|
428
|
+
return setFrameLineFromSource(frame, sourceLines, inferredLine, "JSON.parse");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (isUriError(frame.error_message) && (shouldReinfer || !lineHasDecodeUriComponent(currentLine))) {
|
|
433
|
+
const inferredLine = inferUriErrorLine(frame, sourceLines);
|
|
434
|
+
if (inferredLine) {
|
|
435
|
+
return setFrameLineFromSource(frame, sourceLines, inferredLine, "decodeURIComponent");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (isInvalidArrayLengthError(frame.error_message)) {
|
|
440
|
+
const inferredLine = inferInvalidArrayLengthLine(frame, sourceLines);
|
|
441
|
+
if (inferredLine) {
|
|
442
|
+
const inferredSourceLine = sourceLines[inferredLine - 1];
|
|
443
|
+
const shouldReplace =
|
|
444
|
+
shouldReinfer ||
|
|
445
|
+
!lineHasNewArray(currentLine) ||
|
|
446
|
+
(lineHasInvalidArrayLiteral(inferredSourceLine) &&
|
|
447
|
+
!lineHasInvalidArrayLiteral(currentLine)) ||
|
|
448
|
+
(frame.function?.includes("useEffect") &&
|
|
449
|
+
isInsideSetTimeout(sourceLines, inferredLine - 1) &&
|
|
450
|
+
!isInsideSetTimeout(
|
|
451
|
+
sourceLines,
|
|
452
|
+
lineno ? lineno - 1 : -1,
|
|
453
|
+
getFunctionScopeStart(sourceLines, frame.function)
|
|
454
|
+
));
|
|
455
|
+
|
|
456
|
+
if (shouldReplace) {
|
|
457
|
+
return setFrameLineFromSource(frame, sourceLines, inferredLine, "new Array");
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return lineno;
|
|
463
|
+
}
|
|
464
|
+
|
|
158
465
|
function normalizeSourcePath(source) {
|
|
159
466
|
if (!source) return "";
|
|
160
467
|
|
|
@@ -392,6 +699,8 @@ function resolveFrameLine(frame, lines) {
|
|
|
392
699
|
let lineno = frame.lineno;
|
|
393
700
|
if (!lines?.length || !lineno) return lineno;
|
|
394
701
|
|
|
702
|
+
lineno = adjustLineFromErrorMessage(frame, lines, lineno);
|
|
703
|
+
|
|
395
704
|
if (lineno > lines.length) {
|
|
396
705
|
const inferredLine = inferLineFromErrorMessage(frame, lines);
|
|
397
706
|
if (inferredLine) {
|
|
@@ -569,6 +878,53 @@ function findInlineSourceMap(sourceText) {
|
|
|
569
878
|
return null;
|
|
570
879
|
}
|
|
571
880
|
|
|
881
|
+
function isOriginalSourceFrame(frame) {
|
|
882
|
+
const normalized = normalizeSourcePath(frame.abs_path || frame.module || frame.raw_abs_path);
|
|
883
|
+
if (!normalized) return false;
|
|
884
|
+
|
|
885
|
+
if (/^_next\//.test(normalized) || /\/_next\//.test(normalized)) return false;
|
|
886
|
+
if (/\.(?:m?js|css)(?:\.map)?$/.test(normalized) && !/\.(?:tsx?|jsx?)$/.test(normalized)) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
return (
|
|
891
|
+
/(?:^|\/)src\/.+\.(?:tsx?|jsx?)$/.test(normalized) ||
|
|
892
|
+
/\.(?:tsx?|jsx?)$/.test(normalized)
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function getSourceMapContentForFrame(frame, sourceMap) {
|
|
897
|
+
if (!sourceMap || !Array.isArray(sourceMap.sources)) return null;
|
|
898
|
+
|
|
899
|
+
const sourceIndex = sourceMap.sources.findIndex((source) =>
|
|
900
|
+
sourceMatchesFrame(source, frame.abs_path || frame.module || frame.raw_abs_path)
|
|
901
|
+
);
|
|
902
|
+
if (sourceIndex < 0) return null;
|
|
903
|
+
|
|
904
|
+
const content = Array.isArray(sourceMap.sourcesContent)
|
|
905
|
+
? sourceMap.sourcesContent[sourceIndex]
|
|
906
|
+
: null;
|
|
907
|
+
if (!content) return null;
|
|
908
|
+
|
|
909
|
+
return {
|
|
910
|
+
source: sourceMap.sources[sourceIndex],
|
|
911
|
+
lines: content.split("\n"),
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function applyOriginalSourceFrameContext(frame, sourceMap) {
|
|
916
|
+
if (!isOriginalSourceFrame(frame) || !frame.lineno) return false;
|
|
917
|
+
|
|
918
|
+
const sourceContent = getSourceMapContentForFrame(frame, sourceMap);
|
|
919
|
+
if (!sourceContent?.lines?.length) return false;
|
|
920
|
+
if (frame.lineno > sourceContent.lines.length) return false;
|
|
921
|
+
|
|
922
|
+
frame.abs_path = sourceContent.source;
|
|
923
|
+
frame.filename = normalizeSourcePath(sourceContent.source).split("/").pop() || frame.filename;
|
|
924
|
+
applySourceContext(frame, sourceContent.lines, frame.lineno);
|
|
925
|
+
return Boolean(frame.context_line);
|
|
926
|
+
}
|
|
927
|
+
|
|
572
928
|
async function findSourceMapForFrame(frame) {
|
|
573
929
|
const wanted = normalizeSourcePath(frame.abs_path || frame.module);
|
|
574
930
|
const scriptUrls = [];
|
|
@@ -615,29 +971,13 @@ async function findSourceMapForFrame(frame) {
|
|
|
615
971
|
|
|
616
972
|
async function applySourceMapToFrame(frame, sourceMap) {
|
|
617
973
|
try {
|
|
974
|
+
if (applyOriginalSourceFrameContext(frame, sourceMap)) {
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
|
|
618
978
|
const SourceMapConsumer = await getSourceMapConsumer();
|
|
619
979
|
if (!SourceMapConsumer) return false;
|
|
620
980
|
|
|
621
|
-
const sources = Array.isArray(sourceMap.sources) ? sourceMap.sources : [];
|
|
622
|
-
const contents = Array.isArray(sourceMap.sourcesContent)
|
|
623
|
-
? sourceMap.sourcesContent
|
|
624
|
-
: [];
|
|
625
|
-
const matchedSourceIndex = sources.findIndex((candidate) =>
|
|
626
|
-
sourceMatchesFrame(candidate, frame.abs_path || frame.module || frame.raw_abs_path)
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
if (matchedSourceIndex >= 0 && contents[matchedSourceIndex]) {
|
|
630
|
-
const source = sources[matchedSourceIndex];
|
|
631
|
-
const sourceLines = contents[matchedSourceIndex].split("\n");
|
|
632
|
-
const originalLine = frame.lineno;
|
|
633
|
-
if (originalLine && originalLine <= sourceLines.length) {
|
|
634
|
-
frame.abs_path = source;
|
|
635
|
-
frame.filename = normalizeSourcePath(source).split("/").pop() || frame.filename;
|
|
636
|
-
applySourceContext(frame, sourceLines, originalLine);
|
|
637
|
-
if (frame.context_line) return true;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
981
|
const consumer = await new SourceMapConsumer(sourceMap);
|
|
642
982
|
let source = null;
|
|
643
983
|
let lineno = frame.lineno;
|
|
@@ -655,7 +995,7 @@ async function applySourceMapToFrame(frame, sourceMap) {
|
|
|
655
995
|
}
|
|
656
996
|
}
|
|
657
997
|
|
|
658
|
-
if (!source) {
|
|
998
|
+
if (!source && sourceMap.sources?.length) {
|
|
659
999
|
source = sourceMap.sources?.find((candidate) =>
|
|
660
1000
|
sourceMatchesFrame(candidate, frame.abs_path)
|
|
661
1001
|
);
|
|
@@ -674,11 +1014,15 @@ async function applySourceMapToFrame(frame, sourceMap) {
|
|
|
674
1014
|
|
|
675
1015
|
if (!content) return false;
|
|
676
1016
|
|
|
1017
|
+
const sourceLines = content.split("\n");
|
|
1018
|
+
lineno = adjustLineFromErrorMessage(frame, sourceLines, lineno);
|
|
1019
|
+
colno = frame.colno ?? colno;
|
|
1020
|
+
|
|
677
1021
|
frame.abs_path = source;
|
|
678
1022
|
frame.filename = normalizeSourcePath(source).split("/").pop() || frame.filename;
|
|
679
1023
|
frame.lineno = lineno;
|
|
680
1024
|
frame.colno = colno;
|
|
681
|
-
applySourceContext(frame,
|
|
1025
|
+
applySourceContext(frame, sourceLines, lineno);
|
|
682
1026
|
return Boolean(frame.context_line);
|
|
683
1027
|
} catch {
|
|
684
1028
|
return false;
|
|
@@ -737,20 +1081,30 @@ function getWebpackFrameArtifacts(frame) {
|
|
|
737
1081
|
async function enrichFrameWithBrowserSource(frame) {
|
|
738
1082
|
if (!frame.in_app || !frame.lineno) return;
|
|
739
1083
|
|
|
1084
|
+
if (isOriginalSourceFrame(frame)) {
|
|
1085
|
+
const lines = await fetchBrowserSourceLines(frame.abs_path, frame.raw_abs_path);
|
|
1086
|
+
if (lines) {
|
|
1087
|
+
applySourceContext(frame, lines, frame.lineno);
|
|
1088
|
+
if (frame.context_line) return;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
740
1092
|
const webpackArtifacts = getWebpackFrameArtifacts(frame);
|
|
741
1093
|
if (webpackArtifacts?.sourceMap) {
|
|
742
1094
|
const applied = await applySourceMapToFrame(frame, webpackArtifacts.sourceMap);
|
|
743
1095
|
if (applied) return;
|
|
744
1096
|
}
|
|
745
1097
|
|
|
746
|
-
if (webpackArtifacts?.lines) {
|
|
747
|
-
|
|
1098
|
+
if (webpackArtifacts?.lines && !webpackArtifacts?.sourceMap) {
|
|
1099
|
+
const lineno = resolveFrameLine(frame, webpackArtifacts.lines);
|
|
1100
|
+
applySourceContext(frame, webpackArtifacts.lines, lineno);
|
|
748
1101
|
if (frame.context_line) return;
|
|
749
1102
|
}
|
|
750
1103
|
|
|
751
1104
|
const lines = await fetchBrowserSourceLines(frame.abs_path, frame.raw_abs_path);
|
|
752
1105
|
if (lines) {
|
|
753
|
-
|
|
1106
|
+
const lineno = resolveFrameLine(frame, lines);
|
|
1107
|
+
applySourceContext(frame, lines, lineno);
|
|
754
1108
|
if (frame.context_line) return;
|
|
755
1109
|
}
|
|
756
1110
|
|