diff --git a/examples/browser-telemetry.ts b/examples/browser-telemetry.ts new file mode 100644 index 0000000..e2dbe43 --- /dev/null +++ b/examples/browser-telemetry.ts @@ -0,0 +1,30 @@ +import Kernel from '@onkernel/sdk'; + +async function main() { + const kernel = new Kernel(); + + // Create a browser with telemetry enabled so it emits events while it runs. + const browser = await kernel.browsers.create({ telemetry: { enabled: true } }); + + try { + // Telemetry is a default routing subresource, so the stream goes directly to the VM automatically. + const stream = await kernel.browsers.telemetry.stream(browser.session_id); + + // Make browser activity to generate telemetry. The "api" category emits an event per VM API call, + // so events arrive within ~1s. + for (let i = 0; i < 3; i++) { + await kernel.browsers.curl(browser.session_id, { url: 'https://example.com', method: 'GET' }); + } + + // Print a few events, then stop so the program terminates promptly. + let count = 0; + for await (const event of stream) { + console.log('telemetry event', event); + if (++count >= 3) break; + } + } finally { + await kernel.browsers.deleteByID(browser.session_id); + } +} + +void main(); diff --git a/src/lib/browser-routing.ts b/src/lib/browser-routing.ts index 6580da6..bbedec2 100644 --- a/src/lib/browser-routing.ts +++ b/src/lib/browser-routing.ts @@ -28,7 +28,7 @@ export class BrowserRouteCache { } const BROWSER_ROUTING_SUBRESOURCES_ENV = 'KERNEL_BROWSER_ROUTING_SUBRESOURCES'; -const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl']; +const DEFAULT_BROWSER_ROUTING_SUBRESOURCES = ['curl', 'telemetry']; const BROWSER_ROUTE_CACHEABLE_PATH = /^\/(?:v\d+\/)?browsers(?:\/[^/]+)?\/?$/; const BROWSER_POOL_ACQUIRE_PATH = /^\/(?:v\d+\/)?browser_pools\/[^/]+\/acquire\/?$/; const BROWSER_DELETE_BY_ID_PATH = /^\/(?:v\d+\/)?browsers\/([^/]+)\/?$/; diff --git a/tests/lib/browser-routing.test.ts b/tests/lib/browser-routing.test.ts index 1ca285c..da87ad6 100644 --- a/tests/lib/browser-routing.test.ts +++ b/tests/lib/browser-routing.test.ts @@ -381,9 +381,41 @@ describe('browser routing', () => { ).rejects.toThrow(/unsupported HTTP method/i); }); - test('defaults browser routing subresources to curl when env is unset', async () => { + test('defaults browser routing subresources to curl and telemetry when env is unset', async () => { await withBrowserRoutingEnv(undefined, async () => { - expect(browserRoutingSubresourcesFromEnv()).toEqual(['curl']); + expect(browserRoutingSubresourcesFromEnv()).toEqual(['curl', 'telemetry']); + }); + }); + + test('routes telemetry stream calls to the VM /telemetry/stream path by default', async () => { + await withBrowserRoutingEnv(undefined, async () => { + const calls: Array<{ url: string; headers: Headers }> = []; + const kernel = new Kernel({ + apiKey: 'k', + baseURL: 'https://api.example/', + fetch: async (input, init?: RequestInit) => { + const url = normalizeURL(input); + const headers = input instanceof Request ? new Headers(input.headers) : new Headers(init?.headers); + calls.push({ url, headers }); + if (url === 'https://api.example/browsers') { + return Response.json({ + session_id: 'sess-1', + base_url: 'http://browser-session.test/browser/kernel', + cdp_ws_url: 'wss://browser-session.test/browser/cdp?jwt=token-abc', + }); + } + return new Response('id: 1\ndata: {"seq":1}\n\n', { + status: 200, + headers: { 'content-type': 'text/event-stream' }, + }); + }, + }); + + await kernel.browsers.create(); + await kernel.browsers.telemetry.stream('sess-1'); + + expect(calls[1]?.url).toBe('http://browser-session.test/browser/kernel/telemetry/stream?jwt=token-abc'); + expect(calls[1]?.headers.get('authorization')).toBeNull(); }); });