Skip to content
Closed
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
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
41 changes: 41 additions & 0 deletions src/lib/browser-pools.ts
Original file line number Diff line number Diff line change
@@ -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<AcquireOutcome> {
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;
}
}
42 changes: 42 additions & 0 deletions tests/lib/browser-pools.test.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
Loading