@omnilogin/sdk
v0.1.0
Published
OmniLogin Automation SDK — manage browser profiles and automate with humanized interactions
Maintainers
Readme
@omnilogin/sdk
Official SDK for automating OmniLogin browser profiles with humanized interactions.
Manage profiles via REST, launch isolated browser sessions, drive them with a Playwright-style API. Clicks, fills, hovers and key presses are humanized by default to look natural.
📚 Looking for a full type-level reference (every option, every parameter, every example)? See API.md.
Install
npm install @omnilogin/sdk
# Node < 21 also needs: npm install wsPrerequisites
- OmniLogin desktop running locally — download from omnilogin.net.
- Bridge API enabled in Settings → API (default port
35353). - At least one browser profile created.
Quick Start
import { OmniLogin } from '@omnilogin/sdk';
const omni = new OmniLogin(); // defaults to http://localhost:35353
// Pick a profile to drive (any existing profile id from the desktop UI).
const profileId = 1;
// Launch the profile and connect a Session to its bridge.
const { session } = await omni.open(profileId);
// Drive the active tab — Playwright-style API.
await session.page.goto('https://example.com');
await session.page.locator('#username').fill('[email protected]');
await session.page.locator('#password').fill('secret');
await session.page.locator('button[type="submit"]').click();
// Use host services that ride the same bridge.
const titles = ['Result A', 'Result B'];
await session.services.file.export(titles, { path: 'C:/tmp/out.json', format: 'json' });
await session.services.sheets.append('YOUR_SPREADSHEET_ID', 'Sheet1!A:B', [['name', 42]]);
await omni.close(profileId);Architecture
┌─────────────────────────────────────┐
│ OmniLogin │ ← top-level
│ REST profile mgmt + open(id) │
└──────────┬──────────────────┬───────┘
uses │ │ returns
▼ │ │ ▼
┌──────────┐ │ │ ┌────────────┐
│ RestApi │ │ │ │ Session │
│ profiles │ │ │ │ one open │
│ groups │ │ │ │ browser │
│ proxies │ │ │ │ via WS │
│ workflows│ │ │ └────┬───────┘
│ aiApps │ │ │ │
│ ipv6 │ │ │ ▼
└──────────┘ │ │ ┌────────────────┐
(also as omni.rest) │ │ │ page · tabs │
│ cookies │
│ services │
└────────────────┘| Class | What it is | When to use |
| --- | --- | --- |
| OmniLogin | Top-level facade | Default. Profile mgmt + open() returning Session. |
| RestApi | HTTP REST client (also omni.rest) | Standalone profile management without launching a browser. |
| Session | One bridge connection to one open browser | Usually returned by omni.open(). Construct directly with a bridge URL to attach to a browser launched elsewhere — see Connecting to an already-running bridge. |
A Session exposes:
session.page— the active tab (locators, keyboard, mouse, snapshots)session.tabs— open / close / list / activate tabssession.cookies— cookies + storage statesession.services— host-side services (file, sheets, email, telegram, ai, http, imageSearch, profile, extension)
Connecting to an already-running bridge
If the browser was launched elsewhere (another process, the desktop UI, an earlier script) and you have its bridge URL, construct a Session directly:
import { Session } from '@omnilogin/sdk';
const session = new Session('ws://127.0.0.1:55301');
await session.connect();
await session.page.goto('https://example.com');
// ... drive page / tabs / cookies / services ...
await session.disconnect(); // closes the WebSocket; does NOT stop the browserThis skips the REST /open call entirely — useful for sidecars, background workers, or testing against a long-running profile. Use omni.open(profileId) for the standard launch-and-connect flow.
REST — profile & resource management
All REST operations are grouped by resource. Available via omni.profiles (shortcut) or omni.rest.profiles (canonical):
// List, read, create, clone, delete
const page = await omni.profiles.list({ page: 1, pageSize: 20 });
const profile = await omni.profiles.get(1);
const created = await omni.profiles.create({ name: 'My Profile' });
const cloned = await omni.profiles.clone(1, { name: 'Copy' });
await omni.profiles.delete(1);
// Partial update — pass only the fields you want to change.
// Each field maps to its own endpoint; fields are PUT sequentially.
// NOT atomic: if an intermediate PUT fails, earlier fields remain applied.
// Call `.get(id)` afterwards if you need the refreshed Profile object.
await omni.profiles.update(1, { name: 'Renamed' });
await omni.profiles.update(1, { status: 'active' });
await omni.profiles.update(1, { tags: ['vip', 'qa'] });
await omni.profiles.update(1, { proxyDisabled: true });
// Tags & proxies (multi-profile)
await omni.profiles.setTags([1, 2, 3], ['priority'], true); // append
await omni.profiles.assignProxy(7, [1, 2, 3]); // saved proxy → profiles
await omni.profiles.setEmbeddedProxy([1, 2], {
proxy_type: 'HTTP',
host: '1.2.3.4',
port: 8080,
user_name: 'u',
password: 'p',
});Other REST namespaces follow the same shape:
// Groups
await omni.groups.list();
await omni.groups.get(1);
await omni.groups.create('My Group');
await omni.groups.update(1, 'Renamed');
await omni.groups.delete(1);
// Proxies
await omni.proxies.list();
await omni.proxies.get(1);
await omni.proxies.create({ proxy_type: 'HTTP', host: '1.2.3.4', port: 8080 });
await omni.proxies.update(1, { proxy_type: 'HTTP', host: '5.6.7.8', port: 8080 });
await omni.proxies.delete(1);
// Workflows (classic automation)
const { taskId } = await omni.workflows.start('WORKFLOW_ID');
await omni.workflows.status(taskId!);
await omni.workflows.stop('WORKFLOW_ID');
await omni.workflows.stopAll();
// AI Apps (script-based automation)
await omni.aiApps.list();
await omni.aiApps.get('APP_ID');
await omni.aiApps.run('APP_ID', { mode: 'batch' });
await omni.aiApps.stop('APP_ID');
// IPv6 rotation
await omni.ipv6.rotate('PROXY_ID');
// Low-level browser control (usually replaced by omni.open / omni.close)
await omni.rest.browser.open(1, { launchBridge: true });
await omni.rest.browser.stop(1);
const isOpen = await omni.rest.browser.isActive(1);Browser automation — session.page
// Navigation
await session.page.goto('https://example.com', { waitUntil: 'load' });
await session.page.goBack();
await session.page.goForward();
await session.page.reload();
const url = await session.page.url();
const title = await session.page.title();
const html = await session.page.content();
// Locators (CSS, text=, label=, role=, testid=, xpath=)
await session.page.locator('#btn').click(); // humanized by default
await session.page.locator('input').fill('value');
await session.page.locator('input').pressSequentially('hi'); // char-by-char
await session.page.locator('select').selectOption(['us']);
await session.page.locator('a').hover();
const text = await session.page.locator('h1').textContent();
const src = await session.page.locator('img').getAttribute('src');
const count = await session.page.locator('.row').count();
// Convenience getBy* factories
session.page.getByText('Submit');
session.page.getByRole('button');
session.page.getByLabel('Email');
session.page.getByPlaceholder('Search...');
session.page.getByTestId('login-btn');
session.page.getByAltText('Logo');
// Keyboard / mouse
await session.page.keyboard.type('hello');
await session.page.keyboard.press('Enter');
await session.page.mouse.click(100, 200);
await session.page.mouse.move(300, 400);
await session.page.mouse.wheel(0, -200);
// Wait for state / network / function
await session.page.waitForLoadState('networkidle');
await session.page.waitForURL('https://example.com/dashboard');
await session.page.waitForResponse('/api/me');
// Snapshots (cheap for AI agents)
const ax = await session.page.accessibilitySnapshot({ depth: 10 });
const md = await session.page.markdownSnapshot({ maxLength: 30_000 });
// Screenshot (returns base64; pass options.path to save automatically)
const screenshotBase64 = await session.page.screenshot({ fullPage: true });
// Raw CDP escape hatch
await session.page.rpc.call('DOM.getDocument', { depth: -1, pierce: true });
session.page.rpc.on('Network.responseReceived', event => console.log(event));File upload (smart)
setInputFiles works on a real <input type="file"> and on any button / drag-zone / custom widget that opens a native file chooser on click. Each entry can be:
- An absolute local path
- An
http(s)://...URL — downloaded to a temp file - A
data:<mime>;base64,...— decoded to a temp file <filename>|<url-or-data>— saved with the given filename
await session.page
.locator('#upload')
.setInputFiles([
'C:/path/to/local.pdf',
'https://example.com/avatar.png',
'logo.svg|data:image/svg+xml;base64,PHN2Zy4uLg==',
]);
await session.page.locator('input[type="file"]').setInputFiles([]); // clearTabs & cookies — session-level
// Tabs
const opened = await session.tabs.open('https://x.com', { active: true });
const targetId = opened.targetId;
const { pages } = await session.tabs.list();
await session.tabs.activate(targetId);
await session.tabs.next();
await session.tabs.previous();
await session.tabs.close(targetId);
// Cookies + storage
const cookies = await session.cookies.list();
await session.cookies.add([{ name: 'sid', value: 'abc123', domain: '.example.com', path: '/' }]);
await session.cookies.clear();
const state = await session.cookies.getStorageState();
await session.cookies.setStorageState(state);Host services — session.services
Multi-method services live as namespaces; single-operation services are direct callables.
const { services } = session;
// Sheets
await services.sheets.get('SPREADSHEET_ID', 'Sheet1!A1:D10', { firstRowAsKey: true });
await services.sheets.update('SPREADSHEET_ID', 'Sheet1!A1', [['hello', 'world']]);
await services.sheets.append('SPREADSHEET_ID', 'Sheet1!A:B', [['name', 42]]);
await services.sheets.clear('SPREADSHEET_ID', 'Sheet1!A:D');
// File I/O
const text = await services.file.read('C:/tmp/in.txt');
await services.file.write('C:/tmp/out.txt', 'hello');
await services.file.export({ a: 1 }, { path: 'C:/tmp/out.json', format: 'json' });
const savedTo = await services.file.download('https://example.com/x.png', { path: 'C:/tmp/x.png' });
const pngBase64 = await services.file.download('https://example.com/x.png');
// Email (IMAP)
const messages = await services.email.read({
host: 'imap.gmail.com',
port: 993,
user: '[email protected]',
password: 'app-password',
tls: true,
folder: 'INBOX',
limit: 20,
});
// Profile (the running browser's profile — NOT REST profile mgmt)
const info = await services.profile.info();
await services.profile.setTags(['vip']);
// Direct callables
const res = await services.http('GET', 'https://api.example.com/foo', {
headers: { Authorization: 'Bearer ...' },
proxy: true,
});
await services.telegram('Job done', {
botToken: 'BOT_TOKEN',
chatId: '123456',
parseMode: 'HTML',
});
const reply = await services.ai('Summarize: ...', {
provider: 'openai',
apiKey: 'sk-...',
model: 'gpt-4o',
});
const matches = await services.imageSearch('C:/templates/button.png', {
multiple: true,
});
await services.extension.trigger('EXTENSION_ID');services.file.export() supports format: 'json' | 'csv' | 'text' and onConflict: 'overwrite' | 'append'. services.file.download() returns the saved path when options.path is provided, otherwise it returns the response body as base64.
Defaults & behaviors
- Humanized by default —
click,fill,hover,dblclick,check,uncheck,pressSequentiallyall pass{ humanize: true }. Override with{ humanize: false }. - Async/await everywhere — every method returns a
Promise. - Selector chaining —
session.page.locator('form').locator('input').first().fill('x').
Error handling
import { RpcError } from '@omnilogin/sdk';
try {
await session.page.locator('#missing').click();
} catch (err) {
if (err instanceof RpcError) {
console.error(err.code, err.message, err.data);
}
}Method priority — when to use what
When generating or reviewing automation code, escalate in this order:
- First-class SDK methods —
page.locator(),page.getBy*(),page.goto(),page.waitFor*(), locator text/value/state methods,accessibilitySnapshot(),markdownSnapshot(). - Raw CDP —
session.page.rpc.call()/.on()when the SDK doesn't expose the capability. - Runtime evaluate —
page.evaluate(),locator.evaluate(),locator.evaluateAll(),page.waitForFunction()only as a final fallback.
| ✅ Do | ❌ Avoid |
| --- | --- |
| await session.page.url() | await session.page.evaluate(() => location.href) |
| await session.page.title() | await session.page.evaluate(() => document.title) |
| await session.page.locator(s).textContent() | .evaluate(el => el.textContent) |
| await session.page.locator(s).count() | .evaluate(() => document.querySelectorAll(s).length) |
Resources
- Full API reference —
API.md: every class, method, option and type with examples - Machine-readable reference —
llms.txt: full API surface for LLM agents - Source / issues — github.com/omnilogin-app/omnilogin-sdk
- OmniLogin desktop — omnilogin.net
License
MIT © HiveSoft LTD
