@aluvia/sdk 1.4.1 → 2.0.1

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 +194 -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
@@ -23,7 +23,10 @@ function asErrorEnvelope(value) {
23
23
  const message = error['message'];
24
24
  if (typeof code !== 'string' || typeof message !== 'string')
25
25
  return null;
26
- return { success: false, error: { code, message, details: error['details'] } };
26
+ return {
27
+ success: false,
28
+ error: { code, message, details: error['details'] },
29
+ };
27
30
  }
28
31
  function formatErrorDetails(details) {
29
32
  if (details == null)
@@ -61,9 +61,9 @@ class AluviaClient {
61
61
  this.pageStates = new WeakMap();
62
62
  /** Promise-based mutex to serialize handleDetectionResult's critical section. */
63
63
  this._detectionMutex = Promise.resolve();
64
- const apiKey = String(options.apiKey ?? "").trim();
64
+ const apiKey = String(options.apiKey ?? '').trim();
65
65
  if (!apiKey) {
66
- throw new errors_js_1.MissingApiKeyError("Aluvia apiKey is required");
66
+ throw new errors_js_1.MissingApiKeyError('Aluvia apiKey is required');
67
67
  }
68
68
  const strict = options.strict ?? true;
69
69
  this.options = { ...options, apiKey, strict };
@@ -71,12 +71,12 @@ class AluviaClient {
71
71
  if (connectionId !== undefined && !Number.isFinite(connectionId)) {
72
72
  throw new Error('connectionId must be a finite number');
73
73
  }
74
- const apiBaseUrl = options.apiBaseUrl ?? "https://api.aluvia.io/v1";
74
+ const apiBaseUrl = options.apiBaseUrl ?? 'https://api.aluvia.io/v1';
75
75
  const pollIntervalMs = Math.max(options.pollIntervalMs ?? 5000, 1000);
76
76
  const timeoutMs = options.timeoutMs;
77
- const gatewayProtocol = options.gatewayProtocol ?? "http";
78
- const gatewayPort = options.gatewayPort ?? (gatewayProtocol === "https" ? 8443 : 8080);
79
- const logLevel = options.logLevel ?? "info";
77
+ const gatewayProtocol = options.gatewayProtocol ?? 'http';
78
+ const gatewayPort = options.gatewayPort ?? (gatewayProtocol === 'https' ? 8443 : 8080);
79
+ const logLevel = options.logLevel ?? 'info';
80
80
  this.logger = new logger_js_1.Logger(logLevel);
81
81
  // Create ConfigManager
82
82
  this.configManager = new ConfigManager_js_1.ConfigManager({
@@ -98,7 +98,7 @@ class AluviaClient {
98
98
  });
99
99
  // Initialize block detection if configured
100
100
  if (options.blockDetection !== undefined || options.startPlaywright) {
101
- this.logger.debug("Initializing block detection");
101
+ this.logger.debug('Initializing block detection');
102
102
  const detectionConfig = options.blockDetection ?? { enabled: true };
103
103
  this.blockDetection = new BlockDetection_js_1.BlockDetection(detectionConfig, this.logger);
104
104
  }
@@ -115,10 +115,9 @@ class AluviaClient {
115
115
  };
116
116
  this.pageStates.set(page, pageState);
117
117
  // Capture navigation responses on main frame
118
- page.on("response", (response) => {
118
+ page.on('response', (response) => {
119
119
  try {
120
- if (response.request().isNavigationRequest() &&
121
- response.request().frame() === page.mainFrame()) {
120
+ if (response.request().isNavigationRequest() && response.request().frame() === page.mainFrame()) {
122
121
  pageState.lastResponse = response;
123
122
  pageState.skipFullPass = false;
124
123
  pageState.fastResult = null;
@@ -129,7 +128,7 @@ class AluviaClient {
129
128
  }
130
129
  });
131
130
  // Fast pass at domcontentloaded
132
- page.on("domcontentloaded", async () => {
131
+ page.on('domcontentloaded', async () => {
133
132
  if (!this.blockDetection)
134
133
  return;
135
134
  try {
@@ -146,13 +145,13 @@ class AluviaClient {
146
145
  }
147
146
  });
148
147
  // Full pass at load
149
- page.on("load", async () => {
148
+ page.on('load', async () => {
150
149
  if (!this.blockDetection || pageState.skipFullPass)
151
150
  return;
152
151
  try {
153
152
  // Wait for networkidle with timeout cap
154
153
  try {
155
- await page.waitForLoadState("networkidle", {
154
+ await page.waitForLoadState('networkidle', {
156
155
  timeout: this.blockDetection.getNetworkIdleTimeoutMs(),
157
156
  });
158
157
  }
@@ -168,7 +167,7 @@ class AluviaClient {
168
167
  }
169
168
  });
170
169
  // SPA detection via framenavigated
171
- page.on("framenavigated", async (frame) => {
170
+ page.on('framenavigated', async (frame) => {
172
171
  if (!this.blockDetection)
173
172
  return;
174
173
  try {
@@ -197,14 +196,14 @@ class AluviaClient {
197
196
  * Attaches page listeners to all existing and future pages in a context.
198
197
  */
199
198
  attachBlockDetectionListener(context) {
200
- this.logger.debug("Attaching block detection listener to context");
199
+ this.logger.debug('Attaching block detection listener to context');
201
200
  // Attach to existing pages
202
201
  try {
203
202
  const existingPages = context.pages();
204
203
  for (const page of existingPages) {
205
204
  this.attachPageListeners(page);
206
205
  // Check if page has already loaded (not about:blank)
207
- if (page.url() !== "about:blank" && this.blockDetection) {
206
+ if (page.url() !== 'about:blank' && this.blockDetection) {
208
207
  this.blockDetection
209
208
  .analyzeFull(page, null)
210
209
  .then((result) => {
@@ -220,7 +219,7 @@ class AluviaClient {
220
219
  // Ignore errors
221
220
  }
222
221
  // Attach to future pages
223
- context.on("page", (page) => {
222
+ context.on('page', (page) => {
224
223
  this.logger.debug(`New page detected: ${page.url()}`);
225
224
  this.attachPageListeners(page);
226
225
  });
@@ -254,15 +253,16 @@ class AluviaClient {
254
253
  if (!this.blockDetection.isAutoUnblock())
255
254
  return;
256
255
  // Check if auto-reload should fire for this blockStatus
257
- const shouldReload = result.blockStatus === "blocked" ||
258
- (result.blockStatus === "suspected" &&
259
- this.blockDetection.isAutoUnblockOnSuspected());
256
+ const shouldReload = result.blockStatus === 'blocked' ||
257
+ (result.blockStatus === 'suspected' && this.blockDetection.isAutoUnblockOnSuspected());
260
258
  if (!shouldReload)
261
259
  return;
262
260
  // Serialize the critical section: persistent-block state, rule updates, reload.
263
261
  // This prevents concurrent handlers from reading stale retriedUrls/persistentHostnames.
264
262
  let release;
265
- const gate = new Promise((resolve) => { release = resolve; });
263
+ const gate = new Promise((resolve) => {
264
+ release = resolve;
265
+ });
266
266
  const acquired = this._detectionMutex;
267
267
  this._detectionMutex = this._detectionMutex.then(() => gate);
268
268
  await acquired;
@@ -338,9 +338,7 @@ class AluviaClient {
338
338
  this.startPromise = (async () => {
339
339
  // Fetch initial configuration (may throw InvalidApiKeyError or ApiError)
340
340
  await this.configManager.init();
341
- const browserInstance = this.options.startPlaywright
342
- ? await this._initPlaywright()
343
- : undefined;
341
+ const browserInstance = this.options.startPlaywright ? await this._initPlaywright() : undefined;
344
342
  // Keep config fresh so routing decisions update without restarting.
345
343
  this.configManager.startPolling();
346
344
  try {
@@ -365,10 +363,10 @@ class AluviaClient {
365
363
  if (!d)
366
364
  return;
367
365
  try {
368
- if (typeof d.close === "function") {
366
+ if (typeof d.close === 'function') {
369
367
  await d.close();
370
368
  }
371
- else if (typeof d.destroy === "function") {
369
+ else if (typeof d.destroy === 'function') {
372
370
  d.destroy();
373
371
  }
374
372
  }
@@ -546,20 +544,20 @@ class AluviaClient {
546
544
  */
547
545
  async _initPlaywright() {
548
546
  try {
549
- const pw = await Promise.resolve().then(() => __importStar(require("playwright")));
547
+ const pw = await Promise.resolve().then(() => __importStar(require('playwright')));
550
548
  // @ts-ignore
551
549
  return pw.chromium;
552
550
  }
553
551
  catch {
554
552
  // Playwright not installed — attempt auto-install
555
- this.logger.info("Playwright not found. Installing playwright...");
556
- const { execSync } = await Promise.resolve().then(() => __importStar(require("node:child_process")));
553
+ this.logger.info('Playwright not found. Installing playwright...');
554
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('node:child_process')));
557
555
  try {
558
- execSync("npm install playwright", {
559
- stdio: "inherit",
556
+ execSync('npm install playwright', {
557
+ stdio: 'inherit',
560
558
  cwd: process.cwd(),
561
559
  });
562
- const pw = await Promise.resolve().then(() => __importStar(require("playwright")));
560
+ const pw = await Promise.resolve().then(() => __importStar(require('playwright')));
563
561
  // @ts-ignore
564
562
  return pw.chromium;
565
563
  }
@@ -3,47 +3,36 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.BlockDetection = void 0;
5
5
  const DEFAULT_CHALLENGE_SELECTORS = [
6
- "#challenge-form",
7
- "#challenge-running",
8
- ".cf-browser-verification",
6
+ '#challenge-form',
7
+ '#challenge-running',
8
+ '.cf-browser-verification',
9
9
  'iframe[src*="recaptcha"]',
10
- ".g-recaptcha",
11
- "#px-captcha",
10
+ '.g-recaptcha',
11
+ '#px-captcha',
12
12
  'iframe[src*="hcaptcha"]',
13
- ".h-captcha",
13
+ '.h-captcha',
14
14
  ];
15
15
  const TITLE_KEYWORDS = [
16
- "access denied",
17
- "blocked",
18
- "forbidden",
19
- "security check",
20
- "attention required",
21
- "just a moment",
22
- ];
23
- const STRONG_TEXT_KEYWORDS = [
24
- "captcha",
25
- "access denied",
26
- "verify you are human",
27
- "bot detection",
28
- ];
29
- const WEAK_TEXT_KEYWORDS = [
30
- "blocked",
31
- "forbidden",
32
- "cloudflare",
33
- "please verify",
34
- "unusual activity",
16
+ 'access denied',
17
+ 'blocked',
18
+ 'forbidden',
19
+ 'security check',
20
+ 'attention required',
21
+ 'just a moment',
35
22
  ];
23
+ const STRONG_TEXT_KEYWORDS = ['captcha', 'access denied', 'verify you are human', 'bot detection'];
24
+ const WEAK_TEXT_KEYWORDS = ['blocked', 'forbidden', 'cloudflare', 'please verify', 'unusual activity'];
36
25
  function escapeRegex(str) {
37
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
38
27
  }
39
28
  const WEAK_TEXT_REGEXES = WEAK_TEXT_KEYWORDS.map((keyword) => ({
40
29
  keyword,
41
- regex: new RegExp("\\b" + escapeRegex(keyword) + "\\b", "i"),
30
+ regex: new RegExp('\\b' + escapeRegex(keyword) + '\\b', 'i'),
42
31
  }));
43
32
  const CHALLENGE_DOMAIN_PATTERNS = [
44
- "/cdn-cgi/challenge-platform/",
45
- "challenges.cloudflare.com",
46
- "geo.captcha-delivery.com",
33
+ '/cdn-cgi/challenge-platform/',
34
+ 'challenges.cloudflare.com',
35
+ 'geo.captcha-delivery.com',
47
36
  ];
48
37
  /**
49
38
  * BlockDetection handles detection of website blocks, CAPTCHAs, and WAF challenges
@@ -87,9 +76,9 @@ class BlockDetection {
87
76
  // --- Scoring Engine ---
88
77
  computeScore(signals) {
89
78
  if (signals.length === 0)
90
- return { score: 0, blockStatus: "clear" };
79
+ return { score: 0, blockStatus: 'clear' };
91
80
  const score = 1 - signals.reduce((product, s) => product * (1 - s.weight), 1);
92
- const blockStatus = score >= 0.7 ? "blocked" : score >= 0.4 ? "suspected" : "clear";
81
+ const blockStatus = score >= 0.7 ? 'blocked' : score >= 0.4 ? 'suspected' : 'clear';
93
82
  return { score, blockStatus };
94
83
  }
95
84
  // --- Fast-pass Signal Detectors ---
@@ -102,15 +91,15 @@ class BlockDetection {
102
91
  name: `http_status_${status}`,
103
92
  weight: 0.85,
104
93
  details: `HTTP ${status} response`,
105
- source: "fast",
94
+ source: 'fast',
106
95
  };
107
96
  }
108
97
  if (status === 503) {
109
98
  return {
110
- name: "http_status_503",
99
+ name: 'http_status_503',
111
100
  weight: 0.6,
112
- details: "HTTP 503 response",
113
- source: "fast",
101
+ details: 'HTTP 503 response',
102
+ source: 'fast',
114
103
  };
115
104
  }
116
105
  return null;
@@ -121,23 +110,22 @@ class BlockDetection {
121
110
  return signals;
122
111
  try {
123
112
  const headers = response.headers?.() ?? {};
124
- const cfMitigated = headers["cf-mitigated"];
125
- if (cfMitigated &&
126
- cfMitigated.toLowerCase().includes("challenge")) {
113
+ const cfMitigated = headers['cf-mitigated'];
114
+ if (cfMitigated && cfMitigated.toLowerCase().includes('challenge')) {
127
115
  signals.push({
128
- name: "waf_header_cf_mitigated",
116
+ name: 'waf_header_cf_mitigated',
129
117
  weight: 0.9,
130
118
  details: `cf-mitigated: ${cfMitigated}`,
131
- source: "fast",
119
+ source: 'fast',
132
120
  });
133
121
  }
134
- const server = headers["server"];
135
- if (server && server.toLowerCase().includes("cloudflare")) {
122
+ const server = headers['server'];
123
+ if (server && server.toLowerCase().includes('cloudflare')) {
136
124
  signals.push({
137
- name: "waf_header_cloudflare",
125
+ name: 'waf_header_cloudflare',
138
126
  weight: 0.1,
139
127
  details: `server: ${server}`,
140
- source: "fast",
128
+ source: 'fast',
141
129
  });
142
130
  }
143
131
  }
@@ -153,10 +141,10 @@ class BlockDetection {
153
141
  for (const keyword of this.allTitleKeywords) {
154
142
  if (title.includes(keyword.toLowerCase())) {
155
143
  return {
156
- name: "title_keyword",
144
+ name: 'title_keyword',
157
145
  weight: 0.8,
158
146
  details: `Title contains "${keyword}"`,
159
- source: "full",
147
+ source: 'full',
160
148
  };
161
149
  }
162
150
  }
@@ -178,10 +166,10 @@ class BlockDetection {
178
166
  }, selectors);
179
167
  if (found) {
180
168
  return {
181
- name: "challenge_selector",
169
+ name: 'challenge_selector',
182
170
  weight: 0.8,
183
171
  details: `Challenge selector found: ${found}`,
184
- source: "full",
172
+ source: 'full',
185
173
  };
186
174
  }
187
175
  }
@@ -194,30 +182,27 @@ class BlockDetection {
194
182
  const signals = [];
195
183
  try {
196
184
  const text = useInnerText
197
- ? await page.evaluate(() => document.body?.innerText ?? "")
198
- : await page.evaluate(() => document.body?.textContent ?? "");
185
+ ? await page.evaluate(() => document.body?.innerText ?? '')
186
+ : await page.evaluate(() => document.body?.textContent ?? '');
199
187
  const textLower = text.toLowerCase();
200
188
  if (text.length < 50) {
201
189
  signals.push({
202
- name: "visible_text_short",
190
+ name: 'visible_text_short',
203
191
  weight: 0.2,
204
192
  details: `Visible text very short (${text.length} chars)`,
205
- source: "full",
193
+ source: 'full',
206
194
  });
207
195
  }
208
196
  // Strong keywords (substring match, short page < 500 chars)
209
197
  if (text.length < 500) {
210
- const allStrong = [
211
- ...STRONG_TEXT_KEYWORDS,
212
- ...this.config.extraKeywords,
213
- ];
198
+ const allStrong = [...STRONG_TEXT_KEYWORDS, ...this.config.extraKeywords];
214
199
  for (const keyword of allStrong) {
215
200
  if (textLower.includes(keyword.toLowerCase())) {
216
201
  signals.push({
217
- name: "visible_text_keyword_strong",
202
+ name: 'visible_text_keyword_strong',
218
203
  weight: 0.6,
219
204
  details: `Strong keyword "${keyword}" on short page`,
220
- source: "full",
205
+ source: 'full',
221
206
  });
222
207
  break;
223
208
  }
@@ -227,10 +212,10 @@ class BlockDetection {
227
212
  for (const { keyword, regex } of WEAK_TEXT_REGEXES) {
228
213
  if (regex.test(text)) {
229
214
  signals.push({
230
- name: "visible_text_keyword_weak",
215
+ name: 'visible_text_keyword_weak',
231
216
  weight: 0.15,
232
217
  details: `Weak keyword "${keyword}" found with word boundary`,
233
- source: "full",
218
+ source: 'full',
234
219
  });
235
220
  break;
236
221
  }
@@ -244,17 +229,16 @@ class BlockDetection {
244
229
  async detectTextToHtmlRatio(page) {
245
230
  try {
246
231
  const result = await page.evaluate(() => {
247
- const html = document.documentElement?.outerHTML ?? "";
248
- const text = document.body?.textContent ?? "";
232
+ const html = document.documentElement?.outerHTML ?? '';
233
+ const text = document.body?.textContent ?? '';
249
234
  return { htmlLength: html.length, textLength: text.length };
250
235
  });
251
- if (result.htmlLength >= 1000 &&
252
- result.textLength / result.htmlLength < 0.03) {
236
+ if (result.htmlLength >= 1000 && result.textLength / result.htmlLength < 0.03) {
253
237
  return {
254
- name: "low_text_ratio",
238
+ name: 'low_text_ratio',
255
239
  weight: 0.2,
256
240
  details: `Low text/HTML ratio: ${result.textLength}/${result.htmlLength}`,
257
- source: "full",
241
+ source: 'full',
258
242
  };
259
243
  }
260
244
  }
@@ -278,7 +262,7 @@ class BlockDetection {
278
262
  break;
279
263
  const redirectResponse = redirectedFrom.response?.();
280
264
  hops.push({
281
- url: redirectedFrom.url?.() ?? "",
265
+ url: redirectedFrom.url?.() ?? '',
282
266
  statusCode: redirectResponse?.status?.() ?? 0,
283
267
  });
284
268
  req = redirectedFrom;
@@ -291,24 +275,24 @@ class BlockDetection {
291
275
  for (const pattern of CHALLENGE_DOMAIN_PATTERNS) {
292
276
  if (hop.url.includes(pattern)) {
293
277
  signals.push({
294
- name: "redirect_to_challenge",
278
+ name: 'redirect_to_challenge',
295
279
  weight: 0.7,
296
280
  details: `Redirect through challenge domain: ${hop.url}`,
297
- source: "full",
281
+ source: 'full',
298
282
  });
299
283
  return { signals, chain };
300
284
  }
301
285
  }
302
286
  }
303
287
  // Also check the final response URL
304
- const finalUrl = response.url?.() ?? "";
288
+ const finalUrl = response.url?.() ?? '';
305
289
  for (const pattern of CHALLENGE_DOMAIN_PATTERNS) {
306
290
  if (finalUrl.includes(pattern)) {
307
291
  signals.push({
308
- name: "redirect_to_challenge",
292
+ name: 'redirect_to_challenge',
309
293
  weight: 0.7,
310
294
  details: `Final URL is challenge domain: ${finalUrl}`,
311
- source: "full",
295
+ source: 'full',
312
296
  });
313
297
  break;
314
298
  }
@@ -325,7 +309,7 @@ class BlockDetection {
325
309
  const meta = document.querySelector('meta[http-equiv="refresh"]');
326
310
  if (!meta)
327
311
  return null;
328
- const content = meta.getAttribute("content") ?? "";
312
+ const content = meta.getAttribute('content') ?? '';
329
313
  const match = content.match(/url\s*=\s*(.+)/i);
330
314
  return match ? match[1].trim() : null;
331
315
  });
@@ -333,10 +317,10 @@ class BlockDetection {
333
317
  for (const pattern of CHALLENGE_DOMAIN_PATTERNS) {
334
318
  if (refreshUrl.includes(pattern)) {
335
319
  return {
336
- name: "meta_refresh_challenge",
320
+ name: 'meta_refresh_challenge',
337
321
  weight: 0.65,
338
322
  details: `Meta refresh to challenge URL: ${refreshUrl}`,
339
- source: "full",
323
+ source: 'full',
340
324
  };
341
325
  }
342
326
  }
@@ -356,7 +340,7 @@ class BlockDetection {
356
340
  const url = page.url();
357
341
  const hostname = this.extractHostname(url);
358
342
  if (!this.config.enabled) {
359
- return this.makeResult(url, hostname, [], "fast", []);
343
+ return this.makeResult(url, hostname, [], 'fast', []);
360
344
  }
361
345
  const signals = [];
362
346
  const statusSignal = this.detectHttpStatus(response);
@@ -364,7 +348,7 @@ class BlockDetection {
364
348
  signals.push(statusSignal);
365
349
  const headerSignals = this.detectResponseHeaders(response);
366
350
  signals.push(...headerSignals);
367
- const result = this.makeResult(url, hostname, signals, "fast", []);
351
+ const result = this.makeResult(url, hostname, signals, 'fast', []);
368
352
  this.logResult(result);
369
353
  return result;
370
354
  }
@@ -399,12 +383,10 @@ class BlockDetection {
399
383
  const url = page.url();
400
384
  const hostname = this.extractHostname(url);
401
385
  if (!this.config.enabled) {
402
- return this.makeResult(url, hostname, [], "full", []);
386
+ return this.makeResult(url, hostname, [], 'full', []);
403
387
  }
404
388
  // Start with fast-pass signals
405
- const signals = fastResult
406
- ? [...fastResult.signals]
407
- : [];
389
+ const signals = fastResult ? [...fastResult.signals] : [];
408
390
  // If no fast pass was done and we have a response, run fast detectors
409
391
  if (!fastResult && response) {
410
392
  const statusSignal = this.detectHttpStatus(response);
@@ -413,7 +395,7 @@ class BlockDetection {
413
395
  const headerSignals = this.detectResponseHeaders(response);
414
396
  signals.push(...headerSignals);
415
397
  }
416
- signals.push(...await this.runContentDetectors(page));
398
+ signals.push(...(await this.runContentDetectors(page)));
417
399
  const { signals: redirectSignals, chain } = this.detectRedirectChain(response);
418
400
  signals.push(...redirectSignals);
419
401
  return this.reEvaluateIfSuspected(page, url, hostname, signals, chain);
@@ -425,7 +407,7 @@ class BlockDetection {
425
407
  const url = page.url();
426
408
  const hostname = this.extractHostname(url);
427
409
  if (!this.config.enabled) {
428
- return this.makeResult(url, hostname, [], "full", []);
410
+ return this.makeResult(url, hostname, [], 'full', []);
429
411
  }
430
412
  const signals = await this.runContentDetectors(page);
431
413
  return this.reEvaluateIfSuspected(page, url, hostname, signals, []);
@@ -433,14 +415,14 @@ class BlockDetection {
433
415
  async reEvaluateIfSuspected(page, url, hostname, signals, redirectChain) {
434
416
  const preliminary = this.computeScore(signals);
435
417
  if (preliminary.score >= 0.4 && preliminary.score < 0.7) {
436
- const nonTextSignals = signals.filter((s) => !s.name.startsWith("visible_text_"));
418
+ const nonTextSignals = signals.filter((s) => !s.name.startsWith('visible_text_'));
437
419
  const innerTextSignals = await this.detectVisibleText(page, true);
438
420
  nonTextSignals.push(...innerTextSignals);
439
- const result = this.makeResult(url, hostname, nonTextSignals, "full", redirectChain);
421
+ const result = this.makeResult(url, hostname, nonTextSignals, 'full', redirectChain);
440
422
  this.logResult(result);
441
423
  return result;
442
424
  }
443
- const result = this.makeResult(url, hostname, signals, "full", redirectChain);
425
+ const result = this.makeResult(url, hostname, signals, 'full', redirectChain);
444
426
  this.logResult(result);
445
427
  return result;
446
428
  }
@@ -62,7 +62,12 @@ function matchPattern(hostname, pattern) {
62
62
  */
63
63
  function normalizeRules(rules) {
64
64
  if (!rules || rules.length === 0) {
65
- return { positiveRules: [], negativeRules: [], hasCatchAll: false, empty: true };
65
+ return {
66
+ positiveRules: [],
67
+ negativeRules: [],
68
+ hasCatchAll: false,
69
+ empty: true,
70
+ };
66
71
  }
67
72
  const trimmed = rules
68
73
  .filter((r) => typeof r === 'string')
@@ -70,7 +75,12 @@ function normalizeRules(rules) {
70
75
  .filter((r) => r.length > 0)
71
76
  .filter((r) => r !== 'auto');
72
77
  if (trimmed.length === 0) {
73
- return { positiveRules: [], negativeRules: [], hasCatchAll: false, empty: true };
78
+ return {
79
+ positiveRules: [],
80
+ negativeRules: [],
81
+ hasCatchAll: false,
82
+ empty: true,
83
+ };
74
84
  }
75
85
  const negativeRules = [];
76
86
  const positiveRules = [];
@@ -96,8 +96,8 @@ async function connect(sessionName) {
96
96
  let context;
97
97
  let page;
98
98
  try {
99
- context = browser.contexts()[0] ?? await browser.newContext();
100
- page = context.pages()[0] ?? await context.newPage();
99
+ context = browser.contexts()[0] ?? (await browser.newContext());
100
+ page = context.pages()[0] ?? (await context.newPage());
101
101
  }
102
102
  catch (err) {
103
103
  await browser.close().catch(() => { });
package/dist/cjs/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Aluvia Client Node
3
3
  // Main entry point
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.ConnectError = exports.ProxyStartError = exports.ApiError = exports.InvalidApiKeyError = exports.MissingApiKeyError = exports.connect = exports.AluviaApi = exports.AluviaClient = void 0;
5
+ exports.toLockData = exports.listSessions = exports.generateSessionName = exports.validateSessionName = exports.getLogFilePath = exports.isProcessAlive = exports.removeLock = exports.readLock = exports.writeLock = exports.ConnectError = exports.ProxyStartError = exports.ApiError = exports.InvalidApiKeyError = exports.MissingApiKeyError = exports.connect = exports.AluviaApi = exports.AluviaClient = void 0;
6
6
  // Public class
7
7
  var AluviaClient_js_1 = require("./client/AluviaClient.js");
8
8
  Object.defineProperty(exports, "AluviaClient", { enumerable: true, get: function () { return AluviaClient_js_1.AluviaClient; } });
@@ -18,3 +18,14 @@ Object.defineProperty(exports, "InvalidApiKeyError", { enumerable: true, get: fu
18
18
  Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return errors_js_1.ApiError; } });
19
19
  Object.defineProperty(exports, "ProxyStartError", { enumerable: true, get: function () { return errors_js_1.ProxyStartError; } });
20
20
  Object.defineProperty(exports, "ConnectError", { enumerable: true, get: function () { return errors_js_1.ConnectError; } });
21
+ // Session lock utilities (used by CLI)
22
+ var lock_js_1 = require("./session/lock.js");
23
+ Object.defineProperty(exports, "writeLock", { enumerable: true, get: function () { return lock_js_1.writeLock; } });
24
+ Object.defineProperty(exports, "readLock", { enumerable: true, get: function () { return lock_js_1.readLock; } });
25
+ Object.defineProperty(exports, "removeLock", { enumerable: true, get: function () { return lock_js_1.removeLock; } });
26
+ Object.defineProperty(exports, "isProcessAlive", { enumerable: true, get: function () { return lock_js_1.isProcessAlive; } });
27
+ Object.defineProperty(exports, "getLogFilePath", { enumerable: true, get: function () { return lock_js_1.getLogFilePath; } });
28
+ Object.defineProperty(exports, "validateSessionName", { enumerable: true, get: function () { return lock_js_1.validateSessionName; } });
29
+ Object.defineProperty(exports, "generateSessionName", { enumerable: true, get: function () { return lock_js_1.generateSessionName; } });
30
+ Object.defineProperty(exports, "listSessions", { enumerable: true, get: function () { return lock_js_1.listSessions; } });
31
+ Object.defineProperty(exports, "toLockData", { enumerable: true, get: function () { return lock_js_1.toLockData; } });