Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,45 @@ status and truncation metadata before using output. Public types, including
See the [WebMCP guide](https://hyperbrowser.ai/docs/sessions/webmcp) for complete
examples, cancellation behavior, human submission, and limits.

## Web Bot Auth

A session can sign its requests with Web Bot Auth so websites can verify that they
come from Hyperbrowser. Web Bot Auth is enabled for teams on request.

```typescript
const session = await client.sessions.create({ webBotAuth: true });
console.log(session.launchState?.webBotAuth?.signatureAgent); // "https://www.hyperbrowser.ai"

// Sign only requests to these hosts and their subdomains:
await client.sessions.create({ webBotAuth: { onlyDomains: ["example.com"] } });
```

Agent tasks accept `webBotAuth` in `sessionOptions`; scrape, crawl, and extract
jobs do not. It cannot be combined with `useUltraStealth` or `startFromSnapshot`.
See the [Web Bot Auth guide](https://hyperbrowser.ai/docs/sessions/web-bot-auth).

## Proxy routes

`proxyRoutes` sends some hosts' traffic straight out instead of through the
session's proxy, or the other way round. The first route that matches a host
decides; hosts that no route matches use the proxy. Proxy routes require
`useProxy` and are enabled for teams on request.

```typescript
const session = await client.sessions.create({
useProxy: true,
proxyRoutes: [
{ match: "api.example.com", proxy: "default" }, // stays on the proxy
{ match: ["*.example.com", "cdn.example.net"], proxy: "direct" },
],
});
```

A pattern is a hostname, `*.example.com` (its subdomains, not `example.com` itself),
or `*` (every host). Proxy routes only apply to `sessions.create` and cannot be
combined with `allowInternetAccess: false`, `allowOut`, or `denyOut`. See the
[proxy routes guide](https://hyperbrowser.ai/docs/sessions/proxy-routes).

## Usage

### Playwright
Expand Down
3 changes: 3 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,11 @@ export {
SessionEventLog,
SessionProfile,
SessionLaunchState,
SessionWebBotAuth,
SessionCreditBreakdown,
StartSessionFromSnapshotParams,
WebBotAuthParams,
ProxyRoute,
UpdateSessionProfileParams,
UpdateSessionProxyLocationParams,
UpdateSessionProxyParams,
Expand Down
47 changes: 47 additions & 0 deletions src/types/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,18 @@ export interface SessionProfile {
persistChanges?: boolean;
}

/** How a session signs its requests with Web Bot Auth. */
export interface SessionWebBotAuth {
/** The identity the session signs as, e.g. "https://www.hyperbrowser.ai". */
signatureAgent: string;
/** Present when signing is limited to these hostnames and their subdomains. */
onlyDomains?: string[];
}

export interface SessionLaunchState {
enableWebMcp?: boolean;
/** Present only for a session that signs its requests with Web Bot Auth. */
webBotAuth?: SessionWebBotAuth;
useUltraStealth?: boolean;
useStealth?: boolean;
useProxy?: boolean;
Expand Down Expand Up @@ -165,9 +175,46 @@ export interface StartSessionFromSnapshotParams {
snapshotId: string;
}

export interface WebBotAuthParams {
/**
* Sign only requests to these hostnames (e.g. "example.com") and their
* subdomains. No scheme, port or path; at most 50.
*/
onlyDomains?: string[];
}

export interface ProxyRoute {
/**
* The hosts this route applies to: a hostname, "*.example.com" (subdomains,
* not example.com itself) or "*" (every host), or a list of them.
*/
match: string | string[];
/**
* "direct" sends the traffic straight out of the browser's machine;
* "default" sends it through the session's proxy.
*/
proxy: "direct" | "default";
}

export interface CreateSessionParams {
/** Enable page-provided WebMCP tools. Defaults to false on the server. */
enableWebMcp?: boolean;
/**
* Sign the session's requests with Web Bot Auth so websites can verify they
* come from Hyperbrowser. Pass `{ onlyDomains }` to sign only for those
* hosts. Requires Web Bot Auth to be enabled for your team; works for
* sessions and agent tasks, and cannot be combined with `useUltraStealth`
* or `startFromSnapshot`.
*/
webBotAuth?: boolean | WebBotAuthParams;
/**
* Choose, per host, whether the session's traffic goes through its proxy or
* straight out. The first route that matches a host decides; hosts that no
* route matches use the proxy. Requires `useProxy` and proxy routes to be
* enabled for your team, only applies when creating a session, and cannot
* be combined with `allowInternetAccess: false`, `allowOut` or `denyOut`.
*/
proxyRoutes?: ProxyRoute[];
useUltraStealth?: boolean;
useStealth?: boolean;
useProxy?: boolean;
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/session-web-bot-auth-proxy-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { afterEach, expect, expectTypeOf, test, vi } from "vitest";
import { Response } from "node-fetch";

const fetchMock = vi.fn();
vi.mock("node-fetch", async () => {
const actual = await vi.importActual<typeof import("node-fetch")>("node-fetch");
return { ...actual, default: (...args: unknown[]) => fetchMock(...args) };
});

import { HyperbrowserClient as Hyperbrowser } from "../../src/client";
import type {
CreateSessionParams,
ProxyRoute,
SessionLaunchState,
SessionWebBotAuth,
WebBotAuthParams,
} from "../../src/types";

const client = () => new Hyperbrowser({ apiKey: "test-key", baseUrl: "https://example.test" });
const respond = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status });
const lastBody = () => JSON.parse(fetchMock.mock.lastCall![1].body);

afterEach(() => fetchMock.mockReset());

test("session creation sends webBotAuth as given", async () => {
fetchMock.mockImplementation(() => Promise.resolve(respond({ id: "session" })));
const sdk = client();
for (const params of [
{},
{ webBotAuth: true },
{ webBotAuth: false },
{ webBotAuth: { onlyDomains: ["example.com", "shop.example.org"] } },
] satisfies CreateSessionParams[]) {
await sdk.sessions.create(params);
expect(lastBody()).toEqual(params);
}
});

test("session creation sends proxyRoutes in order", async () => {
fetchMock.mockImplementation(() => Promise.resolve(respond({ id: "session" })));
const params = {
useProxy: true,
proxyRoutes: [
{ match: "api.example.com", proxy: "default" },
{ match: ["*.example.com", "cdn.example.net"], proxy: "direct" },
],
} satisfies CreateSessionParams;
await client().sessions.create(params);
expect(lastBody()).toEqual(params);
});

test("agent tasks pass webBotAuth through sessionOptions", async () => {
fetchMock.mockImplementation(() => Promise.resolve(respond({ jobId: "job" })));
await client().agents.browserUse.start({
task: "Open example.com",
sessionOptions: { webBotAuth: { onlyDomains: ["example.com"] } },
});
expect(fetchMock.mock.lastCall![0]).toBe("https://example.test/api/task/browser-use");
expect(lastBody().sessionOptions).toEqual({ webBotAuth: { onlyDomains: ["example.com"] } });
});

test("public types describe the options and the signing launch state", () => {
expectTypeOf<CreateSessionParams["webBotAuth"]>().toEqualTypeOf<
boolean | WebBotAuthParams | undefined
>();
expectTypeOf<CreateSessionParams["proxyRoutes"]>().toEqualTypeOf<ProxyRoute[] | undefined>();
expectTypeOf<ProxyRoute["proxy"]>().toEqualTypeOf<"direct" | "default">();
expectTypeOf<ProxyRoute["match"]>().toEqualTypeOf<string | string[]>();
expectTypeOf<SessionLaunchState["webBotAuth"]>().toEqualTypeOf<SessionWebBotAuth | undefined>();
expectTypeOf<SessionWebBotAuth>().toEqualTypeOf<{
signatureAgent: string;
onlyDomains?: string[];
}>();
});