bippy 0.6.1-dev.d7876ea → 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/LICENSE +1 -1
- package/README.md +159 -487
- package/dist/core.cjs +1 -1
- package/dist/core.d.cts +24 -70
- package/dist/core.d.ts +24 -70
- 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/errors.d.cts +33 -59
- package/dist/errors.d.ts +33 -59
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -3
- package/dist/index.d.ts +14 -3
- 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.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 +85 -77
- package/dist/source.d.ts +85 -77
- package/dist/source.js +12 -13
- package/package.json +6 -3
- package/src/core.ts +141 -564
- 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 +39 -45
- package/src/{generated/react-work-tags.js → react-internals/generated/react-work-tags.ts} +85 -7
- package/src/{react-internals.ts → react-internals/index.ts} +6 -4
- package/src/{semver.ts → react-internals/semver.ts} +2 -0
- package/src/{types.ts → react-internals/types.ts} +1 -84
- package/src/react.ts +76 -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 +2 -0
- package/src/source/inspect-hooks.ts +74 -96
- package/src/source/owner-stack.ts +66 -91
- 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 +421 -108
- package/dist/index.iife.js +0 -9
- package/dist/install-hook-only.iife.js +0 -9
- package/src/generated/react-work-tags.d.ts +0 -261
- package/src/unsubscribe.ts +0 -17
|
@@ -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: {
|
|
@@ -38,6 +39,7 @@ export interface SourceFetch {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface SourceMapRequestOptions {
|
|
42
|
+
allowUnsafeServerFetch?: boolean;
|
|
41
43
|
maxBundleSizeBytes?: number;
|
|
42
44
|
maxSourceMapSizeBytes?: number;
|
|
43
45
|
signal?: AbortSignal;
|
|
@@ -68,7 +70,6 @@ export interface StandardSourceMap {
|
|
|
68
70
|
x_google_ignoreList?: number[];
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
const SCHEME_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
|
|
72
73
|
const INLINE_SOURCEMAP_REGEX = /^data:application\/json(?:;[^,]*)?,/i;
|
|
73
74
|
const SOURCEMAP_REGEX =
|
|
74
75
|
/(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*(?:\*\/)[ \t]*$)/;
|
|
@@ -80,13 +81,44 @@ interface SourceMapResult {
|
|
|
80
81
|
isTransientFailure: boolean;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
class TransientSourceMapError extends Error {}
|
|
85
|
+
|
|
86
|
+
const pendingSourceMapRequests = new Map<string, Promise<SourceMapResult>>();
|
|
84
87
|
const pendingSourceMapRequestsByFetch = new WeakMap<
|
|
85
88
|
SourceFetch,
|
|
86
89
|
Map<string, Promise<SourceMapResult>>
|
|
87
90
|
>();
|
|
88
91
|
const defaultMaxBundleSizeBytes = 25 * 1024 * 1024;
|
|
89
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
|
+
};
|
|
90
122
|
|
|
91
123
|
const getSourceFromMappings = (
|
|
92
124
|
mappings: SourceMapMappings,
|
|
@@ -94,6 +126,7 @@ const getSourceFromMappings = (
|
|
|
94
126
|
lineIndexInMappings: number,
|
|
95
127
|
column: number,
|
|
96
128
|
ignoredSourceIndices?: Set<number>,
|
|
129
|
+
names?: string[],
|
|
97
130
|
): StackFrame | null => {
|
|
98
131
|
if (lineIndexInMappings < 0 || lineIndexInMappings >= mappings.length) {
|
|
99
132
|
return null;
|
|
@@ -121,24 +154,7 @@ const getSourceFromMappings = (
|
|
|
121
154
|
return null;
|
|
122
155
|
}
|
|
123
156
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (sourceIndex === undefined || sourceLine === undefined || sourceColumn === undefined) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const fileName = sources[sourceIndex];
|
|
131
|
-
|
|
132
|
-
if (!fileName) {
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
columnNumber: sourceColumn,
|
|
138
|
-
fileName,
|
|
139
|
-
lineNumber: sourceLine + 1,
|
|
140
|
-
isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
|
|
141
|
-
};
|
|
157
|
+
return getSourceFromSegment(closestLineSegment, sources, ignoredSourceIndices, names);
|
|
142
158
|
};
|
|
143
159
|
|
|
144
160
|
export const getSourceFromSourceMap = (
|
|
@@ -175,6 +191,7 @@ export const getSourceFromSourceMap = (
|
|
|
175
191
|
relativeLine,
|
|
176
192
|
relativeColumn,
|
|
177
193
|
targetSection.map.ignoredSourceIndices,
|
|
194
|
+
targetSection.map.names,
|
|
178
195
|
);
|
|
179
196
|
}
|
|
180
197
|
|
|
@@ -184,9 +201,97 @@ export const getSourceFromSourceMap = (
|
|
|
184
201
|
line - 1,
|
|
185
202
|
column,
|
|
186
203
|
sourceMap.ignoredSourceIndices,
|
|
204
|
+
sourceMap.names,
|
|
187
205
|
);
|
|
188
206
|
};
|
|
189
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,
|
|
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
|
+
|
|
190
295
|
const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
191
296
|
if (INLINE_SOURCEMAP_REGEX.test(reference)) return reference;
|
|
192
297
|
try {
|
|
@@ -201,12 +306,43 @@ const resolveUrl = (reference: string, baseUrl: string): string | null => {
|
|
|
201
306
|
}
|
|
202
307
|
};
|
|
203
308
|
|
|
204
|
-
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
|
+
|
|
205
341
|
let sourceMapUrl: string | undefined;
|
|
206
|
-
let searchEnd =
|
|
342
|
+
let searchEnd = bundleContent.length;
|
|
207
343
|
while (searchEnd > 0 && !sourceMapUrl) {
|
|
208
|
-
const lineStart =
|
|
209
|
-
const regexMatch =
|
|
344
|
+
const lineStart = bundleContent.lastIndexOf("\n", searchEnd - 1) + 1;
|
|
345
|
+
const regexMatch = bundleContent.slice(lineStart, searchEnd).match(SOURCEMAP_REGEX);
|
|
210
346
|
if (regexMatch) {
|
|
211
347
|
sourceMapUrl = regexMatch[1] || regexMatch[2];
|
|
212
348
|
}
|
|
@@ -214,65 +350,82 @@ const getSourceMapUrl = (url: string, content: string): null | string => {
|
|
|
214
350
|
}
|
|
215
351
|
|
|
216
352
|
if (!sourceMapUrl) {
|
|
217
|
-
return
|
|
353
|
+
return getMetroSourceMapUrl(bundleUrl);
|
|
218
354
|
}
|
|
219
355
|
|
|
220
|
-
return resolveUrl(sourceMapUrl,
|
|
356
|
+
return resolveUrl(sourceMapUrl, bundleUrl);
|
|
221
357
|
};
|
|
222
358
|
|
|
223
|
-
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
|
|
224
|
-
typeof value === "object" && value !== null;
|
|
225
|
-
|
|
226
|
-
const isStringArray = (value: unknown): value is string[] =>
|
|
227
|
-
Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
228
|
-
|
|
229
|
-
const isSourcesContent = (value: unknown): value is Array<string | null> =>
|
|
230
|
-
Array.isArray(value) && value.every((entry) => typeof entry === "string" || entry === null);
|
|
231
|
-
|
|
232
|
-
const isOptionalNumberArray = (value: unknown): value is number[] | undefined =>
|
|
233
|
-
value === undefined ||
|
|
234
|
-
(Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0));
|
|
235
|
-
|
|
236
359
|
const isStandardSourceMap = (value: unknown): value is StandardSourceMap => {
|
|
237
|
-
if (
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
(
|
|
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;
|
|
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,
|
|
252
394
|
);
|
|
253
395
|
};
|
|
254
396
|
|
|
255
397
|
const isIndexSourceMap = (value: unknown): value is IndexSourceMap => {
|
|
256
|
-
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)) {
|
|
257
402
|
return false;
|
|
258
403
|
}
|
|
259
|
-
return
|
|
260
|
-
if (
|
|
261
|
-
const
|
|
262
|
-
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;
|
|
263
414
|
return (
|
|
264
415
|
hasMap !== hasUrl &&
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
416
|
+
typeof offsetColumn === "number" &&
|
|
417
|
+
Number.isInteger(offsetColumn) &&
|
|
418
|
+
offsetColumn >= 0 &&
|
|
419
|
+
typeof offsetLine === "number" &&
|
|
420
|
+
Number.isInteger(offsetLine) &&
|
|
421
|
+
offsetLine >= 0
|
|
269
422
|
);
|
|
270
423
|
});
|
|
271
424
|
};
|
|
272
425
|
|
|
273
426
|
const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> | undefined => {
|
|
274
427
|
const ignoreList = rawSourceMap.ignoreList ?? rawSourceMap.x_google_ignoreList;
|
|
275
|
-
return
|
|
428
|
+
return ignoreList?.length ? new Set(ignoreList) : undefined;
|
|
276
429
|
};
|
|
277
430
|
|
|
278
431
|
const resolveSourceRoot = (
|
|
@@ -386,7 +539,7 @@ const decodeIndexSourceMap = async (
|
|
|
386
539
|
};
|
|
387
540
|
};
|
|
388
541
|
|
|
389
|
-
const isFetchableUrl = (url: string): boolean => {
|
|
542
|
+
const isFetchableUrl = (url: string, shouldAllowCustomProtocol = false): boolean => {
|
|
390
543
|
if (!url) {
|
|
391
544
|
return false;
|
|
392
545
|
}
|
|
@@ -405,17 +558,100 @@ const isFetchableUrl = (url: string): boolean => {
|
|
|
405
558
|
|
|
406
559
|
const scheme = schemeMatch[0].toLowerCase();
|
|
407
560
|
|
|
408
|
-
return
|
|
561
|
+
return shouldAllowCustomProtocol || defaultFetchableProtocols.has(scheme);
|
|
562
|
+
};
|
|
563
|
+
|
|
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 {
|
|
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
|
+
|
|
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;
|
|
605
|
+
}
|
|
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();
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
409
638
|
};
|
|
410
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
|
+
|
|
411
647
|
interface RequestSignal {
|
|
412
648
|
cleanup: () => void;
|
|
413
649
|
signal?: AbortSignal;
|
|
414
650
|
}
|
|
415
651
|
|
|
416
652
|
const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal => {
|
|
417
|
-
if (
|
|
418
|
-
return { cleanup: () => {} };
|
|
653
|
+
if (options.timeoutMs === undefined || options.timeoutMs < 0) {
|
|
654
|
+
return { cleanup: () => {}, signal: options.signal };
|
|
419
655
|
}
|
|
420
656
|
const abortController = new AbortController();
|
|
421
657
|
const abortFromSource = (): void => abortController.abort(options.signal?.reason);
|
|
@@ -424,16 +660,13 @@ const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal =>
|
|
|
424
660
|
} else {
|
|
425
661
|
options.signal?.addEventListener("abort", abortFromSource, { once: true });
|
|
426
662
|
}
|
|
427
|
-
const timeoutHandle =
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
options.timeoutMs,
|
|
432
|
-
)
|
|
433
|
-
: undefined;
|
|
663
|
+
const timeoutHandle = setTimeout(
|
|
664
|
+
() => abortController.abort(new BippySourceMapError("Source map request timed out")),
|
|
665
|
+
options.timeoutMs,
|
|
666
|
+
);
|
|
434
667
|
return {
|
|
435
668
|
cleanup: () => {
|
|
436
|
-
|
|
669
|
+
clearTimeout(timeoutHandle);
|
|
437
670
|
options.signal?.removeEventListener("abort", abortFromSource);
|
|
438
671
|
},
|
|
439
672
|
signal: abortController.signal,
|
|
@@ -446,8 +679,32 @@ const readResponseText = async (
|
|
|
446
679
|
): Promise<string | null> => {
|
|
447
680
|
const contentLength = Number(response.headers.get("content-length"));
|
|
448
681
|
if (Number.isFinite(contentLength) && contentLength > maxSizeBytes) return null;
|
|
449
|
-
|
|
450
|
-
|
|
682
|
+
if (!response.body) {
|
|
683
|
+
const responseText = await response.text();
|
|
684
|
+
return new TextEncoder().encode(responseText).byteLength > maxSizeBytes ? null : responseText;
|
|
685
|
+
}
|
|
686
|
+
|
|
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
|
+
}
|
|
451
708
|
};
|
|
452
709
|
|
|
453
710
|
const decodeBase64 = (encodedContent: string): string | null => {
|
|
@@ -503,12 +760,17 @@ const readSourceMapDocument = async (
|
|
|
503
760
|
sourceFetch: SourceFetch,
|
|
504
761
|
signal: AbortSignal | undefined,
|
|
505
762
|
maxSizeBytes: number,
|
|
763
|
+
shouldRejectRedirects: boolean,
|
|
506
764
|
): Promise<unknown> => {
|
|
507
765
|
if (INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) {
|
|
508
766
|
return readInlineSourceMap(sourceMapUrl, maxSizeBytes);
|
|
509
767
|
}
|
|
510
|
-
const sourceMapResponse = await sourceFetch(sourceMapUrl, {
|
|
511
|
-
|
|
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;
|
|
512
774
|
const sourceMapContent = await readResponseText(sourceMapResponse, maxSizeBytes);
|
|
513
775
|
if (sourceMapContent === null) return null;
|
|
514
776
|
try {
|
|
@@ -518,15 +780,22 @@ const readSourceMapDocument = async (
|
|
|
518
780
|
}
|
|
519
781
|
};
|
|
520
782
|
|
|
521
|
-
|
|
783
|
+
const getSourceMapUncachedInternal = async (
|
|
522
784
|
bundleUrl: string,
|
|
523
785
|
fetchFn?: SourceFetch,
|
|
524
786
|
options: SourceMapRequestOptions = {},
|
|
525
787
|
): Promise<null | SourceMap> => {
|
|
526
|
-
|
|
788
|
+
const shouldAllowCustomProtocol = fetchFn !== undefined;
|
|
789
|
+
if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
|
|
527
790
|
return null;
|
|
528
791
|
}
|
|
529
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
|
+
|
|
530
799
|
const sourceFetch =
|
|
531
800
|
fetchFn ??
|
|
532
801
|
(typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined);
|
|
@@ -536,30 +805,55 @@ export const getSourceMapUncached = async (
|
|
|
536
805
|
if (maxBundleSizeBytes < 0 || maxSourceMapSizeBytes < 0) return null;
|
|
537
806
|
const requestSignal = createRequestSignal(options);
|
|
538
807
|
try {
|
|
539
|
-
const bundleResponse = await sourceFetch(bundleUrl, {
|
|
540
|
-
|
|
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;
|
|
541
814
|
const bundleContent = await readResponseText(bundleResponse, maxBundleSizeBytes);
|
|
542
|
-
if (
|
|
543
|
-
const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent);
|
|
815
|
+
if (bundleContent === null) return null;
|
|
816
|
+
const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent, bundleResponse);
|
|
544
817
|
if (!sourceMapUrl) return null;
|
|
545
|
-
if (
|
|
818
|
+
if (
|
|
819
|
+
!isAllowedSourceMapUrl(
|
|
820
|
+
bundleUrl,
|
|
821
|
+
sourceMapUrl,
|
|
822
|
+
shouldRejectRedirects,
|
|
823
|
+
shouldAllowCustomProtocol,
|
|
824
|
+
)
|
|
825
|
+
) {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
546
828
|
|
|
547
829
|
const rawSourceMap = await readSourceMapDocument(
|
|
548
830
|
sourceMapUrl,
|
|
549
831
|
sourceFetch,
|
|
550
832
|
requestSignal.signal,
|
|
551
833
|
maxSourceMapSizeBytes,
|
|
834
|
+
shouldRejectRedirects,
|
|
552
835
|
);
|
|
553
836
|
if (isStandardSourceMap(rawSourceMap)) {
|
|
554
837
|
return decodeStandardSourceMap(rawSourceMap, sourceMapUrl);
|
|
555
838
|
}
|
|
556
839
|
if (!isIndexSourceMap(rawSourceMap)) return null;
|
|
557
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
|
+
}
|
|
558
851
|
const sectionSourceMap = await readSourceMapDocument(
|
|
559
852
|
sectionUrl,
|
|
560
853
|
sourceFetch,
|
|
561
854
|
requestSignal.signal,
|
|
562
855
|
maxSourceMapSizeBytes,
|
|
856
|
+
shouldRejectRedirects,
|
|
563
857
|
);
|
|
564
858
|
return isStandardSourceMap(sectionSourceMap) ? sectionSourceMap : null;
|
|
565
859
|
});
|
|
@@ -568,34 +862,48 @@ export const getSourceMapUncached = async (
|
|
|
568
862
|
}
|
|
569
863
|
};
|
|
570
864
|
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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;
|
|
577
875
|
}
|
|
578
|
-
return cache;
|
|
579
876
|
};
|
|
580
877
|
|
|
581
|
-
const
|
|
878
|
+
const getPerFetchMap = <Value>(
|
|
879
|
+
mapsByFetch: WeakMap<SourceFetch, Map<string, Value>>,
|
|
880
|
+
globalMap: Map<string, Value>,
|
|
582
881
|
fetchFn: SourceFetch | undefined,
|
|
583
|
-
): Map<string,
|
|
584
|
-
if (!fetchFn) return
|
|
585
|
-
let
|
|
586
|
-
if (!
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
return
|
|
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;
|
|
591
890
|
};
|
|
592
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
|
+
|
|
593
900
|
const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
|
|
901
|
+
options.allowUnsafeServerFetch === undefined &&
|
|
594
902
|
options.maxBundleSizeBytes === undefined &&
|
|
595
903
|
options.maxSourceMapSizeBytes === undefined &&
|
|
596
904
|
options.timeoutMs === undefined
|
|
597
905
|
? file
|
|
598
|
-
: `${file}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
906
|
+
: `${file}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
|
|
599
907
|
|
|
600
908
|
export const getSourceMap = async (
|
|
601
909
|
file: string,
|
|
@@ -616,7 +924,11 @@ export const getSourceMap = async (
|
|
|
616
924
|
return (await pendingRequest).sourceMap;
|
|
617
925
|
}
|
|
618
926
|
|
|
619
|
-
const fetchPromise: Promise<SourceMapResult> =
|
|
927
|
+
const fetchPromise: Promise<SourceMapResult> = getSourceMapUncachedInternal(
|
|
928
|
+
file,
|
|
929
|
+
fetchFn,
|
|
930
|
+
options,
|
|
931
|
+
).then(
|
|
620
932
|
(sourceMap) => ({ sourceMap, isTransientFailure: false }),
|
|
621
933
|
() => ({ sourceMap: null, isTransientFailure: true }),
|
|
622
934
|
);
|
|
@@ -638,7 +950,7 @@ export const getSourceMap = async (
|
|
|
638
950
|
export const symbolicateStack = async (
|
|
639
951
|
stack: StackFrame[],
|
|
640
952
|
cache = true,
|
|
641
|
-
fetchFn?:
|
|
953
|
+
fetchFn?: SourceFetch,
|
|
642
954
|
): Promise<StackFrame[]> => {
|
|
643
955
|
return Promise.all(
|
|
644
956
|
stack.map(async (stackFrame) => {
|
|
@@ -664,6 +976,7 @@ export const symbolicateStack = async (
|
|
|
664
976
|
? stackFrame.source.replace(stackFrame.fileName, symbolicatedSource.fileName)
|
|
665
977
|
: stackFrame.source,
|
|
666
978
|
fileName: symbolicatedSource.fileName,
|
|
979
|
+
functionName: symbolicatedSource.functionName ?? stackFrame.functionName,
|
|
667
980
|
lineNumber: symbolicatedSource.lineNumber,
|
|
668
981
|
columnNumber: symbolicatedSource.columnNumber,
|
|
669
982
|
isIgnoreListed: symbolicatedSource.isIgnoreListed,
|