bippy 0.7.2-dev.b756bd6 → 0.7.2-dev.e113600

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,3 +1,5 @@
1
+ import { getRenderer } from "../core.js";
2
+ import type { ReactDevToolsTarget } from "../rdt-hook.js";
1
3
  import type {
2
4
  Fiber,
3
5
  ContextDependency,
@@ -548,6 +550,19 @@ const parseHookName = (functionName: string | undefined): string => {
548
550
  return functionName.slice(startIndex);
549
551
  };
550
552
 
553
+ // Unlike upstream, bippy routes every dispatcher method through a shared log helper, so those
554
+ // frames appear in captured stacks and must not become tree levels. Names are read off the
555
+ // live functions rather than matched by prefix, so user hooks cannot collide and minified
556
+ // builds stay accurate.
557
+ const INTERNAL_HOOK_FRAME_NAMES = new Set(
558
+ [pushHookLogEntry, ...Object.values(dispatcher)].map((internalFunction) =>
559
+ parseHookName(internalFunction.name),
560
+ ),
561
+ );
562
+
563
+ const isInternalHookFrame = (hookName: string): boolean =>
564
+ hookName !== "" && INTERNAL_HOOK_FRAME_NAMES.has(hookName);
565
+
551
566
  const isReactWrapper = (functionName: string | undefined, wrapperName: string): boolean => {
552
567
  const hookName = parseHookName(functionName);
553
568
  if (wrapperName === "HostTransitionStatus") {
@@ -618,8 +633,10 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
618
633
  const [primitiveFrame, stack] = parseTrimmedStack(rootStack, hook);
619
634
  let displayName = hook.displayName;
620
635
  if (displayName === null && primitiveFrame !== null) {
621
- displayName =
622
- parseHookName(primitiveFrame.functionName) || parseHookName(hook.dispatcherHookName);
636
+ const primitiveName = parseHookName(primitiveFrame.functionName);
637
+ displayName = isInternalHookFrame(primitiveName)
638
+ ? null
639
+ : primitiveName || parseHookName(hook.dispatcherHookName);
623
640
  }
624
641
 
625
642
  if (stack !== null) {
@@ -684,17 +701,40 @@ const buildTree = (rootStack: StackFrame[], capturedHookLog: HookLogEntry[]): Ho
684
701
  }
685
702
 
686
703
  processDebugValues(rootChildren, null);
704
+ removeInternalHookFrames(rootChildren, null);
687
705
  return rootChildren;
688
706
  };
689
707
 
708
+ const removeInternalHookFrames = (
709
+ hooksTree: HooksTree,
710
+ parentHooksNode: HooksNode | null,
711
+ ): void => {
712
+ for (let nodeIndex = 0; nodeIndex < hooksTree.length; nodeIndex++) {
713
+ const hooksNode = hooksTree[nodeIndex];
714
+ if (isInternalHookFrame(hooksNode.name)) {
715
+ if (parentHooksNode !== null && hooksNode.value !== undefined) {
716
+ if (parentHooksNode.value === undefined) parentHooksNode.value = hooksNode.value;
717
+ else if (Array.isArray(parentHooksNode.value)) {
718
+ parentHooksNode.value = [...parentHooksNode.value, hooksNode.value];
719
+ } else parentHooksNode.value = [parentHooksNode.value, hooksNode.value];
720
+ }
721
+ hooksTree.splice(nodeIndex, 1, ...hooksNode.subHooks);
722
+ nodeIndex--;
723
+ } else {
724
+ removeInternalHookFrames(hooksNode.subHooks, hooksNode);
725
+ }
726
+ }
727
+ };
728
+
690
729
  const processDebugValues = (hooksTree: HooksTree, parentHooksNode: HooksNode | null): void => {
691
730
  const debugValueNodes: HooksNode[] = [];
692
731
  for (let nodeIndex = 0; nodeIndex < hooksTree.length; nodeIndex++) {
693
732
  const hooksNode = hooksTree[nodeIndex];
694
- if (hooksNode.name === "DebugValue" && hooksNode.subHooks.length === 0) {
695
- hooksTree.splice(nodeIndex, 1);
733
+ if (hooksNode.name === "DebugValue") {
734
+ hooksTree.splice(nodeIndex, 1, ...hooksNode.subHooks);
696
735
  nodeIndex--;
697
- debugValueNodes.push(hooksNode);
736
+ // Internal frames surface as valueless DebugValue levels; only real labels bubble up.
737
+ if (hooksNode.value !== undefined) debugValueNodes.push(hooksNode);
698
738
  } else {
699
739
  processDebugValues(hooksNode.subHooks, hooksNode);
700
740
  }
@@ -785,9 +825,12 @@ const restoreConsole = (originalMethods: Record<string, unknown>): void => {
785
825
  }
786
826
  };
787
827
 
788
- const performDispatcherInspection = (
828
+ // The render call must happen in the same frame that captures the ancestor stack,
829
+ // otherwise an extra frame lands in every hook's trimmed stack and becomes a bogus tree level.
830
+ const performDispatcherInspection = <TArgs extends unknown[]>(
789
831
  dispatcherRef: RendererDispatcherRef,
790
- renderFn: () => void,
832
+ renderFunction: (...args: TArgs) => unknown,
833
+ ...renderArgs: TArgs
791
834
  ): HooksTree => {
792
835
  const previousDispatcher = readDispatcher(dispatcherRef);
793
836
  writeDispatcher(dispatcherRef, dispatcherProxy);
@@ -797,7 +840,7 @@ const performDispatcherInspection = (
797
840
 
798
841
  try {
799
842
  ancestorStackError = new Error();
800
- renderFn();
843
+ renderFunction(...renderArgs);
801
844
  } catch (renderError) {
802
845
  handleRenderFunctionError(renderError);
803
846
  } finally {
@@ -810,8 +853,12 @@ const performDispatcherInspection = (
810
853
  return buildTree(rootStack, capturedHookLog);
811
854
  };
812
855
 
813
- const requireDispatcherRef = (): RendererDispatcherRef => {
814
- const dispatcherRef = getRendererDispatcherRefs()[0];
856
+ const requireDispatcherRef = (
857
+ fiber?: Fiber,
858
+ target: ReactDevToolsTarget = globalThis,
859
+ ): RendererDispatcherRef => {
860
+ const rendererDispatcherRef = fiber ? getRenderer(fiber, target)?.currentDispatcherRef : null;
861
+ const dispatcherRef = rendererDispatcherRef ?? getRendererDispatcherRefs(target)[0];
815
862
  if (!dispatcherRef) {
816
863
  throw new BippyHookInspectionError(
817
864
  "Bippy couldn’t find a React renderer. Load React and install Bippy’s hook.",
@@ -848,8 +895,73 @@ const resolveContextDependency = (fiber: Fiber): void => {
848
895
  }
849
896
  };
850
897
 
898
+ const normalizeStandaloneHooks = (hooksTree: HooksTree): HooksTree => {
899
+ const normalizeNode = (hooksNode: HooksNode): HooksNode => {
900
+ const subHooks = hooksNode.subHooks.map(normalizeNode);
901
+ if (subHooks.length === 1) {
902
+ const childHook = subHooks[0];
903
+ if (hooksNode.name === "Context" && childHook.id === null) {
904
+ return { ...childHook, hookSource: hooksNode.hookSource };
905
+ }
906
+ if (hooksNode.name === "FormStatus" && childHook.name === "HostTransitionStatus") {
907
+ return { ...hooksNode, subHooks: [], value: childHook.value };
908
+ }
909
+ if (hooksNode.name === "Use") {
910
+ if (childHook.name === "Context") {
911
+ return { ...childHook, hookSource: hooksNode.hookSource };
912
+ }
913
+ if (childHook.name === "Promise" || childHook.name === "Unresolved") {
914
+ return {
915
+ ...hooksNode,
916
+ debugInfo: childHook.debugInfo,
917
+ subHooks: [],
918
+ value: childHook.value,
919
+ };
920
+ }
921
+ }
922
+ if (
923
+ hooksNode.id === null &&
924
+ hooksNode.value === undefined &&
925
+ (hooksNode.name === childHook.name || hooksNode.name === "<anonymous>")
926
+ ) {
927
+ return { ...childHook, hookSource: hooksNode.hookSource };
928
+ }
929
+ }
930
+ return { ...hooksNode, subHooks };
931
+ };
932
+
933
+ return hooksTree.map(normalizeNode);
934
+ };
935
+
936
+ export const inspectHooks = (
937
+ renderFunction: (props: Record<string, unknown>) => unknown,
938
+ props: Record<string, unknown>,
939
+ target: ReactDevToolsTarget = globalThis,
940
+ ): HooksTree => {
941
+ const dispatcherRef = requireDispatcherRef(undefined, target);
942
+ getPrimitiveStackCache();
943
+ currentHook = null;
944
+ currentFiber = null;
945
+ currentContextDependency = null;
946
+ currentThenableState = null;
947
+ currentThenableIndex = 0;
948
+ const originalConsoleMethods = suppressConsole();
949
+ try {
950
+ return normalizeStandaloneHooks(
951
+ performDispatcherInspection(dispatcherRef, renderFunction, props),
952
+ );
953
+ } finally {
954
+ currentHook = null;
955
+ currentFiber = null;
956
+ currentContextDependency = null;
957
+ currentThenableState = null;
958
+ currentThenableIndex = 0;
959
+ restoreConsole(originalConsoleMethods);
960
+ }
961
+ };
962
+
851
963
  export const getFiberHooks = (fiber: Fiber): HooksTree => {
852
- const dispatcherRef = requireDispatcherRef();
964
+ const dispatcherRef = requireDispatcherRef(fiber);
853
965
  const workTags = getReactWorkTagsForFiber(fiber);
854
966
 
855
967
  if (
@@ -895,9 +1007,15 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
895
1007
  if (!isForwardRefRenderType(type)) {
896
1008
  throw new BippyHookInspectionError("ForwardRef fiber is missing its render function.");
897
1009
  }
898
- return performDispatcherInspection(dispatcherRef, () => {
899
- type.render(props, fiber.ref);
900
- });
1010
+ const hooksTree = normalizeStandaloneHooks(
1011
+ performDispatcherInspection(dispatcherRef, type.render, props, fiber.ref),
1012
+ );
1013
+ for (const hooksNode of hooksTree) {
1014
+ if (hooksNode.hookSource?.functionName === "Object.render") {
1015
+ hooksNode.hookSource.functionName = null;
1016
+ }
1017
+ }
1018
+ return hooksTree;
901
1019
  }
902
1020
 
903
1021
  if (typeof type !== "function") {
@@ -905,9 +1023,7 @@ export const getFiberHooks = (fiber: Fiber): HooksTree => {
905
1023
  "Function component fiber is missing its component function.",
906
1024
  );
907
1025
  }
908
- return performDispatcherInspection(dispatcherRef, () => {
909
- type(props);
910
- });
1026
+ return normalizeStandaloneHooks(performDispatcherInspection(dispatcherRef, type, props));
911
1027
  } finally {
912
1028
  currentFiber = null;
913
1029
  currentHook = null;
@@ -18,7 +18,11 @@ import {
18
18
  readDispatcher,
19
19
  writeDispatcher,
20
20
  } from "./renderer-dispatchers.js";
21
- import { symbolicateStack, type SourceFetch } from "./symbolication.js";
21
+ import {
22
+ symbolicateStack,
23
+ type SourceFetch,
24
+ type SourceMapRequestOptions,
25
+ } from "./symbolication.js";
22
26
 
23
27
  interface RendererDispatcherSnapshot {
24
28
  currentDispatcherRef: RendererDispatcherRef;
@@ -534,7 +538,7 @@ const markFlightServerFrame = (stackFrame: StackFrame): StackFrame =>
534
538
  * carries `.debugStack`). This is exact - no re-invoking components, no
535
539
  * name-matching heuristics - but requires React 19.
536
540
  */
537
- const getOwnerStackFromDebugStacks = (fiber: Fiber): StackFrame[] => {
541
+ export const getRawOwnerStack = (fiber: Fiber): StackFrame[] => {
538
542
  const ownerStackFrames: StackFrame[] = [];
539
543
  let owner: Fiber | ServerComponentInfo | null | undefined = fiber;
540
544
  while (owner) {
@@ -625,9 +629,11 @@ export const getRawParentStack = (fiber: Fiber): StackFrame[] => {
625
629
  */
626
630
  export const getParentStack = async (
627
631
  fiber: Fiber,
628
- shouldCache = true,
629
- fetchFunction?: SourceFetch,
630
- ): Promise<StackFrame[]> => symbolicateStack(getRawParentStack(fiber), shouldCache, fetchFunction);
632
+ shouldUseCache = true,
633
+ sourceFetch?: SourceFetch,
634
+ requestOptions: SourceMapRequestOptions = {},
635
+ ): Promise<StackFrame[]> =>
636
+ symbolicateStack(getRawParentStack(fiber), shouldUseCache, sourceFetch, requestOptions);
631
637
 
632
638
  // an owner frame is only actionable if it can point an editor somewhere:
633
639
  // it needs a file location and must not be ignore-listed bundler/framework code
@@ -646,10 +652,11 @@ const isLocatableFrame = (stackFrame: StackFrame): boolean =>
646
652
  */
647
653
  export const getOwnerStack = async (
648
654
  fiber: Fiber,
649
- shouldCache = true,
650
- fetchFunction?: SourceFetch,
655
+ shouldUseCache = true,
656
+ sourceFetch?: SourceFetch,
657
+ requestOptions: SourceMapRequestOptions = {},
651
658
  ): Promise<StackFrame[]> => {
652
- const debugStackFrames = getOwnerStackFromDebugStacks(fiber);
659
+ const debugStackFrames = getRawOwnerStack(fiber);
653
660
  if (debugStackFrames.length > 0) {
654
661
  // the owner chain does not include the fiber itself, but bippy's stacks
655
662
  // always start with the fiber's own frame
@@ -657,8 +664,9 @@ export const getOwnerStack = async (
657
664
  selfFrame.functionName = getDisplayName(fiber.type) ?? selfFrame.functionName;
658
665
  const symbolicatedFrames = await symbolicateStack(
659
666
  [selfFrame, ...debugStackFrames],
660
- shouldCache,
661
- fetchFunction,
667
+ shouldUseCache,
668
+ sourceFetch,
669
+ requestOptions,
662
670
  );
663
671
  const hasLocatableOwnerFrame = symbolicatedFrames.some(
664
672
  (stackFrame, frameIndex) => frameIndex > 0 && isLocatableFrame(stackFrame),
@@ -668,5 +676,5 @@ export const getOwnerStack = async (
668
676
  }
669
677
  }
670
678
 
671
- return getParentStack(fiber, shouldCache, fetchFunction);
679
+ return getParentStack(fiber, shouldUseCache, sourceFetch, requestOptions);
672
680
  };
@@ -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(
@@ -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[];
@@ -298,10 +300,14 @@ const resolveUrl = (reference: string, baseUrl: string): string | null => {
298
300
  return new URL(reference, baseUrl).toString();
299
301
  } catch {
300
302
  try {
301
- const resolvedUrl = new URL(reference, new URL(baseUrl, "https://bippy.invalid/"));
302
- return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
303
+ return new URL(reference).toString();
303
304
  } catch {
304
- return null;
305
+ try {
306
+ const resolvedUrl = new URL(reference, new URL(baseUrl, "https://bippy.invalid/"));
307
+ return `${resolvedUrl.pathname}${resolvedUrl.search}${resolvedUrl.hash}`;
308
+ } catch {
309
+ return null;
310
+ }
305
311
  }
306
312
  }
307
313
  };
@@ -460,6 +466,11 @@ const resolveSourceMapSources = (rawSourceMap: StandardSourceMap, sourceMapUrl:
460
466
  resolveSourceRoot(rawSourceMap.sourceRoot, source, sourceMapUrl),
461
467
  );
462
468
 
469
+ // An inline URL carries the whole encoded map, so retaining it in the cache would
470
+ // double the memory held per source map for the lifetime of the process.
471
+ const getRetainableSourceMapUrl = (sourceMapUrl: string): string | undefined =>
472
+ INLINE_SOURCEMAP_REGEX.test(sourceMapUrl) ? undefined : sourceMapUrl;
473
+
463
474
  const decodeStandardSourceMap = (
464
475
  rawSourceMap: StandardSourceMap,
465
476
  sourceMapUrl: string,
@@ -470,6 +481,7 @@ const decodeStandardSourceMap = (
470
481
  ignoredSourceIndices: getIgnoredSourceIndices(rawSourceMap),
471
482
  mappings: decode(rawSourceMap.mappings),
472
483
  names: rawSourceMap.names,
484
+ sourceMapUrl: getRetainableSourceMapUrl(sourceMapUrl),
473
485
  sourceRoot: rawSourceMap.sourceRoot,
474
486
  sources: resolveSourceMapSources(rawSourceMap, sourceMapUrl),
475
487
  sourcesContent: rawSourceMap.sourcesContent,
@@ -532,6 +544,7 @@ const decodeIndexSourceMap = async (
532
544
  mappings: [],
533
545
  names: [],
534
546
  sections: decodedSections,
547
+ sourceMapUrl: getRetainableSourceMapUrl(sourceMapUrl),
535
548
  sourceRoot: undefined,
536
549
  sources: Array.from(allSources),
537
550
  sourcesContent: undefined,
@@ -782,22 +795,23 @@ const readSourceMapDocument = async (
782
795
 
783
796
  const getSourceMapUncachedInternal = async (
784
797
  bundleUrl: string,
785
- fetchFn?: SourceFetch,
798
+ sourceFetchOverride?: SourceFetch,
786
799
  options: SourceMapRequestOptions = {},
787
800
  ): Promise<null | SourceMap> => {
788
- const shouldAllowCustomProtocol = fetchFn !== undefined;
801
+ const shouldAllowCustomProtocol = sourceFetchOverride !== undefined;
789
802
  if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
790
803
  return null;
791
804
  }
792
805
 
793
806
  const isServer = isServerRuntime();
794
- const shouldRejectRedirects = isServer || isExtensionUrl(bundleUrl);
795
- if (isServer && !fetchFn) {
807
+ const shouldRejectRedirects =
808
+ (isServer || isExtensionUrl(bundleUrl)) && !options.allowCrossOriginSourceMap;
809
+ if (isServer && !sourceFetchOverride) {
796
810
  if (!options.allowUnsafeServerFetch || !isAbsoluteHttpUrl(bundleUrl)) return null;
797
811
  }
798
812
 
799
813
  const sourceFetch =
800
- fetchFn ??
814
+ sourceFetchOverride ??
801
815
  (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : undefined);
802
816
  if (!sourceFetch) return null;
803
817
  const maxBundleSizeBytes = options.maxBundleSizeBytes ?? defaultMaxBundleSizeBytes;
@@ -864,11 +878,11 @@ const getSourceMapUncachedInternal = async (
864
878
 
865
879
  export const getSourceMapUncached = async (
866
880
  bundleUrl: string,
867
- fetchFn?: SourceFetch,
881
+ sourceFetch?: SourceFetch,
868
882
  options: SourceMapRequestOptions = {},
869
883
  ): Promise<null | SourceMap> => {
870
884
  try {
871
- return await getSourceMapUncachedInternal(bundleUrl, fetchFn, options);
885
+ return await getSourceMapUncachedInternal(bundleUrl, sourceFetch, options);
872
886
  } catch (error) {
873
887
  if (error instanceof TransientSourceMapError) return null;
874
888
  throw error;
@@ -878,66 +892,67 @@ export const getSourceMapUncached = async (
878
892
  const getPerFetchMap = <Value>(
879
893
  mapsByFetch: WeakMap<SourceFetch, Map<string, Value>>,
880
894
  globalMap: Map<string, Value>,
881
- fetchFn: SourceFetch | undefined,
895
+ sourceFetch: SourceFetch | undefined,
882
896
  ): Map<string, Value> => {
883
- if (!fetchFn) return globalMap;
884
- let map = mapsByFetch.get(fetchFn);
897
+ if (!sourceFetch) return globalMap;
898
+ let map = mapsByFetch.get(sourceFetch);
885
899
  if (!map) {
886
900
  map = new Map();
887
- mapsByFetch.set(fetchFn, map);
901
+ mapsByFetch.set(sourceFetch, map);
888
902
  }
889
903
  return map;
890
904
  };
891
905
 
892
- const getSourceMapCache = (fetchFn: SourceFetch | undefined): Map<string, null | SourceMap> =>
893
- getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, fetchFn);
906
+ const getSourceMapCache = (sourceFetch: SourceFetch | undefined): Map<string, null | SourceMap> =>
907
+ getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, sourceFetch);
894
908
 
895
909
  const getPendingSourceMapRequests = (
896
- fetchFn: SourceFetch | undefined,
910
+ sourceFetch: SourceFetch | undefined,
897
911
  ): Map<string, Promise<SourceMapResult>> =>
898
- getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, fetchFn);
912
+ getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, sourceFetch);
899
913
 
900
914
  const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
915
+ options.allowCrossOriginSourceMap === undefined &&
901
916
  options.allowUnsafeServerFetch === undefined &&
902
917
  options.maxBundleSizeBytes === undefined &&
903
918
  options.maxSourceMapSizeBytes === undefined &&
904
919
  options.timeoutMs === undefined
905
920
  ? file
906
- : `${file}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
921
+ : `${file}\0${options.allowCrossOriginSourceMap ?? ""}\0${options.allowUnsafeServerFetch ?? ""}\0${options.maxBundleSizeBytes ?? ""}\0${options.maxSourceMapSizeBytes ?? ""}\0${options.timeoutMs ?? ""}`;
907
922
 
908
923
  export const getSourceMap = async (
909
924
  file: string,
910
- useCache = true,
911
- fetchFn?: SourceFetch,
925
+ shouldUseCache = true,
926
+ sourceFetch?: SourceFetch,
912
927
  options: SourceMapRequestOptions = {},
913
928
  ): Promise<null | SourceMap> => {
914
- const shouldUseCache = useCache && options.signal === undefined;
915
- const cache = getSourceMapCache(fetchFn);
916
- const pendingRequests = getPendingSourceMapRequests(fetchFn);
929
+ const canUseCache = shouldUseCache && options.signal === undefined;
930
+ const cache = getSourceMapCache(sourceFetch);
931
+ const pendingRequests = getPendingSourceMapRequests(sourceFetch);
917
932
  const cacheKey = getSourceMapCacheKey(file, options);
918
- if (shouldUseCache && cache.has(cacheKey)) {
933
+ if (canUseCache && cache.has(cacheKey)) {
919
934
  return cache.get(cacheKey) ?? null;
920
935
  }
921
936
 
922
- const pendingRequest = shouldUseCache ? pendingRequests.get(cacheKey) : undefined;
937
+ const pendingRequest = canUseCache ? pendingRequests.get(cacheKey) : undefined;
923
938
  if (pendingRequest) {
924
939
  return (await pendingRequest).sourceMap;
925
940
  }
926
941
 
927
942
  const fetchPromise: Promise<SourceMapResult> = getSourceMapUncachedInternal(
928
943
  file,
929
- fetchFn,
944
+ sourceFetch,
930
945
  options,
931
946
  ).then(
932
947
  (sourceMap) => ({ sourceMap, isTransientFailure: false }),
933
948
  () => ({ sourceMap: null, isTransientFailure: true }),
934
949
  );
935
- if (shouldUseCache) {
950
+ if (canUseCache) {
936
951
  pendingRequests.set(cacheKey, fetchPromise);
937
952
  }
938
953
 
939
954
  const { sourceMap, isTransientFailure } = await fetchPromise;
940
- if (shouldUseCache) {
955
+ if (canUseCache) {
941
956
  pendingRequests.delete(cacheKey);
942
957
  if (!isTransientFailure) {
943
958
  cache.set(cacheKey, sourceMap);
@@ -949,13 +964,19 @@ export const getSourceMap = async (
949
964
 
950
965
  export const symbolicateStack = async (
951
966
  stack: StackFrame[],
952
- cache = true,
953
- fetchFn?: SourceFetch,
967
+ shouldUseCache = true,
968
+ sourceFetch?: SourceFetch,
969
+ requestOptions: SourceMapRequestOptions = {},
954
970
  ): Promise<StackFrame[]> => {
955
971
  return Promise.all(
956
972
  stack.map(async (stackFrame) => {
957
973
  if (!stackFrame.fileName) return stackFrame;
958
- const sourceMap = await getSourceMap(stackFrame.fileName, cache, fetchFn);
974
+ const sourceMap = await getSourceMap(
975
+ stackFrame.fileName,
976
+ shouldUseCache,
977
+ sourceFetch,
978
+ requestOptions,
979
+ );
959
980
  if (
960
981
  !sourceMap ||
961
982
  typeof stackFrame.lineNumber !== "number" ||