@lelantos-ai/cdp-adapter
v0.1.1
Published
Customer-side adapter for connecting Playwright/Puppeteer/raw CDP to a Lelantos browser sandbox. Wraps the bearer-subprotocol WS upgrade so the SDK's connect call works unmodified.
Readme
@lelantos-ai/cdp-adapter
Connect Playwright / Puppeteer / raw CDP clients to a Lelantos browser sandbox without modifying the SDK.
Why
Lelantos sandboxes authenticate the CDP WebSocket upgrade via the
Sec-WebSocket-Protocol: bearer.<token> header — token-in-URL is
deliberately not supported because URLs leak through access logs,
Referer headers, browser history, and bookmarks.
Playwright's chromium.connectOverCDP() and Puppeteer's
puppeteer.connect({ browserWSEndpoint }) don't expose a WS-subprotocol
option, so they can't natively authenticate against our endpoint. This
adapter stands up an in-process loopback WebSocket that injects the
bearer subprotocol on the upstream connection. Your code looks identical
to the standard SDK shape, just with a local URL.
Install
npm install @lelantos-ai/cdp-adapterUsage
Playwright
import { chromium } from 'playwright';
import { connect } from '@lelantos-ai/cdp-adapter';
// 1. Create a sandbox via the Lelantos API (or your own helper).
const res = await fetch('https://api.lelantos.ai/browser-sandboxes', {
method: 'POST',
headers: { 'X-API-Key': process.env.LELANTOS_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ browser: 'chromium', templateID: 'uni-chromium', timeout: 600 }),
});
const sandbox = await res.json();
// For the Firefox engine, request browser: 'firefox' + the uni-firefox alias:
// body: JSON.stringify({ browser: 'firefox', templateID: 'uni-firefox', timeout: 600 })
// 2. Open the local bridge that injects the bearer subprotocol.
const bridge = await connect({
wsEndpoint: sandbox.wsEndpoint,
accessToken: sandbox.accessToken,
});
try {
// 3. Connect Playwright like you'd connect to a local browser.
const browser = await chromium.connectOverCDP(bridge.localWsUrl);
const page = await browser.contexts()[0].pages()[0] ?? await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'shot.png' });
await browser.close();
} finally {
await bridge.close();
}Puppeteer
import puppeteer from 'puppeteer-core';
import { connect } from '@lelantos-ai/cdp-adapter';
const bridge = await connect({ wsEndpoint, accessToken });
try {
const browser = await puppeteer.connect({ browserWSEndpoint: bridge.localWsUrl });
const page = (await browser.pages())[0] ?? await browser.newPage();
await page.goto('https://example.com');
await browser.disconnect();
} finally {
await bridge.close();
}Convenience wrapper (auto-close)
import { withBridge } from '@lelantos-ai/cdp-adapter';
import { chromium } from 'playwright';
await withBridge({ wsEndpoint, accessToken }, async (localWsUrl) => {
const browser = await chromium.connectOverCDP(localWsUrl);
// ... do work ...
await browser.close();
});
// bridge closed automaticallyOne-call helper (createBrowser)
Wraps create + bridge + connectOverCDP into a single call and hands you a
connected Playwright Browser. engine is 'chromium' or 'firefox'.
import { createBrowser } from '@lelantos-ai/cdp-adapter';
const { browser, close } = await createBrowser({
engine: 'chromium', // or 'firefox'
apiBase: 'https://api.lelantos.ai',
apiKey: process.env.LELANTOS_API_KEY,
});
try {
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
} finally {
await close(); // closes browser + bridge + deletes the sandbox
}Per-context proxy (hot-proxy model) — pass proxy and the helper creates a
context with that proxy after connecting, surfacing contextId:
const { browser, contextId, close } = await createBrowser({
engine: 'firefox',
apiBase: 'https://api.lelantos.ai',
apiKey: process.env.LELANTOS_API_KEY,
proxy: { server: 'http://gw.example.com:8080', username: 'u', password: 'p' },
});
createBrowserlazy-importsplaywright, so install it alongside this adapter when you use this helper.
API
connect({ wsEndpoint, accessToken, host?, port? })
Open a bridge. Returns { localWsUrl, close }.
| Option | Type | Default | Notes |
|---|---|---|---|
| wsEndpoint | string | required | Sandbox wsEndpoint from the API. |
| accessToken | string | required | Sandbox accessToken from the API. |
| host | string | 127.0.0.1 | Local bind address. |
| port | number | 0 | Local bind port; 0 picks an unused one. |
The bridge accepts an arbitrary number of incoming connections, but you typically only need one (Playwright / Puppeteer open a single WS to the browser). The same bridge can be reused across reconnects within the sandbox's lifetime.
withBridge(opts, fn)
Convenience wrapper. Opens a bridge, calls fn(localWsUrl), closes the
bridge on return or throw.
Security notes
- The bridge listens only on
127.0.0.1by default. Don't expose it to other hosts. - The token is transmitted to the sandbox via the standard WS handshake headers, not via URL.
- Each
connect()call opens a fresh TCP socket; there is no shared state across calls.
License
Apache 2.0.
