bippy 0.7.2 → 0.7.3-dev.04de4f7
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/README.md +12 -79
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +59 -9
- package/dist/errors.d.ts +59 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +34 -58
- package/dist/index.d.ts +36 -60
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.js +1 -1
- package/dist/rdt-hook.cjs +1 -1
- package/dist/rdt-hook.js +1 -1
- package/dist/source.cjs +11 -11
- package/dist/source.d.cts +47 -18
- package/dist/source.d.ts +47 -18
- package/dist/source.js +12 -12
- package/package.json +7 -35
- package/src/call-listener.ts +13 -0
- package/src/core.ts +367 -282
- package/src/rdt-hook.ts +159 -87
- package/src/react-internals/current-fiber.ts +84 -0
- package/src/react-internals/generated/react-work-tags.ts +3 -0
- package/src/react-internals/index.ts +38 -11
- package/src/react-internals/types.ts +33 -6
- package/src/react.ts +141 -55
- package/src/source/constants.ts +11 -9
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +42 -157
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- package/src/source/normalize-file-name.ts +416 -0
- package/src/source/owner-stack.ts +43 -35
- package/src/source/parse-debug-stack.ts +1 -1
- package/src/source/parse-hook-names.ts +15 -8
- package/src/source/parse-stack.ts +114 -64
- package/src/source/renderer-dispatchers.ts +9 -4
- package/src/source/symbolication.ts +131 -70
|
@@ -28,14 +28,14 @@ export const parseStack = (stackString: string, options?: ParseOptions): StackFr
|
|
|
28
28
|
const frames: StackFrame[] = [];
|
|
29
29
|
for (const rawLine of lines) {
|
|
30
30
|
if (/^\s*at\s+/.test(rawLine)) {
|
|
31
|
-
|
|
32
|
-
if (parsed) frames.push(parsed);
|
|
31
|
+
if (CHROME_IE_STACK_REGEXP.test(rawLine)) frames.push(parseV8Line(rawLine));
|
|
33
32
|
} else if (/^\s*in\s+/.test(rawLine)) {
|
|
34
|
-
const elementName = rawLine
|
|
33
|
+
const elementName = rawLine
|
|
34
|
+
.replace(/^\s*in\s+/, "")
|
|
35
|
+
.replace(/\s*(?:\(at .*\)|\[[^\]]+\])$/, "");
|
|
35
36
|
frames.push({ functionName: elementName, source: rawLine });
|
|
36
37
|
} else if (rawLine.match(FIREFOX_SAFARI_STACK_REGEXP)) {
|
|
37
|
-
|
|
38
|
-
if (parsed) frames.push(parsed);
|
|
38
|
+
if (!SAFARI_NATIVE_CODE_REGEXP.test(rawLine)) frames.push(parseSafariLine(rawLine));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
return frames;
|
|
@@ -46,6 +46,18 @@ export const parseStack = (stackString: string, options?: ParseOptions): StackFr
|
|
|
46
46
|
return parseFFOrSafariString(stackString);
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
const getPositionIndex = (location: string, endIndex: number): number => {
|
|
50
|
+
let positionIndex = endIndex - 1;
|
|
51
|
+
while (positionIndex >= 0) {
|
|
52
|
+
const character = location.charCodeAt(positionIndex);
|
|
53
|
+
if (character < 48 || character > 57) break;
|
|
54
|
+
positionIndex--;
|
|
55
|
+
}
|
|
56
|
+
return positionIndex < endIndex - 1 && location.charCodeAt(positionIndex) === 58
|
|
57
|
+
? positionIndex
|
|
58
|
+
: -1;
|
|
59
|
+
};
|
|
60
|
+
|
|
49
61
|
export const extractLocation = (
|
|
50
62
|
urlLike: string,
|
|
51
63
|
): [string, string | undefined, string | undefined] => {
|
|
@@ -57,77 +69,115 @@ export const extractLocation = (
|
|
|
57
69
|
const isWrappedLocation = urlLike.startsWith("(") && /:\d+\)$/.test(urlLike);
|
|
58
70
|
const sanitizedResult = isWrappedLocation ? urlLike.slice(1, -1) : urlLike;
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
72
|
+
if (/[\n\r\u2028\u2029]/.test(sanitizedResult)) {
|
|
73
|
+
const parts = /(.+?)(?::(\d+))?(?::(\d+))?$/.exec(sanitizedResult);
|
|
74
|
+
return parts
|
|
75
|
+
? [parts[1], parts[2] || undefined, parts[3] || undefined]
|
|
76
|
+
: [sanitizedResult, undefined, undefined];
|
|
77
|
+
}
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const lastPositionIndex = getPositionIndex(sanitizedResult, sanitizedResult.length);
|
|
80
|
+
if (lastPositionIndex <= 0) return [sanitizedResult, undefined, undefined];
|
|
81
|
+
const previousPositionIndex = getPositionIndex(sanitizedResult, lastPositionIndex);
|
|
82
|
+
if (previousPositionIndex <= 0) {
|
|
83
|
+
return [
|
|
84
|
+
sanitizedResult.slice(0, lastPositionIndex),
|
|
85
|
+
sanitizedResult.slice(lastPositionIndex + 1),
|
|
86
|
+
undefined,
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
return [
|
|
90
|
+
sanitizedResult.slice(0, previousPositionIndex),
|
|
91
|
+
sanitizedResult.slice(previousPositionIndex + 1, lastPositionIndex),
|
|
92
|
+
sanitizedResult.slice(lastPositionIndex + 1),
|
|
93
|
+
];
|
|
94
|
+
};
|
|
82
95
|
|
|
83
|
-
|
|
96
|
+
const parseV8Line = (line: string): StackFrame => {
|
|
97
|
+
let currentLine = line;
|
|
98
|
+
if (currentLine.includes("(eval ")) {
|
|
99
|
+
currentLine = currentLine
|
|
100
|
+
.replace(/eval code/g, "eval")
|
|
101
|
+
.replace(/(\(eval at [^()]*)|(,.*$)/g, "");
|
|
102
|
+
}
|
|
103
|
+
let sanitizedLine = currentLine
|
|
104
|
+
.replace(/^\s+/, "")
|
|
105
|
+
.replace(/\(eval code/g, "(")
|
|
106
|
+
.replace(/^.*?\s+/, "");
|
|
107
|
+
|
|
108
|
+
const locationMatch = sanitizedLine.match(/ (\(.+\)$)/);
|
|
109
|
+
|
|
110
|
+
sanitizedLine = locationMatch ? sanitizedLine.replace(locationMatch[0], "") : sanitizedLine;
|
|
111
|
+
|
|
112
|
+
const locationParts = extractLocation(locationMatch ? locationMatch[1] : sanitizedLine);
|
|
113
|
+
const functionName = (locationMatch && sanitizedLine) || undefined;
|
|
114
|
+
const fileName = ["eval", "<anonymous>", "(native)"].includes(locationParts[0])
|
|
115
|
+
? undefined
|
|
116
|
+
: locationParts[0];
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
functionName,
|
|
120
|
+
fileName,
|
|
121
|
+
lineNumber: locationParts[1] ? +locationParts[1] : undefined,
|
|
122
|
+
columnNumber: locationParts[2] ? +locationParts[2] : undefined,
|
|
123
|
+
source: currentLine,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
84
126
|
|
|
85
|
-
|
|
127
|
+
const parseSafariLine = (line: string): StackFrame => {
|
|
128
|
+
let currentLine = line;
|
|
129
|
+
if (currentLine.includes(" > eval"))
|
|
130
|
+
currentLine = currentLine.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ":$1");
|
|
86
131
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
132
|
+
if (!currentLine.includes("@") && !currentLine.includes(":")) {
|
|
133
|
+
return {
|
|
134
|
+
functionName: currentLine,
|
|
135
|
+
};
|
|
136
|
+
} else {
|
|
137
|
+
const functionNameRegex =
|
|
138
|
+
/(([^\n\r"\u2028\u2029]*".[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*(?:@[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*)*(?:[\n\r\u2028\u2029][^@]*)?)?[^@]*)@/;
|
|
139
|
+
const matches = currentLine.match(functionNameRegex);
|
|
140
|
+
const functionName = matches && matches[1] ? matches[1] : undefined;
|
|
141
|
+
const locationParts = extractLocation(currentLine.replace(functionNameRegex, ""));
|
|
92
142
|
|
|
93
143
|
return {
|
|
94
144
|
functionName,
|
|
95
|
-
fileName,
|
|
145
|
+
fileName: locationParts[0],
|
|
96
146
|
lineNumber: locationParts[1] ? +locationParts[1] : undefined,
|
|
97
147
|
columnNumber: locationParts[2] ? +locationParts[2] : undefined,
|
|
98
148
|
source: currentLine,
|
|
99
149
|
};
|
|
100
|
-
}
|
|
150
|
+
}
|
|
101
151
|
};
|
|
102
152
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
117
|
-
} else {
|
|
118
|
-
const functionNameRegex =
|
|
119
|
-
/(([^\n\r"\u2028\u2029]*".[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*(?:@[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*)*(?:[\n\r\u2028\u2029][^@]*)?)?[^@]*)@/;
|
|
120
|
-
const matches = currentLine.match(functionNameRegex);
|
|
121
|
-
const functionName = matches && matches[1] ? matches[1] : undefined;
|
|
122
|
-
const locationParts = extractLocation(currentLine.replace(functionNameRegex, ""));
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
functionName,
|
|
126
|
-
fileName: locationParts[0],
|
|
127
|
-
lineNumber: locationParts[1] ? +locationParts[1] : undefined,
|
|
128
|
-
columnNumber: locationParts[2] ? +locationParts[2] : undefined,
|
|
129
|
-
source: currentLine,
|
|
130
|
-
};
|
|
153
|
+
const parseLines = (
|
|
154
|
+
stack: string,
|
|
155
|
+
isV8: boolean,
|
|
156
|
+
cache?: Map<string, StackFrame>,
|
|
157
|
+
): StackFrame[] => {
|
|
158
|
+
const frames: StackFrame[] = [];
|
|
159
|
+
for (const line of stack.split("\n")) {
|
|
160
|
+
let frame = cache?.get(line);
|
|
161
|
+
if (!frame) {
|
|
162
|
+
if (isV8 ? !CHROME_IE_STACK_REGEXP.test(line) : SAFARI_NATIVE_CODE_REGEXP.test(line))
|
|
163
|
+
continue;
|
|
164
|
+
frame = isV8 ? parseV8Line(line) : parseSafariLine(line);
|
|
165
|
+
cache?.set(line, frame);
|
|
131
166
|
}
|
|
132
|
-
|
|
167
|
+
frames.push(frame);
|
|
168
|
+
}
|
|
169
|
+
return frames;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const parseV8OrIeString = (stack: string): StackFrame[] => parseLines(stack, true);
|
|
173
|
+
|
|
174
|
+
export const parseFFOrSafariString = (stack: string): StackFrame[] => parseLines(stack, false);
|
|
175
|
+
|
|
176
|
+
export const createStackParser = () => {
|
|
177
|
+
const v8Frames = new Map<string, StackFrame>();
|
|
178
|
+
const safariFrames = new Map<string, StackFrame>();
|
|
179
|
+
return (stack: string): StackFrame[] => {
|
|
180
|
+
const isV8 = CHROME_IE_STACK_REGEXP.test(stack);
|
|
181
|
+
return parseLines(stack, isV8, isV8 ? v8Frames : safariFrames);
|
|
182
|
+
};
|
|
133
183
|
};
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { RendererDispatcherRef } from "../react-internals/index.js";
|
|
2
|
-
import { _renderers, getRDTHook } from "../rdt-hook.js";
|
|
2
|
+
import { _renderers, getRDTHook, isRendererMap } from "../rdt-hook.js";
|
|
3
|
+
import type { ReactDevToolsTarget } from "../rdt-hook.js";
|
|
3
4
|
|
|
4
|
-
export const getRendererDispatcherRefs = (
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export const getRendererDispatcherRefs = (
|
|
6
|
+
target: ReactDevToolsTarget = globalThis,
|
|
7
|
+
): RendererDispatcherRef[] => {
|
|
8
|
+
const rdtHook = getRDTHook(undefined, target);
|
|
9
|
+
const targetRenderers = isRendererMap(rdtHook.renderers) ? rdtHook.renderers.values() : [];
|
|
10
|
+
const renderers =
|
|
11
|
+
target === globalThis ? new Set([..._renderers, ...targetRenderers]) : new Set(targetRenderers);
|
|
7
12
|
const currentDispatcherRefs: RendererDispatcherRef[] = [];
|
|
8
13
|
const seenCurrentDispatcherRefs = new Set<object>();
|
|
9
14
|
for (const renderer of renderers) {
|
|
@@ -2,6 +2,7 @@ import { decode, type SourceMapMappings, type SourceMapSegment } from "@jridgewe
|
|
|
2
2
|
|
|
3
3
|
import { BippySourceMapError } from "../errors.js";
|
|
4
4
|
import { SCHEME_REGEX } from "./constants.js";
|
|
5
|
+
import { normalizeFileName } from "./normalize-file-name.js";
|
|
5
6
|
import type { StackFrame } from "./parse-stack.js";
|
|
6
7
|
|
|
7
8
|
export interface DecodedSourceMapSection {
|
|
@@ -39,6 +40,7 @@ export interface SourceFetch {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export interface SourceMapRequestOptions {
|
|
43
|
+
allowCrossOriginSourceMap?: boolean;
|
|
42
44
|
allowUnsafeServerFetch?: boolean;
|
|
43
45
|
maxBundleSizeBytes?: number;
|
|
44
46
|
maxSourceMapSizeBytes?: number;
|
|
@@ -49,6 +51,7 @@ export interface SourceMapRequestOptions {
|
|
|
49
51
|
export interface SourceMap {
|
|
50
52
|
file?: string;
|
|
51
53
|
ignoredSourceIndices?: Set<number>;
|
|
54
|
+
sourceMapUrl?: string;
|
|
52
55
|
mappings: SourceMapSegment[][];
|
|
53
56
|
names?: string[];
|
|
54
57
|
sections?: DecodedSourceMapSection[];
|
|
@@ -205,6 +208,13 @@ export const getSourceFromSourceMap = (
|
|
|
205
208
|
);
|
|
206
209
|
};
|
|
207
210
|
|
|
211
|
+
const getStringIndex = (values: string[], target: string): number => {
|
|
212
|
+
for (let valueIndex = 0; valueIndex < values.length; valueIndex++) {
|
|
213
|
+
if (values[valueIndex] === target) return valueIndex;
|
|
214
|
+
}
|
|
215
|
+
return -1;
|
|
216
|
+
};
|
|
217
|
+
|
|
208
218
|
const getSourceFromMappingsByFunctionName = (
|
|
209
219
|
mappings: SourceMapMappings,
|
|
210
220
|
sources: string[],
|
|
@@ -213,13 +223,17 @@ const getSourceFromMappingsByFunctionName = (
|
|
|
213
223
|
ignoredSourceIndices?: Set<number>,
|
|
214
224
|
): StackFrame | null => {
|
|
215
225
|
if (!names) return null;
|
|
216
|
-
const functionNameIndex = names
|
|
226
|
+
const functionNameIndex = getStringIndex(names, functionName);
|
|
217
227
|
if (functionNameIndex === -1) return null;
|
|
218
228
|
|
|
219
229
|
let ignoredSource: StackFrame | null = null;
|
|
220
|
-
for (
|
|
221
|
-
|
|
230
|
+
for (let lineIndex = 0; lineIndex < mappings.length; lineIndex++) {
|
|
231
|
+
const lineMapping = mappings[lineIndex];
|
|
232
|
+
for (let segmentIndex = 0; segmentIndex < lineMapping.length; segmentIndex++) {
|
|
233
|
+
const segment = lineMapping[segmentIndex];
|
|
222
234
|
if (segment[4] !== functionNameIndex) continue;
|
|
235
|
+
if (ignoredSource && segment[1] !== undefined && ignoredSourceIndices?.has(segment[1]))
|
|
236
|
+
continue;
|
|
223
237
|
const source = getSourceFromSegment(segment, sources, ignoredSourceIndices, names);
|
|
224
238
|
if (!source) continue;
|
|
225
239
|
if (!source.isIgnoreListed) return source;
|
|
@@ -259,37 +273,40 @@ export const getSourceFromSourceMapByFunctionName = (
|
|
|
259
273
|
);
|
|
260
274
|
};
|
|
261
275
|
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
fileName: string,
|
|
266
|
-
): string | null => {
|
|
267
|
-
if (!sourcesContent) return null;
|
|
268
|
-
const sourceIndex = sources.indexOf(fileName);
|
|
269
|
-
return sourceIndex === -1 ? null : (sourcesContent[sourceIndex] ?? null);
|
|
276
|
+
const canonicalSourcePath = (fileName: string): string => {
|
|
277
|
+
const normalizedFileName = normalizeFileName(fileName);
|
|
278
|
+
return normalizedFileName.startsWith("./") ? normalizedFileName.slice(2) : normalizedFileName;
|
|
270
279
|
};
|
|
271
280
|
|
|
272
281
|
export const getSourceContentFromSourceMap = (
|
|
273
282
|
sourceMap: SourceMap,
|
|
274
283
|
originalFileName: string,
|
|
275
284
|
): string | null => {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
originalFileName
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
for (const section of sourceMap.sections) {
|
|
285
|
-
const sectionSourceContent = findSourceContentByFileName(
|
|
286
|
-
section.map.sources,
|
|
287
|
-
section.map.sourcesContent,
|
|
288
|
-
originalFileName,
|
|
289
|
-
);
|
|
290
|
-
if (sectionSourceContent !== null) return sectionSourceContent;
|
|
285
|
+
const maps = [sourceMap, ...(sourceMap.sections?.map((section) => section.map) ?? [])];
|
|
286
|
+
let hasExactSource = false;
|
|
287
|
+
for (const map of maps) {
|
|
288
|
+
const sourceIndex = getStringIndex(map.sources, originalFileName);
|
|
289
|
+
if (sourceIndex === -1) continue;
|
|
290
|
+
hasExactSource = true;
|
|
291
|
+
const sourceContent = map.sourcesContent?.[sourceIndex];
|
|
292
|
+
if (typeof sourceContent === "string") return sourceContent;
|
|
291
293
|
}
|
|
292
|
-
return null;
|
|
294
|
+
if (hasExactSource) return null;
|
|
295
|
+
|
|
296
|
+
const canonicalFileName = canonicalSourcePath(originalFileName);
|
|
297
|
+
if (!canonicalFileName) return null;
|
|
298
|
+
let matchedFileName: string | undefined;
|
|
299
|
+
let matchedContent: string | null = null;
|
|
300
|
+
for (const map of maps) {
|
|
301
|
+
for (let index = 0; index < map.sources.length; index++) {
|
|
302
|
+
const fileName = map.sources[index];
|
|
303
|
+
if (!fileName || canonicalSourcePath(fileName) !== canonicalFileName) continue;
|
|
304
|
+
if (matchedFileName !== undefined && matchedFileName !== fileName) return null;
|
|
305
|
+
matchedFileName = fileName;
|
|
306
|
+
matchedContent ??= map.sourcesContent?.[index] ?? null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return matchedContent;
|
|
293
310
|
};
|
|
294
311
|
|
|
295
312
|
const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
@@ -298,10 +315,14 @@ const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
|
298
315
|
return new URL(reference, baseUrl).toString();
|
|
299
316
|
} catch {
|
|
300
317
|
try {
|
|
301
|
-
|
|
302
|
-
return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
|
|
318
|
+
return new URL(reference).toString();
|
|
303
319
|
} catch {
|
|
304
|
-
|
|
320
|
+
try {
|
|
321
|
+
const resolvedUrl = new URL(reference, new URL(baseUrl, "https://bippy.invalid/"));
|
|
322
|
+
return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
|
|
323
|
+
} catch {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
305
326
|
}
|
|
306
327
|
}
|
|
307
328
|
};
|
|
@@ -428,12 +449,46 @@ const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> |
|
|
|
428
449
|
return ignoreList?.length ? new Set(ignoreList) : undefined;
|
|
429
450
|
};
|
|
430
451
|
|
|
452
|
+
const isAbsoluteSourcePath = (source: string): boolean =>
|
|
453
|
+
source.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(source) || SCHEME_REGEX.test(source);
|
|
454
|
+
|
|
455
|
+
const resolveRelativeSource = (source: string, sourceMapUrl: string): string => {
|
|
456
|
+
if (!source.startsWith("./") && !source.startsWith("../")) return source;
|
|
457
|
+
if (INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) return source;
|
|
458
|
+
return resolveUrl(source, sourceMapUrl) ?? source;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const normalizeJoinedSourcePath = (joinedSource: string): string => {
|
|
462
|
+
const pathSegments = joinedSource.split("/");
|
|
463
|
+
const normalizedPathSegments: string[] = [];
|
|
464
|
+
for (const pathSegment of pathSegments) {
|
|
465
|
+
if (pathSegment === "" || pathSegment === ".") {
|
|
466
|
+
if (pathSegment === "" && normalizedPathSegments.length === 0) {
|
|
467
|
+
normalizedPathSegments.push("");
|
|
468
|
+
}
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
if (pathSegment === "..") {
|
|
472
|
+
const previousSegment = normalizedPathSegments[normalizedPathSegments.length - 1];
|
|
473
|
+
if (previousSegment && previousSegment !== ".." && previousSegment !== "") {
|
|
474
|
+
normalizedPathSegments.pop();
|
|
475
|
+
} else {
|
|
476
|
+
normalizedPathSegments.push("..");
|
|
477
|
+
}
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
normalizedPathSegments.push(pathSegment);
|
|
481
|
+
}
|
|
482
|
+
return normalizedPathSegments.join("/");
|
|
483
|
+
};
|
|
484
|
+
|
|
431
485
|
const resolveSourceRoot = (
|
|
432
486
|
sourceRoot: string | undefined,
|
|
433
487
|
source: string,
|
|
434
488
|
sourceMapUrl: string,
|
|
435
489
|
): string => {
|
|
436
|
-
if (
|
|
490
|
+
if (isAbsoluteSourcePath(source)) return source;
|
|
491
|
+
if (!sourceRoot) return resolveRelativeSource(source, sourceMapUrl);
|
|
437
492
|
const normalizedSourceRoot = sourceRoot.endsWith("/") ? sourceRoot : `${sourceRoot}/`;
|
|
438
493
|
const normalizedSource = source.replace(/^\.\//, "");
|
|
439
494
|
try {
|
|
@@ -442,16 +497,7 @@ const resolveSourceRoot = (
|
|
|
442
497
|
: new URL(normalizedSourceRoot, sourceMapUrl).toString();
|
|
443
498
|
return new URL(normalizedSource, baseUrl).toString();
|
|
444
499
|
} catch {
|
|
445
|
-
|
|
446
|
-
const normalizedPathSegments: string[] = [];
|
|
447
|
-
for (const pathSegment of pathSegments) {
|
|
448
|
-
if (pathSegment === "..") {
|
|
449
|
-
normalizedPathSegments.pop();
|
|
450
|
-
} else if (pathSegment !== ".") {
|
|
451
|
-
normalizedPathSegments.push(pathSegment);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return normalizedPathSegments.join("/");
|
|
500
|
+
return normalizeJoinedSourcePath(`${normalizedSourceRoot}${normalizedSource}`);
|
|
455
501
|
}
|
|
456
502
|
};
|
|
457
503
|
|
|
@@ -460,6 +506,11 @@ const resolveSourceMapSources = (rawSourceMap: StandardSourceMap, sourceMapUrl:
|
|
|
460
506
|
resolveSourceRoot(rawSourceMap.sourceRoot, source, sourceMapUrl),
|
|
461
507
|
);
|
|
462
508
|
|
|
509
|
+
// An inline URL carries the whole encoded map, so retaining it in the cache would
|
|
510
|
+
// double the memory held per source map for the lifetime of the process.
|
|
511
|
+
const getRetainableSourceMapUrl = (sourceMapUrl: string): string | undefined =>
|
|
512
|
+
INLINE_SOURCEMAP_REGEX.test(sourceMapUrl) ? undefined : sourceMapUrl;
|
|
513
|
+
|
|
463
514
|
const decodeStandardSourceMap = (
|
|
464
515
|
rawSourceMap: StandardSourceMap,
|
|
465
516
|
sourceMapUrl: string,
|
|
@@ -470,6 +521,7 @@ const decodeStandardSourceMap = (
|
|
|
470
521
|
ignoredSourceIndices: getIgnoredSourceIndices(rawSourceMap),
|
|
471
522
|
mappings: decode(rawSourceMap.mappings),
|
|
472
523
|
names: rawSourceMap.names,
|
|
524
|
+
sourceMapUrl: getRetainableSourceMapUrl(sourceMapUrl),
|
|
473
525
|
sourceRoot: rawSourceMap.sourceRoot,
|
|
474
526
|
sources: resolveSourceMapSources(rawSourceMap, sourceMapUrl),
|
|
475
527
|
sourcesContent: rawSourceMap.sourcesContent,
|
|
@@ -532,6 +584,7 @@ const decodeIndexSourceMap = async (
|
|
|
532
584
|
mappings: [],
|
|
533
585
|
names: [],
|
|
534
586
|
sections: decodedSections,
|
|
587
|
+
sourceMapUrl: getRetainableSourceMapUrl(sourceMapUrl),
|
|
535
588
|
sourceRoot: undefined,
|
|
536
589
|
sources: Array.from(allSources),
|
|
537
590
|
sourcesContent: undefined,
|
|
@@ -782,22 +835,23 @@ const readSourceMapDocument = async (
|
|
|
782
835
|
|
|
783
836
|
const getSourceMapUncachedInternal = async (
|
|
784
837
|
bundleUrl: string,
|
|
785
|
-
|
|
838
|
+
sourceFetchOverride?: SourceFetch,
|
|
786
839
|
options: SourceMapRequestOptions = {},
|
|
787
840
|
): Promise<null | SourceMap> => {
|
|
788
|
-
const shouldAllowCustomProtocol =
|
|
841
|
+
const shouldAllowCustomProtocol = sourceFetchOverride !== undefined;
|
|
789
842
|
if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
|
|
790
843
|
return null;
|
|
791
844
|
}
|
|
792
845
|
|
|
793
846
|
const isServer = isServerRuntime();
|
|
794
|
-
const shouldRejectRedirects =
|
|
795
|
-
|
|
847
|
+
const shouldRejectRedirects =
|
|
848
|
+
(isServer || isExtensionUrl(bundleUrl)) && !options.allowCrossOriginSourceMap;
|
|
849
|
+
if (isServer && !sourceFetchOverride) {
|
|
796
850
|
if (!options.allowUnsafeServerFetch || !isAbsoluteHttpUrl(bundleUrl)) return null;
|
|
797
851
|
}
|
|
798
852
|
|
|
799
853
|
const sourceFetch =
|
|
800
|
-
|
|
854
|
+
sourceFetchOverride ??
|
|
801
855
|
(typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined);
|
|
802
856
|
if (!sourceFetch) return null;
|
|
803
857
|
const maxBundleSizeBytes = options.maxBundleSizeBytes ?? defaultMaxBundleSizeBytes;
|
|
@@ -864,11 +918,11 @@ const getSourceMapUncachedInternal = async (
|
|
|
864
918
|
|
|
865
919
|
export const getSourceMapUncached = async (
|
|
866
920
|
bundleUrl: string,
|
|
867
|
-
|
|
921
|
+
sourceFetch?: SourceFetch,
|
|
868
922
|
options: SourceMapRequestOptions = {},
|
|
869
923
|
): Promise<null | SourceMap> => {
|
|
870
924
|
try {
|
|
871
|
-
return await getSourceMapUncachedInternal(bundleUrl,
|
|
925
|
+
return await getSourceMapUncachedInternal(bundleUrl, sourceFetch, options);
|
|
872
926
|
} catch (error) {
|
|
873
927
|
if (error instanceof TransientSourceMapError) return null;
|
|
874
928
|
throw error;
|
|
@@ -878,66 +932,67 @@ export const getSourceMapUncached = async (
|
|
|
878
932
|
const getPerFetchMap = <Value>(
|
|
879
933
|
mapsByFetch: WeakMap<SourceFetch, Map<string, Value>>,
|
|
880
934
|
globalMap: Map<string, Value>,
|
|
881
|
-
|
|
935
|
+
sourceFetch: SourceFetch | undefined,
|
|
882
936
|
): Map<string, Value> => {
|
|
883
|
-
if (!
|
|
884
|
-
let map = mapsByFetch.get(
|
|
937
|
+
if (!sourceFetch) return globalMap;
|
|
938
|
+
let map = mapsByFetch.get(sourceFetch);
|
|
885
939
|
if (!map) {
|
|
886
940
|
map = new Map();
|
|
887
|
-
mapsByFetch.set(
|
|
941
|
+
mapsByFetch.set(sourceFetch, map);
|
|
888
942
|
}
|
|
889
943
|
return map;
|
|
890
944
|
};
|
|
891
945
|
|
|
892
|
-
const getSourceMapCache = (
|
|
893
|
-
getPerFetchMap(sourceMapCachesByFetch, sourceMapCache,
|
|
946
|
+
const getSourceMapCache = (sourceFetch: SourceFetch | undefined): Map<string, null | SourceMap> =>
|
|
947
|
+
getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, sourceFetch);
|
|
894
948
|
|
|
895
949
|
const getPendingSourceMapRequests = (
|
|
896
|
-
|
|
950
|
+
sourceFetch: SourceFetch | undefined,
|
|
897
951
|
): Map<string, Promise<SourceMapResult>> =>
|
|
898
|
-
getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests,
|
|
952
|
+
getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, sourceFetch);
|
|
899
953
|
|
|
900
954
|
const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
|
|
955
|
+
options.allowCrossOriginSourceMap === undefined &&
|
|
901
956
|
options.allowUnsafeServerFetch === undefined &&
|
|
902
957
|
options.maxBundleSizeBytes === undefined &&
|
|
903
958
|
options.maxSourceMapSizeBytes === undefined &&
|
|
904
959
|
options.timeoutMs === undefined
|
|
905
960
|
? file
|
|
906
|
-
: `${file}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
961
|
+
: `${file}\0${options.allowCrossOriginSourceMap ?? ""}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
907
962
|
|
|
908
963
|
export const getSourceMap = async (
|
|
909
964
|
file: string,
|
|
910
|
-
|
|
911
|
-
|
|
965
|
+
shouldUseCache = true,
|
|
966
|
+
sourceFetch?: SourceFetch,
|
|
912
967
|
options: SourceMapRequestOptions = {},
|
|
913
968
|
): Promise<null | SourceMap> => {
|
|
914
|
-
const
|
|
915
|
-
const cache = getSourceMapCache(
|
|
916
|
-
const pendingRequests = getPendingSourceMapRequests(
|
|
969
|
+
const canUseCache = shouldUseCache && options.signal === undefined;
|
|
970
|
+
const cache = getSourceMapCache(sourceFetch);
|
|
971
|
+
const pendingRequests = getPendingSourceMapRequests(sourceFetch);
|
|
917
972
|
const cacheKey = getSourceMapCacheKey(file, options);
|
|
918
|
-
if (
|
|
973
|
+
if (canUseCache && cache.has(cacheKey)) {
|
|
919
974
|
return cache.get(cacheKey) ?? null;
|
|
920
975
|
}
|
|
921
976
|
|
|
922
|
-
const pendingRequest =
|
|
977
|
+
const pendingRequest = canUseCache ? pendingRequests.get(cacheKey) : undefined;
|
|
923
978
|
if (pendingRequest) {
|
|
924
979
|
return (await pendingRequest).sourceMap;
|
|
925
980
|
}
|
|
926
981
|
|
|
927
982
|
const fetchPromise: Promise<SourceMapResult> = getSourceMapUncachedInternal(
|
|
928
983
|
file,
|
|
929
|
-
|
|
984
|
+
sourceFetch,
|
|
930
985
|
options,
|
|
931
986
|
).then(
|
|
932
987
|
(sourceMap) => ({ sourceMap, isTransientFailure: false }),
|
|
933
988
|
() => ({ sourceMap: null, isTransientFailure: true }),
|
|
934
989
|
);
|
|
935
|
-
if (
|
|
990
|
+
if (canUseCache) {
|
|
936
991
|
pendingRequests.set(cacheKey, fetchPromise);
|
|
937
992
|
}
|
|
938
993
|
|
|
939
994
|
const { sourceMap, isTransientFailure } = await fetchPromise;
|
|
940
|
-
if (
|
|
995
|
+
if (canUseCache) {
|
|
941
996
|
pendingRequests.delete(cacheKey);
|
|
942
997
|
if (!isTransientFailure) {
|
|
943
998
|
cache.set(cacheKey, sourceMap);
|
|
@@ -949,13 +1004,19 @@ export const getSourceMap = async (
|
|
|
949
1004
|
|
|
950
1005
|
export const symbolicateStack = async (
|
|
951
1006
|
stack: StackFrame[],
|
|
952
|
-
|
|
953
|
-
|
|
1007
|
+
shouldUseCache = true,
|
|
1008
|
+
sourceFetch?: SourceFetch,
|
|
1009
|
+
requestOptions: SourceMapRequestOptions = {},
|
|
954
1010
|
): Promise<StackFrame[]> => {
|
|
955
1011
|
return Promise.all(
|
|
956
1012
|
stack.map(async (stackFrame) => {
|
|
957
1013
|
if (!stackFrame.fileName) return stackFrame;
|
|
958
|
-
const sourceMap = await getSourceMap(
|
|
1014
|
+
const sourceMap = await getSourceMap(
|
|
1015
|
+
stackFrame.fileName,
|
|
1016
|
+
shouldUseCache,
|
|
1017
|
+
sourceFetch,
|
|
1018
|
+
requestOptions,
|
|
1019
|
+
);
|
|
959
1020
|
if (
|
|
960
1021
|
!sourceMap ||
|
|
961
1022
|
typeof stackFrame.lineNumber !== "number" ||
|