hypha-debugger 0.2.6 → 0.2.7
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.js +76 -18
- package/dist/hypha-debugger.min.js +2 -2
- package/dist/hypha-debugger.mjs +76 -18
- package/dist/hypha-debugger.mjs.map +1 -1
- package/package.json +1 -1
package/dist/hypha-debugger.js
CHANGED
|
@@ -10405,12 +10405,16 @@
|
|
|
10405
10405
|
// to the viewport dimensions.
|
|
10406
10406
|
const viewportW = window.innerWidth;
|
|
10407
10407
|
const viewportH = window.innerHeight;
|
|
10408
|
+
// 1x1 transparent PNG — used as placeholder for images that fail
|
|
10409
|
+
// to load (CORS-blocked, 404, etc.) so html-to-image doesn't reject.
|
|
10410
|
+
const TRANSPARENT_PIXEL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
|
|
10408
10411
|
const captureOptions = {
|
|
10409
10412
|
quality: qual,
|
|
10410
10413
|
pixelRatio: 1, // always capture at 1x — we'll resize after
|
|
10411
10414
|
cacheBust: true,
|
|
10412
10415
|
skipAutoScale: true,
|
|
10413
10416
|
skipFonts: true, // CORS-blocked stylesheets can hang font inlining
|
|
10417
|
+
imagePlaceholder: TRANSPARENT_PIXEL, // fallback for broken images
|
|
10414
10418
|
filter: (el) => {
|
|
10415
10419
|
// Exclude the debugger overlay and cursor from screenshots
|
|
10416
10420
|
return (el.id !== "hypha-debugger-host" &&
|
|
@@ -10423,21 +10427,37 @@
|
|
|
10423
10427
|
captureOptions.width = viewportW;
|
|
10424
10428
|
captureOptions.height = viewportH;
|
|
10425
10429
|
}
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
// Hard timeout: pages with cross-origin resources can make
|
|
10430
|
-
// html-to-image wait indefinitely on blocked fetches.
|
|
10431
|
-
const timeoutMs = 15000;
|
|
10432
|
-
dataUrl = await Promise.race([
|
|
10430
|
+
const runCapture = async (opts, timeoutMs = 15000) => {
|
|
10431
|
+
const capturePromise = fmt === "jpeg" ? toJpeg(node, opts) : toPng(node, opts);
|
|
10432
|
+
return Promise.race([
|
|
10433
10433
|
capturePromise,
|
|
10434
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`Screenshot capture timed out after ${timeoutMs}ms
|
|
10434
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Screenshot capture timed out after ${timeoutMs}ms`)), timeoutMs)),
|
|
10435
10435
|
]);
|
|
10436
|
+
};
|
|
10437
|
+
let dataUrl;
|
|
10438
|
+
try {
|
|
10439
|
+
dataUrl = await runCapture(captureOptions);
|
|
10436
10440
|
}
|
|
10437
10441
|
catch (captureErr) {
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
|
|
10442
|
+
// Fallback: retry without images (filter them out). Some pages have
|
|
10443
|
+
// images that html-to-image can't inline even with imagePlaceholder.
|
|
10444
|
+
try {
|
|
10445
|
+
const noImagesOpts = {
|
|
10446
|
+
...captureOptions,
|
|
10447
|
+
filter: (el) => {
|
|
10448
|
+
if (!captureOptions.filter(el))
|
|
10449
|
+
return false;
|
|
10450
|
+
const tag = el.tagName?.toLowerCase();
|
|
10451
|
+
return tag !== "img" && tag !== "picture" && tag !== "video";
|
|
10452
|
+
},
|
|
10453
|
+
};
|
|
10454
|
+
dataUrl = await runCapture(noImagesOpts, 10000);
|
|
10455
|
+
}
|
|
10456
|
+
catch (retryErr) {
|
|
10457
|
+
return {
|
|
10458
|
+
error: `Capture failed: ${errorMessage(captureErr)} (retry without images also failed: ${errorMessage(retryErr)})`,
|
|
10459
|
+
};
|
|
10460
|
+
}
|
|
10441
10461
|
}
|
|
10442
10462
|
// Resize down to fit within (maxW × maxH) and re-encode. If resize
|
|
10443
10463
|
// fails (e.g. data URL too large to load back into an Image), fall
|
|
@@ -10962,6 +10982,29 @@
|
|
|
10962
10982
|
k.startsWith("__reactInternalInstance$"));
|
|
10963
10983
|
return fiberKey ? node[fiberKey] : null;
|
|
10964
10984
|
}
|
|
10985
|
+
/**
|
|
10986
|
+
* Find the topmost DOM element that has a React fiber attached.
|
|
10987
|
+
* React mounts on various nodes — #root is common but not universal
|
|
10988
|
+
* (#app, #__next, body, etc.). We scan until we find one.
|
|
10989
|
+
*/
|
|
10990
|
+
function findReactRoot() {
|
|
10991
|
+
// Try common selectors first (fast path)
|
|
10992
|
+
const common = ["#root", "#app", "#__next", "[data-reactroot]", "main", "body"];
|
|
10993
|
+
for (const sel of common) {
|
|
10994
|
+
const el = document.querySelector(sel);
|
|
10995
|
+
if (el && getFiberFromDOM(el))
|
|
10996
|
+
return el;
|
|
10997
|
+
}
|
|
10998
|
+
// Slow path: walk the tree, find first element with a fiber
|
|
10999
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null);
|
|
11000
|
+
let node = walker.currentNode;
|
|
11001
|
+
while (node) {
|
|
11002
|
+
if (node instanceof Element && getFiberFromDOM(node))
|
|
11003
|
+
return node;
|
|
11004
|
+
node = walker.nextNode();
|
|
11005
|
+
}
|
|
11006
|
+
return null;
|
|
11007
|
+
}
|
|
10965
11008
|
function getComponentName(fiber) {
|
|
10966
11009
|
const { type } = fiber;
|
|
10967
11010
|
if (!type)
|
|
@@ -11072,16 +11115,30 @@
|
|
|
11072
11115
|
return info;
|
|
11073
11116
|
}
|
|
11074
11117
|
function getReactTree(selector, max_depth) {
|
|
11075
|
-
selector = selector ?? "#root";
|
|
11076
11118
|
const maxDepth = max_depth ?? 5;
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11119
|
+
let rootEl;
|
|
11120
|
+
let usedSelector;
|
|
11121
|
+
if (selector) {
|
|
11122
|
+
rootEl = document.querySelector(selector);
|
|
11123
|
+
usedSelector = selector;
|
|
11124
|
+
if (!rootEl) {
|
|
11125
|
+
return { error: `No element found for selector: ${selector}` };
|
|
11126
|
+
}
|
|
11127
|
+
}
|
|
11128
|
+
else {
|
|
11129
|
+
// Auto-detect: scan for any element with a React fiber attached
|
|
11130
|
+
rootEl = findReactRoot();
|
|
11131
|
+
usedSelector = rootEl?.tagName.toLowerCase() + (rootEl?.id ? "#" + rootEl.id : "") || "(auto)";
|
|
11132
|
+
if (!rootEl) {
|
|
11133
|
+
return {
|
|
11134
|
+
error: "No React root found on this page. Tried #root, #app, #__next, [data-reactroot], main, body — none had React fibers. This page may not be a React app, or React may not have rendered yet. Pass an explicit `selector` if you know the mount point.",
|
|
11135
|
+
};
|
|
11136
|
+
}
|
|
11080
11137
|
}
|
|
11081
11138
|
const fiber = getFiberFromDOM(rootEl);
|
|
11082
11139
|
if (!fiber) {
|
|
11083
11140
|
return {
|
|
11084
|
-
error: `No React fiber found on element "${
|
|
11141
|
+
error: `No React fiber found on element "${usedSelector}". Is this a React app?`,
|
|
11085
11142
|
};
|
|
11086
11143
|
}
|
|
11087
11144
|
// Walk up to find the root component fiber (skip HostRoot)
|
|
@@ -11101,13 +11158,14 @@
|
|
|
11101
11158
|
}
|
|
11102
11159
|
getReactTree.__schema__ = {
|
|
11103
11160
|
name: "getReactTree",
|
|
11104
|
-
description: "Inspect the React component tree
|
|
11161
|
+
description: "Inspect the React component tree. Returns component names, props, state (including hooks), and children hierarchy. " +
|
|
11162
|
+
"When no selector is provided, auto-detects the React root by scanning common mount points (#root, #app, #__next, [data-reactroot], main, body) and then walking the DOM for any element with a React fiber attached.",
|
|
11105
11163
|
parameters: {
|
|
11106
11164
|
type: "object",
|
|
11107
11165
|
properties: {
|
|
11108
11166
|
selector: {
|
|
11109
11167
|
type: "string",
|
|
11110
|
-
description:
|
|
11168
|
+
description: "CSS selector of the React root element. Omit for auto-detection.",
|
|
11111
11169
|
},
|
|
11112
11170
|
max_depth: {
|
|
11113
11171
|
type: "number",
|