bippy 0.6.1-dev.b88fcb4 → 0.6.1-dev.f9e6c65
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 +159 -487
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +8 -54
- package/dist/core.d.ts +8 -54
- package/dist/core.js +1 -1
- package/dist/core2.cjs +1 -1
- package/dist/core2.d.cts +3 -3
- package/dist/core2.d.ts +3 -3
- package/dist/core2.js +1 -1
- package/dist/errors.d.cts +2 -57
- package/dist/errors.d.ts +2 -57
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- 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 +12 -13
- package/dist/source.d.cts +77 -78
- package/dist/source.d.ts +77 -78
- package/dist/source.js +12 -13
- package/package.json +6 -3
- package/src/core.ts +101 -530
- package/src/errors.ts +0 -65
- package/src/index.ts +1 -0
- package/src/install-hook-only.ts +2 -2
- package/src/rdt-hook.ts +30 -43
- package/src/react-internals/generated/react-work-tags.ts +6 -2
- package/src/react-internals/index.ts +4 -4
- package/src/react-internals/semver.ts +2 -0
- package/src/react-internals/types.ts +0 -83
- package/src/react.ts +76 -0
- package/src/source/get-display-name-from-source.ts +44 -40
- package/src/source/get-source.ts +40 -24
- package/src/source/index.ts +2 -0
- package/src/source/inspect-hooks.ts +70 -91
- package/src/source/owner-stack.ts +60 -89
- 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 +312 -111
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
|
@@ -1,7 +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
3
|
import { BippySourceMapError } from "../errors.js";
|
|
4
|
-
import {
|
|
4
|
+
import { SCHEME_REGEX } from "./constants.js";
|
|
5
|
+
import type { StackFrame } from "./parse-stack.js";
|
|
5
6
|
|
|
6
7
|
export interface DecodedSourceMapSection {
|
|
7
8
|
map: {
|
|
@@ -69,7 +70,6 @@ export interface StandardSourceMap {
|
|
|
69
70
|
x_google_ignoreList?: number[];
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
const SCHEME_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
73
73
|
const INLINE_SOURCEMAP_REGEX = /^data:application\/json(?:;[^,]*)?,/i;
|
|
74
74
|
const SOURCEMAP_REGEX =
|
|
75
75
|
/(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*(?:\*\/)[ \t]*$)/;
|
|
@@ -83,13 +83,42 @@ interface SourceMapResult {
|
|
|
83
83
|
|
|
84
84
|
class TransientSourceMapError extends Error {}
|
|
85
85
|
|
|
86
|
-
const
|
|
86
|
+
const pendingSourceMapRequests = new Map<string, Promise<SourceMapResult>>();
|
|
87
87
|
const pendingSourceMapRequestsByFetch = new WeakMap<
|
|
88
88
|
SourceFetch,
|
|
89
89
|
Map<string, Promise<SourceMapResult>>
|
|
90
90
|
>();
|
|
91
91
|
const defaultMaxBundleSizeBytes = 25 * 1024 * 1024;
|
|
92
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
|
+
};
|
|
93
122
|
|
|
94
123
|
const getSourceFromMappings = (
|
|
95
124
|
mappings: SourceMapMappings,
|
|
@@ -97,6 +126,7 @@ const getSourceFromMappings = (
|
|
|
97
126
|
lineIndexInMappings: number,
|
|
98
127
|
column: number,
|
|
99
128
|
ignoredSourceIndices?: Set<number>,
|
|
129
|
+
names?: string[],
|
|
100
130
|
): StackFrame | null => {
|
|
101
131
|
if (lineIndexInMappings < 0 || lineIndexInMappings >= mappings.length) {
|
|
102
132
|
return null;
|
|
@@ -124,24 +154,7 @@ const getSourceFromMappings = (
|
|
|
124
154
|
return null;
|
|
125
155
|
}
|
|
126
156
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (sourceIndex === undefined || sourceLine === undefined || sourceColumn === undefined) {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const fileName = sources[sourceIndex];
|
|
134
|
-
|
|
135
|
-
if (!fileName) {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
columnNumber: sourceColumn,
|
|
141
|
-
fileName,
|
|
142
|
-
lineNumber: sourceLine + 1,
|
|
143
|
-
isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
|
|
144
|
-
};
|
|
157
|
+
return getSourceFromSegment(closestLineSegment, sources, ignoredSourceIndices, names);
|
|
145
158
|
};
|
|
146
159
|
|
|
147
160
|
export const getSourceFromSourceMap = (
|
|
@@ -178,6 +191,7 @@ export const getSourceFromSourceMap = (
|
|
|
178
191
|
relativeLine,
|
|
179
192
|
relativeColumn,
|
|
180
193
|
targetSection.map.ignoredSourceIndices,
|
|
194
|
+
targetSection.map.names,
|
|
181
195
|
);
|
|
182
196
|
}
|
|
183
197
|
|
|
@@ -187,9 +201,97 @@ export const getSourceFromSourceMap = (
|
|
|
187
201
|
line - 1,
|
|
188
202
|
column,
|
|
189
203
|
sourceMap.ignoredSourceIndices,
|
|
204
|
+
sourceMap.names,
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
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,
|
|
190
259
|
);
|
|
191
260
|
};
|
|
192
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
|
+
|
|
193
295
|
const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
194
296
|
if (INLINE_SOURCEMAP_REGEX.test(reference)) return reference;
|
|
195
297
|
try {
|
|
@@ -204,12 +306,43 @@ const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
|
204
306
|
}
|
|
205
307
|
};
|
|
206
308
|
|
|
207
|
-
const
|
|
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
|
+
|
|
208
341
|
let sourceMapUrl: string | undefined;
|
|
209
|
-
let searchEnd =
|
|
342
|
+
let searchEnd = bundleContent.length;
|
|
210
343
|
while (searchEnd > 0 && !sourceMapUrl) {
|
|
211
|
-
const lineStart =
|
|
212
|
-
const regexMatch =
|
|
344
|
+
const lineStart = bundleContent.lastIndexOf("\n", searchEnd - 1) + 1;
|
|
345
|
+
const regexMatch = bundleContent.slice(lineStart, searchEnd).match(SOURCEMAP_REGEX);
|
|
213
346
|
if (regexMatch) {
|
|
214
347
|
sourceMapUrl = regexMatch[1] || regexMatch[2];
|
|
215
348
|
}
|
|
@@ -217,70 +350,82 @@ const getSourceMapUrl = (url: string, content: string): null | string => {
|
|
|
217
350
|
}
|
|
218
351
|
|
|
219
352
|
if (!sourceMapUrl) {
|
|
220
|
-
return
|
|
353
|
+
return getMetroSourceMapUrl(bundleUrl);
|
|
221
354
|
}
|
|
222
355
|
|
|
223
|
-
return resolveUrl(sourceMapUrl,
|
|
356
|
+
return resolveUrl(sourceMapUrl, bundleUrl);
|
|
224
357
|
};
|
|
225
358
|
|
|
226
|
-
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
|
|
227
|
-
typeof value === "object" && value !== null;
|
|
228
|
-
|
|
229
|
-
const isStringArray = (value: unknown): value is string[] =>
|
|
230
|
-
Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
231
|
-
|
|
232
|
-
const isSourcesContent = (value: unknown): value is Array<string | null> =>
|
|
233
|
-
Array.isArray(value) && value.every((entry) => typeof entry === "string" || entry === null);
|
|
234
|
-
|
|
235
|
-
const isOptionalNumberArray = (value: unknown): value is number[] | undefined =>
|
|
236
|
-
value === undefined ||
|
|
237
|
-
(Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0));
|
|
238
|
-
|
|
239
359
|
const isStandardSourceMap = (value: unknown): value is StandardSourceMap => {
|
|
240
|
-
if (
|
|
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");
|
|
241
370
|
if (
|
|
242
|
-
|
|
243
|
-
typeof
|
|
244
|
-
(
|
|
245
|
-
(
|
|
246
|
-
|
|
247
|
-
(
|
|
248
|
-
|
|
249
|
-
|
|
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)))
|
|
250
386
|
) {
|
|
251
387
|
return false;
|
|
252
388
|
}
|
|
253
|
-
if (!
|
|
254
|
-
if (
|
|
255
|
-
const sourceCount =
|
|
256
|
-
return [...(
|
|
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(
|
|
257
393
|
(sourceIndex) => sourceIndex < sourceCount,
|
|
258
394
|
);
|
|
259
395
|
};
|
|
260
396
|
|
|
261
397
|
const isIndexSourceMap = (value: unknown): value is IndexSourceMap => {
|
|
262
|
-
if (
|
|
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)) {
|
|
263
402
|
return false;
|
|
264
403
|
}
|
|
265
|
-
return
|
|
266
|
-
if (
|
|
267
|
-
const
|
|
268
|
-
const
|
|
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;
|
|
269
414
|
return (
|
|
270
415
|
hasMap !== hasUrl &&
|
|
271
|
-
typeof
|
|
272
|
-
Number.isInteger(
|
|
273
|
-
|
|
274
|
-
typeof
|
|
275
|
-
Number.isInteger(
|
|
276
|
-
|
|
416
|
+
typeof offsetColumn === "number" &&
|
|
417
|
+
Number.isInteger(offsetColumn) &&
|
|
418
|
+
offsetColumn >= 0 &&
|
|
419
|
+
typeof offsetLine === "number" &&
|
|
420
|
+
Number.isInteger(offsetLine) &&
|
|
421
|
+
offsetLine >= 0
|
|
277
422
|
);
|
|
278
423
|
});
|
|
279
424
|
};
|
|
280
425
|
|
|
281
426
|
const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> | undefined => {
|
|
282
427
|
const ignoreList = rawSourceMap.ignoreList ?? rawSourceMap.x_google_ignoreList;
|
|
283
|
-
return
|
|
428
|
+
return ignoreList?.length ? new Set(ignoreList) : undefined;
|
|
284
429
|
};
|
|
285
430
|
|
|
286
431
|
const resolveSourceRoot = (
|
|
@@ -394,7 +539,7 @@ const decodeIndexSourceMap = async (
|
|
|
394
539
|
};
|
|
395
540
|
};
|
|
396
541
|
|
|
397
|
-
const isFetchableUrl = (url: string): boolean => {
|
|
542
|
+
const isFetchableUrl = (url: string, shouldAllowCustomProtocol = false): boolean => {
|
|
398
543
|
if (!url) {
|
|
399
544
|
return false;
|
|
400
545
|
}
|
|
@@ -413,7 +558,7 @@ const isFetchableUrl = (url: string): boolean => {
|
|
|
413
558
|
|
|
414
559
|
const scheme = schemeMatch[0].toLowerCase();
|
|
415
560
|
|
|
416
|
-
return
|
|
561
|
+
return shouldAllowCustomProtocol || defaultFetchableProtocols.has(scheme);
|
|
417
562
|
};
|
|
418
563
|
|
|
419
564
|
const isServerRuntime = (): boolean =>
|
|
@@ -434,14 +579,53 @@ const isAbsoluteHttpUrl = (url: string): boolean => {
|
|
|
434
579
|
}
|
|
435
580
|
};
|
|
436
581
|
|
|
582
|
+
const getUrlProtocol = (url: string): string | null => {
|
|
583
|
+
try {
|
|
584
|
+
return new URL(url).protocol;
|
|
585
|
+
} catch {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
const isExtensionUrl = (url: string): boolean => {
|
|
591
|
+
const protocol = getUrlProtocol(url);
|
|
592
|
+
return protocol !== null && extensionProtocols.has(protocol);
|
|
593
|
+
};
|
|
594
|
+
|
|
437
595
|
const isSameOrigin = (firstUrl: string, secondUrl: string): boolean => {
|
|
438
596
|
try {
|
|
439
|
-
|
|
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
|
+
);
|
|
440
603
|
} catch {
|
|
441
604
|
return false;
|
|
442
605
|
}
|
|
443
606
|
};
|
|
444
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
|
+
|
|
445
629
|
const isTransientHttpStatus = (status: number): boolean =>
|
|
446
630
|
status === 408 || status === 425 || status === 429 || status >= 500;
|
|
447
631
|
|
|
@@ -453,14 +637,21 @@ const assertResponseIsCacheable = (response: Response): boolean => {
|
|
|
453
637
|
return false;
|
|
454
638
|
};
|
|
455
639
|
|
|
640
|
+
const isResponseUrlAllowed = (
|
|
641
|
+
requestedUrl: string,
|
|
642
|
+
response: Response,
|
|
643
|
+
shouldRejectCrossOrigin: boolean,
|
|
644
|
+
): boolean =>
|
|
645
|
+
!shouldRejectCrossOrigin || response.url.length === 0 || isSameOrigin(requestedUrl, response.url);
|
|
646
|
+
|
|
456
647
|
interface RequestSignal {
|
|
457
648
|
cleanup: () => void;
|
|
458
649
|
signal?: AbortSignal;
|
|
459
650
|
}
|
|
460
651
|
|
|
461
652
|
const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal => {
|
|
462
|
-
if (
|
|
463
|
-
return { cleanup: () => {} };
|
|
653
|
+
if (options.timeoutMs === undefined || options.timeoutMs < 0) {
|
|
654
|
+
return { cleanup: () => {}, signal: options.signal };
|
|
464
655
|
}
|
|
465
656
|
const abortController = new AbortController();
|
|
466
657
|
const abortFromSource = (): void => abortController.abort(options.signal?.reason);
|
|
@@ -469,16 +660,13 @@ const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal =>
|
|
|
469
660
|
} else {
|
|
470
661
|
options.signal?.addEventListener("abort", abortFromSource, { once: true });
|
|
471
662
|
}
|
|
472
|
-
const timeoutHandle =
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
options.timeoutMs,
|
|
477
|
-
)
|
|
478
|
-
: undefined;
|
|
663
|
+
const timeoutHandle = setTimeout(
|
|
664
|
+
() => abortController.abort(new BippySourceMapError("Source map request timed out")),
|
|
665
|
+
options.timeoutMs,
|
|
666
|
+
);
|
|
479
667
|
return {
|
|
480
668
|
cleanup: () => {
|
|
481
|
-
|
|
669
|
+
clearTimeout(timeoutHandle);
|
|
482
670
|
options.signal?.removeEventListener("abort", abortFromSource);
|
|
483
671
|
},
|
|
484
672
|
signal: abortController.signal,
|
|
@@ -491,7 +679,10 @@ const readResponseText = async (
|
|
|
491
679
|
): Promise<string | null> => {
|
|
492
680
|
const contentLength = Number(response.headers.get("content-length"));
|
|
493
681
|
if (Number.isFinite(contentLength) && contentLength > maxSizeBytes) return null;
|
|
494
|
-
if (!response.body)
|
|
682
|
+
if (!response.body) {
|
|
683
|
+
const responseText = await response.text();
|
|
684
|
+
return new TextEncoder().encode(responseText).byteLength > maxSizeBytes ? null : responseText;
|
|
685
|
+
}
|
|
495
686
|
|
|
496
687
|
const reader = response.body.getReader();
|
|
497
688
|
const decoder = new TextDecoder();
|
|
@@ -579,6 +770,7 @@ const readSourceMapDocument = async (
|
|
|
579
770
|
signal,
|
|
580
771
|
});
|
|
581
772
|
if (!assertResponseIsCacheable(sourceMapResponse)) return null;
|
|
773
|
+
if (!isResponseUrlAllowed(sourceMapUrl, sourceMapResponse, shouldRejectRedirects)) return null;
|
|
582
774
|
const sourceMapContent = await readResponseText(sourceMapResponse, maxSizeBytes);
|
|
583
775
|
if (sourceMapContent === null) return null;
|
|
584
776
|
try {
|
|
@@ -593,13 +785,15 @@ const getSourceMapUncachedInternal = async (
|
|
|
593
785
|
fetchFn?: SourceFetch,
|
|
594
786
|
options: SourceMapRequestOptions = {},
|
|
595
787
|
): Promise<null | SourceMap> => {
|
|
596
|
-
|
|
788
|
+
const shouldAllowCustomProtocol = fetchFn !== undefined;
|
|
789
|
+
if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
|
|
597
790
|
return null;
|
|
598
791
|
}
|
|
599
792
|
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
793
|
+
const isServer = isServerRuntime();
|
|
794
|
+
const shouldRejectRedirects = isServer || isExtensionUrl(bundleUrl);
|
|
795
|
+
if (isServer && !fetchFn) {
|
|
796
|
+
if (!options.allowUnsafeServerFetch || !isAbsoluteHttpUrl(bundleUrl)) return null;
|
|
603
797
|
}
|
|
604
798
|
|
|
605
799
|
const sourceFetch =
|
|
@@ -616,15 +810,18 @@ const getSourceMapUncachedInternal = async (
|
|
|
616
810
|
signal: requestSignal.signal,
|
|
617
811
|
});
|
|
618
812
|
if (!assertResponseIsCacheable(bundleResponse)) return null;
|
|
813
|
+
if (!isResponseUrlAllowed(bundleUrl, bundleResponse, shouldRejectRedirects)) return null;
|
|
619
814
|
const bundleContent = await readResponseText(bundleResponse, maxBundleSizeBytes);
|
|
620
|
-
if (
|
|
621
|
-
const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent);
|
|
815
|
+
if (bundleContent === null) return null;
|
|
816
|
+
const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent, bundleResponse);
|
|
622
817
|
if (!sourceMapUrl) return null;
|
|
623
|
-
if (!isFetchableUrl(sourceMapUrl) && !INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) return null;
|
|
624
818
|
if (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
819
|
+
!isAllowedSourceMapUrl(
|
|
820
|
+
bundleUrl,
|
|
821
|
+
sourceMapUrl,
|
|
822
|
+
shouldRejectRedirects,
|
|
823
|
+
shouldAllowCustomProtocol,
|
|
824
|
+
)
|
|
628
825
|
) {
|
|
629
826
|
return null;
|
|
630
827
|
}
|
|
@@ -642,9 +839,12 @@ const getSourceMapUncachedInternal = async (
|
|
|
642
839
|
if (!isIndexSourceMap(rawSourceMap)) return null;
|
|
643
840
|
return decodeIndexSourceMap(rawSourceMap, sourceMapUrl, async (sectionUrl) => {
|
|
644
841
|
if (
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
842
|
+
!isAllowedSourceMapUrl(
|
|
843
|
+
bundleUrl,
|
|
844
|
+
sectionUrl,
|
|
845
|
+
shouldRejectRedirects,
|
|
846
|
+
shouldAllowCustomProtocol,
|
|
847
|
+
)
|
|
648
848
|
) {
|
|
649
849
|
return null;
|
|
650
850
|
}
|
|
@@ -675,27 +875,27 @@ export const getSourceMapUncached = async (
|
|
|
675
875
|
}
|
|
676
876
|
};
|
|
677
877
|
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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;
|
|
686
890
|
};
|
|
687
891
|
|
|
892
|
+
const getSourceMapCache = (fetchFn: SourceFetch | undefined): Map<string, null | SourceMap> =>
|
|
893
|
+
getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, fetchFn);
|
|
894
|
+
|
|
688
895
|
const getPendingSourceMapRequests = (
|
|
689
896
|
fetchFn: SourceFetch | undefined,
|
|
690
|
-
): Map<string, Promise<SourceMapResult>> =>
|
|
691
|
-
|
|
692
|
-
let pendingRequests = pendingSourceMapRequestsByFetch.get(fetchFn);
|
|
693
|
-
if (!pendingRequests) {
|
|
694
|
-
pendingRequests = new Map();
|
|
695
|
-
pendingSourceMapRequestsByFetch.set(fetchFn, pendingRequests);
|
|
696
|
-
}
|
|
697
|
-
return pendingRequests;
|
|
698
|
-
};
|
|
897
|
+
): Map<string, Promise<SourceMapResult>> =>
|
|
898
|
+
getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, fetchFn);
|
|
699
899
|
|
|
700
900
|
const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
|
|
701
901
|
options.allowUnsafeServerFetch === undefined &&
|
|
@@ -750,7 +950,7 @@ export const getSourceMap = async (
|
|
|
750
950
|
export const symbolicateStack = async (
|
|
751
951
|
stack: StackFrame[],
|
|
752
952
|
cache = true,
|
|
753
|
-
fetchFn?:
|
|
953
|
+
fetchFn?: SourceFetch,
|
|
754
954
|
): Promise<StackFrame[]> => {
|
|
755
955
|
return Promise.all(
|
|
756
956
|
stack.map(async (stackFrame) => {
|
|
@@ -776,6 +976,7 @@ export const symbolicateStack = async (
|
|
|
776
976
|
? stackFrame.source.replace(stackFrame.fileName, symbolicatedSource.fileName)
|
|
777
977
|
: stackFrame.source,
|
|
778
978
|
fileName: symbolicatedSource.fileName,
|
|
979
|
+
functionName: symbolicatedSource.functionName ?? stackFrame.functionName,
|
|
779
980
|
lineNumber: symbolicatedSource.lineNumber,
|
|
780
981
|
columnNumber: symbolicatedSource.columnNumber,
|
|
781
982
|
isIgnoreListed: symbolicatedSource.isIgnoreListed,
|