bippy 0.7.2 → 0.7.3-dev.0ac832d
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/LICENSE +1 -0
- package/README.md +9 -76
- package/dist/core.cjs +1 -1
- package/dist/core.js +1 -1
- package/dist/errors.d.cts +56 -9
- package/dist/errors.d.ts +56 -9
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -38
- package/dist/index.d.ts +18 -42
- 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 +36 -9
- package/dist/source.d.ts +36 -9
- package/dist/source.js +11 -11
- package/package.json +5 -33
- package/src/call-listener.ts +13 -0
- package/src/core.ts +287 -258
- package/src/rdt-hook.ts +130 -71
- package/src/react-internals/current-fiber.ts +84 -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 +1 -3
- package/src/source/get-display-name-from-source.ts +10 -3
- package/src/source/get-source.ts +39 -19
- package/src/source/index.ts +17 -3
- package/src/source/inspect-hooks.ts +198 -49
- 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 +68 -36
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { JSX_FACTORY_FRAME_COUNT, REACT_STACK_BOTTOM_FRAME_PATTERNS } from "./constants.js";
|
|
2
2
|
import { getPrepareStackTrace, setPrepareStackTrace } from "./error-stack.js";
|
|
3
|
-
import { parseStack, StackFrame } from "./parse-stack.js";
|
|
3
|
+
import { parseStack, type StackFrame } from "./parse-stack.js";
|
|
4
4
|
|
|
5
5
|
interface V8CallSite {
|
|
6
6
|
getFunctionName?: () => string | null;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getSourceFromSourceMap,
|
|
5
5
|
getSourceMap,
|
|
6
6
|
type SourceFetch,
|
|
7
|
+
type SourceMapRequestOptions,
|
|
7
8
|
} from "./symbolication.js";
|
|
8
9
|
|
|
9
10
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
@@ -19,8 +20,10 @@ const UNNAMED_HOOKS = new Set([
|
|
|
19
20
|
|
|
20
21
|
// HACK: matches `const/let/var [name, ...] = use...(...` or `const/let/var name = use...(...`
|
|
21
22
|
// across up to 10 lines; handles TypeScript generics like `useState<T>(`
|
|
23
|
+
// The member-access prefix consumes one dotless segment per repetition. Letting a segment
|
|
24
|
+
// span dots too would make the parse ambiguous and backtrack exponentially on long chains.
|
|
22
25
|
const HOOK_DECLARATION_REGEX =
|
|
23
|
-
/(?:const|let|var)\s+((?:\[[\s\S]*?\]|\w+))\s*=\s*(?:[\w
|
|
26
|
+
/(?:const|let|var)\s+((?:\[[\s\S]*?\]|\w+))\s*=\s*(?:(?:[\w$]+|require\s*\([^)]*\))\.)*use[A-Z]\w*\s*(?:<[\s\S]*?>)?\s*\(/g;
|
|
24
27
|
|
|
25
28
|
export const getHookSourceLocationKey = (hookSource: HookSource): string =>
|
|
26
29
|
`${hookSource.fileName ?? ""}:${hookSource.lineNumber ?? 0}:${hookSource.columnNumber ?? 0}`;
|
|
@@ -63,12 +66,13 @@ export const extractHookVariableName = (
|
|
|
63
66
|
|
|
64
67
|
const allMatches = [...sourceChunk.matchAll(HOOK_DECLARATION_REGEX)];
|
|
65
68
|
|
|
66
|
-
const
|
|
67
|
-
const
|
|
69
|
+
const hookLineStart = sourceChunk.lastIndexOf("\n") + 1;
|
|
70
|
+
const hookPositionInChunk = hookLineStart + columnNumber;
|
|
71
|
+
const closestMatch =
|
|
72
|
+
allMatches.filter((match) => match.index! <= hookPositionInChunk).at(-1) ??
|
|
73
|
+
(columnNumber === 0 ? allMatches.find((match) => match.index! >= hookLineStart) : undefined);
|
|
68
74
|
|
|
69
|
-
if (closestMatch)
|
|
70
|
-
return extractVariableNameFromBinding(closestMatch[1]);
|
|
71
|
-
}
|
|
75
|
+
if (closestMatch) return extractVariableNameFromBinding(closestMatch[1]);
|
|
72
76
|
|
|
73
77
|
return null;
|
|
74
78
|
};
|
|
@@ -82,6 +86,7 @@ interface ResolvedSource {
|
|
|
82
86
|
interface SourceResolutionContext {
|
|
83
87
|
sourceContentCache: Map<string, string | null>;
|
|
84
88
|
fetchFn?: SourceFetch;
|
|
89
|
+
requestOptions?: SourceMapRequestOptions;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
const resolveOriginalSource = async (
|
|
@@ -90,9 +95,9 @@ const resolveOriginalSource = async (
|
|
|
90
95
|
runtimeColumn: number,
|
|
91
96
|
context: SourceResolutionContext,
|
|
92
97
|
): Promise<ResolvedSource | null> => {
|
|
93
|
-
const { sourceContentCache, fetchFn } = context;
|
|
98
|
+
const { sourceContentCache, fetchFn, requestOptions } = context;
|
|
94
99
|
|
|
95
|
-
const sourceMap = await getSourceMap(runtimeFileName, true, fetchFn);
|
|
100
|
+
const sourceMap = await getSourceMap(runtimeFileName, true, fetchFn, requestOptions);
|
|
96
101
|
|
|
97
102
|
if (sourceMap) {
|
|
98
103
|
const originalLocation = getSourceFromSourceMap(sourceMap, runtimeLine, runtimeColumn);
|
|
@@ -136,6 +141,7 @@ const resolveOriginalSource = async (
|
|
|
136
141
|
export const parseHookNames = async (
|
|
137
142
|
hooksTree: HooksTree,
|
|
138
143
|
fetchFn?: SourceFetch,
|
|
144
|
+
requestOptions?: SourceMapRequestOptions,
|
|
139
145
|
): Promise<HookNames> => {
|
|
140
146
|
const hookNames: HookNames = new Map();
|
|
141
147
|
const hooksList = flattenHooksTree(hooksTree);
|
|
@@ -145,6 +151,7 @@ export const parseHookNames = async (
|
|
|
145
151
|
const resolutionContext: SourceResolutionContext = {
|
|
146
152
|
sourceContentCache: new Map(),
|
|
147
153
|
fetchFn,
|
|
154
|
+
requestOptions,
|
|
148
155
|
};
|
|
149
156
|
|
|
150
157
|
await Promise.all(
|
|
@@ -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) {
|
|
@@ -39,6 +39,7 @@ export interface SourceFetch {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface SourceMapRequestOptions {
|
|
42
|
+
allowCrossOriginSourceMap?: boolean;
|
|
42
43
|
allowUnsafeServerFetch?: boolean;
|
|
43
44
|
maxBundleSizeBytes?: number;
|
|
44
45
|
maxSourceMapSizeBytes?: number;
|
|
@@ -49,6 +50,7 @@ export interface SourceMapRequestOptions {
|
|
|
49
50
|
export interface SourceMap {
|
|
50
51
|
file?: string;
|
|
51
52
|
ignoredSourceIndices?: Set<number>;
|
|
53
|
+
sourceMapUrl?: string;
|
|
52
54
|
mappings: SourceMapSegment[][];
|
|
53
55
|
names?: string[];
|
|
54
56
|
sections?: DecodedSourceMapSection[];
|
|
@@ -205,6 +207,13 @@ export const getSourceFromSourceMap = (
|
|
|
205
207
|
);
|
|
206
208
|
};
|
|
207
209
|
|
|
210
|
+
const getStringIndex = (values: string[], target: string): number => {
|
|
211
|
+
for (let valueIndex = 0; valueIndex < values.length; valueIndex++) {
|
|
212
|
+
if (values[valueIndex] === target) return valueIndex;
|
|
213
|
+
}
|
|
214
|
+
return -1;
|
|
215
|
+
};
|
|
216
|
+
|
|
208
217
|
const getSourceFromMappingsByFunctionName = (
|
|
209
218
|
mappings: SourceMapMappings,
|
|
210
219
|
sources: string[],
|
|
@@ -213,13 +222,17 @@ const getSourceFromMappingsByFunctionName = (
|
|
|
213
222
|
ignoredSourceIndices?: Set<number>,
|
|
214
223
|
): StackFrame | null => {
|
|
215
224
|
if (!names) return null;
|
|
216
|
-
const functionNameIndex = names
|
|
225
|
+
const functionNameIndex = getStringIndex(names, functionName);
|
|
217
226
|
if (functionNameIndex === -1) return null;
|
|
218
227
|
|
|
219
228
|
let ignoredSource: StackFrame | null = null;
|
|
220
|
-
for (
|
|
221
|
-
|
|
229
|
+
for (let lineIndex = 0; lineIndex < mappings.length; lineIndex++) {
|
|
230
|
+
const lineMapping = mappings[lineIndex];
|
|
231
|
+
for (let segmentIndex = 0; segmentIndex < lineMapping.length; segmentIndex++) {
|
|
232
|
+
const segment = lineMapping[segmentIndex];
|
|
222
233
|
if (segment[4] !== functionNameIndex) continue;
|
|
234
|
+
if (ignoredSource && segment[1] !== undefined && ignoredSourceIndices?.has(segment[1]))
|
|
235
|
+
continue;
|
|
223
236
|
const source = getSourceFromSegment(segment, sources, ignoredSourceIndices, names);
|
|
224
237
|
if (!source) continue;
|
|
225
238
|
if (!source.isIgnoreListed) return source;
|
|
@@ -265,7 +278,7 @@ const findSourceContentByFileName = (
|
|
|
265
278
|
fileName: string,
|
|
266
279
|
): string | null => {
|
|
267
280
|
if (!sourcesContent) return null;
|
|
268
|
-
const sourceIndex = sources
|
|
281
|
+
const sourceIndex = getStringIndex(sources, fileName);
|
|
269
282
|
return sourceIndex === -1 ? null : (sourcesContent[sourceIndex] ?? null);
|
|
270
283
|
};
|
|
271
284
|
|
|
@@ -298,10 +311,14 @@ const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
|
298
311
|
return new URL(reference, baseUrl).toString();
|
|
299
312
|
} catch {
|
|
300
313
|
try {
|
|
301
|
-
|
|
302
|
-
return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
|
|
314
|
+
return new URL(reference).toString();
|
|
303
315
|
} catch {
|
|
304
|
-
|
|
316
|
+
try {
|
|
317
|
+
const resolvedUrl = new URL(reference, new URL(baseUrl, "https://bippy.invalid/"));
|
|
318
|
+
return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
|
|
319
|
+
} catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
305
322
|
}
|
|
306
323
|
}
|
|
307
324
|
};
|
|
@@ -460,6 +477,11 @@ const resolveSourceMapSources = (rawSourceMap: StandardSourceMap, sourceMapUrl:
|
|
|
460
477
|
resolveSourceRoot(rawSourceMap.sourceRoot, source, sourceMapUrl),
|
|
461
478
|
);
|
|
462
479
|
|
|
480
|
+
// An inline URL carries the whole encoded map, so retaining it in the cache would
|
|
481
|
+
// double the memory held per source map for the lifetime of the process.
|
|
482
|
+
const getRetainableSourceMapUrl = (sourceMapUrl: string): string | undefined =>
|
|
483
|
+
INLINE_SOURCEMAP_REGEX.test(sourceMapUrl) ? undefined : sourceMapUrl;
|
|
484
|
+
|
|
463
485
|
const decodeStandardSourceMap = (
|
|
464
486
|
rawSourceMap: StandardSourceMap,
|
|
465
487
|
sourceMapUrl: string,
|
|
@@ -470,6 +492,7 @@ const decodeStandardSourceMap = (
|
|
|
470
492
|
ignoredSourceIndices: getIgnoredSourceIndices(rawSourceMap),
|
|
471
493
|
mappings: decode(rawSourceMap.mappings),
|
|
472
494
|
names: rawSourceMap.names,
|
|
495
|
+
sourceMapUrl: getRetainableSourceMapUrl(sourceMapUrl),
|
|
473
496
|
sourceRoot: rawSourceMap.sourceRoot,
|
|
474
497
|
sources: resolveSourceMapSources(rawSourceMap, sourceMapUrl),
|
|
475
498
|
sourcesContent: rawSourceMap.sourcesContent,
|
|
@@ -532,6 +555,7 @@ const decodeIndexSourceMap = async (
|
|
|
532
555
|
mappings: [],
|
|
533
556
|
names: [],
|
|
534
557
|
sections: decodedSections,
|
|
558
|
+
sourceMapUrl: getRetainableSourceMapUrl(sourceMapUrl),
|
|
535
559
|
sourceRoot: undefined,
|
|
536
560
|
sources: Array.from(allSources),
|
|
537
561
|
sourcesContent: undefined,
|
|
@@ -782,22 +806,23 @@ const readSourceMapDocument = async (
|
|
|
782
806
|
|
|
783
807
|
const getSourceMapUncachedInternal = async (
|
|
784
808
|
bundleUrl: string,
|
|
785
|
-
|
|
809
|
+
sourceFetchOverride?: SourceFetch,
|
|
786
810
|
options: SourceMapRequestOptions = {},
|
|
787
811
|
): Promise<null | SourceMap> => {
|
|
788
|
-
const shouldAllowCustomProtocol =
|
|
812
|
+
const shouldAllowCustomProtocol = sourceFetchOverride !== undefined;
|
|
789
813
|
if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
|
|
790
814
|
return null;
|
|
791
815
|
}
|
|
792
816
|
|
|
793
817
|
const isServer = isServerRuntime();
|
|
794
|
-
const shouldRejectRedirects =
|
|
795
|
-
|
|
818
|
+
const shouldRejectRedirects =
|
|
819
|
+
(isServer || isExtensionUrl(bundleUrl)) && !options.allowCrossOriginSourceMap;
|
|
820
|
+
if (isServer && !sourceFetchOverride) {
|
|
796
821
|
if (!options.allowUnsafeServerFetch || !isAbsoluteHttpUrl(bundleUrl)) return null;
|
|
797
822
|
}
|
|
798
823
|
|
|
799
824
|
const sourceFetch =
|
|
800
|
-
|
|
825
|
+
sourceFetchOverride ??
|
|
801
826
|
(typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined);
|
|
802
827
|
if (!sourceFetch) return null;
|
|
803
828
|
const maxBundleSizeBytes = options.maxBundleSizeBytes ?? defaultMaxBundleSizeBytes;
|
|
@@ -864,11 +889,11 @@ const getSourceMapUncachedInternal = async (
|
|
|
864
889
|
|
|
865
890
|
export const getSourceMapUncached = async (
|
|
866
891
|
bundleUrl: string,
|
|
867
|
-
|
|
892
|
+
sourceFetch?: SourceFetch,
|
|
868
893
|
options: SourceMapRequestOptions = {},
|
|
869
894
|
): Promise<null | SourceMap> => {
|
|
870
895
|
try {
|
|
871
|
-
return await getSourceMapUncachedInternal(bundleUrl,
|
|
896
|
+
return await getSourceMapUncachedInternal(bundleUrl, sourceFetch, options);
|
|
872
897
|
} catch (error) {
|
|
873
898
|
if (error instanceof TransientSourceMapError) return null;
|
|
874
899
|
throw error;
|
|
@@ -878,66 +903,67 @@ export const getSourceMapUncached = async (
|
|
|
878
903
|
const getPerFetchMap = <Value>(
|
|
879
904
|
mapsByFetch: WeakMap<SourceFetch, Map<string, Value>>,
|
|
880
905
|
globalMap: Map<string, Value>,
|
|
881
|
-
|
|
906
|
+
sourceFetch: SourceFetch | undefined,
|
|
882
907
|
): Map<string, Value> => {
|
|
883
|
-
if (!
|
|
884
|
-
let map = mapsByFetch.get(
|
|
908
|
+
if (!sourceFetch) return globalMap;
|
|
909
|
+
let map = mapsByFetch.get(sourceFetch);
|
|
885
910
|
if (!map) {
|
|
886
911
|
map = new Map();
|
|
887
|
-
mapsByFetch.set(
|
|
912
|
+
mapsByFetch.set(sourceFetch, map);
|
|
888
913
|
}
|
|
889
914
|
return map;
|
|
890
915
|
};
|
|
891
916
|
|
|
892
|
-
const getSourceMapCache = (
|
|
893
|
-
getPerFetchMap(sourceMapCachesByFetch, sourceMapCache,
|
|
917
|
+
const getSourceMapCache = (sourceFetch: SourceFetch | undefined): Map<string, null | SourceMap> =>
|
|
918
|
+
getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, sourceFetch);
|
|
894
919
|
|
|
895
920
|
const getPendingSourceMapRequests = (
|
|
896
|
-
|
|
921
|
+
sourceFetch: SourceFetch | undefined,
|
|
897
922
|
): Map<string, Promise<SourceMapResult>> =>
|
|
898
|
-
getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests,
|
|
923
|
+
getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, sourceFetch);
|
|
899
924
|
|
|
900
925
|
const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
|
|
926
|
+
options.allowCrossOriginSourceMap === undefined &&
|
|
901
927
|
options.allowUnsafeServerFetch === undefined &&
|
|
902
928
|
options.maxBundleSizeBytes === undefined &&
|
|
903
929
|
options.maxSourceMapSizeBytes === undefined &&
|
|
904
930
|
options.timeoutMs === undefined
|
|
905
931
|
? file
|
|
906
|
-
: `${file}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
932
|
+
: `${file}\0${options.allowCrossOriginSourceMap ?? ""}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
907
933
|
|
|
908
934
|
export const getSourceMap = async (
|
|
909
935
|
file: string,
|
|
910
|
-
|
|
911
|
-
|
|
936
|
+
shouldUseCache = true,
|
|
937
|
+
sourceFetch?: SourceFetch,
|
|
912
938
|
options: SourceMapRequestOptions = {},
|
|
913
939
|
): Promise<null | SourceMap> => {
|
|
914
|
-
const
|
|
915
|
-
const cache = getSourceMapCache(
|
|
916
|
-
const pendingRequests = getPendingSourceMapRequests(
|
|
940
|
+
const canUseCache = shouldUseCache && options.signal === undefined;
|
|
941
|
+
const cache = getSourceMapCache(sourceFetch);
|
|
942
|
+
const pendingRequests = getPendingSourceMapRequests(sourceFetch);
|
|
917
943
|
const cacheKey = getSourceMapCacheKey(file, options);
|
|
918
|
-
if (
|
|
944
|
+
if (canUseCache && cache.has(cacheKey)) {
|
|
919
945
|
return cache.get(cacheKey) ?? null;
|
|
920
946
|
}
|
|
921
947
|
|
|
922
|
-
const pendingRequest =
|
|
948
|
+
const pendingRequest = canUseCache ? pendingRequests.get(cacheKey) : undefined;
|
|
923
949
|
if (pendingRequest) {
|
|
924
950
|
return (await pendingRequest).sourceMap;
|
|
925
951
|
}
|
|
926
952
|
|
|
927
953
|
const fetchPromise: Promise<SourceMapResult> = getSourceMapUncachedInternal(
|
|
928
954
|
file,
|
|
929
|
-
|
|
955
|
+
sourceFetch,
|
|
930
956
|
options,
|
|
931
957
|
).then(
|
|
932
958
|
(sourceMap) => ({ sourceMap, isTransientFailure: false }),
|
|
933
959
|
() => ({ sourceMap: null, isTransientFailure: true }),
|
|
934
960
|
);
|
|
935
|
-
if (
|
|
961
|
+
if (canUseCache) {
|
|
936
962
|
pendingRequests.set(cacheKey, fetchPromise);
|
|
937
963
|
}
|
|
938
964
|
|
|
939
965
|
const { sourceMap, isTransientFailure } = await fetchPromise;
|
|
940
|
-
if (
|
|
966
|
+
if (canUseCache) {
|
|
941
967
|
pendingRequests.delete(cacheKey);
|
|
942
968
|
if (!isTransientFailure) {
|
|
943
969
|
cache.set(cacheKey, sourceMap);
|
|
@@ -949,13 +975,19 @@ export const getSourceMap = async (
|
|
|
949
975
|
|
|
950
976
|
export const symbolicateStack = async (
|
|
951
977
|
stack: StackFrame[],
|
|
952
|
-
|
|
953
|
-
|
|
978
|
+
shouldUseCache = true,
|
|
979
|
+
sourceFetch?: SourceFetch,
|
|
980
|
+
requestOptions: SourceMapRequestOptions = {},
|
|
954
981
|
): Promise<StackFrame[]> => {
|
|
955
982
|
return Promise.all(
|
|
956
983
|
stack.map(async (stackFrame) => {
|
|
957
984
|
if (!stackFrame.fileName) return stackFrame;
|
|
958
|
-
const sourceMap = await getSourceMap(
|
|
985
|
+
const sourceMap = await getSourceMap(
|
|
986
|
+
stackFrame.fileName,
|
|
987
|
+
shouldUseCache,
|
|
988
|
+
sourceFetch,
|
|
989
|
+
requestOptions,
|
|
990
|
+
);
|
|
959
991
|
if (
|
|
960
992
|
!sourceMap ||
|
|
961
993
|
typeof stackFrame.lineNumber !== "number" ||
|