@webcrawlerapi/stealth 1.0.0 → 1.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.
package/README.md CHANGED
@@ -30,6 +30,7 @@ The two functions you normally need are:
30
30
  | `matchMedia` pointer / hover | normalized |
31
31
  | `window.outerWidth` / `outerHeight` | corrected to inner + chrome |
32
32
  | `Error.prepareStackTrace` | installed if absent |
33
+ | `User-Agent` header + `navigator.userAgent` (`HeadlessChrome` token) | read from `browser.userAgent()` and `HeadlessChrome` → `Chrome` via `page.setUserAgent`; never hardcoded, so the version always matches the real engine. Skip with `fixUserAgent: false`, or pass `userAgent` |
33
34
 
34
35
  ---
35
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webcrawlerapi/stealth",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Zero-dependency stealth layer for Puppeteer / CDP browsers (works with launch() and connect()).",
5
5
  "main": "stealth.js",
6
6
  "types": "stealth.d.ts",
package/stealth.d.ts CHANGED
@@ -19,6 +19,10 @@ export interface StealthOptions {
19
19
  userDataDir?: string;
20
20
  /** Launch only: extra chrome args. */
21
21
  args?: string[];
22
+ /** Explicit User-Agent for the page. Default: browser UA with "HeadlessChrome" replaced by "Chrome". */
23
+ userAgent?: string;
24
+ /** Set to false to skip the User-Agent fix. Default true. */
25
+ fixUserAgent?: boolean;
22
26
  /** newPage only: origin for overridePermissions. */
23
27
  origin?: string;
24
28
  /** newPage only: permissions to grant. */
@@ -47,6 +51,7 @@ export declare const UA: string;
47
51
  export declare function buildInitScript(cfg?: StealthOptions): void;
48
52
  export declare function buildSource(opts?: StealthOptions): string;
49
53
  export declare function installStealth(page: PageLike, opts?: StealthOptions): Promise<PageLike>;
54
+ export declare function fixUserAgent(page: PageLike, opts?: StealthOptions): Promise<PageLike>;
50
55
  export declare function injectNow(page: PageLike, opts?: StealthOptions): Promise<PageLike>;
51
56
  export declare function installOopifHook(browser: BrowserLike, opts?: StealthOptions): BrowserLike;
52
57
  export declare function newPage(browser: BrowserLike, opts?: StealthOptions): Promise<PageLike>;
package/stealth.js CHANGED
@@ -21,7 +21,7 @@ const UA =
21
21
  'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' +
22
22
  '(KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36';
23
23
 
24
- const VERSION = '1.0.0';
24
+ const VERSION = '1.0.1';
25
25
 
26
26
  const DEFAULT_RENDERER =
27
27
  'ANGLE (Intel, Intel(R) UHD Graphics 630 (0x00003E9B) Direct3D11 vs_5_0 ps_5_0, D3D11)';
@@ -352,10 +352,43 @@ async function installStealth(page, opts) {
352
352
  opts = opts || {};
353
353
  const cfg = resolveConfig(opts);
354
354
  await page.evaluateOnNewDocument(buildInitScript, cfg);
355
+ await fixUserAgent(page, opts);
355
356
  try { page[INSTALLED] = true; } catch (_) {}
356
357
  return page;
357
358
  }
358
359
 
360
+ /**
361
+ * Headless Chrome announces itself as "HeadlessChrome/<ver>" in both the
362
+ * `User-Agent` request header and navigator.userAgent. The header is set by the
363
+ * network stack, so the in-page patches can't touch it; it has to go through
364
+ * the CDP UA override (page.setUserAgent).
365
+ *
366
+ * The UA is read from the running browser (browser.userAgent()) instead of
367
+ * being hardcoded, so the Chrome version always matches the real engine and
368
+ * never goes stale when the bundled Chrome is upgraded.
369
+ *
370
+ * opts.userAgent explicit UA, used as is
371
+ * opts.fixUserAgent set to false to skip
372
+ *
373
+ * No-op when the browser UA has no "HeadlessChrome" token (headful, connect()).
374
+ */
375
+ async function fixUserAgent(page, opts) {
376
+ opts = opts || {};
377
+ if (opts.fixUserAgent === false || typeof page.setUserAgent !== 'function') return page;
378
+ try {
379
+ let ua = opts.userAgent;
380
+ if (!ua) {
381
+ const browser = typeof page.browser === 'function' ? page.browser() : null;
382
+ if (!browser || typeof browser.userAgent !== 'function') return page;
383
+ const current = await browser.userAgent();
384
+ if (!/HeadlessChrome/.test(current)) return page;
385
+ ua = current.replace('HeadlessChrome', 'Chrome');
386
+ }
387
+ await page.setUserAgent(ua);
388
+ } catch (_) {}
389
+ return page;
390
+ }
391
+
359
392
  /**
360
393
  * Also inject the bootstrap into documents that are ALREADY loaded (useful when
361
394
  * you attach with connect() to a page that is already on the target URL).
@@ -468,6 +501,7 @@ module.exports = {
468
501
  buildInitScript,
469
502
  buildSource,
470
503
  installStealth,
504
+ fixUserAgent,
471
505
  injectNow,
472
506
  installOopifHook,
473
507
  newPage,