surfsky 0.0.1 → 0.0.3

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.
Files changed (3) hide show
  1. package/README.md +17 -66
  2. package/dist/index.js +21 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,7 +47,9 @@ import { Surfsky } from "surfsky";
47
47
  // or new Surfsky({ apiToken: "...", baseUrl: "..." }) instead of env vars
48
48
  await using client = new Surfsky();
49
49
  // starts a session, stops it when the block ends so it doesn't keep billing
50
- await using browser = await client.browser({ proxy: { tier: "premium", country: "us" } });
50
+ await using browser = await client.browser({
51
+ proxy: { tier: "premium", country: "us" },
52
+ });
51
53
  await browser.goto("https://duckduckgo.com", { waitUntil: "domcontentloaded" });
52
54
  await browser.type('[name="q"]', "surfsky cloud browser");
53
55
  await browser.click("#searchbox_homepage button[type=submit]");
@@ -125,7 +127,8 @@ If you need more control, write your own loop on top of the pool:
125
127
 
126
128
  ```ts
127
129
  await using pool = await client.browsers();
128
- await pool.lease(async (browser) => { // waits for a free browser
130
+ await pool.lease(async (browser) => {
131
+ // waits for a free browser
129
132
  await browser.goto("https://example.com");
130
133
  console.log(await browser.title());
131
134
  });
@@ -179,12 +182,14 @@ built-in pools:
179
182
  import { ProxyCycle, ProxyTemplate } from "surfsky";
180
183
 
181
184
  proxy = { tier: "premium", country: "us", region: "ny", type: "mobile" }; // Surfsky premium
182
- proxy = { tier: "shared", country: "us" }; // Surfsky shared, for tests
183
- proxy = { country: "de" }; // premium if set up, else shared
184
- proxy = "socks5://user:pass@host:1080"; // your own
185
- proxy = new ProxyCycle(myProxies); // round-robin over your own list
186
- proxy = new ProxyTemplate("http://user-sessid-{session}:pw@gate.example.com:7000");
187
- proxy = async () => pickOne(); // any function, sync or async
185
+ proxy = { tier: "shared", country: "us" }; // Surfsky shared, for tests
186
+ proxy = { country: "de" }; // premium if set up, else shared
187
+ proxy = "socks5://user:pass@host:1080"; // your own
188
+ proxy = new ProxyCycle(myProxies); // round-robin over your own list
189
+ proxy = new ProxyTemplate(
190
+ "http://user-sessid-{session}:pw@gate.example.com:7000",
191
+ );
192
+ proxy = async () => pickOne(); // any function, sync or async
188
193
  ```
189
194
 
190
195
  `client.proxies` lists available countries, regions and cities, plus your quota.
@@ -277,6 +282,10 @@ waits for one of its own and throws `RateLimitError` only if it has none.
277
282
 
278
283
  ### Reading
279
284
 
285
+ `selector` is CSS, or XPath when it starts with `//`, `..` or `xpath=`. XPath
286
+ covers the DOM reads below and `screenshot({ selector })`; `innerText`,
287
+ `allInnerTexts`, `selectOption` and the input methods take CSS only.
288
+
280
289
  | Method | Description |
281
290
  | ------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
282
291
  | `url()`, `title()` | Current URL and title. |
@@ -408,64 +417,6 @@ Live tests start real sessions and bill your account:
408
417
  SURFSKY_LIVE_TESTS=1 SURFSKY_API_TOKEN=... SURFSKY_API_BASE_URL=... bun run test:live
409
418
  ```
410
419
 
411
- ## Releasing
412
-
413
- One-time, GitHub:
414
-
415
- ```sh
416
- gh repo create surfskyio/surfsky-js --public --source=. --remote=origin --push
417
- ```
418
-
419
- CI (`.github/workflows/ci.yml`) then runs on every push and PR: lint, typecheck,
420
- tests and a build on Node 22, 24 and 26, plus the same on Bun.
421
-
422
- One-time, npm: publishing runs from `.github/workflows/release.yml` with no npm
423
- token, so it needs [trusted
424
- publishing](https://docs.npmjs.com/trusted-publishers) configured for the
425
- package — provider GitHub Actions, repository `surfskyio/surfsky-js`, workflow
426
- `release.yml`, environment `npm`, publish allowed. A trusted publisher is a
427
- setting *on a package*, so the first version goes out by hand:
428
-
429
- ```sh
430
- npm login
431
- bun run check && bun run build
432
- npm publish # no --provenance: that needs the OIDC token only CI has
433
- ```
434
-
435
- Then attach the publisher, on npmjs.com under the package's settings or, with
436
- npm new enough to have the command, from the shell:
437
-
438
- ```sh
439
- npm trust github surfsky --repo surfskyio/surfsky-js --file release.yml \
440
- --env npm --allow-publish
441
- ```
442
-
443
- The alternative to all of it is a granular access token in the repo as
444
- `NPM_TOKEN`, with `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` added to the
445
- publish step.
446
-
447
- Two things bite here: npm never validates a trusted-publisher config when you
448
- save it, so a wrong workflow name surfaces only as a failed publish, and
449
- `repository.url` in `package.json` has to match the GitHub repository exactly.
450
-
451
- The `npm` environment is also where a required reviewer goes, if a release
452
- should wait for an approval.
453
-
454
- Every release after that is a tag:
455
-
456
- ```sh
457
- # bump "version" in package.json first
458
- bun run check && bun run build
459
- git commit -am "release: v0.1.0"
460
- git tag v0.1.0
461
- git push origin main --tags
462
- ```
463
-
464
- The workflow refuses to publish when the tag and `package.json` disagree, then
465
- runs `bun run check`, builds, imports `dist/index.js` once to prove the bundle
466
- loads, and publishes. `files` limits the tarball to `dist/`, `README.md`,
467
- `LICENSE` and `package.json` - check it with `npm pack --dry-run`.
468
-
469
420
  ## License
470
421
 
471
422
  MIT
package/dist/index.js CHANGED
@@ -865,6 +865,9 @@ const WORLD_NAME = "utility";
865
865
  /** A dialog is answered after this long, as a person would. */
866
866
  const DIALOG_DELAY = [600, 1400];
867
867
  const FUNCTION = /^\s*(async\s+)?(function\b|\([^()]*\)\s*=>|[\w$]+\s*=>)/;
868
+ function asXPath(s) {
869
+ return /^\s*(xpath=|\/\/|\.\.)/.test(s) ? s.trim().replace(/^xpath=/, "") : void 0;
870
+ }
868
871
  const SELECT = `(selector, value, label) => {
869
872
  const el = document.querySelector(selector);
870
873
  if (!el) return null;
@@ -1329,6 +1332,8 @@ var Page = class extends Actions {
1329
1332
  }
1330
1333
  async count(selector) {
1331
1334
  const document = await this.send("DOM.getDocument", { depth: 0 });
1335
+ const xpath = asXPath(selector);
1336
+ if (xpath !== void 0) return (await this.#search(xpath)).length;
1332
1337
  return ((await this.send("DOM.querySelectorAll", {
1333
1338
  nodeId: document.root.nodeId,
1334
1339
  selector
@@ -1371,11 +1376,26 @@ var Page = class extends Actions {
1371
1376
  }
1372
1377
  async #nodeId(selector) {
1373
1378
  const document = await this.send("DOM.getDocument", { depth: 0 });
1379
+ const xpath = asXPath(selector);
1380
+ if (xpath !== void 0) return (await this.#search(xpath))[0];
1374
1381
  return (await this.send("DOM.querySelector", {
1375
1382
  nodeId: document.root.nodeId,
1376
1383
  selector
1377
1384
  })).nodeId || void 0;
1378
1385
  }
1386
+ async #search(query) {
1387
+ const { searchId, resultCount = 0 } = await this.send("DOM.performSearch", { query });
1388
+ try {
1389
+ if (!resultCount) return [];
1390
+ return (await this.send("DOM.getSearchResults", {
1391
+ searchId,
1392
+ fromIndex: 0,
1393
+ toIndex: resultCount
1394
+ })).nodeIds ?? [];
1395
+ } finally {
1396
+ await this.send("DOM.discardSearchResults", { searchId }).catch(() => void 0);
1397
+ }
1398
+ }
1379
1399
  /** Wait until the URL contains the fragment. Returns the URL. */
1380
1400
  async waitForUrl(fragment, options = {}) {
1381
1401
  const deadline = new Deadline(options.timeout ?? 3e4, `the page did not reach '${fragment}'`);
@@ -2598,7 +2618,7 @@ var Proxies = class {
2598
2618
  };
2599
2619
  //#endregion
2600
2620
  //#region src/version.ts
2601
- const VERSION = "0.0.1";
2621
+ const VERSION = "0.0.3";
2602
2622
  //#endregion
2603
2623
  //#region src/client.ts
2604
2624
  function userAgent() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "surfsky",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "TypeScript SDK for the Surfsky antidetect browser cloud.",
5
5
  "keywords": [
6
6
  "surfsky",