@botim/mp-debug-sdk 0.3.1 → 0.5.2
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 +160 -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 +160 -154
- package/dist/index.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +12 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -2
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
|
|
|
@@ -270,7 +272,7 @@ function resolveAgainstEndpoint(url, base) {
|
|
|
270
272
|
function detectDeviceInfo(app, override) {
|
|
271
273
|
const ua = typeof navigator !== "undefined" ? navigator.userAgent : void 0;
|
|
272
274
|
return {
|
|
273
|
-
deviceId: override?.deviceId ??
|
|
275
|
+
deviceId: override?.deviceId ?? loadOrCreateDeviceId(),
|
|
274
276
|
platform: override?.platform ?? detectPlatform(ua),
|
|
275
277
|
osVersion: override?.osVersion,
|
|
276
278
|
appName: override?.appName ?? app.name,
|
|
@@ -286,6 +288,21 @@ function detectPlatform(ua) {
|
|
|
286
288
|
if (/Mozilla|Chrome|Safari|Firefox/i.test(ua)) return "web";
|
|
287
289
|
return "unknown";
|
|
288
290
|
}
|
|
291
|
+
var DEVICE_ID_STORAGE_KEY = "botim-debug-sdk:device-id";
|
|
292
|
+
function loadOrCreateDeviceId() {
|
|
293
|
+
try {
|
|
294
|
+
const ls = typeof localStorage !== "undefined" ? localStorage : null;
|
|
295
|
+
if (ls) {
|
|
296
|
+
const stored = ls.getItem(DEVICE_ID_STORAGE_KEY);
|
|
297
|
+
if (stored && stored.length > 0) return stored;
|
|
298
|
+
const fresh = generateDeviceId();
|
|
299
|
+
ls.setItem(DEVICE_ID_STORAGE_KEY, fresh);
|
|
300
|
+
return fresh;
|
|
301
|
+
}
|
|
302
|
+
} catch {
|
|
303
|
+
}
|
|
304
|
+
return generateDeviceId();
|
|
305
|
+
}
|
|
289
306
|
function generateDeviceId() {
|
|
290
307
|
const c = typeof crypto !== "undefined" ? crypto : void 0;
|
|
291
308
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -560,6 +577,11 @@ function wrapFetch(opts) {
|
|
|
560
577
|
method,
|
|
561
578
|
url,
|
|
562
579
|
status: res.status,
|
|
580
|
+
// statusText carries the human label ("OK", "Not Found"). Pre-HTTP/2
|
|
581
|
+
// responses always have it; HTTP/2+ defines it as empty by spec but
|
|
582
|
+
// most browsers synthesize one from the code, so this is reliable
|
|
583
|
+
// enough to display alongside the status code.
|
|
584
|
+
statusText: res.statusText || void 0,
|
|
563
585
|
durationMs: Date.now() - start,
|
|
564
586
|
resHeaders: headersFromResponse(res),
|
|
565
587
|
resBody
|
|
@@ -573,7 +595,14 @@ function wrapFetch(opts) {
|
|
|
573
595
|
url,
|
|
574
596
|
durationMs: Date.now() - start,
|
|
575
597
|
errorMessage: err instanceof Error ? err.message : String(err),
|
|
576
|
-
errorName: err instanceof Error ? err.name : void 0
|
|
598
|
+
errorName: err instanceof Error ? err.name : void 0,
|
|
599
|
+
// Stack from the rejected promise — points into fetch internals
|
|
600
|
+
// and (when present) the call site that issued the request.
|
|
601
|
+
errorStack: err instanceof Error ? err.stack : void 0,
|
|
602
|
+
// undici frequently wraps the real reason in `cause` (e.g.
|
|
603
|
+
// `TypeError: fetch failed` outside, `Error: ECONNREFUSED` inside).
|
|
604
|
+
// Flatten the chain so operators don't have to dig.
|
|
605
|
+
errorCause: collectCauseChain(err)
|
|
577
606
|
});
|
|
578
607
|
throw err;
|
|
579
608
|
}
|
|
@@ -582,6 +611,24 @@ function wrapFetch(opts) {
|
|
|
582
611
|
target.fetch = original;
|
|
583
612
|
};
|
|
584
613
|
}
|
|
614
|
+
function collectCauseChain(err) {
|
|
615
|
+
if (!err || typeof err !== "object") return void 0;
|
|
616
|
+
const lines = [];
|
|
617
|
+
let cur = err.cause;
|
|
618
|
+
const seen = /* @__PURE__ */ new Set();
|
|
619
|
+
while (cur && lines.length < 5) {
|
|
620
|
+
if (seen.has(cur)) break;
|
|
621
|
+
seen.add(cur);
|
|
622
|
+
if (cur instanceof Error) {
|
|
623
|
+
lines.push(`${cur.name}: ${cur.message}`);
|
|
624
|
+
cur = cur.cause;
|
|
625
|
+
} else {
|
|
626
|
+
lines.push(String(cur));
|
|
627
|
+
cur = cur?.cause;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return lines.length ? lines.join("\n") : void 0;
|
|
631
|
+
}
|
|
585
632
|
function wrapXHR(opts) {
|
|
586
633
|
if (typeof XMLHttpRequest === "undefined") return () => {
|
|
587
634
|
};
|
|
@@ -617,6 +664,7 @@ function wrapXHR(opts) {
|
|
|
617
664
|
}
|
|
618
665
|
s.start = Date.now();
|
|
619
666
|
s.reqBody = typeof body === "string" ? body : void 0;
|
|
667
|
+
const sendSiteStack = captureCallSiteStack();
|
|
620
668
|
opts.emit({
|
|
621
669
|
phase: "request",
|
|
622
670
|
reqId: s.reqId,
|
|
@@ -634,25 +682,32 @@ function wrapXHR(opts) {
|
|
|
634
682
|
method: s.method,
|
|
635
683
|
url: s.url,
|
|
636
684
|
status: this.status,
|
|
685
|
+
// XHR exposes statusText directly; same display purpose as fetch.
|
|
686
|
+
statusText: this.statusText || void 0,
|
|
637
687
|
durationMs: Date.now() - s.start,
|
|
638
688
|
resHeaders: headers,
|
|
639
689
|
resBody
|
|
640
690
|
});
|
|
641
691
|
};
|
|
642
|
-
const onError = () => {
|
|
692
|
+
const onError = (kind) => () => {
|
|
643
693
|
opts.emit({
|
|
644
694
|
phase: "error",
|
|
645
695
|
reqId: s.reqId,
|
|
646
696
|
method: s.method,
|
|
647
697
|
url: s.url,
|
|
648
698
|
durationMs: Date.now() - s.start,
|
|
649
|
-
|
|
699
|
+
// Distinguish error/timeout/abort in the message — the standard
|
|
700
|
+
// XHR `statusText` is empty for `error` and unhelpful for the
|
|
701
|
+
// others, so we synthesise a clear label.
|
|
702
|
+
errorMessage: this.statusText || `xhr ${kind}`,
|
|
703
|
+
errorName: `XHR${kind[0].toUpperCase()}${kind.slice(1)}`,
|
|
704
|
+
errorStack: sendSiteStack
|
|
650
705
|
});
|
|
651
706
|
};
|
|
652
707
|
this.addEventListener("load", onLoad);
|
|
653
|
-
this.addEventListener("error", onError);
|
|
654
|
-
this.addEventListener("timeout", onError);
|
|
655
|
-
this.addEventListener("abort", onError);
|
|
708
|
+
this.addEventListener("error", onError("error"));
|
|
709
|
+
this.addEventListener("timeout", onError("timeout"));
|
|
710
|
+
this.addEventListener("abort", onError("abort"));
|
|
656
711
|
return origSend.apply(this, [body]);
|
|
657
712
|
};
|
|
658
713
|
return () => {
|
|
@@ -661,6 +716,15 @@ function wrapXHR(opts) {
|
|
|
661
716
|
proto.setRequestHeader = origSetReqHeader;
|
|
662
717
|
};
|
|
663
718
|
}
|
|
719
|
+
function captureCallSiteStack() {
|
|
720
|
+
try {
|
|
721
|
+
throw new Error("xhr-callsite");
|
|
722
|
+
} catch (err) {
|
|
723
|
+
if (!(err instanceof Error) || !err.stack) return void 0;
|
|
724
|
+
const lines = err.stack.split("\n");
|
|
725
|
+
return lines.slice(2).join("\n") || void 0;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
664
728
|
function parseXhrHeaders(raw) {
|
|
665
729
|
const out = {};
|
|
666
730
|
if (!raw) return out;
|
|
@@ -753,8 +817,6 @@ var CommandRegistry = class {
|
|
|
753
817
|
}
|
|
754
818
|
}
|
|
755
819
|
};
|
|
756
|
-
|
|
757
|
-
// src/commands/builtins.ts
|
|
758
820
|
var MAX_DUMP_BYTES = 64 * 1024;
|
|
759
821
|
var MAX_SCREENSHOT_BYTES = 1024 * 1024;
|
|
760
822
|
async function defaultDomScreenshot() {
|
|
@@ -763,163 +825,107 @@ async function defaultDomScreenshot() {
|
|
|
763
825
|
"[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
764
826
|
);
|
|
765
827
|
}
|
|
828
|
+
const injected = injectAdoptedStyleSheets();
|
|
829
|
+
let tree;
|
|
766
830
|
try {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
801
|
-
}
|
|
802
|
-
function escapeAttr(s) {
|
|
803
|
-
return s.replace(/&/g, "&").replace(/"/g, """);
|
|
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 {
|
|
831
|
+
tree = rrwebSnapshot.snapshot(document, {
|
|
832
|
+
inlineStylesheet: true,
|
|
833
|
+
inlineImages: true,
|
|
834
|
+
recordCanvas: true,
|
|
835
|
+
// Deliberately NOT slimming. SlimDOM drops <link rel="preload">,
|
|
836
|
+
// hidden form metadata, and other "noise" that's actually relevant
|
|
837
|
+
// when reproducing a layout bug.
|
|
838
|
+
slimDOM: false,
|
|
839
|
+
// Don't mask anything by default — debug-relay already runs a
|
|
840
|
+
// top-level redactor on console payloads, and on-screen text is the
|
|
841
|
+
// whole point of capturing a screenshot. Hosts that need PII masking
|
|
842
|
+
// can wire their own builtins.screenshot using rrweb-snapshot's
|
|
843
|
+
// `maskTextSelector` / `maskInputOptions`.
|
|
844
|
+
maskAllInputs: false
|
|
845
|
+
});
|
|
846
|
+
} finally {
|
|
847
|
+
for (const node of injected) {
|
|
848
|
+
try {
|
|
849
|
+
node.remove();
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
822
852
|
}
|
|
823
|
-
const source = assigned.length > 0 ? assigned : Array.from(el.childNodes);
|
|
824
|
-
return source.map((c) => serializeComposed(c, opts, depth + 1)).join("");
|
|
825
853
|
}
|
|
826
|
-
if (
|
|
827
|
-
|
|
854
|
+
if (!tree) {
|
|
855
|
+
throw new Error("[@botim/debug-sdk] rrweb-snapshot returned null tree");
|
|
828
856
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
857
|
+
const payload = {
|
|
858
|
+
snapshot: tree,
|
|
859
|
+
viewport: {
|
|
860
|
+
w: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
861
|
+
h: window.innerHeight || document.documentElement.clientHeight || 0
|
|
862
|
+
},
|
|
863
|
+
url: location.href,
|
|
864
|
+
capturedAt: Date.now()
|
|
865
|
+
};
|
|
866
|
+
return {
|
|
867
|
+
data: JSON.stringify(payload),
|
|
868
|
+
format: "rrweb-snapshot"
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function injectAdoptedStyleSheets() {
|
|
872
|
+
const injected = [];
|
|
873
|
+
const collect = (sheets) => {
|
|
874
|
+
if (!sheets || sheets.length === 0) return "";
|
|
875
|
+
const chunks = [];
|
|
876
|
+
for (const sheet of sheets) {
|
|
835
877
|
try {
|
|
836
|
-
const
|
|
837
|
-
|
|
878
|
+
const rules = sheet.cssRules;
|
|
879
|
+
for (const rule of Array.from(rules)) chunks.push(rule.cssText);
|
|
838
880
|
} catch {
|
|
839
|
-
return "";
|
|
840
881
|
}
|
|
841
882
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
for (const rule of Array.from(rules)) chunks.push(rule.cssText);
|
|
856
|
-
} catch {
|
|
883
|
+
return chunks.join("\n");
|
|
884
|
+
};
|
|
885
|
+
const inject = (parent, css) => {
|
|
886
|
+
if (!css) return;
|
|
887
|
+
const ownerDoc = parent instanceof Document ? parent : parent.ownerDocument;
|
|
888
|
+
if (!ownerDoc) return;
|
|
889
|
+
const style = ownerDoc.createElement("style");
|
|
890
|
+
style.setAttribute("data-botim-adopted", "1");
|
|
891
|
+
style.textContent = css;
|
|
892
|
+
const target = parent instanceof Document ? parent.head ?? parent.documentElement ?? parent.body : parent;
|
|
893
|
+
if (target) {
|
|
894
|
+
target.insertBefore(style, target.firstChild);
|
|
895
|
+
injected.push(style);
|
|
857
896
|
}
|
|
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
|
-
);
|
|
897
|
+
};
|
|
872
898
|
try {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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);
|
|
899
|
+
inject(
|
|
900
|
+
document,
|
|
901
|
+
collect(document.adoptedStyleSheets)
|
|
902
|
+
);
|
|
903
|
+
} catch {
|
|
887
904
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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"
|
|
905
|
+
const walkRoot = (root) => {
|
|
906
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
907
|
+
let n = walker.currentNode;
|
|
908
|
+
while (n) {
|
|
909
|
+
const el = n;
|
|
910
|
+
const sr = el.shadowRoot;
|
|
911
|
+
if (sr) {
|
|
912
|
+
try {
|
|
913
|
+
inject(
|
|
914
|
+
sr,
|
|
915
|
+
collect(sr.adoptedStyleSheets)
|
|
916
|
+
);
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
walkRoot(sr);
|
|
920
|
+
}
|
|
921
|
+
n = walker.nextNode();
|
|
922
|
+
}
|
|
922
923
|
};
|
|
924
|
+
try {
|
|
925
|
+
walkRoot(document);
|
|
926
|
+
} catch {
|
|
927
|
+
}
|
|
928
|
+
return injected;
|
|
923
929
|
}
|
|
924
930
|
function registerBuiltins(registry, hooks = {}) {
|
|
925
931
|
registry.register("ping", ping);
|