@botim/mp-debug-sdk 1.2.0 → 1.3.0
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/auto.cjs +153 -4
- package/dist/auto.d.cts +1 -1
- package/dist/auto.d.ts +1 -1
- package/dist/auto.js +153 -5
- package/dist/index.cjs +153 -4
- package/dist/index.d.cts +21 -19
- package/dist/index.d.ts +21 -19
- package/dist/index.js +153 -5
- package/package.json +1 -1
package/dist/auto.cjs
CHANGED
|
@@ -1036,7 +1036,27 @@ var MAX_EXEC_CODE_BYTES = 8 * 1024;
|
|
|
1036
1036
|
var DEFAULT_EXEC_TIMEOUT_MS = 5e3;
|
|
1037
1037
|
var MIN_EXEC_TIMEOUT_MS = 250;
|
|
1038
1038
|
var MAX_EXEC_TIMEOUT_MS = 3e4;
|
|
1039
|
-
|
|
1039
|
+
var SCREENSHOT_FORMATS = [
|
|
1040
|
+
"png-base64",
|
|
1041
|
+
"jpeg-base64",
|
|
1042
|
+
"rrweb-snapshot",
|
|
1043
|
+
"html-snapshot"
|
|
1044
|
+
];
|
|
1045
|
+
var DEFAULT_JPEG_QUALITY = 0.85;
|
|
1046
|
+
async function defaultDomScreenshot(requested, quality) {
|
|
1047
|
+
switch (requested) {
|
|
1048
|
+
case "png-base64":
|
|
1049
|
+
return rasterizeDom("image/png");
|
|
1050
|
+
case "jpeg-base64":
|
|
1051
|
+
return rasterizeDom("image/jpeg", quality ?? DEFAULT_JPEG_QUALITY);
|
|
1052
|
+
case "html-snapshot":
|
|
1053
|
+
return domHtmlSnapshot();
|
|
1054
|
+
case "rrweb-snapshot":
|
|
1055
|
+
default:
|
|
1056
|
+
return rrwebDomSnapshot();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
async function rrwebDomSnapshot() {
|
|
1040
1060
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1041
1061
|
throw new Error(
|
|
1042
1062
|
"[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
@@ -1085,6 +1105,126 @@ async function defaultDomScreenshot() {
|
|
|
1085
1105
|
format: "rrweb-snapshot"
|
|
1086
1106
|
};
|
|
1087
1107
|
}
|
|
1108
|
+
function domViewport() {
|
|
1109
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
"[@botim/debug-sdk] screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
return {
|
|
1115
|
+
w: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
1116
|
+
h: window.innerHeight || document.documentElement.clientHeight || 0
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
async function rasterizeDom(mime, quality) {
|
|
1120
|
+
const { w, h } = domViewport();
|
|
1121
|
+
if (!w || !h) {
|
|
1122
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: viewport has zero size.");
|
|
1123
|
+
}
|
|
1124
|
+
const canvas = document.createElement("canvas");
|
|
1125
|
+
if (typeof canvas.getContext !== "function") {
|
|
1126
|
+
throw new Error(
|
|
1127
|
+
'[@botim/debug-sdk] raster screenshot: <canvas> unavailable in this runtime \u2014 request format "rrweb-snapshot" instead, or wire builtins.screenshot.'
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
const injected = injectAdoptedStyleSheets();
|
|
1131
|
+
let svgUrl;
|
|
1132
|
+
try {
|
|
1133
|
+
const css = collectDocumentCss();
|
|
1134
|
+
const bodyClone = document.body.cloneNode(true);
|
|
1135
|
+
bodyClone.querySelectorAll("script, noscript").forEach((n) => n.remove());
|
|
1136
|
+
const bodyXml = new XMLSerializer().serializeToString(bodyClone);
|
|
1137
|
+
const bg = safeBodyBackground();
|
|
1138
|
+
const markup = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"><foreignObject x="0" y="0" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="width:${w}px;height:${h}px;overflow:hidden;background:${bg}"><style>${css}</style>${bodyXml}</div></foreignObject></svg>`;
|
|
1139
|
+
svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
1140
|
+
} finally {
|
|
1141
|
+
for (const node of injected) {
|
|
1142
|
+
try {
|
|
1143
|
+
node.remove();
|
|
1144
|
+
} catch {
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const img = await loadImage(svgUrl);
|
|
1149
|
+
canvas.width = w;
|
|
1150
|
+
canvas.height = h;
|
|
1151
|
+
const cx = canvas.getContext("2d");
|
|
1152
|
+
if (!cx) {
|
|
1153
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: 2D canvas context unavailable.");
|
|
1154
|
+
}
|
|
1155
|
+
cx.drawImage(img, 0, 0, w, h);
|
|
1156
|
+
let dataUrl;
|
|
1157
|
+
try {
|
|
1158
|
+
dataUrl = canvas.toDataURL(mime, quality);
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
throw new Error(
|
|
1161
|
+
`[@botim/debug-sdk] raster screenshot: canvas tainted by a cross-origin image/font without CORS \u2014 request "rrweb-snapshot" for full fidelity. (${err instanceof Error ? err.message : String(err)})`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
const comma = dataUrl.indexOf(",");
|
|
1165
|
+
const data = comma >= 0 ? dataUrl.slice(comma + 1) : "";
|
|
1166
|
+
if (!data) {
|
|
1167
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: encoder returned empty image data.");
|
|
1168
|
+
}
|
|
1169
|
+
return { data, format: mime === "image/png" ? "png-base64" : "jpeg-base64" };
|
|
1170
|
+
}
|
|
1171
|
+
function loadImage(src) {
|
|
1172
|
+
return new Promise((resolve, reject) => {
|
|
1173
|
+
const img = new Image();
|
|
1174
|
+
img.onload = () => resolve(img);
|
|
1175
|
+
img.onerror = () => reject(
|
|
1176
|
+
new Error(
|
|
1177
|
+
"[@botim/debug-sdk] raster screenshot: the offscreen SVG failed to render (often a non-XML-serializable DOM or a blocked resource)."
|
|
1178
|
+
)
|
|
1179
|
+
);
|
|
1180
|
+
img.src = src;
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
function collectDocumentCss() {
|
|
1184
|
+
const chunks = [];
|
|
1185
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
1186
|
+
try {
|
|
1187
|
+
for (const rule of Array.from(sheet.cssRules)) chunks.push(rule.cssText);
|
|
1188
|
+
} catch {
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return chunks.join("\n");
|
|
1192
|
+
}
|
|
1193
|
+
function safeBodyBackground() {
|
|
1194
|
+
try {
|
|
1195
|
+
const bg = getComputedStyle(document.body).backgroundColor;
|
|
1196
|
+
if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
|
|
1197
|
+
} catch {
|
|
1198
|
+
}
|
|
1199
|
+
return "#ffffff";
|
|
1200
|
+
}
|
|
1201
|
+
async function domHtmlSnapshot() {
|
|
1202
|
+
const { w, h } = domViewport();
|
|
1203
|
+
const injected = injectAdoptedStyleSheets();
|
|
1204
|
+
let html;
|
|
1205
|
+
try {
|
|
1206
|
+
const clone = document.documentElement.cloneNode(true);
|
|
1207
|
+
clone.querySelectorAll("script").forEach((n) => n.remove());
|
|
1208
|
+
html = `<!doctype html>
|
|
1209
|
+
${clone.outerHTML}`;
|
|
1210
|
+
} finally {
|
|
1211
|
+
for (const node of injected) {
|
|
1212
|
+
try {
|
|
1213
|
+
node.remove();
|
|
1214
|
+
} catch {
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return {
|
|
1219
|
+
data: JSON.stringify({
|
|
1220
|
+
html,
|
|
1221
|
+
viewport: { w, h },
|
|
1222
|
+
url: location.href,
|
|
1223
|
+
capturedAt: Date.now()
|
|
1224
|
+
}),
|
|
1225
|
+
format: "html-snapshot"
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1088
1228
|
function injectAdoptedStyleSheets() {
|
|
1089
1229
|
const injected = [];
|
|
1090
1230
|
const collect = (sheets) => {
|
|
@@ -1189,10 +1329,18 @@ function makeSetFlag(setFeatureFlag) {
|
|
|
1189
1329
|
return { applied: true, key };
|
|
1190
1330
|
};
|
|
1191
1331
|
}
|
|
1332
|
+
function normalizeFormat(v) {
|
|
1333
|
+
return typeof v === "string" && SCREENSHOT_FORMATS.includes(v) ? v : void 0;
|
|
1334
|
+
}
|
|
1335
|
+
function normalizeQuality(v) {
|
|
1336
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return void 0;
|
|
1337
|
+
return Math.min(1, Math.max(0, v));
|
|
1338
|
+
}
|
|
1192
1339
|
function makeScreenshot(screenshot) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
const
|
|
1340
|
+
return async (args) => {
|
|
1341
|
+
const requested = normalizeFormat(args.format);
|
|
1342
|
+
const quality = normalizeQuality(args.quality);
|
|
1343
|
+
const result = screenshot ? await screenshot(requested) : await defaultDomScreenshot(requested, quality);
|
|
1196
1344
|
let data;
|
|
1197
1345
|
let format;
|
|
1198
1346
|
if (typeof result === "string") {
|
|
@@ -1863,6 +2011,7 @@ exports.DEFAULT_MAX_BATCH_SIZE = DEFAULT_MAX_BATCH_SIZE;
|
|
|
1863
2011
|
exports.DEFAULT_MAX_BODY_BYTES = DEFAULT_MAX_BODY_BYTES;
|
|
1864
2012
|
exports.DEFAULT_REDACT_HEADERS = DEFAULT_REDACT_HEADERS;
|
|
1865
2013
|
exports.SCHEMA_VERSION = SCHEMA_VERSION;
|
|
2014
|
+
exports.SCREENSHOT_FORMATS = SCREENSHOT_FORMATS;
|
|
1866
2015
|
exports.clearConsentDecision = clearConsentDecision;
|
|
1867
2016
|
exports.enableRemoteDebug = enableRemoteDebug;
|
|
1868
2017
|
exports.getBOT = getBOT;
|
package/dist/auto.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AppInfo, BotimConfigError, BotimConsentError, BuiltinHostHooks, ConsentDecision, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, DedupOptions, ExecOptions, RedactionConfig, RemoteDebugHandle, RemoteDebugOptions, SamplingConfig, SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision } from './index.cjs';
|
|
1
|
+
export { AppInfo, BotimConfigError, BotimConsentError, BuiltinHostHooks, ConsentDecision, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, DedupOptions, ExecOptions, RedactionConfig, RemoteDebugHandle, RemoteDebugOptions, SCREENSHOT_FORMATS, SamplingConfig, ScreenshotFormat, ScreenshotResult, SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision } from './index.cjs';
|
|
2
2
|
export { AttachRequest, AttachResponse, BotimConfig, BotimEnv, BridgePayload, CommandAckPayload, CommandContext, CommandHandler, CommandPollResponse, CommandRejectedPayload, CommandRequest, ConsentInput, ConsentPromptCopy, ConsolePayload, DebugEvent, DebugEventBase, DeviceInfo, ErrorPayload, ErrorSource, EventLevel, EventMeta, EventType, LifecycleEvent, LifecyclePayload, NetworkPayload, NetworkPhase, PerfPayload, SCHEMA_VERSION, SerializedValue, StreamFrame, UploadAck, UploadBatch } from './types.cjs';
|
package/dist/auto.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { AppInfo, BotimConfigError, BotimConsentError, BuiltinHostHooks, ConsentDecision, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, DedupOptions, ExecOptions, RedactionConfig, RemoteDebugHandle, RemoteDebugOptions, SamplingConfig, SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision } from './index.js';
|
|
1
|
+
export { AppInfo, BotimConfigError, BotimConsentError, BuiltinHostHooks, ConsentDecision, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, DedupOptions, ExecOptions, RedactionConfig, RemoteDebugHandle, RemoteDebugOptions, SCREENSHOT_FORMATS, SamplingConfig, ScreenshotFormat, ScreenshotResult, SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision } from './index.js';
|
|
2
2
|
export { AttachRequest, AttachResponse, BotimConfig, BotimEnv, BridgePayload, CommandAckPayload, CommandContext, CommandHandler, CommandPollResponse, CommandRejectedPayload, CommandRequest, ConsentInput, ConsentPromptCopy, ConsolePayload, DebugEvent, DebugEventBase, DeviceInfo, ErrorPayload, ErrorSource, EventLevel, EventMeta, EventType, LifecycleEvent, LifecyclePayload, NetworkPayload, NetworkPhase, PerfPayload, SCHEMA_VERSION, SerializedValue, StreamFrame, UploadAck, UploadBatch } from './types.js';
|
package/dist/auto.js
CHANGED
|
@@ -1034,7 +1034,27 @@ var MAX_EXEC_CODE_BYTES = 8 * 1024;
|
|
|
1034
1034
|
var DEFAULT_EXEC_TIMEOUT_MS = 5e3;
|
|
1035
1035
|
var MIN_EXEC_TIMEOUT_MS = 250;
|
|
1036
1036
|
var MAX_EXEC_TIMEOUT_MS = 3e4;
|
|
1037
|
-
|
|
1037
|
+
var SCREENSHOT_FORMATS = [
|
|
1038
|
+
"png-base64",
|
|
1039
|
+
"jpeg-base64",
|
|
1040
|
+
"rrweb-snapshot",
|
|
1041
|
+
"html-snapshot"
|
|
1042
|
+
];
|
|
1043
|
+
var DEFAULT_JPEG_QUALITY = 0.85;
|
|
1044
|
+
async function defaultDomScreenshot(requested, quality) {
|
|
1045
|
+
switch (requested) {
|
|
1046
|
+
case "png-base64":
|
|
1047
|
+
return rasterizeDom("image/png");
|
|
1048
|
+
case "jpeg-base64":
|
|
1049
|
+
return rasterizeDom("image/jpeg", quality ?? DEFAULT_JPEG_QUALITY);
|
|
1050
|
+
case "html-snapshot":
|
|
1051
|
+
return domHtmlSnapshot();
|
|
1052
|
+
case "rrweb-snapshot":
|
|
1053
|
+
default:
|
|
1054
|
+
return rrwebDomSnapshot();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async function rrwebDomSnapshot() {
|
|
1038
1058
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1039
1059
|
throw new Error(
|
|
1040
1060
|
"[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
@@ -1083,6 +1103,126 @@ async function defaultDomScreenshot() {
|
|
|
1083
1103
|
format: "rrweb-snapshot"
|
|
1084
1104
|
};
|
|
1085
1105
|
}
|
|
1106
|
+
function domViewport() {
|
|
1107
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1108
|
+
throw new Error(
|
|
1109
|
+
"[@botim/debug-sdk] screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
w: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
1114
|
+
h: window.innerHeight || document.documentElement.clientHeight || 0
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
async function rasterizeDom(mime, quality) {
|
|
1118
|
+
const { w, h } = domViewport();
|
|
1119
|
+
if (!w || !h) {
|
|
1120
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: viewport has zero size.");
|
|
1121
|
+
}
|
|
1122
|
+
const canvas = document.createElement("canvas");
|
|
1123
|
+
if (typeof canvas.getContext !== "function") {
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
'[@botim/debug-sdk] raster screenshot: <canvas> unavailable in this runtime \u2014 request format "rrweb-snapshot" instead, or wire builtins.screenshot.'
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
const injected = injectAdoptedStyleSheets();
|
|
1129
|
+
let svgUrl;
|
|
1130
|
+
try {
|
|
1131
|
+
const css = collectDocumentCss();
|
|
1132
|
+
const bodyClone = document.body.cloneNode(true);
|
|
1133
|
+
bodyClone.querySelectorAll("script, noscript").forEach((n) => n.remove());
|
|
1134
|
+
const bodyXml = new XMLSerializer().serializeToString(bodyClone);
|
|
1135
|
+
const bg = safeBodyBackground();
|
|
1136
|
+
const markup = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"><foreignObject x="0" y="0" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="width:${w}px;height:${h}px;overflow:hidden;background:${bg}"><style>${css}</style>${bodyXml}</div></foreignObject></svg>`;
|
|
1137
|
+
svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
1138
|
+
} finally {
|
|
1139
|
+
for (const node of injected) {
|
|
1140
|
+
try {
|
|
1141
|
+
node.remove();
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const img = await loadImage(svgUrl);
|
|
1147
|
+
canvas.width = w;
|
|
1148
|
+
canvas.height = h;
|
|
1149
|
+
const cx = canvas.getContext("2d");
|
|
1150
|
+
if (!cx) {
|
|
1151
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: 2D canvas context unavailable.");
|
|
1152
|
+
}
|
|
1153
|
+
cx.drawImage(img, 0, 0, w, h);
|
|
1154
|
+
let dataUrl;
|
|
1155
|
+
try {
|
|
1156
|
+
dataUrl = canvas.toDataURL(mime, quality);
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
throw new Error(
|
|
1159
|
+
`[@botim/debug-sdk] raster screenshot: canvas tainted by a cross-origin image/font without CORS \u2014 request "rrweb-snapshot" for full fidelity. (${err instanceof Error ? err.message : String(err)})`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
const comma = dataUrl.indexOf(",");
|
|
1163
|
+
const data = comma >= 0 ? dataUrl.slice(comma + 1) : "";
|
|
1164
|
+
if (!data) {
|
|
1165
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: encoder returned empty image data.");
|
|
1166
|
+
}
|
|
1167
|
+
return { data, format: mime === "image/png" ? "png-base64" : "jpeg-base64" };
|
|
1168
|
+
}
|
|
1169
|
+
function loadImage(src) {
|
|
1170
|
+
return new Promise((resolve, reject) => {
|
|
1171
|
+
const img = new Image();
|
|
1172
|
+
img.onload = () => resolve(img);
|
|
1173
|
+
img.onerror = () => reject(
|
|
1174
|
+
new Error(
|
|
1175
|
+
"[@botim/debug-sdk] raster screenshot: the offscreen SVG failed to render (often a non-XML-serializable DOM or a blocked resource)."
|
|
1176
|
+
)
|
|
1177
|
+
);
|
|
1178
|
+
img.src = src;
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
function collectDocumentCss() {
|
|
1182
|
+
const chunks = [];
|
|
1183
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
1184
|
+
try {
|
|
1185
|
+
for (const rule of Array.from(sheet.cssRules)) chunks.push(rule.cssText);
|
|
1186
|
+
} catch {
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return chunks.join("\n");
|
|
1190
|
+
}
|
|
1191
|
+
function safeBodyBackground() {
|
|
1192
|
+
try {
|
|
1193
|
+
const bg = getComputedStyle(document.body).backgroundColor;
|
|
1194
|
+
if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
|
|
1195
|
+
} catch {
|
|
1196
|
+
}
|
|
1197
|
+
return "#ffffff";
|
|
1198
|
+
}
|
|
1199
|
+
async function domHtmlSnapshot() {
|
|
1200
|
+
const { w, h } = domViewport();
|
|
1201
|
+
const injected = injectAdoptedStyleSheets();
|
|
1202
|
+
let html;
|
|
1203
|
+
try {
|
|
1204
|
+
const clone = document.documentElement.cloneNode(true);
|
|
1205
|
+
clone.querySelectorAll("script").forEach((n) => n.remove());
|
|
1206
|
+
html = `<!doctype html>
|
|
1207
|
+
${clone.outerHTML}`;
|
|
1208
|
+
} finally {
|
|
1209
|
+
for (const node of injected) {
|
|
1210
|
+
try {
|
|
1211
|
+
node.remove();
|
|
1212
|
+
} catch {
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return {
|
|
1217
|
+
data: JSON.stringify({
|
|
1218
|
+
html,
|
|
1219
|
+
viewport: { w, h },
|
|
1220
|
+
url: location.href,
|
|
1221
|
+
capturedAt: Date.now()
|
|
1222
|
+
}),
|
|
1223
|
+
format: "html-snapshot"
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1086
1226
|
function injectAdoptedStyleSheets() {
|
|
1087
1227
|
const injected = [];
|
|
1088
1228
|
const collect = (sheets) => {
|
|
@@ -1187,10 +1327,18 @@ function makeSetFlag(setFeatureFlag) {
|
|
|
1187
1327
|
return { applied: true, key };
|
|
1188
1328
|
};
|
|
1189
1329
|
}
|
|
1330
|
+
function normalizeFormat(v) {
|
|
1331
|
+
return typeof v === "string" && SCREENSHOT_FORMATS.includes(v) ? v : void 0;
|
|
1332
|
+
}
|
|
1333
|
+
function normalizeQuality(v) {
|
|
1334
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return void 0;
|
|
1335
|
+
return Math.min(1, Math.max(0, v));
|
|
1336
|
+
}
|
|
1190
1337
|
function makeScreenshot(screenshot) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
const
|
|
1338
|
+
return async (args) => {
|
|
1339
|
+
const requested = normalizeFormat(args.format);
|
|
1340
|
+
const quality = normalizeQuality(args.quality);
|
|
1341
|
+
const result = screenshot ? await screenshot(requested) : await defaultDomScreenshot(requested, quality);
|
|
1194
1342
|
let data;
|
|
1195
1343
|
let format;
|
|
1196
1344
|
if (typeof result === "string") {
|
|
@@ -1852,6 +2000,6 @@ async function enableRemoteDebug(options) {
|
|
|
1852
2000
|
// src/auto.ts
|
|
1853
2001
|
setDefaultBotimConfig(botimConfig);
|
|
1854
2002
|
|
|
1855
|
-
export { BotimConfigError, BotimConsentError, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, SCHEMA_VERSION, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
|
2003
|
+
export { BotimConfigError, BotimConsentError, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, SCHEMA_VERSION, SCREENSHOT_FORMATS, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
|
1856
2004
|
//# sourceMappingURL=auto.js.map
|
|
1857
2005
|
//# sourceMappingURL=auto.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -1033,7 +1033,27 @@ var MAX_EXEC_CODE_BYTES = 8 * 1024;
|
|
|
1033
1033
|
var DEFAULT_EXEC_TIMEOUT_MS = 5e3;
|
|
1034
1034
|
var MIN_EXEC_TIMEOUT_MS = 250;
|
|
1035
1035
|
var MAX_EXEC_TIMEOUT_MS = 3e4;
|
|
1036
|
-
|
|
1036
|
+
var SCREENSHOT_FORMATS = [
|
|
1037
|
+
"png-base64",
|
|
1038
|
+
"jpeg-base64",
|
|
1039
|
+
"rrweb-snapshot",
|
|
1040
|
+
"html-snapshot"
|
|
1041
|
+
];
|
|
1042
|
+
var DEFAULT_JPEG_QUALITY = 0.85;
|
|
1043
|
+
async function defaultDomScreenshot(requested, quality) {
|
|
1044
|
+
switch (requested) {
|
|
1045
|
+
case "png-base64":
|
|
1046
|
+
return rasterizeDom("image/png");
|
|
1047
|
+
case "jpeg-base64":
|
|
1048
|
+
return rasterizeDom("image/jpeg", quality ?? DEFAULT_JPEG_QUALITY);
|
|
1049
|
+
case "html-snapshot":
|
|
1050
|
+
return domHtmlSnapshot();
|
|
1051
|
+
case "rrweb-snapshot":
|
|
1052
|
+
default:
|
|
1053
|
+
return rrwebDomSnapshot();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
async function rrwebDomSnapshot() {
|
|
1037
1057
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1038
1058
|
throw new Error(
|
|
1039
1059
|
"[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
@@ -1082,6 +1102,126 @@ async function defaultDomScreenshot() {
|
|
|
1082
1102
|
format: "rrweb-snapshot"
|
|
1083
1103
|
};
|
|
1084
1104
|
}
|
|
1105
|
+
function domViewport() {
|
|
1106
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1107
|
+
throw new Error(
|
|
1108
|
+
"[@botim/debug-sdk] screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
return {
|
|
1112
|
+
w: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
1113
|
+
h: window.innerHeight || document.documentElement.clientHeight || 0
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
async function rasterizeDom(mime, quality) {
|
|
1117
|
+
const { w, h } = domViewport();
|
|
1118
|
+
if (!w || !h) {
|
|
1119
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: viewport has zero size.");
|
|
1120
|
+
}
|
|
1121
|
+
const canvas = document.createElement("canvas");
|
|
1122
|
+
if (typeof canvas.getContext !== "function") {
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
'[@botim/debug-sdk] raster screenshot: <canvas> unavailable in this runtime \u2014 request format "rrweb-snapshot" instead, or wire builtins.screenshot.'
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
const injected = injectAdoptedStyleSheets();
|
|
1128
|
+
let svgUrl;
|
|
1129
|
+
try {
|
|
1130
|
+
const css = collectDocumentCss();
|
|
1131
|
+
const bodyClone = document.body.cloneNode(true);
|
|
1132
|
+
bodyClone.querySelectorAll("script, noscript").forEach((n) => n.remove());
|
|
1133
|
+
const bodyXml = new XMLSerializer().serializeToString(bodyClone);
|
|
1134
|
+
const bg = safeBodyBackground();
|
|
1135
|
+
const markup = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"><foreignObject x="0" y="0" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="width:${w}px;height:${h}px;overflow:hidden;background:${bg}"><style>${css}</style>${bodyXml}</div></foreignObject></svg>`;
|
|
1136
|
+
svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
1137
|
+
} finally {
|
|
1138
|
+
for (const node of injected) {
|
|
1139
|
+
try {
|
|
1140
|
+
node.remove();
|
|
1141
|
+
} catch {
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
const img = await loadImage(svgUrl);
|
|
1146
|
+
canvas.width = w;
|
|
1147
|
+
canvas.height = h;
|
|
1148
|
+
const cx = canvas.getContext("2d");
|
|
1149
|
+
if (!cx) {
|
|
1150
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: 2D canvas context unavailable.");
|
|
1151
|
+
}
|
|
1152
|
+
cx.drawImage(img, 0, 0, w, h);
|
|
1153
|
+
let dataUrl;
|
|
1154
|
+
try {
|
|
1155
|
+
dataUrl = canvas.toDataURL(mime, quality);
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
throw new Error(
|
|
1158
|
+
`[@botim/debug-sdk] raster screenshot: canvas tainted by a cross-origin image/font without CORS \u2014 request "rrweb-snapshot" for full fidelity. (${err instanceof Error ? err.message : String(err)})`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
const comma = dataUrl.indexOf(",");
|
|
1162
|
+
const data = comma >= 0 ? dataUrl.slice(comma + 1) : "";
|
|
1163
|
+
if (!data) {
|
|
1164
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: encoder returned empty image data.");
|
|
1165
|
+
}
|
|
1166
|
+
return { data, format: mime === "image/png" ? "png-base64" : "jpeg-base64" };
|
|
1167
|
+
}
|
|
1168
|
+
function loadImage(src) {
|
|
1169
|
+
return new Promise((resolve, reject) => {
|
|
1170
|
+
const img = new Image();
|
|
1171
|
+
img.onload = () => resolve(img);
|
|
1172
|
+
img.onerror = () => reject(
|
|
1173
|
+
new Error(
|
|
1174
|
+
"[@botim/debug-sdk] raster screenshot: the offscreen SVG failed to render (often a non-XML-serializable DOM or a blocked resource)."
|
|
1175
|
+
)
|
|
1176
|
+
);
|
|
1177
|
+
img.src = src;
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
function collectDocumentCss() {
|
|
1181
|
+
const chunks = [];
|
|
1182
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
1183
|
+
try {
|
|
1184
|
+
for (const rule of Array.from(sheet.cssRules)) chunks.push(rule.cssText);
|
|
1185
|
+
} catch {
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return chunks.join("\n");
|
|
1189
|
+
}
|
|
1190
|
+
function safeBodyBackground() {
|
|
1191
|
+
try {
|
|
1192
|
+
const bg = getComputedStyle(document.body).backgroundColor;
|
|
1193
|
+
if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
return "#ffffff";
|
|
1197
|
+
}
|
|
1198
|
+
async function domHtmlSnapshot() {
|
|
1199
|
+
const { w, h } = domViewport();
|
|
1200
|
+
const injected = injectAdoptedStyleSheets();
|
|
1201
|
+
let html;
|
|
1202
|
+
try {
|
|
1203
|
+
const clone = document.documentElement.cloneNode(true);
|
|
1204
|
+
clone.querySelectorAll("script").forEach((n) => n.remove());
|
|
1205
|
+
html = `<!doctype html>
|
|
1206
|
+
${clone.outerHTML}`;
|
|
1207
|
+
} finally {
|
|
1208
|
+
for (const node of injected) {
|
|
1209
|
+
try {
|
|
1210
|
+
node.remove();
|
|
1211
|
+
} catch {
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return {
|
|
1216
|
+
data: JSON.stringify({
|
|
1217
|
+
html,
|
|
1218
|
+
viewport: { w, h },
|
|
1219
|
+
url: location.href,
|
|
1220
|
+
capturedAt: Date.now()
|
|
1221
|
+
}),
|
|
1222
|
+
format: "html-snapshot"
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1085
1225
|
function injectAdoptedStyleSheets() {
|
|
1086
1226
|
const injected = [];
|
|
1087
1227
|
const collect = (sheets) => {
|
|
@@ -1186,10 +1326,18 @@ function makeSetFlag(setFeatureFlag) {
|
|
|
1186
1326
|
return { applied: true, key };
|
|
1187
1327
|
};
|
|
1188
1328
|
}
|
|
1329
|
+
function normalizeFormat(v) {
|
|
1330
|
+
return typeof v === "string" && SCREENSHOT_FORMATS.includes(v) ? v : void 0;
|
|
1331
|
+
}
|
|
1332
|
+
function normalizeQuality(v) {
|
|
1333
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return void 0;
|
|
1334
|
+
return Math.min(1, Math.max(0, v));
|
|
1335
|
+
}
|
|
1189
1336
|
function makeScreenshot(screenshot) {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
const
|
|
1337
|
+
return async (args) => {
|
|
1338
|
+
const requested = normalizeFormat(args.format);
|
|
1339
|
+
const quality = normalizeQuality(args.quality);
|
|
1340
|
+
const result = screenshot ? await screenshot(requested) : await defaultDomScreenshot(requested, quality);
|
|
1193
1341
|
let data;
|
|
1194
1342
|
let format;
|
|
1195
1343
|
if (typeof result === "string") {
|
|
@@ -1857,6 +2005,7 @@ exports.DEFAULT_MAX_BATCH_SIZE = DEFAULT_MAX_BATCH_SIZE;
|
|
|
1857
2005
|
exports.DEFAULT_MAX_BODY_BYTES = DEFAULT_MAX_BODY_BYTES;
|
|
1858
2006
|
exports.DEFAULT_REDACT_HEADERS = DEFAULT_REDACT_HEADERS;
|
|
1859
2007
|
exports.SCHEMA_VERSION = SCHEMA_VERSION;
|
|
2008
|
+
exports.SCREENSHOT_FORMATS = SCREENSHOT_FORMATS;
|
|
1860
2009
|
exports.clearConsentDecision = clearConsentDecision;
|
|
1861
2010
|
exports.enableRemoteDebug = enableRemoteDebug;
|
|
1862
2011
|
exports.getBOT = getBOT;
|
package/dist/index.d.cts
CHANGED
|
@@ -99,15 +99,18 @@ interface BuiltinHostHooks {
|
|
|
99
99
|
/** Apply a feature flag override. */
|
|
100
100
|
setFeatureFlag?: (key: string, value: unknown) => void | Promise<void>;
|
|
101
101
|
/**
|
|
102
|
-
* Capture a screenshot.
|
|
102
|
+
* Capture a screenshot. Receives the format the caller requested (the
|
|
103
|
+
* command's `args.format`, validated to a known value or `undefined`) so
|
|
104
|
+
* the host can honor it — though the host MAY ignore it and return whatever
|
|
105
|
+
* it can produce. Two return shapes are accepted:
|
|
103
106
|
* - a raw base64 string → assumed PNG (legacy form)
|
|
104
107
|
* - `{ data, format }` → format is one of:
|
|
105
108
|
* 'png-base64' | 'jpeg-base64' — raw bitmap, viewer renders as <img>
|
|
106
109
|
* 'rrweb-snapshot' — `data` is JSON the viewer rebuilds
|
|
107
|
-
* 'html-snapshot' —
|
|
110
|
+
* 'html-snapshot' — `data` JSON: { html, … }
|
|
108
111
|
* The SDK enforces a 1 MB base64/JSON cap regardless.
|
|
109
112
|
*/
|
|
110
|
-
screenshot?: () => ScreenshotResult | Promise<ScreenshotResult>;
|
|
113
|
+
screenshot?: (format?: ScreenshotFormat) => ScreenshotResult | Promise<ScreenshotResult>;
|
|
111
114
|
/**
|
|
112
115
|
* Default-enabled remote-eval command (`exec`). Pass `false` to opt out
|
|
113
116
|
* of registration entirely; pass an object to inject extra locals into
|
|
@@ -139,23 +142,22 @@ interface ExecOptions {
|
|
|
139
142
|
*/
|
|
140
143
|
globals?: Record<string, unknown>;
|
|
141
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Wire-level screenshot formats. The caller asks for one via the command's
|
|
147
|
+
* `args.format`; the capture path produces the matching `data`:
|
|
148
|
+
* - 'png-base64' | 'jpeg-base64' — base64 raster bitmap; viewer renders <img>.
|
|
149
|
+
* - 'rrweb-snapshot' — JSON `{ snapshot, viewport, url, capturedAt }`. The
|
|
150
|
+
* admin viewer calls `rrwebSnapshot.rebuild(snapshot, …)` into an iframe
|
|
151
|
+
* to re-materialize the page with full shadow DOM / adoptedStyleSheets /
|
|
152
|
+
* cross-origin CSS fidelity. This is the default (no format requested).
|
|
153
|
+
* - 'html-snapshot' — JSON `{ html, viewport, url, capturedAt }`; viewer
|
|
154
|
+
* renders it via `iframe.srcdoc`.
|
|
155
|
+
*/
|
|
156
|
+
declare const SCREENSHOT_FORMATS: readonly ["png-base64", "jpeg-base64", "rrweb-snapshot", "html-snapshot"];
|
|
157
|
+
type ScreenshotFormat = (typeof SCREENSHOT_FORMATS)[number];
|
|
142
158
|
type ScreenshotResult = string | {
|
|
143
159
|
data: string;
|
|
144
|
-
format?:
|
|
145
|
-
/**
|
|
146
|
-
* rrweb-snapshot tree, JSON-stringified. `data` parses to:
|
|
147
|
-
* { snapshot, viewport: { w, h }, url, capturedAt }
|
|
148
|
-
* The admin viewer calls `rrwebSnapshot.rebuild(snapshot, …)` into
|
|
149
|
-
* an iframe to re-materialize the page with full shadow DOM /
|
|
150
|
-
* adoptedStyleSheets / cross-origin CSS fidelity.
|
|
151
|
-
*/
|
|
152
|
-
| 'rrweb-snapshot'
|
|
153
|
-
/**
|
|
154
|
-
* Legacy self-contained HTML snapshot (pre-0.4). `data` JSON shape
|
|
155
|
-
* is `{ html, viewport, url, capturedAt }`. Kept on the wire for
|
|
156
|
-
* back-compat; admin viewers still know how to render it.
|
|
157
|
-
*/
|
|
158
|
-
| 'html-snapshot';
|
|
160
|
+
format?: ScreenshotFormat;
|
|
159
161
|
};
|
|
160
162
|
declare function getBOT(): unknown;
|
|
161
163
|
|
|
@@ -302,4 +304,4 @@ declare const DEFAULT_MAX_BATCH_SIZE = 50;
|
|
|
302
304
|
declare function setDefaultBotimConfig(config: BotimConfig | null): void;
|
|
303
305
|
declare function enableRemoteDebug(options: RemoteDebugOptions): Promise<RemoteDebugHandle>;
|
|
304
306
|
|
|
305
|
-
export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, type ConsentDecision, ConsentInput, ConsentPromptCopy, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, type ExecOptions, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, type SamplingConfig, type SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
|
307
|
+
export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, type ConsentDecision, ConsentInput, ConsentPromptCopy, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, type ExecOptions, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, SCREENSHOT_FORMATS, type SamplingConfig, type ScreenshotFormat, type ScreenshotResult, type SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
package/dist/index.d.ts
CHANGED
|
@@ -99,15 +99,18 @@ interface BuiltinHostHooks {
|
|
|
99
99
|
/** Apply a feature flag override. */
|
|
100
100
|
setFeatureFlag?: (key: string, value: unknown) => void | Promise<void>;
|
|
101
101
|
/**
|
|
102
|
-
* Capture a screenshot.
|
|
102
|
+
* Capture a screenshot. Receives the format the caller requested (the
|
|
103
|
+
* command's `args.format`, validated to a known value or `undefined`) so
|
|
104
|
+
* the host can honor it — though the host MAY ignore it and return whatever
|
|
105
|
+
* it can produce. Two return shapes are accepted:
|
|
103
106
|
* - a raw base64 string → assumed PNG (legacy form)
|
|
104
107
|
* - `{ data, format }` → format is one of:
|
|
105
108
|
* 'png-base64' | 'jpeg-base64' — raw bitmap, viewer renders as <img>
|
|
106
109
|
* 'rrweb-snapshot' — `data` is JSON the viewer rebuilds
|
|
107
|
-
* 'html-snapshot' —
|
|
110
|
+
* 'html-snapshot' — `data` JSON: { html, … }
|
|
108
111
|
* The SDK enforces a 1 MB base64/JSON cap regardless.
|
|
109
112
|
*/
|
|
110
|
-
screenshot?: () => ScreenshotResult | Promise<ScreenshotResult>;
|
|
113
|
+
screenshot?: (format?: ScreenshotFormat) => ScreenshotResult | Promise<ScreenshotResult>;
|
|
111
114
|
/**
|
|
112
115
|
* Default-enabled remote-eval command (`exec`). Pass `false` to opt out
|
|
113
116
|
* of registration entirely; pass an object to inject extra locals into
|
|
@@ -139,23 +142,22 @@ interface ExecOptions {
|
|
|
139
142
|
*/
|
|
140
143
|
globals?: Record<string, unknown>;
|
|
141
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Wire-level screenshot formats. The caller asks for one via the command's
|
|
147
|
+
* `args.format`; the capture path produces the matching `data`:
|
|
148
|
+
* - 'png-base64' | 'jpeg-base64' — base64 raster bitmap; viewer renders <img>.
|
|
149
|
+
* - 'rrweb-snapshot' — JSON `{ snapshot, viewport, url, capturedAt }`. The
|
|
150
|
+
* admin viewer calls `rrwebSnapshot.rebuild(snapshot, …)` into an iframe
|
|
151
|
+
* to re-materialize the page with full shadow DOM / adoptedStyleSheets /
|
|
152
|
+
* cross-origin CSS fidelity. This is the default (no format requested).
|
|
153
|
+
* - 'html-snapshot' — JSON `{ html, viewport, url, capturedAt }`; viewer
|
|
154
|
+
* renders it via `iframe.srcdoc`.
|
|
155
|
+
*/
|
|
156
|
+
declare const SCREENSHOT_FORMATS: readonly ["png-base64", "jpeg-base64", "rrweb-snapshot", "html-snapshot"];
|
|
157
|
+
type ScreenshotFormat = (typeof SCREENSHOT_FORMATS)[number];
|
|
142
158
|
type ScreenshotResult = string | {
|
|
143
159
|
data: string;
|
|
144
|
-
format?:
|
|
145
|
-
/**
|
|
146
|
-
* rrweb-snapshot tree, JSON-stringified. `data` parses to:
|
|
147
|
-
* { snapshot, viewport: { w, h }, url, capturedAt }
|
|
148
|
-
* The admin viewer calls `rrwebSnapshot.rebuild(snapshot, …)` into
|
|
149
|
-
* an iframe to re-materialize the page with full shadow DOM /
|
|
150
|
-
* adoptedStyleSheets / cross-origin CSS fidelity.
|
|
151
|
-
*/
|
|
152
|
-
| 'rrweb-snapshot'
|
|
153
|
-
/**
|
|
154
|
-
* Legacy self-contained HTML snapshot (pre-0.4). `data` JSON shape
|
|
155
|
-
* is `{ html, viewport, url, capturedAt }`. Kept on the wire for
|
|
156
|
-
* back-compat; admin viewers still know how to render it.
|
|
157
|
-
*/
|
|
158
|
-
| 'html-snapshot';
|
|
160
|
+
format?: ScreenshotFormat;
|
|
159
161
|
};
|
|
160
162
|
declare function getBOT(): unknown;
|
|
161
163
|
|
|
@@ -302,4 +304,4 @@ declare const DEFAULT_MAX_BATCH_SIZE = 50;
|
|
|
302
304
|
declare function setDefaultBotimConfig(config: BotimConfig | null): void;
|
|
303
305
|
declare function enableRemoteDebug(options: RemoteDebugOptions): Promise<RemoteDebugHandle>;
|
|
304
306
|
|
|
305
|
-
export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, type ConsentDecision, ConsentInput, ConsentPromptCopy, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, type ExecOptions, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, type SamplingConfig, type SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
|
307
|
+
export { type AppInfo, BotimConfig, BotimConfigError, BotimConsentError, type BuiltinHostHooks, CommandHandler, type ConsentDecision, ConsentInput, ConsentPromptCopy, ConsolePayload, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, type DedupOptions, DeviceInfo, ErrorPayload, EventLevel, EventType, type ExecOptions, NetworkPayload, type RedactionConfig, type RemoteDebugHandle, type RemoteDebugOptions, SCREENSHOT_FORMATS, type SamplingConfig, type ScreenshotFormat, type ScreenshotResult, type SuppressionSummary, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
package/dist/index.js
CHANGED
|
@@ -1031,7 +1031,27 @@ var MAX_EXEC_CODE_BYTES = 8 * 1024;
|
|
|
1031
1031
|
var DEFAULT_EXEC_TIMEOUT_MS = 5e3;
|
|
1032
1032
|
var MIN_EXEC_TIMEOUT_MS = 250;
|
|
1033
1033
|
var MAX_EXEC_TIMEOUT_MS = 3e4;
|
|
1034
|
-
|
|
1034
|
+
var SCREENSHOT_FORMATS = [
|
|
1035
|
+
"png-base64",
|
|
1036
|
+
"jpeg-base64",
|
|
1037
|
+
"rrweb-snapshot",
|
|
1038
|
+
"html-snapshot"
|
|
1039
|
+
];
|
|
1040
|
+
var DEFAULT_JPEG_QUALITY = 0.85;
|
|
1041
|
+
async function defaultDomScreenshot(requested, quality) {
|
|
1042
|
+
switch (requested) {
|
|
1043
|
+
case "png-base64":
|
|
1044
|
+
return rasterizeDom("image/png");
|
|
1045
|
+
case "jpeg-base64":
|
|
1046
|
+
return rasterizeDom("image/jpeg", quality ?? DEFAULT_JPEG_QUALITY);
|
|
1047
|
+
case "html-snapshot":
|
|
1048
|
+
return domHtmlSnapshot();
|
|
1049
|
+
case "rrweb-snapshot":
|
|
1050
|
+
default:
|
|
1051
|
+
return rrwebDomSnapshot();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async function rrwebDomSnapshot() {
|
|
1035
1055
|
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1036
1056
|
throw new Error(
|
|
1037
1057
|
"[@botim/debug-sdk] default screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
@@ -1080,6 +1100,126 @@ async function defaultDomScreenshot() {
|
|
|
1080
1100
|
format: "rrweb-snapshot"
|
|
1081
1101
|
};
|
|
1082
1102
|
}
|
|
1103
|
+
function domViewport() {
|
|
1104
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
1105
|
+
throw new Error(
|
|
1106
|
+
"[@botim/debug-sdk] screenshot requires a DOM. Provide builtins.screenshot for non-browser runtimes (e.g. native bridge)."
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
w: window.innerWidth || document.documentElement.clientWidth || 0,
|
|
1111
|
+
h: window.innerHeight || document.documentElement.clientHeight || 0
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
async function rasterizeDom(mime, quality) {
|
|
1115
|
+
const { w, h } = domViewport();
|
|
1116
|
+
if (!w || !h) {
|
|
1117
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: viewport has zero size.");
|
|
1118
|
+
}
|
|
1119
|
+
const canvas = document.createElement("canvas");
|
|
1120
|
+
if (typeof canvas.getContext !== "function") {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
'[@botim/debug-sdk] raster screenshot: <canvas> unavailable in this runtime \u2014 request format "rrweb-snapshot" instead, or wire builtins.screenshot.'
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
const injected = injectAdoptedStyleSheets();
|
|
1126
|
+
let svgUrl;
|
|
1127
|
+
try {
|
|
1128
|
+
const css = collectDocumentCss();
|
|
1129
|
+
const bodyClone = document.body.cloneNode(true);
|
|
1130
|
+
bodyClone.querySelectorAll("script, noscript").forEach((n) => n.remove());
|
|
1131
|
+
const bodyXml = new XMLSerializer().serializeToString(bodyClone);
|
|
1132
|
+
const bg = safeBodyBackground();
|
|
1133
|
+
const markup = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}"><foreignObject x="0" y="0" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="width:${w}px;height:${h}px;overflow:hidden;background:${bg}"><style>${css}</style>${bodyXml}</div></foreignObject></svg>`;
|
|
1134
|
+
svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
1135
|
+
} finally {
|
|
1136
|
+
for (const node of injected) {
|
|
1137
|
+
try {
|
|
1138
|
+
node.remove();
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const img = await loadImage(svgUrl);
|
|
1144
|
+
canvas.width = w;
|
|
1145
|
+
canvas.height = h;
|
|
1146
|
+
const cx = canvas.getContext("2d");
|
|
1147
|
+
if (!cx) {
|
|
1148
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: 2D canvas context unavailable.");
|
|
1149
|
+
}
|
|
1150
|
+
cx.drawImage(img, 0, 0, w, h);
|
|
1151
|
+
let dataUrl;
|
|
1152
|
+
try {
|
|
1153
|
+
dataUrl = canvas.toDataURL(mime, quality);
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
throw new Error(
|
|
1156
|
+
`[@botim/debug-sdk] raster screenshot: canvas tainted by a cross-origin image/font without CORS \u2014 request "rrweb-snapshot" for full fidelity. (${err instanceof Error ? err.message : String(err)})`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
const comma = dataUrl.indexOf(",");
|
|
1160
|
+
const data = comma >= 0 ? dataUrl.slice(comma + 1) : "";
|
|
1161
|
+
if (!data) {
|
|
1162
|
+
throw new Error("[@botim/debug-sdk] raster screenshot: encoder returned empty image data.");
|
|
1163
|
+
}
|
|
1164
|
+
return { data, format: mime === "image/png" ? "png-base64" : "jpeg-base64" };
|
|
1165
|
+
}
|
|
1166
|
+
function loadImage(src) {
|
|
1167
|
+
return new Promise((resolve, reject) => {
|
|
1168
|
+
const img = new Image();
|
|
1169
|
+
img.onload = () => resolve(img);
|
|
1170
|
+
img.onerror = () => reject(
|
|
1171
|
+
new Error(
|
|
1172
|
+
"[@botim/debug-sdk] raster screenshot: the offscreen SVG failed to render (often a non-XML-serializable DOM or a blocked resource)."
|
|
1173
|
+
)
|
|
1174
|
+
);
|
|
1175
|
+
img.src = src;
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
function collectDocumentCss() {
|
|
1179
|
+
const chunks = [];
|
|
1180
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
1181
|
+
try {
|
|
1182
|
+
for (const rule of Array.from(sheet.cssRules)) chunks.push(rule.cssText);
|
|
1183
|
+
} catch {
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return chunks.join("\n");
|
|
1187
|
+
}
|
|
1188
|
+
function safeBodyBackground() {
|
|
1189
|
+
try {
|
|
1190
|
+
const bg = getComputedStyle(document.body).backgroundColor;
|
|
1191
|
+
if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
|
|
1192
|
+
} catch {
|
|
1193
|
+
}
|
|
1194
|
+
return "#ffffff";
|
|
1195
|
+
}
|
|
1196
|
+
async function domHtmlSnapshot() {
|
|
1197
|
+
const { w, h } = domViewport();
|
|
1198
|
+
const injected = injectAdoptedStyleSheets();
|
|
1199
|
+
let html;
|
|
1200
|
+
try {
|
|
1201
|
+
const clone = document.documentElement.cloneNode(true);
|
|
1202
|
+
clone.querySelectorAll("script").forEach((n) => n.remove());
|
|
1203
|
+
html = `<!doctype html>
|
|
1204
|
+
${clone.outerHTML}`;
|
|
1205
|
+
} finally {
|
|
1206
|
+
for (const node of injected) {
|
|
1207
|
+
try {
|
|
1208
|
+
node.remove();
|
|
1209
|
+
} catch {
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return {
|
|
1214
|
+
data: JSON.stringify({
|
|
1215
|
+
html,
|
|
1216
|
+
viewport: { w, h },
|
|
1217
|
+
url: location.href,
|
|
1218
|
+
capturedAt: Date.now()
|
|
1219
|
+
}),
|
|
1220
|
+
format: "html-snapshot"
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1083
1223
|
function injectAdoptedStyleSheets() {
|
|
1084
1224
|
const injected = [];
|
|
1085
1225
|
const collect = (sheets) => {
|
|
@@ -1184,10 +1324,18 @@ function makeSetFlag(setFeatureFlag) {
|
|
|
1184
1324
|
return { applied: true, key };
|
|
1185
1325
|
};
|
|
1186
1326
|
}
|
|
1327
|
+
function normalizeFormat(v) {
|
|
1328
|
+
return typeof v === "string" && SCREENSHOT_FORMATS.includes(v) ? v : void 0;
|
|
1329
|
+
}
|
|
1330
|
+
function normalizeQuality(v) {
|
|
1331
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return void 0;
|
|
1332
|
+
return Math.min(1, Math.max(0, v));
|
|
1333
|
+
}
|
|
1187
1334
|
function makeScreenshot(screenshot) {
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
const
|
|
1335
|
+
return async (args) => {
|
|
1336
|
+
const requested = normalizeFormat(args.format);
|
|
1337
|
+
const quality = normalizeQuality(args.quality);
|
|
1338
|
+
const result = screenshot ? await screenshot(requested) : await defaultDomScreenshot(requested, quality);
|
|
1191
1339
|
let data;
|
|
1192
1340
|
let format;
|
|
1193
1341
|
if (typeof result === "string") {
|
|
@@ -1846,6 +1994,6 @@ async function enableRemoteDebug(options) {
|
|
|
1846
1994
|
};
|
|
1847
1995
|
}
|
|
1848
1996
|
|
|
1849
|
-
export { BotimConfigError, BotimConsentError, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, SCHEMA_VERSION, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
|
1997
|
+
export { BotimConfigError, BotimConsentError, DEFAULT_BODY_PATTERNS, DEFAULT_BUFFER_SIZE, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_BATCH_SIZE, DEFAULT_MAX_BODY_BYTES, DEFAULT_REDACT_HEADERS, SCHEMA_VERSION, SCREENSHOT_FORMATS, clearConsentDecision, enableRemoteDebug, getBOT, promptForConsent, readConsentDecision, setDefaultBotimConfig, writeConsentDecision };
|
|
1850
1998
|
//# sourceMappingURL=index.js.map
|
|
1851
1999
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botim/mp-debug-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Remote-debug SDK for BOTIM mini-programs — streams console, network, and error events to a BOTIM debug-relay for live inspection, with an AI-observable command channel.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|