sprntrl 0.1.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 +128 -0
- package/dist/client.d.ts +33 -0
- package/dist/client.js +57 -0
- package/dist/client.js.map +1 -0
- package/dist/core/error.d.ts +46 -0
- package/dist/core/error.js +72 -0
- package/dist/core/error.js.map +1 -0
- package/dist/core/request.d.ts +24 -0
- package/dist/core/request.js +107 -0
- package/dist/core/request.js.map +1 -0
- package/dist/core/resource.d.ts +5 -0
- package/dist/core/resource.js +7 -0
- package/dist/core/resource.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/browser.d.ts +7 -0
- package/dist/lib/browser.js +56 -0
- package/dist/lib/browser.js.map +1 -0
- package/dist/resources/api-keys.d.ts +11 -0
- package/dist/resources/api-keys.js +28 -0
- package/dist/resources/api-keys.js.map +1 -0
- package/dist/resources/ip-whitelist.d.ts +10 -0
- package/dist/resources/ip-whitelist.js +28 -0
- package/dist/resources/ip-whitelist.js.map +1 -0
- package/dist/resources/profiles.d.ts +19 -0
- package/dist/resources/profiles.js +51 -0
- package/dist/resources/profiles.js.map +1 -0
- package/dist/resources/session-files.d.ts +10 -0
- package/dist/resources/session-files.js +36 -0
- package/dist/resources/session-files.js.map +1 -0
- package/dist/resources/sessions.d.ts +72 -0
- package/dist/resources/sessions.js +195 -0
- package/dist/resources/sessions.js.map +1 -0
- package/dist/resources/templates.d.ts +5 -0
- package/dist/resources/templates.js +11 -0
- package/dist/resources/templates.js.map +1 -0
- package/dist/resources/usage.d.ts +6 -0
- package/dist/resources/usage.js +17 -0
- package/dist/resources/usage.js.map +1 -0
- package/dist/resources/user.d.ts +26 -0
- package/dist/resources/user.js +38 -0
- package/dist/resources/user.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Supernatural
|
|
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,128 @@
|
|
|
1
|
+
# Supernatural Node SDK
|
|
2
|
+
|
|
3
|
+
Official Node.js / TypeScript client for the [Supernatural](https://supernatural.sh) stealth browser-as-a-service API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install sprntrl
|
|
9
|
+
# Optional — pick whichever browser automation library you use:
|
|
10
|
+
npm install playwright
|
|
11
|
+
npm install puppeteer
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { Sprntrl } from "sprntrl";
|
|
18
|
+
import type { Browser } from "playwright";
|
|
19
|
+
|
|
20
|
+
const client = new Sprntrl(); // reads SPRNTRL_API_KEY from env
|
|
21
|
+
|
|
22
|
+
const session = await client.sessions.create({ os: "macos", location: "us-east" });
|
|
23
|
+
|
|
24
|
+
// browserSession waits for the session, connects Playwright, and hands you
|
|
25
|
+
// a disposable handle. autoWhitelist registers your IP (CDP is IP-gated).
|
|
26
|
+
const handle = await client.sessions.browserSession(session.id, { autoWhitelist: true });
|
|
27
|
+
try {
|
|
28
|
+
const browser = handle.browser as Browser;
|
|
29
|
+
const page = await browser.contexts()[0].newPage();
|
|
30
|
+
await page.goto("https://bot.sannysoft.com");
|
|
31
|
+
await page.screenshot({ path: "out.png" });
|
|
32
|
+
} finally {
|
|
33
|
+
await handle.close();
|
|
34
|
+
await client.sessions.stop(session.id);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### With `await using` (Node 24+ / TS 5.2+)
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
{
|
|
42
|
+
await using handle = await client.sessions.browserSession(session.id, { autoWhitelist: true });
|
|
43
|
+
const page = await handle.browser.contexts()[0].newPage();
|
|
44
|
+
await page.goto("https://example.com");
|
|
45
|
+
} // browser auto-closes here
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Puppeteer
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const handle = await client.sessions.browserSession(session.id, {
|
|
52
|
+
framework: "puppeteer",
|
|
53
|
+
autoWhitelist: true,
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Lower-level `connect()` and `cdpUrl()`
|
|
58
|
+
|
|
59
|
+
If you want to manage the browser lifecycle yourself:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const browser = await client.sessions.connect(session.id, { autoWhitelist: true });
|
|
63
|
+
// ...
|
|
64
|
+
await browser.close();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or get the raw CDP WebSocket URL for any CDP client (chrome-remote-interface, raw ws, etc.):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const url = client.sessions.cdpUrl(session.id);
|
|
71
|
+
// wss://api.supernatural.sh/api/v1/sessions/<id>/cdp
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuration
|
|
75
|
+
|
|
76
|
+
| Env var | Default |
|
|
77
|
+
|---------------------|----------------------------|
|
|
78
|
+
| `SPRNTRL_API_KEY` | — |
|
|
79
|
+
| `SPRNTRL_BASE_URL` | `https://api.supernatural.sh` |
|
|
80
|
+
|
|
81
|
+
Or per client:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const client = new Sprntrl({
|
|
85
|
+
apiKey: "sk_...",
|
|
86
|
+
baseURL: "https://api.supernatural.sh",
|
|
87
|
+
timeout: 30_000,
|
|
88
|
+
maxRetries: 2,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Resources
|
|
93
|
+
|
|
94
|
+
- `client.sessions` — `create`, `list`, `listActive`, `listHistory`, `listResumable`, `listLocations`, `get`, `stop`, `resume`, `deletePersistent`, `waitUntilReady`, `connect`, `browserSession`, `cdpUrl`
|
|
95
|
+
- `client.sessions.files` — `list`, `download`, `upload`
|
|
96
|
+
- `client.profiles` — `create`, `list`, `get`, `update`, `duplicate`, `delete`
|
|
97
|
+
- `client.templates.list()`
|
|
98
|
+
- `client.ipWhitelist` — `list`, `add`, `remove`
|
|
99
|
+
- `client.usage` — `current`, `history`
|
|
100
|
+
- `client.user` — `me`, `update`, `updateSettings`, `changePassword`
|
|
101
|
+
- `client.apiKeys` — `list`, `create` (full key returned ONCE), `revoke`
|
|
102
|
+
|
|
103
|
+
## Error handling
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { Sprntrl, APIError, RateLimitError, AuthenticationError } from "sprntrl";
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await client.sessions.create({ os: "macos", location: "us-east" });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err instanceof RateLimitError) console.log("rate limited", err.status);
|
|
112
|
+
else if (err instanceof AuthenticationError) console.log("bad API key");
|
|
113
|
+
else if (err instanceof APIError) console.log("api error", err.status, err.body);
|
|
114
|
+
else throw err;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Transient errors (5xx, 429, 408, connection errors) are retried automatically up to `maxRetries` with exponential backoff.
|
|
119
|
+
|
|
120
|
+
## Gotchas
|
|
121
|
+
|
|
122
|
+
- **CDP access is IP-whitelist gated.** The WebSocket at `/api/v1/sessions/:id/cdp` does not accept bearer auth — instead your public IP (as Cloudflare sees it) must be in your account's whitelist. Use `client.ipWhitelist.add("current")` or pass `{ autoWhitelist: true }` to `sessions.connect`.
|
|
123
|
+
- **Sessions start async.** `sessions.create` returns immediately with `status: "creating"`. Call `sessions.waitUntilReady(id)` before connecting, or just use `sessions.connect()` which waits for you.
|
|
124
|
+
- **API key is shown only once.** `apiKeys.create()` returns the full `key` field exactly once — store it immediately.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type RequestOptions } from "./core/request.js";
|
|
2
|
+
import { Sessions } from "./resources/sessions.js";
|
|
3
|
+
import { Profiles } from "./resources/profiles.js";
|
|
4
|
+
import { Templates } from "./resources/templates.js";
|
|
5
|
+
import { IPWhitelist } from "./resources/ip-whitelist.js";
|
|
6
|
+
import { Usage } from "./resources/usage.js";
|
|
7
|
+
import { User } from "./resources/user.js";
|
|
8
|
+
import { APIKeys } from "./resources/api-keys.js";
|
|
9
|
+
export interface ClientOptions {
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
baseURL?: string;
|
|
12
|
+
timeout?: number;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
defaultHeaders?: Record<string, string>;
|
|
15
|
+
fetch?: typeof fetch;
|
|
16
|
+
}
|
|
17
|
+
export declare class Sprntrl {
|
|
18
|
+
readonly apiKey: string;
|
|
19
|
+
readonly baseURL: string;
|
|
20
|
+
readonly timeout: number;
|
|
21
|
+
readonly maxRetries: number;
|
|
22
|
+
private readonly _config;
|
|
23
|
+
readonly sessions: Sessions;
|
|
24
|
+
readonly profiles: Profiles;
|
|
25
|
+
readonly templates: Templates;
|
|
26
|
+
readonly ipWhitelist: IPWhitelist;
|
|
27
|
+
readonly usage: Usage;
|
|
28
|
+
readonly user: User;
|
|
29
|
+
readonly apiKeys: APIKeys;
|
|
30
|
+
constructor(opts?: ClientOptions);
|
|
31
|
+
/** Low-level request escape hatch. Resources use this under the hood. */
|
|
32
|
+
request<T = unknown>(opts: RequestOptions): Promise<T>;
|
|
33
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SprntrlError } from "./core/error.js";
|
|
2
|
+
import { request as coreRequest } from "./core/request.js";
|
|
3
|
+
import { Sessions } from "./resources/sessions.js";
|
|
4
|
+
import { Profiles } from "./resources/profiles.js";
|
|
5
|
+
import { Templates } from "./resources/templates.js";
|
|
6
|
+
import { IPWhitelist } from "./resources/ip-whitelist.js";
|
|
7
|
+
import { Usage } from "./resources/usage.js";
|
|
8
|
+
import { User } from "./resources/user.js";
|
|
9
|
+
import { APIKeys } from "./resources/api-keys.js";
|
|
10
|
+
const DEFAULT_BASE_URL = "https://api.supernatural.sh";
|
|
11
|
+
const DEFAULT_TIMEOUT = 60_000;
|
|
12
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
13
|
+
export class Sprntrl {
|
|
14
|
+
apiKey;
|
|
15
|
+
baseURL;
|
|
16
|
+
timeout;
|
|
17
|
+
maxRetries;
|
|
18
|
+
_config;
|
|
19
|
+
sessions;
|
|
20
|
+
profiles;
|
|
21
|
+
templates;
|
|
22
|
+
ipWhitelist;
|
|
23
|
+
usage;
|
|
24
|
+
user;
|
|
25
|
+
apiKeys;
|
|
26
|
+
constructor(opts = {}) {
|
|
27
|
+
const apiKey = opts.apiKey ?? process.env.SPRNTRL_API_KEY;
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
throw new SprntrlError("No API key provided. Pass apiKey or set SPRNTRL_API_KEY.");
|
|
30
|
+
}
|
|
31
|
+
const baseURL = (opts.baseURL ?? process.env.SPRNTRL_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
32
|
+
this.apiKey = apiKey;
|
|
33
|
+
this.baseURL = baseURL;
|
|
34
|
+
this.timeout = opts.timeout ?? DEFAULT_TIMEOUT;
|
|
35
|
+
this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
36
|
+
this._config = {
|
|
37
|
+
apiKey,
|
|
38
|
+
baseURL,
|
|
39
|
+
timeout: this.timeout,
|
|
40
|
+
maxRetries: this.maxRetries,
|
|
41
|
+
defaultHeaders: opts.defaultHeaders ?? {},
|
|
42
|
+
fetch: opts.fetch,
|
|
43
|
+
};
|
|
44
|
+
this.sessions = new Sessions(this);
|
|
45
|
+
this.profiles = new Profiles(this);
|
|
46
|
+
this.templates = new Templates(this);
|
|
47
|
+
this.ipWhitelist = new IPWhitelist(this);
|
|
48
|
+
this.usage = new Usage(this);
|
|
49
|
+
this.user = new User(this);
|
|
50
|
+
this.apiKeys = new APIKeys(this);
|
|
51
|
+
}
|
|
52
|
+
/** Low-level request escape hatch. Resources use this under the hood. */
|
|
53
|
+
request(opts) {
|
|
54
|
+
return coreRequest(this._config, opts);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,OAAO,IAAI,WAAW,EAA2C,MAAM,mBAAmB,CAAC;AACpG,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAWlD,MAAM,gBAAgB,GAAG,6BAA6B,CAAC;AACvD,MAAM,eAAe,GAAG,MAAM,CAAC;AAC/B,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,MAAM,OAAO,OAAO;IACT,MAAM,CAAS;IACf,OAAO,CAAS;IAChB,OAAO,CAAS;IAChB,UAAU,CAAS;IAEX,OAAO,CAAgB;IAE/B,QAAQ,CAAW;IACnB,QAAQ,CAAW;IACnB,SAAS,CAAY;IACrB,WAAW,CAAc;IACzB,KAAK,CAAQ;IACb,IAAI,CAAO;IACX,OAAO,CAAU;IAE1B,YAAY,OAAsB,EAAE;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,YAAY,CACpB,0DAA0D,CAC3D,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,gBAAgB,CAAC,CAAC,OAAO,CACxF,MAAM,EACN,EAAE,CACH,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC;QAC/C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC;QAEzD,IAAI,CAAC,OAAO,GAAG;YACb,MAAM;YACN,OAAO;YACP,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE;YACzC,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,yEAAyE;IACzE,OAAO,CAAc,IAAoB;QACvC,OAAO,WAAW,CAAI,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;CACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare class SprntrlError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare class APIError extends SprntrlError {
|
|
5
|
+
readonly status?: number;
|
|
6
|
+
readonly body?: unknown;
|
|
7
|
+
readonly headers?: Headers;
|
|
8
|
+
constructor(message: string, opts?: {
|
|
9
|
+
status?: number;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
headers?: Headers;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export declare class BadRequestError extends APIError {
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class AuthenticationError extends APIError {
|
|
18
|
+
name: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class PermissionDeniedError extends APIError {
|
|
21
|
+
name: string;
|
|
22
|
+
}
|
|
23
|
+
export declare class NotFoundError extends APIError {
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
export declare class ConflictError extends APIError {
|
|
27
|
+
name: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class UnprocessableEntityError extends APIError {
|
|
30
|
+
name: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class RateLimitError extends APIError {
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
export declare class InternalServerError extends APIError {
|
|
36
|
+
name: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class APIConnectionError extends APIError {
|
|
39
|
+
constructor(message?: string, opts?: {
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export declare class APIConnectionTimeoutError extends APIConnectionError {
|
|
44
|
+
constructor(message?: string);
|
|
45
|
+
}
|
|
46
|
+
export declare function errorForStatus(status: number, message: string, body?: unknown, headers?: Headers): APIError;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export class SprntrlError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "SprntrlError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class APIError extends SprntrlError {
|
|
8
|
+
status;
|
|
9
|
+
body;
|
|
10
|
+
headers;
|
|
11
|
+
constructor(message, opts = {}) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "APIError";
|
|
14
|
+
this.status = opts.status;
|
|
15
|
+
this.body = opts.body;
|
|
16
|
+
this.headers = opts.headers;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class BadRequestError extends APIError {
|
|
20
|
+
name = "BadRequestError";
|
|
21
|
+
}
|
|
22
|
+
export class AuthenticationError extends APIError {
|
|
23
|
+
name = "AuthenticationError";
|
|
24
|
+
}
|
|
25
|
+
export class PermissionDeniedError extends APIError {
|
|
26
|
+
name = "PermissionDeniedError";
|
|
27
|
+
}
|
|
28
|
+
export class NotFoundError extends APIError {
|
|
29
|
+
name = "NotFoundError";
|
|
30
|
+
}
|
|
31
|
+
export class ConflictError extends APIError {
|
|
32
|
+
name = "ConflictError";
|
|
33
|
+
}
|
|
34
|
+
export class UnprocessableEntityError extends APIError {
|
|
35
|
+
name = "UnprocessableEntityError";
|
|
36
|
+
}
|
|
37
|
+
export class RateLimitError extends APIError {
|
|
38
|
+
name = "RateLimitError";
|
|
39
|
+
}
|
|
40
|
+
export class InternalServerError extends APIError {
|
|
41
|
+
name = "InternalServerError";
|
|
42
|
+
}
|
|
43
|
+
export class APIConnectionError extends APIError {
|
|
44
|
+
constructor(message = "Connection error", opts = {}) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "APIConnectionError";
|
|
47
|
+
if (opts.cause !== undefined)
|
|
48
|
+
this.cause = opts.cause;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class APIConnectionTimeoutError extends APIConnectionError {
|
|
52
|
+
constructor(message = "Request timed out") {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "APIConnectionTimeoutError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function errorForStatus(status, message, body, headers) {
|
|
58
|
+
const opts = { status, body, headers };
|
|
59
|
+
switch (status) {
|
|
60
|
+
case 400: return new BadRequestError(message, opts);
|
|
61
|
+
case 401: return new AuthenticationError(message, opts);
|
|
62
|
+
case 403: return new PermissionDeniedError(message, opts);
|
|
63
|
+
case 404: return new NotFoundError(message, opts);
|
|
64
|
+
case 409: return new ConflictError(message, opts);
|
|
65
|
+
case 422: return new UnprocessableEntityError(message, opts);
|
|
66
|
+
case 429: return new RateLimitError(message, opts);
|
|
67
|
+
}
|
|
68
|
+
if (status >= 500)
|
|
69
|
+
return new InternalServerError(message, opts);
|
|
70
|
+
return new APIError(message, opts);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/core/error.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface RequestConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseURL: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
defaultHeaders: Record<string, string>;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
11
|
+
path: string;
|
|
12
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
/** If body is FormData/Blob/ArrayBuffer, don't set content-type/JSON-encode. */
|
|
16
|
+
rawBody?: boolean;
|
|
17
|
+
/** If true, return the raw Response (for binary downloads). */
|
|
18
|
+
stream?: boolean;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
/** Per-request override. */
|
|
21
|
+
timeout?: number;
|
|
22
|
+
maxRetries?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function request<T = unknown>(config: RequestConfig, opts: RequestOptions): Promise<T>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { APIConnectionError, APIConnectionTimeoutError, APIError, errorForStatus, } from "./error.js";
|
|
2
|
+
const USER_AGENT = "sprntrl-node/0.1.0";
|
|
3
|
+
function buildURL(baseURL, path, query) {
|
|
4
|
+
const url = path.startsWith("http") ? new URL(path) : new URL(path, baseURL);
|
|
5
|
+
if (query) {
|
|
6
|
+
for (const [k, v] of Object.entries(query)) {
|
|
7
|
+
if (v !== undefined && v !== null)
|
|
8
|
+
url.searchParams.set(k, String(v));
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return url.toString();
|
|
12
|
+
}
|
|
13
|
+
function shouldRetry(status) {
|
|
14
|
+
if (status === null)
|
|
15
|
+
return true;
|
|
16
|
+
return status === 408 || status === 409 || status === 429 || status >= 500;
|
|
17
|
+
}
|
|
18
|
+
function backoff(attempt) {
|
|
19
|
+
const base = 500 * 2 ** attempt;
|
|
20
|
+
return base + Math.floor(Math.random() * 250);
|
|
21
|
+
}
|
|
22
|
+
async function parseError(response) {
|
|
23
|
+
let body;
|
|
24
|
+
try {
|
|
25
|
+
body = await response.clone().json();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
body = await response.clone().text().catch(() => undefined);
|
|
29
|
+
}
|
|
30
|
+
let message;
|
|
31
|
+
if (body && typeof body === "object") {
|
|
32
|
+
const b = body;
|
|
33
|
+
message = b.error || b.message || b.detail;
|
|
34
|
+
}
|
|
35
|
+
return { message: message ?? `HTTP ${response.status}`, body };
|
|
36
|
+
}
|
|
37
|
+
export async function request(config, opts) {
|
|
38
|
+
const url = buildURL(config.baseURL, opts.path, opts.query);
|
|
39
|
+
const headers = {
|
|
40
|
+
Authorization: `ApiKey ${config.apiKey}`,
|
|
41
|
+
Accept: "application/json",
|
|
42
|
+
"User-Agent": USER_AGENT,
|
|
43
|
+
...config.defaultHeaders,
|
|
44
|
+
...(opts.headers ?? {}),
|
|
45
|
+
};
|
|
46
|
+
let init = { method: opts.method, headers };
|
|
47
|
+
if (opts.body !== undefined) {
|
|
48
|
+
if (opts.rawBody) {
|
|
49
|
+
init.body = opts.body;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
headers["Content-Type"] = "application/json";
|
|
53
|
+
init.body = JSON.stringify(opts.body);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const fetchFn = config.fetch ?? fetch;
|
|
57
|
+
const timeout = opts.timeout ?? config.timeout;
|
|
58
|
+
const maxRetries = opts.maxRetries ?? config.maxRetries;
|
|
59
|
+
let lastErr;
|
|
60
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const timer = setTimeout(() => controller.abort(new Error("timeout")), timeout);
|
|
63
|
+
const signal = opts.signal
|
|
64
|
+
? AbortSignal.any([opts.signal, controller.signal])
|
|
65
|
+
: controller.signal;
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetchFn(url, { ...init, signal });
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
if (response.ok) {
|
|
70
|
+
if (opts.stream)
|
|
71
|
+
return response;
|
|
72
|
+
if (response.status === 204)
|
|
73
|
+
return undefined;
|
|
74
|
+
const ctype = response.headers.get("content-type") ?? "";
|
|
75
|
+
if (ctype.includes("application/json"))
|
|
76
|
+
return (await response.json());
|
|
77
|
+
return (await response.arrayBuffer());
|
|
78
|
+
}
|
|
79
|
+
if (shouldRetry(response.status) && attempt < maxRetries) {
|
|
80
|
+
await sleep(backoff(attempt));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const { message, body } = await parseError(response);
|
|
84
|
+
throw errorForStatus(response.status, message, body, response.headers);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
if (err instanceof APIError)
|
|
89
|
+
throw err;
|
|
90
|
+
// network/abort
|
|
91
|
+
const isTimeout = err?.name === "AbortError";
|
|
92
|
+
lastErr = isTimeout
|
|
93
|
+
? new APIConnectionTimeoutError()
|
|
94
|
+
: new APIConnectionError(err instanceof Error ? err.message : "Connection error", { cause: err });
|
|
95
|
+
if (attempt < maxRetries) {
|
|
96
|
+
await sleep(backoff(attempt));
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
throw lastErr;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
throw lastErr;
|
|
103
|
+
}
|
|
104
|
+
function sleep(ms) {
|
|
105
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/core/request.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/core/resource.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,WAAW;IACZ,OAAO,CAAU;IAE3B,YAAY,MAAe;QACzB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Sprntrl, type ClientOptions } from "./client.js";
|
|
2
|
+
export { SprntrlError, APIError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, APIConnectionError, APIConnectionTimeoutError, } from "./core/error.js";
|
|
3
|
+
export type { OS, ProxyProtocol, ProxyConfig, SessionStatus, Session, ProxySummary, PaginatedSessions, Profile, Template, IPWhitelistEntry, APIKey, APIKeyCreated, Usage, UsageMonth, User, AccountStatus, FileInfo, } from "./types.js";
|
|
4
|
+
export type { SessionCreateParams, ConnectOptions, BrowserHandle } from "./resources/sessions.js";
|
|
5
|
+
export type { UserSettings, ChangePasswordResult } from "./resources/user.js";
|
|
6
|
+
export type { ProfileCreateParams } from "./resources/profiles.js";
|
|
7
|
+
export type { FileContent } from "./resources/session-files.js";
|
|
8
|
+
export { Sprntrl as default } from "./client.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { Sprntrl } from "./client.js";
|
|
2
|
+
export { SprntrlError, APIError, BadRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, APIConnectionError, APIConnectionTimeoutError, } from "./core/error.js";
|
|
3
|
+
export { Sprntrl as default } from "./client.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAsB,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,aAAa,EACb,wBAAwB,EACxB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AAyBzB,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Sprntrl } from "../client.js";
|
|
2
|
+
import type { Session } from "../types.js";
|
|
3
|
+
export interface ConnectArgs {
|
|
4
|
+
framework?: "playwright" | "puppeteer";
|
|
5
|
+
autoWhitelist?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function connect(client: Sprntrl, session: Session, opts?: ConnectArgs): Promise<unknown>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SprntrlError } from "../core/error.js";
|
|
2
|
+
function cdpUrlFor(client, session) {
|
|
3
|
+
// Build the URL from the client's base URL. The API proxies the WebSocket
|
|
4
|
+
// through /api/v1/sessions/:id/cdp with an IP-whitelist check — this is what
|
|
5
|
+
// external callers must hit. The server also returns session.cdp_url but
|
|
6
|
+
// that's an internal host:port that isn't reachable from outside the API host.
|
|
7
|
+
const base = new URL(client.baseURL);
|
|
8
|
+
const scheme = base.protocol === "https:" ? "wss:" : "ws:";
|
|
9
|
+
return `${scheme}//${base.host}/api/v1/sessions/${session.id}/cdp`;
|
|
10
|
+
}
|
|
11
|
+
export async function connect(client, session, opts = {}) {
|
|
12
|
+
const framework = opts.framework ?? "playwright";
|
|
13
|
+
if (opts.autoWhitelist) {
|
|
14
|
+
try {
|
|
15
|
+
await client.request({
|
|
16
|
+
method: "POST",
|
|
17
|
+
path: "/api/v1/settings/ip-whitelist",
|
|
18
|
+
body: { ip: "current" },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Already-whitelisted or race; the connect attempt will surface real errors.
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const cdpUrl = cdpUrlFor(client, session);
|
|
26
|
+
// Use a variable-indirected import so TypeScript does not resolve the
|
|
27
|
+
// optional peer dependency's types at compile time.
|
|
28
|
+
const dynImport = (name) => import(name);
|
|
29
|
+
if (framework === "playwright") {
|
|
30
|
+
let mod;
|
|
31
|
+
try {
|
|
32
|
+
mod = (await dynImport("playwright"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new SprntrlError("playwright is not installed. Run `npm install playwright`.");
|
|
36
|
+
}
|
|
37
|
+
return mod.chromium.connectOverCDP(cdpUrl);
|
|
38
|
+
}
|
|
39
|
+
if (framework === "puppeteer") {
|
|
40
|
+
let mod;
|
|
41
|
+
try {
|
|
42
|
+
mod = (await dynImport("puppeteer-core"));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
try {
|
|
46
|
+
mod = (await dynImport("puppeteer"));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new SprntrlError("puppeteer is not installed. Run `npm install puppeteer` (or puppeteer-core).");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return mod.connect({ browserWSEndpoint: cdpUrl });
|
|
53
|
+
}
|
|
54
|
+
throw new SprntrlError(`Unsupported framework '${framework}'`);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/lib/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAShD,SAAS,SAAS,CAAC,MAAe,EAAE,OAAgB;IAClD,0EAA0E;IAC1E,6EAA6E;IAC7E,yEAAyE;IACzE,+EAA+E;IAC/E,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3D,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC,IAAI,oBAAoB,OAAO,CAAC,EAAE,MAAM,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAe,EACf,OAAgB,EAChB,OAAoB,EAAE;IAEtB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,YAAY,CAAC;IACjD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC;gBACnB,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,+BAA+B;gBACrC,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;aACxB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,6EAA6E;QAC/E,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1C,sEAAsE;IACtE,oDAAoD;IACpD,MAAM,SAAS,GAAuC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE7E,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;QAC/B,IAAI,GAAwE,CAAC;QAC7E,IAAI,CAAC;YACH,GAAG,GAAG,CAAC,MAAM,SAAS,CAAC,YAAY,CAAC,CAAe,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,YAAY,CACpB,4DAA4D,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,SAAS,KAAK,WAAW,EAAE,CAAC;QAC9B,IAAI,GAA2E,CAAC;QAChF,IAAI,CAAC;YACH,GAAG,GAAG,CAAC,MAAM,SAAS,CAAC,gBAAgB,CAAC,CAAe,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,GAAG,GAAG,CAAC,MAAM,SAAS,CAAC,WAAW,CAAC,CAAe,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,YAAY,CACpB,8EAA8E,CAC/E,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC,OAAO,CAAC,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,IAAI,YAAY,CAAC,0BAA0B,SAAS,GAAG,CAAC,CAAC;AACjE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { APIResource } from "../core/resource.js";
|
|
2
|
+
import type { APIKey, APIKeyCreated } from "../types.js";
|
|
3
|
+
export declare class APIKeys extends APIResource {
|
|
4
|
+
list(): Promise<APIKey[]>;
|
|
5
|
+
/**
|
|
6
|
+
* Create an API key. The full `key` field is returned ONLY on creation —
|
|
7
|
+
* store it immediately; the server cannot show it again.
|
|
8
|
+
*/
|
|
9
|
+
create(name: string): Promise<APIKeyCreated>;
|
|
10
|
+
revoke(keyId: string): Promise<void>;
|
|
11
|
+
}
|