bippy 0.7.2 → 0.7.3-dev.0ac832d

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