@aluvia/sdk 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/README.md +162 -477
  3. package/dist/cjs/api/apiUtils.js +4 -1
  4. package/dist/cjs/client/AluviaClient.js +30 -32
  5. package/dist/cjs/client/BlockDetection.js +69 -87
  6. package/dist/cjs/client/rules.js +12 -2
  7. package/dist/cjs/connect.js +2 -2
  8. package/dist/cjs/index.js +12 -1
  9. package/dist/cjs/session/lock.js +40 -4
  10. package/dist/esm/api/apiUtils.js +4 -1
  11. package/dist/esm/client/AluviaClient.js +38 -40
  12. package/dist/esm/client/BlockDetection.js +69 -87
  13. package/dist/esm/client/rules.js +12 -2
  14. package/dist/esm/connect.js +2 -2
  15. package/dist/esm/index.js +6 -4
  16. package/dist/esm/session/lock.js +40 -4
  17. package/dist/types/client/AluviaClient.d.ts +2 -2
  18. package/dist/types/client/BlockDetection.d.ts +4 -4
  19. package/dist/types/client/types.d.ts +11 -11
  20. package/dist/types/index.d.ts +9 -7
  21. package/package.json +15 -23
  22. package/dist/cjs/bin/account.js +0 -31
  23. package/dist/cjs/bin/api-helpers.js +0 -58
  24. package/dist/cjs/bin/cli-adapter.js +0 -16
  25. package/dist/cjs/bin/cli.js +0 -245
  26. package/dist/cjs/bin/close.js +0 -120
  27. package/dist/cjs/bin/geos.js +0 -10
  28. package/dist/cjs/bin/mcp-helpers.js +0 -57
  29. package/dist/cjs/bin/open.js +0 -317
  30. package/dist/cjs/bin/session.js +0 -259
  31. package/dist/esm/bin/account.js +0 -28
  32. package/dist/esm/bin/api-helpers.js +0 -53
  33. package/dist/esm/bin/cli-adapter.js +0 -8
  34. package/dist/esm/bin/cli.js +0 -242
  35. package/dist/esm/bin/close.js +0 -117
  36. package/dist/esm/bin/geos.js +0 -7
  37. package/dist/esm/bin/mcp-helpers.js +0 -51
  38. package/dist/esm/bin/open.js +0 -280
  39. package/dist/esm/bin/session.js +0 -252
  40. package/dist/types/bin/account.d.ts +0 -1
  41. package/dist/types/bin/api-helpers.d.ts +0 -20
  42. package/dist/types/bin/cli-adapter.d.ts +0 -8
  43. package/dist/types/bin/cli.d.ts +0 -2
  44. package/dist/types/bin/close.d.ts +0 -1
  45. package/dist/types/bin/geos.d.ts +0 -1
  46. package/dist/types/bin/mcp-helpers.d.ts +0 -28
  47. package/dist/types/bin/open.d.ts +0 -21
  48. package/dist/types/bin/session.d.ts +0 -11
@@ -1,120 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleClose = handleClose;
4
- const lock_js_1 = require("../session/lock.js");
5
- const cli_js_1 = require("./cli.js");
6
- async function handleClose(sessionName, closeAll) {
7
- if (closeAll) {
8
- const sessions = (0, lock_js_1.listSessions)();
9
- if (sessions.length === 0) {
10
- return (0, cli_js_1.output)({ error: 'No running browser sessions found.', closed: [], count: 0 }, 1);
11
- }
12
- // Send SIGTERM to all sessions
13
- for (const s of sessions) {
14
- try {
15
- process.kill(s.pid, 'SIGTERM');
16
- }
17
- catch {
18
- // ignore
19
- }
20
- }
21
- // Wait up to 10 seconds for all processes to exit
22
- const maxWait = 40;
23
- const alive = new Set(sessions.map((s) => s.pid));
24
- for (let i = 0; i < maxWait && alive.size > 0; i++) {
25
- await new Promise((r) => setTimeout(r, 250));
26
- for (const pid of alive) {
27
- if (!(0, lock_js_1.isProcessAlive)(pid)) {
28
- alive.delete(pid);
29
- }
30
- }
31
- }
32
- // Force-kill any survivors
33
- for (const pid of alive) {
34
- try {
35
- process.kill(pid, 'SIGKILL');
36
- }
37
- catch {
38
- // ignore
39
- }
40
- }
41
- // Now remove all locks
42
- const closed = [];
43
- for (const s of sessions) {
44
- (0, lock_js_1.removeLock)(s.session);
45
- closed.push(s.session);
46
- }
47
- return (0, cli_js_1.output)({ message: 'All browser sessions closed.', closed, count: closed.length });
48
- }
49
- // If no session name specified, figure out what to close
50
- if (!sessionName) {
51
- const sessions = (0, lock_js_1.listSessions)();
52
- if (sessions.length === 0) {
53
- return (0, cli_js_1.output)({ error: 'No running browser sessions found.' }, 1);
54
- }
55
- if (sessions.length > 1) {
56
- return (0, cli_js_1.output)({
57
- error: 'Multiple sessions running. Specify --browser-session <name> or --all.',
58
- browserSessions: sessions.map((s) => s.session),
59
- }, 1);
60
- }
61
- // Single session — use its data directly instead of re-reading the lock file
62
- const session = sessions[0];
63
- sessionName = session.session;
64
- return closeSession(sessionName, (0, lock_js_1.toLockData)(session));
65
- }
66
- // Session name provided — need to verify it's alive
67
- const sessions = (0, lock_js_1.listSessions)();
68
- const match = sessions.find((s) => s.session === sessionName);
69
- if (!match) {
70
- return (0, cli_js_1.output)({ browserSession: sessionName, error: 'No running browser session found.' }, 1);
71
- }
72
- return closeSession(sessionName, (0, lock_js_1.toLockData)(match));
73
- }
74
- async function closeSession(sessionName, lock) {
75
- if (!(0, lock_js_1.isProcessAlive)(lock.pid)) {
76
- (0, lock_js_1.removeLock)(sessionName);
77
- return (0, cli_js_1.output)({
78
- browserSession: sessionName,
79
- message: 'Browser process was not running. Lock file cleaned up.',
80
- });
81
- }
82
- try {
83
- process.kill(lock.pid, 'SIGTERM');
84
- }
85
- catch (err) {
86
- return (0, cli_js_1.output)({ browserSession: sessionName, error: `Failed to stop process: ${err.message}` }, 1);
87
- }
88
- // Wait for the process to exit (up to 10 seconds)
89
- const maxWait = 40;
90
- for (let i = 0; i < maxWait; i++) {
91
- await new Promise((r) => setTimeout(r, 250));
92
- if (!(0, lock_js_1.isProcessAlive)(lock.pid)) {
93
- (0, lock_js_1.removeLock)(sessionName);
94
- return (0, cli_js_1.output)({
95
- browserSession: sessionName,
96
- pid: lock.pid,
97
- message: 'Browser session closed.',
98
- startUrl: lock.url ?? null,
99
- cdpUrl: lock.cdpUrl ?? null,
100
- connectionId: lock.connectionId ?? null,
101
- });
102
- }
103
- }
104
- // Force kill if still alive
105
- try {
106
- process.kill(lock.pid, 'SIGKILL');
107
- }
108
- catch {
109
- // ignore
110
- }
111
- (0, lock_js_1.removeLock)(sessionName);
112
- return (0, cli_js_1.output)({
113
- browserSession: sessionName,
114
- pid: lock.pid,
115
- message: 'Browser session force-killed.',
116
- startUrl: lock.url ?? null,
117
- cdpUrl: lock.cdpUrl ?? null,
118
- connectionId: lock.connectionId ?? null,
119
- });
120
- }
@@ -1,10 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleGeos = handleGeos;
4
- const api_helpers_js_1 = require("./api-helpers.js");
5
- const cli_js_1 = require("./cli.js");
6
- async function handleGeos() {
7
- const api = (0, api_helpers_js_1.requireApi)();
8
- const geos = await api.geos.list();
9
- return (0, cli_js_1.output)({ geos, count: geos.length });
10
- }
@@ -1,57 +0,0 @@
1
- "use strict";
2
- /**
3
- * MCP output capture helpers.
4
- *
5
- * The CLI handlers call `output()` which normally does `console.log` + `process.exit()`.
6
- * In MCP mode, we switch `output()` to throw an MCPOutputCapture instead,
7
- * allowing us to catch and return the data without exiting the process.
8
- *
9
- * Uses AsyncLocalStorage so concurrent MCP tool calls don't interfere.
10
- */
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.MCPOutputCapture = void 0;
13
- exports.isCapturing = isCapturing;
14
- exports.captureOutput = captureOutput;
15
- const node_async_hooks_1 = require("node:async_hooks");
16
- /**
17
- * Thrown by output() when in capture mode.
18
- * Contains the JSON data and exit code that would have been written to stdout.
19
- */
20
- class MCPOutputCapture {
21
- constructor(data, exitCode) {
22
- this.data = data;
23
- this.exitCode = exitCode;
24
- }
25
- }
26
- exports.MCPOutputCapture = MCPOutputCapture;
27
- const captureContext = new node_async_hooks_1.AsyncLocalStorage();
28
- function isCapturing() {
29
- return captureContext.getStore() ?? false;
30
- }
31
- /**
32
- * Run a CLI handler function in capture mode.
33
- * Returns the data that output() would have written to stdout.
34
- * Safe for concurrent use — each call gets its own async context.
35
- */
36
- async function captureOutput(fn) {
37
- return captureContext.run(true, async () => {
38
- try {
39
- await fn();
40
- // Handler completed without calling output() — shouldn't happen for CLI handlers
41
- return { data: { error: "Handler did not produce output" }, isError: true };
42
- }
43
- catch (err) {
44
- if (err instanceof MCPOutputCapture) {
45
- return {
46
- data: err.data,
47
- isError: err.exitCode !== 0,
48
- };
49
- }
50
- // Unexpected error (not from output())
51
- return {
52
- data: { error: err instanceof Error ? err.message : String(err) },
53
- isError: true,
54
- };
55
- }
56
- });
57
- }
@@ -1,317 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.handleOpen = handleOpen;
37
- exports.handleOpenDaemon = handleOpenDaemon;
38
- const AluviaClient_js_1 = require("../client/AluviaClient.js");
39
- const lock_js_1 = require("../session/lock.js");
40
- const cli_js_1 = require("./cli.js");
41
- const node_child_process_1 = require("node:child_process");
42
- const fs = __importStar(require("node:fs"));
43
- const path = __importStar(require("node:path"));
44
- const node_url_1 = require("node:url");
45
- // Determine the directory of this module at load time
46
- // @ts-ignore - import.meta.url exists at runtime in ESM
47
- const thisModuleDir = path.dirname((0, node_url_1.fileURLToPath)(import.meta.url));
48
- /**
49
- * Get the path to cli.js for spawning daemon processes.
50
- * Looks in the same directory as this module (works for both dev and installed).
51
- */
52
- function getCliScriptPath() {
53
- // cli.js should be in the same directory as open.js
54
- const cliPath = path.join(thisModuleDir, 'cli.js');
55
- if (fs.existsSync(cliPath)) {
56
- return cliPath;
57
- }
58
- throw new Error(`Could not find cli.js at ${cliPath}`);
59
- }
60
- /**
61
- * Called from cli.ts when running `session start <url>`.
62
- * Spawns the actual browser in a detached child and polls until ready.
63
- * Returns a Promise that resolves via process.exit() (never returns normally).
64
- */
65
- function handleOpen({ url, connectionId, headless, sessionName, autoUnblock, disableBlockDetection, run }) {
66
- // Generate session name if not provided
67
- const session = sessionName ?? (0, lock_js_1.generateSessionName)();
68
- // Validate session name early (before spawning daemon)
69
- if (sessionName && !(0, lock_js_1.validateSessionName)(sessionName)) {
70
- (0, cli_js_1.output)({ error: 'Invalid session name. Use only letters, numbers, hyphens, and underscores.' }, 1);
71
- }
72
- // Check for existing instance with this session name
73
- const existing = (0, lock_js_1.readLock)(session);
74
- if (existing !== null && (0, lock_js_1.isProcessAlive)(existing.pid)) {
75
- (0, cli_js_1.output)({
76
- error: `A browser session named '${session}' is already running.`,
77
- browserSession: session,
78
- startUrl: existing.url ?? null,
79
- cdpUrl: existing.cdpUrl ?? null,
80
- connectionId: existing.connectionId ?? null,
81
- pid: existing.pid,
82
- }, 1);
83
- }
84
- // Clean up stale lock if process is dead
85
- if (existing !== null) {
86
- (0, lock_js_1.removeLock)(session);
87
- }
88
- // Require API key
89
- const apiKey = process.env.ALUVIA_API_KEY;
90
- if (!apiKey) {
91
- (0, cli_js_1.output)({ error: 'ALUVIA_API_KEY environment variable is required.' }, 1);
92
- }
93
- // Spawn a detached child process that runs the daemon
94
- const logFile = (0, lock_js_1.getLogFilePath)(session);
95
- const out = fs.openSync(logFile, 'a');
96
- const args = ['--daemon', url, '--browser-session', session];
97
- if (connectionId != null) {
98
- args.push('--connection-id', String(connectionId));
99
- }
100
- if (!headless) {
101
- args.push('--headful');
102
- }
103
- if (autoUnblock) {
104
- args.push('--auto-unblock');
105
- }
106
- if (disableBlockDetection) {
107
- args.push('--disable-block-detection');
108
- }
109
- if (run) {
110
- args.push('--run', run);
111
- }
112
- let child;
113
- try {
114
- // Get the path to cli.js in the same directory as this module
115
- const cliPath = getCliScriptPath();
116
- child = (0, node_child_process_1.spawn)(process.execPath, [cliPath, ...args], {
117
- detached: true,
118
- stdio: ['ignore', out, out],
119
- env: { ...process.env, ALUVIA_API_KEY: apiKey },
120
- });
121
- child.unref();
122
- }
123
- catch (err) {
124
- fs.closeSync(out);
125
- return (0, cli_js_1.output)({ browserSession: session, error: `Failed to spawn browser process: ${err.message}` }, 1);
126
- }
127
- fs.closeSync(out);
128
- // Wait for the daemon to be fully ready (lock file with ready: true)
129
- return new Promise((resolve, reject) => {
130
- let attempts = 0;
131
- const maxAttempts = 240; // 60 seconds max
132
- const poll = setInterval(() => {
133
- attempts++;
134
- try {
135
- // Early exit if daemon process died
136
- if (child.pid && !(0, lock_js_1.isProcessAlive)(child.pid)) {
137
- clearInterval(poll);
138
- (0, lock_js_1.removeLock)(session);
139
- (0, cli_js_1.output)({
140
- browserSession: session,
141
- error: 'Browser process exited unexpectedly.',
142
- logFile,
143
- }, 1);
144
- }
145
- const lock = (0, lock_js_1.readLock)(session);
146
- if (lock && lock.ready) {
147
- clearInterval(poll);
148
- (0, cli_js_1.output)({
149
- browserSession: session,
150
- pid: lock.pid,
151
- startUrl: lock.url ?? null,
152
- cdpUrl: lock.cdpUrl ?? null,
153
- connectionId: lock.connectionId ?? null,
154
- blockDetection: lock.blockDetection ?? false,
155
- autoUnblock: lock.autoUnblock ?? false,
156
- });
157
- }
158
- if (attempts >= maxAttempts) {
159
- clearInterval(poll);
160
- const alive = child.pid ? (0, lock_js_1.isProcessAlive)(child.pid) : false;
161
- (0, cli_js_1.output)({
162
- browserSession: session,
163
- error: alive ? 'Browser is still initializing (timeout).' : 'Browser process exited unexpectedly.',
164
- logFile,
165
- }, 1);
166
- }
167
- }
168
- catch (err) {
169
- // In MCP capture mode, output() throws MCPOutputCapture which we need to propagate
170
- clearInterval(poll);
171
- reject(err);
172
- }
173
- }, 250);
174
- });
175
- }
176
- /**
177
- * Daemon entry point — runs in the detached child process.
178
- * Starts the proxy + browser, writes lock, and stays alive.
179
- * Logs go to the daemon log file (stdout is redirected), not to the user.
180
- */
181
- async function handleOpenDaemon({ url, connectionId, headless, sessionName, autoUnblock, disableBlockDetection, run }) {
182
- const apiKey = process.env.ALUVIA_API_KEY;
183
- const blockDetectionEnabled = !disableBlockDetection;
184
- const updateLockWithDetection = (result) => {
185
- const lock = (0, lock_js_1.readLock)(sessionName);
186
- if (!lock)
187
- return;
188
- const lastDetection = {
189
- hostname: result.hostname,
190
- lastUrl: result.url,
191
- blockStatus: result.blockStatus,
192
- score: result.score,
193
- signals: result.signals.map((s) => s.name),
194
- pass: result.pass,
195
- persistentBlock: result.persistentBlock,
196
- timestamp: Date.now(),
197
- };
198
- (0, lock_js_1.writeLock)({ ...lock, lastDetection }, sessionName);
199
- };
200
- const client = new AluviaClient_js_1.AluviaClient({
201
- apiKey,
202
- startPlaywright: true,
203
- ...(connectionId != null ? { connectionId } : {}),
204
- headless: headless ?? true,
205
- blockDetection: blockDetectionEnabled
206
- ? autoUnblock
207
- ? { enabled: true, autoUnblock: true, onDetection: updateLockWithDetection }
208
- : { enabled: true, onDetection: updateLockWithDetection }
209
- : { enabled: false },
210
- });
211
- const connection = await client.start();
212
- // Write early lock so parent knows daemon is alive
213
- (0, lock_js_1.writeLock)({ pid: process.pid, session: sessionName, url, proxyUrl: connection.url, blockDetection: blockDetectionEnabled, autoUnblock: blockDetectionEnabled && !!autoUnblock }, sessionName);
214
- if (autoUnblock)
215
- console.log('[daemon] Auto-unblock enabled');
216
- console.log(`[daemon] Browser initialized — proxy: ${connection.url}`);
217
- if (connection.cdpUrl)
218
- console.log(`[daemon] CDP URL: ${connection.cdpUrl}`);
219
- if (connectionId != null)
220
- console.log(`[daemon] Connection ID: ${connectionId}`);
221
- if (sessionName)
222
- console.log(`[daemon] Session: ${sessionName}`);
223
- console.log(`[daemon] Opening ${url}`);
224
- // Navigate to URL in the browser
225
- const page = await connection.browserContext.newPage();
226
- await page.goto(url, { waitUntil: 'domcontentloaded' });
227
- // Gather session info
228
- const pageTitle = await page.title().catch(() => '');
229
- const cdpUrl = connection.cdpUrl ?? '';
230
- // Get connection ID: use the one passed in, or read from ConfigManager
231
- const connId = connectionId ?? client.connectionId;
232
- // Write lock file with full session metadata (marks session as ready)
233
- // Read existing lock first to preserve lastDetection written by the onDetection callback
234
- const existingLock = (0, lock_js_1.readLock)(sessionName);
235
- (0, lock_js_1.writeLock)({
236
- pid: process.pid,
237
- session: sessionName,
238
- connectionId: connId,
239
- cdpUrl,
240
- proxyUrl: connection.url,
241
- url,
242
- ready: true,
243
- blockDetection: blockDetectionEnabled,
244
- autoUnblock: blockDetectionEnabled && !!autoUnblock,
245
- lastDetection: existingLock?.lastDetection,
246
- }, sessionName);
247
- console.log(`[daemon] Session ready — session: ${sessionName ?? 'default'}, url: ${url}, cdpUrl: ${cdpUrl}, connectionId: ${connId ?? 'unknown'}, pid: ${process.pid}`);
248
- if (pageTitle)
249
- console.log(`[daemon] Page title: ${pageTitle}`);
250
- // If --run was provided, execute the script and then shut down
251
- if (run) {
252
- const scriptPath = path.resolve(run);
253
- if (!fs.existsSync(scriptPath)) {
254
- console.error(`[daemon] Script not found: ${scriptPath}`);
255
- (0, lock_js_1.removeLock)(sessionName);
256
- await connection.close();
257
- process.exit(1);
258
- }
259
- console.log(`[daemon] Running script: ${scriptPath}`);
260
- // Inject page, browser, context as globals so the script can use them directly
261
- const browser = connection.browser;
262
- const context = connection.browserContext;
263
- globalThis.page = page;
264
- globalThis.browser = browser;
265
- globalThis.context = context;
266
- let exitCode = 0;
267
- try {
268
- await Promise.resolve(`${(0, node_url_1.pathToFileURL)(scriptPath).href}`).then(s => __importStar(require(s)));
269
- }
270
- catch (err) {
271
- console.error(`[daemon] Script error: ${err.message}`);
272
- if (err.stack)
273
- console.error(err.stack);
274
- exitCode = 1;
275
- }
276
- // Clean up globals
277
- delete globalThis.page;
278
- delete globalThis.browser;
279
- delete globalThis.context;
280
- console.log(`[daemon] Script finished.`);
281
- (0, lock_js_1.removeLock)(sessionName);
282
- await connection.close();
283
- process.exit(exitCode);
284
- }
285
- // Graceful shutdown handler
286
- let stopping = false;
287
- const shutdown = async () => {
288
- if (stopping)
289
- return;
290
- stopping = true;
291
- console.log('[daemon] Shutting down...');
292
- try {
293
- await connection.close();
294
- }
295
- catch {
296
- // ignore
297
- }
298
- (0, lock_js_1.removeLock)(sessionName);
299
- console.log('[daemon] Stopped.');
300
- process.exit(0);
301
- };
302
- process.on('SIGINT', shutdown);
303
- process.on('SIGTERM', shutdown);
304
- // Detect when the browser is closed by the user
305
- if (connection.browser) {
306
- connection.browser.on('disconnected', () => {
307
- if (!stopping) {
308
- console.log('[daemon] Browser closed by user.');
309
- (0, lock_js_1.removeLock)(sessionName);
310
- client
311
- .stop()
312
- .catch(() => { })
313
- .finally(() => process.exit(0));
314
- }
315
- });
316
- }
317
- }