bippy 0.6.1-dev.b88fcb4 → 0.6.1-dev.f9e6c65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +159 -487
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.d.cts +8 -54
  4. package/dist/core.d.ts +8 -54
  5. package/dist/core.js +1 -1
  6. package/dist/core2.cjs +1 -1
  7. package/dist/core2.d.cts +3 -3
  8. package/dist/core2.d.ts +3 -3
  9. package/dist/core2.js +1 -1
  10. package/dist/errors.d.cts +2 -57
  11. package/dist/errors.d.ts +2 -57
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.cts +6 -3
  14. package/dist/index.d.ts +6 -3
  15. package/dist/index.js +1 -1
  16. package/dist/install-hook-only.cjs +1 -1
  17. package/dist/install-hook-only.js +1 -1
  18. package/dist/rdt-hook.cjs +1 -1
  19. package/dist/rdt-hook.js +1 -1
  20. package/dist/source.cjs +12 -13
  21. package/dist/source.d.cts +77 -78
  22. package/dist/source.d.ts +77 -78
  23. package/dist/source.js +12 -13
  24. package/package.json +6 -3
  25. package/src/core.ts +101 -530
  26. package/src/errors.ts +0 -65
  27. package/src/index.ts +1 -0
  28. package/src/install-hook-only.ts +2 -2
  29. package/src/rdt-hook.ts +30 -43
  30. package/src/react-internals/generated/react-work-tags.ts +6 -2
  31. package/src/react-internals/index.ts +4 -4
  32. package/src/react-internals/semver.ts +2 -0
  33. package/src/react-internals/types.ts +0 -83
  34. package/src/react.ts +76 -0
  35. package/src/source/get-display-name-from-source.ts +44 -40
  36. package/src/source/get-source.ts +40 -24
  37. package/src/source/index.ts +2 -0
  38. package/src/source/inspect-hooks.ts +70 -91
  39. package/src/source/owner-stack.ts +60 -89
  40. package/src/source/parse-hook-names.ts +14 -53
  41. package/src/source/parse-stack.ts +14 -31
  42. package/src/source/renderer-dispatchers.ts +30 -0
  43. package/src/source/symbolication.ts +312 -111
  44. package/dist/index.iife.js +0 -9
  45. package/dist/install-hook-only.iife.js +0 -9
@@ -1,7 +1,8 @@
1
- import { decode, SourceMapMappings, type SourceMapSegment } from "@jridgewell/sourcemap-codec";
1
+ import { decode, type SourceMapMappings, type SourceMapSegment } from "@jridgewell/sourcemap-codec";
2
2
 
3
3
  import { BippySourceMapError } from "../errors.js";
4
- import { StackFrame } from "./parse-stack.js";
4
+ import { SCHEME_REGEX } from "./constants.js";
5
+ import type { StackFrame } from "./parse-stack.js";
5
6
 
6
7
  export interface DecodedSourceMapSection {
7
8
  map: {
@@ -69,7 +70,6 @@ export interface StandardSourceMap {
69
70
  x_google_ignoreList?: number[];
70
71
  }
71
72
 
72
- const SCHEME_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*:/;
73
73
  const INLINE_SOURCEMAP_REGEX = /^data:application\/json(?:;[^,]*)?,/i;
74
74
  const SOURCEMAP_REGEX =
75
75
  /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^*]+?)[ \t]*(?:\*\/)[ \t]*$)/;
@@ -83,13 +83,42 @@ interface SourceMapResult {
83
83
 
84
84
  class TransientSourceMapError extends Error {}
85
85
 
86
- const _pendingSourceMapRequests = new Map<string, Promise<SourceMapResult>>();
86
+ const pendingSourceMapRequests = new Map<string, Promise<SourceMapResult>>();
87
87
  const pendingSourceMapRequestsByFetch = new WeakMap<
88
88
  SourceFetch,
89
89
  Map<string, Promise<SourceMapResult>>
90
90
  >();
91
91
  const defaultMaxBundleSizeBytes = 25 * 1024 * 1024;
92
92
  const defaultMaxSourceMapSizeBytes = 100 * 1024 * 1024;
93
+ const extensionProtocols = new Set([
94
+ "chrome-extension:",
95
+ "moz-extension:",
96
+ "safari-web-extension:",
97
+ ]);
98
+ const defaultFetchableProtocols = new Set(["http:", "https:", ...extensionProtocols]);
99
+
100
+ const getSourceFromSegment = (
101
+ segment: SourceMapSegment,
102
+ sources: string[],
103
+ ignoredSourceIndices?: Set<number>,
104
+ names?: string[],
105
+ ): StackFrame | null => {
106
+ const [, sourceIndex, sourceLine, sourceColumn, nameIndex] = segment;
107
+ if (sourceIndex === undefined || sourceLine === undefined || sourceColumn === undefined) {
108
+ return null;
109
+ }
110
+
111
+ const fileName = sources[sourceIndex];
112
+ if (!fileName) return null;
113
+
114
+ return {
115
+ columnNumber: sourceColumn,
116
+ fileName,
117
+ functionName: nameIndex === undefined ? undefined : names?.[nameIndex] || undefined,
118
+ isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
119
+ lineNumber: sourceLine + 1,
120
+ };
121
+ };
93
122
 
94
123
  const getSourceFromMappings = (
95
124
  mappings: SourceMapMappings,
@@ -97,6 +126,7 @@ const getSourceFromMappings = (
97
126
  lineIndexInMappings: number,
98
127
  column: number,
99
128
  ignoredSourceIndices?: Set<number>,
129
+ names?: string[],
100
130
  ): StackFrame | null => {
101
131
  if (lineIndexInMappings < 0 || lineIndexInMappings >= mappings.length) {
102
132
  return null;
@@ -124,24 +154,7 @@ const getSourceFromMappings = (
124
154
  return null;
125
155
  }
126
156
 
127
- const [, sourceIndex, sourceLine, sourceColumn] = closestLineSegment;
128
-
129
- if (sourceIndex === undefined || sourceLine === undefined || sourceColumn === undefined) {
130
- return null;
131
- }
132
-
133
- const fileName = sources[sourceIndex];
134
-
135
- if (!fileName) {
136
- return null;
137
- }
138
-
139
- return {
140
- columnNumber: sourceColumn,
141
- fileName,
142
- lineNumber: sourceLine + 1,
143
- isIgnoreListed: ignoredSourceIndices?.has(sourceIndex) ?? false,
144
- };
157
+ return getSourceFromSegment(closestLineSegment, sources, ignoredSourceIndices, names);
145
158
  };
146
159
 
147
160
  export const getSourceFromSourceMap = (
@@ -178,6 +191,7 @@ export const getSourceFromSourceMap = (
178
191
  relativeLine,
179
192
  relativeColumn,
180
193
  targetSection.map.ignoredSourceIndices,
194
+ targetSection.map.names,
181
195
  );
182
196
  }
183
197
 
@@ -187,9 +201,97 @@ export const getSourceFromSourceMap = (
187
201
  line - 1,
188
202
  column,
189
203
  sourceMap.ignoredSourceIndices,
204
+ sourceMap.names,
205
+ );
206
+ };
207
+
208
+ const getSourceFromMappingsByFunctionName = (
209
+ mappings: SourceMapMappings,
210
+ sources: string[],
211
+ names: string[] | undefined,
212
+ functionName: string,
213
+ ignoredSourceIndices?: Set<number>,
214
+ ): StackFrame | null => {
215
+ if (!names) return null;
216
+ const functionNameIndex = names.indexOf(functionName);
217
+ if (functionNameIndex === -1) return null;
218
+
219
+ let ignoredSource: StackFrame | null = null;
220
+ for (const lineMapping of mappings) {
221
+ for (const segment of lineMapping) {
222
+ if (segment[4] !== functionNameIndex) continue;
223
+ const source = getSourceFromSegment(segment, sources, ignoredSourceIndices, names);
224
+ if (!source) continue;
225
+ if (!source.isIgnoreListed) return source;
226
+ ignoredSource ??= source;
227
+ }
228
+ }
229
+ return ignoredSource;
230
+ };
231
+
232
+ export const getSourceFromSourceMapByFunctionName = (
233
+ sourceMap: SourceMap,
234
+ functionName: string,
235
+ ): StackFrame | null => {
236
+ if (sourceMap.sections) {
237
+ let ignoredSource: StackFrame | null = null;
238
+ for (const section of sourceMap.sections) {
239
+ const source = getSourceFromMappingsByFunctionName(
240
+ section.map.mappings,
241
+ section.map.sources,
242
+ section.map.names,
243
+ functionName,
244
+ section.map.ignoredSourceIndices,
245
+ );
246
+ if (!source) continue;
247
+ if (!source.isIgnoreListed) return source;
248
+ ignoredSource ??= source;
249
+ }
250
+ return ignoredSource;
251
+ }
252
+
253
+ return getSourceFromMappingsByFunctionName(
254
+ sourceMap.mappings,
255
+ sourceMap.sources,
256
+ sourceMap.names,
257
+ functionName,
258
+ sourceMap.ignoredSourceIndices,
190
259
  );
191
260
  };
192
261
 
262
+ const findSourceContentByFileName = (
263
+ sources: string[],
264
+ sourcesContent: Array<string | null> | undefined,
265
+ fileName: string,
266
+ ): string | null => {
267
+ if (!sourcesContent) return null;
268
+ const sourceIndex = sources.indexOf(fileName);
269
+ return sourceIndex === -1 ? null : (sourcesContent[sourceIndex] ?? null);
270
+ };
271
+
272
+ export const getSourceContentFromSourceMap = (
273
+ sourceMap: SourceMap,
274
+ originalFileName: string,
275
+ ): string | null => {
276
+ const sourceContent = findSourceContentByFileName(
277
+ sourceMap.sources,
278
+ sourceMap.sourcesContent,
279
+ originalFileName,
280
+ );
281
+ if (sourceContent !== null) return sourceContent;
282
+
283
+ if (!sourceMap.sections) return null;
284
+ for (const section of sourceMap.sections) {
285
+ const sectionSourceContent = findSourceContentByFileName(
286
+ section.map.sources,
287
+ section.map.sourcesContent,
288
+ originalFileName,
289
+ );
290
+ if (sectionSourceContent !== null) return sectionSourceContent;
291
+ }
292
+ return null;
293
+ };
294
+
193
295
  const resolveUrl = (reference: string, baseUrl: string): string | null => {
194
296
  if (INLINE_SOURCEMAP_REGEX.test(reference)) return reference;
195
297
  try {
@@ -204,12 +306,43 @@ const resolveUrl = (reference: string, baseUrl: string): string | null => {
204
306
  }
205
307
  };
206
308
 
207
- const getSourceMapUrl = (url: string, content: string): null | string => {
309
+ const getMetroSourceMapUrl = (bundleUrl: string): string | null => {
310
+ try {
311
+ const parsedBundleUrl = new URL(bundleUrl);
312
+ const bundleExtensionIndex = parsedBundleUrl.pathname.lastIndexOf(".bundle");
313
+ if (bundleExtensionIndex === -1) return null;
314
+ const embeddedSearch = parsedBundleUrl.pathname.slice(bundleExtensionIndex + ".bundle".length);
315
+ if (embeddedSearch && !embeddedSearch.startsWith("//&")) return null;
316
+ parsedBundleUrl.pathname = `${parsedBundleUrl.pathname.slice(0, bundleExtensionIndex)}.map`;
317
+ if (embeddedSearch) {
318
+ const embeddedSearchParameters = new URLSearchParams(embeddedSearch.slice("//&".length));
319
+ for (const [parameterName, parameterValue] of embeddedSearchParameters) {
320
+ parsedBundleUrl.searchParams.set(parameterName, parameterValue);
321
+ }
322
+ }
323
+ parsedBundleUrl.hash = "";
324
+ return parsedBundleUrl.toString();
325
+ } catch {
326
+ return null;
327
+ }
328
+ };
329
+
330
+ const getSourceMapUrl = (
331
+ bundleUrl: string,
332
+ bundleContent: string,
333
+ bundleResponse: Response,
334
+ ): null | string => {
335
+ const sourceMapHeader =
336
+ bundleResponse.headers.get("sourcemap") ?? bundleResponse.headers.get("x-sourcemap");
337
+ if (sourceMapHeader) {
338
+ return resolveUrl(sourceMapHeader.trim(), bundleUrl);
339
+ }
340
+
208
341
  let sourceMapUrl: string | undefined;
209
- let searchEnd = content.length;
342
+ let searchEnd = bundleContent.length;
210
343
  while (searchEnd > 0 && !sourceMapUrl) {
211
- const lineStart = content.lastIndexOf("\n", searchEnd - 1) + 1;
212
- const regexMatch = content.slice(lineStart, searchEnd).match(SOURCEMAP_REGEX);
344
+ const lineStart = bundleContent.lastIndexOf("\n", searchEnd - 1) + 1;
345
+ const regexMatch = bundleContent.slice(lineStart, searchEnd).match(SOURCEMAP_REGEX);
213
346
  if (regexMatch) {
214
347
  sourceMapUrl = regexMatch[1] || regexMatch[2];
215
348
  }
@@ -217,70 +350,82 @@ const getSourceMapUrl = (url: string, content: string): null | string => {
217
350
  }
218
351
 
219
352
  if (!sourceMapUrl) {
220
- return null;
353
+ return getMetroSourceMapUrl(bundleUrl);
221
354
  }
222
355
 
223
- return resolveUrl(sourceMapUrl, url);
356
+ return resolveUrl(sourceMapUrl, bundleUrl);
224
357
  };
225
358
 
226
- const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
227
- typeof value === "object" && value !== null;
228
-
229
- const isStringArray = (value: unknown): value is string[] =>
230
- Array.isArray(value) && value.every((entry) => typeof entry === "string");
231
-
232
- const isSourcesContent = (value: unknown): value is Array<string | null> =>
233
- Array.isArray(value) && value.every((entry) => typeof entry === "string" || entry === null);
234
-
235
- const isOptionalNumberArray = (value: unknown): value is number[] | undefined =>
236
- value === undefined ||
237
- (Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0));
238
-
239
359
  const isStandardSourceMap = (value: unknown): value is StandardSourceMap => {
240
- if (!isObjectRecord(value)) return false;
360
+ if (typeof value !== "object" || value === null) return false;
361
+ const version = Reflect.get(value, "version");
362
+ const mappings = Reflect.get(value, "mappings");
363
+ const file = Reflect.get(value, "file");
364
+ const names = Reflect.get(value, "names");
365
+ const sourceRoot = Reflect.get(value, "sourceRoot");
366
+ const sources = Reflect.get(value, "sources");
367
+ const sourcesContent = Reflect.get(value, "sourcesContent");
368
+ const ignoreList = Reflect.get(value, "ignoreList");
369
+ const googleIgnoreList = Reflect.get(value, "x_google_ignoreList");
241
370
  if (
242
- value.version !== 3 ||
243
- typeof value.mappings !== "string" ||
244
- (value.file !== undefined && typeof value.file !== "string") ||
245
- (value.names !== undefined && !isStringArray(value.names)) ||
246
- (value.sourceRoot !== undefined && typeof value.sourceRoot !== "string") ||
247
- (value.sourcesContent !== undefined && !isSourcesContent(value.sourcesContent)) ||
248
- !isOptionalNumberArray(value.ignoreList) ||
249
- !isOptionalNumberArray(value.x_google_ignoreList)
371
+ version !== 3 ||
372
+ typeof mappings !== "string" ||
373
+ (file !== undefined && typeof file !== "string") ||
374
+ (names !== undefined &&
375
+ (!Array.isArray(names) || names.some((entry) => typeof entry !== "string"))) ||
376
+ (sourceRoot !== undefined && typeof sourceRoot !== "string") ||
377
+ (sourcesContent !== undefined &&
378
+ (!Array.isArray(sourcesContent) ||
379
+ sourcesContent.some((entry) => typeof entry !== "string" && entry !== null))) ||
380
+ (ignoreList !== undefined &&
381
+ (!Array.isArray(ignoreList) ||
382
+ ignoreList.some((entry) => !Number.isInteger(entry) || entry < 0))) ||
383
+ (googleIgnoreList !== undefined &&
384
+ (!Array.isArray(googleIgnoreList) ||
385
+ googleIgnoreList.some((entry) => !Number.isInteger(entry) || entry < 0)))
250
386
  ) {
251
387
  return false;
252
388
  }
253
- if (!isStringArray(value.sources)) return false;
254
- if (value.sourcesContent && value.sourcesContent.length !== value.sources.length) return false;
255
- const sourceCount = value.sources.length;
256
- return [...(value.ignoreList ?? []), ...(value.x_google_ignoreList ?? [])].every(
389
+ if (!Array.isArray(sources) || sources.some((entry) => typeof entry !== "string")) return false;
390
+ if (sourcesContent && sourcesContent.length !== sources.length) return false;
391
+ const sourceCount = sources.length;
392
+ return [...(ignoreList ?? []), ...(googleIgnoreList ?? [])].every(
257
393
  (sourceIndex) => sourceIndex < sourceCount,
258
394
  );
259
395
  };
260
396
 
261
397
  const isIndexSourceMap = (value: unknown): value is IndexSourceMap => {
262
- if (!isObjectRecord(value) || value.version !== 3 || !Array.isArray(value.sections)) {
398
+ if (typeof value !== "object" || value === null) return false;
399
+ const version = Reflect.get(value, "version");
400
+ const sections = Reflect.get(value, "sections");
401
+ if (version !== 3 || !Array.isArray(sections)) {
263
402
  return false;
264
403
  }
265
- return value.sections.every((section) => {
266
- if (!isObjectRecord(section) || !isObjectRecord(section.offset)) return false;
267
- const hasMap = isStandardSourceMap(section.map);
268
- const hasUrl = typeof section.url === "string" && section.url.length > 0;
404
+ return sections.every((section: unknown) => {
405
+ if (typeof section !== "object" || section === null) return false;
406
+ const map = Reflect.get(section, "map");
407
+ const url = Reflect.get(section, "url");
408
+ const offset = Reflect.get(section, "offset");
409
+ if (typeof offset !== "object" || offset === null) return false;
410
+ const offsetColumn = Reflect.get(offset, "column");
411
+ const offsetLine = Reflect.get(offset, "line");
412
+ const hasMap = isStandardSourceMap(map);
413
+ const hasUrl = typeof url === "string" && url.length > 0;
269
414
  return (
270
415
  hasMap !== hasUrl &&
271
- typeof section.offset.column === "number" &&
272
- Number.isInteger(section.offset.column) &&
273
- section.offset.column >= 0 &&
274
- typeof section.offset.line === "number" &&
275
- Number.isInteger(section.offset.line) &&
276
- section.offset.line >= 0
416
+ typeof offsetColumn === "number" &&
417
+ Number.isInteger(offsetColumn) &&
418
+ offsetColumn >= 0 &&
419
+ typeof offsetLine === "number" &&
420
+ Number.isInteger(offsetLine) &&
421
+ offsetLine >= 0
277
422
  );
278
423
  });
279
424
  };
280
425
 
281
426
  const getIgnoredSourceIndices = (rawSourceMap: StandardSourceMap): Set<number> | undefined => {
282
427
  const ignoreList = rawSourceMap.ignoreList ?? rawSourceMap.x_google_ignoreList;
283
- return Array.isArray(ignoreList) && ignoreList.length > 0 ? new Set(ignoreList) : undefined;
428
+ return ignoreList?.length ? new Set(ignoreList) : undefined;
284
429
  };
285
430
 
286
431
  const resolveSourceRoot = (
@@ -394,7 +539,7 @@ const decodeIndexSourceMap = async (
394
539
  };
395
540
  };
396
541
 
397
- const isFetchableUrl = (url: string): boolean => {
542
+ const isFetchableUrl = (url: string, shouldAllowCustomProtocol = false): boolean => {
398
543
  if (!url) {
399
544
  return false;
400
545
  }
@@ -413,7 +558,7 @@ const isFetchableUrl = (url: string): boolean => {
413
558
 
414
559
  const scheme = schemeMatch[0].toLowerCase();
415
560
 
416
- return scheme === "http:" || scheme === "https:";
561
+ return shouldAllowCustomProtocol || defaultFetchableProtocols.has(scheme);
417
562
  };
418
563
 
419
564
  const isServerRuntime = (): boolean =>
@@ -434,14 +579,53 @@ const isAbsoluteHttpUrl = (url: string): boolean => {
434
579
  }
435
580
  };
436
581
 
582
+ const getUrlProtocol = (url: string): string | null => {
583
+ try {
584
+ return new URL(url).protocol;
585
+ } catch {
586
+ return null;
587
+ }
588
+ };
589
+
590
+ const isExtensionUrl = (url: string): boolean => {
591
+ const protocol = getUrlProtocol(url);
592
+ return protocol !== null && extensionProtocols.has(protocol);
593
+ };
594
+
437
595
  const isSameOrigin = (firstUrl: string, secondUrl: string): boolean => {
438
596
  try {
439
- return new URL(firstUrl).origin === new URL(secondUrl).origin;
597
+ const firstParsedUrl = new URL(firstUrl);
598
+ const secondParsedUrl = new URL(secondUrl);
599
+ return (
600
+ firstParsedUrl.protocol === secondParsedUrl.protocol &&
601
+ firstParsedUrl.host === secondParsedUrl.host
602
+ );
440
603
  } catch {
441
604
  return false;
442
605
  }
443
606
  };
444
607
 
608
+ const isAllowedSourceMapUrl = (
609
+ bundleUrl: string,
610
+ sourceMapUrl: string,
611
+ shouldRejectCrossOrigin: boolean,
612
+ shouldAllowCustomProtocol: boolean,
613
+ ): boolean => {
614
+ if (INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) return true;
615
+ if (!isFetchableUrl(sourceMapUrl, shouldAllowCustomProtocol)) return false;
616
+ const bundleProtocol = getUrlProtocol(bundleUrl);
617
+ const sourceMapProtocol = getUrlProtocol(sourceMapUrl);
618
+ const hasCustomProtocol =
619
+ (bundleProtocol !== null && !defaultFetchableProtocols.has(bundleProtocol)) ||
620
+ (sourceMapProtocol !== null && !defaultFetchableProtocols.has(sourceMapProtocol));
621
+ const shouldRequireSameOrigin =
622
+ shouldRejectCrossOrigin ||
623
+ hasCustomProtocol ||
624
+ isExtensionUrl(bundleUrl) ||
625
+ isExtensionUrl(sourceMapUrl);
626
+ return !shouldRequireSameOrigin || isSameOrigin(bundleUrl, sourceMapUrl);
627
+ };
628
+
445
629
  const isTransientHttpStatus = (status: number): boolean =>
446
630
  status === 408 || status === 425 || status === 429 || status >= 500;
447
631
 
@@ -453,14 +637,21 @@ const assertResponseIsCacheable = (response: Response): boolean => {
453
637
  return false;
454
638
  };
455
639
 
640
+ const isResponseUrlAllowed = (
641
+ requestedUrl: string,
642
+ response: Response,
643
+ shouldRejectCrossOrigin: boolean,
644
+ ): boolean =>
645
+ !shouldRejectCrossOrigin || response.url.length === 0 || isSameOrigin(requestedUrl, response.url);
646
+
456
647
  interface RequestSignal {
457
648
  cleanup: () => void;
458
649
  signal?: AbortSignal;
459
650
  }
460
651
 
461
652
  const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal => {
462
- if (!options.signal && options.timeoutMs === undefined) {
463
- return { cleanup: () => {} };
653
+ if (options.timeoutMs === undefined || options.timeoutMs < 0) {
654
+ return { cleanup: () => {}, signal: options.signal };
464
655
  }
465
656
  const abortController = new AbortController();
466
657
  const abortFromSource = (): void => abortController.abort(options.signal?.reason);
@@ -469,16 +660,13 @@ const createRequestSignal = (options: SourceMapRequestOptions): RequestSignal =>
469
660
  } else {
470
661
  options.signal?.addEventListener("abort", abortFromSource, { once: true });
471
662
  }
472
- const timeoutHandle =
473
- options.timeoutMs !== undefined && options.timeoutMs >= 0
474
- ? setTimeout(
475
- () => abortController.abort(new BippySourceMapError("Source map request timed out")),
476
- options.timeoutMs,
477
- )
478
- : undefined;
663
+ const timeoutHandle = setTimeout(
664
+ () => abortController.abort(new BippySourceMapError("Source map request timed out")),
665
+ options.timeoutMs,
666
+ );
479
667
  return {
480
668
  cleanup: () => {
481
- if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
669
+ clearTimeout(timeoutHandle);
482
670
  options.signal?.removeEventListener("abort", abortFromSource);
483
671
  },
484
672
  signal: abortController.signal,
@@ -491,7 +679,10 @@ const readResponseText = async (
491
679
  ): Promise<string | null> => {
492
680
  const contentLength = Number(response.headers.get("content-length"));
493
681
  if (Number.isFinite(contentLength) && contentLength > maxSizeBytes) return null;
494
- if (!response.body) return "";
682
+ if (!response.body) {
683
+ const responseText = await response.text();
684
+ return new TextEncoder().encode(responseText).byteLength > maxSizeBytes ? null : responseText;
685
+ }
495
686
 
496
687
  const reader = response.body.getReader();
497
688
  const decoder = new TextDecoder();
@@ -579,6 +770,7 @@ const readSourceMapDocument = async (
579
770
  signal,
580
771
  });
581
772
  if (!assertResponseIsCacheable(sourceMapResponse)) return null;
773
+ if (!isResponseUrlAllowed(sourceMapUrl, sourceMapResponse, shouldRejectRedirects)) return null;
582
774
  const sourceMapContent = await readResponseText(sourceMapResponse, maxSizeBytes);
583
775
  if (sourceMapContent === null) return null;
584
776
  try {
@@ -593,13 +785,15 @@ const getSourceMapUncachedInternal = async (
593
785
  fetchFn?: SourceFetch,
594
786
  options: SourceMapRequestOptions = {},
595
787
  ): Promise<null | SourceMap> => {
596
- if (!isFetchableUrl(bundleUrl)) {
788
+ const shouldAllowCustomProtocol = fetchFn !== undefined;
789
+ if (!isFetchableUrl(bundleUrl, shouldAllowCustomProtocol)) {
597
790
  return null;
598
791
  }
599
792
 
600
- const shouldRejectRedirects = isServerRuntime();
601
- if (shouldRejectRedirects) {
602
- if ((!fetchFn && !options.allowUnsafeServerFetch) || !isAbsoluteHttpUrl(bundleUrl)) return null;
793
+ const isServer = isServerRuntime();
794
+ const shouldRejectRedirects = isServer || isExtensionUrl(bundleUrl);
795
+ if (isServer && !fetchFn) {
796
+ if (!options.allowUnsafeServerFetch || !isAbsoluteHttpUrl(bundleUrl)) return null;
603
797
  }
604
798
 
605
799
  const sourceFetch =
@@ -616,15 +810,18 @@ const getSourceMapUncachedInternal = async (
616
810
  signal: requestSignal.signal,
617
811
  });
618
812
  if (!assertResponseIsCacheable(bundleResponse)) return null;
813
+ if (!isResponseUrlAllowed(bundleUrl, bundleResponse, shouldRejectRedirects)) return null;
619
814
  const bundleContent = await readResponseText(bundleResponse, maxBundleSizeBytes);
620
- if (!bundleContent) return null;
621
- const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent);
815
+ if (bundleContent === null) return null;
816
+ const sourceMapUrl = getSourceMapUrl(bundleUrl, bundleContent, bundleResponse);
622
817
  if (!sourceMapUrl) return null;
623
- if (!isFetchableUrl(sourceMapUrl) && !INLINE_SOURCEMAP_REGEX.test(sourceMapUrl)) return null;
624
818
  if (
625
- shouldRejectRedirects &&
626
- !INLINE_SOURCEMAP_REGEX.test(sourceMapUrl) &&
627
- !isSameOrigin(bundleUrl, sourceMapUrl)
819
+ !isAllowedSourceMapUrl(
820
+ bundleUrl,
821
+ sourceMapUrl,
822
+ shouldRejectRedirects,
823
+ shouldAllowCustomProtocol,
824
+ )
628
825
  ) {
629
826
  return null;
630
827
  }
@@ -642,9 +839,12 @@ const getSourceMapUncachedInternal = async (
642
839
  if (!isIndexSourceMap(rawSourceMap)) return null;
643
840
  return decodeIndexSourceMap(rawSourceMap, sourceMapUrl, async (sectionUrl) => {
644
841
  if (
645
- shouldRejectRedirects &&
646
- !INLINE_SOURCEMAP_REGEX.test(sectionUrl) &&
647
- !isSameOrigin(bundleUrl, sectionUrl)
842
+ !isAllowedSourceMapUrl(
843
+ bundleUrl,
844
+ sectionUrl,
845
+ shouldRejectRedirects,
846
+ shouldAllowCustomProtocol,
847
+ )
648
848
  ) {
649
849
  return null;
650
850
  }
@@ -675,27 +875,27 @@ export const getSourceMapUncached = async (
675
875
  }
676
876
  };
677
877
 
678
- const getSourceMapCache = (fetchFn: SourceFetch | undefined): Map<string, null | SourceMap> => {
679
- if (!fetchFn) return sourceMapCache;
680
- let cache = sourceMapCachesByFetch.get(fetchFn);
681
- if (!cache) {
682
- cache = new Map();
683
- sourceMapCachesByFetch.set(fetchFn, cache);
684
- }
685
- return cache;
878
+ const getPerFetchMap = <Value>(
879
+ mapsByFetch: WeakMap<SourceFetch, Map<string, Value>>,
880
+ globalMap: Map<string, Value>,
881
+ fetchFn: SourceFetch | undefined,
882
+ ): Map<string, Value> => {
883
+ if (!fetchFn) return globalMap;
884
+ let map = mapsByFetch.get(fetchFn);
885
+ if (!map) {
886
+ map = new Map();
887
+ mapsByFetch.set(fetchFn, map);
888
+ }
889
+ return map;
686
890
  };
687
891
 
892
+ const getSourceMapCache = (fetchFn: SourceFetch | undefined): Map<string, null | SourceMap> =>
893
+ getPerFetchMap(sourceMapCachesByFetch, sourceMapCache, fetchFn);
894
+
688
895
  const getPendingSourceMapRequests = (
689
896
  fetchFn: SourceFetch | undefined,
690
- ): Map<string, Promise<SourceMapResult>> => {
691
- if (!fetchFn) return _pendingSourceMapRequests;
692
- let pendingRequests = pendingSourceMapRequestsByFetch.get(fetchFn);
693
- if (!pendingRequests) {
694
- pendingRequests = new Map();
695
- pendingSourceMapRequestsByFetch.set(fetchFn, pendingRequests);
696
- }
697
- return pendingRequests;
698
- };
897
+ ): Map<string, Promise<SourceMapResult>> =>
898
+ getPerFetchMap(pendingSourceMapRequestsByFetch, pendingSourceMapRequests, fetchFn);
699
899
 
700
900
  const getSourceMapCacheKey = (file: string, options: SourceMapRequestOptions): string =>
701
901
  options.allowUnsafeServerFetch === undefined &&
@@ -750,7 +950,7 @@ export const getSourceMap = async (
750
950
  export const symbolicateStack = async (
751
951
  stack: StackFrame[],
752
952
  cache = true,
753
- fetchFn?: (url: string) => Promise<Response>,
953
+ fetchFn?: SourceFetch,
754
954
  ): Promise<StackFrame[]> => {
755
955
  return Promise.all(
756
956
  stack.map(async (stackFrame) => {
@@ -776,6 +976,7 @@ export const symbolicateStack = async (
776
976
  ? stackFrame.source.replace(stackFrame.fileName, symbolicatedSource.fileName)
777
977
  : stackFrame.source,
778
978
  fileName: symbolicatedSource.fileName,
979
+ functionName: symbolicatedSource.functionName ?? stackFrame.functionName,
779
980
  lineNumber: symbolicatedSource.lineNumber,
780
981
  columnNumber: symbolicatedSource.columnNumber,
781
982
  isIgnoreListed: symbolicatedSource.isIgnoreListed,