@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 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
- async function defaultDomScreenshot() {
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
- const capture = screenshot ?? defaultDomScreenshot;
1194
- return async () => {
1195
- const result = await capture();
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
- async function defaultDomScreenshot() {
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
- const capture = screenshot ?? defaultDomScreenshot;
1192
- return async () => {
1193
- const result = await capture();
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
- async function defaultDomScreenshot() {
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
- const capture = screenshot ?? defaultDomScreenshot;
1191
- return async () => {
1192
- const result = await capture();
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. Two return shapes are accepted:
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' — legacy `data` JSON: { html, … }
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?: 'png-base64' | 'jpeg-base64'
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. Two return shapes are accepted:
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' — legacy `data` JSON: { html, … }
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?: 'png-base64' | 'jpeg-base64'
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
- async function defaultDomScreenshot() {
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
- const capture = screenshot ?? defaultDomScreenshot;
1189
- return async () => {
1190
- const result = await capture();
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.2.0",
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",