Skip to content

Commit fc7c6f9

Browse files
committed
feat: add browser-scoped session client
Bind browser subresource calls to a browser session's base_url and expose raw HTTP through fetch so metro-routed access feels like normal JavaScript networking. Made-with: Cursor
1 parent 591019f commit fc7c6f9

7 files changed

Lines changed: 710 additions & 0 deletions

File tree

examples/browser-scoped.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Browser-scoped client: call metro-routed browser APIs without repeating the
3+
* session id, and run `fetch`-style HTTP through the browser network stack.
4+
*
5+
* Run after `yarn build` so `dist/` matches sources, or import from `src/` via
6+
* ts-node with path aliases.
7+
*/
8+
import Kernel from '@onkernel/sdk';
9+
10+
async function main() {
11+
const kernel = new Kernel();
12+
13+
const created = await kernel.browsers.create({});
14+
const browser = kernel.forBrowser(created);
15+
16+
await browser.computer.clickMouse({ x: 10, y: 10 });
17+
18+
const page = await browser.fetch('https://example.com', { method: 'GET' });
19+
console.log('status', page.status);
20+
21+
await kernel.browsers.deleteByID(created.session_id);
22+
}
23+
24+
void main();

src/client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import {
6767
Deployments,
6868
} from './resources/deployments';
6969
import { KernelApp } from './core/app-framework';
70+
import { KernelBrowserSession, type KernelBrowserInput } from './lib/kernel-browser-session';
7071
import {
7172
ExtensionDownloadFromChromeStoreParams,
7273
ExtensionListResponse,
@@ -879,6 +880,15 @@ export class Kernel {
879880
return new KernelApp(name);
880881
}
881882

883+
/**
884+
* Returns a browser-scoped client: subresource calls omit the session id and,
885+
* when the browser response includes base_url, requests are routed through the
886+
* metro HTTP base for that session.
887+
*/
888+
public forBrowser(browser: KernelBrowserInput): KernelBrowserSession {
889+
return new KernelBrowserSession(this, browser);
890+
}
891+
882892
static Kernel = this;
883893
static DEFAULT_TIMEOUT = 60000; // 1 minute
884894

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ export { Kernel as default } from './client';
55
export { type Uploadable, toFile } from './core/uploads';
66
export { APIPromise } from './core/api-promise';
77
export { Kernel, type ClientOptions } from './client';
8+
export {
9+
KernelBrowserSession,
10+
type BrowserFetchInit,
11+
type KernelBrowserInput,
12+
} from './lib/kernel-browser-session';
13+
export { type KernelBrowserLike, type ResolvedBrowserTransport } from './lib/browser-transport';
814
export { PagePromise } from './core/pagination';
915
export {
1016
KernelError,

src/lib/browser-transport.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { RequestOptions } from '../internal/request-options';
2+
3+
/**
4+
* Resolved HTTP routing for a browser session. Metro requests use defaultBaseURL
5+
* plus a per-request jwt query param. A future client-wide browser-id → base_url
6+
* cache can plug in by supplying an alternate resolver before constructing
7+
* {@link KernelBrowserSession}.
8+
*/
9+
export type ResolvedBrowserTransport = {
10+
sessionId: string;
11+
defaultBaseURL?: string | undefined;
12+
jwt?: string | undefined;
13+
};
14+
15+
export type KernelBrowserLike = {
16+
session_id: string;
17+
base_url?: string | null | undefined;
18+
cdp_ws_url?: string | null | undefined;
19+
/** When set, overrides jwt parsed from cdp_ws_url */
20+
jwt?: string | null | undefined;
21+
};
22+
23+
export function parseJwtFromCdpWsUrl(cdpWsUrl: string | null | undefined): string | undefined {
24+
if (!cdpWsUrl) {
25+
return undefined;
26+
}
27+
try {
28+
const u = new URL(cdpWsUrl);
29+
const jwt = u.searchParams.get('jwt');
30+
return jwt ?? undefined;
31+
} catch {
32+
return undefined;
33+
}
34+
}
35+
36+
export function resolveBrowserTransport(browser: KernelBrowserLike): ResolvedBrowserTransport {
37+
const sessionId = browser.session_id;
38+
const rawBase = browser.base_url?.trim();
39+
const defaultBaseURL = rawBase && rawBase.length > 0 ? rawBase : undefined;
40+
const jwt =
41+
(typeof browser.jwt === 'string' && browser.jwt.length > 0 ? browser.jwt : undefined) ??
42+
parseJwtFromCdpWsUrl(browser.cdp_ws_url ?? undefined);
43+
return { sessionId, defaultBaseURL, jwt };
44+
}
45+
46+
export function mergeBrowserScopedRequestOptions(
47+
transport: ResolvedBrowserTransport,
48+
options?: RequestOptions,
49+
): RequestOptions | undefined {
50+
if (!transport.defaultBaseURL) {
51+
return options;
52+
}
53+
const next: RequestOptions = { ...options, defaultBaseURL: transport.defaultBaseURL };
54+
if (transport.jwt) {
55+
const prev =
56+
options?.query && typeof options.query === 'object' && !Array.isArray(options.query) ?
57+
(options.query as Record<string, unknown>)
58+
: {};
59+
next.query = { ...prev, jwt: transport.jwt };
60+
}
61+
return next;
62+
}

0 commit comments

Comments
 (0)