surfsky 0.0.5 → 0.0.6
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/README.md +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +102 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,7 +99,7 @@ Cookies and browser state persist between leases. Handlers are not retried.
|
|
|
99
99
|
|
|
100
100
|
- [SDK API reference](https://github.com/surfskyio/surfsky-js/blob/main/docs/api.md)
|
|
101
101
|
- [REST API](https://docs.surfsky.io/api-reference)
|
|
102
|
-
- [Examples](https://github.com/surfskyio/surfsky-js/tree/main/examples): forms, tabs, retries, profiles and CDP connections.
|
|
102
|
+
- [Examples](https://github.com/surfskyio/surfsky-js/tree/main/examples): forms, tabs, retries, profiles, a closed shadow root and CDP connections.
|
|
103
103
|
|
|
104
104
|
`client.session()` exposes a WebSocket URL for external browser clients.
|
|
105
105
|
See the [Playwright](https://github.com/surfskyio/surfsky-js/blob/main/examples/playwright_connect.ts),
|
package/dist/index.d.ts
CHANGED
|
@@ -1370,6 +1370,12 @@ declare class Page extends Actions {
|
|
|
1370
1370
|
fill(selector: string, text: string): Promise<any>;
|
|
1371
1371
|
getAttribute(selector: string, name: string): Promise<string | null>;
|
|
1372
1372
|
count(selector: string): Promise<number>;
|
|
1373
|
+
boundingBox(selector: string): Promise<{
|
|
1374
|
+
x: number;
|
|
1375
|
+
y: number;
|
|
1376
|
+
width: number;
|
|
1377
|
+
height: number;
|
|
1378
|
+
} | null>;
|
|
1373
1379
|
selectOption(selector: string, option: string | {
|
|
1374
1380
|
value?: string;
|
|
1375
1381
|
label?: string;
|
package/dist/index.js
CHANGED
|
@@ -860,18 +860,26 @@ const FUNCTION = /^\s*(async\s+)?(function\b|\([^()]*\)\s*=>|[\w$]+\s*=>)/;
|
|
|
860
860
|
function asXPath(s) {
|
|
861
861
|
return /^\s*(xpath=|\/\/|\.\.)/.test(s) ? s.trim().replace(/^xpath=/, "") : void 0;
|
|
862
862
|
}
|
|
863
|
-
const SELECT = `(
|
|
864
|
-
const
|
|
865
|
-
if (!el) return null;
|
|
866
|
-
const option = Array.from(el.options).find(
|
|
863
|
+
const SELECT = `function (value, label) {
|
|
864
|
+
const option = Array.from(this.options).find(
|
|
867
865
|
o => label === null ? o.value === value : o.label === label || o.text === label
|
|
868
866
|
);
|
|
869
867
|
if (!option) return false;
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
868
|
+
this.value = option.value;
|
|
869
|
+
this.dispatchEvent(new Event("input", {bubbles: true}));
|
|
870
|
+
this.dispatchEvent(new Event("change", {bubbles: true}));
|
|
873
871
|
return option.value;
|
|
874
872
|
}`;
|
|
873
|
+
const INNER_TEXT = "function () { return this.innerText }";
|
|
874
|
+
const IS_FOCUSED = "function () { return this.getRootNode().activeElement === this }";
|
|
875
|
+
const MATCHES = "function (s) { return this.nodeType === 1 && (s == null || this.matches(s)) }";
|
|
876
|
+
const WORLD_GONE = ["Cannot find context", "does not belong to the document"];
|
|
877
|
+
const worldIsGone = (err) => err instanceof CDPError && WORLD_GONE.some((text) => err.message.includes(text));
|
|
878
|
+
function remoteResult(result, what) {
|
|
879
|
+
const details = result.exceptionDetails;
|
|
880
|
+
if (details) throw new CDPError(`${what} failed: ${(details.exception ?? {}).description ?? details.text}`);
|
|
881
|
+
return result.result;
|
|
882
|
+
}
|
|
875
883
|
function cookieParam(cookie) {
|
|
876
884
|
const out = dropNulls(cookie);
|
|
877
885
|
const expires = out.expirationDate;
|
|
@@ -1062,11 +1070,13 @@ var Page = class extends Actions {
|
|
|
1062
1070
|
}
|
|
1063
1071
|
/** @internal */
|
|
1064
1072
|
async _setup(waiting) {
|
|
1073
|
+
const patterns = this._browser._fetchPatterns;
|
|
1065
1074
|
const commands = [
|
|
1066
1075
|
["Page.enable", void 0],
|
|
1067
1076
|
["Page.setLifecycleEventsEnabled", { enabled: true }],
|
|
1068
|
-
["
|
|
1077
|
+
["Network.enable", void 0]
|
|
1069
1078
|
];
|
|
1079
|
+
if (patterns.length > 0) commands.push(["Fetch.enable", { patterns }]);
|
|
1070
1080
|
if (waiting) commands.push(["Runtime.runIfWaitingForDebugger", void 0]);
|
|
1071
1081
|
const timeout = this._browser.commandTimeout;
|
|
1072
1082
|
let posted = [];
|
|
@@ -1167,15 +1177,13 @@ var Page = class extends Actions {
|
|
|
1167
1177
|
/** Record responses whose URL contains a fragment. Call before navigating. */
|
|
1168
1178
|
async captureResponses(...fragments) {
|
|
1169
1179
|
if (fragments.length === 0) throw new TypeError("captureResponses needs at least one URL fragment");
|
|
1170
|
-
|
|
1180
|
+
this._requireOpen();
|
|
1171
1181
|
this._captures.push(...fragments);
|
|
1172
1182
|
}
|
|
1173
1183
|
async stopCapturing() {
|
|
1174
|
-
if (this._captures.length === 0) return;
|
|
1175
1184
|
this._captures = [];
|
|
1176
1185
|
this._responses = [];
|
|
1177
1186
|
this._inFlight.clear();
|
|
1178
|
-
await this.send("Network.disable");
|
|
1179
1187
|
}
|
|
1180
1188
|
async waitForResponse(fragment, options = {}) {
|
|
1181
1189
|
if (this._captures.length === 0) throw new Error("call captureResponses() before the navigation");
|
|
@@ -1191,6 +1199,7 @@ var Page = class extends Actions {
|
|
|
1191
1199
|
/** @internal */
|
|
1192
1200
|
_onResponse(event) {
|
|
1193
1201
|
const response = isRecord(event.response) ? event.response : {};
|
|
1202
|
+
if (event.type === "Document" && event.frameId === this._frameId) this._status = typeof response.status === "number" ? response.status : void 0;
|
|
1194
1203
|
const url = typeof response.url === "string" ? response.url : "";
|
|
1195
1204
|
if (this._captures.some((fragment) => url.includes(fragment))) this._inFlight.set(String(event.requestId), response);
|
|
1196
1205
|
}
|
|
@@ -1279,14 +1288,38 @@ var Page = class extends Actions {
|
|
|
1279
1288
|
try {
|
|
1280
1289
|
result = await this.send("Runtime.evaluate", params);
|
|
1281
1290
|
} catch (err) {
|
|
1282
|
-
if (!isolated || !(err
|
|
1291
|
+
if (!isolated || !worldIsGone(err)) throw err;
|
|
1283
1292
|
this._worldId = void 0;
|
|
1284
1293
|
params.contextId = await this.#world();
|
|
1285
1294
|
result = await this.send("Runtime.evaluate", params);
|
|
1286
1295
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1296
|
+
return remoteResult(result, "evaluate");
|
|
1297
|
+
}
|
|
1298
|
+
async #call(nodeId, fn, ...args) {
|
|
1299
|
+
const params = {
|
|
1300
|
+
functionDeclaration: fn,
|
|
1301
|
+
arguments: args.map((value) => ({ value })),
|
|
1302
|
+
returnByValue: true
|
|
1303
|
+
};
|
|
1304
|
+
let result;
|
|
1305
|
+
try {
|
|
1306
|
+
result = await this.#callInWorld(nodeId, params);
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
if (!worldIsGone(err)) throw err;
|
|
1309
|
+
this._worldId = void 0;
|
|
1310
|
+
result = await this.#callInWorld(nodeId, params);
|
|
1311
|
+
}
|
|
1312
|
+
return remoteResult(result, "call").value;
|
|
1313
|
+
}
|
|
1314
|
+
async #callInWorld(nodeId, params) {
|
|
1315
|
+
const resolved = await this.send("DOM.resolveNode", {
|
|
1316
|
+
nodeId,
|
|
1317
|
+
executionContextId: await this.#world()
|
|
1318
|
+
});
|
|
1319
|
+
return await this.send("Runtime.callFunctionOn", {
|
|
1320
|
+
...params,
|
|
1321
|
+
objectId: resolved.object.objectId
|
|
1322
|
+
});
|
|
1290
1323
|
}
|
|
1291
1324
|
async #world() {
|
|
1292
1325
|
if (this._worldId === void 0) {
|
|
@@ -1309,8 +1342,10 @@ var Page = class extends Actions {
|
|
|
1309
1342
|
await sleep(100);
|
|
1310
1343
|
}
|
|
1311
1344
|
}
|
|
1312
|
-
innerText(selector) {
|
|
1313
|
-
|
|
1345
|
+
async innerText(selector) {
|
|
1346
|
+
const nodeId = await this.#nodeId(selector);
|
|
1347
|
+
if (nodeId === void 0) return null;
|
|
1348
|
+
return await this.#call(nodeId, INNER_TEXT);
|
|
1314
1349
|
}
|
|
1315
1350
|
allInnerTexts(selector) {
|
|
1316
1351
|
return this.evaluate("s => Array.from(document.querySelectorAll(s), e => e.innerText)", { args: [selector] });
|
|
@@ -1327,7 +1362,8 @@ var Page = class extends Actions {
|
|
|
1327
1362
|
return this.keyboard.type(text);
|
|
1328
1363
|
}
|
|
1329
1364
|
async #validateFocus(selector) {
|
|
1330
|
-
|
|
1365
|
+
const nodeId = await this.#nodeId(selector);
|
|
1366
|
+
if (nodeId === void 0 || !await this.#call(nodeId, IS_FOCUSED)) throw new Error(`'${selector}' did not take focus`);
|
|
1331
1367
|
}
|
|
1332
1368
|
async getAttribute(selector, name) {
|
|
1333
1369
|
const nodeId = await this.#nodeId(selector);
|
|
@@ -1338,22 +1374,38 @@ var Page = class extends Actions {
|
|
|
1338
1374
|
}
|
|
1339
1375
|
async count(selector) {
|
|
1340
1376
|
const document = await this.send("DOM.getDocument", { depth: 0 });
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
return ((await this.send("DOM.querySelectorAll", {
|
|
1377
|
+
if (asXPath(selector) !== void 0) return (await this.#search(selector)).length;
|
|
1378
|
+
const nodeIds = (await this.send("DOM.querySelectorAll", {
|
|
1344
1379
|
nodeId: document.root.nodeId,
|
|
1345
1380
|
selector
|
|
1346
|
-
})).nodeIds ?? []
|
|
1381
|
+
})).nodeIds ?? [];
|
|
1382
|
+
if (nodeIds.length > 0) return nodeIds.length;
|
|
1383
|
+
return (await this.#search(selector)).length;
|
|
1384
|
+
}
|
|
1385
|
+
async boundingBox(selector) {
|
|
1386
|
+
const nodeId = await this.#nodeId(selector);
|
|
1387
|
+
if (nodeId === void 0) return null;
|
|
1388
|
+
let box;
|
|
1389
|
+
try {
|
|
1390
|
+
box = await this.send("DOM.getBoxModel", { nodeId });
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
if (err instanceof CDPError) return null;
|
|
1393
|
+
throw err;
|
|
1394
|
+
}
|
|
1395
|
+
const quad = box.model.content;
|
|
1396
|
+
return {
|
|
1397
|
+
x: quad[0],
|
|
1398
|
+
y: quad[1],
|
|
1399
|
+
width: quad[2] - quad[0],
|
|
1400
|
+
height: quad[5] - quad[1]
|
|
1401
|
+
};
|
|
1347
1402
|
}
|
|
1348
1403
|
async selectOption(selector, option) {
|
|
1349
1404
|
const { value, label } = typeof option === "string" ? { value: option } : option;
|
|
1350
1405
|
if (value === void 0 === (label === void 0)) throw new TypeError("selectOption takes either a value or a label");
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
label ?? null
|
|
1355
|
-
] });
|
|
1356
|
-
if (picked === null) throw new Error(`nothing matches '${selector}'`);
|
|
1406
|
+
const nodeId = await this.#nodeId(selector);
|
|
1407
|
+
if (nodeId === void 0) throw new Error(`nothing matches '${selector}'`);
|
|
1408
|
+
const picked = await this.#call(nodeId, SELECT, value ?? null, label ?? null);
|
|
1357
1409
|
if (picked === false) throw new Error(`'${selector}' has no option '${label ?? value}'`);
|
|
1358
1410
|
return picked;
|
|
1359
1411
|
}
|
|
@@ -1382,22 +1434,34 @@ var Page = class extends Actions {
|
|
|
1382
1434
|
}
|
|
1383
1435
|
async #nodeId(selector) {
|
|
1384
1436
|
const document = await this.send("DOM.getDocument", { depth: 0 });
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
return (await this.send("DOM.querySelector", {
|
|
1437
|
+
if (asXPath(selector) !== void 0) return (await this.#search(selector))[0];
|
|
1438
|
+
const found = await this.send("DOM.querySelector", {
|
|
1388
1439
|
nodeId: document.root.nodeId,
|
|
1389
1440
|
selector
|
|
1390
|
-
})
|
|
1441
|
+
});
|
|
1442
|
+
if (found.nodeId) return found.nodeId;
|
|
1443
|
+
return (await this.#search(selector))[0];
|
|
1391
1444
|
}
|
|
1392
|
-
async #search(
|
|
1393
|
-
const
|
|
1445
|
+
async #search(selector) {
|
|
1446
|
+
const xpath = asXPath(selector);
|
|
1447
|
+
const { searchId, resultCount = 0 } = await this.send("DOM.performSearch", {
|
|
1448
|
+
query: xpath ?? selector,
|
|
1449
|
+
includeUserAgentShadowDOM: false
|
|
1450
|
+
});
|
|
1394
1451
|
try {
|
|
1395
1452
|
if (!resultCount) return [];
|
|
1396
|
-
|
|
1453
|
+
const nodeIds = (await this.send("DOM.getSearchResults", {
|
|
1397
1454
|
searchId,
|
|
1398
1455
|
fromIndex: 0,
|
|
1399
1456
|
toIndex: resultCount
|
|
1400
1457
|
})).nodeIds ?? [];
|
|
1458
|
+
const css = xpath === void 0 ? selector : null;
|
|
1459
|
+
await this.#world();
|
|
1460
|
+
const kept = await Promise.all(nodeIds.map((nodeId) => this.#call(nodeId, MATCHES, css).catch((err) => {
|
|
1461
|
+
if (err instanceof CDPError) return false;
|
|
1462
|
+
throw err;
|
|
1463
|
+
})));
|
|
1464
|
+
return nodeIds.filter((_, i) => kept[i]);
|
|
1401
1465
|
} finally {
|
|
1402
1466
|
await this.send("DOM.discardSearchResults", { searchId }).catch(() => void 0);
|
|
1403
1467
|
}
|
|
@@ -1520,12 +1584,7 @@ var Page = class extends Actions {
|
|
|
1520
1584
|
this._browser.logger.warn("Fetch.requestPaused without a requestId");
|
|
1521
1585
|
return;
|
|
1522
1586
|
}
|
|
1523
|
-
|
|
1524
|
-
if (status !== void 0 && status !== null) {
|
|
1525
|
-
if (event.frameId === this._frameId) this._status = status;
|
|
1526
|
-
this._spawn(this._send("Fetch.continueResponse", { requestId }));
|
|
1527
|
-
} else if (event.responseErrorReason) this._spawn(this._send("Fetch.continueRequest", { requestId }));
|
|
1528
|
-
else this._spawn(this._send("Fetch.failRequest", {
|
|
1587
|
+
this._spawn(this._send("Fetch.failRequest", {
|
|
1529
1588
|
requestId,
|
|
1530
1589
|
errorReason: "BlockedByClient"
|
|
1531
1590
|
}));
|
|
@@ -1537,11 +1596,6 @@ var Page = class extends Actions {
|
|
|
1537
1596
|
};
|
|
1538
1597
|
//#endregion
|
|
1539
1598
|
//#region src/browser/browser.ts
|
|
1540
|
-
const STATUS_PATTERN = {
|
|
1541
|
-
urlPattern: "*",
|
|
1542
|
-
resourceType: "Document",
|
|
1543
|
-
requestStage: "Response"
|
|
1544
|
-
};
|
|
1545
1599
|
const AUTO_ATTACH = {
|
|
1546
1600
|
autoAttach: true,
|
|
1547
1601
|
waitForDebuggerOnStart: true,
|
|
@@ -1659,7 +1713,7 @@ var Browser = class extends Page {
|
|
|
1659
1713
|
resourceType: RESOURCE_TYPES[name]
|
|
1660
1714
|
}));
|
|
1661
1715
|
for (const pattern of this.blockedUrls) patterns.push({ urlPattern: pattern });
|
|
1662
|
-
return
|
|
1716
|
+
return patterns;
|
|
1663
1717
|
}
|
|
1664
1718
|
/** Replace this browser with a fresh one after the lease. */
|
|
1665
1719
|
retire() {
|
|
@@ -2624,7 +2678,7 @@ var Proxies = class {
|
|
|
2624
2678
|
};
|
|
2625
2679
|
//#endregion
|
|
2626
2680
|
//#region src/version.ts
|
|
2627
|
-
const VERSION = "0.0.
|
|
2681
|
+
const VERSION = "0.0.6";
|
|
2628
2682
|
//#endregion
|
|
2629
2683
|
//#region src/client.ts
|
|
2630
2684
|
function userAgent() {
|