@scalebrowser/sdk 0.2.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/LICENSE +21 -0
- package/README.md +168 -0
- package/dist/index.cjs +1264 -0
- package/dist/index.d.cts +1613 -0
- package/dist/index.d.ts +1613 -0
- package/dist/index.js +1239 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scalebrowser
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @scalebrowser/sdk
|
|
2
|
+
|
|
3
|
+
Official **Node / TypeScript SDK** for the [Scalebrowser](https://scalebrowser.net)
|
|
4
|
+
daemon: a typed REST client plus a **direct-CDP driver plane**.
|
|
5
|
+
|
|
6
|
+
The driver plane is deliberately **direct-CDP (nodriver-style), not Playwright /
|
|
7
|
+
Puppeteer**: anti-bot stacks block the Playwright control plane independently of
|
|
8
|
+
how good the browser patches are, so this SDK speaks the Chrome DevTools
|
|
9
|
+
Protocol over a raw websocket and never calls `Runtime.enable`.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @scalebrowser/sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Ships dual ESM + CJS with full type declarations, and needs Node ≥ 18. The SDK
|
|
18
|
+
is MIT-licensed; the daemon it talks to is a separate, licensed product.
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { ScalebrowserClient } from '@scalebrowser/sdk';
|
|
24
|
+
|
|
25
|
+
const sb = new ScalebrowserClient({
|
|
26
|
+
baseUrl: 'http://127.0.0.1:8787', // daemon native API (default)
|
|
27
|
+
token: process.env.SCALEBROWSER_TOKEN,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Create a coherent, per-seed-unlinkable profile.
|
|
31
|
+
const profile = await sb.createProfile({ name: 'acct-01' });
|
|
32
|
+
|
|
33
|
+
// Start it + open a direct-CDP session. `await using` stops the profile and
|
|
34
|
+
// closes CDP when the block exits.
|
|
35
|
+
await using session = await sb.launch(profile.id, { headless: true });
|
|
36
|
+
|
|
37
|
+
await session.cdp.navigate('https://example.com');
|
|
38
|
+
const title = await session.cdp.evaluate<string>('document.title');
|
|
39
|
+
|
|
40
|
+
// Humanized, trusted input is routed through the daemon (real OS/CDP events).
|
|
41
|
+
await session.cdp.humanizeMove(120, 220);
|
|
42
|
+
await session.cdp.humanizeClick(120, 220);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Without `await using` (or for explicit control):
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const { cdp_ws } = await sb.startProfile(profile.id, { headless: true });
|
|
49
|
+
const cdp = await sb.connectCdp(cdp_ws, profile.id);
|
|
50
|
+
try {
|
|
51
|
+
await cdp.navigate('https://example.com');
|
|
52
|
+
} finally {
|
|
53
|
+
cdp.close();
|
|
54
|
+
await sb.stopProfile(profile.id);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## REST surface
|
|
59
|
+
|
|
60
|
+
Every `/v1` endpoint is a typed method on `ScalebrowserClient`:
|
|
61
|
+
|
|
62
|
+
- **Profiles** — `listProfiles`, `getProfile`, `createProfile`, `updateProfile`,
|
|
63
|
+
`deleteProfile`, `startProfile`, `stopProfile`
|
|
64
|
+
- **Bulk** — `bulkCreateProfiles`, `bulkStart`, `bulkStop`, `bulkDelete`,
|
|
65
|
+
`bulkAssignProxy`
|
|
66
|
+
- **Groups / Presets** — `listGroups`/`createGroup`/`getGroup`/`updateGroup`/`deleteGroup`,
|
|
67
|
+
`listPresets`/`createPreset`/`getPreset`/`updatePreset`/`deletePreset`,
|
|
68
|
+
`getPersonaConstraints`. A preset is `config` (what the profiles do:
|
|
69
|
+
`geo_mode`, `proxy_id`, …) plus `constraints` (what they are: `country`, which
|
|
70
|
+
pins the persona's language, timezone and voices). Both are typed and the daemon
|
|
71
|
+
refuses an unknown key with a 400 — read the valid regions from
|
|
72
|
+
`getPersonaConstraints()` rather than hardcoding them.
|
|
73
|
+
- **Proxies** — `listProxies`, `createProxy`, `getProxy`, `updateProxy`,
|
|
74
|
+
`deleteProxy`, `checkProxy`, `checkProxyConfig` (probe a config before you save
|
|
75
|
+
it; pass `id` to reuse an existing proxy's stored credentials) — credentials
|
|
76
|
+
are write-only, never returned
|
|
77
|
+
- **Extensions** — `listExtensions`, `attachExtension`, `detachExtension`, plus
|
|
78
|
+
the daemon-wide library (`uploadExtension`, `getLibraryExtension`,
|
|
79
|
+
`deleteLibraryExtension`). An attached package IS loaded into the browser at
|
|
80
|
+
launch, under the canonical Web-Store id its own key derives
|
|
81
|
+
- **Credentials** — `listCredentials`, `putCredential`, `revealCredential`
|
|
82
|
+
(needs the vault password), `exportCredentials`, `importCredentials`
|
|
83
|
+
- **Cookies** — `revealCookies`, the one route a cookie VALUE leaves through,
|
|
84
|
+
behind the same vault password
|
|
85
|
+
- **Sessions** — `exportSession`, `importSession`
|
|
86
|
+
- **Mailboxes** — `listInboxes`, `createInbox`, `updateInbox`, `deleteInbox`,
|
|
87
|
+
`getInboxBindings`, `bindInbox`, `unbindInbox` — where a profile's
|
|
88
|
+
confirmation codes arrive
|
|
89
|
+
- **Passkeys** — `listPasskeys`, `deletePasskey`. Metadata only: the private key
|
|
90
|
+
has no field and no endpoint
|
|
91
|
+
- **Agent runs** — `listRuns`, `getRun`, `listRunSteps`, `getRunShot`,
|
|
92
|
+
`getActivity`. Read-only, all of it
|
|
93
|
+
- **Interruptions** — `listInterruptionLocks`, `setInterruptionLock`,
|
|
94
|
+
`listInterruptionRules`, `setInterruptionRule`, `deleteInterruptionRule` — who
|
|
95
|
+
may answer when the browser asks something
|
|
96
|
+
- **Artifacts** — `putArtifact` (hand the daemon a file to upload later),
|
|
97
|
+
`getArtifact` (fetch a screenshot, download or saved PDF as bytes)
|
|
98
|
+
- **Input** — `sendInput`
|
|
99
|
+
- **Metrics / Account / Health** — `getMetrics`, `getAccount`, `health`, `ready`.
|
|
100
|
+
The first two answer 404 on a daemon without them, which the SDK treats as
|
|
101
|
+
information rather than as an error: metrics are then derived from profile
|
|
102
|
+
state, and the account reads `licensed: false`, which is what "self-hosted, no
|
|
103
|
+
control plane" means
|
|
104
|
+
|
|
105
|
+
Errors map to typed exceptions carrying the daemon's numeric code (4001–4010):
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { ApiError, ErrorCode } from '@scalebrowser/sdk';
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await sb.startProfile(id);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof ApiError && err.code === ErrorCode.Capacity) {
|
|
114
|
+
// 4003 — capacity exceeded
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Lifecycle events (SSE)
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// callback form
|
|
123
|
+
const unsubscribe = sb.subscribeEvents({
|
|
124
|
+
onEvent: (e) => {
|
|
125
|
+
if (e.type === 'profile_crashed') console.warn(e.profile_id, e.reason);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// or async-iterator form
|
|
130
|
+
for await (const e of sb.events()) {
|
|
131
|
+
if (e.type === 'profile_started') console.log(e.cdp_ws);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Direct-CDP driver
|
|
136
|
+
|
|
137
|
+
`CdpSession` (returned by `connectCdp` / `launch`) gives you:
|
|
138
|
+
|
|
139
|
+
- `send<T>(method, params?)` — raw CDP command, awaited by id (concurrent-safe)
|
|
140
|
+
- `navigate(url, { wait, timeout })`, `evaluate<T>(expr, { isolated })`
|
|
141
|
+
- `createIsolatedWorld(frameId?, worldName?)`
|
|
142
|
+
- `attachToPage({ targetId?, create?, url? })`
|
|
143
|
+
- `on(method, cb)` / `events()` — CDP event subscription scoped to the session
|
|
144
|
+
- `humanizeMove` / `humanizeClick` / `humanizeType` / `humanizeScroll`
|
|
145
|
+
- `close()`
|
|
146
|
+
|
|
147
|
+
## Security
|
|
148
|
+
|
|
149
|
+
- Bearer token is sent on every request and the SSE/CDP handshakes; it is
|
|
150
|
+
**never logged** (the client's inspect output masks it).
|
|
151
|
+
- Proxy credentials are injected by the daemon and never travel over CDP.
|
|
152
|
+
|
|
153
|
+
## Develop
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm install
|
|
157
|
+
npm run typecheck # tsc --noEmit (strict)
|
|
158
|
+
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
|
|
159
|
+
npm test # vitest (unit; e2e skipped unless SCALEBROWSER_E2E=1)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The env-gated end-to-end test runs the full path against a real daemon + mock
|
|
163
|
+
engine:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
SCALEBROWSER_E2E=1 SCALEBROWSER_BASE_URL=http://127.0.0.1:8787 \
|
|
167
|
+
SCALEBROWSER_TOKEN=… npm test
|
|
168
|
+
```
|