bippy 0.6.1-dev.3a24abf → 0.6.1-dev.93556ef
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 -1
- package/README.md +26 -1
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +39 -42
- package/dist/core.d.ts +39 -42
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +11 -3
- package/dist/core2.d.ts +11 -3
- package/dist/core2.js +1 -1
- package/dist/{types.d.cts → errors.d.cts} +187 -68
- package/dist/{types.d.ts → errors.d.ts} +187 -68
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.iife.js +1 -1
- package/dist/index.js +1 -1
- package/dist/install-hook-only.cjs +1 -1
- package/dist/install-hook-only.d.cts +8 -0
- package/dist/install-hook-only.d.ts +8 -0
- package/dist/install-hook-only.iife.js +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 +13 -14
- package/dist/source.d.cts +86 -69
- package/dist/source.d.ts +86 -69
- package/dist/source.js +13 -14
- package/package.json +10 -6
- package/src/core.ts +246 -293
- package/src/errors.ts +34 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +152 -110
- package/src/react-internals/generated/react-work-tags.ts +318 -0
- package/src/react-internals/index.ts +67 -0
- package/src/react-internals/semver.ts +80 -0
- package/src/{types.ts → react-internals/types.ts} +13 -86
- package/src/source/error-stack.ts +11 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +42 -26
- package/src/source/index.ts +11 -1
- package/src/source/inspect-hooks.ts +220 -207
- package/src/source/owner-stack.ts +86 -128
- package/src/source/parse-debug-stack.ts +4 -4
- package/src/source/parse-hook-names.ts +14 -53
- package/src/source/parse-stack.ts +14 -31
- package/src/source/renderer-dispatchers.ts +30 -0
- package/src/source/symbolication.ts +703 -135
- package/src/generated/react-work-tags.ts +0 -169
- package/src/react-internals.ts +0 -67
- package/src/unsubscribe.ts +0 -17
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { decode, SourceMapMappings, type SourceMapSegment } from "@jridgewell/sourcemap-codec";
|
|
1
|
+
import { decode, type SourceMapMappings, type SourceMapSegment } from "@jridgewell/sourcemap-codec";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { BippySourceMapError } from "../errors.js";
|
|
4
|
+
import { SCHEME_REGEX } from "./constants.js";
|
|
5
|
+
import type { StackFrame } from "./parse-stack.js";
|
|
4
6
|
|
|
5
7
|
export interface DecodedSourceMapSection {
|
|
6
8
|
map: {
|
|
@@ -10,7 +12,7 @@ export interface DecodedSourceMapSection {
|
|
|
10
12
|
names?: string[];
|
|
11
13
|
sourceRoot?: string;
|
|
12
14
|
sources: string[];
|
|
13
|
-
sourcesContent?: string
|
|
15
|
+
sourcesContent?: Array<string | null>;
|
|
14
16
|
version: 3;
|
|
15
17
|
};
|
|
16
18
|
offset: {
|
|
@@ -19,20 +21,30 @@ export interface DecodedSourceMapSection {
|
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
// https://tc39.es/ecma426/#sec-index-source-map
|
|
23
24
|
export interface IndexSourceMap {
|
|
24
25
|
file?: string;
|
|
25
26
|
sections: Array<{
|
|
26
|
-
map
|
|
27
|
+
map?: StandardSourceMap;
|
|
27
28
|
offset: {
|
|
28
29
|
column: number;
|
|
29
30
|
line: number;
|
|
30
31
|
};
|
|
32
|
+
url?: string;
|
|
31
33
|
}>;
|
|
32
34
|
version: 3;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
export
|
|
37
|
+
export interface SourceFetch {
|
|
38
|
+
(url: string, init?: RequestInit): Promise<Response>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SourceMapRequestOptions {
|
|
42
|
+
allowUnsafeServerFetch?: boolean;
|
|
43
|
+
maxBundleSizeBytes?: number;
|
|
44
|
+
maxSourceMapSizeBytes?: number;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
}
|
|
36
48
|
|
|
37
49
|
export interface SourceMap {
|
|
38
50
|
file?: string;
|
|
@@ -42,11 +54,10 @@ export interface SourceMap {
|
|
|
42
54
|
sections?: DecodedSourceMapSection[];
|
|
43
55
|
sourceRoot?: string;
|
|
44
56
|
sources: string[];
|
|
45
|
-
sourcesContent?: string
|
|
57
|
+
sourcesContent?: Array<string | null>;
|
|
46
58
|
version: 3;
|
|
47
59
|
}
|
|
48
60
|
|
|
49
|
-
// https://developer.chrome.com/blog/sourcemaps#the_anatomy_of_a_source_map
|
|
50
61
|
export interface StandardSourceMap {
|
|
51
62
|
file?: string;
|
|
52
63
|
ignoreList?: number[];
|
|
@@ -54,27 +65,60 @@ export interface StandardSourceMap {
|
|
|
54
65
|
names?: string[];
|
|
55
66
|
sourceRoot?: string;
|
|
56
67
|
sources: string[];
|
|
57
|
-
sourcesContent?: string
|
|
68
|
+
sourcesContent?: Array<string | null>;
|
|
58
69
|
version: 3;
|
|
59
70
|
x_google_ignoreList?: number[];
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
|
|
64
|
-
const SCHEME_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
65
|
-
// inline sourcemap, e.g. data:application/json;base64,...
|
|
66
|
-
const INLINE_SOURCEMAP_REGEX = /^data:application\/json[^,]+base64,/;
|
|
67
|
-
// sourcemap url, e.g. //@ sourceMappingURL=... or /* @ sourceMappingURL=... */ at the end of the file
|
|
73
|
+
const INLINE_SOURCEMAP_REGEX = /^data:application\/json(?:;[^,]*)?,/i;
|
|
68
74
|
const SOURCEMAP_REGEX =
|
|
69
75
|
/(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*(?:\*\/)[ \t]*$)/;
|
|
70
76
|
|
|
71
77
|
export const sourceMapCache = new Map<string, null | SourceMap>();
|
|
78
|
+
const sourceMapCachesByFetch = new WeakMap<SourceFetch, Map<string, null | SourceMap>>();
|
|
72
79
|
interface SourceMapResult {
|
|
73
80
|
sourceMap: null | SourceMap;
|
|
74
81
|
isTransientFailure: boolean;
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
class TransientSourceMapError extends Error {}
|
|
85
|
+
|
|
86
|
+
const pendingSourceMapRequests = new Map<string, Promise<SourceMapResult>>();
|
|
87
|
+
const pendingSourceMapRequestsByFetch = new WeakMap<
|
|
88
|
+
SourceFetch,
|
|
89
|
+
Map<string, Promise<SourceMapResult>>
|
|
90
|
+
>();
|
|
91
|
+
const defaultMaxBundleSizeBytes = 25 * 1024 * 1024;
|
|
92
|
+
const defaultMaxSourceMapSizeBytes = 100 * 1024 * 1024;
|
|
93
|
+
const extensionProtocols = new Set([
|
|
94
|
+
"chrome-extension:",
|
|
95
|
+
"moz-extension:",
|
|
96
|
+
"safari-web-extension:",
|
|
97
|
+
]);
|
|
98
|
+
const defaultFetchableProtocols = new Set(["http:", "https:", ...extensionProtocols]);
|
|
99
|
+
|
|
100
|
+
const getSourceFromSegment = (
|
|
101
|
+
segment: SourceMapSegment,
|
|
102
|
+
sources: string[],
|
|
103
|
+
ignoredSourceIndices?: Set<number>,
|
|
104
|
+
names?: string[],
|
|
105
|
+
): StackFrame | null => {
|
|
106
|
+
const [, sourceIndex, sourceLine, sourceColumn, nameIndex] = segment;
|
|
107
|
+
if (sourceIndex === undefined || sourceLine === undefined || sourceColumn === undefined) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fileName = sources[sourceIndex];
|
|
112
|
+
if (!fileName) return null;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
columnNumber: sourceColumn,
|
|
116
|
+
fileName,
|
|
117
|
+
functionName: nameIndex === undefined ? undefined : names?.[nameIndex] || undefined,
|
|
118
|
+
isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
|
|
119
|
+
lineNumber: sourceLine + 1,
|
|
120
|
+
};
|
|
121
|
+
};
|
|
78
122
|
|
|
79
123
|
const getSourceFromMappings = (
|
|
80
124
|
mappings: SourceMapMappings,
|
|
@@ -82,6 +126,7 @@ const getSourceFromMappings = (
|
|
|
82
126
|
lineIndexInMappings: number,
|
|
83
127
|
column: number,
|
|
84
128
|
ignoredSourceIndices?: Set<number>,
|
|
129
|
+
names?: string[],
|
|
85
130
|
): StackFrame | null => {
|
|
86
131
|
if (lineIndexInMappings < 0 || lineIndexInMappings >= mappings.length) {
|
|
87
132
|
return null;
|
|
@@ -92,8 +137,6 @@ const getSourceFromMappings = (
|
|
|
92
137
|
return null;
|
|
93
138
|
}
|
|
94
139
|
|
|
95
|
-
// Segments within a line are sorted by generated column, so binary search for
|
|
96
|
-
// the last segment at or before the column.
|
|
97
140
|
let closestLineSegment: null | SourceMapSegment = null;
|
|
98
141
|
let lowIndex = 0;
|
|
99
142
|
let highIndex = lineMapping.length - 1;
|
|
@@ -111,24 +154,7 @@ const getSourceFromMappings = (
|
|
|
111
154
|
return null;
|
|
112
155
|
}
|
|
113
156
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (sourceIndex === undefined || sourceLine === undefined || sourceColumn === undefined) {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const fileName = sources[sourceIndex];
|
|
121
|
-
|
|
122
|
-
if (!fileName) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
columnNumber: sourceColumn,
|
|
128
|
-
fileName,
|
|
129
|
-
lineNumber: sourceLine + 1,
|
|
130
|
-
isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
|
|
131
|
-
};
|
|
157
|
+
return getSourceFromSegment(closestLineSegment, sources, ignoredSourceIndices, names);
|
|
132
158
|
};
|
|
133
159
|
|
|
134
160
|
export const getSourceFromSourceMap = (
|
|
@@ -137,7 +163,6 @@ export const getSourceFromSourceMap = (
|
|
|
137
163
|
column: number,
|
|
138
164
|
): StackFrame | null => {
|
|
139
165
|
if (sourceMap.sections) {
|
|
140
|
-
// Section offsets are 0-based while stack trace lines are 1-based.
|
|
141
166
|
const lineIndex = line - 1;
|
|
142
167
|
let targetSection: DecodedSourceMapSection | null = null;
|
|
143
168
|
|
|
@@ -166,6 +191,7 @@ export const getSourceFromSourceMap = (
|
|
|
166
191
|
relativeLine,
|
|
167
192
|
relativeColumn,
|
|
168
193
|
targetSection.map.ignoredSourceIndices,
|
|
194
|
+
targetSection.map.names,
|
|
169
195
|
);
|
|
170
196
|
}
|
|
171
197
|
|
|
@@ -175,17 +201,148 @@ export const getSourceFromSourceMap = (
|
|
|
175
201
|
line - 1,
|
|
176
202
|
column,
|
|
177
203
|
sourceMap.ignoredSourceIndices,
|
|
204
|
+
sourceMap.names,
|
|
178
205
|
);
|
|
179
206
|
};
|
|
180
207
|
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
208
|
+
const getSourceFromMappingsByFunctionName = (
|
|
209
|
+
mappings: SourceMapMappings,
|
|
210
|
+
sources: string[],
|
|
211
|
+
names: string[] | undefined,
|
|
212
|
+
functionName: string,
|
|
213
|
+
ignoredSourceIndices?: Set<number>,
|
|
214
|
+
): StackFrame | null => {
|
|
215
|
+
if (!names) return null;
|
|
216
|
+
const functionNameIndex = names.indexOf(functionName);
|
|
217
|
+
if (functionNameIndex === -1) return null;
|
|
218
|
+
|
|
219
|
+
let ignoredSource: StackFrame | null = null;
|
|
220
|
+
for (const lineMapping of mappings) {
|
|
221
|
+
for (const segment of lineMapping) {
|
|
222
|
+
if (segment[4] !== functionNameIndex) continue;
|
|
223
|
+
const source = getSourceFromSegment(segment, sources, ignoredSourceIndices, names);
|
|
224
|
+
if (!source) continue;
|
|
225
|
+
if (!source.isIgnoreListed) return source;
|
|
226
|
+
ignoredSource ??= source;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return ignoredSource;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const getSourceFromSourceMapByFunctionName = (
|
|
233
|
+
sourceMap: SourceMap,
|
|
234
|
+
functionName: string,
|
|
235
|
+
): StackFrame | null => {
|
|
236
|
+
if (sourceMap.sections) {
|
|
237
|
+
let ignoredSource: StackFrame | null = null;
|
|
238
|
+
for (const section of sourceMap.sections) {
|
|
239
|
+
const source = getSourceFromMappingsByFunctionName(
|
|
240
|
+
section.map.mappings,
|
|
241
|
+
section.map.sources,
|
|
242
|
+
section.map.names,
|
|
243
|
+
functionName,
|
|
244
|
+
section.map.ignoredSourceIndices,
|
|
245
|
+
);
|
|
246
|
+
if (!source) continue;
|
|
247
|
+
if (!source.isIgnoreListed) return source;
|
|
248
|
+
ignoredSource ??= source;
|
|
249
|
+
}
|
|
250
|
+
return ignoredSource;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return getSourceFromMappingsByFunctionName(
|
|
254
|
+
sourceMap.mappings,
|
|
255
|
+
sourceMap.sources,
|
|
256
|
+
sourceMap.names,
|
|
257
|
+
functionName,
|
|
258
|
+
sourceMap.ignoredSourceIndices,
|
|
259
|
+
);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const findSourceContentByFileName = (
|
|
263
|
+
sources: string[],
|
|
264
|
+
sourcesContent: Array<string | null> | undefined,
|
|
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);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const getSourceContentFromSourceMap = (
|
|
273
|
+
sourceMap: SourceMap,
|
|
274
|
+
originalFileName: string,
|
|
275
|
+
): string | null => {
|
|
276
|
+
const sourceContent = findSourceContentByFileName(
|
|
277
|
+
sourceMap.sources,
|
|
278
|
+
sourceMap.sourcesContent,
|
|
279
|
+
originalFileName,
|
|
280
|
+
);
|
|
281
|
+
if (sourceContent !== null) return sourceContent;
|
|
282
|
+
|
|
283
|
+
if (!sourceMap.sections) return null;
|
|
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;
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
296
|
+
if (INLINE_SOURCEMAP_REGEX.test(reference)) return reference;
|
|
297
|
+
try {
|
|
298
|
+
return new URL(reference, baseUrl).toString();
|
|
299
|
+
} catch {
|
|
300
|
+
try {
|
|
301
|
+
const resolvedUrl = new URL(reference, new URL(baseUrl, "https://bippy.invalid/"));
|
|
302
|
+
return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
|
|
303
|
+
} catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const getMetroSourceMapUrl = (bundleUrl: string): string | null => {
|
|
310
|
+
try {
|
|
311
|
+
const parsedBundleUrl = new URL(bundleUrl);
|
|
312
|
+
const bundleExtensionIndex = parsedBundleUrl.pathname.lastIndexOf(".bundle");
|
|
313
|
+
if (bundleExtensionIndex === -1) return null;
|
|
314
|
+
const embeddedSearch = parsedBundleUrl.pathname.slice(bundleExtensionIndex + ".bundle".length);
|
|
315
|
+
if (embeddedSearch && !embeddedSearch.startsWith("//&")) return null;
|
|
316
|
+
parsedBundleUrl.pathname = `${parsedBundleUrl.pathname.slice(0, bundleExtensionIndex)}.map`;
|
|
317
|
+
if (embeddedSearch) {
|
|
318
|
+
const embeddedSearchParameters = new URLSearchParams(embeddedSearch.slice("//&".length));
|
|
319
|
+
for (const [parameterName, parameterValue] of embeddedSearchParameters) {
|
|
320
|
+
parsedBundleUrl.searchParams.set(parameterName, parameterValue);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
parsedBundleUrl.hash = "";
|
|
324
|
+
return parsedBundleUrl.toString();
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const getSourceMapUrl = (
|
|
331
|
+
bundleUrl: string,
|
|
332
|
+
bundleContent: string,
|
|
333
|
+
bundleResponse: Response,
|
|
334
|
+
): null | string => {
|
|
335
|
+
const sourceMapHeader =
|
|
336
|
+
bundleResponse.headers.get("sourcemap") ?? bundleResponse.headers.get("x-sourcemap");
|
|
337
|
+
if (sourceMapHeader) {
|
|
338
|
+
return resolveUrl(sourceMapHeader.trim(), bundleUrl);
|
|
339
|
+
}
|
|
340
|
+
|
|
184
341
|
let sourceMapUrl: string | undefined;
|
|
185
|
-
let searchEnd =
|
|
342
|
+
let searchEnd = bundleContent.length;
|
|
186
343
|
while (searchEnd > 0 && !sourceMapUrl) {
|
|
187
|
-
const lineStart =
|
|
188
|
-
const regexMatch =
|
|
344
|
+
const lineStart = bundleContent.lastIndexOf("\n", searchEnd - 1) + 1;
|
|
345
|
+
const regexMatch = bundleContent.slice(lineStart, searchEnd).match(SOURCEMAP_REGEX);
|
|
189
346
|
if (regexMatch) {
|
|
190
347
|
sourceMapUrl = regexMatch[1] || regexMatch[2];
|
|
191
348
|
}
|
|
@@ -193,63 +350,176 @@ const getSourceMapUrl = (url: string, content: string): null | string => {
|
|
|
193
350
|
}
|
|
194
351
|
|
|
195
352
|
if (!sourceMapUrl) {
|
|
196
|
-
return
|
|
353
|
+
return getMetroSourceMapUrl(bundleUrl);
|
|
197
354
|
}
|
|
198
355
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
356
|
+
return resolveUrl(sourceMapUrl, bundleUrl);
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const isStandardSourceMap = (value: unknown): value is StandardSourceMap => {
|
|
360
|
+
if (typeof value !== "object" || value === null) return false;
|
|
361
|
+
const version = Reflect.get(value, "version");
|
|
362
|
+
const mappings = Reflect.get(value, "mappings");
|
|
363
|
+
const file = Reflect.get(value, "file");
|
|
364
|
+
const names = Reflect.get(value, "names");
|
|
365
|
+
const sourceRoot = Reflect.get(value, "sourceRoot");
|
|
366
|
+
const sources = Reflect.get(value, "sources");
|
|
367
|
+
const sourcesContent = Reflect.get(value, "sourcesContent");
|
|
368
|
+
const ignoreList = Reflect.get(value, "ignoreList");
|
|
369
|
+
const googleIgnoreList = Reflect.get(value, "x_google_ignoreList");
|
|
370
|
+
if (
|
|
371
|
+
version !== 3 ||
|
|
372
|
+
typeof mappings !== "string" ||
|
|
373
|
+
(file !== undefined && typeof file !== "string") ||
|
|
374
|
+
(names !== undefined &&
|
|
375
|
+
(!Array.isArray(names) || names.some((entry) => typeof entry !== "string"))) ||
|
|
376
|
+
(sourceRoot !== undefined && typeof sourceRoot !== "string") ||
|
|
377
|
+
(sourcesContent !== undefined &&
|
|
378
|
+
(!Array.isArray(sourcesContent) ||
|
|
379
|
+
sourcesContent.some((entry) => typeof entry !== "string" && entry !== null))) ||
|
|
380
|
+
(ignoreList !== undefined &&
|
|
381
|
+
(!Array.isArray(ignoreList) ||
|
|
382
|
+
ignoreList.some((entry) => !Number.isInteger(entry) || entry < 0))) ||
|
|
383
|
+
(googleIgnoreList !== undefined &&
|
|
384
|
+
(!Array.isArray(googleIgnoreList) ||
|
|
385
|
+
googleIgnoreList.some((entry) => !Number.isInteger(entry) || entry < 0)))
|
|
386
|
+
) {
|
|
387
|
+
return false;
|
|
204
388
|
}
|
|
389
|
+
if (!Array.isArray(sources) || sources.some((entry) => typeof entry !== "string")) return false;
|
|
390
|
+
if (sourcesContent && sourcesContent.length !== sources.length) return false;
|
|
391
|
+
const sourceCount = sources.length;
|
|
392
|
+
return [...(ignoreList ?? []), ...(googleIgnoreList ?? [])].every(
|
|
393
|
+
(sourceIndex) => sourceIndex < sourceCount,
|
|
394
|
+
);
|
|
395
|
+
};
|
|
205
396
|
|
|
206
|
-
|
|
397
|
+
const isIndexSourceMap = (value: unknown): value is IndexSourceMap => {
|
|
398
|
+
if (typeof value !== "object" || value === null) return false;
|
|
399
|
+
const version = Reflect.get(value, "version");
|
|
400
|
+
const sections = Reflect.get(value, "sections");
|
|
401
|
+
if (version !== 3 || !Array.isArray(sections)) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return sections.every((section: unknown) => {
|
|
405
|
+
if (typeof section !== "object" || section === null) return false;
|
|
406
|
+
const map = Reflect.get(section, "map");
|
|
407
|
+
const url = Reflect.get(section, "url");
|
|
408
|
+
const offset = Reflect.get(section, "offset");
|
|
409
|
+
if (typeof offset !== "object" || offset === null) return false;
|
|
410
|
+
const offsetColumn = Reflect.get(offset, "column");
|
|
411
|
+
const offsetLine = Reflect.get(offset, "line");
|
|
412
|
+
const hasMap = isStandardSourceMap(map);
|
|
413
|
+
const hasUrl = typeof url === "string" && url.length > 0;
|
|
414
|
+
return (
|
|
415
|
+
hasMap !== hasUrl &&
|
|
416
|
+
typeof offsetColumn === "number" &&
|
|
417
|
+
Number.isInteger(offsetColumn) &&
|
|
418
|
+
offsetColumn >= 0 &&
|
|
419
|
+
typeof offsetLine === "number" &&
|
|
420
|
+
Number.isInteger(offsetLine) &&
|
|
421
|
+
offsetLine >= 0
|
|
422
|
+
);
|
|
423
|
+
});
|
|
207
424
|
};
|
|
208
425
|
|
|
209
426
|
const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> | undefined => {
|
|
210
427
|
const ignoreList = rawSourceMap.ignoreList ?? rawSourceMap.x_google_ignoreList;
|
|
211
|
-
return
|
|
428
|
+
return ignoreList?.length ? new Set(ignoreList) : undefined;
|
|
212
429
|
};
|
|
213
430
|
|
|
214
|
-
const resolveSourceRoot = (
|
|
431
|
+
const resolveSourceRoot = (
|
|
432
|
+
sourceRoot: string | undefined,
|
|
433
|
+
source: string,
|
|
434
|
+
sourceMapUrl: string,
|
|
435
|
+
): string => {
|
|
215
436
|
if (!sourceRoot || SCHEME_REGEX.test(source) || source.startsWith("/")) return source;
|
|
216
437
|
const normalizedSourceRoot = sourceRoot.endsWith("/") ? sourceRoot : `${sourceRoot}/`;
|
|
217
438
|
const normalizedSource = source.replace(/^\.\//, "");
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const decodeIndexSourceMap = (rawSourceMap: IndexSourceMap): SourceMap => {
|
|
241
|
-
const decodedSections: DecodedSourceMapSection[] = rawSourceMap.sections.map(
|
|
242
|
-
({ map, offset }) => ({
|
|
243
|
-
map: {
|
|
244
|
-
...map,
|
|
245
|
-
ignoredSourceIndices: getIgnoredSourceIndices(map),
|
|
246
|
-
mappings: decode(map.mappings),
|
|
247
|
-
sources: resolveSourceMapSources(map),
|
|
248
|
-
},
|
|
249
|
-
offset,
|
|
250
|
-
}),
|
|
439
|
+
try {
|
|
440
|
+
const baseUrl = SCHEME_REGEX.test(normalizedSourceRoot)
|
|
441
|
+
? normalizedSourceRoot
|
|
442
|
+
: new URL(normalizedSourceRoot, sourceMapUrl).toString();
|
|
443
|
+
return new URL(normalizedSource, baseUrl).toString();
|
|
444
|
+
} catch {
|
|
445
|
+
const pathSegments = `${normalizedSourceRoot}${normalizedSource}`.split("/");
|
|
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("/");
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const resolveSourceMapSources = (rawSourceMap: StandardSourceMap, sourceMapUrl: string): string[] =>
|
|
459
|
+
rawSourceMap.sources.map((source) =>
|
|
460
|
+
resolveSourceRoot(rawSourceMap.sourceRoot, source, sourceMapUrl),
|
|
251
461
|
);
|
|
252
462
|
|
|
463
|
+
const decodeStandardSourceMap = (
|
|
464
|
+
rawSourceMap: StandardSourceMap,
|
|
465
|
+
sourceMapUrl: string,
|
|
466
|
+
): SourceMap | null => {
|
|
467
|
+
try {
|
|
468
|
+
return {
|
|
469
|
+
file: rawSourceMap.file,
|
|
470
|
+
ignoredSourceIndices: getIgnoredSourceIndices(rawSourceMap),
|
|
471
|
+
mappings: decode(rawSourceMap.mappings),
|
|
472
|
+
names: rawSourceMap.names,
|
|
473
|
+
sourceRoot: rawSourceMap.sourceRoot,
|
|
474
|
+
sources: resolveSourceMapSources(rawSourceMap, sourceMapUrl),
|
|
475
|
+
sourcesContent: rawSourceMap.sourcesContent,
|
|
476
|
+
version: 3,
|
|
477
|
+
};
|
|
478
|
+
} catch {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
interface LoadStandardSourceMap {
|
|
484
|
+
(url: string): Promise<StandardSourceMap | null>;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const decodeIndexSourceMap = async (
|
|
488
|
+
rawSourceMap: IndexSourceMap,
|
|
489
|
+
sourceMapUrl: string,
|
|
490
|
+
loadStandardSourceMap: LoadStandardSourceMap,
|
|
491
|
+
): Promise<SourceMap | null> => {
|
|
492
|
+
const decodedSections: DecodedSourceMapSection[] = [];
|
|
493
|
+
let previousOffsetLine = -1;
|
|
494
|
+
let previousOffsetColumn = -1;
|
|
495
|
+
for (const section of rawSourceMap.sections) {
|
|
496
|
+
if (
|
|
497
|
+
section.offset.line < previousOffsetLine ||
|
|
498
|
+
(section.offset.line === previousOffsetLine && section.offset.column <= previousOffsetColumn)
|
|
499
|
+
) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
previousOffsetLine = section.offset.line;
|
|
503
|
+
previousOffsetColumn = section.offset.column;
|
|
504
|
+
const sectionUrl = section.url ? resolveUrl(section.url, sourceMapUrl) : null;
|
|
505
|
+
const map = section.map ?? (sectionUrl ? await loadStandardSourceMap(sectionUrl) : null);
|
|
506
|
+
if (!map) return null;
|
|
507
|
+
const sectionSourceMapUrl = sectionUrl ?? sourceMapUrl;
|
|
508
|
+
try {
|
|
509
|
+
decodedSections.push({
|
|
510
|
+
map: {
|
|
511
|
+
...map,
|
|
512
|
+
ignoredSourceIndices: getIgnoredSourceIndices(map),
|
|
513
|
+
mappings: decode(map.mappings),
|
|
514
|
+
sources: resolveSourceMapSources(map, sectionSourceMapUrl),
|
|
515
|
+
},
|
|
516
|
+
offset: section.offset,
|
|
517
|
+
});
|
|
518
|
+
} catch {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
253
523
|
const allSources = new Set<string>();
|
|
254
524
|
for (const section of decodedSections) {
|
|
255
525
|
for (const source of section.map.sources) {
|
|
@@ -269,7 +539,7 @@ const decodeIndexSourceMap = (rawSourceMap: IndexSourceMap): SourceMap => {
|
|
|
269
539
|
};
|
|
270
540
|
};
|
|
271
541
|
|
|
272
|
-
const isFetchableUrl = (url: string): boolean => {
|
|
542
|
+
const isFetchableUrl = (url: string, shouldAllowCustomProtocol = false): boolean => {
|
|
273
543
|
if (!url) {
|
|
274
544
|
return false;
|
|
275
545
|
}
|
|
@@ -288,92 +558,389 @@ const isFetchableUrl = (url: string): boolean => {
|
|
|
288
558
|
|
|
289
559
|
const scheme = schemeMatch[0].toLowerCase();
|
|
290
560
|
|
|
291
|
-
return
|
|
561
|
+
return shouldAllowCustomProtocol || defaultFetchableProtocols.has(scheme);
|
|
292
562
|
};
|
|
293
563
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
564
|
+
const isServerRuntime = (): boolean =>
|
|
565
|
+
typeof window === "undefined" &&
|
|
566
|
+
typeof process !== "undefined" &&
|
|
567
|
+
typeof process.versions?.node === "string";
|
|
568
|
+
|
|
569
|
+
const isAbsoluteHttpUrl = (url: string): boolean => {
|
|
570
|
+
try {
|
|
571
|
+
const parsedUrl = new URL(url);
|
|
572
|
+
return (
|
|
573
|
+
(parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") &&
|
|
574
|
+
!parsedUrl.username &&
|
|
575
|
+
!parsedUrl.password
|
|
576
|
+
);
|
|
577
|
+
} catch {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const getUrlProtocol = (url: string): string | null => {
|
|
583
|
+
try {
|
|
584
|
+
return new URL(url).protocol;
|
|
585
|
+
} catch {
|
|
304
586
|
return null;
|
|
305
587
|
}
|
|
588
|
+
};
|
|
306
589
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
590
|
+
const isExtensionUrl = (url: string): boolean => {
|
|
591
|
+
const protocol = getUrlProtocol(url);
|
|
592
|
+
return protocol !== null && extensionProtocols.has(protocol);
|
|
593
|
+
};
|
|
311
594
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
595
|
+
const isSameOrigin = (firstUrl: string, secondUrl: string): boolean => {
|
|
596
|
+
try {
|
|
597
|
+
const firstParsedUrl = new URL(firstUrl);
|
|
598
|
+
const secondParsedUrl = new URL(secondUrl);
|
|
599
|
+
return (
|
|
600
|
+
firstParsedUrl.protocol === secondParsedUrl.protocol &&
|
|
601
|
+
firstParsedUrl.host === secondParsedUrl.host
|
|
602
|
+
);
|
|
603
|
+
} catch {
|
|
604
|
+
return false;
|
|
315
605
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const isAllowedSourceMapUrl = (
|
|
609
|
+
bundleUrl: string,
|
|
610
|
+
sourceMapUrl: string,
|
|
611
|
+
shouldRejectCrossOrigin: boolean,
|
|
612
|
+
shouldAllowCustomProtocol: boolean,
|
|
613
|
+
): boolean => {
|
|
614
|
+
if (INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) return true;
|
|
615
|
+
if (!isFetchableUrl(sourceMapUrl, shouldAllowCustomProtocol)) return false;
|
|
616
|
+
const bundleProtocol = getUrlProtocol(bundleUrl);
|
|
617
|
+
const sourceMapProtocol = getUrlProtocol(sourceMapUrl);
|
|
618
|
+
const hasCustomProtocol =
|
|
619
|
+
(bundleProtocol !== null && !defaultFetchableProtocols.has(bundleProtocol)) ||
|
|
620
|
+
(sourceMapProtocol !== null && !defaultFetchableProtocols.has(sourceMapProtocol));
|
|
621
|
+
const shouldRequireSameOrigin =
|
|
622
|
+
shouldRejectCrossOrigin ||
|
|
623
|
+
hasCustomProtocol ||
|
|
624
|
+
isExtensionUrl(bundleUrl) ||
|
|
625
|
+
isExtensionUrl(sourceMapUrl);
|
|
626
|
+
return !shouldRequireSameOrigin || isSameOrigin(bundleUrl, sourceMapUrl);
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const isTransientHttpStatus = (status: number): boolean =>
|
|
630
|
+
status === 408 || status === 425 || status === 429 || status >= 500;
|
|
631
|
+
|
|
632
|
+
const assertResponseIsCacheable = (response: Response): boolean => {
|
|
633
|
+
if (response.ok) return true;
|
|
634
|
+
if (isTransientHttpStatus(response.status)) {
|
|
635
|
+
throw new TransientSourceMapError();
|
|
319
636
|
}
|
|
637
|
+
return false;
|
|
638
|
+
};
|
|
320
639
|
|
|
321
|
-
|
|
640
|
+
const isResponseUrlAllowed = (
|
|
641
|
+
requestedUrl: string,
|
|
642
|
+
response: Response,
|
|
643
|
+
shouldRejectCrossOrigin: boolean,
|
|
644
|
+
): boolean =>
|
|
645
|
+
!shouldRejectCrossOrigin || response.url.length === 0 || isSameOrigin(requestedUrl, response.url);
|
|
322
646
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
647
|
+
interface RequestSignal {
|
|
648
|
+
cleanup: () => void;
|
|
649
|
+
signal?: AbortSignal;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal => {
|
|
653
|
+
if (options.timeoutMs === undefined || options.timeoutMs < 0) {
|
|
654
|
+
return { cleanup: () => {}, signal: options.signal };
|
|
655
|
+
}
|
|
656
|
+
const abortController = new AbortController();
|
|
657
|
+
const abortFromSource = (): void => abortController.abort(options.signal?.reason);
|
|
658
|
+
if (options.signal?.aborted) {
|
|
659
|
+
abortFromSource();
|
|
660
|
+
} else {
|
|
661
|
+
options.signal?.addEventListener("abort", abortFromSource, { once: true });
|
|
662
|
+
}
|
|
663
|
+
const timeoutHandle = setTimeout(
|
|
664
|
+
() => abortController.abort(new BippySourceMapError("Source map request timed out")),
|
|
665
|
+
options.timeoutMs,
|
|
666
|
+
);
|
|
667
|
+
return {
|
|
668
|
+
cleanup: () => {
|
|
669
|
+
clearTimeout(timeoutHandle);
|
|
670
|
+
options.signal?.removeEventListener("abort", abortFromSource);
|
|
671
|
+
},
|
|
672
|
+
signal: abortController.signal,
|
|
673
|
+
};
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const readResponseText = async (
|
|
677
|
+
response: Response,
|
|
678
|
+
maxSizeBytes: number,
|
|
679
|
+
): Promise<string | null> => {
|
|
680
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
681
|
+
if (Number.isFinite(contentLength) && contentLength > maxSizeBytes) return null;
|
|
682
|
+
if (!response.body) {
|
|
683
|
+
const responseText = await response.text();
|
|
684
|
+
return new TextEncoder().encode(responseText).byteLength > maxSizeBytes ? null : responseText;
|
|
328
685
|
}
|
|
329
686
|
|
|
330
|
-
const
|
|
331
|
-
|
|
687
|
+
const reader = response.body.getReader();
|
|
688
|
+
const decoder = new TextDecoder();
|
|
689
|
+
const decodedChunks: string[] = [];
|
|
690
|
+
let totalSizeBytes = 0;
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
while (true) {
|
|
694
|
+
const { done, value } = await reader.read();
|
|
695
|
+
if (done) break;
|
|
696
|
+
totalSizeBytes += value.byteLength;
|
|
697
|
+
if (totalSizeBytes > maxSizeBytes) {
|
|
698
|
+
await reader.cancel();
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
decodedChunks.push(decoder.decode(value, { stream: true }));
|
|
702
|
+
}
|
|
703
|
+
decodedChunks.push(decoder.decode());
|
|
704
|
+
return decodedChunks.join("");
|
|
705
|
+
} finally {
|
|
706
|
+
reader.releaseLock();
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const decodeBase64 = (encodedContent: string): string | null => {
|
|
711
|
+
try {
|
|
712
|
+
if (typeof globalThis.atob === "function") {
|
|
713
|
+
return new TextDecoder().decode(
|
|
714
|
+
Uint8Array.from(globalThis.atob(encodedContent), (character) => character.charCodeAt(0)),
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
} catch {
|
|
332
718
|
return null;
|
|
333
719
|
}
|
|
334
720
|
|
|
721
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
722
|
+
const normalizedContent = encodedContent.replace(/\s/g, "").replace(/=+$/, "");
|
|
723
|
+
const bytes: number[] = [];
|
|
724
|
+
let bitBuffer = 0;
|
|
725
|
+
let bitCount = 0;
|
|
726
|
+
for (const character of normalizedContent) {
|
|
727
|
+
const value = alphabet.indexOf(character);
|
|
728
|
+
if (value === -1) return null;
|
|
729
|
+
bitBuffer = bitBuffer * 64 + value;
|
|
730
|
+
bitCount += 6;
|
|
731
|
+
if (bitCount >= 8) {
|
|
732
|
+
bitCount -= 8;
|
|
733
|
+
bytes.push(Math.floor(bitBuffer / 2 ** bitCount) & 255);
|
|
734
|
+
bitBuffer %= 2 ** bitCount;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return new TextDecoder().decode(Uint8Array.from(bytes));
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
const readInlineSourceMap = (sourceMapUrl: string, maxSizeBytes: number): unknown => {
|
|
741
|
+
const commaIndex = sourceMapUrl.indexOf(",");
|
|
742
|
+
if (commaIndex === -1) return null;
|
|
743
|
+
const metadata = sourceMapUrl.slice(0, commaIndex);
|
|
744
|
+
const encodedContent = sourceMapUrl.slice(commaIndex + 1);
|
|
745
|
+
if (encodedContent.length > maxSizeBytes * 2) return null;
|
|
335
746
|
try {
|
|
336
|
-
const
|
|
747
|
+
const content = metadata.toLowerCase().includes(";base64")
|
|
748
|
+
? decodeBase64(encodedContent)
|
|
749
|
+
: decodeURIComponent(encodedContent);
|
|
750
|
+
if (content === null) return null;
|
|
751
|
+
if (new TextEncoder().encode(content).byteLength > maxSizeBytes) return null;
|
|
752
|
+
return JSON.parse(content);
|
|
753
|
+
} catch {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
337
757
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
758
|
+
const readSourceMapDocument = async (
|
|
759
|
+
sourceMapUrl: string,
|
|
760
|
+
sourceFetch: SourceFetch,
|
|
761
|
+
signal: AbortSignal | undefined,
|
|
762
|
+
maxSizeBytes: number,
|
|
763
|
+
shouldRejectRedirects: boolean,
|
|
764
|
+
): Promise<unknown> => {
|
|
765
|
+
if (INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) {
|
|
766
|
+
return readInlineSourceMap(sourceMapUrl, maxSizeBytes);
|
|
767
|
+
}
|
|
768
|
+
const sourceMapResponse = await sourceFetch(sourceMapUrl, {
|
|
769
|
+
redirect: shouldRejectRedirects ? "error" : "follow",
|
|
770
|
+
signal,
|
|
771
|
+
});
|
|
772
|
+
if (!assertResponseIsCacheable(sourceMapResponse)) return null;
|
|
773
|
+
if (!isResponseUrlAllowed(sourceMapUrl, sourceMapResponse, shouldRejectRedirects)) return null;
|
|
774
|
+
const sourceMapContent = await readResponseText(sourceMapResponse, maxSizeBytes);
|
|
775
|
+
if (sourceMapContent === null) return null;
|
|
776
|
+
try {
|
|
777
|
+
return JSON.parse(sourceMapContent);
|
|
341
778
|
} catch {
|
|
342
779
|
return null;
|
|
343
780
|
}
|
|
344
781
|
};
|
|
345
782
|
|
|
783
|
+
const getSourceMapUncachedInternal = async (
|
|
784
|
+
bundleUrl: string,
|
|
785
|
+
fetchFn?: SourceFetch,
|
|
786
|
+
options: SourceMapRequestOptions = {},
|
|
787
|
+
): Promise<null | SourceMap> => {
|
|
788
|
+
const shouldAllowCustomProtocol = fetchFn !== undefined;
|
|
789
|
+
if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const isServer = isServerRuntime();
|
|
794
|
+
const shouldRejectRedirects = isServer || isExtensionUrl(bundleUrl);
|
|
795
|
+
if (isServer && !fetchFn) {
|
|
796
|
+
if (!options.allowUnsafeServerFetch || !isAbsoluteHttpUrl(bundleUrl)) return null;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const sourceFetch =
|
|
800
|
+
fetchFn ??
|
|
801
|
+
(typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined);
|
|
802
|
+
if (!sourceFetch) return null;
|
|
803
|
+
const maxBundleSizeBytes = options.maxBundleSizeBytes ?? defaultMaxBundleSizeBytes;
|
|
804
|
+
const maxSourceMapSizeBytes = options.maxSourceMapSizeBytes ?? defaultMaxSourceMapSizeBytes;
|
|
805
|
+
if (maxBundleSizeBytes < 0 || maxSourceMapSizeBytes < 0) return null;
|
|
806
|
+
const requestSignal = createRequestSignal(options);
|
|
807
|
+
try {
|
|
808
|
+
const bundleResponse = await sourceFetch(bundleUrl, {
|
|
809
|
+
redirect: shouldRejectRedirects ? "error" : "follow",
|
|
810
|
+
signal: requestSignal.signal,
|
|
811
|
+
});
|
|
812
|
+
if (!assertResponseIsCacheable(bundleResponse)) return null;
|
|
813
|
+
if (!isResponseUrlAllowed(bundleUrl, bundleResponse, shouldRejectRedirects)) return null;
|
|
814
|
+
const bundleContent = await readResponseText(bundleResponse, maxBundleSizeBytes);
|
|
815
|
+
if (bundleContent === null) return null;
|
|
816
|
+
const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent, bundleResponse);
|
|
817
|
+
if (!sourceMapUrl) return null;
|
|
818
|
+
if (
|
|
819
|
+
!isAllowedSourceMapUrl(
|
|
820
|
+
bundleUrl,
|
|
821
|
+
sourceMapUrl,
|
|
822
|
+
shouldRejectRedirects,
|
|
823
|
+
shouldAllowCustomProtocol,
|
|
824
|
+
)
|
|
825
|
+
) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const rawSourceMap = await readSourceMapDocument(
|
|
830
|
+
sourceMapUrl,
|
|
831
|
+
sourceFetch,
|
|
832
|
+
requestSignal.signal,
|
|
833
|
+
maxSourceMapSizeBytes,
|
|
834
|
+
shouldRejectRedirects,
|
|
835
|
+
);
|
|
836
|
+
if (isStandardSourceMap(rawSourceMap)) {
|
|
837
|
+
return decodeStandardSourceMap(rawSourceMap, sourceMapUrl);
|
|
838
|
+
}
|
|
839
|
+
if (!isIndexSourceMap(rawSourceMap)) return null;
|
|
840
|
+
return decodeIndexSourceMap(rawSourceMap, sourceMapUrl, async (sectionUrl) => {
|
|
841
|
+
if (
|
|
842
|
+
!isAllowedSourceMapUrl(
|
|
843
|
+
bundleUrl,
|
|
844
|
+
sectionUrl,
|
|
845
|
+
shouldRejectRedirects,
|
|
846
|
+
shouldAllowCustomProtocol,
|
|
847
|
+
)
|
|
848
|
+
) {
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
const sectionSourceMap = await readSourceMapDocument(
|
|
852
|
+
sectionUrl,
|
|
853
|
+
sourceFetch,
|
|
854
|
+
requestSignal.signal,
|
|
855
|
+
maxSourceMapSizeBytes,
|
|
856
|
+
shouldRejectRedirects,
|
|
857
|
+
);
|
|
858
|
+
return isStandardSourceMap(sectionSourceMap) ? sectionSourceMap : null;
|
|
859
|
+
});
|
|
860
|
+
} finally {
|
|
861
|
+
requestSignal.cleanup();
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
export const getSourceMapUncached = async (
|
|
866
|
+
bundleUrl: string,
|
|
867
|
+
fetchFn?: SourceFetch,
|
|
868
|
+
options: SourceMapRequestOptions = {},
|
|
869
|
+
): Promise<null | SourceMap> => {
|
|
870
|
+
try {
|
|
871
|
+
return await getSourceMapUncachedInternal(bundleUrl, fetchFn, options);
|
|
872
|
+
} catch (error) {
|
|
873
|
+
if (error instanceof TransientSourceMapError) return null;
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
const getPerFetchMap = <Value>(
|
|
879
|
+
mapsByFetch: WeakMap<SourceFetch, Map<string, Value>>,
|
|
880
|
+
globalMap: Map<string, Value>,
|
|
881
|
+
fetchFn: SourceFetch | undefined,
|
|
882
|
+
): Map<string, Value> => {
|
|
883
|
+
if (!fetchFn) return globalMap;
|
|
884
|
+
let map = mapsByFetch.get(fetchFn);
|
|
885
|
+
if (!map) {
|
|
886
|
+
map = new Map();
|
|
887
|
+
mapsByFetch.set(fetchFn, map);
|
|
888
|
+
}
|
|
889
|
+
return map;
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
const getSourceMapCache = (fetchFn: SourceFetch | undefined): Map<string, null | SourceMap> =>
|
|
893
|
+
getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, fetchFn);
|
|
894
|
+
|
|
895
|
+
const getPendingSourceMapRequests = (
|
|
896
|
+
fetchFn: SourceFetch | undefined,
|
|
897
|
+
): Map<string, Promise<SourceMapResult>> =>
|
|
898
|
+
getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, fetchFn);
|
|
899
|
+
|
|
900
|
+
const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
|
|
901
|
+
options.allowUnsafeServerFetch === undefined &&
|
|
902
|
+
options.maxBundleSizeBytes === undefined &&
|
|
903
|
+
options.maxSourceMapSizeBytes === undefined &&
|
|
904
|
+
options.timeoutMs === undefined
|
|
905
|
+
? file
|
|
906
|
+
: `${file}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
907
|
+
|
|
346
908
|
export const getSourceMap = async (
|
|
347
909
|
file: string,
|
|
348
910
|
useCache = true,
|
|
349
|
-
fetchFn?:
|
|
911
|
+
fetchFn?: SourceFetch,
|
|
912
|
+
options: SourceMapRequestOptions = {},
|
|
350
913
|
): Promise<null | SourceMap> => {
|
|
351
|
-
|
|
352
|
-
|
|
914
|
+
const shouldUseCache = useCache && options.signal === undefined;
|
|
915
|
+
const cache = getSourceMapCache(fetchFn);
|
|
916
|
+
const pendingRequests = getPendingSourceMapRequests(fetchFn);
|
|
917
|
+
const cacheKey = getSourceMapCacheKey(file, options);
|
|
918
|
+
if (shouldUseCache && cache.has(cacheKey)) {
|
|
919
|
+
return cache.get(cacheKey) ?? null;
|
|
353
920
|
}
|
|
354
921
|
|
|
355
|
-
const pendingRequest =
|
|
922
|
+
const pendingRequest = shouldUseCache ? pendingRequests.get(cacheKey) : undefined;
|
|
356
923
|
if (pendingRequest) {
|
|
357
924
|
return (await pendingRequest).sourceMap;
|
|
358
925
|
}
|
|
359
926
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
927
|
+
const fetchPromise: Promise<SourceMapResult> = getSourceMapUncachedInternal(
|
|
928
|
+
file,
|
|
929
|
+
fetchFn,
|
|
930
|
+
options,
|
|
931
|
+
).then(
|
|
365
932
|
(sourceMap) => ({ sourceMap, isTransientFailure: false }),
|
|
366
933
|
() => ({ sourceMap: null, isTransientFailure: true }),
|
|
367
934
|
);
|
|
368
|
-
if (
|
|
369
|
-
|
|
935
|
+
if (shouldUseCache) {
|
|
936
|
+
pendingRequests.set(cacheKey, fetchPromise);
|
|
370
937
|
}
|
|
371
938
|
|
|
372
939
|
const { sourceMap, isTransientFailure } = await fetchPromise;
|
|
373
|
-
if (
|
|
374
|
-
|
|
940
|
+
if (shouldUseCache) {
|
|
941
|
+
pendingRequests.delete(cacheKey);
|
|
375
942
|
if (!isTransientFailure) {
|
|
376
|
-
|
|
943
|
+
cache.set(cacheKey, sourceMap);
|
|
377
944
|
}
|
|
378
945
|
}
|
|
379
946
|
|
|
@@ -383,9 +950,9 @@ export const getSourceMap = async (
|
|
|
383
950
|
export const symbolicateStack = async (
|
|
384
951
|
stack: StackFrame[],
|
|
385
952
|
cache = true,
|
|
386
|
-
fetchFn?:
|
|
953
|
+
fetchFn?: SourceFetch,
|
|
387
954
|
): Promise<StackFrame[]> => {
|
|
388
|
-
return
|
|
955
|
+
return Promise.all(
|
|
389
956
|
stack.map(async (stackFrame) => {
|
|
390
957
|
if (!stackFrame.fileName) return stackFrame;
|
|
391
958
|
const sourceMap = await getSourceMap(stackFrame.fileName, cache, fetchFn);
|
|
@@ -409,6 +976,7 @@ export const symbolicateStack = async (
|
|
|
409
976
|
? stackFrame.source.replace(stackFrame.fileName, symbolicatedSource.fileName)
|
|
410
977
|
: stackFrame.source,
|
|
411
978
|
fileName: symbolicatedSource.fileName,
|
|
979
|
+
functionName: symbolicatedSource.functionName ?? stackFrame.functionName,
|
|
412
980
|
lineNumber: symbolicatedSource.lineNumber,
|
|
413
981
|
columnNumber: symbolicatedSource.columnNumber,
|
|
414
982
|
isIgnoreListed: symbolicatedSource.isIgnoreListed,
|