mcp-scraper 0.40.2 → 0.40.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/README.md +1 -1
- package/dist/bin/api-server.cjs +1517 -236
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +155 -87
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +6 -5
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs +31 -9
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +4 -4
- package/dist/{chunk-XORPNO3Z.js → chunk-27DUCRAZ.js} +2 -2
- package/dist/{chunk-44HZLHDV.js → chunk-5UN33CGU.js} +38 -1
- package/dist/chunk-5UN33CGU.js.map +1 -0
- package/dist/{chunk-X2LKCX6H.js → chunk-6J57U6HA.js} +3 -3
- package/dist/{chunk-LP6E462I.js → chunk-6SZ52BQ5.js} +49 -101
- package/dist/chunk-6SZ52BQ5.js.map +1 -0
- package/dist/chunk-BBI7RGOT.js +99 -0
- package/dist/chunk-BBI7RGOT.js.map +1 -0
- package/dist/{chunk-NVXNEOUQ.js → chunk-CS4HE6QY.js} +47 -11
- package/dist/chunk-CS4HE6QY.js.map +1 -0
- package/dist/{chunk-3PIWJS6Y.js → chunk-EHES33KB.js} +2 -2
- package/dist/{chunk-GUVKHCKE.js → chunk-IQTT7CAB.js} +94 -3
- package/dist/chunk-IQTT7CAB.js.map +1 -0
- package/dist/{chunk-7XBBFBYY.js → chunk-JVIW4GK2.js} +44 -12
- package/dist/chunk-JVIW4GK2.js.map +1 -0
- package/dist/chunk-XU4ZLIB2.js +7 -0
- package/dist/chunk-XU4ZLIB2.js.map +1 -0
- package/dist/{db-W3CP562I.js → db-LVVU6NOK.js} +16 -2
- package/dist/{extract-bundle-K4PG3RZJ.js → extract-bundle-SKXEDG4Z.js} +4 -4
- package/dist/index.cjs +31 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{location-data-repository-RLQX6SNM.js → location-data-repository-NBTCOA7B.js} +3 -3
- package/dist/{server-KM3CFWCF.js → server-W76RUYD3.js} +1108 -66
- package/dist/server-W76RUYD3.js.map +1 -0
- package/dist/{site-extract-repository-2SMMFKKL.js → site-extract-repository-6GX72U4N.js} +4 -4
- package/dist/{worker-FXAGFYOE.js → worker-5RWLFSAJ.js} +27 -28
- package/dist/worker-5RWLFSAJ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-44HZLHDV.js.map +0 -1
- package/dist/chunk-7XBBFBYY.js.map +0 -1
- package/dist/chunk-GUVKHCKE.js.map +0 -1
- package/dist/chunk-LP6E462I.js.map +0 -1
- package/dist/chunk-NVXNEOUQ.js.map +0 -1
- package/dist/chunk-ZUJLSICT.js +0 -7
- package/dist/chunk-ZUJLSICT.js.map +0 -1
- package/dist/server-KM3CFWCF.js.map +0 -1
- package/dist/worker-FXAGFYOE.js.map +0 -1
- /package/dist/{chunk-XORPNO3Z.js.map → chunk-27DUCRAZ.js.map} +0 -0
- /package/dist/{chunk-X2LKCX6H.js.map → chunk-6J57U6HA.js.map} +0 -0
- /package/dist/{chunk-3PIWJS6Y.js.map → chunk-EHES33KB.js.map} +0 -0
- /package/dist/{db-W3CP562I.js.map → db-LVVU6NOK.js.map} +0 -0
- /package/dist/{extract-bundle-K4PG3RZJ.js.map → extract-bundle-SKXEDG4Z.js.map} +0 -0
- /package/dist/{location-data-repository-RLQX6SNM.js.map → location-data-repository-NBTCOA7B.js.map} +0 -0
- /package/dist/{site-extract-repository-2SMMFKKL.js.map → site-extract-repository-6GX72U4N.js.map} +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
publicErrorMessage
|
|
3
|
+
} from "./chunk-5UN33CGU.js";
|
|
4
|
+
|
|
5
|
+
// src/api/outbound-sanitize.ts
|
|
6
|
+
var KEY_RENAMES = {
|
|
7
|
+
kernel: "browserRuntime"
|
|
8
|
+
};
|
|
9
|
+
var SANITIZED_VALUE_KEYS = /error|message/i;
|
|
10
|
+
var INTERNAL_DIAGNOSTIC_KEYS = /* @__PURE__ */ new Set([
|
|
11
|
+
"kernel_session_id",
|
|
12
|
+
"browser_session_id",
|
|
13
|
+
"kernelSessionId",
|
|
14
|
+
"browserSessionId",
|
|
15
|
+
"sessionId",
|
|
16
|
+
"kernel_delete_started",
|
|
17
|
+
"session_cleanup_started",
|
|
18
|
+
"kernelDeleteStarted",
|
|
19
|
+
"sessionCleanupStarted",
|
|
20
|
+
"kernel_delete_succeeded",
|
|
21
|
+
"session_cleanup_succeeded",
|
|
22
|
+
"kernelDeleteSucceeded",
|
|
23
|
+
"sessionCleanupSucceeded",
|
|
24
|
+
"kernel_delete_error",
|
|
25
|
+
"session_cleanup_error",
|
|
26
|
+
"kernelDeleteError",
|
|
27
|
+
"sessionCleanupError",
|
|
28
|
+
"kernelProxyId",
|
|
29
|
+
"proxyId",
|
|
30
|
+
"requestedProxyIdSuffix",
|
|
31
|
+
"retrievedProxyIdSuffix",
|
|
32
|
+
"provider",
|
|
33
|
+
"providerName",
|
|
34
|
+
"service",
|
|
35
|
+
"serviceName",
|
|
36
|
+
"vendor",
|
|
37
|
+
"vendorName",
|
|
38
|
+
"organization",
|
|
39
|
+
"organizationId",
|
|
40
|
+
"accountId"
|
|
41
|
+
]);
|
|
42
|
+
var VENDOR_URL_KEYS = /* @__PURE__ */ new Set([
|
|
43
|
+
"hosted_url",
|
|
44
|
+
"hostedUrl",
|
|
45
|
+
"live_view_url",
|
|
46
|
+
"liveViewUrl",
|
|
47
|
+
"cdp_ws_url",
|
|
48
|
+
"cdpWsUrl",
|
|
49
|
+
"browser_live_view_url"
|
|
50
|
+
]);
|
|
51
|
+
var VENDOR_URL_RE = /\b(?:wss?|https?):\/\/[^\s"'<>]*\bonkernel\.com[^\s"'<>]*/gi;
|
|
52
|
+
function redactVendorUrls(value) {
|
|
53
|
+
return value.replace(VENDOR_URL_RE, "[browser-service]");
|
|
54
|
+
}
|
|
55
|
+
function sanitizeOutboundDiagnostics(value, parentKey = "") {
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
let out = redactVendorUrls(value);
|
|
58
|
+
if (SANITIZED_VALUE_KEYS.test(parentKey)) {
|
|
59
|
+
out = publicErrorMessage("service_unavailable");
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) return value.map((v) => sanitizeOutboundDiagnostics(v, parentKey));
|
|
64
|
+
if (value !== null && typeof value === "object") {
|
|
65
|
+
const out = {};
|
|
66
|
+
for (const [key, val] of Object.entries(value)) {
|
|
67
|
+
if (INTERNAL_DIAGNOSTIC_KEYS.has(key)) continue;
|
|
68
|
+
const renamed = KEY_RENAMES[key] ?? key;
|
|
69
|
+
if (VENDOR_URL_KEYS.has(key)) {
|
|
70
|
+
out[renamed] = null;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
out[renamed] = sanitizeOutboundDiagnostics(val, key);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function sanitizeAttempts(attempts) {
|
|
80
|
+
return attempts.map((a) => sanitizeOutboundDiagnostics(a));
|
|
81
|
+
}
|
|
82
|
+
function sanitizeHarvestResult(result) {
|
|
83
|
+
const diagnostics = result?.diagnostics;
|
|
84
|
+
if (!diagnostics?.debug) return result;
|
|
85
|
+
return {
|
|
86
|
+
...result,
|
|
87
|
+
diagnostics: {
|
|
88
|
+
...diagnostics,
|
|
89
|
+
debug: sanitizeOutboundDiagnostics(diagnostics.debug)
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
sanitizeOutboundDiagnostics,
|
|
96
|
+
sanitizeAttempts,
|
|
97
|
+
sanitizeHarvestResult
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=chunk-BBI7RGOT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/outbound-sanitize.ts"],"sourcesContent":["import { publicErrorMessage } from '../errors.js'\n\nconst KEY_RENAMES: Record<string, string> = {\n kernel: 'browserRuntime',\n}\n\nconst SANITIZED_VALUE_KEYS = /error|message/i\nconst INTERNAL_DIAGNOSTIC_KEYS = new Set([\n 'kernel_session_id',\n 'browser_session_id',\n 'kernelSessionId',\n 'browserSessionId',\n 'sessionId',\n 'kernel_delete_started',\n 'session_cleanup_started',\n 'kernelDeleteStarted',\n 'sessionCleanupStarted',\n 'kernel_delete_succeeded',\n 'session_cleanup_succeeded',\n 'kernelDeleteSucceeded',\n 'sessionCleanupSucceeded',\n 'kernel_delete_error',\n 'session_cleanup_error',\n 'kernelDeleteError',\n 'sessionCleanupError',\n 'kernelProxyId',\n 'proxyId',\n 'requestedProxyIdSuffix',\n 'retrievedProxyIdSuffix',\n 'provider',\n 'providerName',\n 'service',\n 'serviceName',\n 'vendor',\n 'vendorName',\n 'organization',\n 'organizationId',\n 'accountId',\n])\n\nconst VENDOR_URL_KEYS = new Set([\n 'hosted_url',\n 'hostedUrl',\n 'live_view_url',\n 'liveViewUrl',\n 'cdp_ws_url',\n 'cdpWsUrl',\n 'browser_live_view_url',\n])\nconst VENDOR_URL_RE = /\\b(?:wss?|https?):\\/\\/[^\\s\"'<>]*\\bonkernel\\.com[^\\s\"'<>]*/gi\n\nfunction redactVendorUrls(value: string): string {\n return value.replace(VENDOR_URL_RE, '[browser-service]')\n}\n\nexport function sanitizeOutboundDiagnostics<T>(value: T, parentKey = ''): T {\n if (typeof value === 'string') {\n let out = redactVendorUrls(value)\n if (SANITIZED_VALUE_KEYS.test(parentKey)) {\n out = publicErrorMessage('service_unavailable')\n }\n return out as T\n }\n if (Array.isArray(value)) return value.map(v => sanitizeOutboundDiagnostics(v, parentKey)) as T\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n if (INTERNAL_DIAGNOSTIC_KEYS.has(key)) continue\n const renamed = KEY_RENAMES[key] ?? key\n if (VENDOR_URL_KEYS.has(key)) {\n out[renamed] = null\n continue\n }\n out[renamed] = sanitizeOutboundDiagnostics(val, key)\n }\n return out as T\n }\n return value\n}\n\nexport function sanitizeAttempts<T>(attempts: T[]): T[] {\n return attempts.map(a => sanitizeOutboundDiagnostics(a))\n}\n\nexport function sanitizeHarvestResult<T extends Record<string, unknown>>(result: T): T {\n const diagnostics = result?.diagnostics as Record<string, unknown> | undefined\n if (!diagnostics?.debug) return result\n return {\n ...result,\n diagnostics: {\n ...diagnostics,\n debug: sanitizeOutboundDiagnostics(diagnostics.debug),\n },\n }\n}\n"],"mappings":";;;;;AAEA,IAAM,cAAsC;AAAA,EAC1C,QAAQ;AACV;AAEA,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B,oBAAI,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACD,IAAM,gBAAgB;AAEtB,SAAS,iBAAiB,OAAuB;AAC/C,SAAO,MAAM,QAAQ,eAAe,mBAAmB;AACzD;AAEO,SAAS,4BAA+B,OAAU,YAAY,IAAO;AAC1E,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,MAAM,iBAAiB,KAAK;AAChC,QAAI,qBAAqB,KAAK,SAAS,GAAG;AACxC,YAAM,mBAAmB,qBAAqB;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,OAAK,4BAA4B,GAAG,SAAS,CAAC;AACzF,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACzE,UAAI,yBAAyB,IAAI,GAAG,EAAG;AACvC,YAAM,UAAU,YAAY,GAAG,KAAK;AACpC,UAAI,gBAAgB,IAAI,GAAG,GAAG;AAC5B,YAAI,OAAO,IAAI;AACf;AAAA,MACF;AACA,UAAI,OAAO,IAAI,4BAA4B,KAAK,GAAG;AAAA,IACrD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,iBAAoB,UAAoB;AACtD,SAAO,SAAS,IAAI,OAAK,4BAA4B,CAAC,CAAC;AACzD;AAEO,SAAS,sBAAyD,QAAc;AACrF,QAAM,cAAc,QAAQ;AAC5B,MAAI,CAAC,aAAa,MAAO,QAAO;AAChC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,aAAa;AAAA,MACX,GAAG;AAAA,MACH,OAAO,4BAA4B,YAAY,KAAK;AAAA,IACtD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CaptchaError,
|
|
3
3
|
LocationMismatchError,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
|
|
5
|
+
RequestAbortedError,
|
|
6
|
+
publicErrorMessage
|
|
7
|
+
} from "./chunk-5UN33CGU.js";
|
|
6
8
|
import {
|
|
7
9
|
finishHarvestAttempt,
|
|
8
10
|
startHarvestAttempt
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-IQTT7CAB.js";
|
|
12
|
+
|
|
13
|
+
// src/api/webhook.ts
|
|
14
|
+
async function deliverWebhook(url, payload, retries = 3) {
|
|
15
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(url, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "content-type": "application/json" },
|
|
20
|
+
body: JSON.stringify(payload),
|
|
21
|
+
signal: AbortSignal.timeout(1e4)
|
|
22
|
+
});
|
|
23
|
+
if (res.ok) return;
|
|
24
|
+
console.warn(`[webhook] attempt ${attempt} \u2192 ${res.status} from ${url}`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.warn(`[webhook] attempt ${attempt} failed:`, err instanceof Error ? err.message : err);
|
|
27
|
+
}
|
|
28
|
+
if (attempt < retries) await new Promise((r) => setTimeout(r, 1e3 * attempt * 2));
|
|
29
|
+
}
|
|
30
|
+
console.error(`[webhook] gave up after ${retries} attempts for ${url}`);
|
|
31
|
+
}
|
|
10
32
|
|
|
11
33
|
// src/api/harvest-problems.ts
|
|
12
34
|
function errorMessage(err) {
|
|
@@ -25,13 +47,16 @@ function looksLikeProxyTunnelFailure(message) {
|
|
|
25
47
|
function looksLikeProxyUnavailable(message) {
|
|
26
48
|
return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message);
|
|
27
49
|
}
|
|
50
|
+
function looksLikeInternalServiceUnavailable(message) {
|
|
51
|
+
return /(?:spending|billing|organization|account).{0,80}(?:cap|limit|blocked|disabled)|(?:quota|capacity).{0,40}(?:reached|exceeded)|https?:\/\/\S*dashboard/i.test(message);
|
|
52
|
+
}
|
|
28
53
|
function classifyHarvestProblem(err) {
|
|
29
54
|
const message = errorMessage(err);
|
|
30
55
|
if (err instanceof RequestAbortedError) {
|
|
31
56
|
return {
|
|
32
57
|
error_code: "request_aborted",
|
|
33
58
|
error_type: "request_aborted",
|
|
34
|
-
message,
|
|
59
|
+
message: publicErrorMessage("request_aborted"),
|
|
35
60
|
retryable: true,
|
|
36
61
|
httpStatus: 408,
|
|
37
62
|
terminalStatus: "cancelled"
|
|
@@ -41,7 +66,7 @@ function classifyHarvestProblem(err) {
|
|
|
41
66
|
return {
|
|
42
67
|
error_code: "captcha_exhausted",
|
|
43
68
|
error_type: "captcha",
|
|
44
|
-
message,
|
|
69
|
+
message: publicErrorMessage("captcha_exhausted"),
|
|
45
70
|
retryable: true,
|
|
46
71
|
httpStatus: 503,
|
|
47
72
|
terminalStatus: "failed"
|
|
@@ -51,7 +76,7 @@ function classifyHarvestProblem(err) {
|
|
|
51
76
|
return {
|
|
52
77
|
error_code: "location_mismatch",
|
|
53
78
|
error_type: "location_mismatch",
|
|
54
|
-
message,
|
|
79
|
+
message: publicErrorMessage("location_mismatch"),
|
|
55
80
|
retryable: true,
|
|
56
81
|
httpStatus: 503,
|
|
57
82
|
terminalStatus: "failed"
|
|
@@ -61,7 +86,7 @@ function classifyHarvestProblem(err) {
|
|
|
61
86
|
return {
|
|
62
87
|
error_code: "proxy_tunnel_failed",
|
|
63
88
|
error_type: "proxy_tunnel_failed",
|
|
64
|
-
message,
|
|
89
|
+
message: publicErrorMessage("proxy_tunnel_failed"),
|
|
65
90
|
retryable: true,
|
|
66
91
|
httpStatus: 503,
|
|
67
92
|
terminalStatus: "failed"
|
|
@@ -71,7 +96,7 @@ function classifyHarvestProblem(err) {
|
|
|
71
96
|
return {
|
|
72
97
|
error_code: "proxy_unavailable",
|
|
73
98
|
error_type: "proxy_unavailable",
|
|
74
|
-
message,
|
|
99
|
+
message: publicErrorMessage("proxy_unavailable"),
|
|
75
100
|
retryable: true,
|
|
76
101
|
httpStatus: 503,
|
|
77
102
|
terminalStatus: "failed"
|
|
@@ -81,16 +106,26 @@ function classifyHarvestProblem(err) {
|
|
|
81
106
|
return {
|
|
82
107
|
error_code: "harvest_timeout",
|
|
83
108
|
error_type: "timeout",
|
|
84
|
-
message,
|
|
109
|
+
message: publicErrorMessage("harvest_timeout"),
|
|
85
110
|
retryable: true,
|
|
86
111
|
httpStatus: 504,
|
|
87
112
|
terminalStatus: "failed"
|
|
88
113
|
};
|
|
89
114
|
}
|
|
115
|
+
if (looksLikeInternalServiceUnavailable(message)) {
|
|
116
|
+
return {
|
|
117
|
+
error_code: "service_unavailable",
|
|
118
|
+
error_type: "service_unavailable",
|
|
119
|
+
message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
|
|
120
|
+
retryable: true,
|
|
121
|
+
httpStatus: 503,
|
|
122
|
+
terminalStatus: "failed"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
90
125
|
return {
|
|
91
126
|
error_code: "extraction_failed",
|
|
92
127
|
error_type: "extraction",
|
|
93
|
-
message,
|
|
128
|
+
message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
|
|
94
129
|
retryable: false,
|
|
95
130
|
httpStatus: 500,
|
|
96
131
|
terminalStatus: "failed"
|
|
@@ -150,9 +185,10 @@ function createHarvestAttemptRecorder(jobId, userId) {
|
|
|
150
185
|
}
|
|
151
186
|
|
|
152
187
|
export {
|
|
188
|
+
deliverWebhook,
|
|
153
189
|
classifyHarvestProblem,
|
|
154
190
|
serializeHarvestProblem,
|
|
155
191
|
harvestProblemResponse,
|
|
156
192
|
createHarvestAttemptRecorder
|
|
157
193
|
};
|
|
158
|
-
//# sourceMappingURL=chunk-
|
|
194
|
+
//# sourceMappingURL=chunk-CS4HE6QY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/webhook.ts","../src/api/harvest-problems.ts","../src/api/harvest-attempt-events.ts"],"sourcesContent":["export async function deliverWebhook(url: string, payload: object, retries = 3): Promise<void> {\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(10_000),\n })\n if (res.ok) return\n console.warn(`[webhook] attempt ${attempt} → ${res.status} from ${url}`)\n } catch (err) {\n console.warn(`[webhook] attempt ${attempt} failed:`, err instanceof Error ? err.message : err)\n }\n if (attempt < retries) await new Promise((r) => setTimeout(r, 1000 * attempt * 2))\n }\n console.error(`[webhook] gave up after ${retries} attempts for ${url}`)\n}\n","import {\n CaptchaError,\n LocationMismatchError,\n PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,\n RequestAbortedError,\n publicErrorMessage,\n} from '../errors.js'\n\nexport type HarvestProblemCode =\n | 'request_aborted'\n | 'captcha_exhausted'\n | 'location_mismatch'\n | 'proxy_tunnel_failed'\n | 'proxy_unavailable'\n | 'harvest_timeout'\n | 'service_unavailable'\n | 'extraction_failed'\n\nexport interface HarvestProblem {\n error_code: HarvestProblemCode\n error_type: string\n message: string\n retryable: boolean\n httpStatus: number\n terminalStatus: 'cancelled' | 'failed'\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n\nfunction looksLikeTimeout(err: unknown, message: string): boolean {\n if (err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError')) return true\n return /timeout|timed out|Timeout \\d+ms exceeded|deadline/i.test(message)\n}\n\nfunction looksLikeCaptcha(message: string): boolean {\n return /captcha|recaptcha|unusual traffic|google\\.com\\/sorry|blocked/i.test(message)\n}\n\nfunction looksLikeProxyTunnelFailure(message: string): boolean {\n return /ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(message)\n}\n\nfunction looksLikeProxyUnavailable(message: string): boolean {\n return /proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(message)\n}\n\nfunction looksLikeInternalServiceUnavailable(message: string): boolean {\n return /(?:spending|billing|organization|account).{0,80}(?:cap|limit|blocked|disabled)|(?:quota|capacity).{0,40}(?:reached|exceeded)|https?:\\/\\/\\S*dashboard/i.test(message)\n}\n\nexport function classifyHarvestProblem(err: unknown): HarvestProblem {\n const message = errorMessage(err)\n\n if (err instanceof RequestAbortedError) {\n return {\n error_code: 'request_aborted',\n error_type: 'request_aborted',\n message: publicErrorMessage('request_aborted'),\n retryable: true,\n httpStatus: 408,\n terminalStatus: 'cancelled',\n }\n }\n\n if (err instanceof CaptchaError || looksLikeCaptcha(message)) {\n return {\n error_code: 'captcha_exhausted',\n error_type: 'captcha',\n message: publicErrorMessage('captcha_exhausted'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (err instanceof LocationMismatchError) {\n return {\n error_code: 'location_mismatch',\n error_type: 'location_mismatch',\n message: publicErrorMessage('location_mismatch'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyTunnelFailure(message)) {\n return {\n error_code: 'proxy_tunnel_failed',\n error_type: 'proxy_tunnel_failed',\n message: publicErrorMessage('proxy_tunnel_failed'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeProxyUnavailable(message)) {\n return {\n error_code: 'proxy_unavailable',\n error_type: 'proxy_unavailable',\n message: publicErrorMessage('proxy_unavailable'),\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeTimeout(err, message)) {\n return {\n error_code: 'harvest_timeout',\n error_type: 'timeout',\n message: publicErrorMessage('harvest_timeout'),\n retryable: true,\n httpStatus: 504,\n terminalStatus: 'failed',\n }\n }\n\n if (looksLikeInternalServiceUnavailable(message)) {\n return {\n error_code: 'service_unavailable',\n error_type: 'service_unavailable',\n message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,\n retryable: true,\n httpStatus: 503,\n terminalStatus: 'failed',\n }\n }\n\n return {\n error_code: 'extraction_failed',\n error_type: 'extraction',\n message: PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,\n retryable: false,\n httpStatus: 500,\n terminalStatus: 'failed',\n }\n}\n\nexport function serializeHarvestProblem(problem: HarvestProblem): string {\n return JSON.stringify({\n error_code: problem.error_code,\n error_type: problem.error_type,\n message: problem.message,\n retryable: problem.retryable,\n })\n}\n\nexport function harvestProblemResponse(problem: HarvestProblem): {\n error: string\n error_code: HarvestProblemCode\n error_type: string\n retryable: boolean\n} {\n return {\n error: problem.message,\n error_code: problem.error_code,\n error_type: problem.error_type,\n retryable: problem.retryable,\n }\n}\n","import type { HarvestAttemptLogEvent } from '../harvest.js'\nimport { finishHarvestAttempt, startHarvestAttempt } from './db.js'\n\nexport function createHarvestAttemptRecorder(jobId: string, userId: number | bigint) {\n return async (event: HarvestAttemptLogEvent): Promise<void> => {\n if (event.type === 'started') {\n await startHarvestAttempt({\n jobId,\n userId,\n attemptNumber: event.attemptNumber,\n maxAttempts: event.maxAttempts,\n query: event.query,\n location: event.location,\n maxQuestions: event.maxQuestions,\n startedAt: event.startedAt,\n })\n return\n }\n\n await finishHarvestAttempt({\n jobId,\n attemptNumber: event.attemptNumber,\n outcome: event.outcome,\n kernelSessionId: event.kernelSessionId,\n questionCount: event.questionCount,\n durationMs: event.durationMs,\n error: event.error,\n willRetry: event.willRetry,\n kernelDeleteStarted: event.cleanup.kernelDeleteStarted,\n kernelDeleteSucceeded: event.cleanup.kernelDeleteSucceeded,\n kernelDeleteError: event.cleanup.kernelDeleteError,\n browserCloseSucceeded: event.cleanup.browserCloseSucceeded,\n browserCloseError: event.cleanup.browserCloseError,\n debug: event.debug,\n completedAt: event.completedAt,\n })\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IQTT7CAB.js";
|
|
4
4
|
|
|
5
5
|
// src/lib/browser-service-env.ts
|
|
6
6
|
function browserServiceApiKey() {
|
|
@@ -273,4 +273,4 @@ export {
|
|
|
273
273
|
recordKernelSession,
|
|
274
274
|
recordVendorUsage
|
|
275
275
|
};
|
|
276
|
-
//# sourceMappingURL=chunk-
|
|
276
|
+
//# sourceMappingURL=chunk-EHES33KB.js.map
|
|
@@ -1389,11 +1389,15 @@ function deserialize(raw) {
|
|
|
1389
1389
|
}
|
|
1390
1390
|
async function createJob(userId, query, options, callbackUrl) {
|
|
1391
1391
|
const id = randomUUID();
|
|
1392
|
+
await createJobWithId(id, userId, query, options, callbackUrl);
|
|
1393
|
+
return id;
|
|
1394
|
+
}
|
|
1395
|
+
async function createJobWithId(id, userId, query, options, callbackUrl) {
|
|
1396
|
+
if (!/^[a-zA-Z0-9-]{1,100}$/.test(id)) throw new Error("job id is invalid");
|
|
1392
1397
|
await getDb().execute({
|
|
1393
1398
|
sql: "INSERT INTO jobs (id, user_id, query, options, callback_url) VALUES (?, ?, ?, ?, ?)",
|
|
1394
1399
|
args: [id, userId, query, JSON.stringify(options), callbackUrl ?? null]
|
|
1395
1400
|
});
|
|
1396
|
-
return id;
|
|
1397
1401
|
}
|
|
1398
1402
|
async function createRunningJob(userId, query, options) {
|
|
1399
1403
|
const id = randomUUID();
|
|
@@ -1924,7 +1928,13 @@ async function countActiveJobsForUser(userId) {
|
|
|
1924
1928
|
}
|
|
1925
1929
|
async function claimPendingJob() {
|
|
1926
1930
|
const db = getDb();
|
|
1927
|
-
const res = await db.execute(`
|
|
1931
|
+
const res = await db.execute(`
|
|
1932
|
+
SELECT * FROM jobs
|
|
1933
|
+
WHERE status = 'pending'
|
|
1934
|
+
AND COALESCE(json_extract(options, '$.executionOwner'), 'cron') != 'inngest'
|
|
1935
|
+
ORDER BY created_at
|
|
1936
|
+
LIMIT 1
|
|
1937
|
+
`);
|
|
1928
1938
|
const job = res.rows[0] ? rowToRawJob(res.rows[0]) : void 0;
|
|
1929
1939
|
if (!job) return void 0;
|
|
1930
1940
|
const upd = await db.execute({
|
|
@@ -1933,6 +1943,80 @@ async function claimPendingJob() {
|
|
|
1933
1943
|
});
|
|
1934
1944
|
return upd.rowsAffected === 0 ? void 0 : { ...job, status: "running" };
|
|
1935
1945
|
}
|
|
1946
|
+
async function claimInngestPaaJob(id) {
|
|
1947
|
+
const db = getDb();
|
|
1948
|
+
const current = await getJob(id);
|
|
1949
|
+
if (!current || current.options.executionOwner !== "inngest" || current.options.serpOnly === true) return void 0;
|
|
1950
|
+
if (current.status === "running") return void 0;
|
|
1951
|
+
if (current.status === "pending") {
|
|
1952
|
+
const updated = await db.execute({
|
|
1953
|
+
sql: `UPDATE jobs
|
|
1954
|
+
SET status = 'running', started_at = COALESCE(started_at, datetime('now')), error = NULL
|
|
1955
|
+
WHERE id = ? AND status = 'pending'`,
|
|
1956
|
+
args: [id]
|
|
1957
|
+
});
|
|
1958
|
+
if (updated.rowsAffected === 0) return getJob(id);
|
|
1959
|
+
return { ...current, status: "running", started_at: current.started_at ?? (/* @__PURE__ */ new Date()).toISOString() };
|
|
1960
|
+
}
|
|
1961
|
+
return current;
|
|
1962
|
+
}
|
|
1963
|
+
async function checkpointJob(id, result) {
|
|
1964
|
+
const updated = await getDb().execute({
|
|
1965
|
+
sql: `UPDATE jobs SET result = ? WHERE id = ? AND status = 'running'`,
|
|
1966
|
+
args: [JSON.stringify(result), id]
|
|
1967
|
+
});
|
|
1968
|
+
return updated.rowsAffected === 1;
|
|
1969
|
+
}
|
|
1970
|
+
async function listPendingInngestPaaJobs(limit = 25) {
|
|
1971
|
+
const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)));
|
|
1972
|
+
const rows = await getDb().execute({
|
|
1973
|
+
sql: `SELECT * FROM jobs
|
|
1974
|
+
WHERE status = 'pending'
|
|
1975
|
+
AND json_extract(options, '$.executionOwner') = 'inngest'
|
|
1976
|
+
AND COALESCE(json_extract(options, '$.serpOnly'), 0) = 0
|
|
1977
|
+
ORDER BY created_at
|
|
1978
|
+
LIMIT ?`,
|
|
1979
|
+
args: [safeLimit]
|
|
1980
|
+
});
|
|
1981
|
+
return rows.rows.map((row) => deserialize(rowToRawJob(row)));
|
|
1982
|
+
}
|
|
1983
|
+
async function listRecentTerminalInngestPaaJobs(limit = 25) {
|
|
1984
|
+
const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)));
|
|
1985
|
+
const rows = await getDb().execute({
|
|
1986
|
+
sql: `SELECT * FROM jobs
|
|
1987
|
+
WHERE status IN ('done', 'failed', 'cancelled')
|
|
1988
|
+
AND json_extract(options, '$.executionOwner') = 'inngest'
|
|
1989
|
+
AND json_extract(options, '$.billingDebitKey') IS NOT NULL
|
|
1990
|
+
AND json_extract(options, '$.billingSettledAt') IS NULL
|
|
1991
|
+
ORDER BY completed_at DESC
|
|
1992
|
+
LIMIT ?`,
|
|
1993
|
+
args: [safeLimit]
|
|
1994
|
+
});
|
|
1995
|
+
return rows.rows.map((row) => deserialize(rowToRawJob(row)));
|
|
1996
|
+
}
|
|
1997
|
+
async function listStaleRunningInngestPaaJobs(limit = 25) {
|
|
1998
|
+
const safeLimit = Math.max(1, Math.min(100, Math.floor(limit)));
|
|
1999
|
+
const rows = await getDb().execute({
|
|
2000
|
+
sql: `SELECT * FROM jobs
|
|
2001
|
+
WHERE status = 'running'
|
|
2002
|
+
AND started_at <= datetime('now', '-10 minutes')
|
|
2003
|
+
AND json_extract(options, '$.executionOwner') = 'inngest'
|
|
2004
|
+
ORDER BY started_at
|
|
2005
|
+
LIMIT ?`,
|
|
2006
|
+
args: [safeLimit]
|
|
2007
|
+
});
|
|
2008
|
+
return rows.rows.map((row) => deserialize(rowToRawJob(row)));
|
|
2009
|
+
}
|
|
2010
|
+
async function markJobBillingSettled(id, billedMc, settledAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2011
|
+
if (!Number.isSafeInteger(billedMc) || billedMc < 0) throw new Error("job billed amount must be a non-negative integer");
|
|
2012
|
+
const updated = await getDb().execute({
|
|
2013
|
+
sql: `UPDATE jobs
|
|
2014
|
+
SET options = json_set(options, '$.billingSettledAt', ?, '$.billedMc', ?)
|
|
2015
|
+
WHERE id = ?`,
|
|
2016
|
+
args: [settledAt, billedMc, id]
|
|
2017
|
+
});
|
|
2018
|
+
return updated.rowsAffected === 1;
|
|
2019
|
+
}
|
|
1936
2020
|
async function completeJob(id, result) {
|
|
1937
2021
|
await getDb().execute({ sql: `UPDATE jobs SET status = 'done', result = ?, completed_at = datetime('now') WHERE id = ?`, args: [JSON.stringify(result), id] });
|
|
1938
2022
|
}
|
|
@@ -2965,6 +3049,7 @@ export {
|
|
|
2965
3049
|
listUsers,
|
|
2966
3050
|
deactivateUser,
|
|
2967
3051
|
createJob,
|
|
3052
|
+
createJobWithId,
|
|
2968
3053
|
createRunningJob,
|
|
2969
3054
|
startHarvestAttempt,
|
|
2970
3055
|
finishHarvestAttempt,
|
|
@@ -2999,6 +3084,12 @@ export {
|
|
|
2999
3084
|
countActiveUsers,
|
|
3000
3085
|
countActiveJobsForUser,
|
|
3001
3086
|
claimPendingJob,
|
|
3087
|
+
claimInngestPaaJob,
|
|
3088
|
+
checkpointJob,
|
|
3089
|
+
listPendingInngestPaaJobs,
|
|
3090
|
+
listRecentTerminalInngestPaaJobs,
|
|
3091
|
+
listStaleRunningInngestPaaJobs,
|
|
3092
|
+
markJobBillingSettled,
|
|
3002
3093
|
completeJob,
|
|
3003
3094
|
failJob,
|
|
3004
3095
|
cancelJob,
|
|
@@ -3047,4 +3138,4 @@ export {
|
|
|
3047
3138
|
insertInboxMessage,
|
|
3048
3139
|
listActiveUserIds
|
|
3049
3140
|
};
|
|
3050
|
-
//# sourceMappingURL=chunk-
|
|
3141
|
+
//# sourceMappingURL=chunk-IQTT7CAB.js.map
|