haltija 1.2.5 → 1.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/apps/desktop/package.json +1 -1
- package/apps/desktop/resources/component.js +19 -3
- package/bin/cli-subcommand.mjs +3 -3
- package/bin/format-tree.mjs +4 -4
- package/bin/hints.json +1 -1
- package/dist/component.js +19 -3
- package/dist/hj.js +9 -9
- package/dist/index.js +47 -68
- package/dist/server.js +47 -68
- package/package.json +1 -1
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.2.
|
|
49
|
+
var VERSION = "1.2.7";
|
|
50
50
|
|
|
51
51
|
// src/text-selector.ts
|
|
52
52
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -1310,9 +1310,22 @@
|
|
|
1310
1310
|
}
|
|
1311
1311
|
return summary;
|
|
1312
1312
|
}
|
|
1313
|
+
function pruneNonInteractive(node) {
|
|
1314
|
+
const isInteractive = node.flags?.interactive || node.flags?.hasEvents;
|
|
1315
|
+
const children = node.children?.map((c) => pruneNonInteractive(c)).filter((c) => c !== null);
|
|
1316
|
+
const shadowChildren = node.shadowChildren?.map((c) => pruneNonInteractive(c)).filter((c) => c !== null);
|
|
1317
|
+
const hasInteractiveDescendant = children && children.length > 0 || shadowChildren && shadowChildren.length > 0;
|
|
1318
|
+
if (!isInteractive && !hasInteractiveDescendant)
|
|
1319
|
+
return null;
|
|
1320
|
+
return {
|
|
1321
|
+
...node,
|
|
1322
|
+
children: children?.length ? children : undefined,
|
|
1323
|
+
shadowChildren: shadowChildren?.length ? shadowChildren : undefined
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1313
1326
|
function buildDomTree(el, options, currentDepth = 0) {
|
|
1314
1327
|
const {
|
|
1315
|
-
depth =
|
|
1328
|
+
depth = -1,
|
|
1316
1329
|
includeText = true,
|
|
1317
1330
|
allAttributes = false,
|
|
1318
1331
|
includeStyles = false,
|
|
@@ -5529,7 +5542,10 @@ ${elementSummary}${moreText}`;
|
|
|
5529
5542
|
const summary = buildActionableSummary(el);
|
|
5530
5543
|
this.respond(msg2.id, true, summary);
|
|
5531
5544
|
} else {
|
|
5532
|
-
|
|
5545
|
+
let tree = buildDomTree(el, request);
|
|
5546
|
+
if (request.interactiveOnly && tree) {
|
|
5547
|
+
tree = pruneNonInteractive(tree);
|
|
5548
|
+
}
|
|
5533
5549
|
if (request.ancestors && tree) {
|
|
5534
5550
|
const ancestors = [];
|
|
5535
5551
|
let parent = el.parentElement;
|
package/bin/cli-subcommand.mjs
CHANGED
|
@@ -202,10 +202,10 @@ export function parseTreeArgs(args) {
|
|
|
202
202
|
for (let i = 0; i < args.length; i++) {
|
|
203
203
|
const a = args[i]
|
|
204
204
|
if (a === '--depth' || a === '-d') { body.depth = num(args[++i]); continue }
|
|
205
|
-
if (a === '--all' || a === '-a') { body.depth = -1; continue }
|
|
206
205
|
if (a === '--selector' || a === '-s') { body.selector = args[++i]; continue }
|
|
207
206
|
if (a === '--compact' || a === '-c') { body.compact = true; continue }
|
|
208
|
-
if (a === '--
|
|
207
|
+
if (a === '--interactive' || a === '-i') { body.interactiveOnly = true; continue }
|
|
208
|
+
if (a === '--visible' || a === '-v') { body.visibleOnly = true; continue }
|
|
209
209
|
if (a === '--text') { body.includeText = true; continue }
|
|
210
210
|
if (a === '--no-text') { body.includeText = false; continue }
|
|
211
211
|
if (a === '--shadow') { body.pierceShadow = true; continue }
|
|
@@ -832,7 +832,7 @@ export function listSubcommands() {
|
|
|
832
832
|
return `
|
|
833
833
|
Subcommands (replace curl with simple commands):
|
|
834
834
|
${bold('Inspect')}
|
|
835
|
-
tree [selector] [-d N] [-
|
|
835
|
+
tree [selector] [-d N] [-i] [-v] DOM tree (full depth, -i=interactive, -v=visible)
|
|
836
836
|
query <selector> Find elements matching selector
|
|
837
837
|
inspect <@ref|selector> Detailed element info
|
|
838
838
|
inspectAll <selector> Deep inspect all matches
|
package/bin/format-tree.mjs
CHANGED
|
@@ -33,11 +33,11 @@ export function formatTree(node, indent = 0, { depth } = {}) {
|
|
|
33
33
|
const lines = []
|
|
34
34
|
formatNode(node, indent, lines)
|
|
35
35
|
|
|
36
|
-
// Footer:
|
|
37
|
-
const d = depth ??
|
|
38
|
-
const
|
|
36
|
+
// Footer: options hint
|
|
37
|
+
const d = depth ?? -1
|
|
38
|
+
const depthLabel = d === -1 ? 'unlimited' : String(d)
|
|
39
39
|
lines.push('---')
|
|
40
|
-
lines.push(`depth=${
|
|
40
|
+
lines.push(`depth=${depthLabel} | -d N | -i (interactive) | --visible | --json`)
|
|
41
41
|
|
|
42
42
|
return lines.join('\n')
|
|
43
43
|
}
|
package/bin/hints.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"tree": "-d
|
|
2
|
+
"tree": "-d 3 (shallow), -i (interactive only), --visible, --compact | see: inspect, query, click",
|
|
3
3
|
"query": "@ref or \"selector\", --all | see: tree, inspect",
|
|
4
4
|
"inspect": "@ref or \"selector\", --styles, --rules, --ancestors | see: tree, query",
|
|
5
5
|
"click": "@ref or \"selector\", :text(Button), --diff | see: tree, wait, type",
|
package/dist/component.js
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.2.
|
|
49
|
+
var VERSION = "1.2.7";
|
|
50
50
|
|
|
51
51
|
// src/text-selector.ts
|
|
52
52
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -1310,9 +1310,22 @@
|
|
|
1310
1310
|
}
|
|
1311
1311
|
return summary;
|
|
1312
1312
|
}
|
|
1313
|
+
function pruneNonInteractive(node) {
|
|
1314
|
+
const isInteractive = node.flags?.interactive || node.flags?.hasEvents;
|
|
1315
|
+
const children = node.children?.map((c) => pruneNonInteractive(c)).filter((c) => c !== null);
|
|
1316
|
+
const shadowChildren = node.shadowChildren?.map((c) => pruneNonInteractive(c)).filter((c) => c !== null);
|
|
1317
|
+
const hasInteractiveDescendant = children && children.length > 0 || shadowChildren && shadowChildren.length > 0;
|
|
1318
|
+
if (!isInteractive && !hasInteractiveDescendant)
|
|
1319
|
+
return null;
|
|
1320
|
+
return {
|
|
1321
|
+
...node,
|
|
1322
|
+
children: children?.length ? children : undefined,
|
|
1323
|
+
shadowChildren: shadowChildren?.length ? shadowChildren : undefined
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1313
1326
|
function buildDomTree(el, options, currentDepth = 0) {
|
|
1314
1327
|
const {
|
|
1315
|
-
depth =
|
|
1328
|
+
depth = -1,
|
|
1316
1329
|
includeText = true,
|
|
1317
1330
|
allAttributes = false,
|
|
1318
1331
|
includeStyles = false,
|
|
@@ -5529,7 +5542,10 @@ ${elementSummary}${moreText}`;
|
|
|
5529
5542
|
const summary = buildActionableSummary(el);
|
|
5530
5543
|
this.respond(msg2.id, true, summary);
|
|
5531
5544
|
} else {
|
|
5532
|
-
|
|
5545
|
+
let tree = buildDomTree(el, request);
|
|
5546
|
+
if (request.interactiveOnly && tree) {
|
|
5547
|
+
tree = pruneNonInteractive(tree);
|
|
5548
|
+
}
|
|
5533
5549
|
if (request.ancestors && tree) {
|
|
5534
5550
|
const ancestors = [];
|
|
5535
5551
|
let parent = el.parentElement;
|
package/dist/hj.js
CHANGED
|
@@ -15,10 +15,10 @@ function formatTree(node, indent = 0, { depth } = {}) {
|
|
|
15
15
|
return "";
|
|
16
16
|
const lines = [];
|
|
17
17
|
formatNode(node, indent, lines);
|
|
18
|
-
const d = depth ??
|
|
19
|
-
const
|
|
18
|
+
const d = depth ?? -1;
|
|
19
|
+
const depthLabel = d === -1 ? "unlimited" : String(d);
|
|
20
20
|
lines.push("---");
|
|
21
|
-
lines.push(`depth=${
|
|
21
|
+
lines.push(`depth=${depthLabel} | -d N | -i (interactive) | --visible | --json`);
|
|
22
22
|
return lines.join(`
|
|
23
23
|
`);
|
|
24
24
|
}
|
|
@@ -892,10 +892,6 @@ function parseTreeArgs(args) {
|
|
|
892
892
|
body.depth = num(args[++i]);
|
|
893
893
|
continue;
|
|
894
894
|
}
|
|
895
|
-
if (a === "--all" || a === "-a") {
|
|
896
|
-
body.depth = -1;
|
|
897
|
-
continue;
|
|
898
|
-
}
|
|
899
895
|
if (a === "--selector" || a === "-s") {
|
|
900
896
|
body.selector = args[++i];
|
|
901
897
|
continue;
|
|
@@ -904,7 +900,11 @@ function parseTreeArgs(args) {
|
|
|
904
900
|
body.compact = true;
|
|
905
901
|
continue;
|
|
906
902
|
}
|
|
907
|
-
if (a === "--
|
|
903
|
+
if (a === "--interactive" || a === "-i") {
|
|
904
|
+
body.interactiveOnly = true;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (a === "--visible" || a === "-v") {
|
|
908
908
|
body.visibleOnly = true;
|
|
909
909
|
continue;
|
|
910
910
|
}
|
|
@@ -1516,7 +1516,7 @@ function listSubcommands() {
|
|
|
1516
1516
|
return `
|
|
1517
1517
|
Subcommands (replace curl with simple commands):
|
|
1518
1518
|
${bold("Inspect")}
|
|
1519
|
-
tree [selector] [-d N] [-
|
|
1519
|
+
tree [selector] [-d N] [-i] [-v] DOM tree (full depth, -i=interactive, -v=visible)
|
|
1520
1520
|
query <selector> Find elements matching selector
|
|
1521
1521
|
inspect <@ref|selector> Detailed element info
|
|
1522
1522
|
inspectAll <selector> Deep inspect all matches
|
package/dist/index.js
CHANGED
|
@@ -674,7 +674,7 @@ var injectorCode = `
|
|
|
674
674
|
`;
|
|
675
675
|
|
|
676
676
|
// src/version.ts
|
|
677
|
-
var VERSION = "1.2.
|
|
677
|
+
var VERSION = "1.2.7";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -1078,9 +1078,10 @@ Use ancestors:true to see parent elements when inspecting deep elements.
|
|
|
1078
1078
|
| Name | Type | Description |
|
|
1079
1079
|
|------|------|-------------|
|
|
1080
1080
|
| \`selector\` | string,null | Root element selector |
|
|
1081
|
-
| \`depth\` | number,null | Max depth (-1 = unlimited
|
|
1081
|
+
| \`depth\` | number,null | Max depth (-1 = unlimited). Default: unlimited |
|
|
1082
1082
|
| \`includeText\` | boolean,null | Include text content (default true) |
|
|
1083
1083
|
| \`visibleOnly\` | boolean,null | Only visible elements (default false) |
|
|
1084
|
+
| \`interactiveOnly\` | boolean,null | Only interactive elements and their ancestors (default false) |
|
|
1084
1085
|
| \`pierceShadow\` | boolean,null | Pierce shadow DOM (default true) |
|
|
1085
1086
|
| \`pierceFrames\` | boolean,null | Pierce same-origin iframes (default true) |
|
|
1086
1087
|
| \`compact\` | boolean,null | Minimal output (default false) |
|
|
@@ -1089,17 +1090,17 @@ Use ancestors:true to see parent elements when inspecting deep elements.
|
|
|
1089
1090
|
|
|
1090
1091
|
**Examples:**
|
|
1091
1092
|
|
|
1092
|
-
- **overview**: Quick page overview
|
|
1093
|
+
- **overview**: Quick page overview (shallow)
|
|
1093
1094
|
\`\`\`json
|
|
1094
|
-
{"depth":
|
|
1095
|
+
{"depth":3}
|
|
1095
1096
|
\`\`\`
|
|
1096
1097
|
- **form-only**: Full form structure
|
|
1097
1098
|
\`\`\`json
|
|
1098
|
-
{"selector":"form"
|
|
1099
|
+
{"selector":"form"}
|
|
1099
1100
|
\`\`\`
|
|
1100
|
-
- **
|
|
1101
|
+
- **interactive**: Only buttons, inputs, links, and their containers
|
|
1101
1102
|
\`\`\`json
|
|
1102
|
-
{"
|
|
1103
|
+
{"interactiveOnly":true}
|
|
1103
1104
|
\`\`\`
|
|
1104
1105
|
- **with-context**: See element with parent context
|
|
1105
1106
|
\`\`\`json
|
|
@@ -3702,9 +3703,10 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
3702
3703
|
category: "dom",
|
|
3703
3704
|
input: L.object({
|
|
3704
3705
|
selector: L.string.describe("Root element selector").optional,
|
|
3705
|
-
depth: L.number.describe("Max depth (-1 = unlimited
|
|
3706
|
+
depth: L.number.describe("Max depth (-1 = unlimited). Default: unlimited").optional,
|
|
3706
3707
|
includeText: L.boolean.describe("Include text content (default true)").optional,
|
|
3707
3708
|
visibleOnly: L.boolean.describe("Only visible elements (default false)").optional,
|
|
3709
|
+
interactiveOnly: L.boolean.describe("Only interactive elements and their ancestors (default false)").optional,
|
|
3708
3710
|
pierceShadow: L.boolean.describe("Pierce shadow DOM (default true)").optional,
|
|
3709
3711
|
pierceFrames: L.boolean.describe("Pierce same-origin iframes (default true)").optional,
|
|
3710
3712
|
compact: L.boolean.describe("Minimal output (default false)").optional,
|
|
@@ -3714,18 +3716,18 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
3714
3716
|
examples: [
|
|
3715
3717
|
{
|
|
3716
3718
|
name: "overview",
|
|
3717
|
-
input: { depth:
|
|
3718
|
-
description: "Quick page overview"
|
|
3719
|
+
input: { depth: 3 },
|
|
3720
|
+
description: "Quick page overview (shallow)"
|
|
3719
3721
|
},
|
|
3720
3722
|
{
|
|
3721
3723
|
name: "form-only",
|
|
3722
|
-
input: { selector: "form"
|
|
3724
|
+
input: { selector: "form" },
|
|
3723
3725
|
description: "Full form structure"
|
|
3724
3726
|
},
|
|
3725
3727
|
{
|
|
3726
|
-
name: "
|
|
3727
|
-
input: {
|
|
3728
|
-
description: "
|
|
3728
|
+
name: "interactive",
|
|
3729
|
+
input: { interactiveOnly: true },
|
|
3730
|
+
description: "Only buttons, inputs, links, and their containers"
|
|
3729
3731
|
},
|
|
3730
3732
|
{
|
|
3731
3733
|
name: "with-context",
|
|
@@ -3733,7 +3735,7 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
3733
3735
|
description: "See element with parent context"
|
|
3734
3736
|
}
|
|
3735
3737
|
],
|
|
3736
|
-
hints:
|
|
3738
|
+
hints: "-d 3 (shallow), -i (interactive only), --visible, --compact | see: inspect, query, click"
|
|
3737
3739
|
});
|
|
3738
3740
|
var query = endpoint({
|
|
3739
3741
|
path: "/query",
|
|
@@ -6326,34 +6328,12 @@ registerHandler(unhighlight, async (_body, ctx) => {
|
|
|
6326
6328
|
registerHandler(navigate, async (body, ctx) => {
|
|
6327
6329
|
const windowId = body.window || ctx.targetWindowId;
|
|
6328
6330
|
const response = await ctx.requestFromBrowser("navigation", "goto", { url: body.url }, 5000, windowId);
|
|
6329
|
-
if (!response.success) {
|
|
6330
|
-
return Response.json(response, { headers: ctx.headers });
|
|
6331
|
-
}
|
|
6332
|
-
const reconnected = await ctx.waitForReconnect(windowId, 1e4);
|
|
6333
|
-
if (!reconnected) {
|
|
6334
|
-
return Response.json({
|
|
6335
|
-
...response,
|
|
6336
|
-
success: false,
|
|
6337
|
-
error: `Navigation sent but page did not reconnect within 10s. The page may have loaded without the Haltija widget.`
|
|
6338
|
-
}, { headers: ctx.headers });
|
|
6339
|
-
}
|
|
6340
6331
|
return Response.json(response, { headers: ctx.headers });
|
|
6341
6332
|
});
|
|
6342
6333
|
registerHandler(refresh, async (body, ctx) => {
|
|
6343
6334
|
const soft = body.soft ?? false;
|
|
6344
6335
|
const windowId = body.window || ctx.targetWindowId;
|
|
6345
6336
|
const response = await ctx.requestFromBrowser("navigation", "refresh", { soft }, 5000, windowId);
|
|
6346
|
-
if (!response.success) {
|
|
6347
|
-
return Response.json(response, { headers: ctx.headers });
|
|
6348
|
-
}
|
|
6349
|
-
const reconnected = await ctx.waitForReconnect(windowId, 8000);
|
|
6350
|
-
if (!reconnected) {
|
|
6351
|
-
return Response.json({
|
|
6352
|
-
...response,
|
|
6353
|
-
success: false,
|
|
6354
|
-
error: `Refresh sent but page did not reconnect within 8s. The page may have loaded without the Haltija widget.`
|
|
6355
|
-
}, { headers: ctx.headers });
|
|
6356
|
-
}
|
|
6357
6337
|
return Response.json(response, { headers: ctx.headers });
|
|
6358
6338
|
});
|
|
6359
6339
|
registerHandler(tree, async (body, ctx) => {
|
|
@@ -6976,7 +6956,7 @@ async function wrapWithDeprecation(response, endpoint2) {
|
|
|
6976
6956
|
}
|
|
6977
6957
|
}
|
|
6978
6958
|
var GET_DEFAULTS = {
|
|
6979
|
-
"/tree": { selector: "body", depth:
|
|
6959
|
+
"/tree": { selector: "body", depth: -1 },
|
|
6980
6960
|
"/screenshot": {},
|
|
6981
6961
|
"/click": {},
|
|
6982
6962
|
"/type": {},
|
|
@@ -8310,12 +8290,12 @@ var createHandlerContext = (req, url) => {
|
|
|
8310
8290
|
return recordings2.get(id);
|
|
8311
8291
|
};
|
|
8312
8292
|
const sessionFilteredRequest = async (channel, action, payload, timeoutMs, windowId) => {
|
|
8313
|
-
if (sessionId && browsers.size === 0) {
|
|
8293
|
+
if (sessionId && (browsers.size === 0 || windows2.size === 0)) {
|
|
8314
8294
|
const waitStart = Date.now();
|
|
8315
8295
|
while (Date.now() - waitStart < 5000) {
|
|
8316
|
-
|
|
8317
|
-
if (browsers.size > 0)
|
|
8296
|
+
if (browsers.size > 0 && windows2.size > 0)
|
|
8318
8297
|
break;
|
|
8298
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
8319
8299
|
}
|
|
8320
8300
|
}
|
|
8321
8301
|
const effectiveWindowId = windowId || targetWindowId;
|
|
@@ -8325,7 +8305,8 @@ var createHandlerContext = (req, url) => {
|
|
|
8325
8305
|
return requestFromBrowser(channel, action, payload, timeoutMs, effectiveWindowId);
|
|
8326
8306
|
}
|
|
8327
8307
|
if (sessionId) {
|
|
8328
|
-
const
|
|
8308
|
+
const allWins = Array.from(windows2.values());
|
|
8309
|
+
const matching = allWins.filter((w) => w.session === sessionId).sort((a, b) => {
|
|
8329
8310
|
if (a.id === focusedWindowId)
|
|
8330
8311
|
return -1;
|
|
8331
8312
|
if (b.id === focusedWindowId)
|
|
@@ -8336,8 +8317,22 @@ var createHandlerContext = (req, url) => {
|
|
|
8336
8317
|
agentWindowAffinity.set(sessionId, matching[0].id);
|
|
8337
8318
|
return requestFromBrowser(channel, action, payload, timeoutMs, matching[0].id);
|
|
8338
8319
|
}
|
|
8320
|
+
const distinctSessions = new Set(allWins.map((w) => w.session).filter(Boolean));
|
|
8321
|
+
if (distinctSessions.size <= 1 && !isSecureMode) {
|
|
8322
|
+
const best = allWins.sort((a, b) => {
|
|
8323
|
+
if (a.id === focusedWindowId)
|
|
8324
|
+
return -1;
|
|
8325
|
+
if (b.id === focusedWindowId)
|
|
8326
|
+
return 1;
|
|
8327
|
+
return b.lastSeen - a.lastSeen;
|
|
8328
|
+
})[0];
|
|
8329
|
+
if (best) {
|
|
8330
|
+
agentWindowAffinity.set(sessionId, best.id);
|
|
8331
|
+
return requestFromBrowser(channel, action, payload, timeoutMs, best.id);
|
|
8332
|
+
}
|
|
8333
|
+
}
|
|
8339
8334
|
if (windows2.size > 0) {
|
|
8340
|
-
const anyWindow =
|
|
8335
|
+
const anyWindow = allWins[0];
|
|
8341
8336
|
try {
|
|
8342
8337
|
const openResult = await requestFromBrowser("tabs", "open", { url: undefined, session: sessionId }, 5000, anyWindow.id);
|
|
8343
8338
|
if (openResult.success) {
|
|
@@ -8353,8 +8348,7 @@ var createHandlerContext = (req, url) => {
|
|
|
8353
8348
|
}
|
|
8354
8349
|
} catch {}
|
|
8355
8350
|
}
|
|
8356
|
-
const
|
|
8357
|
-
const hint = allSessions.size > 0 ? `Available sessions: ${[...allSessions].map((s) => s.slice(0, 8) + "\u2026").join(", ")}. Check the widget UI for the full token.` : "No widgets have session tokens. Connect a browser tab first.";
|
|
8351
|
+
const hint = distinctSessions.size > 0 ? `Available sessions: ${[...distinctSessions].map((s) => s.slice(0, 8) + "\u2026").join(", ")}. Check the widget UI for the full token.` : "No widgets have session tokens. Connect a browser tab first.";
|
|
8358
8352
|
return { id: "", success: false, error: `No windows in session ${sessionId.slice(0, 8)}\u2026. ${hint}`, timestamp: Date.now() };
|
|
8359
8353
|
}
|
|
8360
8354
|
if (isSecureMode) {
|
|
@@ -8362,26 +8356,6 @@ var createHandlerContext = (req, url) => {
|
|
|
8362
8356
|
}
|
|
8363
8357
|
return requestFromBrowser(channel, action, payload, timeoutMs);
|
|
8364
8358
|
};
|
|
8365
|
-
const waitForReconnect = async (windowId, timeoutMs = 8000) => {
|
|
8366
|
-
const id = windowId || targetWindowId || focusedWindowId;
|
|
8367
|
-
const prevBrowserId = id ? windows2.get(id)?.browserId : undefined;
|
|
8368
|
-
const start = Date.now();
|
|
8369
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
8370
|
-
while (Date.now() - start < timeoutMs) {
|
|
8371
|
-
if (id) {
|
|
8372
|
-
const w = windows2.get(id);
|
|
8373
|
-
if (w && w.browserId !== prevBrowserId) {
|
|
8374
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
8375
|
-
return true;
|
|
8376
|
-
}
|
|
8377
|
-
} else if (browsers.size > 0) {
|
|
8378
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
8379
|
-
return true;
|
|
8380
|
-
}
|
|
8381
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
8382
|
-
}
|
|
8383
|
-
return false;
|
|
8384
|
-
};
|
|
8385
8359
|
return {
|
|
8386
8360
|
requestFromBrowser: sessionFilteredRequest,
|
|
8387
8361
|
targetWindowId,
|
|
@@ -8390,7 +8364,6 @@ var createHandlerContext = (req, url) => {
|
|
|
8390
8364
|
sessionId,
|
|
8391
8365
|
getWindowInfo,
|
|
8392
8366
|
updateSessionAffinity,
|
|
8393
|
-
waitForReconnect,
|
|
8394
8367
|
startRecordingSession,
|
|
8395
8368
|
stopRecordingSession,
|
|
8396
8369
|
getRecordingSession,
|
|
@@ -8623,7 +8596,10 @@ async function handleRest(req) {
|
|
|
8623
8596
|
const reqSession = req.headers.get("X-Haltija-Session") || undefined;
|
|
8624
8597
|
let allWindows = Array.from(windows2.values());
|
|
8625
8598
|
if (reqSession) {
|
|
8626
|
-
|
|
8599
|
+
const distinctSessions = new Set(allWindows.map((w) => w.session).filter(Boolean));
|
|
8600
|
+
if (distinctSessions.size > 1) {
|
|
8601
|
+
allWindows = allWindows.filter((w) => w.session === reqSession);
|
|
8602
|
+
}
|
|
8627
8603
|
} else if (isSecureMode) {
|
|
8628
8604
|
return Response.json({
|
|
8629
8605
|
ok: windows2.size > 0,
|
|
@@ -10326,7 +10302,10 @@ Type 'help <topic>' for details.`);
|
|
|
10326
10302
|
const reqSession = req.headers.get("X-Haltija-Session") || undefined;
|
|
10327
10303
|
let allWindows = Array.from(windows2.values());
|
|
10328
10304
|
if (reqSession) {
|
|
10329
|
-
|
|
10305
|
+
const distinctSessions = new Set(allWindows.map((w) => w.session).filter(Boolean));
|
|
10306
|
+
if (distinctSessions.size > 1) {
|
|
10307
|
+
allWindows = allWindows.filter((w) => w.session === reqSession);
|
|
10308
|
+
}
|
|
10330
10309
|
} else if (isSecureMode) {
|
|
10331
10310
|
const totalWindows = windows2.size;
|
|
10332
10311
|
return Response.json({
|
package/dist/server.js
CHANGED
|
@@ -674,7 +674,7 @@ var injectorCode = `
|
|
|
674
674
|
`;
|
|
675
675
|
|
|
676
676
|
// src/version.ts
|
|
677
|
-
var VERSION = "1.2.
|
|
677
|
+
var VERSION = "1.2.7";
|
|
678
678
|
|
|
679
679
|
// src/embedded-assets.ts
|
|
680
680
|
var APP_MD = `# Haltija App
|
|
@@ -1078,9 +1078,10 @@ Use ancestors:true to see parent elements when inspecting deep elements.
|
|
|
1078
1078
|
| Name | Type | Description |
|
|
1079
1079
|
|------|------|-------------|
|
|
1080
1080
|
| \`selector\` | string,null | Root element selector |
|
|
1081
|
-
| \`depth\` | number,null | Max depth (-1 = unlimited
|
|
1081
|
+
| \`depth\` | number,null | Max depth (-1 = unlimited). Default: unlimited |
|
|
1082
1082
|
| \`includeText\` | boolean,null | Include text content (default true) |
|
|
1083
1083
|
| \`visibleOnly\` | boolean,null | Only visible elements (default false) |
|
|
1084
|
+
| \`interactiveOnly\` | boolean,null | Only interactive elements and their ancestors (default false) |
|
|
1084
1085
|
| \`pierceShadow\` | boolean,null | Pierce shadow DOM (default true) |
|
|
1085
1086
|
| \`pierceFrames\` | boolean,null | Pierce same-origin iframes (default true) |
|
|
1086
1087
|
| \`compact\` | boolean,null | Minimal output (default false) |
|
|
@@ -1089,17 +1090,17 @@ Use ancestors:true to see parent elements when inspecting deep elements.
|
|
|
1089
1090
|
|
|
1090
1091
|
**Examples:**
|
|
1091
1092
|
|
|
1092
|
-
- **overview**: Quick page overview
|
|
1093
|
+
- **overview**: Quick page overview (shallow)
|
|
1093
1094
|
\`\`\`json
|
|
1094
|
-
{"depth":
|
|
1095
|
+
{"depth":3}
|
|
1095
1096
|
\`\`\`
|
|
1096
1097
|
- **form-only**: Full form structure
|
|
1097
1098
|
\`\`\`json
|
|
1098
|
-
{"selector":"form"
|
|
1099
|
+
{"selector":"form"}
|
|
1099
1100
|
\`\`\`
|
|
1100
|
-
- **
|
|
1101
|
+
- **interactive**: Only buttons, inputs, links, and their containers
|
|
1101
1102
|
\`\`\`json
|
|
1102
|
-
{"
|
|
1103
|
+
{"interactiveOnly":true}
|
|
1103
1104
|
\`\`\`
|
|
1104
1105
|
- **with-context**: See element with parent context
|
|
1105
1106
|
\`\`\`json
|
|
@@ -3702,9 +3703,10 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
3702
3703
|
category: "dom",
|
|
3703
3704
|
input: L.object({
|
|
3704
3705
|
selector: L.string.describe("Root element selector").optional,
|
|
3705
|
-
depth: L.number.describe("Max depth (-1 = unlimited
|
|
3706
|
+
depth: L.number.describe("Max depth (-1 = unlimited). Default: unlimited").optional,
|
|
3706
3707
|
includeText: L.boolean.describe("Include text content (default true)").optional,
|
|
3707
3708
|
visibleOnly: L.boolean.describe("Only visible elements (default false)").optional,
|
|
3709
|
+
interactiveOnly: L.boolean.describe("Only interactive elements and their ancestors (default false)").optional,
|
|
3708
3710
|
pierceShadow: L.boolean.describe("Pierce shadow DOM (default true)").optional,
|
|
3709
3711
|
pierceFrames: L.boolean.describe("Pierce same-origin iframes (default true)").optional,
|
|
3710
3712
|
compact: L.boolean.describe("Minimal output (default false)").optional,
|
|
@@ -3714,18 +3716,18 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
3714
3716
|
examples: [
|
|
3715
3717
|
{
|
|
3716
3718
|
name: "overview",
|
|
3717
|
-
input: { depth:
|
|
3718
|
-
description: "Quick page overview"
|
|
3719
|
+
input: { depth: 3 },
|
|
3720
|
+
description: "Quick page overview (shallow)"
|
|
3719
3721
|
},
|
|
3720
3722
|
{
|
|
3721
3723
|
name: "form-only",
|
|
3722
|
-
input: { selector: "form"
|
|
3724
|
+
input: { selector: "form" },
|
|
3723
3725
|
description: "Full form structure"
|
|
3724
3726
|
},
|
|
3725
3727
|
{
|
|
3726
|
-
name: "
|
|
3727
|
-
input: {
|
|
3728
|
-
description: "
|
|
3728
|
+
name: "interactive",
|
|
3729
|
+
input: { interactiveOnly: true },
|
|
3730
|
+
description: "Only buttons, inputs, links, and their containers"
|
|
3729
3731
|
},
|
|
3730
3732
|
{
|
|
3731
3733
|
name: "with-context",
|
|
@@ -3733,7 +3735,7 @@ Use ancestors:true to see parent elements when inspecting deep elements.`,
|
|
|
3733
3735
|
description: "See element with parent context"
|
|
3734
3736
|
}
|
|
3735
3737
|
],
|
|
3736
|
-
hints:
|
|
3738
|
+
hints: "-d 3 (shallow), -i (interactive only), --visible, --compact | see: inspect, query, click"
|
|
3737
3739
|
});
|
|
3738
3740
|
var query = endpoint({
|
|
3739
3741
|
path: "/query",
|
|
@@ -6326,34 +6328,12 @@ registerHandler(unhighlight, async (_body, ctx) => {
|
|
|
6326
6328
|
registerHandler(navigate, async (body, ctx) => {
|
|
6327
6329
|
const windowId = body.window || ctx.targetWindowId;
|
|
6328
6330
|
const response = await ctx.requestFromBrowser("navigation", "goto", { url: body.url }, 5000, windowId);
|
|
6329
|
-
if (!response.success) {
|
|
6330
|
-
return Response.json(response, { headers: ctx.headers });
|
|
6331
|
-
}
|
|
6332
|
-
const reconnected = await ctx.waitForReconnect(windowId, 1e4);
|
|
6333
|
-
if (!reconnected) {
|
|
6334
|
-
return Response.json({
|
|
6335
|
-
...response,
|
|
6336
|
-
success: false,
|
|
6337
|
-
error: `Navigation sent but page did not reconnect within 10s. The page may have loaded without the Haltija widget.`
|
|
6338
|
-
}, { headers: ctx.headers });
|
|
6339
|
-
}
|
|
6340
6331
|
return Response.json(response, { headers: ctx.headers });
|
|
6341
6332
|
});
|
|
6342
6333
|
registerHandler(refresh, async (body, ctx) => {
|
|
6343
6334
|
const soft = body.soft ?? false;
|
|
6344
6335
|
const windowId = body.window || ctx.targetWindowId;
|
|
6345
6336
|
const response = await ctx.requestFromBrowser("navigation", "refresh", { soft }, 5000, windowId);
|
|
6346
|
-
if (!response.success) {
|
|
6347
|
-
return Response.json(response, { headers: ctx.headers });
|
|
6348
|
-
}
|
|
6349
|
-
const reconnected = await ctx.waitForReconnect(windowId, 8000);
|
|
6350
|
-
if (!reconnected) {
|
|
6351
|
-
return Response.json({
|
|
6352
|
-
...response,
|
|
6353
|
-
success: false,
|
|
6354
|
-
error: `Refresh sent but page did not reconnect within 8s. The page may have loaded without the Haltija widget.`
|
|
6355
|
-
}, { headers: ctx.headers });
|
|
6356
|
-
}
|
|
6357
6337
|
return Response.json(response, { headers: ctx.headers });
|
|
6358
6338
|
});
|
|
6359
6339
|
registerHandler(tree, async (body, ctx) => {
|
|
@@ -6976,7 +6956,7 @@ async function wrapWithDeprecation(response, endpoint2) {
|
|
|
6976
6956
|
}
|
|
6977
6957
|
}
|
|
6978
6958
|
var GET_DEFAULTS = {
|
|
6979
|
-
"/tree": { selector: "body", depth:
|
|
6959
|
+
"/tree": { selector: "body", depth: -1 },
|
|
6980
6960
|
"/screenshot": {},
|
|
6981
6961
|
"/click": {},
|
|
6982
6962
|
"/type": {},
|
|
@@ -8310,12 +8290,12 @@ var createHandlerContext = (req, url) => {
|
|
|
8310
8290
|
return recordings2.get(id);
|
|
8311
8291
|
};
|
|
8312
8292
|
const sessionFilteredRequest = async (channel, action, payload, timeoutMs, windowId) => {
|
|
8313
|
-
if (sessionId && browsers.size === 0) {
|
|
8293
|
+
if (sessionId && (browsers.size === 0 || windows2.size === 0)) {
|
|
8314
8294
|
const waitStart = Date.now();
|
|
8315
8295
|
while (Date.now() - waitStart < 5000) {
|
|
8316
|
-
|
|
8317
|
-
if (browsers.size > 0)
|
|
8296
|
+
if (browsers.size > 0 && windows2.size > 0)
|
|
8318
8297
|
break;
|
|
8298
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
8319
8299
|
}
|
|
8320
8300
|
}
|
|
8321
8301
|
const effectiveWindowId = windowId || targetWindowId;
|
|
@@ -8325,7 +8305,8 @@ var createHandlerContext = (req, url) => {
|
|
|
8325
8305
|
return requestFromBrowser(channel, action, payload, timeoutMs, effectiveWindowId);
|
|
8326
8306
|
}
|
|
8327
8307
|
if (sessionId) {
|
|
8328
|
-
const
|
|
8308
|
+
const allWins = Array.from(windows2.values());
|
|
8309
|
+
const matching = allWins.filter((w) => w.session === sessionId).sort((a, b) => {
|
|
8329
8310
|
if (a.id === focusedWindowId)
|
|
8330
8311
|
return -1;
|
|
8331
8312
|
if (b.id === focusedWindowId)
|
|
@@ -8336,8 +8317,22 @@ var createHandlerContext = (req, url) => {
|
|
|
8336
8317
|
agentWindowAffinity.set(sessionId, matching[0].id);
|
|
8337
8318
|
return requestFromBrowser(channel, action, payload, timeoutMs, matching[0].id);
|
|
8338
8319
|
}
|
|
8320
|
+
const distinctSessions = new Set(allWins.map((w) => w.session).filter(Boolean));
|
|
8321
|
+
if (distinctSessions.size <= 1 && !isSecureMode) {
|
|
8322
|
+
const best = allWins.sort((a, b) => {
|
|
8323
|
+
if (a.id === focusedWindowId)
|
|
8324
|
+
return -1;
|
|
8325
|
+
if (b.id === focusedWindowId)
|
|
8326
|
+
return 1;
|
|
8327
|
+
return b.lastSeen - a.lastSeen;
|
|
8328
|
+
})[0];
|
|
8329
|
+
if (best) {
|
|
8330
|
+
agentWindowAffinity.set(sessionId, best.id);
|
|
8331
|
+
return requestFromBrowser(channel, action, payload, timeoutMs, best.id);
|
|
8332
|
+
}
|
|
8333
|
+
}
|
|
8339
8334
|
if (windows2.size > 0) {
|
|
8340
|
-
const anyWindow =
|
|
8335
|
+
const anyWindow = allWins[0];
|
|
8341
8336
|
try {
|
|
8342
8337
|
const openResult = await requestFromBrowser("tabs", "open", { url: undefined, session: sessionId }, 5000, anyWindow.id);
|
|
8343
8338
|
if (openResult.success) {
|
|
@@ -8353,8 +8348,7 @@ var createHandlerContext = (req, url) => {
|
|
|
8353
8348
|
}
|
|
8354
8349
|
} catch {}
|
|
8355
8350
|
}
|
|
8356
|
-
const
|
|
8357
|
-
const hint = allSessions.size > 0 ? `Available sessions: ${[...allSessions].map((s) => s.slice(0, 8) + "\u2026").join(", ")}. Check the widget UI for the full token.` : "No widgets have session tokens. Connect a browser tab first.";
|
|
8351
|
+
const hint = distinctSessions.size > 0 ? `Available sessions: ${[...distinctSessions].map((s) => s.slice(0, 8) + "\u2026").join(", ")}. Check the widget UI for the full token.` : "No widgets have session tokens. Connect a browser tab first.";
|
|
8358
8352
|
return { id: "", success: false, error: `No windows in session ${sessionId.slice(0, 8)}\u2026. ${hint}`, timestamp: Date.now() };
|
|
8359
8353
|
}
|
|
8360
8354
|
if (isSecureMode) {
|
|
@@ -8362,26 +8356,6 @@ var createHandlerContext = (req, url) => {
|
|
|
8362
8356
|
}
|
|
8363
8357
|
return requestFromBrowser(channel, action, payload, timeoutMs);
|
|
8364
8358
|
};
|
|
8365
|
-
const waitForReconnect = async (windowId, timeoutMs = 8000) => {
|
|
8366
|
-
const id = windowId || targetWindowId || focusedWindowId;
|
|
8367
|
-
const prevBrowserId = id ? windows2.get(id)?.browserId : undefined;
|
|
8368
|
-
const start = Date.now();
|
|
8369
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
8370
|
-
while (Date.now() - start < timeoutMs) {
|
|
8371
|
-
if (id) {
|
|
8372
|
-
const w = windows2.get(id);
|
|
8373
|
-
if (w && w.browserId !== prevBrowserId) {
|
|
8374
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
8375
|
-
return true;
|
|
8376
|
-
}
|
|
8377
|
-
} else if (browsers.size > 0) {
|
|
8378
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
8379
|
-
return true;
|
|
8380
|
-
}
|
|
8381
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
8382
|
-
}
|
|
8383
|
-
return false;
|
|
8384
|
-
};
|
|
8385
8359
|
return {
|
|
8386
8360
|
requestFromBrowser: sessionFilteredRequest,
|
|
8387
8361
|
targetWindowId,
|
|
@@ -8390,7 +8364,6 @@ var createHandlerContext = (req, url) => {
|
|
|
8390
8364
|
sessionId,
|
|
8391
8365
|
getWindowInfo,
|
|
8392
8366
|
updateSessionAffinity,
|
|
8393
|
-
waitForReconnect,
|
|
8394
8367
|
startRecordingSession,
|
|
8395
8368
|
stopRecordingSession,
|
|
8396
8369
|
getRecordingSession,
|
|
@@ -8623,7 +8596,10 @@ async function handleRest(req) {
|
|
|
8623
8596
|
const reqSession = req.headers.get("X-Haltija-Session") || undefined;
|
|
8624
8597
|
let allWindows = Array.from(windows2.values());
|
|
8625
8598
|
if (reqSession) {
|
|
8626
|
-
|
|
8599
|
+
const distinctSessions = new Set(allWindows.map((w) => w.session).filter(Boolean));
|
|
8600
|
+
if (distinctSessions.size > 1) {
|
|
8601
|
+
allWindows = allWindows.filter((w) => w.session === reqSession);
|
|
8602
|
+
}
|
|
8627
8603
|
} else if (isSecureMode) {
|
|
8628
8604
|
return Response.json({
|
|
8629
8605
|
ok: windows2.size > 0,
|
|
@@ -10326,7 +10302,10 @@ Type 'help <topic>' for details.`);
|
|
|
10326
10302
|
const reqSession = req.headers.get("X-Haltija-Session") || undefined;
|
|
10327
10303
|
let allWindows = Array.from(windows2.values());
|
|
10328
10304
|
if (reqSession) {
|
|
10329
|
-
|
|
10305
|
+
const distinctSessions = new Set(allWindows.map((w) => w.session).filter(Boolean));
|
|
10306
|
+
if (distinctSessions.size > 1) {
|
|
10307
|
+
allWindows = allWindows.filter((w) => w.session === reqSession);
|
|
10308
|
+
}
|
|
10330
10309
|
} else if (isSecureMode) {
|
|
10331
10310
|
const totalWindows = windows2.size;
|
|
10332
10311
|
return Response.json({
|