mcp-scraper 0.40.2 → 0.41.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 (62) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/api-server.cjs +2264 -337
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +180 -92
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +6 -5
  14. package/dist/bin/mcp-stdio-server.js.map +1 -1
  15. package/dist/bin/paa-harvest.cjs +31 -9
  16. package/dist/bin/paa-harvest.cjs.map +1 -1
  17. package/dist/bin/paa-harvest.js +4 -4
  18. package/dist/{chunk-XORPNO3Z.js → chunk-27DUCRAZ.js} +2 -2
  19. package/dist/{chunk-44HZLHDV.js → chunk-5UN33CGU.js} +38 -1
  20. package/dist/chunk-5UN33CGU.js.map +1 -0
  21. package/dist/{chunk-X2LKCX6H.js → chunk-6J57U6HA.js} +3 -3
  22. package/dist/chunk-BBI7RGOT.js +99 -0
  23. package/dist/chunk-BBI7RGOT.js.map +1 -0
  24. package/dist/{chunk-NVXNEOUQ.js → chunk-CS4HE6QY.js} +47 -11
  25. package/dist/chunk-CS4HE6QY.js.map +1 -0
  26. package/dist/{chunk-3PIWJS6Y.js → chunk-EHES33KB.js} +2 -2
  27. package/dist/{chunk-GUVKHCKE.js → chunk-IQTT7CAB.js} +94 -3
  28. package/dist/chunk-IQTT7CAB.js.map +1 -0
  29. package/dist/{chunk-7XBBFBYY.js → chunk-JVIW4GK2.js} +44 -12
  30. package/dist/chunk-JVIW4GK2.js.map +1 -0
  31. package/dist/chunk-OOB35KFT.js +7 -0
  32. package/dist/chunk-OOB35KFT.js.map +1 -0
  33. package/dist/{chunk-LP6E462I.js → chunk-SIE5LZ2V.js} +74 -106
  34. package/dist/chunk-SIE5LZ2V.js.map +1 -0
  35. package/dist/{db-W3CP562I.js → db-LVVU6NOK.js} +16 -2
  36. package/dist/{extract-bundle-K4PG3RZJ.js → extract-bundle-SKXEDG4Z.js} +4 -4
  37. package/dist/index.cjs +31 -9
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.js +4 -4
  40. package/dist/{location-data-repository-RLQX6SNM.js → location-data-repository-NBTCOA7B.js} +3 -3
  41. package/dist/{server-KM3CFWCF.js → server-2JJPCZH4.js} +1812 -162
  42. package/dist/server-2JJPCZH4.js.map +1 -0
  43. package/dist/{site-extract-repository-2SMMFKKL.js → site-extract-repository-6GX72U4N.js} +4 -4
  44. package/dist/{worker-FXAGFYOE.js → worker-5RWLFSAJ.js} +27 -28
  45. package/dist/worker-5RWLFSAJ.js.map +1 -0
  46. package/package.json +1 -1
  47. package/dist/chunk-44HZLHDV.js.map +0 -1
  48. package/dist/chunk-7XBBFBYY.js.map +0 -1
  49. package/dist/chunk-GUVKHCKE.js.map +0 -1
  50. package/dist/chunk-LP6E462I.js.map +0 -1
  51. package/dist/chunk-NVXNEOUQ.js.map +0 -1
  52. package/dist/chunk-ZUJLSICT.js +0 -7
  53. package/dist/chunk-ZUJLSICT.js.map +0 -1
  54. package/dist/server-KM3CFWCF.js.map +0 -1
  55. package/dist/worker-FXAGFYOE.js.map +0 -1
  56. /package/dist/{chunk-XORPNO3Z.js.map → chunk-27DUCRAZ.js.map} +0 -0
  57. /package/dist/{chunk-X2LKCX6H.js.map → chunk-6J57U6HA.js.map} +0 -0
  58. /package/dist/{chunk-3PIWJS6Y.js.map → chunk-EHES33KB.js.map} +0 -0
  59. /package/dist/{db-W3CP562I.js.map → db-LVVU6NOK.js.map} +0 -0
  60. /package/dist/{extract-bundle-K4PG3RZJ.js.map → extract-bundle-SKXEDG4Z.js.map} +0 -0
  61. /package/dist/{location-data-repository-RLQX6SNM.js.map → location-data-repository-NBTCOA7B.js.map} +0 -0
  62. /package/dist/{site-extract-repository-2SMMFKKL.js.map → site-extract-repository-6GX72U4N.js.map} +0 -0
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  harvest
4
- } from "../chunk-7XBBFBYY.js";
4
+ } from "../chunk-JVIW4GK2.js";
5
5
  import {
6
6
  browserServiceApiKey
7
- } from "../chunk-3PIWJS6Y.js";
7
+ } from "../chunk-EHES33KB.js";
8
8
  import "../chunk-K443GQY5.js";
9
9
  import "../chunk-CB5C3BPB.js";
10
- import "../chunk-44HZLHDV.js";
11
- import "../chunk-GUVKHCKE.js";
10
+ import "../chunk-5UN33CGU.js";
11
+ import "../chunk-IQTT7CAB.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import { Command } from "commander";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-G3P3ZDB4.js";
4
4
  import {
5
5
  getDb
6
- } from "./chunk-GUVKHCKE.js";
6
+ } from "./chunk-IQTT7CAB.js";
7
7
 
8
8
  // src/api/location-data-repository.ts
9
9
  import { createHash, randomUUID } from "crypto";
@@ -681,4 +681,4 @@ export {
681
681
  importHostedCensusPlacesCsv,
682
682
  queryHostedLocationMarkets
683
683
  };
684
- //# sourceMappingURL=chunk-XORPNO3Z.js.map
684
+ //# sourceMappingURL=chunk-27DUCRAZ.js.map
@@ -1,5 +1,39 @@
1
1
  // src/errors.ts
2
2
  var RECAPTCHA_INSTRUCTIONS = "Google returned a CAPTCHA. Run with --headless=false to re-warm the browser profile, then retry.";
3
+ var PUBLIC_SERVICE_UNAVAILABLE_MESSAGE = "This operation is temporarily unavailable. Please retry later.";
4
+ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
5
+ "request_aborted",
6
+ "captcha_exhausted",
7
+ "captcha_or_blocked",
8
+ "location_mismatch",
9
+ "proxy_tunnel_failed",
10
+ "proxy_unavailable",
11
+ "harvest_timeout",
12
+ "mcp_request_timeout",
13
+ "service_unavailable"
14
+ ]);
15
+ function normalizePublicErrorCode(code) {
16
+ return typeof code === "string" && PUBLIC_ERROR_CODES.has(code) ? code : "service_unavailable";
17
+ }
18
+ function publicErrorMessage(code) {
19
+ switch (normalizePublicErrorCode(code)) {
20
+ case "request_aborted":
21
+ return "The request was cancelled before it completed.";
22
+ case "captcha_exhausted":
23
+ case "captcha_or_blocked":
24
+ return "The target site returned a verification challenge. Please retry later.";
25
+ case "location_mismatch":
26
+ return "The target site returned results for a different location. Please retry.";
27
+ case "proxy_tunnel_failed":
28
+ case "proxy_unavailable":
29
+ return "The web request could not establish a working connection. Please retry later.";
30
+ case "harvest_timeout":
31
+ case "mcp_request_timeout":
32
+ return "The request timed out before it completed. Please retry with a smaller request.";
33
+ default:
34
+ return PUBLIC_SERVICE_UNAVAILABLE_MESSAGE;
35
+ }
36
+ }
3
37
  function sanitizeVendorName(message) {
4
38
  return message.replace(/kernel\.sh\s+sessions?/gi, "sessions").replace(/kernel\.sh\s+session/gi, "this session").replace(/kernel\.sh/gi, "the service").replace(/kernel\s+sessions?/gi, "sessions").replace(/kernel\s+session/gi, "this session").replace(/\bkernel\b/gi, "the service").replace(/ +/g, " ").trim();
5
39
  }
@@ -42,6 +76,9 @@ var LocationMismatchError = class extends Error {
42
76
 
43
77
  export {
44
78
  RECAPTCHA_INSTRUCTIONS,
79
+ PUBLIC_SERVICE_UNAVAILABLE_MESSAGE,
80
+ normalizePublicErrorCode,
81
+ publicErrorMessage,
45
82
  sanitizeVendorName,
46
83
  CaptchaError,
47
84
  ExtractionError,
@@ -49,4 +86,4 @@ export {
49
86
  RequestAbortedError,
50
87
  LocationMismatchError
51
88
  };
52
- //# sourceMappingURL=chunk-44HZLHDV.js.map
89
+ //# sourceMappingURL=chunk-5UN33CGU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts"],"sourcesContent":["export const RECAPTCHA_INSTRUCTIONS = 'Google returned a CAPTCHA. Run with --headless=false to re-warm the browser profile, then retry.'\nexport const PUBLIC_SERVICE_UNAVAILABLE_MESSAGE = 'This operation is temporarily unavailable. Please retry later.'\n\nconst PUBLIC_ERROR_CODES = new Set([\n 'request_aborted',\n 'captcha_exhausted',\n 'captcha_or_blocked',\n 'location_mismatch',\n 'proxy_tunnel_failed',\n 'proxy_unavailable',\n 'harvest_timeout',\n 'mcp_request_timeout',\n 'service_unavailable',\n])\n\nexport function normalizePublicErrorCode(code: unknown): string {\n return typeof code === 'string' && PUBLIC_ERROR_CODES.has(code)\n ? code\n : 'service_unavailable'\n}\n\nexport function publicErrorMessage(code: unknown): string {\n switch (normalizePublicErrorCode(code)) {\n case 'request_aborted':\n return 'The request was cancelled before it completed.'\n case 'captcha_exhausted':\n case 'captcha_or_blocked':\n return 'The target site returned a verification challenge. Please retry later.'\n case 'location_mismatch':\n return 'The target site returned results for a different location. Please retry.'\n case 'proxy_tunnel_failed':\n case 'proxy_unavailable':\n return 'The web request could not establish a working connection. Please retry later.'\n case 'harvest_timeout':\n case 'mcp_request_timeout':\n return 'The request timed out before it completed. Please retry with a smaller request.'\n default:\n return PUBLIC_SERVICE_UNAVAILABLE_MESSAGE\n }\n}\n\nexport function sanitizeVendorName(message: string): string {\n return message\n .replace(/kernel\\.sh\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\.sh\\s+session/gi, 'this session')\n .replace(/kernel\\.sh/gi, 'the service')\n .replace(/kernel\\s+sessions?/gi, 'sessions')\n .replace(/kernel\\s+session/gi, 'this session')\n .replace(/\\bkernel\\b/gi, 'the service')\n .replace(/ +/g, ' ')\n .trim()\n}\n\nexport class CaptchaError extends Error {\n readonly name = 'CaptchaError'\n constructor(public readonly instructions: string) {\n super(`CAPTCHA detected. ${instructions}`)\n }\n}\n\nexport class ExtractionError extends Error {\n readonly name = 'ExtractionError'\n constructor(message: string, public readonly cause?: unknown) {\n super(message)\n }\n}\n\n/**\n * Google Maps sometimes finishes navigation without rendering either result\n * cards or its explicit no-results message. Treating that page as a genuine\n * empty search loses data, so callers should retry it with a fresh browser (and\n * a fresh proxy when location rotation is enabled).\n */\nexport class MapsSearchIncompletePageError extends Error {\n readonly name = 'MapsSearchIncompletePageError'\n\n constructor(public readonly pageState: string) {\n super(`Google Maps search did not reach a complete result state (${pageState})`)\n }\n}\n\nexport class RequestAbortedError extends Error {\n readonly name = 'RequestAbortedError'\n constructor(message = 'Request aborted before harvest completed') {\n super(message)\n }\n}\n\nexport class LocationMismatchError extends Error {\n readonly name = 'LocationMismatchError'\n constructor(message = 'Google returned results for a different location than requested') {\n super(message)\n }\n}\n"],"mappings":";AAAO,IAAM,yBAAyB;AAC/B,IAAM,qCAAqC;AAElD,IAAM,qBAAqB,oBAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,yBAAyB,MAAuB;AAC9D,SAAO,OAAO,SAAS,YAAY,mBAAmB,IAAI,IAAI,IAC1D,OACA;AACN;AAEO,SAAS,mBAAmB,MAAuB;AACxD,UAAQ,yBAAyB,IAAI,GAAG;AAAA,IACtC,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,mBAAmB,SAAyB;AAC1D,SAAO,QACJ,QAAQ,4BAA4B,UAAU,EAC9C,QAAQ,0BAA0B,cAAc,EAChD,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,wBAAwB,UAAU,EAC1C,QAAQ,sBAAsB,cAAc,EAC5C,QAAQ,gBAAgB,aAAa,EACrC,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACV;AAEO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAEtC,YAA4B,cAAsB;AAChD,UAAM,qBAAqB,YAAY,EAAE;AADf;AAAA,EAE5B;AAAA,EAF4B;AAAA,EADnB,OAAO;AAIlB;AAEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAEzC,YAAY,SAAiC,OAAiB;AAC5D,UAAM,OAAO;AAD8B;AAAA,EAE7C;AAAA,EAF6C;AAAA,EADpC,OAAO;AAIlB;AAQO,IAAM,gCAAN,cAA4C,MAAM;AAAA,EAGvD,YAA4B,WAAmB;AAC7C,UAAM,6DAA6D,SAAS,GAAG;AADrD;AAAA,EAE5B;AAAA,EAF4B;AAAA,EAFnB,OAAO;AAKlB;AAEO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EACpC,OAAO;AAAA,EAChB,YAAY,UAAU,4CAA4C;AAChE,UAAM,OAAO;AAAA,EACf;AACF;AAEO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EACtC,OAAO;AAAA,EAChB,YAAY,UAAU,mEAAmE;AACvF,UAAM,OAAO;AAAA,EACf;AACF;","names":[]}
@@ -3,12 +3,12 @@ import {
3
3
  } from "./chunk-SBLGBZZB.js";
4
4
  import {
5
5
  sanitizeVendorName
6
- } from "./chunk-44HZLHDV.js";
6
+ } from "./chunk-5UN33CGU.js";
7
7
  import {
8
8
  CREDIT_LOT_TTL,
9
9
  getDb,
10
10
  settleDebitMcIdempotent
11
- } from "./chunk-GUVKHCKE.js";
11
+ } from "./chunk-IQTT7CAB.js";
12
12
 
13
13
  // src/api/site-extract-repository.ts
14
14
  function extractJobLimitInfo(job) {
@@ -496,4 +496,4 @@ export {
496
496
  failUnfundedExtractJob,
497
497
  settleExtractJob
498
498
  };
499
- //# sourceMappingURL=chunk-X2LKCX6H.js.map
499
+ //# sourceMappingURL=chunk-6J57U6HA.js.map
@@ -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
- RequestAbortedError
5
- } from "./chunk-44HZLHDV.js";
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-GUVKHCKE.js";
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-NVXNEOUQ.js.map
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-GUVKHCKE.js";
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-3PIWJS6Y.js.map
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(`SELECT * FROM jobs WHERE status = 'pending' ORDER BY created_at LIMIT 1`);
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-GUVKHCKE.js.map
3141
+ //# sourceMappingURL=chunk-IQTT7CAB.js.map