surfsky 0.0.3 → 0.0.5
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 +59 -344
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,419 +2,134 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript SDK for [Surfsky](https://surfsky.io), a cloud-based antidetect browser.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
- Raw CDP, without the automation traces stock Playwright, Puppeteer or
|
|
10
|
-
Selenium leave in the page.
|
|
11
|
-
- Input goes through our
|
|
12
|
-
[human-emulation](https://docs.surfsky.io/human_emulation) framework: real
|
|
13
|
-
timing, typing, cursor movement.
|
|
14
|
-
- Residential and mobile proxies, 100M+ IPs, with geo, ASN and sticky-session
|
|
15
|
-
targeting. Or use your own.
|
|
16
|
-
- Real browser fingerprints from a pool of 2.5M+ devices, not synthetic ones.
|
|
17
|
-
- Browsers run in the cloud. Playwright-style API, fully typed, Node 22+ and
|
|
18
|
-
Bun.
|
|
19
|
-
|
|
20
|
-
REST API docs for the service: https://docs.surfsky.io/api-reference
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
5
|
+
## Install
|
|
23
6
|
|
|
24
7
|
```sh
|
|
25
8
|
npm install surfsky
|
|
26
9
|
```
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
ESM only.
|
|
11
|
+
Or `bun add surfsky` / `pnpm add surfsky`. ESM only; requires Node 22+ or Bun.
|
|
30
12
|
|
|
31
13
|
## Quick start
|
|
32
14
|
|
|
33
|
-
|
|
34
|
-
[dashboard](https://app.surfsky.io) shows your API token and base URL. Put them
|
|
35
|
-
in the environment:
|
|
15
|
+
Get your API token and base URL from the [dashboard](https://app.surfsky.io):
|
|
36
16
|
|
|
37
17
|
```sh
|
|
38
|
-
export SURFSKY_API_TOKEN
|
|
39
|
-
export SURFSKY_API_BASE_URL
|
|
18
|
+
export SURFSKY_API_TOKEN='your-token'
|
|
19
|
+
export SURFSKY_API_BASE_URL='your-base-url'
|
|
40
20
|
```
|
|
41
21
|
|
|
42
|
-
Then start a browser:
|
|
43
|
-
|
|
44
22
|
```ts
|
|
45
23
|
import { Surfsky } from "surfsky";
|
|
46
24
|
|
|
47
|
-
// or new Surfsky({ apiToken: "...", baseUrl: "..." }) instead of env vars
|
|
48
25
|
await using client = new Surfsky();
|
|
49
|
-
// starts a session, stops it when the block ends so it doesn't keep billing
|
|
50
26
|
await using browser = await client.browser({
|
|
51
27
|
proxy: { tier: "premium", country: "us" },
|
|
52
28
|
});
|
|
53
|
-
await browser.goto("https://
|
|
54
|
-
await browser.
|
|
55
|
-
await browser.click("#searchbox_homepage button[type=submit]");
|
|
56
|
-
console.log(await browser.waitForUrl("?q="));
|
|
29
|
+
await browser.goto("https://example.com");
|
|
30
|
+
console.log(await browser.title());
|
|
57
31
|
```
|
|
58
32
|
|
|
59
|
-
`await using`
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
33
|
+
`await using` closes the browser and stops its session when the scope ends.
|
|
34
|
+
It requires Node 24+ or Bun; compile TypeScript for Node 22, or use
|
|
35
|
+
`try/finally` with `await browser.close()`. Sessions are billed until stopped,
|
|
36
|
+
including idle time.
|
|
63
37
|
|
|
64
|
-
|
|
38
|
+
You can also pass `apiToken` and `baseUrl` to `new Surfsky()`.
|
|
39
|
+
Browser methods are async. SDK timeouts are in milliseconds; cloud input
|
|
40
|
+
timing options use the server's units. Input uses Surfsky's
|
|
41
|
+
[human emulation](https://docs.surfsky.io/human_emulation).
|
|
65
42
|
|
|
66
|
-
|
|
67
|
-
`fill`, `hover`, `waitForSelector`, `innerText`, `selectOption`,
|
|
68
|
-
`keyboard.press`, `mouse.move` and so on. Input goes through Surfsky's
|
|
69
|
-
[human emulation](https://docs.surfsky.io/human_emulation). Reads use plain CDP
|
|
70
|
-
and run no JavaScript in the page.
|
|
43
|
+
## Profiles and proxies
|
|
71
44
|
|
|
72
|
-
|
|
45
|
+
A profile preserves its fingerprint, proxy and cookies across sessions:
|
|
73
46
|
|
|
74
47
|
```ts
|
|
75
|
-
await
|
|
76
|
-
|
|
77
|
-
|
|
48
|
+
const profile = await client.profiles.create({
|
|
49
|
+
title: "account-1",
|
|
50
|
+
fingerprint: { os: "win", os_arch: "x86", os_version: "11" },
|
|
51
|
+
proxy: { tier: "premium", country: "us" },
|
|
52
|
+
});
|
|
53
|
+
await using saved = await client.browser({ profileUuid: profile.uuid });
|
|
54
|
+
await saved.goto("https://example.com/login");
|
|
78
55
|
```
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Also useful:
|
|
57
|
+
Reuse the profile ID for subsequent sessions. `proxy` accepts a premium or
|
|
58
|
+
shared configuration, or your own proxy URL. Premium supports residential and
|
|
59
|
+
mobile IPs; shared is for testing. `client.proxies` lists locations and quota.
|
|
84
60
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- Grab the JSON a page fetches instead of parsing its HTML:
|
|
61
|
+
SDK methods and options use camelCase. API fields keep snake_case, such as
|
|
62
|
+
`internal_uuid`, `os_arch` and `storage_options`.
|
|
88
63
|
|
|
89
|
-
|
|
90
|
-
await browser.captureResponses("/api/search");
|
|
91
|
-
await browser.goto(url);
|
|
92
|
-
const data = (await browser.waitForResponse("/api/search")).json();
|
|
93
|
-
```
|
|
64
|
+
## Parallel browsers
|
|
94
65
|
|
|
95
|
-
|
|
96
|
-
included, and `newPage()` adds one:
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
await browser.newPage();
|
|
100
|
-
await browser.pages[1].goto("https://example.com");
|
|
101
|
-
console.log(await browser.pages[0].title(), await browser.pages[1].title());
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Every method is listed in the [API reference](#api-reference).
|
|
105
|
-
|
|
106
|
-
## Running multiple browsers
|
|
107
|
-
|
|
108
|
-
`client.map` runs a function over a list of items in parallel, one browser per
|
|
109
|
-
item, and collects the results:
|
|
66
|
+
`client.map` distributes items across a browser pool:
|
|
110
67
|
|
|
111
68
|
```ts
|
|
69
|
+
import type { Browser } from "surfsky";
|
|
70
|
+
|
|
112
71
|
async function title(browser: Browser, url: string) {
|
|
113
72
|
await browser.goto(url);
|
|
114
73
|
return browser.title();
|
|
115
74
|
}
|
|
116
75
|
|
|
117
|
-
|
|
118
|
-
|
|
76
|
+
const urls = ["https://example.com", "https://example.org"];
|
|
77
|
+
for (const result of await client.map(title, urls, { concurrency: 2 })) {
|
|
78
|
+
console.log(result.item, result.ok ? result.value : result.error);
|
|
119
79
|
}
|
|
120
80
|
```
|
|
121
81
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
(`concurrency: "auto"`). Pass `concurrency: 5` to cap it.
|
|
82
|
+
Without a concurrency limit, the pool uses your plan's maximum. Results include
|
|
83
|
+
per-item errors.
|
|
125
84
|
|
|
126
|
-
|
|
85
|
+
To use a browser from the pool:
|
|
127
86
|
|
|
128
87
|
```ts
|
|
129
88
|
await using pool = await client.browsers();
|
|
130
89
|
await pool.lease(async (browser) => {
|
|
131
|
-
// waits for a free browser
|
|
132
90
|
await browser.goto("https://example.com");
|
|
133
91
|
console.log(await browser.title());
|
|
134
92
|
});
|
|
135
93
|
```
|
|
136
94
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
a fresh browser.
|
|
140
|
-
|
|
141
|
-
Neither `map` nor `lease` retries: an error is reported and the run goes on.
|
|
142
|
-
Retries belong to your handler, since only it knows a timeout from a 404. Loop
|
|
143
|
-
around `lease` and `retire()` the browser first, so the next attempt gets a
|
|
144
|
-
fresh browser: [`examples/retry.ts`](https://github.com/surfskyio/surfsky-js/blob/main/examples/retry.ts).
|
|
95
|
+
`lease()` waits for a free browser and returns it to the pool after the callback.
|
|
96
|
+
Cookies and browser state persist between leases. Handlers are not retried.
|
|
145
97
|
|
|
146
|
-
##
|
|
98
|
+
## Reference and examples
|
|
147
99
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
session lifecycle:
|
|
100
|
+
- [SDK API reference](https://github.com/surfskyio/surfsky-js/blob/main/docs/api.md)
|
|
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.
|
|
152
103
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await using client = new Surfsky();
|
|
158
|
-
await using session = await client.session();
|
|
159
|
-
const browser = await chromium.connectOverCDP(session.ws_url);
|
|
160
|
-
const page = browser.contexts()[0].pages()[0];
|
|
161
|
-
await page.goto("https://example.com");
|
|
162
|
-
```
|
|
104
|
+
`client.session()` exposes a WebSocket URL for external browser clients.
|
|
105
|
+
See the [Playwright](https://github.com/surfskyio/surfsky-js/blob/main/examples/playwright_connect.ts),
|
|
106
|
+
[Puppeteer](https://github.com/surfskyio/surfsky-js/blob/main/examples/puppeteer_connect.ts)
|
|
107
|
+
and [Selenium](https://github.com/surfskyio/surfsky-js/blob/main/examples/selenium_connect.ts) examples.
|
|
163
108
|
|
|
164
|
-
|
|
165
|
-
avoids, `Runtime.enable` above all. The patched forks strip them and are
|
|
166
|
-
drop-in replacements.
|
|
167
|
-
|
|
168
|
-
Selenium works too (`enable_chromedriver: true`, see
|
|
169
|
-
[`examples/selenium_connect.ts`](https://github.com/surfskyio/surfsky-js/blob/main/examples/selenium_connect.ts)) but isn't
|
|
170
|
-
recommended: chromedriver is easy to detect.
|
|
171
|
-
|
|
172
|
-
## Proxies
|
|
173
|
-
|
|
174
|
-
Every session start accepts `proxy`. Use your own, or one of Surfsky's
|
|
175
|
-
built-in pools:
|
|
176
|
-
|
|
177
|
-
- Premium: clean residential and mobile IPs, targeted by country, region,
|
|
178
|
-
city, ASN or coordinates, with sticky sessions. Use it for production.
|
|
179
|
-
- Shared: a pool for testing. Don't rely on it against sites that matter.
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { ProxyCycle, ProxyTemplate } from "surfsky";
|
|
183
|
-
|
|
184
|
-
proxy = { tier: "premium", country: "us", region: "ny", type: "mobile" }; // Surfsky premium
|
|
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
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
`client.proxies` lists available countries, regions and cities, plus your quota.
|
|
196
|
-
|
|
197
|
-
## Profiles and the REST API
|
|
198
|
-
|
|
199
|
-
Profiles, proxies, fingerprints, extensions and account limits are all typed
|
|
200
|
-
calls on the client. You'll mostly use profiles: a profile is a saved browser,
|
|
201
|
-
so every session started on it gets the same fingerprint, proxy and cookies.
|
|
202
|
-
|
|
203
|
-
```ts
|
|
204
|
-
await using client = new Surfsky();
|
|
205
|
-
const profile = await client.profiles.create({
|
|
206
|
-
title: "account-1",
|
|
207
|
-
fingerprint: { os: "win", os_arch: "x86", os_version: "11" },
|
|
208
|
-
proxy: { tier: "premium", country: "us" },
|
|
209
|
-
});
|
|
210
|
-
await using browser = await client.browser({ profileUuid: profile.uuid });
|
|
211
|
-
await browser.goto("https://example.com/login");
|
|
212
|
-
// log in once, the cookies stay with the profile
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Next time, `client.browser({ profileUuid })` brings back the same browser,
|
|
216
|
-
still logged in. `client.profiles.iterAll()` lists your profiles and
|
|
217
|
-
`delete(uuid)` removes one.
|
|
218
|
-
|
|
219
|
-
Fields on request and response objects use the API's own names
|
|
220
|
-
(`internal_uuid`, `os_arch`, `storage_options`), so the
|
|
221
|
-
[API reference](https://docs.surfsky.io/api-reference) maps 1:1. For endpoints
|
|
222
|
-
the SDK doesn't cover, `client.request(method, path, options)` sends the
|
|
223
|
-
request and returns the raw fetch `Response`.
|
|
224
|
-
|
|
225
|
-
## API reference
|
|
226
|
-
|
|
227
|
-
Every browser method is async. `Browser` is a `Page` plus the connection: page
|
|
228
|
-
methods on it act on the session's first tab. Waits take `timeout` in
|
|
229
|
-
milliseconds, default 30 000, and throw `BrowserTimeoutError`.
|
|
230
|
-
|
|
231
|
-
### Client
|
|
232
|
-
|
|
233
|
-
`new Surfsky({ apiToken, baseUrl, timeout: 30_000, maxRetries: 3, backoff: 500, headers, logger, fetch })`.
|
|
234
|
-
|
|
235
|
-
| Method | Description |
|
|
236
|
-
| ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
237
|
-
| `session({ profileUuid, ...options })` | Start a session. Returns the `Session` (`internal_uuid`, `ws_url`) with `stop()` and `await using` support. |
|
|
238
|
-
| `browser({ profileUuid, blockResources, blockUrls, connectTimeout, commandTimeout, ...options })` | Start a session and connect a `Browser`. `close()` stops both. |
|
|
239
|
-
| `browsers({ concurrency: "auto", blockResources, blockUrls, ...options })` | An open `BrowserPool`. `close()` stops every browser. |
|
|
240
|
-
| `map(handler, items, poolOptions)` | `browsers()` and `pool.map()` in one call. |
|
|
241
|
-
| `withOptions({ timeout, maxRetries, headers })` | Copy with overrides. Same fetch and logger. |
|
|
242
|
-
| `request(method, path, { json, params, body, headers, timeout })` | Raw call. Returns the fetch `Response`, never throws on status. |
|
|
243
|
-
| `close()` | Nothing to release; here for `await using`. |
|
|
244
|
-
|
|
245
|
-
Session options: `fingerprint`, `proxy`, `browser_settings`
|
|
246
|
-
(`inactive_kill_timeout`, `cache_enabled`, `cache_key`), `enable_chromedriver`,
|
|
247
|
-
`extensions` (up to 5 uuids), `proxy_blacklist`, `domain_routes`, `cookies`.
|
|
248
|
-
`fingerprint` and `cookies` apply to one-time sessions only. Requests are
|
|
249
|
-
validated with [zod](https://zod.dev) before they leave: an unknown key, a
|
|
250
|
-
bad range or an impossible proxy targeting throws `ValidationError`, so a typo
|
|
251
|
-
never starts a billed session.
|
|
252
|
-
|
|
253
|
-
`logger` takes `{ debug, info, warn, error }` (any subset). By default warnings
|
|
254
|
-
and errors go to the console; `logger: null` silences the SDK.
|
|
255
|
-
|
|
256
|
-
### Pool
|
|
257
|
-
|
|
258
|
-
| Member | Description |
|
|
259
|
-
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
260
|
-
| `pool.lease(fn)` | Runs `fn(browser)` on a live browser and hands it back after. Waits while all are busy. |
|
|
261
|
-
| `pool.map(handler, items)` | `handler(browser, item)` per item, `capacity` at a time. Returns `PoolOutcome` list in input order: `{ ok: true, item, index, value }` or `{ ok: false, item, index, error }`. Throw `StopRun` to end early. |
|
|
262
|
-
| `pool.capacity` | Max live browsers. `"auto"` is the plan's limit, `SURFSKY_MAX_BROWSERS` overrides it. |
|
|
263
|
-
| `browser.data` | Per-browser object. Survives leases. |
|
|
264
|
-
| `browser.useCount` | Leases so far, current included. |
|
|
265
|
-
| `browser.retire()` | Replace this browser with a fresh one after the lease. |
|
|
266
|
-
| `browser.internalUuid` | Session id. |
|
|
267
|
-
| `browser.connected` | Socket is up. |
|
|
268
|
-
|
|
269
|
-
The plan limit counts browsers started elsewhere with the same token. `lease()`
|
|
270
|
-
waits for one of its own and throws `RateLimitError` only if it has none.
|
|
271
|
-
|
|
272
|
-
### Navigation
|
|
273
|
-
|
|
274
|
-
| Method | Description |
|
|
275
|
-
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
276
|
-
| `goto(url, { waitUntil: "load", timeout })` | Navigate. `waitUntil`: `commit`, `domcontentloaded`, `load`, `networkidle`. Follows redirects. |
|
|
277
|
-
| `reload({ waitUntil, timeout })` | Reload. |
|
|
278
|
-
| `goBack({ timeout })`, `goForward({ timeout })` | Returns the new URL, `null` at the end of history. |
|
|
279
|
-
| `waitForLoadState(state = "load", { timeout })` | Wait for the current document to reach `state`. |
|
|
280
|
-
| `waitForUrl(fragment, { timeout })` | Wait until the URL contains `fragment`. Returns the URL. |
|
|
281
|
-
| `status` | HTTP status of the current document. Set even when `goto` throws. |
|
|
282
|
-
|
|
283
|
-
### Reading
|
|
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
|
-
|
|
289
|
-
| Method | Description |
|
|
290
|
-
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
291
|
-
| `url()`, `title()` | Current URL and title. |
|
|
292
|
-
| `content()` | Full HTML. |
|
|
293
|
-
| `outerHtml(selector)` | HTML of the first match, `null` if none. |
|
|
294
|
-
| `innerText(selector)`, `allInnerTexts(selector)` | Rendered text of the first match, or of every match. Runs script in the isolated world. |
|
|
295
|
-
| `getAttribute(selector, name)` | `null` if missing. |
|
|
296
|
-
| `count(selector)` | Number of matches. |
|
|
297
|
-
| `isVisible(selector)` | First match has a bounding box. |
|
|
298
|
-
| `waitForSelector(selector, { visible: true, timeout })` | Wait for the element, visible by default. |
|
|
299
|
-
| `screenshot({ selector, fullPage, format: "png", quality })` | `Uint8Array`. Viewport, one element or the full page. `format`: `png`, `jpeg`, `webp`. |
|
|
300
|
-
|
|
301
|
-
### Input
|
|
302
|
-
|
|
303
|
-
Server-side human emulation. The first CSS match is used. `click`, `dblclick`
|
|
304
|
-
and `hover` also take `waitForVisible`, `scrollIntoView`, `preDelay`,
|
|
305
|
-
`postDelay`, `timeout`.
|
|
306
|
-
|
|
307
|
-
| Method | Description |
|
|
308
|
-
| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
309
|
-
| `click(selector, { button, clickCount, modifiers })` | `button`: `left`, `right`, `middle`. `modifiers`: `Alt`, `Control`, `Meta`, `Shift`. Waits up to 30s for the element. |
|
|
310
|
-
| `dblclick(selector, ...)` | Double-click. |
|
|
311
|
-
| `hover(selector)` | Move the mouse over it. |
|
|
312
|
-
| `type(selector, text)` | Click, then type after the existing text. |
|
|
313
|
-
| `fill(selector, text)` | Select the existing text, then type over it. |
|
|
314
|
-
| `selectOption(selector, value)` / `selectOption(selector, { label })` | Pick an `<option>` by value or label. Returns the value. |
|
|
315
|
-
| `scroll({ deltaX, deltaY, duration })` | Animated scroll. |
|
|
316
|
-
| `scrollIntoView(selector, { behavior })`, `scrollTo({ x, y, behavior })` | `behavior`: `smooth`, `instant`. |
|
|
317
|
-
| `keyboard.type(text)`, `keyboard.press(key, { modifiers, delay })` | Keys to the focused element. `press("Enter")` submits a form; older pods need the button. |
|
|
318
|
-
| `mouse.move(x, y)`, `mouse.click(x, y)`, `mouse.down(x, y)`, `mouse.up(x, y)`, `mouse.wheel({ deltaX, deltaY })`, `mouse.drag({ startX, startY, endX, endY })` | Viewport coordinates. |
|
|
319
|
-
|
|
320
|
-
### Script
|
|
321
|
-
|
|
322
|
-
| Method | Description |
|
|
323
|
-
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
324
|
-
| `evaluate(expression, { args, isolated: true, awaitPromise: true })` | Run JS. A function, or a string that looks like one, is called with `args` as JSON; anything else is an expression. Isolated world by default. |
|
|
325
|
-
| `waitForFunction(expression, { args, isolated, timeout })` | Poll until truthy. Returns the value. |
|
|
326
|
-
| `send(method, params)` | Raw page-level CDP command. |
|
|
327
|
-
| `browser.cdp` | Raw browser-level client: `send`, `post`, `on`. |
|
|
328
|
-
|
|
329
|
-
### Cookies and storage
|
|
330
|
-
|
|
331
|
-
| Method | Description |
|
|
332
|
-
| ----------------------------------------------- | --------------------------------- |
|
|
333
|
-
| `cookies()` | All cookies, `httpOnly` included. |
|
|
334
|
-
| `setCookies(cookies)` | Cookie objects. |
|
|
335
|
-
| `clearCookies()` | Remove every cookie. |
|
|
336
|
-
| `localStorage()`, `setLocalStorage(values)` | Current origin, as an object. |
|
|
337
|
-
| `sessionStorage()`, `setSessionStorage(values)` | Same for sessionStorage. |
|
|
338
|
-
|
|
339
|
-
### Network
|
|
340
|
-
|
|
341
|
-
| Method | Description |
|
|
342
|
-
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
343
|
-
| `captureResponses(...fragments)` | Record responses whose URL contains a fragment. Call before navigating. |
|
|
344
|
-
| `waitForResponse(fragment, { timeout })` | First captured match. `CapturedResponse`: `url`, `status`, `headers`, `body` (`Uint8Array`), `text`, `json()`. |
|
|
345
|
-
| `responses` | Everything captured, oldest first. |
|
|
346
|
-
| `stopCapturing()` | Drop captures, stop recording. |
|
|
347
|
-
|
|
348
|
-
### Dialogs
|
|
349
|
-
|
|
350
|
-
`page.onDialog = (kind, message) => ...`. `kind`: `alert`, `confirm`, `prompt`,
|
|
351
|
-
`beforeunload`. Return `true` to accept, `false` to dismiss, a string to answer
|
|
352
|
-
a prompt, `undefined` for the default. Default: dismiss, except `beforeunload`
|
|
353
|
-
is accepted.
|
|
354
|
-
|
|
355
|
-
### Pages
|
|
356
|
-
|
|
357
|
-
| Member | Description |
|
|
358
|
-
| ------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
359
|
-
| `browser.pages` | Every open page. The browser's own first, newest last. |
|
|
360
|
-
| `browser.newPage()` | Blank page in a new window. |
|
|
361
|
-
| `browser.waitForPage(action, { timeout })` | Await `action` (a click promise, or a function) and return the page it opened. |
|
|
362
|
-
| `page.close()` | Close the tab. On the browser itself: close the connection. |
|
|
363
|
-
| `page.closed` | `true` once gone. Commands then throw `PageClosedError`. |
|
|
364
|
-
| `page.bringToFront()` | Make it the visible tab. Screenshots of hidden tabs hang. |
|
|
365
|
-
| `page.targetId` | CDP target id. |
|
|
366
|
-
|
|
367
|
-
### REST
|
|
368
|
-
|
|
369
|
-
| Namespace | Methods |
|
|
370
|
-
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
371
|
-
| `client.profiles` | `startOneTime(options)`, `start(uuid, options)`, `stop(session)`, `stopAll()`, `listActive()`, `create({ title, fingerprint, description, proxy, cookies, storage_options })`, `get(uuid)`, `update(uuid, fields)`, `delete(uuid)`, `deleteMany(uuids)`, `listPage({ page, page_len, ordering })`, `iterAll({ page_len, ordering })`, `exportCookies(uuid, { export_format })`, `importCookies(uuid, cookies)`, `scrape(session, url, { screenshot, wait, wait_until, wait_for, human_actions })` |
|
|
372
|
-
| `client.proxies` | `countries()`, `regions(country)`, `cities(country, region)`, `quota()`, `premiumStats()`, `sharedCountries()`, `sharedQuota()`, `sharedStats()`. The first four need a premium provider on the account. |
|
|
373
|
-
| `client.fingerprints` | `renderers(os, osArch)`, `screens(os, osArch)`, `deviceModels({ os, os_arch, os_version, device_type })` |
|
|
374
|
-
| `client.extensions` | `upload(file, name)` (path, bytes or Blob, zip up to 100 MB), `listAll()`, `get(uuid)`, `update(uuid, { name })`, `delete(uuid)` |
|
|
375
|
-
| `client.account` | `sessionLimits()`, `browserLimits()`, `maxBrowsers()` |
|
|
376
|
-
|
|
377
|
-
### Errors
|
|
378
|
-
|
|
379
|
-
All subclasses of `SurfskyError`. `ValidationError` for a request that failed
|
|
380
|
-
validation before it was sent (`issues` lists the fields). HTTP: `APIError`
|
|
381
|
-
subclasses named after the status (`NotFoundError`, `RateLimitError`, ...) with
|
|
382
|
-
`statusCode`, `code`, `body`, `requestId`, `retryAfter` (ms). Browser:
|
|
383
|
-
`CDPError`, `BrowserTimeoutError`, `PageClosedError`. Idempotent requests retry
|
|
384
|
-
on 429, 5xx and connection errors. POST and PATCH retry on a 429 or a failure
|
|
385
|
-
that proves the request never left (a refused connection, a DNS error), so a
|
|
386
|
-
lost reply can't start a second billed session.
|
|
387
|
-
|
|
388
|
-
## Examples
|
|
389
|
-
|
|
390
|
-
More examples in [`examples/`](https://github.com/surfskyio/surfsky-js/tree/main/examples). They import `surfsky` by name:
|
|
109
|
+
From the repository, run examples with Bun or Node 24+:
|
|
391
110
|
|
|
392
111
|
```sh
|
|
393
|
-
bun
|
|
394
|
-
bun run
|
|
112
|
+
bun install
|
|
113
|
+
bun run examples/one_time.ts
|
|
114
|
+
# Or, on Node 24+:
|
|
115
|
+
bun run build && node examples/one_time.ts
|
|
395
116
|
```
|
|
396
117
|
|
|
397
|
-
On Node 22 compile the examples first
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
The Playwright, Puppeteer and Selenium examples need their dev dependencies
|
|
401
|
-
(`bun install` brings them in). Run `playwright_connect.ts` on Node: under Bun,
|
|
402
|
-
Playwright's own WebSocket transport opens the socket and then never sends a
|
|
403
|
-
command, so `connectOverCDP` times out. Bun runs the SDK and the Puppeteer
|
|
404
|
-
example fine.
|
|
118
|
+
On Node 22, compile the examples first. Run the Playwright example on Node;
|
|
119
|
+
its WebSocket connection can hang under Bun.
|
|
405
120
|
|
|
406
121
|
## Development
|
|
407
122
|
|
|
408
123
|
```sh
|
|
409
124
|
bun install
|
|
410
|
-
bun run check
|
|
125
|
+
bun run check
|
|
411
126
|
bun run build
|
|
412
127
|
```
|
|
413
128
|
|
|
414
|
-
Live tests
|
|
129
|
+
Live tests require credentials and bill your account:
|
|
415
130
|
|
|
416
131
|
```sh
|
|
417
|
-
SURFSKY_LIVE_TESTS=1
|
|
132
|
+
SURFSKY_LIVE_TESTS=1 bun run test:live
|
|
418
133
|
```
|
|
419
134
|
|
|
420
135
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -1097,8 +1097,6 @@ declare abstract class Actions {
|
|
|
1097
1097
|
click(selector: string, options?: ClickOptions): Promise<any>;
|
|
1098
1098
|
dblclick(selector: string, options?: DblclickOptions): Promise<any>;
|
|
1099
1099
|
hover(selector: string, options?: HoverOptions): Promise<any>;
|
|
1100
|
-
type(selector: string, text: string): Promise<any>;
|
|
1101
|
-
fill(selector: string, text: string): Promise<any>;
|
|
1102
1100
|
scroll(options?: ScrollOptions): Promise<any>;
|
|
1103
1101
|
scrollIntoView(selector: string, options?: {
|
|
1104
1102
|
behavior?: ScrollBehavior;
|
|
@@ -1368,6 +1366,8 @@ declare class Page extends Actions {
|
|
|
1368
1366
|
waitForFunction(expression: string | ((...args: any[]) => unknown), options?: EvaluateOptions & WaitOptions): Promise<any>;
|
|
1369
1367
|
innerText(selector: string): Promise<string | null>;
|
|
1370
1368
|
allInnerTexts(selector: string): Promise<string[]>;
|
|
1369
|
+
type(selector: string, text: string): Promise<any>;
|
|
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
1373
|
selectOption(selector: string, option: string | {
|
package/dist/index.js
CHANGED
|
@@ -85,14 +85,6 @@ var Actions = class {
|
|
|
85
85
|
...options
|
|
86
86
|
}));
|
|
87
87
|
}
|
|
88
|
-
async type(selector, text) {
|
|
89
|
-
await this.click(selector);
|
|
90
|
-
return this.keyboard.type(text);
|
|
91
|
-
}
|
|
92
|
-
async fill(selector, text) {
|
|
93
|
-
await this.click(selector, { clickCount: 3 });
|
|
94
|
-
return this.keyboard.type(text);
|
|
95
|
-
}
|
|
96
88
|
scroll(options = {}) {
|
|
97
89
|
return this.send("Human.scroll", cdpParams({ ...options }));
|
|
98
90
|
}
|
|
@@ -1323,6 +1315,20 @@ var Page = class extends Actions {
|
|
|
1323
1315
|
allInnerTexts(selector) {
|
|
1324
1316
|
return this.evaluate("s => Array.from(document.querySelectorAll(s), e => e.innerText)", { args: [selector] });
|
|
1325
1317
|
}
|
|
1318
|
+
async type(selector, text) {
|
|
1319
|
+
await this.click(selector);
|
|
1320
|
+
await this.#validateFocus(selector);
|
|
1321
|
+
return this.keyboard.type(text);
|
|
1322
|
+
}
|
|
1323
|
+
async fill(selector, text) {
|
|
1324
|
+
await this.click(selector, { clickCount: 3 });
|
|
1325
|
+
await this.#validateFocus(selector);
|
|
1326
|
+
if (text === "") return this.keyboard.press("Backspace");
|
|
1327
|
+
return this.keyboard.type(text);
|
|
1328
|
+
}
|
|
1329
|
+
async #validateFocus(selector) {
|
|
1330
|
+
if (!await this.evaluate("s => document.activeElement === document.querySelector(s)", { args: [selector] })) throw new Error(`'${selector}' did not take focus`);
|
|
1331
|
+
}
|
|
1326
1332
|
async getAttribute(selector, name) {
|
|
1327
1333
|
const nodeId = await this.#nodeId(selector);
|
|
1328
1334
|
if (nodeId === void 0) return null;
|
|
@@ -2618,7 +2624,7 @@ var Proxies = class {
|
|
|
2618
2624
|
};
|
|
2619
2625
|
//#endregion
|
|
2620
2626
|
//#region src/version.ts
|
|
2621
|
-
const VERSION = "0.0.
|
|
2627
|
+
const VERSION = "0.0.5";
|
|
2622
2628
|
//#endregion
|
|
2623
2629
|
//#region src/client.ts
|
|
2624
2630
|
function userAgent() {
|