@botim/mp-debug-sdk 0.3.0 → 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 +100 -154
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -7
- package/dist/index.d.ts +16 -7
- package/dist/index.js +100 -154
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.d.cts
CHANGED
|
@@ -45,9 +45,11 @@ interface BuiltinHostHooks {
|
|
|
45
45
|
/**
|
|
46
46
|
* Capture a screenshot. Two return shapes are accepted:
|
|
47
47
|
* - a raw base64 string → assumed PNG (legacy form)
|
|
48
|
-
* - `{ data, format }` → format is one of
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* - `{ data, format }` → format is one of:
|
|
49
|
+
* 'png-base64' | 'jpeg-base64' — raw bitmap, viewer renders as <img>
|
|
50
|
+
* 'rrweb-snapshot' — `data` is JSON the viewer rebuilds
|
|
51
|
+
* 'html-snapshot' — legacy `data` JSON: { html, … }
|
|
52
|
+
* The SDK enforces a 1 MB base64/JSON cap regardless.
|
|
51
53
|
*/
|
|
52
54
|
screenshot?: () => ScreenshotResult | Promise<ScreenshotResult>;
|
|
53
55
|
}
|
|
@@ -55,10 +57,17 @@ type ScreenshotResult = string | {
|
|
|
55
57
|
data: string;
|
|
56
58
|
format?: 'png-base64' | 'jpeg-base64'
|
|
57
59
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* rrweb-snapshot tree, JSON-stringified. `data` parses to:
|
|
61
|
+
* { snapshot, viewport: { w, h }, url, capturedAt }
|
|
62
|
+
* The admin viewer calls `rrwebSnapshot.rebuild(snapshot, …)` into
|
|
63
|
+
* an iframe to re-materialize the page with full shadow DOM /
|
|
64
|
+
* adoptedStyleSheets / cross-origin CSS fidelity.
|
|
65
|
+
*/
|
|
66
|
+
| 'rrweb-snapshot'
|
|
67
|
+
/**
|
|
68
|
+
* Legacy self-contained HTML snapshot (pre-0.4). `data` JSON shape
|
|
69
|
+
* is `{ html, viewport, url, capturedAt }`. Kept on the wire for
|
|
70
|
+
* back-compat; admin viewers still know how to render it.
|
|
62
71
|
*/
|
|
63
72
|
| 'html-snapshot';
|
|
64
73
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -45,9 +45,11 @@ interface BuiltinHostHooks {
|
|
|
45
45
|
/**
|
|
46
46
|
* Capture a screenshot. Two return shapes are accepted:
|
|
47
47
|
* - a raw base64 string → assumed PNG (legacy form)
|
|
48
|
-
* - `{ data, format }` → format is one of
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* - `{ data, format }` → format is one of:
|
|
49
|
+
* 'png-base64' | 'jpeg-base64' — raw bitmap, viewer renders as <img>
|
|
50
|
+
* 'rrweb-snapshot' — `data` is JSON the viewer rebuilds
|
|
51
|
+
* 'html-snapshot' — legacy `data` JSON: { html, … }
|
|
52
|
+
* The SDK enforces a 1 MB base64/JSON cap regardless.
|
|
51
53
|
*/
|
|
52
54
|
screenshot?: () => ScreenshotResult | Promise<ScreenshotResult>;
|
|
53
55
|
}
|
|
@@ -55,10 +57,17 @@ type ScreenshotResult = string | {
|
|
|
55
57
|
data: string;
|
|
56
58
|
format?: 'png-base64' | 'jpeg-base64'
|
|
57
59
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
60
|
+
* rrweb-snapshot tree, JSON-stringified. `data` parses to:
|
|
61
|
+
* { snapshot, viewport: { w, h }, url, capturedAt }
|
|
62
|
+
* The admin viewer calls `rrwebSnapshot.rebuild(snapshot, …)` into
|
|
63
|
+
* an iframe to re-materialize the page with full shadow DOM /
|
|
64
|
+
* adoptedStyleSheets / cross-origin CSS fidelity.
|
|
65
|
+
*/
|
|
66
|
+
| 'rrweb-snapshot'
|
|
67
|
+
/**
|
|
68
|
+
* Legacy self-contained HTML snapshot (pre-0.4). `data` JSON shape
|
|
69
|
+
* is `{ html, viewport, url, capturedAt }`. Kept on the wire for
|
|
70
|
+
* back-compat; admin viewers still know how to render it.
|
|
62
71
|
*/
|
|
63
72
|
| 'html-snapshot';
|
|
64
73
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { snapshot } from 'rrweb-snapshot';
|
|
2
|
+
|
|
1
3
|
// src/types.ts
|
|
2
4
|
var SCHEMA_VERSION = 2;
|
|
3
5
|
|
|
@@ -105,8 +107,7 @@ var Transport = class {
|
|
|
105
107
|
Authorization: `Bearer ${this.opts.deviceToken}`
|
|
106
108
|
},
|
|
107
109
|
body: JSON.stringify(batch),
|
|
108
|
-
signal: this.inflightUpload.signal
|
|
109
|
-
keepalive: true
|
|
110
|
+
signal: this.inflightUpload.signal
|
|
110
111
|
});
|
|
111
112
|
if (!res.ok) {
|
|
112
113
|
throw new Error(`ingest http ${res.status}`);
|
|
@@ -198,6 +199,12 @@ var Transport = class {
|
|
|
198
199
|
}
|
|
199
200
|
if (this.opts.buffer.size() > 0) {
|
|
200
201
|
const events = this.opts.buffer.drain(this.opts.buffer.size());
|
|
202
|
+
const body = JSON.stringify({
|
|
203
|
+
sessionToken: this.opts.deviceToken,
|
|
204
|
+
events
|
|
205
|
+
});
|
|
206
|
+
const KEEPALIVE_BODY_LIMIT = 60 * 1024;
|
|
207
|
+
const useKeepalive = body.length <= KEEPALIVE_BODY_LIMIT;
|
|
201
208
|
try {
|
|
202
209
|
await this.internalFetch(this.opts.ingestUrl, {
|
|
203
210
|
method: "POST",
|
|
@@ -205,11 +212,8 @@ var Transport = class {
|
|
|
205
212
|
"Content-Type": "application/json",
|
|
206
213
|
Authorization: `Bearer ${this.opts.deviceToken}`
|
|
207
214
|
},
|
|
208
|
-
body
|
|
209
|
-
|
|
210
|
-
events
|
|
211
|
-
}),
|
|
212
|
-
keepalive: true
|
|
215
|
+
body,
|
|
216
|
+
...useKeepalive ? { keepalive: true } : {}
|
|
213
217
|
});
|
|
214
218
|
} catch (err) {
|
|
215
219
|
this.opts.onError?.(err);
|
|
@@ -749,8 +753,6 @@ var CommandRegistry = class {
|
|
|
749
753
|
}
|
|
750
754
|
}
|
|
751
755
|
};
|
|
752
|
-
|
|
753
|
-
// src/commands/builtins.ts
|
|
754
756
|
var MAX_DUMP_BYTES = 64 * 1024;
|
|
755
757
|
var MAX_SCREENSHOT_BYTES = 1024 * 1024;
|
|
756
758
|
async function defaultDomScreenshot() {
|
|
@@ -759,163 +761,107 @@ async function defaultDomScreenshot() {
|
|
|
759
761
|
"[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
760
762
|
);
|
|
761
763
|
}
|
|
764
|
+
const injected = injectAdoptedStyleSheets();
|
|
765
|
+
let tree;
|
|
762
766
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
"link",
|
|
785
|
-
"meta",
|
|
786
|
-
"source",
|
|
787
|
-
"track",
|
|
788
|
-
"wbr"
|
|
789
|
-
]);
|
|
790
|
-
var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "noscript", "template"]);
|
|
791
|
-
var DEVTOOL_OVERLAY_PREFIXES = ["vite-", "astro-dev-", "next-route-"];
|
|
792
|
-
function isDevtoolOverlay(tag) {
|
|
793
|
-
return DEVTOOL_OVERLAY_PREFIXES.some((p) => tag.startsWith(p));
|
|
794
|
-
}
|
|
795
|
-
function escapeText(s) {
|
|
796
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
797
|
-
}
|
|
798
|
-
function escapeAttr(s) {
|
|
799
|
-
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
800
|
-
}
|
|
801
|
-
function serializeComposed(node, opts, depth) {
|
|
802
|
-
if (depth > 256) return "";
|
|
803
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
804
|
-
return escapeText(node.textContent ?? "");
|
|
805
|
-
}
|
|
806
|
-
if (node.nodeType === Node.COMMENT_NODE) return "";
|
|
807
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return "";
|
|
808
|
-
const el = node;
|
|
809
|
-
const tag = el.tagName.toLowerCase();
|
|
810
|
-
if (SKIP_TAGS.has(tag)) return "";
|
|
811
|
-
if (isDevtoolOverlay(tag)) return "";
|
|
812
|
-
if (tag === "slot") {
|
|
813
|
-
const slot = el;
|
|
814
|
-
let assigned = [];
|
|
815
|
-
try {
|
|
816
|
-
assigned = slot.assignedNodes({ flatten: true });
|
|
817
|
-
} catch {
|
|
767
|
+
tree = snapshot(document, {
|
|
768
|
+
inlineStylesheet: true,
|
|
769
|
+
inlineImages: true,
|
|
770
|
+
recordCanvas: true,
|
|
771
|
+
// Deliberately NOT slimming. SlimDOM drops <link rel="preload">,
|
|
772
|
+
// hidden form metadata, and other "noise" that's actually relevant
|
|
773
|
+
// when reproducing a layout bug.
|
|
774
|
+
slimDOM: false,
|
|
775
|
+
// Don't mask anything by default — debug-relay already runs a
|
|
776
|
+
// top-level redactor on console payloads, and on-screen text is the
|
|
777
|
+
// whole point of capturing a screenshot. Hosts that need PII masking
|
|
778
|
+
// can wire their own builtins.screenshot using rrweb-snapshot's
|
|
779
|
+
// `maskTextSelector` / `maskInputOptions`.
|
|
780
|
+
maskAllInputs: false
|
|
781
|
+
});
|
|
782
|
+
} finally {
|
|
783
|
+
for (const node of injected) {
|
|
784
|
+
try {
|
|
785
|
+
node.remove();
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
818
788
|
}
|
|
819
|
-
const source = assigned.length > 0 ? assigned : Array.from(el.childNodes);
|
|
820
|
-
return source.map((c) => serializeComposed(c, opts, depth + 1)).join("");
|
|
821
789
|
}
|
|
822
|
-
if (
|
|
823
|
-
|
|
790
|
+
if (!tree) {
|
|
791
|
+
throw new Error("[@botim/debug-sdk] rrweb-snapshot returned null tree");
|
|
824
792
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
793
|
+
const payload = {
|
|
794
|
+
snapshot: tree,
|
|
795
|
+
viewport: {
|
|
796
|
+
w: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
797
|
+
h: window.innerHeight || document.documentElement.clientHeight || 0
|
|
798
|
+
},
|
|
799
|
+
url: location.href,
|
|
800
|
+
capturedAt: Date.now()
|
|
801
|
+
};
|
|
802
|
+
return {
|
|
803
|
+
data: JSON.stringify(payload),
|
|
804
|
+
format: "rrweb-snapshot"
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function injectAdoptedStyleSheets() {
|
|
808
|
+
const injected = [];
|
|
809
|
+
const collect = (sheets) => {
|
|
810
|
+
if (!sheets || sheets.length === 0) return "";
|
|
811
|
+
const chunks = [];
|
|
812
|
+
for (const sheet of sheets) {
|
|
831
813
|
try {
|
|
832
|
-
const
|
|
833
|
-
|
|
814
|
+
const rules = sheet.cssRules;
|
|
815
|
+
for (const rule of Array.from(rules)) chunks.push(rule.cssText);
|
|
834
816
|
} catch {
|
|
835
|
-
return "";
|
|
836
817
|
}
|
|
837
818
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
for (const rule of Array.from(rules)) chunks.push(rule.cssText);
|
|
852
|
-
} catch {
|
|
819
|
+
return chunks.join("\n");
|
|
820
|
+
};
|
|
821
|
+
const inject = (parent, css) => {
|
|
822
|
+
if (!css) return;
|
|
823
|
+
const ownerDoc = parent instanceof Document ? parent : parent.ownerDocument;
|
|
824
|
+
if (!ownerDoc) return;
|
|
825
|
+
const style = ownerDoc.createElement("style");
|
|
826
|
+
style.setAttribute("data-botim-adopted", "1");
|
|
827
|
+
style.textContent = css;
|
|
828
|
+
const target = parent instanceof Document ? parent.head ?? parent.documentElement ?? parent.body : parent;
|
|
829
|
+
if (target) {
|
|
830
|
+
target.insertBefore(style, target.firstChild);
|
|
831
|
+
injected.push(style);
|
|
853
832
|
}
|
|
854
|
-
}
|
|
855
|
-
return chunks.join("\n");
|
|
856
|
-
}
|
|
857
|
-
async function captureViaSvgForeignObject() {
|
|
858
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
859
|
-
const w = Math.max(1, Math.min(window.innerWidth || document.documentElement.clientWidth || 1024, 2400));
|
|
860
|
-
const h = Math.max(1, Math.min(document.documentElement.scrollHeight, 4e3));
|
|
861
|
-
const inlinedCss = readInlineableStyles();
|
|
862
|
-
const bodyHtml = serializeComposed(document.body, { stripCrossOriginImages: true }, 0);
|
|
863
|
-
const htmlStr = `<html xmlns="http://www.w3.org/1999/xhtml"><body>${bodyHtml}</body></html>`;
|
|
864
|
-
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>`;
|
|
865
|
-
const blobUrl = URL.createObjectURL(
|
|
866
|
-
new Blob([svg], { type: "image/svg+xml;charset=utf-8" })
|
|
867
|
-
);
|
|
833
|
+
};
|
|
868
834
|
try {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
if (!ctx) throw new Error("canvas 2D context unavailable");
|
|
875
|
-
const pageBg = getComputedStyle(document.body).backgroundColor || "#ffffff";
|
|
876
|
-
ctx.fillStyle = pageBg.startsWith("rgba(0, 0, 0, 0)") ? "#ffffff" : pageBg;
|
|
877
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
878
|
-
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
879
|
-
const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
|
|
880
|
-
return { data: dataUrl.split(",")[1], format: "jpeg-base64" };
|
|
881
|
-
} finally {
|
|
882
|
-
URL.revokeObjectURL(blobUrl);
|
|
835
|
+
inject(
|
|
836
|
+
document,
|
|
837
|
+
collect(document.adoptedStyleSheets)
|
|
838
|
+
);
|
|
839
|
+
} catch {
|
|
883
840
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
<head>
|
|
903
|
-
<meta charset="utf-8">
|
|
904
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
905
|
-
<base href="${escapeAttr(location.href)}">
|
|
906
|
-
` + (inlinedCss ? `<style data-botim-snapshot="1">${inlinedCss}</style>
|
|
907
|
-
` : "") + `</head>
|
|
908
|
-
<body>${bodyHtml}</body>
|
|
909
|
-
</html>`;
|
|
910
|
-
return {
|
|
911
|
-
data: JSON.stringify({
|
|
912
|
-
html,
|
|
913
|
-
viewport: { w: window.innerWidth, h: window.innerHeight },
|
|
914
|
-
url: location.href,
|
|
915
|
-
capturedAt: Date.now()
|
|
916
|
-
}),
|
|
917
|
-
format: "html-snapshot"
|
|
841
|
+
const walkRoot = (root) => {
|
|
842
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
843
|
+
let n = walker.currentNode;
|
|
844
|
+
while (n) {
|
|
845
|
+
const el = n;
|
|
846
|
+
const sr = el.shadowRoot;
|
|
847
|
+
if (sr) {
|
|
848
|
+
try {
|
|
849
|
+
inject(
|
|
850
|
+
sr,
|
|
851
|
+
collect(sr.adoptedStyleSheets)
|
|
852
|
+
);
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
855
|
+
walkRoot(sr);
|
|
856
|
+
}
|
|
857
|
+
n = walker.nextNode();
|
|
858
|
+
}
|
|
918
859
|
};
|
|
860
|
+
try {
|
|
861
|
+
walkRoot(document);
|
|
862
|
+
} catch {
|
|
863
|
+
}
|
|
864
|
+
return injected;
|
|
919
865
|
}
|
|
920
866
|
function registerBuiltins(registry, hooks = {}) {
|
|
921
867
|
registry.register("ping", ping);
|