diff --git a/src/index.ts b/src/index.ts index 81aa335..b02338a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ export { APIPromise } from './core/api-promise'; export { Kernel, type ClientOptions } from './client'; export { type BrowserFetchInit } from './lib/browser-fetch'; export { BrowserRouteCache, type BrowserRoute } from './lib/browser-routing'; +export { acquire as acquireBrowserFromPool, type AcquireOutcome } from './lib/browser-pools'; export { PagePromise } from './core/pagination'; export { KernelError, diff --git a/src/lib/browser-pools.ts b/src/lib/browser-pools.ts new file mode 100644 index 0000000..34d9d04 --- /dev/null +++ b/src/lib/browser-pools.ts @@ -0,0 +1,41 @@ +import { NotFoundError } from '../core/error'; +import type { Kernel } from '../client'; +import type { RequestOptions } from '../internal/request-options'; +import type { BrowserPoolAcquireParams, BrowserPoolAcquireResponse } from '../resources/browser-pools'; + +export type AcquireOutcome = + | { status: 'acquired'; browser: BrowserPoolAcquireResponse } + | { status: 'timed_out' } + | { status: 'not_found' }; + +/** + * Long-polling acquire that surfaces the HTTP outcome as a typed result. + * + * Resolves to one of: + * + * - `{ status: 'acquired', browser }` — a browser was leased from the pool. + * - `{ status: 'timed_out' }` — the long poll expired without a browser becoming + * available. Retry to keep waiting. + * - `{ status: 'not_found' }` — no pool exists with the given id or name. + * + * Other API errors (auth, server errors, etc.) still reject. + */ +export async function acquire( + client: Kernel, + idOrName: string, + body: BrowserPoolAcquireParams = {}, + options?: RequestOptions, +): Promise { + try { + const { data, response } = await client.browserPools.acquire(idOrName, body, options).withResponse(); + if (response.status === 204) { + return { status: 'timed_out' }; + } + return { status: 'acquired', browser: data }; + } catch (err) { + if (err instanceof NotFoundError) { + return { status: 'not_found' }; + } + throw err; + } +} diff --git a/tests/lib/browser-pools.test.ts b/tests/lib/browser-pools.test.ts new file mode 100644 index 0000000..b7ce258 --- /dev/null +++ b/tests/lib/browser-pools.test.ts @@ -0,0 +1,42 @@ +import Kernel from '@onkernel/sdk'; + +import { acquire, type AcquireOutcome } from '../../src/lib/browser-pools'; + +const baseBrowser = { + session_id: 'sess-1', + base_url: 'http://browser-session.test/browser/kernel', + cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=t', + webdriver_ws_url: 'wss://x', + created_at: '2020-01-01T00:00:00Z', + headless: true, + stealth: false, + timeout_seconds: 60, +}; + +const clientWith = (fetcher: typeof fetch) => + new Kernel({ apiKey: 'k', baseURL: 'https://api.example/', fetch: fetcher }); + +describe('browser pool typed acquire', () => { + test('resolves to acquired on 200', async () => { + const client = clientWith(async () => Response.json(baseBrowser)); + const result: AcquireOutcome = await acquire(client, 'my-pool'); + expect(result.status).toBe('acquired'); + if (result.status === 'acquired') { + expect(result.browser.session_id).toBe('sess-1'); + } + }); + + test('resolves to timed_out on 204', async () => { + const client = clientWith(async () => new Response(null, { status: 204 })); + const result = await acquire(client, 'my-pool'); + expect(result.status).toBe('timed_out'); + }); + + test('resolves to not_found on 404', async () => { + const client = clientWith(async () => + Response.json({ code: 'not_found', message: 'pool not found' }, { status: 404 }), + ); + const result = await acquire(client, 'missing'); + expect(result.status).toBe('not_found'); + }); +});