@solarisdk/browserbase-compat 0.0.0-stage → 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/COMPAT.md +65 -0
- package/LICENSE +21 -0
- package/README.md +25 -2
- package/dist/client.d.ts +93 -0
- package/dist/client.js +284 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +29 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/mapping.d.ts +17 -0
- package/dist/mapping.js +115 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +1 -0
- package/package.json +26 -4
package/COMPAT.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Browserbase compatibility — what works, what differs, what raises
|
|
2
|
+
|
|
3
|
+
`@solarisdk/browserbase-compat` gives Solari Browser the shape of the Browserbase SDK, so code written against
|
|
4
|
+
`new Browserbase({ apiKey })` runs against Solari with the import and the key changed. This page is the full list.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import Browserbase from "@solarisdk/browserbase-compat";
|
|
8
|
+
import { chromium } from "playwright-core";
|
|
9
|
+
|
|
10
|
+
const bb = new Browserbase({ apiKey: process.env.SOLARI_API_KEY }); // slr_… key
|
|
11
|
+
const session = await bb.sessions.create({ browserSettings: { recordSession: true } });
|
|
12
|
+
const browser = await chromium.connectOverCDP(session.connectUrl); // Puppeteer: puppeteer.connect({ browserWSEndpoint: session.connectUrl })
|
|
13
|
+
// … drive the page …
|
|
14
|
+
await browser.close();
|
|
15
|
+
await bb.sessions.update(session.id, { status: "REQUEST_RELEASE" });
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`connectUrl` is a raw CDP websocket URL, so any CDP client works and **nothing needs a pinned Playwright version**.
|
|
19
|
+
Keep it private: the session id inside it is the credential.
|
|
20
|
+
|
|
21
|
+
## Keys and endpoint
|
|
22
|
+
| | |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Key | `apiKey` option, else `SOLARI_API_KEY`, else `BROWSERBASE_API_KEY`. **A key that does not start with `slr_` is never used or sent** (it is probably a Browserbase secret): it is skipped with a warning that does not print it, and if no `slr_` key remains the constructor throws. |
|
|
25
|
+
| Endpoint | `baseURL` option or `SOLARI_BASE_URL`; default `https://api.getsolari.com`. |
|
|
26
|
+
| `projectId` | Accepted and ignored: Solari keys belong to an organisation, there are no projects. |
|
|
27
|
+
|
|
28
|
+
## Works
|
|
29
|
+
| Browserbase | Here |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `sessions.create` | `POST /sessions`; returns `connectUrl` (CDP), `id`, `status: "RUNNING"`, `expiresAt`, `region`, `keepAlive`, `contextId`. A create is retried safely (idempotency key). |
|
|
32
|
+
| `sessions.retrieve`, `sessions.list({ status })` | Solari `active/released/expired/unknown` map to `RUNNING/COMPLETED/TIMED_OUT/ERROR`. `proxyBytes` is `0`, `startedAt` is the create time. |
|
|
33
|
+
| `sessions.update(id, { status: "REQUEST_RELEASE" })` | Releases the session. |
|
|
34
|
+
| `proxies: true`, `proxies: [{ type: "browserbase", geolocation }]` | A managed proxy; `geolocation.country/state/city` map across (a proxy needs the stealth browser, so `stealth` is switched on for you). `{ type: "none" }` means no proxy. |
|
|
35
|
+
| `browserSettings.verified`, `advancedStealth` | Solari's stealth browser. |
|
|
36
|
+
| `browserSettings.solveCaptchas: true` | Solari captcha solving (stealth browser, plan-gated). **Off unless you pass `true`**: Browserbase turns it on by default. |
|
|
37
|
+
| `browserSettings.recordSession: true` | Session recording. **Off unless you pass `true`**: Browserbase records by default. Read it back with `sessions.recording.retrieve(id)` (rrweb events). |
|
|
38
|
+
| `browserSettings.allowedDomains` | Egress allowlist (enterprise plan, stealth browser); a plan error comes back as an `APIError` with `code: "FeatureRequiresPlan"`. |
|
|
39
|
+
| `browserSettings.context: { id, persist }`, `contexts.create/retrieve/delete` | A Context is a Solari **profile**. See [Contexts](#contexts). |
|
|
40
|
+
| `sessions.debug(id, { expiresIn })` | A view-only live-view link (`debuggerUrl` and `debuggerFullscreenUrl` are the same link; `pages` is empty; `wsUrl` is the CDP URL). |
|
|
41
|
+
|
|
42
|
+
## Differs (documented, not errors)
|
|
43
|
+
- **Recording is a DOM-event log, not video.** `sessions.recording.retrieve(id)` returns rrweb events once the replay has been uploaded (a few seconds after release; until then it throws `APIError` with `code: "ReplayPending"`). There is no HLS playlist and no MP4.
|
|
44
|
+
- **Contexts store cookies and localStorage only** (Playwright `storageState`), not IndexedDB, service workers or autofill data.
|
|
45
|
+
- **No choice of operating system or viewport at create time.** Set the viewport from your automation client.
|
|
46
|
+
- **`keepAlive`**: a disconnected session is released about 45 s after its socket closes (reconnect with the same `connectUrl` inside that window). Sessions end at your plan's maximum session length.
|
|
47
|
+
- **`timeout`, `userMetadata`, `keepAlive: true`, `viewport`, `blockAds: true`, `logSession: true`, `os`** are accepted with a one-line warning and have no effect. Pass `strict: true` to make them throw instead.
|
|
48
|
+
|
|
49
|
+
## Contexts
|
|
50
|
+
```ts
|
|
51
|
+
const ctx = await bb.contexts.create({ name: "shop-login" });
|
|
52
|
+
const s = await bb.sessions.create({ browserSettings: { context: { id: ctx.id, persist: true } } });
|
|
53
|
+
const browser = await chromium.connectOverCDP(s.connectUrl);
|
|
54
|
+
// First run: log in, then release; the context is saved when you release through this client.
|
|
55
|
+
await bb.sessions.update(s.id, { status: "REQUEST_RELEASE" });
|
|
56
|
+
|
|
57
|
+
// Later: the saved login comes back as `storageState`. Create your browser context with it:
|
|
58
|
+
const s2 = await bb.sessions.create({ browserSettings: { context: { id: ctx.id } } });
|
|
59
|
+
const b2 = await chromium.connectOverCDP(s2.connectUrl);
|
|
60
|
+
const page = await (await b2.newContext({ storageState: s2.storageState })).newPage();
|
|
61
|
+
```
|
|
62
|
+
Two differences from Browserbase: Solari applies a saved context only to a browser context created *with* it (use `newContext({ storageState })`, not `browser.contexts()[0]`), and `persist: true` saves when you release **through the same client object** (it is remembered in memory).
|
|
63
|
+
|
|
64
|
+
## Raises `SolariCompatUnsupported`
|
|
65
|
+
Extensions (`extensionId`), the Selenium endpoint (`seleniumRemoteUrl` is always empty), downloads (`sessions.downloads`) and uploads (`sessions.uploads`) APIs, video replays (`sessions.replays`), session logs (`sessions.logs`), external/bring-your-own proxies, per-domain proxy rules (`domainPattern`), custom CA certificates, `ignoreCertificateErrors: true`, custom captcha selectors, `os: "mobile" | "tablet"`, a `region` other than `us-west-2` (or `us-west`), `sessions.list({ q })` (metadata queries), `contexts.update`, and the separate `projects`, `extensions`, `agents`, `fetchAPI` and `search` resources.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Solari
|
|
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
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @solarisdk/browserbase-compat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Browserbase-shaped SDK for [Solari Browser](https://getsolari.com): change the import and the key, keep your code.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @solarisdk/browserbase-compat
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import Browserbase from "@solarisdk/browserbase-compat";
|
|
11
|
+
import { chromium } from "playwright-core";
|
|
12
|
+
|
|
13
|
+
const bb = new Browserbase({ apiKey: process.env.SOLARI_API_KEY }); // slr_… key
|
|
14
|
+
const session = await bb.sessions.create();
|
|
15
|
+
const browser = await chromium.connectOverCDP(session.connectUrl);
|
|
16
|
+
const page = browser.contexts()[0].pages()[0] ?? (await browser.contexts()[0].newPage());
|
|
17
|
+
await page.goto("https://example.com");
|
|
18
|
+
await browser.close();
|
|
19
|
+
await bb.sessions.update(session.id, { status: "REQUEST_RELEASE" });
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Sessions, contexts, live view and recording are supported; extensions, downloads/uploads APIs, video replays,
|
|
23
|
+
bring-your-own proxies and Selenium raise `SolariCompatUnsupported`. See [COMPAT.md](./COMPAT.md) for the full list
|
|
24
|
+
and every difference. Requires Node 20+. No Playwright version pin is needed: `connectUrl` is a plain CDP endpoint.
|
|
25
|
+
|
|
26
|
+
Licence: MIT.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Context, ContextCreateResponse, Session, SessionCreateParams, SessionCreateResponse, SessionLiveURLs, SessionRetrieveResponse, SessionStatus } from "./types.js";
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://api.getsolari.com";
|
|
3
|
+
export interface BrowserbaseOptions {
|
|
4
|
+
/** Solari API key (`slr_...`). Falls back to SOLARI_API_KEY, then BROWSERBASE_API_KEY. A key that does not start with `slr_` is never used or sent. */
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
/** Gateway base URL. Default https://api.getsolari.com (env SOLARI_BASE_URL). */
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
/** Throw SolariCompatUnsupported instead of warning when an ignorable setting (timeout, keepAlive, ...) is passed. */
|
|
9
|
+
strict?: boolean;
|
|
10
|
+
/** Called for each soft difference; default console.warn. */
|
|
11
|
+
onWarning?: (message: string) => void;
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
/** Per-request timeout in ms. */
|
|
14
|
+
timeout?: number;
|
|
15
|
+
/** Test hook. */
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
export declare class Browserbase {
|
|
19
|
+
readonly sessions: Sessions;
|
|
20
|
+
readonly contexts: Contexts;
|
|
21
|
+
readonly extensions: UnsupportedResource;
|
|
22
|
+
readonly projects: UnsupportedResource;
|
|
23
|
+
readonly agents: UnsupportedResource;
|
|
24
|
+
readonly fetchAPI: UnsupportedResource;
|
|
25
|
+
readonly search: UnsupportedResource;
|
|
26
|
+
readonly apiKey: string;
|
|
27
|
+
readonly baseURL: string;
|
|
28
|
+
/** @internal */ readonly strict: boolean;
|
|
29
|
+
/** @internal */ readonly warn: (m: string) => void;
|
|
30
|
+
private readonly fetchImpl;
|
|
31
|
+
private readonly maxRetries;
|
|
32
|
+
private readonly timeoutMs;
|
|
33
|
+
/** session id -> profile to save on release (browserSettings.context.persist). In-process only. */
|
|
34
|
+
/** @internal */ readonly persist: Map<string, string>;
|
|
35
|
+
constructor(opts?: BrowserbaseOptions);
|
|
36
|
+
/** @internal GET a presigned URL (no Solari auth header). */
|
|
37
|
+
fetchPublic(url: string): Promise<Response>;
|
|
38
|
+
/** @internal */
|
|
39
|
+
request(method: string, path: string, body?: unknown, opts?: {
|
|
40
|
+
idempotencyKey?: string;
|
|
41
|
+
}): Promise<Response>;
|
|
42
|
+
/** @internal */
|
|
43
|
+
json<T>(method: string, path: string, body?: unknown, opts?: {
|
|
44
|
+
idempotencyKey?: string;
|
|
45
|
+
}): Promise<T>;
|
|
46
|
+
}
|
|
47
|
+
/** A resource whose every method throws SolariCompatUnsupported. */
|
|
48
|
+
export type UnsupportedResource = {
|
|
49
|
+
[method: string]: (...args: unknown[]) => never;
|
|
50
|
+
};
|
|
51
|
+
declare class Sessions {
|
|
52
|
+
private readonly c;
|
|
53
|
+
readonly recording: Recording;
|
|
54
|
+
readonly replays: UnsupportedResource;
|
|
55
|
+
readonly logs: UnsupportedResource;
|
|
56
|
+
readonly downloads: UnsupportedResource;
|
|
57
|
+
readonly uploads: UnsupportedResource;
|
|
58
|
+
constructor(c: Browserbase);
|
|
59
|
+
create(params?: SessionCreateParams): Promise<SessionCreateResponse>;
|
|
60
|
+
retrieve(id: string): Promise<SessionRetrieveResponse>;
|
|
61
|
+
list(query?: {
|
|
62
|
+
status?: SessionStatus;
|
|
63
|
+
q?: string;
|
|
64
|
+
}): Promise<Session[]>;
|
|
65
|
+
/** `{ status: "REQUEST_RELEASE" }` releases the session (saving the context first when it was created with persist: true). */
|
|
66
|
+
update(id: string, params: {
|
|
67
|
+
status: "REQUEST_RELEASE";
|
|
68
|
+
projectId?: string;
|
|
69
|
+
}): Promise<Session>;
|
|
70
|
+
/** Live-view links. Both URLs are the same view-only share link; `pages` is empty. */
|
|
71
|
+
debug(id: string, query?: {
|
|
72
|
+
expiresIn?: number;
|
|
73
|
+
}): Promise<SessionLiveURLs>;
|
|
74
|
+
}
|
|
75
|
+
declare class Recording {
|
|
76
|
+
private readonly c;
|
|
77
|
+
constructor(c: Browserbase);
|
|
78
|
+
/** The session's DOM recording as an array of rrweb events. Needs `browserSettings: { recordSession: true }`; available a few seconds after release. */
|
|
79
|
+
retrieve(id: string): Promise<unknown[]>;
|
|
80
|
+
}
|
|
81
|
+
declare class Contexts {
|
|
82
|
+
private readonly c;
|
|
83
|
+
constructor(c: Browserbase);
|
|
84
|
+
/** A Context is a Solari profile (cookies + localStorage). */
|
|
85
|
+
create(params?: {
|
|
86
|
+
projectId?: string;
|
|
87
|
+
name?: string;
|
|
88
|
+
}): Promise<ContextCreateResponse>;
|
|
89
|
+
retrieve(id: string): Promise<Context>;
|
|
90
|
+
update(): never;
|
|
91
|
+
delete(id: string): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
export {};
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
import { APIError, BrowserbaseError, SolariCompatUnsupported } from "./errors.js";
|
|
3
|
+
import { mapCreateParams, mapStatus } from "./mapping.js";
|
|
4
|
+
export const DEFAULT_BASE_URL = "https://api.getsolari.com";
|
|
5
|
+
const REGION = "us-west-2"; // reported for Browserbase-shaped objects; Solari's single region (us-west)
|
|
6
|
+
function resolveApiKey(explicit, warn) {
|
|
7
|
+
const sources = [
|
|
8
|
+
["apiKey", explicit],
|
|
9
|
+
["SOLARI_API_KEY", process.env.SOLARI_API_KEY],
|
|
10
|
+
["BROWSERBASE_API_KEY", process.env.BROWSERBASE_API_KEY],
|
|
11
|
+
];
|
|
12
|
+
for (const [name, v] of sources) {
|
|
13
|
+
if (!v)
|
|
14
|
+
continue;
|
|
15
|
+
if (v.startsWith("slr_"))
|
|
16
|
+
return v;
|
|
17
|
+
// Never echo any part of the value: it is probably a Browserbase secret.
|
|
18
|
+
warn(`${name} does not look like a Solari key (expected the slr_ prefix); ignoring it. It was not sent anywhere.`);
|
|
19
|
+
}
|
|
20
|
+
throw new BrowserbaseError("No Solari API key: pass apiKey or set SOLARI_API_KEY (keys start with slr_)");
|
|
21
|
+
}
|
|
22
|
+
function sleep(ms) {
|
|
23
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
24
|
+
}
|
|
25
|
+
export class Browserbase {
|
|
26
|
+
sessions;
|
|
27
|
+
contexts;
|
|
28
|
+
extensions;
|
|
29
|
+
projects;
|
|
30
|
+
agents;
|
|
31
|
+
fetchAPI;
|
|
32
|
+
search;
|
|
33
|
+
apiKey;
|
|
34
|
+
baseURL;
|
|
35
|
+
/** @internal */ strict;
|
|
36
|
+
/** @internal */ warn;
|
|
37
|
+
fetchImpl;
|
|
38
|
+
maxRetries;
|
|
39
|
+
timeoutMs;
|
|
40
|
+
/** session id -> profile to save on release (browserSettings.context.persist). In-process only. */
|
|
41
|
+
/** @internal */ persist = new Map();
|
|
42
|
+
constructor(opts = {}) {
|
|
43
|
+
this.warn = opts.onWarning ?? ((m) => console.warn(`[solari-browserbase-compat] ${m}`));
|
|
44
|
+
// Non-enumerable so console.log(client) / JSON.stringify(client) never print the key.
|
|
45
|
+
Object.defineProperty(this, "apiKey", { value: resolveApiKey(opts.apiKey, this.warn), enumerable: false });
|
|
46
|
+
this.baseURL = (opts.baseURL ?? process.env.SOLARI_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
47
|
+
this.strict = opts.strict === true;
|
|
48
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
49
|
+
this.maxRetries = opts.maxRetries ?? 2;
|
|
50
|
+
this.timeoutMs = opts.timeout ?? 60_000;
|
|
51
|
+
this.sessions = new Sessions(this);
|
|
52
|
+
this.contexts = new Contexts(this);
|
|
53
|
+
this.extensions = unsupported("extensions", "browser extensions cannot be loaded");
|
|
54
|
+
this.projects = unsupported("projects", "Solari keys are organisation-scoped; there are no projects");
|
|
55
|
+
this.agents = unsupported("agents", "Browserbase Agents is a separate product");
|
|
56
|
+
this.fetchAPI = unsupported("fetchAPI", "Browserbase Fetch is a separate product");
|
|
57
|
+
this.search = unsupported("search", "Browserbase Search is a separate product");
|
|
58
|
+
}
|
|
59
|
+
/** @internal GET a presigned URL (no Solari auth header). */
|
|
60
|
+
async fetchPublic(url) {
|
|
61
|
+
const ac = new AbortController();
|
|
62
|
+
const timer = setTimeout(() => ac.abort(), 15_000);
|
|
63
|
+
try {
|
|
64
|
+
return await this.fetchImpl(url, { signal: ac.signal });
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** @internal */
|
|
71
|
+
async request(method, path, body, opts) {
|
|
72
|
+
const headers = { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" };
|
|
73
|
+
if (opts?.idempotencyKey)
|
|
74
|
+
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
75
|
+
const replaySafe = method === "GET" || method === "DELETE" || opts?.idempotencyKey !== undefined;
|
|
76
|
+
let lastErr;
|
|
77
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
78
|
+
const ac = new AbortController();
|
|
79
|
+
const timer = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
80
|
+
try {
|
|
81
|
+
const res = await this.fetchImpl(`${this.baseURL}${path}`, {
|
|
82
|
+
method,
|
|
83
|
+
headers,
|
|
84
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
85
|
+
signal: ac.signal,
|
|
86
|
+
});
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
if (res.ok || !replaySafe || ![502, 503, 504].includes(res.status) || attempt === this.maxRetries)
|
|
89
|
+
return res;
|
|
90
|
+
await res.text().catch(() => { });
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
if (!replaySafe || attempt === this.maxRetries)
|
|
95
|
+
throw e;
|
|
96
|
+
lastErr = e;
|
|
97
|
+
}
|
|
98
|
+
await sleep(300 * (attempt + 1));
|
|
99
|
+
}
|
|
100
|
+
throw lastErr instanceof Error ? lastErr : new BrowserbaseError("request failed");
|
|
101
|
+
}
|
|
102
|
+
/** @internal */
|
|
103
|
+
async json(method, path, body, opts) {
|
|
104
|
+
const res = await this.request(method, path, body, opts);
|
|
105
|
+
if (res.status === 204)
|
|
106
|
+
return undefined;
|
|
107
|
+
const text = await res.text();
|
|
108
|
+
let parsed;
|
|
109
|
+
try {
|
|
110
|
+
parsed = text ? JSON.parse(text) : undefined;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
parsed = undefined;
|
|
114
|
+
}
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
const o = (parsed ?? {});
|
|
117
|
+
throw new APIError(res.status, `Solari ${method} ${path}: ${res.status} ${o.error ?? text.slice(0, 200)}`, o.code, parsed);
|
|
118
|
+
}
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
class UnsupportedImpl {
|
|
123
|
+
feature;
|
|
124
|
+
hint;
|
|
125
|
+
constructor(feature, hint) {
|
|
126
|
+
this.feature = feature;
|
|
127
|
+
this.hint = hint;
|
|
128
|
+
// Every method on this object throws; a Proxy keeps the surface open-ended (create/list/retrieve/...).
|
|
129
|
+
return new Proxy(this, {
|
|
130
|
+
get: (target, prop) => {
|
|
131
|
+
if (typeof prop === "symbol" || prop === "then" || prop in target)
|
|
132
|
+
return Reflect.get(target, prop);
|
|
133
|
+
return () => {
|
|
134
|
+
throw new SolariCompatUnsupported(`${target.feature}.${prop}`, target.hint);
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function unsupported(feature, hint) {
|
|
141
|
+
return new UnsupportedImpl(feature, hint);
|
|
142
|
+
}
|
|
143
|
+
function newKey() {
|
|
144
|
+
return globalThis.crypto?.randomUUID?.() ?? `bbc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
145
|
+
}
|
|
146
|
+
function toSession(row, extra = {}) {
|
|
147
|
+
const created = row.createdAt ?? new Date().toISOString();
|
|
148
|
+
const status = extra.status ?? mapStatus(row.status);
|
|
149
|
+
return {
|
|
150
|
+
id: row.id,
|
|
151
|
+
createdAt: created,
|
|
152
|
+
updatedAt: created,
|
|
153
|
+
projectId: "",
|
|
154
|
+
startedAt: created,
|
|
155
|
+
...(status === "COMPLETED" || status === "TIMED_OUT" ? { endedAt: new Date().toISOString() } : {}),
|
|
156
|
+
expiresAt: row.expiresAt ?? created,
|
|
157
|
+
status,
|
|
158
|
+
proxyBytes: 0,
|
|
159
|
+
keepAlive: extra.keepAlive ?? false,
|
|
160
|
+
...(extra.contextId ? { contextId: extra.contextId } : {}),
|
|
161
|
+
region: REGION,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function withConnect(s, cdp) {
|
|
165
|
+
return { ...s, connectUrl: cdp, seleniumRemoteUrl: "", signingKey: "" };
|
|
166
|
+
}
|
|
167
|
+
class Sessions {
|
|
168
|
+
c;
|
|
169
|
+
recording;
|
|
170
|
+
replays;
|
|
171
|
+
logs;
|
|
172
|
+
downloads;
|
|
173
|
+
uploads;
|
|
174
|
+
constructor(c) {
|
|
175
|
+
this.c = c;
|
|
176
|
+
this.recording = new Recording(c);
|
|
177
|
+
this.replays = unsupported("sessions.replays", "Solari records DOM events, not video; use sessions.recording.retrieve");
|
|
178
|
+
this.logs = unsupported("sessions.logs", "session command logs are not available");
|
|
179
|
+
this.downloads = unsupported("sessions.downloads", "file downloads cannot be retrieved through the API");
|
|
180
|
+
this.uploads = unsupported("sessions.uploads", "use your automation client's setInputFiles");
|
|
181
|
+
}
|
|
182
|
+
async create(params) {
|
|
183
|
+
const mapped = mapCreateParams(params, this.c.strict);
|
|
184
|
+
for (const w of mapped.warnings)
|
|
185
|
+
this.c.warn(w);
|
|
186
|
+
const data = await this.c.json("POST", "/sessions", Object.keys(mapped.body).length ? mapped.body : undefined, { idempotencyKey: newKey() });
|
|
187
|
+
if (!data?.sessionId || !data.cdpEndpoint) {
|
|
188
|
+
throw new BrowserbaseError(`Solari returned an unexpected session response: ${JSON.stringify(data)}`);
|
|
189
|
+
}
|
|
190
|
+
if (mapped.persistProfileId)
|
|
191
|
+
this.c.persist.set(data.sessionId, mapped.persistProfileId);
|
|
192
|
+
const ctx = params?.browserSettings?.context?.id;
|
|
193
|
+
const s = toSession({ id: data.sessionId, status: "active", createdAt: new Date().toISOString(), ...(data.expiresAt ? { expiresAt: data.expiresAt } : {}) }, { keepAlive: params?.keepAlive === true, ...(ctx ? { contextId: ctx } : {}), status: "RUNNING" });
|
|
194
|
+
const out = withConnect(s, data.cdpEndpoint);
|
|
195
|
+
if (ctx) {
|
|
196
|
+
let state = data.storageState ?? null;
|
|
197
|
+
if (!state && data.storageStateUrl?.url) {
|
|
198
|
+
const r = await this.c.fetchPublic(data.storageStateUrl.url).catch(() => undefined);
|
|
199
|
+
if (r?.ok)
|
|
200
|
+
state = (await r.json().catch(() => null));
|
|
201
|
+
else
|
|
202
|
+
this.c.warn("could not download the saved context; the session starts without it");
|
|
203
|
+
}
|
|
204
|
+
out.storageState = state;
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
async retrieve(id) {
|
|
209
|
+
const row = await this.c.json("GET", `/sessions/${encodeURIComponent(id)}`);
|
|
210
|
+
return withConnect(toSession({ ...row, id: row.id ?? id }), row.cdpEndpoint ?? "");
|
|
211
|
+
}
|
|
212
|
+
async list(query) {
|
|
213
|
+
if (query?.q)
|
|
214
|
+
throw new SolariCompatUnsupported("sessions.list q (userMetadata query)", "metadata is not stored");
|
|
215
|
+
const data = await this.c.json("GET", "/sessions");
|
|
216
|
+
const all = (data?.sessions ?? []).map((r) => toSession(r));
|
|
217
|
+
return query?.status ? all.filter((s) => s.status === query.status) : all;
|
|
218
|
+
}
|
|
219
|
+
/** `{ status: "REQUEST_RELEASE" }` releases the session (saving the context first when it was created with persist: true). */
|
|
220
|
+
async update(id, params) {
|
|
221
|
+
if (params?.status !== "REQUEST_RELEASE")
|
|
222
|
+
throw new SolariCompatUnsupported(`sessions.update status ${String(params?.status)}`, "only REQUEST_RELEASE exists");
|
|
223
|
+
const profileId = this.c.persist.get(id);
|
|
224
|
+
if (profileId) {
|
|
225
|
+
await this.c.json("POST", `/sessions/${encodeURIComponent(id)}/save-profile`, { profileId });
|
|
226
|
+
this.c.persist.delete(id);
|
|
227
|
+
}
|
|
228
|
+
await this.c.json("DELETE", `/sessions/${encodeURIComponent(id)}`);
|
|
229
|
+
return toSession({ id, status: "released" }, { status: "COMPLETED" });
|
|
230
|
+
}
|
|
231
|
+
/** Live-view links. Both URLs are the same view-only share link; `pages` is empty. */
|
|
232
|
+
async debug(id, query) {
|
|
233
|
+
const share = await this.c.json("POST", `/sessions/${encodeURIComponent(id)}/shares`, query?.expiresIn !== undefined ? { ttlSeconds: query.expiresIn } : {});
|
|
234
|
+
const row = await this.c.json("GET", `/sessions/${encodeURIComponent(id)}`);
|
|
235
|
+
return { debuggerFullscreenUrl: share.url, debuggerUrl: share.url, wsUrl: row.cdpEndpoint ?? "", pages: [] };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
class Recording {
|
|
239
|
+
c;
|
|
240
|
+
constructor(c) {
|
|
241
|
+
this.c = c;
|
|
242
|
+
}
|
|
243
|
+
/** The session's DOM recording as an array of rrweb events. Needs `browserSettings: { recordSession: true }`; available a few seconds after release. */
|
|
244
|
+
async retrieve(id) {
|
|
245
|
+
const meta = await this.c.json("GET", `/sessions/${encodeURIComponent(id)}/replay-url`);
|
|
246
|
+
if (!meta?.url)
|
|
247
|
+
throw new BrowserbaseError("Solari returned no replay URL");
|
|
248
|
+
const r = await fetch(meta.url);
|
|
249
|
+
if (!r.ok)
|
|
250
|
+
throw new APIError(r.status, `replay download failed: ${r.status}`);
|
|
251
|
+
let buf = Buffer.from(await r.arrayBuffer());
|
|
252
|
+
if (buf.length > 2 && buf[0] === 0x1f && buf[1] === 0x8b)
|
|
253
|
+
buf = gunzipSync(buf);
|
|
254
|
+
return buf
|
|
255
|
+
.toString("utf8")
|
|
256
|
+
.split("\n")
|
|
257
|
+
.filter((l) => l.trim() !== "")
|
|
258
|
+
.map((l) => JSON.parse(l));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
class Contexts {
|
|
262
|
+
c;
|
|
263
|
+
constructor(c) {
|
|
264
|
+
this.c = c;
|
|
265
|
+
}
|
|
266
|
+
/** A Context is a Solari profile (cookies + localStorage). */
|
|
267
|
+
async create(params) {
|
|
268
|
+
const p = await this.c.json("POST", "/profiles", { name: params?.name ?? `context-${newKey().slice(0, 8)}` });
|
|
269
|
+
return { id: p.id, publicKey: "", cipherAlgorithm: "", initializationVectorSize: 0 };
|
|
270
|
+
}
|
|
271
|
+
async retrieve(id) {
|
|
272
|
+
const list = await this.c.json("GET", "/profiles");
|
|
273
|
+
const p = (list ?? []).find((x) => x.id === id);
|
|
274
|
+
if (!p)
|
|
275
|
+
throw new APIError(404, `context ${id} not found`, "NotFound");
|
|
276
|
+
return { id: p.id, ...(p.name ? { name: p.name } : {}), ...(p.createdAt ? { createdAt: p.createdAt } : {}) };
|
|
277
|
+
}
|
|
278
|
+
update() {
|
|
279
|
+
throw new SolariCompatUnsupported("contexts.update", "contexts are saved when a persist:true session is released");
|
|
280
|
+
}
|
|
281
|
+
async delete(id) {
|
|
282
|
+
await this.c.json("DELETE", `/profiles/${encodeURIComponent(id)}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Base class for everything this package throws. */
|
|
2
|
+
export declare class BrowserbaseError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** The gateway answered with a non-2xx status. `code` is Solari's machine-readable error code when present. */
|
|
6
|
+
export declare class APIError extends BrowserbaseError {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly code: string | undefined;
|
|
9
|
+
readonly body: unknown;
|
|
10
|
+
constructor(status: number, message: string, code?: string, body?: unknown);
|
|
11
|
+
}
|
|
12
|
+
/** A Browserbase feature Solari Browser does not offer (see COMPAT.md). Never silently ignored when it would change results. */
|
|
13
|
+
export declare class SolariCompatUnsupported extends BrowserbaseError {
|
|
14
|
+
readonly feature: string;
|
|
15
|
+
readonly hint: string | undefined;
|
|
16
|
+
constructor(feature: string, hint?: string);
|
|
17
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Base class for everything this package throws. */
|
|
2
|
+
export class BrowserbaseError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
/** The gateway answered with a non-2xx status. `code` is Solari's machine-readable error code when present. */
|
|
9
|
+
export class APIError extends BrowserbaseError {
|
|
10
|
+
status;
|
|
11
|
+
code;
|
|
12
|
+
body;
|
|
13
|
+
constructor(status, message, code, body) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.body = body;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** A Browserbase feature Solari Browser does not offer (see COMPAT.md). Never silently ignored when it would change results. */
|
|
21
|
+
export class SolariCompatUnsupported extends BrowserbaseError {
|
|
22
|
+
feature;
|
|
23
|
+
hint;
|
|
24
|
+
constructor(feature, hint) {
|
|
25
|
+
super(`Not supported by Solari Browser: ${feature}${hint ? ` — ${hint}` : ""}`);
|
|
26
|
+
this.feature = feature;
|
|
27
|
+
this.hint = hint;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Browserbase } from "./client.js";
|
|
2
|
+
export { Browserbase, DEFAULT_BASE_URL } from "./client.js";
|
|
3
|
+
export type { BrowserbaseOptions, UnsupportedResource } from "./client.js";
|
|
4
|
+
export { APIError, BrowserbaseError, SolariCompatUnsupported } from "./errors.js";
|
|
5
|
+
export { mapCreateParams, mapStatus } from "./mapping.js";
|
|
6
|
+
export type * from "./types.js";
|
|
7
|
+
export default Browserbase;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Browserbase } from "./client.js";
|
|
2
|
+
export { Browserbase, DEFAULT_BASE_URL } from "./client.js";
|
|
3
|
+
export { APIError, BrowserbaseError, SolariCompatUnsupported } from "./errors.js";
|
|
4
|
+
export { mapCreateParams, mapStatus } from "./mapping.js";
|
|
5
|
+
export default Browserbase;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SessionCreateParams, SessionStatus } from "./types.js";
|
|
2
|
+
export interface MappedCreate {
|
|
3
|
+
/** Body for POST /sessions on the Solari gateway. */
|
|
4
|
+
body: Record<string, unknown>;
|
|
5
|
+
/** Context to save back to when the session is released (browserSettings.context.persist). */
|
|
6
|
+
persistProfileId?: string;
|
|
7
|
+
/** Soft differences the caller should know about (also emitted via the warn callback). */
|
|
8
|
+
warnings: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Translate Browserbase `sessions.create` params into a Solari `POST /sessions` body.
|
|
12
|
+
* Hard gaps throw SolariCompatUnsupported. Soft gaps (a setting we cannot honour but whose absence does not change
|
|
13
|
+
* what the page sees) are returned as warnings, or thrown when `strict` is set.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mapCreateParams(params: SessionCreateParams | undefined, strict: boolean): MappedCreate;
|
|
16
|
+
/** Solari session state -> Browserbase status. */
|
|
17
|
+
export declare function mapStatus(s: string | undefined): SessionStatus;
|
package/dist/mapping.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { SolariCompatUnsupported } from "./errors.js";
|
|
2
|
+
const SOLARI_REGION_ALIASES = new Set(["us-west-2"]);
|
|
3
|
+
function proxyBody(proxies, caCerts) {
|
|
4
|
+
if (caCerts && caCerts.length > 0) {
|
|
5
|
+
throw new SolariCompatUnsupported("proxySettings.caCertificates", "custom CA certificates are not supported");
|
|
6
|
+
}
|
|
7
|
+
if (proxies === undefined || proxies === false)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (proxies === true)
|
|
10
|
+
return { country: "us" };
|
|
11
|
+
if (!Array.isArray(proxies) || proxies.length === 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (proxies.length > 1) {
|
|
14
|
+
throw new SolariCompatUnsupported("multiple proxies / domain routing rules", "pass at most one managed proxy; per-domain routing is not supported");
|
|
15
|
+
}
|
|
16
|
+
const p = proxies[0];
|
|
17
|
+
if (p.domainPattern !== undefined && p.domainPattern !== "") {
|
|
18
|
+
throw new SolariCompatUnsupported("proxies[].domainPattern", "per-domain proxy routing is not supported");
|
|
19
|
+
}
|
|
20
|
+
if (p.type === "external") {
|
|
21
|
+
throw new SolariCompatUnsupported("external proxy", "bring-your-own proxy servers are not supported; use a managed proxy");
|
|
22
|
+
}
|
|
23
|
+
if (p.type === "none")
|
|
24
|
+
return undefined; // no proxy, same as not asking for one
|
|
25
|
+
if (p.type !== "browserbase") {
|
|
26
|
+
throw new SolariCompatUnsupported(`proxy type ${JSON.stringify(p.type)}`);
|
|
27
|
+
}
|
|
28
|
+
const g = p.geolocation;
|
|
29
|
+
if (!g)
|
|
30
|
+
return { country: "us" };
|
|
31
|
+
const out = { country: g.country.toLowerCase() };
|
|
32
|
+
if (g.state)
|
|
33
|
+
out.state = g.state.toLowerCase();
|
|
34
|
+
if (g.city)
|
|
35
|
+
out.city = g.city;
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Translate Browserbase `sessions.create` params into a Solari `POST /sessions` body.
|
|
40
|
+
* Hard gaps throw SolariCompatUnsupported. Soft gaps (a setting we cannot honour but whose absence does not change
|
|
41
|
+
* what the page sees) are returned as warnings, or thrown when `strict` is set.
|
|
42
|
+
*/
|
|
43
|
+
export function mapCreateParams(params, strict) {
|
|
44
|
+
const p = params ?? {};
|
|
45
|
+
const bs = p.browserSettings ?? {};
|
|
46
|
+
const warnings = [];
|
|
47
|
+
const soft = (feature, why) => {
|
|
48
|
+
if (strict)
|
|
49
|
+
throw new SolariCompatUnsupported(feature, why);
|
|
50
|
+
warnings.push(`${feature}: ${why}`);
|
|
51
|
+
};
|
|
52
|
+
// ── hard gaps ────────────────────────────────────────────────────────────────
|
|
53
|
+
if (p.extensionId || bs.extensionId)
|
|
54
|
+
throw new SolariCompatUnsupported("extensions", "browser extensions cannot be loaded");
|
|
55
|
+
if (bs.ignoreCertificateErrors === true)
|
|
56
|
+
throw new SolariCompatUnsupported("browserSettings.ignoreCertificateErrors");
|
|
57
|
+
if (bs.captchaImageSelector || bs.captchaInputSelector)
|
|
58
|
+
throw new SolariCompatUnsupported("custom CAPTCHA selectors");
|
|
59
|
+
if (bs.os === "mobile" || bs.os === "tablet")
|
|
60
|
+
throw new SolariCompatUnsupported(`browserSettings.os=${bs.os}`, "only desktop browsers");
|
|
61
|
+
if (p.region !== undefined && !SOLARI_REGION_ALIASES.has(p.region) && p.region !== "us-west") {
|
|
62
|
+
throw new SolariCompatUnsupported(`region ${p.region}`, "Solari Browser runs in one region (us-west)");
|
|
63
|
+
}
|
|
64
|
+
// ── soft gaps ────────────────────────────────────────────────────────────────
|
|
65
|
+
if (p.timeout !== undefined)
|
|
66
|
+
soft("timeout", "sessions end at your plan's maximum session length, not at this value; call sessions.update to release earlier");
|
|
67
|
+
if (p.keepAlive === true)
|
|
68
|
+
soft("keepAlive", "a disconnected session is released about 45 s after the socket closes; reconnect inside that window or hold the connection");
|
|
69
|
+
if (p.userMetadata !== undefined)
|
|
70
|
+
soft("userMetadata", "metadata is not stored or queryable");
|
|
71
|
+
if (bs.viewport !== undefined)
|
|
72
|
+
soft("browserSettings.viewport", "set the viewport from your automation client (page.setViewportSize)");
|
|
73
|
+
if (bs.blockAds === true)
|
|
74
|
+
soft("browserSettings.blockAds", "ad blocking is not available");
|
|
75
|
+
if (bs.logSession === true)
|
|
76
|
+
soft("browserSettings.logSession", "session command logs are not available");
|
|
77
|
+
if (bs.os === "windows" || bs.os === "mac" || bs.os === "linux")
|
|
78
|
+
soft("browserSettings.os", "the operating system profile cannot be chosen");
|
|
79
|
+
// ── mapping ──────────────────────────────────────────────────────────────────
|
|
80
|
+
const proxy = proxyBody(p.proxies, p.proxySettings?.caCertificates);
|
|
81
|
+
const allowedDomains = bs.allowedDomains && bs.allowedDomains.length > 0 ? bs.allowedDomains : undefined;
|
|
82
|
+
const captcha = bs.solveCaptchas === true;
|
|
83
|
+
const stealth = bs.verified === true || bs.advancedStealth === true || captcha || proxy !== undefined || allowedDomains !== undefined;
|
|
84
|
+
const body = {};
|
|
85
|
+
if (stealth)
|
|
86
|
+
body.stealth = true;
|
|
87
|
+
if (captcha)
|
|
88
|
+
body.captcha = true;
|
|
89
|
+
if (bs.recordSession === true)
|
|
90
|
+
body.recording = true;
|
|
91
|
+
if (proxy !== undefined)
|
|
92
|
+
body.proxy = proxy;
|
|
93
|
+
if (allowedDomains)
|
|
94
|
+
body.allowedDomains = allowedDomains;
|
|
95
|
+
if (bs.context?.id)
|
|
96
|
+
body.profileId = bs.context.id;
|
|
97
|
+
return {
|
|
98
|
+
body,
|
|
99
|
+
...(bs.context?.id && bs.context.persist === true ? { persistProfileId: bs.context.id } : {}),
|
|
100
|
+
warnings,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Solari session state -> Browserbase status. */
|
|
104
|
+
export function mapStatus(s) {
|
|
105
|
+
switch (s) {
|
|
106
|
+
case "active":
|
|
107
|
+
return "RUNNING";
|
|
108
|
+
case "released":
|
|
109
|
+
return "COMPLETED";
|
|
110
|
+
case "expired":
|
|
111
|
+
return "TIMED_OUT";
|
|
112
|
+
default:
|
|
113
|
+
return "ERROR";
|
|
114
|
+
}
|
|
115
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Browserbase-shaped types (the subset this package supports). */
|
|
2
|
+
export type SessionStatus = "PENDING" | "RUNNING" | "ERROR" | "TIMED_OUT" | "COMPLETED";
|
|
3
|
+
export interface ProxyGeolocation {
|
|
4
|
+
country: string;
|
|
5
|
+
state?: string;
|
|
6
|
+
city?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface BrowserbaseProxyConfig {
|
|
9
|
+
type: "browserbase";
|
|
10
|
+
geolocation?: ProxyGeolocation;
|
|
11
|
+
domainPattern?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ExternalProxyConfig {
|
|
14
|
+
type: "external";
|
|
15
|
+
server: string;
|
|
16
|
+
username?: string;
|
|
17
|
+
password?: string;
|
|
18
|
+
domainPattern?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface NoneProxyConfig {
|
|
21
|
+
type: "none";
|
|
22
|
+
domainPattern?: string;
|
|
23
|
+
}
|
|
24
|
+
export type ProxyConfig = BrowserbaseProxyConfig | ExternalProxyConfig | NoneProxyConfig;
|
|
25
|
+
export interface BrowserSettings {
|
|
26
|
+
context?: {
|
|
27
|
+
id: string;
|
|
28
|
+
persist?: boolean;
|
|
29
|
+
};
|
|
30
|
+
extensionId?: string;
|
|
31
|
+
viewport?: {
|
|
32
|
+
width?: number;
|
|
33
|
+
height?: number;
|
|
34
|
+
};
|
|
35
|
+
blockAds?: boolean;
|
|
36
|
+
solveCaptchas?: boolean;
|
|
37
|
+
recordSession?: boolean;
|
|
38
|
+
logSession?: boolean;
|
|
39
|
+
advancedStealth?: boolean;
|
|
40
|
+
verified?: boolean;
|
|
41
|
+
captchaImageSelector?: string;
|
|
42
|
+
captchaInputSelector?: string;
|
|
43
|
+
os?: "windows" | "mac" | "linux" | "mobile" | "tablet";
|
|
44
|
+
allowedDomains?: string[];
|
|
45
|
+
ignoreCertificateErrors?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface SessionCreateParams {
|
|
48
|
+
projectId?: string;
|
|
49
|
+
extensionId?: string;
|
|
50
|
+
browserSettings?: BrowserSettings;
|
|
51
|
+
timeout?: number;
|
|
52
|
+
keepAlive?: boolean;
|
|
53
|
+
proxies?: boolean | ProxyConfig[];
|
|
54
|
+
proxySettings?: {
|
|
55
|
+
caCertificates?: string[];
|
|
56
|
+
};
|
|
57
|
+
region?: string;
|
|
58
|
+
userMetadata?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
export interface Session {
|
|
61
|
+
id: string;
|
|
62
|
+
createdAt: string;
|
|
63
|
+
updatedAt: string;
|
|
64
|
+
projectId: string;
|
|
65
|
+
startedAt: string;
|
|
66
|
+
endedAt?: string;
|
|
67
|
+
expiresAt: string;
|
|
68
|
+
status: SessionStatus;
|
|
69
|
+
proxyBytes: number;
|
|
70
|
+
keepAlive: boolean;
|
|
71
|
+
contextId?: string;
|
|
72
|
+
region: string;
|
|
73
|
+
userMetadata?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
export interface SessionCreateResponse extends Session {
|
|
76
|
+
/** CDP websocket URL: `chromium.connectOverCDP(connectUrl)` / `puppeteer.connect({browserWSEndpoint})`. */
|
|
77
|
+
connectUrl: string;
|
|
78
|
+
/** Always empty: Solari Browser has no WebDriver endpoint. */
|
|
79
|
+
seleniumRemoteUrl: string;
|
|
80
|
+
/** Always empty: nothing to sign, the session id in the URL is the credential. */
|
|
81
|
+
signingKey: string;
|
|
82
|
+
/**
|
|
83
|
+
* Solari extension. For a session created with `browserSettings.context`, the saved cookies and localStorage.
|
|
84
|
+
* Solari applies a saved context only to a browser context created with it, so create yours with
|
|
85
|
+
* `browser.newContext({ storageState: session.storageState })`. Absent when no context was requested.
|
|
86
|
+
*/
|
|
87
|
+
storageState?: StorageState | null;
|
|
88
|
+
}
|
|
89
|
+
export interface StorageState {
|
|
90
|
+
cookies?: Array<Record<string, unknown>>;
|
|
91
|
+
origins?: Array<Record<string, unknown>>;
|
|
92
|
+
}
|
|
93
|
+
export type SessionRetrieveResponse = SessionCreateResponse;
|
|
94
|
+
export interface SessionLiveURLs {
|
|
95
|
+
debuggerFullscreenUrl: string;
|
|
96
|
+
debuggerUrl: string;
|
|
97
|
+
wsUrl: string;
|
|
98
|
+
pages: Array<{
|
|
99
|
+
id: string;
|
|
100
|
+
url: string;
|
|
101
|
+
title: string;
|
|
102
|
+
faviconUrl: string;
|
|
103
|
+
debuggerUrl: string;
|
|
104
|
+
debuggerFullscreenUrl: string;
|
|
105
|
+
}>;
|
|
106
|
+
}
|
|
107
|
+
export interface Context {
|
|
108
|
+
id: string;
|
|
109
|
+
name?: string;
|
|
110
|
+
createdAt?: string;
|
|
111
|
+
}
|
|
112
|
+
export interface ContextCreateResponse {
|
|
113
|
+
id: string;
|
|
114
|
+
/** Empty: Solari profiles are stored server-side, no client-side encryption material. */
|
|
115
|
+
publicKey: string;
|
|
116
|
+
cipherAlgorithm: string;
|
|
117
|
+
initializationVectorSize: number;
|
|
118
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solarisdk/browserbase-compat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Drop-in Browserbase-style SDK surface for Solari Browser: sessions, connectUrl (CDP), contexts, live view, replay events.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist", "README.md", "COMPAT.md", "LICENSE"],
|
|
16
|
+
"keywords": ["browserbase", "solari", "browser", "playwright", "puppeteer", "cdp", "compat"],
|
|
17
|
+
"engines": { "node": ">=20" },
|
|
18
|
+
"publishConfig": { "access": "public" },
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"typecheck": "node ../../scripts/typecheck-guard.mjs 2 -- tsc --noEmit",
|
|
22
|
+
"test": "tsc && node --test test/*.test.mjs"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^20.0.0",
|
|
26
|
+
"typescript": "^5.4.0"
|
|
27
|
+
}
|
|
28
|
+
}
|