@watchforge/browser 0.1.13 → 0.1.14

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/stacktrace.js +98 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@watchforge/browser",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "main": "./src/index.js",
5
5
  "types": "./src/index.d.ts",
6
6
  "description": "WatchForge JavaScript SDK for browser JavaScript, Next.js, React, Node.js, and Express.js",
package/src/stacktrace.js CHANGED
@@ -538,6 +538,37 @@ async function fetchBrowserSourceMap(absPath) {
538
538
  }
539
539
  }
540
540
 
541
+ function parseDataSourceMap(sourceMappingUrl) {
542
+ if (!sourceMappingUrl?.startsWith("data:")) return null;
543
+
544
+ try {
545
+ const commaIndex = sourceMappingUrl.indexOf(",");
546
+ if (commaIndex === -1) return null;
547
+
548
+ const metadata = sourceMappingUrl.slice(0, commaIndex);
549
+ const payload = sourceMappingUrl.slice(commaIndex + 1);
550
+ const json = metadata.includes(";base64")
551
+ ? decodeURIComponent(escape(atob(payload)))
552
+ : decodeURIComponent(payload);
553
+ return JSON.parse(json);
554
+ } catch {
555
+ return null;
556
+ }
557
+ }
558
+
559
+ function findInlineSourceMap(sourceText) {
560
+ if (!sourceText || typeof sourceText !== "string") return null;
561
+
562
+ const sourceMapPattern = /\/\/# sourceMappingURL=([^\s"'\\)]+)/g;
563
+ let match;
564
+ while ((match = sourceMapPattern.exec(sourceText))) {
565
+ const sourceMap = parseDataSourceMap(match[1]);
566
+ if (sourceMap) return sourceMap;
567
+ }
568
+
569
+ return null;
570
+ }
571
+
541
572
  async function findSourceMapForFrame(frame) {
542
573
  const wanted = normalizeSourcePath(frame.abs_path || frame.module);
543
574
  const scriptUrls = [];
@@ -582,16 +613,31 @@ async function findSourceMapForFrame(frame) {
582
613
  return null;
583
614
  }
584
615
 
585
- async function enrichFrameWithSourceMap(frame) {
586
- const resolved = await findSourceMapForFrame(frame);
587
- if (!resolved) return false;
588
-
589
- const { sourceMap } = resolved;
590
-
616
+ async function applySourceMapToFrame(frame, sourceMap) {
591
617
  try {
592
618
  const SourceMapConsumer = await getSourceMapConsumer();
593
619
  if (!SourceMapConsumer) return false;
594
620
 
621
+ const sources = Array.isArray(sourceMap.sources) ? sourceMap.sources : [];
622
+ const contents = Array.isArray(sourceMap.sourcesContent)
623
+ ? sourceMap.sourcesContent
624
+ : [];
625
+ const matchedSourceIndex = sources.findIndex((candidate) =>
626
+ sourceMatchesFrame(candidate, frame.abs_path || frame.module || frame.raw_abs_path)
627
+ );
628
+
629
+ if (matchedSourceIndex >= 0 && contents[matchedSourceIndex]) {
630
+ const source = sources[matchedSourceIndex];
631
+ const sourceLines = contents[matchedSourceIndex].split("\n");
632
+ const originalLine = frame.lineno;
633
+ if (originalLine && originalLine <= sourceLines.length) {
634
+ frame.abs_path = source;
635
+ frame.filename = normalizeSourcePath(source).split("/").pop() || frame.filename;
636
+ applySourceContext(frame, sourceLines, originalLine);
637
+ if (frame.context_line) return true;
638
+ }
639
+ }
640
+
595
641
  const consumer = await new SourceMapConsumer(sourceMap);
596
642
  let source = null;
597
643
  let lineno = frame.lineno;
@@ -639,28 +685,49 @@ async function enrichFrameWithSourceMap(frame) {
639
685
  }
640
686
  }
641
687
 
642
- function getSourceContentFromWebpackFrame(frame) {
643
- const globalObject = typeof globalThis !== "undefined" ? globalThis : window;
644
- const webpackChunks =
645
- Object.values(globalObject).find(
646
- (value) =>
647
- Array.isArray(value) &&
648
- value.some((item) => Array.isArray(item) && item.length >= 2)
649
- ) || [];
688
+ async function enrichFrameWithSourceMap(frame) {
689
+ const resolved = await findSourceMapForFrame(frame);
690
+ if (!resolved) return false;
650
691
 
692
+ return applySourceMapToFrame(frame, resolved.sourceMap);
693
+ }
694
+
695
+ function getWebpackChunkGlobals(globalObject) {
696
+ return Object.entries(globalObject)
697
+ .filter(([, value]) => Array.isArray(value))
698
+ .sort(([nameA], [nameB]) => {
699
+ const score = (name) => (name.startsWith("webpackChunk") ? 0 : 1);
700
+ return score(nameA) - score(nameB);
701
+ })
702
+ .map(([, value]) => value);
703
+ }
704
+
705
+ function getWebpackFrameArtifacts(frame) {
706
+ const globalObject = typeof globalThis !== "undefined" ? globalThis : window;
651
707
  const framePath = normalizeSourcePath(
652
708
  frame.abs_path || frame.module || frame.raw_abs_path
653
709
  );
654
- if (!framePath || !Array.isArray(webpackChunks)) return null;
655
-
656
- for (const chunk of webpackChunks) {
657
- const modules = Array.isArray(chunk) ? chunk[1] : null;
658
- if (!modules || typeof modules !== "object") continue;
710
+ if (!framePath) return null;
711
+
712
+ for (const webpackChunks of getWebpackChunkGlobals(globalObject)) {
713
+ for (const chunk of webpackChunks) {
714
+ const modules = Array.isArray(chunk) ? chunk[1] : null;
715
+ if (!modules || typeof modules !== "object") continue;
716
+
717
+ for (const [moduleId, factory] of Object.entries(modules)) {
718
+ const factorySource = String(factory);
719
+ if (
720
+ !sourceMatchesFrame(moduleId, framePath) &&
721
+ !sourceMatchesFrame(factorySource.match(/sourceURL=([^\s"'\\)]+)/)?.[1], framePath)
722
+ ) {
723
+ continue;
724
+ }
659
725
 
660
- for (const [moduleId, factory] of Object.entries(modules)) {
661
- if (!sourceMatchesFrame(moduleId, framePath)) continue;
662
- const source = String(factory);
663
- return source.includes("\n") ? source.split("\n") : null;
726
+ return {
727
+ lines: factorySource.includes("\n") ? factorySource.split("\n") : null,
728
+ sourceMap: findInlineSourceMap(factorySource),
729
+ };
730
+ }
664
731
  }
665
732
  }
666
733
 
@@ -670,9 +737,14 @@ function getSourceContentFromWebpackFrame(frame) {
670
737
  async function enrichFrameWithBrowserSource(frame) {
671
738
  if (!frame.in_app || !frame.lineno) return;
672
739
 
673
- const webpackLines = getSourceContentFromWebpackFrame(frame);
674
- if (webpackLines) {
675
- applySourceContext(frame, webpackLines, frame.lineno);
740
+ const webpackArtifacts = getWebpackFrameArtifacts(frame);
741
+ if (webpackArtifacts?.sourceMap) {
742
+ const applied = await applySourceMapToFrame(frame, webpackArtifacts.sourceMap);
743
+ if (applied) return;
744
+ }
745
+
746
+ if (webpackArtifacts?.lines) {
747
+ applySourceContext(frame, webpackArtifacts.lines, frame.lineno);
676
748
  if (frame.context_line) return;
677
749
  }
678
750