opensteer 0.4.14 → 0.5.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/{chunk-WXUXG76V.js → chunk-SXPIGCSD.js} +746 -899
- package/dist/cli/server.cjs +777 -929
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +777 -931
- package/dist/index.d.cts +16 -29
- package/dist/index.d.ts +16 -29
- package/dist/index.js +1 -3
- package/package.json +1 -1
package/dist/cli/server.cjs
CHANGED
|
@@ -360,7 +360,7 @@ var import_net = require("net");
|
|
|
360
360
|
var import_fs4 = require("fs");
|
|
361
361
|
|
|
362
362
|
// src/opensteer.ts
|
|
363
|
-
var
|
|
363
|
+
var import_crypto = require("crypto");
|
|
364
364
|
|
|
365
365
|
// src/browser/pool.ts
|
|
366
366
|
var import_playwright = require("playwright");
|
|
@@ -2142,7 +2142,6 @@ var LocalSelectorStorage = class {
|
|
|
2142
2142
|
|
|
2143
2143
|
// src/html/pipeline.ts
|
|
2144
2144
|
var cheerio3 = __toESM(require("cheerio"), 1);
|
|
2145
|
-
var import_crypto = require("crypto");
|
|
2146
2145
|
|
|
2147
2146
|
// src/html/serializer.ts
|
|
2148
2147
|
var cheerio = __toESM(require("cheerio"), 1);
|
|
@@ -2415,9 +2414,6 @@ var ENSURE_NAME_SHIM_SCRIPT = `
|
|
|
2415
2414
|
`;
|
|
2416
2415
|
var OS_FRAME_TOKEN_KEY = "__opensteerFrameToken";
|
|
2417
2416
|
var OS_INSTANCE_TOKEN_KEY = "__opensteerInstanceToken";
|
|
2418
|
-
var OS_COUNTER_OWNER_KEY = "__opensteerCounterOwner";
|
|
2419
|
-
var OS_COUNTER_VALUE_KEY = "__opensteerCounterValue";
|
|
2420
|
-
var OS_COUNTER_NEXT_KEY = "__opensteerCounterNext";
|
|
2421
2417
|
|
|
2422
2418
|
// src/element-path/build.ts
|
|
2423
2419
|
var MAX_ATTRIBUTE_VALUE_LENGTH = 300;
|
|
@@ -4207,567 +4203,178 @@ function cleanForAction(html) {
|
|
|
4207
4203
|
return compactHtml(htmlOut);
|
|
4208
4204
|
}
|
|
4209
4205
|
|
|
4210
|
-
// src/
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
const text = rawText.replace(/\s+/g, " ").trim();
|
|
4225
|
-
return text || null;
|
|
4226
|
-
}
|
|
4227
|
-
function pickSingleListAttributeValue(attribute, raw) {
|
|
4228
|
-
if (attribute === "ping") {
|
|
4229
|
-
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
4230
|
-
return firstUrl.trim();
|
|
4231
|
-
}
|
|
4232
|
-
if (attribute === "srcset" || attribute === "imagesrcset") {
|
|
4233
|
-
const picked = pickBestSrcsetCandidate(raw);
|
|
4234
|
-
if (picked) return picked;
|
|
4235
|
-
return pickFirstSrcsetToken(raw) || "";
|
|
4236
|
-
}
|
|
4237
|
-
return raw.trim();
|
|
4238
|
-
}
|
|
4239
|
-
function pickBestSrcsetCandidate(raw) {
|
|
4240
|
-
const candidates = parseSrcsetCandidates(raw);
|
|
4241
|
-
if (!candidates.length) return null;
|
|
4242
|
-
const widthCandidates = candidates.filter(
|
|
4243
|
-
(candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
|
|
4244
|
-
);
|
|
4245
|
-
if (widthCandidates.length) {
|
|
4246
|
-
return widthCandidates.reduce(
|
|
4247
|
-
(best, candidate) => candidate.width > best.width ? candidate : best
|
|
4248
|
-
).url;
|
|
4249
|
-
}
|
|
4250
|
-
const densityCandidates = candidates.filter(
|
|
4251
|
-
(candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
|
|
4252
|
-
);
|
|
4253
|
-
if (densityCandidates.length) {
|
|
4254
|
-
return densityCandidates.reduce(
|
|
4255
|
-
(best, candidate) => candidate.density > best.density ? candidate : best
|
|
4256
|
-
).url;
|
|
4257
|
-
}
|
|
4258
|
-
return candidates[0]?.url || null;
|
|
4259
|
-
}
|
|
4260
|
-
function parseSrcsetCandidates(raw) {
|
|
4261
|
-
const text = String(raw || "").trim();
|
|
4262
|
-
if (!text) return [];
|
|
4263
|
-
const out = [];
|
|
4264
|
-
let index = 0;
|
|
4265
|
-
while (index < text.length) {
|
|
4266
|
-
index = skipSeparators(text, index);
|
|
4267
|
-
if (index >= text.length) break;
|
|
4268
|
-
const urlToken = readUrlToken(text, index);
|
|
4269
|
-
index = urlToken.nextIndex;
|
|
4270
|
-
const url = urlToken.value.trim();
|
|
4271
|
-
if (!url) continue;
|
|
4272
|
-
index = skipWhitespace(text, index);
|
|
4273
|
-
const descriptors = [];
|
|
4274
|
-
while (index < text.length && text[index] !== ",") {
|
|
4275
|
-
const descriptorToken = readDescriptorToken(text, index);
|
|
4276
|
-
if (!descriptorToken.value) {
|
|
4277
|
-
index = descriptorToken.nextIndex;
|
|
4278
|
-
continue;
|
|
4279
|
-
}
|
|
4280
|
-
descriptors.push(descriptorToken.value);
|
|
4281
|
-
index = descriptorToken.nextIndex;
|
|
4282
|
-
index = skipWhitespace(text, index);
|
|
4283
|
-
}
|
|
4284
|
-
if (index < text.length && text[index] === ",") {
|
|
4285
|
-
index += 1;
|
|
4286
|
-
}
|
|
4287
|
-
let width = null;
|
|
4288
|
-
let density = null;
|
|
4289
|
-
for (const descriptor of descriptors) {
|
|
4290
|
-
const token = descriptor.trim().toLowerCase();
|
|
4291
|
-
if (!token) continue;
|
|
4292
|
-
const widthMatch = token.match(/^(\d+)w$/);
|
|
4293
|
-
if (widthMatch) {
|
|
4294
|
-
const parsed = Number.parseInt(widthMatch[1], 10);
|
|
4295
|
-
if (Number.isFinite(parsed)) {
|
|
4296
|
-
width = parsed;
|
|
4297
|
-
}
|
|
4298
|
-
continue;
|
|
4299
|
-
}
|
|
4300
|
-
const densityMatch = token.match(/^(\d*\.?\d+)x$/);
|
|
4301
|
-
if (densityMatch) {
|
|
4302
|
-
const parsed = Number.parseFloat(densityMatch[1]);
|
|
4303
|
-
if (Number.isFinite(parsed)) {
|
|
4304
|
-
density = parsed;
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
}
|
|
4308
|
-
out.push({
|
|
4309
|
-
url,
|
|
4310
|
-
width,
|
|
4311
|
-
density
|
|
4312
|
-
});
|
|
4313
|
-
}
|
|
4314
|
-
return out;
|
|
4315
|
-
}
|
|
4316
|
-
function pickFirstSrcsetToken(raw) {
|
|
4317
|
-
const candidate = parseSrcsetCandidates(raw)[0];
|
|
4318
|
-
if (candidate?.url) {
|
|
4319
|
-
return candidate.url;
|
|
4320
|
-
}
|
|
4321
|
-
const text = String(raw || "");
|
|
4322
|
-
const start = skipSeparators(text, 0);
|
|
4323
|
-
if (start >= text.length) return null;
|
|
4324
|
-
const firstToken = readUrlToken(text, start).value.trim();
|
|
4325
|
-
return firstToken || null;
|
|
4326
|
-
}
|
|
4327
|
-
function skipWhitespace(value, index) {
|
|
4328
|
-
let cursor = index;
|
|
4329
|
-
while (cursor < value.length && /\s/.test(value[cursor])) {
|
|
4330
|
-
cursor += 1;
|
|
4331
|
-
}
|
|
4332
|
-
return cursor;
|
|
4333
|
-
}
|
|
4334
|
-
function skipSeparators(value, index) {
|
|
4335
|
-
let cursor = skipWhitespace(value, index);
|
|
4336
|
-
while (cursor < value.length && value[cursor] === ",") {
|
|
4337
|
-
cursor += 1;
|
|
4338
|
-
cursor = skipWhitespace(value, cursor);
|
|
4339
|
-
}
|
|
4340
|
-
return cursor;
|
|
4341
|
-
}
|
|
4342
|
-
function readUrlToken(value, index) {
|
|
4343
|
-
let cursor = index;
|
|
4344
|
-
let out = "";
|
|
4345
|
-
const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
|
|
4346
|
-
while (cursor < value.length) {
|
|
4347
|
-
const char = value[cursor];
|
|
4348
|
-
if (/\s/.test(char)) {
|
|
4349
|
-
break;
|
|
4350
|
-
}
|
|
4351
|
-
if (char === "," && !isDataUrl) {
|
|
4352
|
-
break;
|
|
4353
|
-
}
|
|
4354
|
-
out += char;
|
|
4355
|
-
cursor += 1;
|
|
4356
|
-
}
|
|
4357
|
-
if (isDataUrl && out.endsWith(",") && cursor < value.length) {
|
|
4358
|
-
out = out.slice(0, -1);
|
|
4206
|
+
// src/html/pipeline.ts
|
|
4207
|
+
function applyCleaner(mode, html) {
|
|
4208
|
+
switch (mode) {
|
|
4209
|
+
case "clickable":
|
|
4210
|
+
return cleanForClickable(html);
|
|
4211
|
+
case "scrollable":
|
|
4212
|
+
return cleanForScrollable(html);
|
|
4213
|
+
case "extraction":
|
|
4214
|
+
return cleanForExtraction(html);
|
|
4215
|
+
case "full":
|
|
4216
|
+
return cleanForFull(html);
|
|
4217
|
+
case "action":
|
|
4218
|
+
default:
|
|
4219
|
+
return cleanForAction(html);
|
|
4359
4220
|
}
|
|
4360
|
-
return {
|
|
4361
|
-
value: out,
|
|
4362
|
-
nextIndex: cursor
|
|
4363
|
-
};
|
|
4364
4221
|
}
|
|
4365
|
-
function
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4222
|
+
async function assignCounters(page, html, nodePaths, nodeMeta) {
|
|
4223
|
+
const $ = cheerio3.load(html, { xmlMode: false });
|
|
4224
|
+
const counterIndex = /* @__PURE__ */ new Map();
|
|
4225
|
+
let nextCounter = 1;
|
|
4226
|
+
const assignedByNodeId = /* @__PURE__ */ new Map();
|
|
4227
|
+
$("*").each(function() {
|
|
4228
|
+
const el = $(this);
|
|
4229
|
+
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
4230
|
+
if (!nodeId) return;
|
|
4231
|
+
const counter = nextCounter++;
|
|
4232
|
+
assignedByNodeId.set(nodeId, counter);
|
|
4233
|
+
const path5 = nodePaths.get(nodeId);
|
|
4234
|
+
el.attr("c", String(counter));
|
|
4235
|
+
el.removeAttr(OS_NODE_ID_ATTR);
|
|
4236
|
+
if (path5) {
|
|
4237
|
+
counterIndex.set(counter, cloneElementPath(path5));
|
|
4372
4238
|
}
|
|
4373
|
-
|
|
4374
|
-
|
|
4239
|
+
});
|
|
4240
|
+
try {
|
|
4241
|
+
await syncLiveCounters(page, nodeMeta, assignedByNodeId);
|
|
4242
|
+
} catch (error) {
|
|
4243
|
+
await clearLiveCounters(page);
|
|
4244
|
+
throw error;
|
|
4375
4245
|
}
|
|
4246
|
+
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
4376
4247
|
return {
|
|
4377
|
-
|
|
4378
|
-
|
|
4248
|
+
html: $.html(),
|
|
4249
|
+
counterIndex
|
|
4379
4250
|
};
|
|
4380
4251
|
}
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
super(message);
|
|
4387
|
-
this.name = "CounterResolutionError";
|
|
4388
|
-
this.code = code;
|
|
4389
|
-
}
|
|
4390
|
-
};
|
|
4391
|
-
async function ensureLiveCounters(page, nodeMeta, nodeIds) {
|
|
4392
|
-
const out = /* @__PURE__ */ new Map();
|
|
4393
|
-
if (!nodeIds.length) return out;
|
|
4394
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
4395
|
-
for (const nodeId of nodeIds) {
|
|
4252
|
+
async function syncLiveCounters(page, nodeMeta, assignedByNodeId) {
|
|
4253
|
+
await clearLiveCounters(page);
|
|
4254
|
+
if (!assignedByNodeId.size) return;
|
|
4255
|
+
const groupedByFrame = /* @__PURE__ */ new Map();
|
|
4256
|
+
for (const [nodeId, counter] of assignedByNodeId.entries()) {
|
|
4396
4257
|
const meta = nodeMeta.get(nodeId);
|
|
4397
|
-
if (!meta)
|
|
4398
|
-
|
|
4399
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4400
|
-
`Missing metadata for node ${nodeId}. Run snapshot() again.`
|
|
4401
|
-
);
|
|
4402
|
-
}
|
|
4403
|
-
const list = grouped.get(meta.frameToken) || [];
|
|
4258
|
+
if (!meta?.frameToken) continue;
|
|
4259
|
+
const list = groupedByFrame.get(meta.frameToken) || [];
|
|
4404
4260
|
list.push({
|
|
4405
4261
|
nodeId,
|
|
4406
|
-
|
|
4262
|
+
counter
|
|
4407
4263
|
});
|
|
4408
|
-
|
|
4264
|
+
groupedByFrame.set(meta.frameToken, list);
|
|
4409
4265
|
}
|
|
4266
|
+
if (!groupedByFrame.size) return;
|
|
4267
|
+
const failures = [];
|
|
4410
4268
|
const framesByToken = await mapFramesByToken(page);
|
|
4411
|
-
|
|
4412
|
-
const usedCounters = /* @__PURE__ */ new Map();
|
|
4413
|
-
for (const [frameToken, entries] of grouped.entries()) {
|
|
4269
|
+
for (const [frameToken, entries] of groupedByFrame.entries()) {
|
|
4414
4270
|
const frame = framesByToken.get(frameToken);
|
|
4415
4271
|
if (!frame) {
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4272
|
+
for (const entry of entries) {
|
|
4273
|
+
failures.push({
|
|
4274
|
+
nodeId: entry.nodeId,
|
|
4275
|
+
counter: entry.counter,
|
|
4276
|
+
frameToken,
|
|
4277
|
+
reason: "frame_missing"
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
continue;
|
|
4420
4281
|
}
|
|
4421
|
-
|
|
4422
|
-
(
|
|
4423
|
-
entries: entries2,
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
counterValueKey,
|
|
4428
|
-
startCounter
|
|
4429
|
-
}) => {
|
|
4430
|
-
const helpers = {
|
|
4431
|
-
pushNode(map, node) {
|
|
4432
|
-
const nodeId = node.getAttribute(nodeAttr);
|
|
4433
|
-
if (!nodeId) return;
|
|
4434
|
-
const list = map.get(nodeId) || [];
|
|
4435
|
-
list.push(node);
|
|
4436
|
-
map.set(nodeId, list);
|
|
4437
|
-
},
|
|
4438
|
-
walk(map, root) {
|
|
4282
|
+
try {
|
|
4283
|
+
const unresolved = await frame.evaluate(
|
|
4284
|
+
({ entries: entries2, nodeAttr }) => {
|
|
4285
|
+
const index = /* @__PURE__ */ new Map();
|
|
4286
|
+
const unresolved2 = [];
|
|
4287
|
+
const walk = (root) => {
|
|
4439
4288
|
const children = Array.from(root.children);
|
|
4440
4289
|
for (const child of children) {
|
|
4441
|
-
|
|
4442
|
-
|
|
4290
|
+
const nodeId = child.getAttribute(nodeAttr);
|
|
4291
|
+
if (nodeId) {
|
|
4292
|
+
const list = index.get(nodeId) || [];
|
|
4293
|
+
list.push(child);
|
|
4294
|
+
index.set(nodeId, list);
|
|
4295
|
+
}
|
|
4296
|
+
walk(child);
|
|
4443
4297
|
if (child.shadowRoot) {
|
|
4444
|
-
|
|
4298
|
+
walk(child.shadowRoot);
|
|
4445
4299
|
}
|
|
4446
4300
|
}
|
|
4447
|
-
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
if (!matches.length) {
|
|
4461
|
-
failures.push({
|
|
4462
|
-
nodeId: entry.nodeId,
|
|
4463
|
-
reason: "missing"
|
|
4464
|
-
});
|
|
4465
|
-
continue;
|
|
4466
|
-
}
|
|
4467
|
-
if (matches.length !== 1) {
|
|
4468
|
-
failures.push({
|
|
4469
|
-
nodeId: entry.nodeId,
|
|
4470
|
-
reason: "ambiguous"
|
|
4471
|
-
});
|
|
4472
|
-
continue;
|
|
4473
|
-
}
|
|
4474
|
-
const target = matches[0];
|
|
4475
|
-
if (target[instanceTokenKey] !== entry.instanceToken) {
|
|
4476
|
-
failures.push({
|
|
4477
|
-
nodeId: entry.nodeId,
|
|
4478
|
-
reason: "instance_mismatch"
|
|
4479
|
-
});
|
|
4480
|
-
continue;
|
|
4481
|
-
}
|
|
4482
|
-
const owned = target[counterOwnerKey] === true;
|
|
4483
|
-
const runtimeCounter = Number(target[counterValueKey] || 0);
|
|
4484
|
-
if (owned && Number.isFinite(runtimeCounter) && runtimeCounter > 0) {
|
|
4485
|
-
target.setAttribute("c", String(runtimeCounter));
|
|
4486
|
-
assigned.push({
|
|
4487
|
-
nodeId: entry.nodeId,
|
|
4488
|
-
counter: runtimeCounter
|
|
4489
|
-
});
|
|
4490
|
-
continue;
|
|
4301
|
+
};
|
|
4302
|
+
walk(document);
|
|
4303
|
+
for (const entry of entries2) {
|
|
4304
|
+
const matches = index.get(entry.nodeId) || [];
|
|
4305
|
+
if (matches.length !== 1) {
|
|
4306
|
+
unresolved2.push({
|
|
4307
|
+
nodeId: entry.nodeId,
|
|
4308
|
+
counter: entry.counter,
|
|
4309
|
+
matches: matches.length
|
|
4310
|
+
});
|
|
4311
|
+
continue;
|
|
4312
|
+
}
|
|
4313
|
+
matches[0].setAttribute("c", String(entry.counter));
|
|
4491
4314
|
}
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
configurable: true
|
|
4498
|
-
});
|
|
4499
|
-
Object.defineProperty(target, counterValueKey, {
|
|
4500
|
-
value: counter,
|
|
4501
|
-
writable: true,
|
|
4502
|
-
configurable: true
|
|
4503
|
-
});
|
|
4504
|
-
assigned.push({ nodeId: entry.nodeId, counter });
|
|
4315
|
+
return unresolved2;
|
|
4316
|
+
},
|
|
4317
|
+
{
|
|
4318
|
+
entries,
|
|
4319
|
+
nodeAttr: OS_NODE_ID_ATTR
|
|
4505
4320
|
}
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
}
|
|
4525
|
-
nextCounter = result.nextCounter;
|
|
4526
|
-
for (const item of result.assigned) {
|
|
4527
|
-
const existingNode = usedCounters.get(item.counter);
|
|
4528
|
-
if (existingNode && existingNode !== item.nodeId) {
|
|
4529
|
-
throw new CounterResolutionError(
|
|
4530
|
-
"ERR_COUNTER_AMBIGUOUS",
|
|
4531
|
-
`Counter ${item.counter} is assigned to multiple nodes (${existingNode}, ${item.nodeId}). Run snapshot() again.`
|
|
4532
|
-
);
|
|
4321
|
+
);
|
|
4322
|
+
for (const entry of unresolved) {
|
|
4323
|
+
failures.push({
|
|
4324
|
+
nodeId: entry.nodeId,
|
|
4325
|
+
counter: entry.counter,
|
|
4326
|
+
frameToken,
|
|
4327
|
+
reason: "match_count",
|
|
4328
|
+
matches: entry.matches
|
|
4329
|
+
});
|
|
4330
|
+
}
|
|
4331
|
+
} catch {
|
|
4332
|
+
for (const entry of entries) {
|
|
4333
|
+
failures.push({
|
|
4334
|
+
nodeId: entry.nodeId,
|
|
4335
|
+
counter: entry.counter,
|
|
4336
|
+
frameToken,
|
|
4337
|
+
reason: "frame_unavailable"
|
|
4338
|
+
});
|
|
4533
4339
|
}
|
|
4534
|
-
usedCounters.set(item.counter, item.nodeId);
|
|
4535
|
-
out.set(item.nodeId, item.counter);
|
|
4536
4340
|
}
|
|
4537
4341
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
}
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
const framesByToken = await mapFramesByToken(page);
|
|
4544
|
-
const frame = framesByToken.get(binding.frameToken);
|
|
4545
|
-
if (!frame) {
|
|
4546
|
-
throw new CounterResolutionError(
|
|
4547
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
4548
|
-
`Counter ${counter} frame is unavailable. Run snapshot() again.`
|
|
4549
|
-
);
|
|
4550
|
-
}
|
|
4551
|
-
const status = await frame.evaluate(
|
|
4552
|
-
({
|
|
4553
|
-
nodeId,
|
|
4554
|
-
instanceToken,
|
|
4555
|
-
counter: counter2,
|
|
4556
|
-
nodeAttr,
|
|
4557
|
-
instanceTokenKey,
|
|
4558
|
-
counterOwnerKey,
|
|
4559
|
-
counterValueKey
|
|
4560
|
-
}) => {
|
|
4561
|
-
const helpers = {
|
|
4562
|
-
walk(map, root) {
|
|
4563
|
-
const children = Array.from(root.children);
|
|
4564
|
-
for (const child of children) {
|
|
4565
|
-
const id = child.getAttribute(nodeAttr);
|
|
4566
|
-
if (id) {
|
|
4567
|
-
const list = map.get(id) || [];
|
|
4568
|
-
list.push(child);
|
|
4569
|
-
map.set(id, list);
|
|
4570
|
-
}
|
|
4571
|
-
helpers.walk(map, child);
|
|
4572
|
-
if (child.shadowRoot) {
|
|
4573
|
-
helpers.walk(map, child.shadowRoot);
|
|
4574
|
-
}
|
|
4575
|
-
}
|
|
4576
|
-
},
|
|
4577
|
-
buildNodeIndex() {
|
|
4578
|
-
const map = /* @__PURE__ */ new Map();
|
|
4579
|
-
helpers.walk(map, document);
|
|
4580
|
-
return map;
|
|
4581
|
-
}
|
|
4582
|
-
};
|
|
4583
|
-
const matches = helpers.buildNodeIndex().get(nodeId) || [];
|
|
4584
|
-
if (!matches.length) return "missing";
|
|
4585
|
-
if (matches.length !== 1) return "ambiguous";
|
|
4586
|
-
const target = matches[0];
|
|
4587
|
-
if (target[instanceTokenKey] !== instanceToken) {
|
|
4588
|
-
return "instance_mismatch";
|
|
4589
|
-
}
|
|
4590
|
-
if (target[counterOwnerKey] !== true) {
|
|
4591
|
-
return "instance_mismatch";
|
|
4342
|
+
if (failures.length) {
|
|
4343
|
+
const preview = failures.slice(0, 3).map((failure) => {
|
|
4344
|
+
const base = `counter ${failure.counter} (nodeId "${failure.nodeId}") in frame "${failure.frameToken}"`;
|
|
4345
|
+
if (failure.reason === "frame_missing") {
|
|
4346
|
+
return `${base} could not be synchronized because the frame is missing.`;
|
|
4592
4347
|
}
|
|
4593
|
-
if (
|
|
4594
|
-
return
|
|
4348
|
+
if (failure.reason === "frame_unavailable") {
|
|
4349
|
+
return `${base} could not be synchronized because frame evaluation failed.`;
|
|
4595
4350
|
}
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
nodeId: binding.nodeId,
|
|
4603
|
-
instanceToken: binding.instanceToken,
|
|
4604
|
-
counter,
|
|
4605
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4606
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
4607
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4608
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
4609
|
-
}
|
|
4610
|
-
);
|
|
4611
|
-
if (status !== "ok") {
|
|
4612
|
-
throw buildCounterFailureError(binding.nodeId, status);
|
|
4351
|
+
return `${base} expected exactly one live node but found ${failure.matches ?? 0}.`;
|
|
4352
|
+
});
|
|
4353
|
+
const remaining = failures.length > 3 ? ` (+${failures.length - 3} more)` : "";
|
|
4354
|
+
throw new Error(
|
|
4355
|
+
`Failed to synchronize snapshot counters with the live DOM: ${preview.join(" ")}${remaining}`
|
|
4356
|
+
);
|
|
4613
4357
|
}
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4358
|
+
}
|
|
4359
|
+
async function clearLiveCounters(page) {
|
|
4360
|
+
for (const frame of page.frames()) {
|
|
4361
|
+
try {
|
|
4362
|
+
await frame.evaluate(() => {
|
|
4363
|
+
const walk = (root) => {
|
|
4618
4364
|
const children = Array.from(root.children);
|
|
4619
4365
|
for (const child of children) {
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
}
|
|
4623
|
-
helpers.walk(matches, child);
|
|
4366
|
+
child.removeAttribute("c");
|
|
4367
|
+
walk(child);
|
|
4624
4368
|
if (child.shadowRoot) {
|
|
4625
|
-
|
|
4626
|
-
}
|
|
4627
|
-
}
|
|
4628
|
-
},
|
|
4629
|
-
findUniqueNode() {
|
|
4630
|
-
const matches = [];
|
|
4631
|
-
helpers.walk(matches, document);
|
|
4632
|
-
if (matches.length !== 1) return null;
|
|
4633
|
-
return matches[0];
|
|
4634
|
-
}
|
|
4635
|
-
};
|
|
4636
|
-
return helpers.findUniqueNode();
|
|
4637
|
-
},
|
|
4638
|
-
{
|
|
4639
|
-
nodeId: binding.nodeId,
|
|
4640
|
-
nodeAttr: OS_NODE_ID_ATTR
|
|
4641
|
-
}
|
|
4642
|
-
);
|
|
4643
|
-
const element = handle.asElement();
|
|
4644
|
-
if (!element) {
|
|
4645
|
-
await handle.dispose();
|
|
4646
|
-
throw new CounterResolutionError(
|
|
4647
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4648
|
-
`Counter ${counter} became stale. Run snapshot() again.`
|
|
4649
|
-
);
|
|
4650
|
-
}
|
|
4651
|
-
return element;
|
|
4652
|
-
}
|
|
4653
|
-
async function resolveCountersBatch(page, snapshot, requests) {
|
|
4654
|
-
const out = {};
|
|
4655
|
-
if (!requests.length) return out;
|
|
4656
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
4657
|
-
for (const request of requests) {
|
|
4658
|
-
const binding = readBinding(snapshot, request.counter);
|
|
4659
|
-
const list = grouped.get(binding.frameToken) || [];
|
|
4660
|
-
list.push({
|
|
4661
|
-
...request,
|
|
4662
|
-
...binding
|
|
4663
|
-
});
|
|
4664
|
-
grouped.set(binding.frameToken, list);
|
|
4665
|
-
}
|
|
4666
|
-
const framesByToken = await mapFramesByToken(page);
|
|
4667
|
-
for (const [frameToken, entries] of grouped.entries()) {
|
|
4668
|
-
const frame = framesByToken.get(frameToken);
|
|
4669
|
-
if (!frame) {
|
|
4670
|
-
throw new CounterResolutionError(
|
|
4671
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
4672
|
-
`Counter frame ${frameToken} is unavailable. Run snapshot() again.`
|
|
4673
|
-
);
|
|
4674
|
-
}
|
|
4675
|
-
const result = await frame.evaluate(
|
|
4676
|
-
({
|
|
4677
|
-
entries: entries2,
|
|
4678
|
-
nodeAttr,
|
|
4679
|
-
instanceTokenKey,
|
|
4680
|
-
counterOwnerKey,
|
|
4681
|
-
counterValueKey
|
|
4682
|
-
}) => {
|
|
4683
|
-
const values = [];
|
|
4684
|
-
const failures = [];
|
|
4685
|
-
const helpers = {
|
|
4686
|
-
walk(map, root) {
|
|
4687
|
-
const children = Array.from(root.children);
|
|
4688
|
-
for (const child of children) {
|
|
4689
|
-
const id = child.getAttribute(nodeAttr);
|
|
4690
|
-
if (id) {
|
|
4691
|
-
const list = map.get(id) || [];
|
|
4692
|
-
list.push(child);
|
|
4693
|
-
map.set(id, list);
|
|
4694
|
-
}
|
|
4695
|
-
helpers.walk(map, child);
|
|
4696
|
-
if (child.shadowRoot) {
|
|
4697
|
-
helpers.walk(map, child.shadowRoot);
|
|
4698
|
-
}
|
|
4369
|
+
walk(child.shadowRoot);
|
|
4699
4370
|
}
|
|
4700
|
-
},
|
|
4701
|
-
buildNodeIndex() {
|
|
4702
|
-
const map = /* @__PURE__ */ new Map();
|
|
4703
|
-
helpers.walk(map, document);
|
|
4704
|
-
return map;
|
|
4705
|
-
},
|
|
4706
|
-
readRawValue(element, attribute) {
|
|
4707
|
-
if (attribute) {
|
|
4708
|
-
return element.getAttribute(attribute);
|
|
4709
|
-
}
|
|
4710
|
-
return element.textContent;
|
|
4711
|
-
}
|
|
4712
|
-
};
|
|
4713
|
-
const index = helpers.buildNodeIndex();
|
|
4714
|
-
for (const entry of entries2) {
|
|
4715
|
-
const matches = index.get(entry.nodeId) || [];
|
|
4716
|
-
if (!matches.length) {
|
|
4717
|
-
failures.push({
|
|
4718
|
-
nodeId: entry.nodeId,
|
|
4719
|
-
reason: "missing"
|
|
4720
|
-
});
|
|
4721
|
-
continue;
|
|
4722
|
-
}
|
|
4723
|
-
if (matches.length !== 1) {
|
|
4724
|
-
failures.push({
|
|
4725
|
-
nodeId: entry.nodeId,
|
|
4726
|
-
reason: "ambiguous"
|
|
4727
|
-
});
|
|
4728
|
-
continue;
|
|
4729
|
-
}
|
|
4730
|
-
const target = matches[0];
|
|
4731
|
-
if (target[instanceTokenKey] !== entry.instanceToken || target[counterOwnerKey] !== true || Number(target[counterValueKey] || 0) !== entry.counter || target.getAttribute("c") !== String(entry.counter)) {
|
|
4732
|
-
failures.push({
|
|
4733
|
-
nodeId: entry.nodeId,
|
|
4734
|
-
reason: "instance_mismatch"
|
|
4735
|
-
});
|
|
4736
|
-
continue;
|
|
4737
4371
|
}
|
|
4738
|
-
values.push({
|
|
4739
|
-
key: entry.key,
|
|
4740
|
-
value: helpers.readRawValue(target, entry.attribute)
|
|
4741
|
-
});
|
|
4742
|
-
}
|
|
4743
|
-
return {
|
|
4744
|
-
values,
|
|
4745
|
-
failures
|
|
4746
4372
|
};
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4751
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
4752
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4753
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
4754
|
-
}
|
|
4755
|
-
);
|
|
4756
|
-
if (result.failures.length) {
|
|
4757
|
-
const first = result.failures[0];
|
|
4758
|
-
throw buildCounterFailureError(first.nodeId, first.reason);
|
|
4759
|
-
}
|
|
4760
|
-
const attributeByKey = new Map(
|
|
4761
|
-
entries.map((entry) => [entry.key, entry.attribute])
|
|
4762
|
-
);
|
|
4763
|
-
for (const item of result.values) {
|
|
4764
|
-
out[item.key] = normalizeExtractedValue(
|
|
4765
|
-
item.value,
|
|
4766
|
-
attributeByKey.get(item.key)
|
|
4767
|
-
);
|
|
4373
|
+
walk(document);
|
|
4374
|
+
});
|
|
4375
|
+
} catch {
|
|
4768
4376
|
}
|
|
4769
4377
|
}
|
|
4770
|
-
return out;
|
|
4771
4378
|
}
|
|
4772
4379
|
async function mapFramesByToken(page) {
|
|
4773
4380
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -4789,180 +4396,6 @@ async function readFrameToken(frame) {
|
|
|
4789
4396
|
return null;
|
|
4790
4397
|
}
|
|
4791
4398
|
}
|
|
4792
|
-
async function readGlobalNextCounter(page) {
|
|
4793
|
-
const current = await page.mainFrame().evaluate((counterNextKey) => {
|
|
4794
|
-
const win = window;
|
|
4795
|
-
return Number(win[counterNextKey] || 0);
|
|
4796
|
-
}, OS_COUNTER_NEXT_KEY).catch(() => 0);
|
|
4797
|
-
if (Number.isFinite(current) && current > 0) {
|
|
4798
|
-
return current;
|
|
4799
|
-
}
|
|
4800
|
-
let max = 0;
|
|
4801
|
-
for (const frame of page.frames()) {
|
|
4802
|
-
try {
|
|
4803
|
-
const frameMax = await frame.evaluate(
|
|
4804
|
-
({ nodeAttr, counterOwnerKey, counterValueKey }) => {
|
|
4805
|
-
let localMax = 0;
|
|
4806
|
-
const helpers = {
|
|
4807
|
-
walk(root) {
|
|
4808
|
-
const children = Array.from(
|
|
4809
|
-
root.children
|
|
4810
|
-
);
|
|
4811
|
-
for (const child of children) {
|
|
4812
|
-
const candidate = child;
|
|
4813
|
-
const hasNodeId = child.hasAttribute(nodeAttr);
|
|
4814
|
-
const owned = candidate[counterOwnerKey] === true;
|
|
4815
|
-
if (hasNodeId && owned) {
|
|
4816
|
-
const value = Number(
|
|
4817
|
-
candidate[counterValueKey] || 0
|
|
4818
|
-
);
|
|
4819
|
-
if (Number.isFinite(value) && value > localMax) {
|
|
4820
|
-
localMax = value;
|
|
4821
|
-
}
|
|
4822
|
-
}
|
|
4823
|
-
helpers.walk(child);
|
|
4824
|
-
if (child.shadowRoot) {
|
|
4825
|
-
helpers.walk(child.shadowRoot);
|
|
4826
|
-
}
|
|
4827
|
-
}
|
|
4828
|
-
}
|
|
4829
|
-
};
|
|
4830
|
-
helpers.walk(document);
|
|
4831
|
-
return localMax;
|
|
4832
|
-
},
|
|
4833
|
-
{
|
|
4834
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
4835
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
4836
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
4837
|
-
}
|
|
4838
|
-
);
|
|
4839
|
-
if (frameMax > max) {
|
|
4840
|
-
max = frameMax;
|
|
4841
|
-
}
|
|
4842
|
-
} catch {
|
|
4843
|
-
}
|
|
4844
|
-
}
|
|
4845
|
-
const next = max + 1;
|
|
4846
|
-
await writeGlobalNextCounter(page, next);
|
|
4847
|
-
return next;
|
|
4848
|
-
}
|
|
4849
|
-
async function writeGlobalNextCounter(page, nextCounter) {
|
|
4850
|
-
await page.mainFrame().evaluate(
|
|
4851
|
-
({ counterNextKey, nextCounter: nextCounter2 }) => {
|
|
4852
|
-
const win = window;
|
|
4853
|
-
win[counterNextKey] = nextCounter2;
|
|
4854
|
-
},
|
|
4855
|
-
{
|
|
4856
|
-
counterNextKey: OS_COUNTER_NEXT_KEY,
|
|
4857
|
-
nextCounter
|
|
4858
|
-
}
|
|
4859
|
-
).catch(() => void 0);
|
|
4860
|
-
}
|
|
4861
|
-
function readBinding(snapshot, counter) {
|
|
4862
|
-
if (!snapshot.counterBindings) {
|
|
4863
|
-
throw new CounterResolutionError(
|
|
4864
|
-
"ERR_COUNTER_NOT_FOUND",
|
|
4865
|
-
`Counter ${counter} is unavailable because this snapshot has no counter bindings. Run snapshot() with counters first.`
|
|
4866
|
-
);
|
|
4867
|
-
}
|
|
4868
|
-
const binding = snapshot.counterBindings.get(counter);
|
|
4869
|
-
if (!binding) {
|
|
4870
|
-
throw new CounterResolutionError(
|
|
4871
|
-
"ERR_COUNTER_NOT_FOUND",
|
|
4872
|
-
`Counter ${counter} was not found in the current snapshot. Run snapshot() again.`
|
|
4873
|
-
);
|
|
4874
|
-
}
|
|
4875
|
-
if (binding.sessionId !== snapshot.snapshotSessionId) {
|
|
4876
|
-
throw new CounterResolutionError(
|
|
4877
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4878
|
-
`Counter ${counter} is stale for this snapshot session. Run snapshot() again.`
|
|
4879
|
-
);
|
|
4880
|
-
}
|
|
4881
|
-
return binding;
|
|
4882
|
-
}
|
|
4883
|
-
function buildCounterFailureError(nodeId, reason) {
|
|
4884
|
-
if (reason === "ambiguous") {
|
|
4885
|
-
return new CounterResolutionError(
|
|
4886
|
-
"ERR_COUNTER_AMBIGUOUS",
|
|
4887
|
-
`Counter target is ambiguous for node ${nodeId}. Run snapshot() again.`
|
|
4888
|
-
);
|
|
4889
|
-
}
|
|
4890
|
-
return new CounterResolutionError(
|
|
4891
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
4892
|
-
`Counter target is stale or missing for node ${nodeId}. Run snapshot() again.`
|
|
4893
|
-
);
|
|
4894
|
-
}
|
|
4895
|
-
|
|
4896
|
-
// src/html/pipeline.ts
|
|
4897
|
-
function applyCleaner(mode, html) {
|
|
4898
|
-
switch (mode) {
|
|
4899
|
-
case "clickable":
|
|
4900
|
-
return cleanForClickable(html);
|
|
4901
|
-
case "scrollable":
|
|
4902
|
-
return cleanForScrollable(html);
|
|
4903
|
-
case "extraction":
|
|
4904
|
-
return cleanForExtraction(html);
|
|
4905
|
-
case "full":
|
|
4906
|
-
return cleanForFull(html);
|
|
4907
|
-
case "action":
|
|
4908
|
-
default:
|
|
4909
|
-
return cleanForAction(html);
|
|
4910
|
-
}
|
|
4911
|
-
}
|
|
4912
|
-
async function assignCounters(page, html, nodePaths, nodeMeta, snapshotSessionId) {
|
|
4913
|
-
const $ = cheerio3.load(html, { xmlMode: false });
|
|
4914
|
-
const counterIndex = /* @__PURE__ */ new Map();
|
|
4915
|
-
const counterBindings = /* @__PURE__ */ new Map();
|
|
4916
|
-
const orderedNodeIds = [];
|
|
4917
|
-
$("*").each(function() {
|
|
4918
|
-
const el = $(this);
|
|
4919
|
-
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
4920
|
-
if (!nodeId) return;
|
|
4921
|
-
orderedNodeIds.push(nodeId);
|
|
4922
|
-
});
|
|
4923
|
-
const countersByNodeId = await ensureLiveCounters(
|
|
4924
|
-
page,
|
|
4925
|
-
nodeMeta,
|
|
4926
|
-
orderedNodeIds
|
|
4927
|
-
);
|
|
4928
|
-
$("*").each(function() {
|
|
4929
|
-
const el = $(this);
|
|
4930
|
-
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
4931
|
-
if (!nodeId) return;
|
|
4932
|
-
const path5 = nodePaths.get(nodeId);
|
|
4933
|
-
const meta = nodeMeta.get(nodeId);
|
|
4934
|
-
const counter = countersByNodeId.get(nodeId);
|
|
4935
|
-
if (counter == null || !Number.isFinite(counter)) {
|
|
4936
|
-
throw new Error(
|
|
4937
|
-
`Counter assignment failed for node ${nodeId}. Run snapshot() again.`
|
|
4938
|
-
);
|
|
4939
|
-
}
|
|
4940
|
-
if (counterBindings.has(counter) && counterBindings.get(counter)?.nodeId !== nodeId) {
|
|
4941
|
-
throw new Error(
|
|
4942
|
-
`Counter ${counter} was assigned to multiple nodes. Run snapshot() again.`
|
|
4943
|
-
);
|
|
4944
|
-
}
|
|
4945
|
-
el.attr("c", String(counter));
|
|
4946
|
-
el.removeAttr(OS_NODE_ID_ATTR);
|
|
4947
|
-
if (path5) {
|
|
4948
|
-
counterIndex.set(counter, cloneElementPath(path5));
|
|
4949
|
-
}
|
|
4950
|
-
if (meta) {
|
|
4951
|
-
counterBindings.set(counter, {
|
|
4952
|
-
sessionId: snapshotSessionId,
|
|
4953
|
-
frameToken: meta.frameToken,
|
|
4954
|
-
nodeId,
|
|
4955
|
-
instanceToken: meta.instanceToken
|
|
4956
|
-
});
|
|
4957
|
-
}
|
|
4958
|
-
});
|
|
4959
|
-
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
4960
|
-
return {
|
|
4961
|
-
html: $.html(),
|
|
4962
|
-
counterIndex,
|
|
4963
|
-
counterBindings
|
|
4964
|
-
};
|
|
4965
|
-
}
|
|
4966
4399
|
function stripNodeIds(html) {
|
|
4967
4400
|
if (!html.includes(OS_NODE_ID_ATTR)) return html;
|
|
4968
4401
|
const $ = cheerio3.load(html, { xmlMode: false });
|
|
@@ -4970,7 +4403,6 @@ function stripNodeIds(html) {
|
|
|
4970
4403
|
return $.html();
|
|
4971
4404
|
}
|
|
4972
4405
|
async function prepareSnapshot(page, options = {}) {
|
|
4973
|
-
const snapshotSessionId = (0, import_crypto.randomUUID)();
|
|
4974
4406
|
const mode = options.mode ?? "action";
|
|
4975
4407
|
const withCounters = options.withCounters ?? true;
|
|
4976
4408
|
const shouldMarkInteractive = options.markInteractive ?? true;
|
|
@@ -4983,18 +4415,15 @@ async function prepareSnapshot(page, options = {}) {
|
|
|
4983
4415
|
const reducedHtml = applyCleaner(mode, processedHtml);
|
|
4984
4416
|
let cleanedHtml = reducedHtml;
|
|
4985
4417
|
let counterIndex = null;
|
|
4986
|
-
let counterBindings = null;
|
|
4987
4418
|
if (withCounters) {
|
|
4988
4419
|
const counted = await assignCounters(
|
|
4989
4420
|
page,
|
|
4990
4421
|
reducedHtml,
|
|
4991
4422
|
serialized.nodePaths,
|
|
4992
|
-
serialized.nodeMeta
|
|
4993
|
-
snapshotSessionId
|
|
4423
|
+
serialized.nodeMeta
|
|
4994
4424
|
);
|
|
4995
4425
|
cleanedHtml = counted.html;
|
|
4996
4426
|
counterIndex = counted.counterIndex;
|
|
4997
|
-
counterBindings = counted.counterBindings;
|
|
4998
4427
|
} else {
|
|
4999
4428
|
cleanedHtml = stripNodeIds(cleanedHtml);
|
|
5000
4429
|
}
|
|
@@ -5003,15 +4432,13 @@ async function prepareSnapshot(page, options = {}) {
|
|
|
5003
4432
|
cleanedHtml = $unwrap("body").html()?.trim() || cleanedHtml;
|
|
5004
4433
|
}
|
|
5005
4434
|
return {
|
|
5006
|
-
snapshotSessionId,
|
|
5007
4435
|
mode,
|
|
5008
4436
|
url: page.url(),
|
|
5009
4437
|
rawHtml,
|
|
5010
4438
|
processedHtml,
|
|
5011
4439
|
reducedHtml,
|
|
5012
4440
|
cleanedHtml,
|
|
5013
|
-
counterIndex
|
|
5014
|
-
counterBindings
|
|
4441
|
+
counterIndex
|
|
5015
4442
|
};
|
|
5016
4443
|
}
|
|
5017
4444
|
|
|
@@ -5185,143 +4612,544 @@ function selectInRoot(root, selectors) {
|
|
|
5185
4612
|
mode: "unique"
|
|
5186
4613
|
};
|
|
5187
4614
|
}
|
|
5188
|
-
if (count > 1 && !fallback) {
|
|
5189
|
-
fallback = {
|
|
5190
|
-
selector,
|
|
5191
|
-
count,
|
|
5192
|
-
mode: "fallback"
|
|
5193
|
-
};
|
|
4615
|
+
if (count > 1 && !fallback) {
|
|
4616
|
+
fallback = {
|
|
4617
|
+
selector,
|
|
4618
|
+
count,
|
|
4619
|
+
mode: "fallback"
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
return fallback;
|
|
4624
|
+
}
|
|
4625
|
+
function countInDocument(selectors) {
|
|
4626
|
+
const out = [];
|
|
4627
|
+
for (const selector of selectors) {
|
|
4628
|
+
if (!selector) continue;
|
|
4629
|
+
let count = 0;
|
|
4630
|
+
try {
|
|
4631
|
+
count = document.querySelectorAll(selector).length;
|
|
4632
|
+
} catch {
|
|
4633
|
+
count = 0;
|
|
4634
|
+
}
|
|
4635
|
+
out.push({ selector, count });
|
|
4636
|
+
}
|
|
4637
|
+
return out;
|
|
4638
|
+
}
|
|
4639
|
+
function countInRoot(root, selectors) {
|
|
4640
|
+
if (!(root instanceof ShadowRoot)) return [];
|
|
4641
|
+
const out = [];
|
|
4642
|
+
for (const selector of selectors) {
|
|
4643
|
+
if (!selector) continue;
|
|
4644
|
+
let count = 0;
|
|
4645
|
+
try {
|
|
4646
|
+
count = root.querySelectorAll(selector).length;
|
|
4647
|
+
} catch {
|
|
4648
|
+
count = 0;
|
|
4649
|
+
}
|
|
4650
|
+
out.push({ selector, count });
|
|
4651
|
+
}
|
|
4652
|
+
return out;
|
|
4653
|
+
}
|
|
4654
|
+
function isPathDebugEnabled() {
|
|
4655
|
+
const value = process.env.OPENSTEER_DEBUG_PATH || process.env.OPENSTEER_DEBUG || process.env.DEBUG_SELECTORS;
|
|
4656
|
+
if (!value) return false;
|
|
4657
|
+
const normalized = value.trim().toLowerCase();
|
|
4658
|
+
return normalized === "1" || normalized === "true";
|
|
4659
|
+
}
|
|
4660
|
+
function debugPath(message, data) {
|
|
4661
|
+
if (!isPathDebugEnabled()) return;
|
|
4662
|
+
if (data !== void 0) {
|
|
4663
|
+
console.log(`[opensteer:path] ${message}`, data);
|
|
4664
|
+
} else {
|
|
4665
|
+
console.log(`[opensteer:path] ${message}`);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
async function disposeHandle(handle) {
|
|
4669
|
+
if (!handle) return;
|
|
4670
|
+
try {
|
|
4671
|
+
await handle.dispose();
|
|
4672
|
+
} catch {
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4676
|
+
// src/actions/actionability-probe.ts
|
|
4677
|
+
async function probeActionabilityState(element) {
|
|
4678
|
+
try {
|
|
4679
|
+
return await element.evaluate((target) => {
|
|
4680
|
+
if (!(target instanceof Element)) {
|
|
4681
|
+
return {
|
|
4682
|
+
connected: false,
|
|
4683
|
+
visible: null,
|
|
4684
|
+
enabled: null,
|
|
4685
|
+
editable: null,
|
|
4686
|
+
blocker: null
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
const connected = target.isConnected;
|
|
4690
|
+
if (!connected) {
|
|
4691
|
+
return {
|
|
4692
|
+
connected: false,
|
|
4693
|
+
visible: null,
|
|
4694
|
+
enabled: null,
|
|
4695
|
+
editable: null,
|
|
4696
|
+
blocker: null
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
const style = window.getComputedStyle(target);
|
|
4700
|
+
const rect = target.getBoundingClientRect();
|
|
4701
|
+
const hasBox = rect.width > 0 && rect.height > 0;
|
|
4702
|
+
const opacity = Number.parseFloat(style.opacity || "1");
|
|
4703
|
+
const isVisible = hasBox && style.display !== "none" && style.visibility !== "hidden" && style.visibility !== "collapse" && (!Number.isFinite(opacity) || opacity > 0);
|
|
4704
|
+
let enabled = null;
|
|
4705
|
+
if (target instanceof HTMLButtonElement || target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement || target instanceof HTMLOptionElement || target instanceof HTMLOptGroupElement || target instanceof HTMLFieldSetElement) {
|
|
4706
|
+
enabled = !target.disabled;
|
|
4707
|
+
}
|
|
4708
|
+
let editable = null;
|
|
4709
|
+
if (target instanceof HTMLInputElement) {
|
|
4710
|
+
editable = !target.readOnly && !target.disabled;
|
|
4711
|
+
} else if (target instanceof HTMLTextAreaElement) {
|
|
4712
|
+
editable = !target.readOnly && !target.disabled;
|
|
4713
|
+
} else if (target instanceof HTMLSelectElement) {
|
|
4714
|
+
editable = !target.disabled;
|
|
4715
|
+
} else if (target instanceof HTMLElement && target.isContentEditable) {
|
|
4716
|
+
editable = true;
|
|
4717
|
+
}
|
|
4718
|
+
let blocker = null;
|
|
4719
|
+
if (hasBox && window.innerWidth > 0 && window.innerHeight > 0) {
|
|
4720
|
+
const x = Math.min(
|
|
4721
|
+
Math.max(rect.left + rect.width / 2, 0),
|
|
4722
|
+
window.innerWidth - 1
|
|
4723
|
+
);
|
|
4724
|
+
const y = Math.min(
|
|
4725
|
+
Math.max(rect.top + rect.height / 2, 0),
|
|
4726
|
+
window.innerHeight - 1
|
|
4727
|
+
);
|
|
4728
|
+
const top = document.elementFromPoint(x, y);
|
|
4729
|
+
if (top && top !== target && !target.contains(top)) {
|
|
4730
|
+
const classes = String(top.className || "").split(/\s+/).map((value) => value.trim()).filter(Boolean).slice(0, 5);
|
|
4731
|
+
blocker = {
|
|
4732
|
+
tag: top.tagName.toLowerCase(),
|
|
4733
|
+
id: top.id || null,
|
|
4734
|
+
classes,
|
|
4735
|
+
role: top.getAttribute("role"),
|
|
4736
|
+
text: (top.textContent || "").trim().slice(0, 80) || null
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4740
|
+
return {
|
|
4741
|
+
connected,
|
|
4742
|
+
visible: isVisible,
|
|
4743
|
+
enabled,
|
|
4744
|
+
editable,
|
|
4745
|
+
blocker
|
|
4746
|
+
};
|
|
4747
|
+
});
|
|
4748
|
+
} catch {
|
|
4749
|
+
return null;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
// src/extract-value-normalization.ts
|
|
4754
|
+
var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
|
|
4755
|
+
function normalizeExtractedValue(raw, attribute) {
|
|
4756
|
+
if (raw == null) return null;
|
|
4757
|
+
const rawText = String(raw);
|
|
4758
|
+
if (!rawText.trim()) return null;
|
|
4759
|
+
const normalizedAttribute = String(attribute || "").trim().toLowerCase();
|
|
4760
|
+
if (URL_LIST_ATTRIBUTES.has(normalizedAttribute)) {
|
|
4761
|
+
const singleValue = pickSingleListAttributeValue(
|
|
4762
|
+
normalizedAttribute,
|
|
4763
|
+
rawText
|
|
4764
|
+
).trim();
|
|
4765
|
+
return singleValue || null;
|
|
4766
|
+
}
|
|
4767
|
+
const text = rawText.replace(/\s+/g, " ").trim();
|
|
4768
|
+
return text || null;
|
|
4769
|
+
}
|
|
4770
|
+
function pickSingleListAttributeValue(attribute, raw) {
|
|
4771
|
+
if (attribute === "ping") {
|
|
4772
|
+
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
4773
|
+
return firstUrl.trim();
|
|
4774
|
+
}
|
|
4775
|
+
if (attribute === "srcset" || attribute === "imagesrcset") {
|
|
4776
|
+
const picked = pickBestSrcsetCandidate(raw);
|
|
4777
|
+
if (picked) return picked;
|
|
4778
|
+
return pickFirstSrcsetToken(raw) || "";
|
|
4779
|
+
}
|
|
4780
|
+
return raw.trim();
|
|
4781
|
+
}
|
|
4782
|
+
function pickBestSrcsetCandidate(raw) {
|
|
4783
|
+
const candidates = parseSrcsetCandidates(raw);
|
|
4784
|
+
if (!candidates.length) return null;
|
|
4785
|
+
const widthCandidates = candidates.filter(
|
|
4786
|
+
(candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
|
|
4787
|
+
);
|
|
4788
|
+
if (widthCandidates.length) {
|
|
4789
|
+
return widthCandidates.reduce(
|
|
4790
|
+
(best, candidate) => candidate.width > best.width ? candidate : best
|
|
4791
|
+
).url;
|
|
4792
|
+
}
|
|
4793
|
+
const densityCandidates = candidates.filter(
|
|
4794
|
+
(candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
|
|
4795
|
+
);
|
|
4796
|
+
if (densityCandidates.length) {
|
|
4797
|
+
return densityCandidates.reduce(
|
|
4798
|
+
(best, candidate) => candidate.density > best.density ? candidate : best
|
|
4799
|
+
).url;
|
|
4800
|
+
}
|
|
4801
|
+
return candidates[0]?.url || null;
|
|
4802
|
+
}
|
|
4803
|
+
function parseSrcsetCandidates(raw) {
|
|
4804
|
+
const text = String(raw || "").trim();
|
|
4805
|
+
if (!text) return [];
|
|
4806
|
+
const out = [];
|
|
4807
|
+
let index = 0;
|
|
4808
|
+
while (index < text.length) {
|
|
4809
|
+
index = skipSeparators(text, index);
|
|
4810
|
+
if (index >= text.length) break;
|
|
4811
|
+
const urlToken = readUrlToken(text, index);
|
|
4812
|
+
index = urlToken.nextIndex;
|
|
4813
|
+
const url = urlToken.value.trim();
|
|
4814
|
+
if (!url) continue;
|
|
4815
|
+
index = skipWhitespace(text, index);
|
|
4816
|
+
const descriptors = [];
|
|
4817
|
+
while (index < text.length && text[index] !== ",") {
|
|
4818
|
+
const descriptorToken = readDescriptorToken(text, index);
|
|
4819
|
+
if (!descriptorToken.value) {
|
|
4820
|
+
index = descriptorToken.nextIndex;
|
|
4821
|
+
continue;
|
|
4822
|
+
}
|
|
4823
|
+
descriptors.push(descriptorToken.value);
|
|
4824
|
+
index = descriptorToken.nextIndex;
|
|
4825
|
+
index = skipWhitespace(text, index);
|
|
4826
|
+
}
|
|
4827
|
+
if (index < text.length && text[index] === ",") {
|
|
4828
|
+
index += 1;
|
|
4829
|
+
}
|
|
4830
|
+
let width = null;
|
|
4831
|
+
let density = null;
|
|
4832
|
+
for (const descriptor of descriptors) {
|
|
4833
|
+
const token = descriptor.trim().toLowerCase();
|
|
4834
|
+
if (!token) continue;
|
|
4835
|
+
const widthMatch = token.match(/^(\d+)w$/);
|
|
4836
|
+
if (widthMatch) {
|
|
4837
|
+
const parsed = Number.parseInt(widthMatch[1], 10);
|
|
4838
|
+
if (Number.isFinite(parsed)) {
|
|
4839
|
+
width = parsed;
|
|
4840
|
+
}
|
|
4841
|
+
continue;
|
|
4842
|
+
}
|
|
4843
|
+
const densityMatch = token.match(/^(\d*\.?\d+)x$/);
|
|
4844
|
+
if (densityMatch) {
|
|
4845
|
+
const parsed = Number.parseFloat(densityMatch[1]);
|
|
4846
|
+
if (Number.isFinite(parsed)) {
|
|
4847
|
+
density = parsed;
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
out.push({
|
|
4852
|
+
url,
|
|
4853
|
+
width,
|
|
4854
|
+
density
|
|
4855
|
+
});
|
|
4856
|
+
}
|
|
4857
|
+
return out;
|
|
4858
|
+
}
|
|
4859
|
+
function pickFirstSrcsetToken(raw) {
|
|
4860
|
+
const candidate = parseSrcsetCandidates(raw)[0];
|
|
4861
|
+
if (candidate?.url) {
|
|
4862
|
+
return candidate.url;
|
|
4863
|
+
}
|
|
4864
|
+
const text = String(raw || "");
|
|
4865
|
+
const start = skipSeparators(text, 0);
|
|
4866
|
+
if (start >= text.length) return null;
|
|
4867
|
+
const firstToken = readUrlToken(text, start).value.trim();
|
|
4868
|
+
return firstToken || null;
|
|
4869
|
+
}
|
|
4870
|
+
function skipWhitespace(value, index) {
|
|
4871
|
+
let cursor = index;
|
|
4872
|
+
while (cursor < value.length && /\s/.test(value[cursor])) {
|
|
4873
|
+
cursor += 1;
|
|
4874
|
+
}
|
|
4875
|
+
return cursor;
|
|
4876
|
+
}
|
|
4877
|
+
function skipSeparators(value, index) {
|
|
4878
|
+
let cursor = skipWhitespace(value, index);
|
|
4879
|
+
while (cursor < value.length && value[cursor] === ",") {
|
|
4880
|
+
cursor += 1;
|
|
4881
|
+
cursor = skipWhitespace(value, cursor);
|
|
4882
|
+
}
|
|
4883
|
+
return cursor;
|
|
4884
|
+
}
|
|
4885
|
+
function readUrlToken(value, index) {
|
|
4886
|
+
let cursor = index;
|
|
4887
|
+
let out = "";
|
|
4888
|
+
const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
|
|
4889
|
+
while (cursor < value.length) {
|
|
4890
|
+
const char = value[cursor];
|
|
4891
|
+
if (/\s/.test(char)) {
|
|
4892
|
+
break;
|
|
4893
|
+
}
|
|
4894
|
+
if (char === "," && !isDataUrl) {
|
|
4895
|
+
break;
|
|
4896
|
+
}
|
|
4897
|
+
out += char;
|
|
4898
|
+
cursor += 1;
|
|
4899
|
+
}
|
|
4900
|
+
if (isDataUrl && out.endsWith(",") && cursor < value.length) {
|
|
4901
|
+
out = out.slice(0, -1);
|
|
4902
|
+
}
|
|
4903
|
+
return {
|
|
4904
|
+
value: out,
|
|
4905
|
+
nextIndex: cursor
|
|
4906
|
+
};
|
|
4907
|
+
}
|
|
4908
|
+
function readDescriptorToken(value, index) {
|
|
4909
|
+
let cursor = skipWhitespace(value, index);
|
|
4910
|
+
let out = "";
|
|
4911
|
+
while (cursor < value.length) {
|
|
4912
|
+
const char = value[cursor];
|
|
4913
|
+
if (char === "," || /\s/.test(char)) {
|
|
4914
|
+
break;
|
|
4915
|
+
}
|
|
4916
|
+
out += char;
|
|
4917
|
+
cursor += 1;
|
|
4918
|
+
}
|
|
4919
|
+
return {
|
|
4920
|
+
value: out.trim(),
|
|
4921
|
+
nextIndex: cursor
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
|
|
4925
|
+
// src/html/counter-runtime.ts
|
|
4926
|
+
var CounterResolutionError = class extends Error {
|
|
4927
|
+
code;
|
|
4928
|
+
constructor(code, message) {
|
|
4929
|
+
super(message);
|
|
4930
|
+
this.name = "CounterResolutionError";
|
|
4931
|
+
this.code = code;
|
|
4932
|
+
}
|
|
4933
|
+
};
|
|
4934
|
+
async function resolveCounterElement(page, counter) {
|
|
4935
|
+
const normalized = normalizeCounter(counter);
|
|
4936
|
+
if (normalized == null) {
|
|
4937
|
+
throw buildCounterNotFoundError(counter);
|
|
4938
|
+
}
|
|
4939
|
+
const scan = await scanCounterOccurrences(page, [normalized]);
|
|
4940
|
+
const entry = scan.get(normalized);
|
|
4941
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
4942
|
+
throw buildCounterNotFoundError(counter);
|
|
4943
|
+
}
|
|
4944
|
+
if (entry.count > 1) {
|
|
4945
|
+
throw buildCounterAmbiguousError(counter);
|
|
4946
|
+
}
|
|
4947
|
+
const handle = await resolveUniqueHandleInFrame(entry.frame, normalized);
|
|
4948
|
+
const element = handle.asElement();
|
|
4949
|
+
if (!element) {
|
|
4950
|
+
await handle.dispose();
|
|
4951
|
+
throw buildCounterNotFoundError(counter);
|
|
4952
|
+
}
|
|
4953
|
+
return element;
|
|
4954
|
+
}
|
|
4955
|
+
async function resolveCountersBatch(page, requests) {
|
|
4956
|
+
const out = {};
|
|
4957
|
+
if (!requests.length) return out;
|
|
4958
|
+
const counters = dedupeCounters(requests);
|
|
4959
|
+
const scan = await scanCounterOccurrences(page, counters);
|
|
4960
|
+
for (const counter of counters) {
|
|
4961
|
+
const entry = scan.get(counter);
|
|
4962
|
+
if (entry.count > 1) {
|
|
4963
|
+
throw buildCounterAmbiguousError(counter);
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
const valueCache = /* @__PURE__ */ new Map();
|
|
4967
|
+
for (const request of requests) {
|
|
4968
|
+
const normalized = normalizeCounter(request.counter);
|
|
4969
|
+
if (normalized == null) {
|
|
4970
|
+
out[request.key] = null;
|
|
4971
|
+
continue;
|
|
4972
|
+
}
|
|
4973
|
+
const entry = scan.get(normalized);
|
|
4974
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
4975
|
+
out[request.key] = null;
|
|
4976
|
+
continue;
|
|
4977
|
+
}
|
|
4978
|
+
const cacheKey = `${normalized}:${request.attribute || ""}`;
|
|
4979
|
+
if (valueCache.has(cacheKey)) {
|
|
4980
|
+
out[request.key] = valueCache.get(cacheKey);
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4983
|
+
const read = await readCounterValueInFrame(
|
|
4984
|
+
entry.frame,
|
|
4985
|
+
normalized,
|
|
4986
|
+
request.attribute
|
|
4987
|
+
);
|
|
4988
|
+
if (read.status === "ambiguous") {
|
|
4989
|
+
throw buildCounterAmbiguousError(normalized);
|
|
4990
|
+
}
|
|
4991
|
+
if (read.status === "missing") {
|
|
4992
|
+
valueCache.set(cacheKey, null);
|
|
4993
|
+
out[request.key] = null;
|
|
4994
|
+
continue;
|
|
5194
4995
|
}
|
|
4996
|
+
const normalizedValue = normalizeExtractedValue(
|
|
4997
|
+
read.value ?? null,
|
|
4998
|
+
request.attribute
|
|
4999
|
+
);
|
|
5000
|
+
valueCache.set(cacheKey, normalizedValue);
|
|
5001
|
+
out[request.key] = normalizedValue;
|
|
5195
5002
|
}
|
|
5196
|
-
return
|
|
5003
|
+
return out;
|
|
5197
5004
|
}
|
|
5198
|
-
function
|
|
5005
|
+
function dedupeCounters(requests) {
|
|
5006
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5199
5007
|
const out = [];
|
|
5200
|
-
for (const
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
} catch {
|
|
5206
|
-
count = 0;
|
|
5207
|
-
}
|
|
5208
|
-
out.push({ selector, count });
|
|
5008
|
+
for (const request of requests) {
|
|
5009
|
+
const normalized = normalizeCounter(request.counter);
|
|
5010
|
+
if (normalized == null || seen.has(normalized)) continue;
|
|
5011
|
+
seen.add(normalized);
|
|
5012
|
+
out.push(normalized);
|
|
5209
5013
|
}
|
|
5210
5014
|
return out;
|
|
5211
5015
|
}
|
|
5212
|
-
function
|
|
5213
|
-
if (!(
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5016
|
+
function normalizeCounter(counter) {
|
|
5017
|
+
if (!Number.isFinite(counter)) return null;
|
|
5018
|
+
if (!Number.isInteger(counter)) return null;
|
|
5019
|
+
if (counter <= 0) return null;
|
|
5020
|
+
return counter;
|
|
5021
|
+
}
|
|
5022
|
+
async function scanCounterOccurrences(page, counters) {
|
|
5023
|
+
const out = /* @__PURE__ */ new Map();
|
|
5024
|
+
for (const counter of counters) {
|
|
5025
|
+
out.set(counter, {
|
|
5026
|
+
count: 0,
|
|
5027
|
+
frame: null
|
|
5028
|
+
});
|
|
5029
|
+
}
|
|
5030
|
+
if (!counters.length) return out;
|
|
5031
|
+
for (const frame of page.frames()) {
|
|
5032
|
+
let frameCounts;
|
|
5218
5033
|
try {
|
|
5219
|
-
|
|
5034
|
+
frameCounts = await frame.evaluate((candidates) => {
|
|
5035
|
+
const keys = new Set(candidates.map((value) => String(value)));
|
|
5036
|
+
const counts = {};
|
|
5037
|
+
for (const key of keys) {
|
|
5038
|
+
counts[key] = 0;
|
|
5039
|
+
}
|
|
5040
|
+
const walk = (root) => {
|
|
5041
|
+
const children = Array.from(root.children);
|
|
5042
|
+
for (const child of children) {
|
|
5043
|
+
const value = child.getAttribute("c");
|
|
5044
|
+
if (value && keys.has(value)) {
|
|
5045
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
5046
|
+
}
|
|
5047
|
+
walk(child);
|
|
5048
|
+
if (child.shadowRoot) {
|
|
5049
|
+
walk(child.shadowRoot);
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
};
|
|
5053
|
+
walk(document);
|
|
5054
|
+
return counts;
|
|
5055
|
+
}, counters);
|
|
5220
5056
|
} catch {
|
|
5221
|
-
|
|
5057
|
+
continue;
|
|
5058
|
+
}
|
|
5059
|
+
for (const [rawCounter, rawCount] of Object.entries(frameCounts)) {
|
|
5060
|
+
const counter = Number.parseInt(rawCounter, 10);
|
|
5061
|
+
if (!Number.isFinite(counter)) continue;
|
|
5062
|
+
const count = Number(rawCount || 0);
|
|
5063
|
+
if (!Number.isFinite(count) || count <= 0) continue;
|
|
5064
|
+
const entry = out.get(counter);
|
|
5065
|
+
entry.count += count;
|
|
5066
|
+
if (!entry.frame) {
|
|
5067
|
+
entry.frame = frame;
|
|
5068
|
+
}
|
|
5222
5069
|
}
|
|
5223
|
-
out.push({ selector, count });
|
|
5224
5070
|
}
|
|
5225
5071
|
return out;
|
|
5226
5072
|
}
|
|
5227
|
-
function
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5073
|
+
async function resolveUniqueHandleInFrame(frame, counter) {
|
|
5074
|
+
return frame.evaluateHandle((targetCounter) => {
|
|
5075
|
+
const matches = [];
|
|
5076
|
+
const walk = (root) => {
|
|
5077
|
+
const children = Array.from(root.children);
|
|
5078
|
+
for (const child of children) {
|
|
5079
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
5080
|
+
matches.push(child);
|
|
5081
|
+
}
|
|
5082
|
+
walk(child);
|
|
5083
|
+
if (child.shadowRoot) {
|
|
5084
|
+
walk(child.shadowRoot);
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
};
|
|
5088
|
+
walk(document);
|
|
5089
|
+
if (matches.length !== 1) {
|
|
5090
|
+
return null;
|
|
5091
|
+
}
|
|
5092
|
+
return matches[0];
|
|
5093
|
+
}, String(counter));
|
|
5247
5094
|
}
|
|
5248
|
-
|
|
5249
|
-
// src/actions/actionability-probe.ts
|
|
5250
|
-
async function probeActionabilityState(element) {
|
|
5095
|
+
async function readCounterValueInFrame(frame, counter, attribute) {
|
|
5251
5096
|
try {
|
|
5252
|
-
return await
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
visible: null,
|
|
5267
|
-
enabled: null,
|
|
5268
|
-
editable: null,
|
|
5269
|
-
blocker: null
|
|
5097
|
+
return await frame.evaluate(
|
|
5098
|
+
({ targetCounter, attribute: attribute2 }) => {
|
|
5099
|
+
const matches = [];
|
|
5100
|
+
const walk = (root) => {
|
|
5101
|
+
const children = Array.from(root.children);
|
|
5102
|
+
for (const child of children) {
|
|
5103
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
5104
|
+
matches.push(child);
|
|
5105
|
+
}
|
|
5106
|
+
walk(child);
|
|
5107
|
+
if (child.shadowRoot) {
|
|
5108
|
+
walk(child.shadowRoot);
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5270
5111
|
};
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
}
|
|
5281
|
-
let editable = null;
|
|
5282
|
-
if (target instanceof HTMLInputElement) {
|
|
5283
|
-
editable = !target.readOnly && !target.disabled;
|
|
5284
|
-
} else if (target instanceof HTMLTextAreaElement) {
|
|
5285
|
-
editable = !target.readOnly && !target.disabled;
|
|
5286
|
-
} else if (target instanceof HTMLSelectElement) {
|
|
5287
|
-
editable = !target.disabled;
|
|
5288
|
-
} else if (target instanceof HTMLElement && target.isContentEditable) {
|
|
5289
|
-
editable = true;
|
|
5290
|
-
}
|
|
5291
|
-
let blocker = null;
|
|
5292
|
-
if (hasBox && window.innerWidth > 0 && window.innerHeight > 0) {
|
|
5293
|
-
const x = Math.min(
|
|
5294
|
-
Math.max(rect.left + rect.width / 2, 0),
|
|
5295
|
-
window.innerWidth - 1
|
|
5296
|
-
);
|
|
5297
|
-
const y = Math.min(
|
|
5298
|
-
Math.max(rect.top + rect.height / 2, 0),
|
|
5299
|
-
window.innerHeight - 1
|
|
5300
|
-
);
|
|
5301
|
-
const top = document.elementFromPoint(x, y);
|
|
5302
|
-
if (top && top !== target && !target.contains(top)) {
|
|
5303
|
-
const classes = String(top.className || "").split(/\s+/).map((value) => value.trim()).filter(Boolean).slice(0, 5);
|
|
5304
|
-
blocker = {
|
|
5305
|
-
tag: top.tagName.toLowerCase(),
|
|
5306
|
-
id: top.id || null,
|
|
5307
|
-
classes,
|
|
5308
|
-
role: top.getAttribute("role"),
|
|
5309
|
-
text: (top.textContent || "").trim().slice(0, 80) || null
|
|
5112
|
+
walk(document);
|
|
5113
|
+
if (!matches.length) {
|
|
5114
|
+
return {
|
|
5115
|
+
status: "missing"
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
if (matches.length > 1) {
|
|
5119
|
+
return {
|
|
5120
|
+
status: "ambiguous"
|
|
5310
5121
|
};
|
|
5311
5122
|
}
|
|
5123
|
+
const target = matches[0];
|
|
5124
|
+
const value = attribute2 ? target.getAttribute(attribute2) : target.textContent;
|
|
5125
|
+
return {
|
|
5126
|
+
status: "ok",
|
|
5127
|
+
value
|
|
5128
|
+
};
|
|
5129
|
+
},
|
|
5130
|
+
{
|
|
5131
|
+
targetCounter: String(counter),
|
|
5132
|
+
attribute
|
|
5312
5133
|
}
|
|
5313
|
-
|
|
5314
|
-
connected,
|
|
5315
|
-
visible: isVisible,
|
|
5316
|
-
enabled,
|
|
5317
|
-
editable,
|
|
5318
|
-
blocker
|
|
5319
|
-
};
|
|
5320
|
-
});
|
|
5134
|
+
);
|
|
5321
5135
|
} catch {
|
|
5322
|
-
return
|
|
5136
|
+
return {
|
|
5137
|
+
status: "missing"
|
|
5138
|
+
};
|
|
5323
5139
|
}
|
|
5324
5140
|
}
|
|
5141
|
+
function buildCounterNotFoundError(counter) {
|
|
5142
|
+
return new CounterResolutionError(
|
|
5143
|
+
"ERR_COUNTER_NOT_FOUND",
|
|
5144
|
+
`Counter ${counter} was not found in the live DOM.`
|
|
5145
|
+
);
|
|
5146
|
+
}
|
|
5147
|
+
function buildCounterAmbiguousError(counter) {
|
|
5148
|
+
return new CounterResolutionError(
|
|
5149
|
+
"ERR_COUNTER_AMBIGUOUS",
|
|
5150
|
+
`Counter ${counter} matches multiple live elements.`
|
|
5151
|
+
);
|
|
5152
|
+
}
|
|
5325
5153
|
|
|
5326
5154
|
// src/actions/failure-classifier.ts
|
|
5327
5155
|
var ACTION_FAILURE_CODES = [
|
|
@@ -5437,13 +5265,6 @@ function classifyTypedError(error) {
|
|
|
5437
5265
|
classificationSource: "typed_error"
|
|
5438
5266
|
});
|
|
5439
5267
|
}
|
|
5440
|
-
if (error.code === "ERR_COUNTER_FRAME_UNAVAILABLE") {
|
|
5441
|
-
return buildFailure({
|
|
5442
|
-
code: "TARGET_UNAVAILABLE",
|
|
5443
|
-
message: error.message,
|
|
5444
|
-
classificationSource: "typed_error"
|
|
5445
|
-
});
|
|
5446
|
-
}
|
|
5447
5268
|
if (error.code === "ERR_COUNTER_AMBIGUOUS") {
|
|
5448
5269
|
return buildFailure({
|
|
5449
5270
|
code: "TARGET_AMBIGUOUS",
|
|
@@ -5451,13 +5272,6 @@ function classifyTypedError(error) {
|
|
|
5451
5272
|
classificationSource: "typed_error"
|
|
5452
5273
|
});
|
|
5453
5274
|
}
|
|
5454
|
-
if (error.code === "ERR_COUNTER_STALE_OR_NOT_FOUND") {
|
|
5455
|
-
return buildFailure({
|
|
5456
|
-
code: "TARGET_STALE",
|
|
5457
|
-
message: error.message,
|
|
5458
|
-
classificationSource: "typed_error"
|
|
5459
|
-
});
|
|
5460
|
-
}
|
|
5461
5275
|
}
|
|
5462
5276
|
return null;
|
|
5463
5277
|
}
|
|
@@ -10721,7 +10535,7 @@ var Opensteer = class _Opensteer {
|
|
|
10721
10535
|
let persistPath = null;
|
|
10722
10536
|
try {
|
|
10723
10537
|
if (storageKey && resolution.shouldPersist) {
|
|
10724
|
-
persistPath = await this.
|
|
10538
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10725
10539
|
handle,
|
|
10726
10540
|
"hover",
|
|
10727
10541
|
resolution.counter
|
|
@@ -10820,7 +10634,7 @@ var Opensteer = class _Opensteer {
|
|
|
10820
10634
|
let persistPath = null;
|
|
10821
10635
|
try {
|
|
10822
10636
|
if (storageKey && resolution.shouldPersist) {
|
|
10823
|
-
persistPath = await this.
|
|
10637
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10824
10638
|
handle,
|
|
10825
10639
|
"input",
|
|
10826
10640
|
resolution.counter
|
|
@@ -10923,7 +10737,7 @@ var Opensteer = class _Opensteer {
|
|
|
10923
10737
|
let persistPath = null;
|
|
10924
10738
|
try {
|
|
10925
10739
|
if (storageKey && resolution.shouldPersist) {
|
|
10926
|
-
persistPath = await this.
|
|
10740
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10927
10741
|
handle,
|
|
10928
10742
|
"select",
|
|
10929
10743
|
resolution.counter
|
|
@@ -11033,7 +10847,7 @@ var Opensteer = class _Opensteer {
|
|
|
11033
10847
|
let persistPath = null;
|
|
11034
10848
|
try {
|
|
11035
10849
|
if (storageKey && resolution.shouldPersist) {
|
|
11036
|
-
persistPath = await this.
|
|
10850
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11037
10851
|
handle,
|
|
11038
10852
|
"scroll",
|
|
11039
10853
|
resolution.counter
|
|
@@ -11329,17 +11143,19 @@ var Opensteer = class _Opensteer {
|
|
|
11329
11143
|
const handle = await this.resolveCounterHandle(resolution.counter);
|
|
11330
11144
|
try {
|
|
11331
11145
|
if (storageKey && resolution.shouldPersist) {
|
|
11332
|
-
const persistPath = await this.
|
|
11146
|
+
const persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11333
11147
|
handle,
|
|
11334
11148
|
method,
|
|
11335
11149
|
resolution.counter
|
|
11336
11150
|
);
|
|
11337
|
-
|
|
11338
|
-
|
|
11339
|
-
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11151
|
+
if (persistPath) {
|
|
11152
|
+
this.persistPath(
|
|
11153
|
+
storageKey,
|
|
11154
|
+
method,
|
|
11155
|
+
options.description,
|
|
11156
|
+
persistPath
|
|
11157
|
+
);
|
|
11158
|
+
}
|
|
11343
11159
|
}
|
|
11344
11160
|
return await counterFn(handle);
|
|
11345
11161
|
} catch (err) {
|
|
@@ -11377,7 +11193,7 @@ var Opensteer = class _Opensteer {
|
|
|
11377
11193
|
let persistPath = null;
|
|
11378
11194
|
try {
|
|
11379
11195
|
if (storageKey && resolution.shouldPersist) {
|
|
11380
|
-
persistPath = await this.
|
|
11196
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11381
11197
|
handle,
|
|
11382
11198
|
"uploadFile",
|
|
11383
11199
|
resolution.counter
|
|
@@ -11653,7 +11469,7 @@ var Opensteer = class _Opensteer {
|
|
|
11653
11469
|
let persistPath = null;
|
|
11654
11470
|
try {
|
|
11655
11471
|
if (storageKey && resolution.shouldPersist) {
|
|
11656
|
-
persistPath = await this.
|
|
11472
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11657
11473
|
handle,
|
|
11658
11474
|
"click",
|
|
11659
11475
|
resolution.counter
|
|
@@ -11749,17 +11565,6 @@ var Opensteer = class _Opensteer {
|
|
|
11749
11565
|
}
|
|
11750
11566
|
}
|
|
11751
11567
|
if (options.element != null) {
|
|
11752
|
-
const pathFromElement = await this.tryBuildPathFromCounter(
|
|
11753
|
-
options.element
|
|
11754
|
-
);
|
|
11755
|
-
if (pathFromElement) {
|
|
11756
|
-
return {
|
|
11757
|
-
path: pathFromElement,
|
|
11758
|
-
counter: null,
|
|
11759
|
-
shouldPersist: Boolean(storageKey),
|
|
11760
|
-
source: "element"
|
|
11761
|
-
};
|
|
11762
|
-
}
|
|
11763
11568
|
return {
|
|
11764
11569
|
path: null,
|
|
11765
11570
|
counter: options.element,
|
|
@@ -11787,17 +11592,6 @@ var Opensteer = class _Opensteer {
|
|
|
11787
11592
|
options.description
|
|
11788
11593
|
);
|
|
11789
11594
|
if (resolved?.counter != null) {
|
|
11790
|
-
const pathFromAiCounter = await this.tryBuildPathFromCounter(
|
|
11791
|
-
resolved.counter
|
|
11792
|
-
);
|
|
11793
|
-
if (pathFromAiCounter) {
|
|
11794
|
-
return {
|
|
11795
|
-
path: pathFromAiCounter,
|
|
11796
|
-
counter: null,
|
|
11797
|
-
shouldPersist: Boolean(storageKey),
|
|
11798
|
-
source: "ai"
|
|
11799
|
-
};
|
|
11800
|
-
}
|
|
11801
11595
|
return {
|
|
11802
11596
|
path: null,
|
|
11803
11597
|
counter: resolved.counter,
|
|
@@ -11879,23 +11673,22 @@ var Opensteer = class _Opensteer {
|
|
|
11879
11673
|
try {
|
|
11880
11674
|
const builtPath = await buildElementPathFromHandle(handle);
|
|
11881
11675
|
if (builtPath) {
|
|
11882
|
-
|
|
11676
|
+
const withFrameContext = await this.withHandleIframeContext(
|
|
11677
|
+
handle,
|
|
11678
|
+
builtPath
|
|
11679
|
+
);
|
|
11680
|
+
return this.withIndexedIframeContext(
|
|
11681
|
+
withFrameContext,
|
|
11682
|
+
indexedPath
|
|
11683
|
+
);
|
|
11883
11684
|
}
|
|
11884
11685
|
return indexedPath;
|
|
11885
11686
|
} finally {
|
|
11886
11687
|
await handle.dispose();
|
|
11887
11688
|
}
|
|
11888
11689
|
}
|
|
11889
|
-
async tryBuildPathFromCounter(counter) {
|
|
11890
|
-
try {
|
|
11891
|
-
return await this.buildPathFromElement(counter);
|
|
11892
|
-
} catch {
|
|
11893
|
-
return null;
|
|
11894
|
-
}
|
|
11895
|
-
}
|
|
11896
11690
|
async resolveCounterHandle(element) {
|
|
11897
|
-
|
|
11898
|
-
return resolveCounterElement(this.page, snapshot, element);
|
|
11691
|
+
return resolveCounterElement(this.page, element);
|
|
11899
11692
|
}
|
|
11900
11693
|
async resolveCounterHandleForAction(action, description, element) {
|
|
11901
11694
|
try {
|
|
@@ -11919,8 +11712,12 @@ var Opensteer = class _Opensteer {
|
|
|
11919
11712
|
const indexedPath = await this.readPathFromCounterIndex(counter);
|
|
11920
11713
|
const builtPath = await buildElementPathFromHandle(handle);
|
|
11921
11714
|
if (builtPath) {
|
|
11715
|
+
const withFrameContext = await this.withHandleIframeContext(
|
|
11716
|
+
handle,
|
|
11717
|
+
builtPath
|
|
11718
|
+
);
|
|
11922
11719
|
const normalized = this.withIndexedIframeContext(
|
|
11923
|
-
|
|
11720
|
+
withFrameContext,
|
|
11924
11721
|
indexedPath
|
|
11925
11722
|
);
|
|
11926
11723
|
if (normalized.nodes.length) return normalized;
|
|
@@ -11930,15 +11727,34 @@ var Opensteer = class _Opensteer {
|
|
|
11930
11727
|
`Unable to build element path from counter ${counter} during ${action}.`
|
|
11931
11728
|
);
|
|
11932
11729
|
}
|
|
11730
|
+
async tryBuildPathFromResolvedHandle(handle, action, counter) {
|
|
11731
|
+
try {
|
|
11732
|
+
return await this.buildPathFromResolvedHandle(handle, action, counter);
|
|
11733
|
+
} catch (error) {
|
|
11734
|
+
this.logDebugError(
|
|
11735
|
+
`path persistence skipped for ${action} counter ${counter}`,
|
|
11736
|
+
error
|
|
11737
|
+
);
|
|
11738
|
+
return null;
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11933
11741
|
withIndexedIframeContext(builtPath, indexedPath) {
|
|
11934
11742
|
const normalizedBuilt = this.normalizePath(builtPath);
|
|
11935
11743
|
if (!indexedPath) return normalizedBuilt;
|
|
11936
11744
|
const iframePrefix = collectIframeContextPrefix(indexedPath);
|
|
11937
11745
|
if (!iframePrefix.length) return normalizedBuilt;
|
|
11746
|
+
const builtContext = cloneContextHops(normalizedBuilt.context);
|
|
11747
|
+
const overlap = measureContextOverlap(iframePrefix, builtContext);
|
|
11748
|
+
const missingPrefix = cloneContextHops(
|
|
11749
|
+
iframePrefix.slice(0, iframePrefix.length - overlap)
|
|
11750
|
+
);
|
|
11751
|
+
if (!missingPrefix.length) {
|
|
11752
|
+
return normalizedBuilt;
|
|
11753
|
+
}
|
|
11938
11754
|
const merged = {
|
|
11939
11755
|
context: [
|
|
11940
|
-
...
|
|
11941
|
-
...
|
|
11756
|
+
...missingPrefix,
|
|
11757
|
+
...builtContext
|
|
11942
11758
|
],
|
|
11943
11759
|
nodes: cloneElementPath(normalizedBuilt).nodes
|
|
11944
11760
|
};
|
|
@@ -11948,9 +11764,48 @@ var Opensteer = class _Opensteer {
|
|
|
11948
11764
|
if (fallback.nodes.length) return fallback;
|
|
11949
11765
|
return normalizedBuilt;
|
|
11950
11766
|
}
|
|
11767
|
+
async withHandleIframeContext(handle, path5) {
|
|
11768
|
+
const ownFrame = await handle.ownerFrame();
|
|
11769
|
+
if (!ownFrame) {
|
|
11770
|
+
return this.normalizePath(path5);
|
|
11771
|
+
}
|
|
11772
|
+
let frame = ownFrame;
|
|
11773
|
+
let prefix2 = [];
|
|
11774
|
+
while (frame && frame !== this.page.mainFrame()) {
|
|
11775
|
+
const parent = frame.parentFrame();
|
|
11776
|
+
if (!parent) break;
|
|
11777
|
+
const frameElement = await frame.frameElement().catch(() => null);
|
|
11778
|
+
if (!frameElement) break;
|
|
11779
|
+
try {
|
|
11780
|
+
const frameElementPath = await buildElementPathFromHandle(frameElement);
|
|
11781
|
+
if (frameElementPath?.nodes.length) {
|
|
11782
|
+
const segment = [
|
|
11783
|
+
...cloneContextHops(frameElementPath.context),
|
|
11784
|
+
{
|
|
11785
|
+
kind: "iframe",
|
|
11786
|
+
host: cloneElementPath(frameElementPath).nodes
|
|
11787
|
+
}
|
|
11788
|
+
];
|
|
11789
|
+
prefix2 = [...segment, ...prefix2];
|
|
11790
|
+
}
|
|
11791
|
+
} finally {
|
|
11792
|
+
await frameElement.dispose().catch(() => void 0);
|
|
11793
|
+
}
|
|
11794
|
+
frame = parent;
|
|
11795
|
+
}
|
|
11796
|
+
if (!prefix2.length) {
|
|
11797
|
+
return this.normalizePath(path5);
|
|
11798
|
+
}
|
|
11799
|
+
return this.normalizePath({
|
|
11800
|
+
context: [...prefix2, ...cloneContextHops(path5.context)],
|
|
11801
|
+
nodes: cloneElementPath(path5).nodes
|
|
11802
|
+
});
|
|
11803
|
+
}
|
|
11951
11804
|
async readPathFromCounterIndex(counter) {
|
|
11952
|
-
|
|
11953
|
-
|
|
11805
|
+
if (!this.snapshotCache || this.snapshotCache.url !== this.page.url() || !this.snapshotCache.counterIndex) {
|
|
11806
|
+
return null;
|
|
11807
|
+
}
|
|
11808
|
+
const indexed = this.snapshotCache.counterIndex.get(counter);
|
|
11954
11809
|
if (!indexed) return null;
|
|
11955
11810
|
const normalized = this.normalizePath(indexed);
|
|
11956
11811
|
if (!normalized.nodes.length) return null;
|
|
@@ -11961,15 +11816,6 @@ var Opensteer = class _Opensteer {
|
|
|
11961
11816
|
if (!path5) return null;
|
|
11962
11817
|
return this.normalizePath(path5);
|
|
11963
11818
|
}
|
|
11964
|
-
async ensureSnapshotWithCounters() {
|
|
11965
|
-
if (!this.snapshotCache || !this.snapshotCache.counterBindings || this.snapshotCache.url !== this.page.url()) {
|
|
11966
|
-
await this.snapshot({
|
|
11967
|
-
mode: "full",
|
|
11968
|
-
withCounters: true
|
|
11969
|
-
});
|
|
11970
|
-
}
|
|
11971
|
-
return this.snapshotCache;
|
|
11972
|
-
}
|
|
11973
11819
|
persistPath(id, method, description, path5) {
|
|
11974
11820
|
const now = Date.now();
|
|
11975
11821
|
const safeFile = this.storage.getSelectorFileName(id);
|
|
@@ -12219,17 +12065,6 @@ var Opensteer = class _Opensteer {
|
|
|
12219
12065
|
return;
|
|
12220
12066
|
}
|
|
12221
12067
|
if (normalized.element != null) {
|
|
12222
|
-
const path5 = await this.tryBuildPathFromCounter(
|
|
12223
|
-
normalized.element
|
|
12224
|
-
);
|
|
12225
|
-
if (path5) {
|
|
12226
|
-
fields.push({
|
|
12227
|
-
key: fieldKey,
|
|
12228
|
-
path: path5,
|
|
12229
|
-
attribute: normalized.attribute
|
|
12230
|
-
});
|
|
12231
|
-
return;
|
|
12232
|
-
}
|
|
12233
12068
|
fields.push({
|
|
12234
12069
|
key: fieldKey,
|
|
12235
12070
|
counter: normalized.element,
|
|
@@ -12274,15 +12109,6 @@ var Opensteer = class _Opensteer {
|
|
|
12274
12109
|
continue;
|
|
12275
12110
|
}
|
|
12276
12111
|
if (fieldPlan.element != null) {
|
|
12277
|
-
const path6 = await this.tryBuildPathFromCounter(fieldPlan.element);
|
|
12278
|
-
if (path6) {
|
|
12279
|
-
fields.push({
|
|
12280
|
-
key,
|
|
12281
|
-
path: path6,
|
|
12282
|
-
attribute: fieldPlan.attribute
|
|
12283
|
-
});
|
|
12284
|
-
continue;
|
|
12285
|
-
}
|
|
12286
12112
|
fields.push({
|
|
12287
12113
|
key,
|
|
12288
12114
|
counter: fieldPlan.element,
|
|
@@ -12337,12 +12163,7 @@ var Opensteer = class _Opensteer {
|
|
|
12337
12163
|
}
|
|
12338
12164
|
}
|
|
12339
12165
|
if (counterRequests.length) {
|
|
12340
|
-
const
|
|
12341
|
-
const counterValues = await resolveCountersBatch(
|
|
12342
|
-
this.page,
|
|
12343
|
-
snapshot,
|
|
12344
|
-
counterRequests
|
|
12345
|
-
);
|
|
12166
|
+
const counterValues = await resolveCountersBatch(this.page, counterRequests);
|
|
12346
12167
|
Object.assign(result, counterValues);
|
|
12347
12168
|
}
|
|
12348
12169
|
if (pathFields.length) {
|
|
@@ -12372,7 +12193,7 @@ var Opensteer = class _Opensteer {
|
|
|
12372
12193
|
const path5 = await this.buildPathFromElement(field.counter);
|
|
12373
12194
|
if (!path5) {
|
|
12374
12195
|
throw new Error(
|
|
12375
|
-
`Unable to
|
|
12196
|
+
`Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
|
|
12376
12197
|
);
|
|
12377
12198
|
}
|
|
12378
12199
|
resolved.push({
|
|
@@ -12394,7 +12215,7 @@ var Opensteer = class _Opensteer {
|
|
|
12394
12215
|
}
|
|
12395
12216
|
resolveStorageKey(description) {
|
|
12396
12217
|
if (!description) return null;
|
|
12397
|
-
return (0,
|
|
12218
|
+
return (0, import_crypto.createHash)("sha256").update(description).digest("hex").slice(0, 16);
|
|
12398
12219
|
}
|
|
12399
12220
|
normalizePath(path5) {
|
|
12400
12221
|
return sanitizeElementPath(path5);
|
|
@@ -12418,6 +12239,33 @@ function collectIframeContextPrefix(path5) {
|
|
|
12418
12239
|
if (lastIframeIndex < 0) return [];
|
|
12419
12240
|
return cloneContextHops(context.slice(0, lastIframeIndex + 1));
|
|
12420
12241
|
}
|
|
12242
|
+
function measureContextOverlap(indexedPrefix, builtContext) {
|
|
12243
|
+
const maxOverlap = Math.min(indexedPrefix.length, builtContext.length);
|
|
12244
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
12245
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, true)) {
|
|
12246
|
+
return size;
|
|
12247
|
+
}
|
|
12248
|
+
}
|
|
12249
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
12250
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, false)) {
|
|
12251
|
+
return size;
|
|
12252
|
+
}
|
|
12253
|
+
}
|
|
12254
|
+
return 0;
|
|
12255
|
+
}
|
|
12256
|
+
function matchesContextPrefix(indexedPrefix, builtContext, size, strictHost) {
|
|
12257
|
+
for (let idx = 0; idx < size; idx += 1) {
|
|
12258
|
+
const left = indexedPrefix[indexedPrefix.length - size + idx];
|
|
12259
|
+
const right = builtContext[idx];
|
|
12260
|
+
if (left.kind !== right.kind) {
|
|
12261
|
+
return false;
|
|
12262
|
+
}
|
|
12263
|
+
if (strictHost && JSON.stringify(left.host) !== JSON.stringify(right.host)) {
|
|
12264
|
+
return false;
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
return true;
|
|
12268
|
+
}
|
|
12421
12269
|
function normalizeSchemaValue(value) {
|
|
12422
12270
|
if (!value) return null;
|
|
12423
12271
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -12439,7 +12287,7 @@ function normalizeExtractSource(source) {
|
|
|
12439
12287
|
}
|
|
12440
12288
|
function computeSchemaHash(schema) {
|
|
12441
12289
|
const stable = stableStringify(schema);
|
|
12442
|
-
return (0,
|
|
12290
|
+
return (0, import_crypto.createHash)("sha256").update(stable).digest("hex");
|
|
12443
12291
|
}
|
|
12444
12292
|
function buildPathMap(fields) {
|
|
12445
12293
|
const out = {};
|
|
@@ -12675,7 +12523,7 @@ function isInternalOrBlankPageUrl(url) {
|
|
|
12675
12523
|
}
|
|
12676
12524
|
function buildLocalRunId(namespace) {
|
|
12677
12525
|
const normalized = namespace.trim() || "default";
|
|
12678
|
-
return `${normalized}-${Date.now().toString(36)}-${(0,
|
|
12526
|
+
return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto.randomUUID)().slice(0, 8)}`;
|
|
12679
12527
|
}
|
|
12680
12528
|
|
|
12681
12529
|
// src/cli/paths.ts
|