hypha-debugger 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hypha-debugger.mjs
CHANGED
|
@@ -1972,6 +1972,23 @@ async function toJpeg(node, options = {}) {
|
|
|
1972
1972
|
* Images are downscaled before being returned so agents don't receive
|
|
1973
1973
|
* multi-megabyte base64 payloads that can crash their context window.
|
|
1974
1974
|
*/
|
|
1975
|
+
/** Extract a useful string from an unknown error value. */
|
|
1976
|
+
function errorMessage(err) {
|
|
1977
|
+
if (!err)
|
|
1978
|
+
return "Unknown error";
|
|
1979
|
+
if (typeof err === "string")
|
|
1980
|
+
return err;
|
|
1981
|
+
if (err.message)
|
|
1982
|
+
return err.message;
|
|
1983
|
+
if (err instanceof Event)
|
|
1984
|
+
return `Event: ${err.type}`;
|
|
1985
|
+
try {
|
|
1986
|
+
return JSON.stringify(err);
|
|
1987
|
+
}
|
|
1988
|
+
catch {
|
|
1989
|
+
return String(err);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1975
1992
|
/**
|
|
1976
1993
|
* Resize an image data URL via a canvas. Returns a new data URL at the
|
|
1977
1994
|
* requested format/quality. Maintains aspect ratio: fits within
|
|
@@ -1981,31 +1998,34 @@ async function resizeDataUrl(dataUrl, maxWidth, maxHeight, format, quality) {
|
|
|
1981
1998
|
return new Promise((resolve, reject) => {
|
|
1982
1999
|
const img = new Image();
|
|
1983
2000
|
img.onload = () => {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2001
|
+
try {
|
|
2002
|
+
const srcW = img.naturalWidth;
|
|
2003
|
+
const srcH = img.naturalHeight;
|
|
2004
|
+
const scale = Math.min(maxWidth / srcW, maxHeight / srcH, 1);
|
|
2005
|
+
const dstW = Math.max(1, Math.round(srcW * scale));
|
|
2006
|
+
const dstH = Math.max(1, Math.round(srcH * scale));
|
|
2007
|
+
const canvas = document.createElement("canvas");
|
|
2008
|
+
canvas.width = dstW;
|
|
2009
|
+
canvas.height = dstH;
|
|
2010
|
+
const ctx = canvas.getContext("2d");
|
|
2011
|
+
if (!ctx) {
|
|
2012
|
+
reject(new Error("Could not get 2D canvas context"));
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (format === "jpeg") {
|
|
2016
|
+
ctx.fillStyle = "#ffffff";
|
|
2017
|
+
ctx.fillRect(0, 0, dstW, dstH);
|
|
2018
|
+
}
|
|
2019
|
+
ctx.drawImage(img, 0, 0, dstW, dstH);
|
|
2020
|
+
const mime = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
2021
|
+
const out = canvas.toDataURL(mime, quality);
|
|
2022
|
+
resolve({ dataUrl: out, width: dstW, height: dstH });
|
|
1997
2023
|
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
ctx.fillStyle = "#ffffff";
|
|
2001
|
-
ctx.fillRect(0, 0, dstW, dstH);
|
|
2024
|
+
catch (drawErr) {
|
|
2025
|
+
reject(new Error(`Canvas resize failed: ${errorMessage(drawErr)}`));
|
|
2002
2026
|
}
|
|
2003
|
-
ctx.drawImage(img, 0, 0, dstW, dstH);
|
|
2004
|
-
const mime = format === "jpeg" ? "image/jpeg" : "image/png";
|
|
2005
|
-
const out = canvas.toDataURL(mime, quality);
|
|
2006
|
-
resolve({ dataUrl: out, width: dstW, height: dstH });
|
|
2007
2027
|
};
|
|
2008
|
-
img.onerror = () => reject(new Error(
|
|
2028
|
+
img.onerror = (ev) => reject(new Error(`Failed to load captured image for resizing${ev instanceof Event ? ` (${ev.type})` : ""}`));
|
|
2009
2029
|
img.src = dataUrl;
|
|
2010
2030
|
});
|
|
2011
2031
|
}
|
|
@@ -2045,6 +2065,7 @@ async function takeScreenshot(selector, format, quality, max_width, max_height,
|
|
|
2045
2065
|
pixelRatio: 1, // always capture at 1x — we'll resize after
|
|
2046
2066
|
cacheBust: true,
|
|
2047
2067
|
skipAutoScale: true,
|
|
2068
|
+
skipFonts: true, // CORS-blocked stylesheets can hang font inlining
|
|
2048
2069
|
filter: (el) => {
|
|
2049
2070
|
// Exclude the debugger overlay and cursor from screenshots
|
|
2050
2071
|
return (el.id !== "hypha-debugger-host" &&
|
|
@@ -2058,25 +2079,51 @@ async function takeScreenshot(selector, format, quality, max_width, max_height,
|
|
|
2058
2079
|
captureOptions.height = viewportH;
|
|
2059
2080
|
}
|
|
2060
2081
|
let dataUrl;
|
|
2061
|
-
|
|
2062
|
-
|
|
2082
|
+
try {
|
|
2083
|
+
const capturePromise = fmt === "jpeg" ? toJpeg(node, captureOptions) : toPng(node, captureOptions);
|
|
2084
|
+
// Hard timeout: pages with cross-origin resources can make
|
|
2085
|
+
// html-to-image wait indefinitely on blocked fetches.
|
|
2086
|
+
const timeoutMs = 15000;
|
|
2087
|
+
dataUrl = await Promise.race([
|
|
2088
|
+
capturePromise,
|
|
2089
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Screenshot capture timed out after ${timeoutMs}ms (likely CORS-blocked resources)`)), timeoutMs)),
|
|
2090
|
+
]);
|
|
2091
|
+
}
|
|
2092
|
+
catch (captureErr) {
|
|
2093
|
+
return {
|
|
2094
|
+
error: `Capture failed (html-to-image): ${errorMessage(captureErr)}`,
|
|
2095
|
+
};
|
|
2063
2096
|
}
|
|
2064
|
-
|
|
2065
|
-
|
|
2097
|
+
// Resize down to fit within (maxW × maxH) and re-encode. If resize
|
|
2098
|
+
// fails (e.g. data URL too large to load back into an Image), fall
|
|
2099
|
+
// back to returning the original capture so the caller still gets
|
|
2100
|
+
// something useful.
|
|
2101
|
+
try {
|
|
2102
|
+
const resized = await resizeDataUrl(dataUrl, maxW, maxH, fmt, qual);
|
|
2103
|
+
const sizeKb = Math.round((resized.dataUrl.length * 0.75) / 1024);
|
|
2104
|
+
return {
|
|
2105
|
+
data: resized.dataUrl,
|
|
2106
|
+
format: fmt,
|
|
2107
|
+
width: resized.width,
|
|
2108
|
+
height: resized.height,
|
|
2109
|
+
size_kb: sizeKb,
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
catch (resizeErr) {
|
|
2113
|
+
const rect = node.getBoundingClientRect();
|
|
2114
|
+
const sizeKb = Math.round((dataUrl.length * 0.75) / 1024);
|
|
2115
|
+
return {
|
|
2116
|
+
data: dataUrl,
|
|
2117
|
+
format: fmt,
|
|
2118
|
+
width: Math.round(rect.width),
|
|
2119
|
+
height: Math.round(rect.height),
|
|
2120
|
+
size_kb: sizeKb,
|
|
2121
|
+
warning: `Resize failed, returning original: ${errorMessage(resizeErr)}`,
|
|
2122
|
+
};
|
|
2066
2123
|
}
|
|
2067
|
-
// Resize down to fit within (maxW × maxH) and re-encode
|
|
2068
|
-
const resized = await resizeDataUrl(dataUrl, maxW, maxH, fmt, qual);
|
|
2069
|
-
const sizeKb = Math.round((resized.dataUrl.length * 0.75) / 1024); // rough base64 → bytes
|
|
2070
|
-
return {
|
|
2071
|
-
data: resized.dataUrl,
|
|
2072
|
-
format: fmt,
|
|
2073
|
-
width: resized.width,
|
|
2074
|
-
height: resized.height,
|
|
2075
|
-
size_kb: sizeKb,
|
|
2076
|
-
};
|
|
2077
2124
|
}
|
|
2078
2125
|
catch (err) {
|
|
2079
|
-
return { error: `Screenshot failed: ${err
|
|
2126
|
+
return { error: `Screenshot failed: ${errorMessage(err)}` };
|
|
2080
2127
|
}
|
|
2081
2128
|
}
|
|
2082
2129
|
takeScreenshot.__schema__ = {
|
|
@@ -5538,7 +5585,14 @@ async function getBrowserState(viewport_only) {
|
|
|
5538
5585
|
if (viewport_only !== undefined) {
|
|
5539
5586
|
ctrl.config.viewportExpansion = viewport_only ? 0 : -1;
|
|
5540
5587
|
}
|
|
5541
|
-
|
|
5588
|
+
// Hard timeout: pages with heavy DOMs or cross-origin iframes can
|
|
5589
|
+
// make the tree walk take much longer than expected. Don't leave
|
|
5590
|
+
// the HTTP caller waiting forever.
|
|
5591
|
+
const timeoutMs = 15000;
|
|
5592
|
+
return Promise.race([
|
|
5593
|
+
ctrl.getBrowserState(),
|
|
5594
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`get_browser_state timed out after ${timeoutMs}ms (complex DOM or cross-origin iframes)`)), timeoutMs)),
|
|
5595
|
+
]);
|
|
5542
5596
|
}
|
|
5543
5597
|
getBrowserState.__schema__ = {
|
|
5544
5598
|
name: "getBrowserState",
|