@botim/mp-debug-sdk 0.3.1 → 0.4.1

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.
package/dist/index.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var rrwebSnapshot = require('rrweb-snapshot');
4
+
3
5
  // src/types.ts
4
6
  var SCHEMA_VERSION = 2;
5
7
 
@@ -753,8 +755,6 @@ var CommandRegistry = class {
753
755
  }
754
756
  }
755
757
  };
756
-
757
- // src/commands/builtins.ts
758
758
  var MAX_DUMP_BYTES = 64 * 1024;
759
759
  var MAX_SCREENSHOT_BYTES = 1024 * 1024;
760
760
  async function defaultDomScreenshot() {
@@ -763,163 +763,107 @@ async function defaultDomScreenshot() {
763
763
  "[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
764
764
  );
765
765
  }
766
+ const injected = injectAdoptedStyleSheets();
767
+ let tree;
766
768
  try {
767
- return await captureViaSvgForeignObject();
768
- } catch (err) {
769
- try {
770
- console.warn(
771
- "[@botim/debug-sdk] SVG/canvas screenshot failed, falling back to HTML snapshot:",
772
- err instanceof Error ? err.message : err
773
- );
774
- } catch {
775
- }
776
- return await captureViaHtmlSnapshot();
777
- }
778
- }
779
- var VOID_ELEMENTS = /* @__PURE__ */ new Set([
780
- "area",
781
- "base",
782
- "br",
783
- "col",
784
- "embed",
785
- "hr",
786
- "img",
787
- "input",
788
- "link",
789
- "meta",
790
- "source",
791
- "track",
792
- "wbr"
793
- ]);
794
- var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "noscript", "template"]);
795
- var DEVTOOL_OVERLAY_PREFIXES = ["vite-", "astro-dev-", "next-route-"];
796
- function isDevtoolOverlay(tag) {
797
- return DEVTOOL_OVERLAY_PREFIXES.some((p) => tag.startsWith(p));
798
- }
799
- function escapeText(s) {
800
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
801
- }
802
- function escapeAttr(s) {
803
- return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
804
- }
805
- function serializeComposed(node, opts, depth) {
806
- if (depth > 256) return "";
807
- if (node.nodeType === Node.TEXT_NODE) {
808
- return escapeText(node.textContent ?? "");
809
- }
810
- if (node.nodeType === Node.COMMENT_NODE) return "";
811
- if (node.nodeType !== Node.ELEMENT_NODE) return "";
812
- const el = node;
813
- const tag = el.tagName.toLowerCase();
814
- if (SKIP_TAGS.has(tag)) return "";
815
- if (isDevtoolOverlay(tag)) return "";
816
- if (tag === "slot") {
817
- const slot = el;
818
- let assigned = [];
819
- try {
820
- assigned = slot.assignedNodes({ flatten: true });
821
- } catch {
769
+ tree = rrwebSnapshot.snapshot(document, {
770
+ inlineStylesheet: true,
771
+ inlineImages: true,
772
+ recordCanvas: true,
773
+ // Deliberately NOT slimming. SlimDOM drops <link rel="preload">,
774
+ // hidden form metadata, and other "noise" that's actually relevant
775
+ // when reproducing a layout bug.
776
+ slimDOM: false,
777
+ // Don't mask anything by default — debug-relay already runs a
778
+ // top-level redactor on console payloads, and on-screen text is the
779
+ // whole point of capturing a screenshot. Hosts that need PII masking
780
+ // can wire their own builtins.screenshot using rrweb-snapshot's
781
+ // `maskTextSelector` / `maskInputOptions`.
782
+ maskAllInputs: false
783
+ });
784
+ } finally {
785
+ for (const node of injected) {
786
+ try {
787
+ node.remove();
788
+ } catch {
789
+ }
822
790
  }
823
- const source = assigned.length > 0 ? assigned : Array.from(el.childNodes);
824
- return source.map((c) => serializeComposed(c, opts, depth + 1)).join("");
825
791
  }
826
- if (tag === "link" && (el.getAttribute("rel") || "").toLowerCase() === "stylesheet") {
827
- return "";
792
+ if (!tree) {
793
+ throw new Error("[@botim/debug-sdk] rrweb-snapshot returned null tree");
828
794
  }
829
- if (tag === "style") {
830
- return `<style>${el.textContent ?? ""}</style>`;
831
- }
832
- if (tag === "img" && opts.stripCrossOriginImages) {
833
- const src = el.getAttribute("src") || "";
834
- if (/^(https?:|\/\/)/i.test(src)) {
795
+ const payload = {
796
+ snapshot: tree,
797
+ viewport: {
798
+ w: window.innerWidth || document.documentElement.clientWidth || 0,
799
+ h: window.innerHeight || document.documentElement.clientHeight || 0
800
+ },
801
+ url: location.href,
802
+ capturedAt: Date.now()
803
+ };
804
+ return {
805
+ data: JSON.stringify(payload),
806
+ format: "rrweb-snapshot"
807
+ };
808
+ }
809
+ function injectAdoptedStyleSheets() {
810
+ const injected = [];
811
+ const collect = (sheets) => {
812
+ if (!sheets || sheets.length === 0) return "";
813
+ const chunks = [];
814
+ for (const sheet of sheets) {
835
815
  try {
836
- const u = new URL(src, location.href);
837
- if (u.origin !== location.origin) return "";
816
+ const rules = sheet.cssRules;
817
+ for (const rule of Array.from(rules)) chunks.push(rule.cssText);
838
818
  } catch {
839
- return "";
840
819
  }
841
820
  }
842
- }
843
- if (tag === "canvas") return `<canvas></canvas>`;
844
- const attrs = Array.from(el.attributes).filter((a) => !a.name.startsWith("on")).map((a) => ` ${a.name}="${escapeAttr(a.value)}"`).join("");
845
- if (VOID_ELEMENTS.has(tag)) return `<${tag}${attrs}>`;
846
- const childSource = el.shadowRoot ?? el;
847
- const inner = Array.from(childSource.childNodes).map((c) => serializeComposed(c, opts, depth + 1)).join("");
848
- return `<${tag}${attrs}>${inner}</${tag}>`;
849
- }
850
- function readInlineableStyles() {
851
- const chunks = [];
852
- for (const sheet of Array.from(document.styleSheets)) {
853
- try {
854
- const rules = sheet.cssRules;
855
- for (const rule of Array.from(rules)) chunks.push(rule.cssText);
856
- } catch {
821
+ return chunks.join("\n");
822
+ };
823
+ const inject = (parent, css) => {
824
+ if (!css) return;
825
+ const ownerDoc = parent instanceof Document ? parent : parent.ownerDocument;
826
+ if (!ownerDoc) return;
827
+ const style = ownerDoc.createElement("style");
828
+ style.setAttribute("data-botim-adopted", "1");
829
+ style.textContent = css;
830
+ const target = parent instanceof Document ? parent.head ?? parent.documentElement ?? parent.body : parent;
831
+ if (target) {
832
+ target.insertBefore(style, target.firstChild);
833
+ injected.push(style);
857
834
  }
858
- }
859
- return chunks.join("\n");
860
- }
861
- async function captureViaSvgForeignObject() {
862
- const dpr = Math.min(window.devicePixelRatio || 1, 2);
863
- const w = Math.max(1, Math.min(window.innerWidth || document.documentElement.clientWidth || 1024, 2400));
864
- const h = Math.max(1, Math.min(document.documentElement.scrollHeight, 4e3));
865
- const inlinedCss = readInlineableStyles();
866
- const bodyHtml = serializeComposed(document.body, { stripCrossOriginImages: true }, 0);
867
- const htmlStr = `<html xmlns="http://www.w3.org/1999/xhtml"><body>${bodyHtml}</body></html>`;
868
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"><foreignObject width="100%" height="100%">` + (inlinedCss ? `<style xmlns="http://www.w3.org/1999/xhtml">${escapeForXml(inlinedCss)}</style>` : "") + htmlStr + `</foreignObject></svg>`;
869
- const blobUrl = URL.createObjectURL(
870
- new Blob([svg], { type: "image/svg+xml;charset=utf-8" })
871
- );
835
+ };
872
836
  try {
873
- const img = await loadImage(blobUrl);
874
- const canvas = document.createElement("canvas");
875
- canvas.width = w * dpr;
876
- canvas.height = h * dpr;
877
- const ctx = canvas.getContext("2d");
878
- if (!ctx) throw new Error("canvas 2D context unavailable");
879
- const pageBg = getComputedStyle(document.body).backgroundColor || "#ffffff";
880
- ctx.fillStyle = pageBg.startsWith("rgba(0, 0, 0, 0)") ? "#ffffff" : pageBg;
881
- ctx.fillRect(0, 0, canvas.width, canvas.height);
882
- ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
883
- const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
884
- return { data: dataUrl.split(",")[1], format: "jpeg-base64" };
885
- } finally {
886
- URL.revokeObjectURL(blobUrl);
837
+ inject(
838
+ document,
839
+ collect(document.adoptedStyleSheets)
840
+ );
841
+ } catch {
887
842
  }
888
- }
889
- function loadImage(src) {
890
- return new Promise((resolve, reject) => {
891
- const img = new Image();
892
- img.crossOrigin = "anonymous";
893
- img.onload = () => resolve(img);
894
- img.onerror = () => reject(new Error("failed to render snapshot SVG"));
895
- img.src = src;
896
- });
897
- }
898
- function escapeForXml(s) {
899
- return s.replace(/]]>/g, "]]&gt;").replace(/<\//g, "&lt;/");
900
- }
901
- async function captureViaHtmlSnapshot() {
902
- const inlinedCss = readInlineableStyles();
903
- const bodyHtml = serializeComposed(document.body, { stripCrossOriginImages: false }, 0);
904
- const html = `<!DOCTYPE html>
905
- <html lang="en">
906
- <head>
907
- <meta charset="utf-8">
908
- <meta name="viewport" content="width=device-width,initial-scale=1">
909
- <base href="${escapeAttr(location.href)}">
910
- ` + (inlinedCss ? `<style data-botim-snapshot="1">${inlinedCss}</style>
911
- ` : "") + `</head>
912
- <body>${bodyHtml}</body>
913
- </html>`;
914
- return {
915
- data: JSON.stringify({
916
- html,
917
- viewport: { w: window.innerWidth, h: window.innerHeight },
918
- url: location.href,
919
- capturedAt: Date.now()
920
- }),
921
- format: "html-snapshot"
843
+ const walkRoot = (root) => {
844
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
845
+ let n = walker.currentNode;
846
+ while (n) {
847
+ const el = n;
848
+ const sr = el.shadowRoot;
849
+ if (sr) {
850
+ try {
851
+ inject(
852
+ sr,
853
+ collect(sr.adoptedStyleSheets)
854
+ );
855
+ } catch {
856
+ }
857
+ walkRoot(sr);
858
+ }
859
+ n = walker.nextNode();
860
+ }
922
861
  };
862
+ try {
863
+ walkRoot(document);
864
+ } catch {
865
+ }
866
+ return injected;
923
867
  }
924
868
  function registerBuiltins(registry, hooks = {}) {
925
869
  registry.register("ping", ping);