webcrawl 1.3.2 → 1.3.3

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/dist/bin.js CHANGED
File without changes
@@ -43,8 +43,11 @@ export declare function lastFetchTier(): "impit-tls" | "impersonate" | "proxy-im
43
43
  export declare function lastProxyBytesCount(): number;
44
44
  /**
45
45
  * Residential proxy URL from env, or null when the tier is disabled.
46
- * Accepts a full `DECODO_URL` (`http://user:pass@host:port`) or the split
47
- * `DECODO_USER` + `DECODO_PWD` pair (gateway defaults to `gate.decodo.com:7000`).
46
+ * Generic: a full `WEBCRAWL_PROXY_URL` (`http://user:pass@host:port`) or the
47
+ * split `WEBCRAWL_PROXY_USER` + `WEBCRAWL_PROXY_PWD` pair, with the gateway
48
+ * host/port overridable via `WEBCRAWL_PROXY_HOST` / `WEBCRAWL_PROXY_PORT`.
49
+ * `DECODO_*` equivalents are honored as a legacy fallback so existing
50
+ * deployments keep working (their default gateway is `gate.decodo.com:7000`).
48
51
  */
49
52
  export declare function residentialProxyUrl(): string | null;
50
53
  /**
@@ -134,17 +134,22 @@ export function lastProxyBytesCount() {
134
134
  }
135
135
  /**
136
136
  * Residential proxy URL from env, or null when the tier is disabled.
137
- * Accepts a full `DECODO_URL` (`http://user:pass@host:port`) or the split
138
- * `DECODO_USER` + `DECODO_PWD` pair (gateway defaults to `gate.decodo.com:7000`).
137
+ * Generic: a full `WEBCRAWL_PROXY_URL` (`http://user:pass@host:port`) or the
138
+ * split `WEBCRAWL_PROXY_USER` + `WEBCRAWL_PROXY_PWD` pair, with the gateway
139
+ * host/port overridable via `WEBCRAWL_PROXY_HOST` / `WEBCRAWL_PROXY_PORT`.
140
+ * `DECODO_*` equivalents are honored as a legacy fallback so existing
141
+ * deployments keep working (their default gateway is `gate.decodo.com:7000`).
139
142
  */
140
143
  export function residentialProxyUrl() {
141
- const direct = process.env.DECODO_URL?.trim();
144
+ const direct = process.env.WEBCRAWL_PROXY_URL?.trim() || process.env.DECODO_URL?.trim();
142
145
  if (direct)
143
146
  return direct;
144
- const user = process.env.DECODO_USER?.trim();
145
- const pwd = process.env.DECODO_PWD?.trim();
147
+ const user = process.env.WEBCRAWL_PROXY_USER?.trim() || process.env.DECODO_USER?.trim();
148
+ const pwd = process.env.WEBCRAWL_PROXY_PWD?.trim() || process.env.DECODO_PWD?.trim();
146
149
  if (user && pwd) {
147
- return `http://${encodeURIComponent(user)}:${encodeURIComponent(pwd)}@gate.decodo.com:7000`;
150
+ const host = process.env.WEBCRAWL_PROXY_HOST?.trim() || "gate.decodo.com";
151
+ const port = process.env.WEBCRAWL_PROXY_PORT?.trim() || "7000";
152
+ return `http://${encodeURIComponent(user)}:${encodeURIComponent(pwd)}@${host}:${port}`;
148
153
  }
149
154
  return null;
150
155
  }
@@ -38,6 +38,15 @@ export type StepAction = Record<string, unknown> & {
38
38
  */
39
39
  export declare function interactBrowserAvailable(): boolean;
40
40
  export declare function interactBackend(): "local" | "sandbox" | "none";
41
+ /**
42
+ * Path to a real, user-installed Chrome when one exists, else null.
43
+ * Anti-bot vendors (DataDome, Akamai) fingerprint the automation build of
44
+ * Chromium and serve degraded pages to it, while the same engine driving a
45
+ * real installed Chrome gets full content — practitioner reports confirm
46
+ * this on DataDome-protected sites. Priority: explicit env override, then
47
+ * the standard per-OS install locations.
48
+ */
49
+ export declare function installedChromePath(): string | null;
41
50
  export declare function startBrowser(opts?: {
42
51
  timeoutMs?: number;
43
52
  userAgent?: string;
@@ -9,7 +9,7 @@
9
9
  * (Local World / `next dev`).
10
10
  */
11
11
  import { execFile, spawn } from "node:child_process";
12
- import { mkdtempSync, writeFileSync } from "node:fs";
12
+ import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
13
13
  import { tmpdir } from "node:os";
14
14
  import path from "node:path";
15
15
  import { Sandbox } from "@vercel/sandbox";
@@ -131,6 +131,38 @@ async function startWarmSandboxBrowser(domain) {
131
131
  }
132
132
  }
133
133
  /* ------------------------------ local backend ----------------------------- */
134
+ /**
135
+ * Path to a real, user-installed Chrome when one exists, else null.
136
+ * Anti-bot vendors (DataDome, Akamai) fingerprint the automation build of
137
+ * Chromium and serve degraded pages to it, while the same engine driving a
138
+ * real installed Chrome gets full content — practitioner reports confirm
139
+ * this on DataDome-protected sites. Priority: explicit env override, then
140
+ * the standard per-OS install locations.
141
+ */
142
+ export function installedChromePath() {
143
+ if (process.env.INTERACT_CHROME_PATH)
144
+ return process.env.INTERACT_CHROME_PATH;
145
+ const candidates = [];
146
+ if (process.platform === "darwin") {
147
+ candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", `${process.env.HOME || ""}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`);
148
+ }
149
+ else if (process.platform === "win32") {
150
+ candidates.push(`${process.env["ProgramFiles"] || "C:\\Program Files"}\\Google\\Chrome\\Application\\chrome.exe`, `${process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)"}\\Google\\Chrome\\Application\\chrome.exe`);
151
+ }
152
+ else {
153
+ candidates.push("/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium-browser", "/usr/bin/chromium", "/snap/bin/chromium");
154
+ }
155
+ for (const p of candidates) {
156
+ try {
157
+ if (existsSync(p))
158
+ return p;
159
+ }
160
+ catch {
161
+ /* not installed here */
162
+ }
163
+ }
164
+ return null;
165
+ }
134
166
  async function resolveLocalPlaywright() {
135
167
  const candidates = [
136
168
  process.env.WC_PLAYWRIGHT,
@@ -140,7 +172,10 @@ async function resolveLocalPlaywright() {
140
172
  for (const p of candidates) {
141
173
  try {
142
174
  const mod = (await import(/* webpackIgnore: true */ p));
143
- const chrome = process.env.INTERACT_CHROME_PATH || mod.chromium.executablePath();
175
+ // Prefer a real installed Chrome over playwright's bundled automation
176
+ // Chromium (see installedChromePath); fall back to the bundle when the
177
+ // host has no Chrome (CI, containers, minimal servers).
178
+ const chrome = installedChromePath() || mod.chromium.executablePath();
144
179
  return { playwright: p, chrome };
145
180
  }
146
181
  catch {
@@ -188,6 +188,13 @@ try {
188
188
  // only needs the document, its scripts and the challenge sensor (the
189
189
  // sensor host is never on the ad blocklist). A byte cap aborts the rest
190
190
  // of the page once the billed total blows past the budget.
191
+ // Anti-bot sensor/challenge hosts are ALWAYS allowed, before any block
192
+ // rule: blocking them would make the DataDome / Cloudflare / AWS WAF
193
+ // challenge unsolvable — no cookie is minted, the interstitial loops
194
+ // forever, and the failed sensor POST is itself a bot signal. The
195
+ // sensor payload is a few KB of form-encoded text, so allowing it
196
+ // costs essentially nothing against the byte budget.
197
+ const allowRe = /(api-js\.datadome\.co|js\.datadome\.co|ct\.captcha-delivery\.com|geo\.captcha-delivery\.com|challenges\.cloudflare\.com|[a-z0-9-]+\.awswaf\.com)/i;
191
198
  let proxyBilled = 0;
192
199
  if (action.lightweight || action.proxyUser) {
193
200
  const skip = new Set(action.proxyUser ? ["image", "media", "font", "stylesheet"] : ["image", "media", "font"]);
@@ -197,6 +204,7 @@ try {
197
204
  await page.route("**/*", (r) => {
198
205
  try {
199
206
  const req = r.request();
207
+ if (allowRe.test(req.url())) return r.continue(); // never block a challenge sensor, even past the cap
200
208
  if (proxyBilled > cap) return r.abort();
201
209
  if (skip.has(req.resourceType())) return r.abort();
202
210
  if (adRe.test(req.url())) return r.abort();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcrawl",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Webcrawl Anything CLI & Core Engine — crawl, search, and extract structured evidence from any website, SPA, PDF, Office file, YouTube video, GitHub repo, ArXiv paper, or CVE",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -127,8 +127,8 @@
127
127
  "@ai-sdk/gateway": "^4.0.123",
128
128
  "@firecrawl/pdf-inspector": "^1.25.2",
129
129
  "@vercel/sandbox": "^1.0.0",
130
- "@webcrawl/client": "^1.3.2",
131
- "@webcrawl/curl-impersonate": "^1.3.2",
130
+ "@webcrawl/client": "^1.3.3",
131
+ "@webcrawl/curl-impersonate": "^1.3.3",
132
132
  "ai": "^7.0.150",
133
133
  "impit": "^0.14.7",
134
134
  "mammoth": "^1.13.0",