bippy 0.7.2 → 0.7.3-dev.04de4f7

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