mcp-scraper 0.43.3 → 0.43.5
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 +170 -76
- 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 +1 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +4 -4
- package/dist/bin/paa-harvest.cjs +42 -22
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +3 -3
- package/dist/{chunk-FCFQ634B.js → chunk-5X7CJEK3.js} +25 -8
- package/dist/chunk-5X7CJEK3.js.map +1 -0
- package/dist/chunk-H4RPUFRR.js +7 -0
- package/dist/chunk-H4RPUFRR.js.map +1 -0
- package/dist/{chunk-ZBQ6EXZW.js → chunk-OVD4E4AP.js} +2 -2
- package/dist/{chunk-H7LQ6PTI.js → chunk-RJMOOII7.js} +50 -24
- package/dist/chunk-RJMOOII7.js.map +1 -0
- package/dist/{chunk-CHC3RCFA.js → chunk-RSGUS5V6.js} +3 -3
- package/dist/{chunk-DAS7L4SF.js → chunk-T5AFM4G5.js} +4 -2
- package/dist/chunk-T5AFM4G5.js.map +1 -0
- package/dist/{chunk-QOWBJ4YY.js → chunk-WT2UHDSE.js} +2 -2
- package/dist/{chunk-GUI33Q27.js → chunk-WWIJ2NID.js} +2 -2
- package/dist/{db-C5ELI55I.js → db-4ABEYNDM.js} +4 -2
- package/dist/{extract-bundle-IOM45SFL.js → extract-bundle-ITPFWRNI.js} +3 -3
- package/dist/index.cjs +42 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/{location-data-repository-SHEOIBTL.js → location-data-repository-HDNCU4Z4.js} +3 -3
- package/dist/{server-7CLMCX42.js → server-BTDXZA5U.js} +101 -57
- package/dist/server-BTDXZA5U.js.map +1 -0
- package/dist/{site-extract-repository-XNI4ZK2M.js → site-extract-repository-CKCJDP7N.js} +3 -3
- package/dist/{worker-2LIOH744.js → worker-73AYU4IK.js} +5 -5
- package/package.json +3 -1
- package/dist/chunk-4LQVW2EK.js +0 -7
- package/dist/chunk-4LQVW2EK.js.map +0 -1
- package/dist/chunk-DAS7L4SF.js.map +0 -1
- package/dist/chunk-FCFQ634B.js.map +0 -1
- package/dist/chunk-H7LQ6PTI.js.map +0 -1
- package/dist/server-7CLMCX42.js.map +0 -1
- /package/dist/{chunk-ZBQ6EXZW.js.map → chunk-OVD4E4AP.js.map} +0 -0
- /package/dist/{chunk-CHC3RCFA.js.map → chunk-RSGUS5V6.js.map} +0 -0
- /package/dist/{chunk-QOWBJ4YY.js.map → chunk-WT2UHDSE.js.map} +0 -0
- /package/dist/{chunk-GUI33Q27.js.map → chunk-WWIJ2NID.js.map} +0 -0
- /package/dist/{db-C5ELI55I.js.map → db-4ABEYNDM.js.map} +0 -0
- /package/dist/{extract-bundle-IOM45SFL.js.map → extract-bundle-ITPFWRNI.js.map} +0 -0
- /package/dist/{location-data-repository-SHEOIBTL.js.map → location-data-repository-HDNCU4Z4.js.map} +0 -0
- /package/dist/{site-extract-repository-XNI4ZK2M.js.map → site-extract-repository-CKCJDP7N.js.map} +0 -0
- /package/dist/{worker-2LIOH744.js.map → worker-73AYU4IK.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -3671,6 +3671,7 @@ __export(db_exports, {
|
|
|
3671
3671
|
CORE_SCHEMA_VERSION: () => CORE_SCHEMA_VERSION,
|
|
3672
3672
|
CREDIT_LOTS_EPOCH: () => CREDIT_LOTS_EPOCH,
|
|
3673
3673
|
CREDIT_LOT_TTL: () => CREDIT_LOT_TTL,
|
|
3674
|
+
SITE_EXTRACT_COUNTER_REPAIR_VERSION: () => SITE_EXTRACT_COUNTER_REPAIR_VERSION,
|
|
3674
3675
|
SiteAuditJobRowSchema: () => SiteAuditJobRowSchema,
|
|
3675
3676
|
SiteAuditPhaseLogRowSchema: () => SiteAuditPhaseLogRowSchema,
|
|
3676
3677
|
advanceWorkflowRunStep: () => advanceWorkflowRunStep,
|
|
@@ -3808,7 +3809,7 @@ async function hasCurrentCoreSchema(db) {
|
|
|
3808
3809
|
const result = await db.execute(`SELECT ${checks.join(" AND ")} AS ready`);
|
|
3809
3810
|
return Number(result.rows[0]?.ready ?? 0) === 1;
|
|
3810
3811
|
}
|
|
3811
|
-
async function
|
|
3812
|
+
async function runSiteExtractCounterRepair(db) {
|
|
3812
3813
|
await db.execute(`
|
|
3813
3814
|
UPDATE site_extract_jobs SET
|
|
3814
3815
|
attempted_urls = done_urls,
|
|
@@ -3848,6 +3849,18 @@ async function runIdempotentCoreDataBackfills(db) {
|
|
|
3848
3849
|
AND failed_urls = 0
|
|
3849
3850
|
`);
|
|
3850
3851
|
}
|
|
3852
|
+
async function runPendingDataRepairs(db) {
|
|
3853
|
+
const applied = await db.execute({
|
|
3854
|
+
sql: "SELECT 1 AS applied FROM schema_migrations WHERE version = ? LIMIT 1",
|
|
3855
|
+
args: [SITE_EXTRACT_COUNTER_REPAIR_VERSION]
|
|
3856
|
+
});
|
|
3857
|
+
if (applied.rows.length > 0) return;
|
|
3858
|
+
await runSiteExtractCounterRepair(db);
|
|
3859
|
+
await db.execute({
|
|
3860
|
+
sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
|
|
3861
|
+
args: [SITE_EXTRACT_COUNTER_REPAIR_VERSION]
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3851
3864
|
function getDb() {
|
|
3852
3865
|
if (!_db) _db = (0, import_http.createClient)({ url: DB_URL, authToken: DB_TOKEN });
|
|
3853
3866
|
return _db;
|
|
@@ -3855,7 +3868,7 @@ function getDb() {
|
|
|
3855
3868
|
function setDbForTesting(client2) {
|
|
3856
3869
|
_db = client2;
|
|
3857
3870
|
}
|
|
3858
|
-
async function migrate() {
|
|
3871
|
+
async function migrate(options = {}) {
|
|
3859
3872
|
const db = getDb();
|
|
3860
3873
|
let applied;
|
|
3861
3874
|
try {
|
|
@@ -3874,12 +3887,15 @@ async function migrate() {
|
|
|
3874
3887
|
`);
|
|
3875
3888
|
applied = { rows: [] };
|
|
3876
3889
|
}
|
|
3877
|
-
if (applied.rows.length > 0
|
|
3878
|
-
|
|
3879
|
-
|
|
3890
|
+
if (applied.rows.length > 0) {
|
|
3891
|
+
if (!options.verifyCurrentSchema) return;
|
|
3892
|
+
if (await hasCurrentCoreSchema(db)) {
|
|
3893
|
+
if (options.runDataRepairs) await runPendingDataRepairs(db);
|
|
3894
|
+
return;
|
|
3895
|
+
}
|
|
3880
3896
|
}
|
|
3881
3897
|
if (await hasCurrentCoreSchema(db)) {
|
|
3882
|
-
await
|
|
3898
|
+
await runPendingDataRepairs(db);
|
|
3883
3899
|
await db.execute({
|
|
3884
3900
|
sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
|
|
3885
3901
|
args: [CORE_SCHEMA_VERSION]
|
|
@@ -4692,7 +4708,7 @@ async function migrate() {
|
|
|
4692
4708
|
if (!await hasCurrentCoreSchema(db)) {
|
|
4693
4709
|
throw new Error("core schema migration finished without all required tables, indexes, and columns");
|
|
4694
4710
|
}
|
|
4695
|
-
await
|
|
4711
|
+
await runPendingDataRepairs(db);
|
|
4696
4712
|
await db.execute({
|
|
4697
4713
|
sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
|
|
4698
4714
|
args: [CORE_SCHEMA_VERSION]
|
|
@@ -6666,7 +6682,7 @@ async function listActiveUserIds() {
|
|
|
6666
6682
|
const res = await getDb().execute("SELECT id FROM users WHERE active = 1");
|
|
6667
6683
|
return res.rows.map((r) => Number(r.id));
|
|
6668
6684
|
}
|
|
6669
|
-
var import_http, import_node_crypto, import_zod, DB_URL, DB_TOKEN, _db, _rateLimitSchemaReady, CORE_SCHEMA_VERSION, CORE_SCHEMA_TABLES, CORE_SCHEMA_INDEXES, CORE_SCHEMA_TRIGGERS, CORE_SCHEMA_COLUMNS, REQUEST_EVENT_RESULT_MAX_BYTES, CREDIT_LOT_TTL, SiteAuditJobRowSchema, SiteAuditPhaseLogRowSchema;
|
|
6685
|
+
var import_http, import_node_crypto, import_zod, DB_URL, DB_TOKEN, _db, _rateLimitSchemaReady, CORE_SCHEMA_VERSION, SITE_EXTRACT_COUNTER_REPAIR_VERSION, CORE_SCHEMA_TABLES, CORE_SCHEMA_INDEXES, CORE_SCHEMA_TRIGGERS, CORE_SCHEMA_COLUMNS, REQUEST_EVENT_RESULT_MAX_BYTES, CREDIT_LOT_TTL, SiteAuditJobRowSchema, SiteAuditPhaseLogRowSchema;
|
|
6670
6686
|
var init_db = __esm({
|
|
6671
6687
|
"src/api/db.ts"() {
|
|
6672
6688
|
"use strict";
|
|
@@ -6680,6 +6696,7 @@ var init_db = __esm({
|
|
|
6680
6696
|
_db = null;
|
|
6681
6697
|
_rateLimitSchemaReady = false;
|
|
6682
6698
|
CORE_SCHEMA_VERSION = "2026-07-30.1";
|
|
6699
|
+
SITE_EXTRACT_COUNTER_REPAIR_VERSION = "data-2026-07-28-site-extract-counters.1";
|
|
6683
6700
|
CORE_SCHEMA_TABLES = [
|
|
6684
6701
|
"admin_credit_adjustment_events",
|
|
6685
6702
|
"admin_credit_adjustments",
|
|
@@ -6842,11 +6859,12 @@ function runWithCostContext(ctx, fn) {
|
|
|
6842
6859
|
function currentCostContext() {
|
|
6843
6860
|
return storage.getStore();
|
|
6844
6861
|
}
|
|
6845
|
-
var import_node_async_hooks, storage;
|
|
6862
|
+
var import_node_async_hooks, HEADLESS_HARVEST_TEST_ENV, storage;
|
|
6846
6863
|
var init_cost_context = __esm({
|
|
6847
6864
|
"src/api/cost-context.ts"() {
|
|
6848
6865
|
"use strict";
|
|
6849
6866
|
import_node_async_hooks = require("async_hooks");
|
|
6867
|
+
HEADLESS_HARVEST_TEST_ENV = "MCP_SCRAPER_ALLOW_HEADLESS_HARVEST_TEST";
|
|
6850
6868
|
storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
6851
6869
|
}
|
|
6852
6870
|
});
|
|
@@ -7197,6 +7215,42 @@ var init_bounded_response = __esm({
|
|
|
7197
7215
|
}
|
|
7198
7216
|
});
|
|
7199
7217
|
|
|
7218
|
+
// src/lib/browser-user-agent.ts
|
|
7219
|
+
async function detectPageUserAgent(page) {
|
|
7220
|
+
try {
|
|
7221
|
+
const value = await page.evaluate(() => navigator.userAgent);
|
|
7222
|
+
return typeof value === "string" && RUNTIME_BROWSER_UA.test(value) ? value : null;
|
|
7223
|
+
} catch {
|
|
7224
|
+
return null;
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
async function detectRuntimeUserAgent(browser) {
|
|
7228
|
+
let probeContext = null;
|
|
7229
|
+
try {
|
|
7230
|
+
let page = browser.contexts().flatMap((context) => context.pages())[0];
|
|
7231
|
+
if (!page) {
|
|
7232
|
+
probeContext = await browser.newContext();
|
|
7233
|
+
page = probeContext.pages()[0] ?? await probeContext.newPage();
|
|
7234
|
+
}
|
|
7235
|
+
return await detectPageUserAgent(page);
|
|
7236
|
+
} catch {
|
|
7237
|
+
return null;
|
|
7238
|
+
} finally {
|
|
7239
|
+
if (probeContext) await probeContext.close().catch(() => {
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
7242
|
+
}
|
|
7243
|
+
function withRuntimeUserAgent(options, runtimeUserAgent) {
|
|
7244
|
+
return runtimeUserAgent ? { ...options, userAgent: runtimeUserAgent } : options;
|
|
7245
|
+
}
|
|
7246
|
+
var RUNTIME_BROWSER_UA;
|
|
7247
|
+
var init_browser_user_agent = __esm({
|
|
7248
|
+
"src/lib/browser-user-agent.ts"() {
|
|
7249
|
+
"use strict";
|
|
7250
|
+
RUNTIME_BROWSER_UA = /\b(?:HeadlessChrome|Chrome|Chromium|Firefox)\/\d+|\bVersion\/\d+.*\bSafari\//;
|
|
7251
|
+
}
|
|
7252
|
+
});
|
|
7253
|
+
|
|
7200
7254
|
// src/api/kernel-fetch.ts
|
|
7201
7255
|
async function fetchWithKernel(url) {
|
|
7202
7256
|
const checked = await validatePublicHttpUrl(url, { field: "browser URL" });
|
|
@@ -7210,10 +7264,10 @@ async function fetchWithKernel(url) {
|
|
|
7210
7264
|
const kb = await client2.browsers.create({ stealth: true, ...useHeadless ? { headless: true } : {}, timeout_seconds: 60 });
|
|
7211
7265
|
const browser = await import_playwright.chromium.connectOverCDP(kb.cdp_ws_url);
|
|
7212
7266
|
try {
|
|
7213
|
-
const
|
|
7214
|
-
|
|
7267
|
+
const runtimeUserAgent = await detectRuntimeUserAgent(browser);
|
|
7268
|
+
const context = await browser.newContext(withRuntimeUserAgent({
|
|
7215
7269
|
serviceWorkers: "block"
|
|
7216
|
-
});
|
|
7270
|
+
}, runtimeUserAgent));
|
|
7217
7271
|
const page = context.pages()[0] ?? await context.newPage();
|
|
7218
7272
|
const guard = await installBrowserRequestGuard(page, auditedUrl);
|
|
7219
7273
|
await page.goto(auditedUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
@@ -7244,6 +7298,7 @@ var init_kernel_fetch = __esm({
|
|
|
7244
7298
|
init_browser_request_guard();
|
|
7245
7299
|
init_url_utils();
|
|
7246
7300
|
init_bounded_response();
|
|
7301
|
+
init_browser_user_agent();
|
|
7247
7302
|
}
|
|
7248
7303
|
});
|
|
7249
7304
|
|
|
@@ -7827,14 +7882,13 @@ async function scrollAndStitch(page, device = "desktop") {
|
|
|
7827
7882
|
}
|
|
7828
7883
|
async function captureLocal(url, device, wantScreenshot, wantBranding) {
|
|
7829
7884
|
const vp = device === "mobile" ? MOBILE_VIEWPORT : DESKTOP_VIEWPORT;
|
|
7830
|
-
const ua = device === "mobile" ? MOBILE_UA : DESKTOP_UA;
|
|
7831
7885
|
const browser = await import_playwright2.chromium.launch({ headless: true });
|
|
7832
|
-
const
|
|
7886
|
+
const runtimeUserAgent = await detectRuntimeUserAgent(browser);
|
|
7887
|
+
const context = await browser.newContext(withRuntimeUserAgent({
|
|
7833
7888
|
viewport: vp,
|
|
7834
7889
|
locale: "en-US",
|
|
7835
|
-
userAgent: ua,
|
|
7836
7890
|
isMobile: device === "mobile"
|
|
7837
|
-
});
|
|
7891
|
+
}, runtimeUserAgent));
|
|
7838
7892
|
const page = await context.newPage();
|
|
7839
7893
|
try {
|
|
7840
7894
|
await page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
|
|
@@ -7853,12 +7907,11 @@ async function captureKernel(url, kernelApiKey, device, wantScreenshot, wantBran
|
|
|
7853
7907
|
const session = await kernel.browsers.create({ stealth: true, timeout_seconds: 240 });
|
|
7854
7908
|
const browser = await import_playwright2.chromium.connectOverCDP(session.cdp_ws_url);
|
|
7855
7909
|
const vp = device === "mobile" ? MOBILE_VIEWPORT : DESKTOP_VIEWPORT;
|
|
7856
|
-
const
|
|
7857
|
-
const context = browser.contexts()[0] ?? await browser.newContext({
|
|
7910
|
+
const runtimeUserAgent = await detectRuntimeUserAgent(browser);
|
|
7911
|
+
const context = browser.contexts()[0] ?? await browser.newContext(withRuntimeUserAgent({
|
|
7858
7912
|
viewport: vp,
|
|
7859
|
-
userAgent: ua,
|
|
7860
7913
|
isMobile: device === "mobile"
|
|
7861
|
-
});
|
|
7914
|
+
}, runtimeUserAgent));
|
|
7862
7915
|
const page = context.pages()[0] ?? await context.newPage();
|
|
7863
7916
|
try {
|
|
7864
7917
|
await page.goto(url, { waitUntil: "networkidle", timeout: 6e4 });
|
|
@@ -7892,7 +7945,7 @@ async function capturePageData(url, opts = {}) {
|
|
|
7892
7945
|
return await captureKernel(url, opts.kernelApiKey, device, wantShot, wantBranding);
|
|
7893
7946
|
}
|
|
7894
7947
|
}
|
|
7895
|
-
var import_playwright2, import_sdk2, DESKTOP_VIEWPORT, MOBILE_VIEWPORT,
|
|
7948
|
+
var import_playwright2, import_sdk2, DESKTOP_VIEWPORT, MOBILE_VIEWPORT, SCROLL_STEP, SETTLE_MS;
|
|
7896
7949
|
var init_screenshot = __esm({
|
|
7897
7950
|
"src/lib/screenshot.ts"() {
|
|
7898
7951
|
"use strict";
|
|
@@ -7900,10 +7953,9 @@ var init_screenshot = __esm({
|
|
|
7900
7953
|
import_sdk2 = __toESM(require("@onkernel/sdk"), 1);
|
|
7901
7954
|
init_branding_extractor();
|
|
7902
7955
|
init_cost_telemetry();
|
|
7956
|
+
init_browser_user_agent();
|
|
7903
7957
|
DESKTOP_VIEWPORT = { width: 1440, height: 900 };
|
|
7904
7958
|
MOBILE_VIEWPORT = { width: 390, height: 844 };
|
|
7905
|
-
DESKTOP_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
7906
|
-
MOBILE_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1";
|
|
7907
7959
|
SCROLL_STEP = 900;
|
|
7908
7960
|
SETTLE_MS = 400;
|
|
7909
7961
|
}
|
|
@@ -8715,6 +8767,7 @@ async function crawlWithRotation(urls, opts) {
|
|
|
8715
8767
|
const worker = async () => {
|
|
8716
8768
|
let kb = null;
|
|
8717
8769
|
let browser = null;
|
|
8770
|
+
let runtimeUserAgent = null;
|
|
8718
8771
|
let inBrowser = 0;
|
|
8719
8772
|
let kbOpenedAtMs = 0;
|
|
8720
8773
|
const retire = async () => {
|
|
@@ -8722,6 +8775,7 @@ async function crawlWithRotation(urls, opts) {
|
|
|
8722
8775
|
browser = null;
|
|
8723
8776
|
kb = null;
|
|
8724
8777
|
kbOpenedAtMs = 0;
|
|
8778
|
+
runtimeUserAgent = null;
|
|
8725
8779
|
if (b) await b.close().catch(() => {
|
|
8726
8780
|
});
|
|
8727
8781
|
if (k) {
|
|
@@ -8743,9 +8797,10 @@ async function crawlWithRotation(urls, opts) {
|
|
|
8743
8797
|
kb = await createBrowserWithRetry(client2, headless);
|
|
8744
8798
|
kbOpenedAtMs = Date.now();
|
|
8745
8799
|
browser = await withTimeout(import_playwright3.chromium.connectOverCDP(kb.cdp_ws_url), 3e4);
|
|
8800
|
+
runtimeUserAgent = await detectRuntimeUserAgent(browser);
|
|
8746
8801
|
inBrowser = 0;
|
|
8747
8802
|
}
|
|
8748
|
-
const context = await browser.newContext({ serviceWorkers: "block" });
|
|
8803
|
+
const context = await browser.newContext(withRuntimeUserAgent({ serviceWorkers: "block" }, runtimeUserAgent));
|
|
8749
8804
|
try {
|
|
8750
8805
|
r = await crawlPage(context, url);
|
|
8751
8806
|
} finally {
|
|
@@ -8812,6 +8867,7 @@ var init_rotating_proxy_crawl = __esm({
|
|
|
8812
8867
|
init_cost_telemetry();
|
|
8813
8868
|
init_browser_request_guard();
|
|
8814
8869
|
init_bounded_response();
|
|
8870
|
+
init_browser_user_agent();
|
|
8815
8871
|
init_browser_request_guard();
|
|
8816
8872
|
DEFAULT_CONCURRENCY = 1;
|
|
8817
8873
|
DEFAULT_URLS_PER_BROWSER = 10;
|
|
@@ -18981,21 +19037,11 @@ async function ensureKernelProfile(kernelClient, name) {
|
|
|
18981
19037
|
if (!isKernelProfileConflict(err)) throw err;
|
|
18982
19038
|
}
|
|
18983
19039
|
}
|
|
18984
|
-
async function detectRuntimeUserAgent(browser) {
|
|
18985
|
-
try {
|
|
18986
|
-
const page = browser.contexts()[0]?.pages()[0];
|
|
18987
|
-
if (!page) return null;
|
|
18988
|
-
const ua = await page.evaluate(() => navigator.userAgent);
|
|
18989
|
-
return typeof ua === "string" && /Chrome\/\d+/.test(ua) ? ua : null;
|
|
18990
|
-
} catch {
|
|
18991
|
-
return null;
|
|
18992
|
-
}
|
|
18993
|
-
}
|
|
18994
19040
|
function rankCheckContextOptions(config, runtimeUserAgent) {
|
|
18995
19041
|
return {
|
|
18996
19042
|
viewport: config.viewport,
|
|
18997
19043
|
locale: config.locale,
|
|
18998
|
-
|
|
19044
|
+
...runtimeUserAgent ? { userAgent: runtimeUserAgent } : {},
|
|
18999
19045
|
...config.deviceScaleFactor ? { deviceScaleFactor: config.deviceScaleFactor } : {},
|
|
19000
19046
|
...config.isMobile !== void 0 ? { isMobile: config.isMobile } : {},
|
|
19001
19047
|
...config.hasTouch !== void 0 ? { hasTouch: config.hasTouch } : {}
|
|
@@ -19041,7 +19087,7 @@ function buildYouTubeChannelVideosUrl(channelInput) {
|
|
|
19041
19087
|
}
|
|
19042
19088
|
return `https://www.youtube.com/${handle}/videos`;
|
|
19043
19089
|
}
|
|
19044
|
-
var import_playwright_extra, import_puppeteer_extra_plugin_stealth, import_playwright4, import_sdk5,
|
|
19090
|
+
var import_playwright_extra, import_puppeteer_extra_plugin_stealth, import_playwright4, import_sdk5, DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS, KERNEL_BROWSER_CLOSE_TIMEOUT_MS, KERNEL_SESSION_DELETE_TIMEOUT_MS, BrowserDriver;
|
|
19045
19091
|
var init_BrowserDriver = __esm({
|
|
19046
19092
|
"src/driver/BrowserDriver.ts"() {
|
|
19047
19093
|
"use strict";
|
|
@@ -19054,9 +19100,9 @@ var init_BrowserDriver = __esm({
|
|
|
19054
19100
|
init_cost_telemetry();
|
|
19055
19101
|
init_cost_context();
|
|
19056
19102
|
init_cost_rates();
|
|
19103
|
+
init_browser_user_agent();
|
|
19104
|
+
init_browser_user_agent();
|
|
19057
19105
|
import_playwright_extra.chromium.use((0, import_puppeteer_extra_plugin_stealth.default)());
|
|
19058
|
-
DESKTOP_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
19059
|
-
MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1";
|
|
19060
19106
|
DEFAULT_KERNEL_BROWSER_TIMEOUT_SECONDS = 180;
|
|
19061
19107
|
KERNEL_BROWSER_CLOSE_TIMEOUT_MS = 3e3;
|
|
19062
19108
|
KERNEL_SESSION_DELETE_TIMEOUT_MS = 5e3;
|
|
@@ -19089,7 +19135,7 @@ var init_BrowserDriver = __esm({
|
|
|
19089
19135
|
viewport: config.viewport,
|
|
19090
19136
|
locale: config.locale,
|
|
19091
19137
|
device,
|
|
19092
|
-
userAgent:
|
|
19138
|
+
userAgent: null,
|
|
19093
19139
|
deviceScaleFactor: config.deviceScaleFactor ?? null,
|
|
19094
19140
|
isMobile: config.isMobile === true,
|
|
19095
19141
|
hasTouch: config.hasTouch === true
|
|
@@ -19177,7 +19223,7 @@ var init_BrowserDriver = __esm({
|
|
|
19177
19223
|
}
|
|
19178
19224
|
this.browser = await import_playwright4.chromium.connectOverCDP(kernelBrowser.cdp_ws_url);
|
|
19179
19225
|
const runtimeUserAgent = await detectRuntimeUserAgent(this.browser);
|
|
19180
|
-
this.debugSnapshot.context.userAgent =
|
|
19226
|
+
this.debugSnapshot.context.userAgent = runtimeUserAgent;
|
|
19181
19227
|
this.context = await this.browser.newContext(rankCheckContextOptions(config, runtimeUserAgent));
|
|
19182
19228
|
await this.installEsbuildHelperShims(this.context);
|
|
19183
19229
|
this.page = await this.context.newPage();
|
|
@@ -19199,17 +19245,19 @@ var init_BrowserDriver = __esm({
|
|
|
19199
19245
|
...config.browserExecutablePath ? { executablePath: config.browserExecutablePath } : {},
|
|
19200
19246
|
...config.ignoreDefaultArgs ? { ignoreDefaultArgs: config.ignoreDefaultArgs } : {}
|
|
19201
19247
|
};
|
|
19202
|
-
const ctxOpts = rankCheckContextOptions(config);
|
|
19203
19248
|
if (config.profileDir) {
|
|
19204
19249
|
this.context = await import_playwright_extra.chromium.launchPersistentContext(config.profileDir, {
|
|
19205
19250
|
...launchOpts,
|
|
19206
|
-
...
|
|
19251
|
+
...rankCheckContextOptions(config)
|
|
19207
19252
|
});
|
|
19208
19253
|
await this.installEsbuildHelperShims(this.context);
|
|
19209
19254
|
this.page = await this.context.newPage();
|
|
19255
|
+
this.debugSnapshot.context.userAgent = await detectPageUserAgent(this.page);
|
|
19210
19256
|
} else {
|
|
19211
19257
|
this.browser = await import_playwright_extra.chromium.launch(launchOpts);
|
|
19212
|
-
|
|
19258
|
+
const runtimeUserAgent = await detectRuntimeUserAgent(this.browser);
|
|
19259
|
+
this.debugSnapshot.context.userAgent = runtimeUserAgent;
|
|
19260
|
+
this.context = await this.browser.newContext(rankCheckContextOptions(config, runtimeUserAgent));
|
|
19213
19261
|
await this.installEsbuildHelperShims(this.context);
|
|
19214
19262
|
this.page = await this.context.newPage();
|
|
19215
19263
|
}
|
|
@@ -29553,7 +29601,6 @@ var init_facebook_ad_routes = __esm({
|
|
|
29553
29601
|
try {
|
|
29554
29602
|
res = await fetch(parsedUrl.href, {
|
|
29555
29603
|
headers: {
|
|
29556
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
|
29557
29604
|
"Referer": "https://www.facebook.com/"
|
|
29558
29605
|
},
|
|
29559
29606
|
signal: AbortSignal.timeout(6e4)
|
|
@@ -30892,10 +30939,10 @@ function blobKeyPrefix(shortcode, sourceUrl) {
|
|
|
30892
30939
|
const slug = shortcode ? `instagram-${shortcode}` : safeFilePart(sourceUrl);
|
|
30893
30940
|
return `instagram/${stamp}-${slug}`;
|
|
30894
30941
|
}
|
|
30895
|
-
async function fetchMediaBytes(url, referer) {
|
|
30942
|
+
async function fetchMediaBytes(url, referer, runtimeUserAgent) {
|
|
30896
30943
|
const res = await fetch(url, {
|
|
30897
30944
|
headers: {
|
|
30898
|
-
"User-Agent":
|
|
30945
|
+
...runtimeUserAgent ? { "User-Agent": runtimeUserAgent } : {},
|
|
30899
30946
|
"Referer": referer
|
|
30900
30947
|
},
|
|
30901
30948
|
signal: AbortSignal.timeout(9e4)
|
|
@@ -30946,6 +30993,7 @@ var init_instagram_routes = __esm({
|
|
|
30946
30993
|
import_node_os8 = require("os");
|
|
30947
30994
|
import_node_path11 = require("path");
|
|
30948
30995
|
import_node_child_process2 = require("child_process");
|
|
30996
|
+
init_browser_user_agent();
|
|
30949
30997
|
init_browser_service_env();
|
|
30950
30998
|
init_BrowserDriver();
|
|
30951
30999
|
init_api_auth();
|
|
@@ -31062,6 +31110,7 @@ var init_instagram_routes = __esm({
|
|
|
31062
31110
|
const launch = await resolveInstagramLaunch(body);
|
|
31063
31111
|
await driver.launch(launch.config);
|
|
31064
31112
|
const page = driver.getPage();
|
|
31113
|
+
const runtimeUserAgent = await detectPageUserAgent(page);
|
|
31065
31114
|
const capturedMediaUrls = [];
|
|
31066
31115
|
page.on("response", (response) => {
|
|
31067
31116
|
const url = normalizeInstagramMediaUrl(response.url());
|
|
@@ -31090,7 +31139,7 @@ var init_instagram_routes = __esm({
|
|
|
31090
31139
|
downloads.push({ kind: "text", url: null, savedPath: storedText.url, sizeBytes: storedText.bytes, mimeType: "text/plain", error: null });
|
|
31091
31140
|
if (mediaTypes.has("image") && extraction.imageUrl) {
|
|
31092
31141
|
try {
|
|
31093
|
-
const { bytes, mimeType } = await fetchMediaBytes(extraction.imageUrl, extraction.pageUrl);
|
|
31142
|
+
const { bytes, mimeType } = await fetchMediaBytes(extraction.imageUrl, extraction.pageUrl, runtimeUserAgent);
|
|
31094
31143
|
const ext = extFromMime(mimeType, "jpg");
|
|
31095
31144
|
const stored = await store.put(`${prefix}/image.${ext}`, bytes, mimeType ?? "application/octet-stream");
|
|
31096
31145
|
downloads.push({ kind: "image", url: extraction.imageUrl, savedPath: stored.url, sizeBytes: stored.bytes, mimeType, error: null });
|
|
@@ -31106,7 +31155,7 @@ var init_instagram_routes = __esm({
|
|
|
31106
31155
|
if (track.streamType === "audio" && !mediaTypes.has("audio")) continue;
|
|
31107
31156
|
const kind = track.streamType === "audio" ? "audio" : "video";
|
|
31108
31157
|
try {
|
|
31109
|
-
const { bytes, mimeType } = await fetchMediaBytes(track.url, extraction.pageUrl);
|
|
31158
|
+
const { bytes, mimeType } = await fetchMediaBytes(track.url, extraction.pageUrl, runtimeUserAgent);
|
|
31110
31159
|
const stored = await store.put(`${prefix}/${trackFilename(track, index, extraction.shortcode)}`, bytes, mimeType ?? "video/mp4");
|
|
31111
31160
|
downloads.push({ kind, url: track.url, savedPath: stored.url, sizeBytes: stored.bytes, mimeType, error: null });
|
|
31112
31161
|
const isMuxInput = muxEligible && (track.url === extraction.selectedVideoTrack.url || track.url === extraction.selectedAudioTrack.url);
|
|
@@ -31823,7 +31872,7 @@ var init_ai_surfaces = __esm({
|
|
|
31823
31872
|
});
|
|
31824
31873
|
|
|
31825
31874
|
// src/extractor/PAAExtractor.ts
|
|
31826
|
-
var
|
|
31875
|
+
var PAAExtractor;
|
|
31827
31876
|
var init_PAAExtractor = __esm({
|
|
31828
31877
|
"src/extractor/PAAExtractor.ts"() {
|
|
31829
31878
|
"use strict";
|
|
@@ -31835,8 +31884,6 @@ var init_PAAExtractor = __esm({
|
|
|
31835
31884
|
init_paa_answer_cleanup();
|
|
31836
31885
|
init_errors();
|
|
31837
31886
|
init_ai_surfaces();
|
|
31838
|
-
DESKTOP_USER_AGENT2 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
31839
|
-
MOBILE_USER_AGENT2 = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1";
|
|
31840
31887
|
PAAExtractor = class {
|
|
31841
31888
|
constructor(driver, reporter) {
|
|
31842
31889
|
this.driver = driver;
|
|
@@ -32646,7 +32693,6 @@ var init_PAAExtractor = __esm({
|
|
|
32646
32693
|
requireUsEgress: options.requireUsEgress,
|
|
32647
32694
|
viewport: isMobile ? { width: 390, height: 844 } : { width: 1280, height: 800 },
|
|
32648
32695
|
locale: `${options.hl}-${options.gl.toUpperCase()}`,
|
|
32649
|
-
userAgent: isMobile ? MOBILE_USER_AGENT2 : DESKTOP_USER_AGENT2,
|
|
32650
32696
|
deviceScaleFactor: isMobile ? 3 : 1,
|
|
32651
32697
|
isMobile,
|
|
32652
32698
|
hasTouch: isMobile,
|
|
@@ -33265,7 +33311,12 @@ async function harvest(rawOptions) {
|
|
|
33265
33311
|
max_questions: attemptOptions.maxQuestions
|
|
33266
33312
|
}));
|
|
33267
33313
|
const baseCtx = currentCostContext();
|
|
33268
|
-
const
|
|
33314
|
+
const allowHeadlessTest = baseCtx?.harvestBrowserMode === "headless-test" && process.env[HEADLESS_HARVEST_TEST_ENV] === "true";
|
|
33315
|
+
const attempt = baseCtx ? await runWithCostContext({
|
|
33316
|
+
...baseCtx,
|
|
33317
|
+
forceHeadless: allowHeadlessTest,
|
|
33318
|
+
forceHeadful: !allowHeadlessTest
|
|
33319
|
+
}, () => extractOnce2(attemptOptions, signal)) : await extractOnce2(attemptOptions, signal);
|
|
33269
33320
|
if (attempt.error) {
|
|
33270
33321
|
const err = attempt.error;
|
|
33271
33322
|
const outcome2 = classifyAttemptError(err);
|
|
@@ -39483,7 +39534,7 @@ var PACKAGE_VERSION;
|
|
|
39483
39534
|
var init_version = __esm({
|
|
39484
39535
|
"src/version.ts"() {
|
|
39485
39536
|
"use strict";
|
|
39486
|
-
PACKAGE_VERSION = "0.43.
|
|
39537
|
+
PACKAGE_VERSION = "0.43.5";
|
|
39487
39538
|
}
|
|
39488
39539
|
});
|
|
39489
39540
|
|
|
@@ -50524,6 +50575,12 @@ var init_run_capture = __esm({
|
|
|
50524
50575
|
// src/services/browser-agent/browser-agent-service.ts
|
|
50525
50576
|
function keepHostedBrowserAlive(_browser) {
|
|
50526
50577
|
}
|
|
50578
|
+
async function currentOrRuntimeContext(browser) {
|
|
50579
|
+
const current = browser.contexts()[0];
|
|
50580
|
+
if (current) return current;
|
|
50581
|
+
const runtimeUserAgent = await detectRuntimeUserAgent(browser);
|
|
50582
|
+
return browser.newContext(withRuntimeUserAgent({}, runtimeUserAgent));
|
|
50583
|
+
}
|
|
50527
50584
|
function client() {
|
|
50528
50585
|
const apiKey = browserServiceApiKey();
|
|
50529
50586
|
if (!apiKey) throw new Error("Browser backend API key is required");
|
|
@@ -50647,7 +50704,7 @@ async function pressKeys(runtimeSessionId, keys) {
|
|
|
50647
50704
|
async function goto(cdpWsUrl, url) {
|
|
50648
50705
|
const browser = await import_playwright5.chromium.connectOverCDP(cdpWsUrl);
|
|
50649
50706
|
try {
|
|
50650
|
-
const context =
|
|
50707
|
+
const context = await currentOrRuntimeContext(browser);
|
|
50651
50708
|
const page = context.pages()[0] ?? await context.newPage();
|
|
50652
50709
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
50653
50710
|
return { url: page.url(), title: await page.title() };
|
|
@@ -50658,7 +50715,7 @@ async function goto(cdpWsUrl, url) {
|
|
|
50658
50715
|
async function captureFanout(cdpWsUrl, input) {
|
|
50659
50716
|
const browser = await import_playwright5.chromium.connectOverCDP(cdpWsUrl);
|
|
50660
50717
|
try {
|
|
50661
|
-
const context =
|
|
50718
|
+
const context = await currentOrRuntimeContext(browser);
|
|
50662
50719
|
const page = context.pages()[0] ?? await context.newPage();
|
|
50663
50720
|
return await runFanoutCapture(page, input);
|
|
50664
50721
|
} finally {
|
|
@@ -50668,7 +50725,7 @@ async function captureFanout(cdpWsUrl, input) {
|
|
|
50668
50725
|
async function readPage(cdpWsUrl) {
|
|
50669
50726
|
const browser = await import_playwright5.chromium.connectOverCDP(cdpWsUrl);
|
|
50670
50727
|
try {
|
|
50671
|
-
const context =
|
|
50728
|
+
const context = await currentOrRuntimeContext(browser);
|
|
50672
50729
|
const page = context.pages()[0] ?? await context.newPage();
|
|
50673
50730
|
const url = page.url();
|
|
50674
50731
|
const title = await page.title().catch(() => "");
|
|
@@ -50723,7 +50780,7 @@ async function readPage(cdpWsUrl) {
|
|
|
50723
50780
|
async function locatePageTargets(cdpWsUrl, targets) {
|
|
50724
50781
|
const browser = await import_playwright5.chromium.connectOverCDP(cdpWsUrl);
|
|
50725
50782
|
try {
|
|
50726
|
-
const context =
|
|
50783
|
+
const context = await currentOrRuntimeContext(browser);
|
|
50727
50784
|
const page = context.pages()[0] ?? await context.newPage();
|
|
50728
50785
|
const url = page.url();
|
|
50729
50786
|
const title = await page.title().catch(() => "");
|
|
@@ -50893,6 +50950,7 @@ var init_browser_agent_service = __esm({
|
|
|
50893
50950
|
import_playwright5 = require("playwright");
|
|
50894
50951
|
init_browser_service_env();
|
|
50895
50952
|
init_run_capture();
|
|
50953
|
+
init_browser_user_agent();
|
|
50896
50954
|
DEFAULT_TIMEOUT_SECONDS = 600;
|
|
50897
50955
|
}
|
|
50898
50956
|
});
|
|
@@ -52055,6 +52113,22 @@ function isScraperResource(resource) {
|
|
|
52055
52113
|
if (!resource) return false;
|
|
52056
52114
|
return resource.replace(/\/$/, "") === SCRAPER_RESOURCE();
|
|
52057
52115
|
}
|
|
52116
|
+
async function withOAuthDatabaseDeadline(stage, operation, timeoutMs = OAUTH_DATABASE_TIMEOUT_MS) {
|
|
52117
|
+
const startedAt = Date.now();
|
|
52118
|
+
let timeout;
|
|
52119
|
+
try {
|
|
52120
|
+
const value = await Promise.race([
|
|
52121
|
+
operation,
|
|
52122
|
+
new Promise((_, reject) => {
|
|
52123
|
+
timeout = setTimeout(() => reject(new OAuthDatabaseUnavailableError(stage)), timeoutMs);
|
|
52124
|
+
})
|
|
52125
|
+
]);
|
|
52126
|
+
console.log("[oauth-db] stage=%s status=ok duration_ms=%d", stage, Date.now() - startedAt);
|
|
52127
|
+
return value;
|
|
52128
|
+
} finally {
|
|
52129
|
+
if (timeout) clearTimeout(timeout);
|
|
52130
|
+
}
|
|
52131
|
+
}
|
|
52058
52132
|
async function getKeys() {
|
|
52059
52133
|
if (cachedKeys) return cachedKeys;
|
|
52060
52134
|
const raw = process.env.OAUTH_SIGNING_KEY;
|
|
@@ -52108,7 +52182,7 @@ async function sessionUser(token4) {
|
|
|
52108
52182
|
if (!token4) return null;
|
|
52109
52183
|
const userId = verifySession(token4);
|
|
52110
52184
|
if (!userId) return null;
|
|
52111
|
-
const user = await getUserById(userId);
|
|
52185
|
+
const user = await withOAuthDatabaseDeadline("session-user", getUserById(userId));
|
|
52112
52186
|
return user ?? null;
|
|
52113
52187
|
}
|
|
52114
52188
|
function escapeHtml2(value) {
|
|
@@ -52233,7 +52307,7 @@ function parseAuthParams(get) {
|
|
|
52233
52307
|
async function validateAuthRequest(p) {
|
|
52234
52308
|
if (p.response_type !== "code") return { ok: false, status: 400, message: "response_type must be code" };
|
|
52235
52309
|
if (!p.client_id) return { ok: false, status: 400, message: "client_id is required" };
|
|
52236
|
-
const client2 = await getClient(p.client_id);
|
|
52310
|
+
const client2 = await withOAuthDatabaseDeadline("get-client", getClient(p.client_id));
|
|
52237
52311
|
if (!client2) return { ok: false, status: 400, message: "unknown client_id" };
|
|
52238
52312
|
if (!p.redirect_uri || !client2.redirect_uris.includes(p.redirect_uri)) return { ok: false, status: 400, message: "redirect_uri does not match the registered set" };
|
|
52239
52313
|
if (!isAllowedRedirect(p.redirect_uri)) return { ok: false, status: 400, message: "redirect_uri host not allowed" };
|
|
@@ -52252,7 +52326,7 @@ async function mintAccessToken(identity, scope, plan, audience) {
|
|
|
52252
52326
|
function tokenErrorResponse(c, error, description, status) {
|
|
52253
52327
|
return c.json({ error, error_description: description }, status);
|
|
52254
52328
|
}
|
|
52255
|
-
var import_hono23, import_cookie, import_node_crypto28, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
|
|
52329
|
+
var import_hono23, import_cookie, import_node_crypto28, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, OAUTH_DATABASE_TIMEOUT_MS, OAuthDatabaseUnavailableError, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
|
|
52256
52330
|
var init_oauth_routes = __esm({
|
|
52257
52331
|
"src/api/oauth-routes.ts"() {
|
|
52258
52332
|
"use strict";
|
|
@@ -52274,6 +52348,15 @@ var init_oauth_routes = __esm({
|
|
|
52274
52348
|
REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
52275
52349
|
CODE_TTL_SECONDS = 60;
|
|
52276
52350
|
ROTATION_GRACE_SECONDS = 120;
|
|
52351
|
+
OAUTH_DATABASE_TIMEOUT_MS = 5e3;
|
|
52352
|
+
OAuthDatabaseUnavailableError = class extends Error {
|
|
52353
|
+
stage;
|
|
52354
|
+
constructor(stage) {
|
|
52355
|
+
super("The authorization service database is temporarily unavailable.");
|
|
52356
|
+
this.name = "OAuthDatabaseUnavailableError";
|
|
52357
|
+
this.stage = stage;
|
|
52358
|
+
}
|
|
52359
|
+
};
|
|
52277
52360
|
secureCookies = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
52278
52361
|
sessionCookieOptions = {
|
|
52279
52362
|
httpOnly: true,
|
|
@@ -52284,6 +52367,18 @@ var init_oauth_routes = __esm({
|
|
|
52284
52367
|
};
|
|
52285
52368
|
cachedKeys = null;
|
|
52286
52369
|
oauthApp = new import_hono23.Hono();
|
|
52370
|
+
oauthApp.onError((error, c) => {
|
|
52371
|
+
if (error instanceof OAuthDatabaseUnavailableError) {
|
|
52372
|
+
console.error("[oauth-db] stage=%s status=timeout timeout_ms=%d", error.stage, OAUTH_DATABASE_TIMEOUT_MS);
|
|
52373
|
+
c.header("Retry-After", "5");
|
|
52374
|
+
return c.json({
|
|
52375
|
+
error: "temporarily_unavailable",
|
|
52376
|
+
error_description: "Authorization is temporarily unavailable. Please retry shortly."
|
|
52377
|
+
}, 503);
|
|
52378
|
+
}
|
|
52379
|
+
console.error("[oauth] unhandled error:", error);
|
|
52380
|
+
return c.json({ error: "server_error", error_description: "Authorization could not be completed." }, 500);
|
|
52381
|
+
});
|
|
52287
52382
|
oauthApp.use("*", async (c, next) => {
|
|
52288
52383
|
c.header("Access-Control-Allow-Origin", "*");
|
|
52289
52384
|
c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -52345,7 +52440,7 @@ var init_oauth_routes = __esm({
|
|
|
52345
52440
|
}
|
|
52346
52441
|
const clientName = typeof body.client_name === "string" ? body.client_name : null;
|
|
52347
52442
|
const clientId = `client_${(0, import_node_crypto28.randomBytes)(16).toString("hex")}`;
|
|
52348
|
-
await registerClient(clientId, redirectUris, clientName);
|
|
52443
|
+
await withOAuthDatabaseDeadline("register-client", registerClient(clientId, redirectUris, clientName));
|
|
52349
52444
|
console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
|
|
52350
52445
|
return c.json({
|
|
52351
52446
|
client_id: clientId,
|
|
@@ -52383,7 +52478,7 @@ var init_oauth_routes = __esm({
|
|
|
52383
52478
|
if (action === "login") {
|
|
52384
52479
|
const email = (get("email") ?? "").trim().toLowerCase();
|
|
52385
52480
|
const password = get("password") ?? "";
|
|
52386
|
-
const user2 = email ? await getUserByEmail(email) : void 0;
|
|
52481
|
+
const user2 = email ? await withOAuthDatabaseDeadline("login-user", getUserByEmail(email)) : void 0;
|
|
52387
52482
|
const okPw = !!(user2 && user2.password_hash && verifyPassword(password, user2.password_hash));
|
|
52388
52483
|
console.log("[oauth-dcr] LOGIN email=%s found=%s hasHash=%s pwLen=%d ok=%s", email, !!user2, !!(user2 && user2.password_hash), password.length, okPw);
|
|
52389
52484
|
if (!okPw) {
|
|
@@ -52399,7 +52494,7 @@ var init_oauth_routes = __esm({
|
|
|
52399
52494
|
const scope = negotiateScope(p.scope, user, p.resource);
|
|
52400
52495
|
const code = `code_${(0, import_node_crypto28.randomBytes)(32).toString("base64url")}`;
|
|
52401
52496
|
const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
|
|
52402
|
-
await putCode({
|
|
52497
|
+
await withOAuthDatabaseDeadline("put-authorization-code", putCode({
|
|
52403
52498
|
code,
|
|
52404
52499
|
client_id: p.client_id,
|
|
52405
52500
|
redirect_uri: p.redirect_uri,
|
|
@@ -52408,7 +52503,7 @@ var init_oauth_routes = __esm({
|
|
|
52408
52503
|
scope,
|
|
52409
52504
|
resource: p.resource || RESOURCE(),
|
|
52410
52505
|
expires_at: expiresAt
|
|
52411
|
-
});
|
|
52506
|
+
}));
|
|
52412
52507
|
const u = new URL(p.redirect_uri);
|
|
52413
52508
|
u.searchParams.set("code", code);
|
|
52414
52509
|
if (p.state) u.searchParams.set("state", p.state);
|
|
@@ -52426,18 +52521,18 @@ var init_oauth_routes = __esm({
|
|
|
52426
52521
|
const clientId = get("client_id") ?? "";
|
|
52427
52522
|
const redirectUri = get("redirect_uri") ?? "";
|
|
52428
52523
|
const verifier = get("code_verifier") ?? "";
|
|
52429
|
-
const record = await consumeCode(code);
|
|
52524
|
+
const record = await withOAuthDatabaseDeadline("consume-authorization-code", consumeCode(code));
|
|
52430
52525
|
if (!record) return tokenErrorResponse(c, "invalid_grant", "Authorization code is invalid or already used", 400);
|
|
52431
52526
|
if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Authorization code has expired", 400);
|
|
52432
52527
|
if (record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
|
|
52433
52528
|
if (record.redirect_uri !== redirectUri) return tokenErrorResponse(c, "invalid_grant", "redirect_uri mismatch", 400);
|
|
52434
52529
|
if (!pkceMatches(verifier, record.code_challenge)) return tokenErrorResponse(c, "invalid_grant", "PKCE verification failed", 400);
|
|
52435
|
-
const user = await getUserByEmail(record.identity);
|
|
52530
|
+
const user = await withOAuthDatabaseDeadline("token-user", getUserByEmail(record.identity));
|
|
52436
52531
|
const plan = user ? resolvePlan(user) : "free";
|
|
52437
52532
|
const audience = record.resource ?? RESOURCE();
|
|
52438
52533
|
const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
|
|
52439
52534
|
const refreshToken = `rt_${(0, import_node_crypto28.randomBytes)(40).toString("base64url")}`;
|
|
52440
|
-
await putRefresh({
|
|
52535
|
+
await withOAuthDatabaseDeadline("put-refresh-token", putRefresh({
|
|
52441
52536
|
refresh_token: refreshToken,
|
|
52442
52537
|
client_id: clientId,
|
|
52443
52538
|
identity: record.identity,
|
|
@@ -52445,15 +52540,15 @@ var init_oauth_routes = __esm({
|
|
|
52445
52540
|
resource: audience,
|
|
52446
52541
|
expires_at: new Date(Date.now() + REFRESH_TTL_SECONDS * 1e3).toISOString(),
|
|
52447
52542
|
revoked: 0
|
|
52448
|
-
});
|
|
52543
|
+
}));
|
|
52449
52544
|
return c.json({ access_token: accessToken, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: refreshToken, scope: record.scope });
|
|
52450
52545
|
}
|
|
52451
52546
|
if (grantType === "refresh_token") {
|
|
52452
52547
|
const refreshToken = get("refresh_token") ?? "";
|
|
52453
52548
|
const clientId = get("client_id") ?? "";
|
|
52454
|
-
const record = await getRefresh(refreshToken);
|
|
52549
|
+
const record = await withOAuthDatabaseDeadline("get-refresh-token", getRefresh(refreshToken));
|
|
52455
52550
|
if (record && record.revoked && record.replaced_by && record.rotated_at && Date.now() - new Date(record.rotated_at).getTime() < ROTATION_GRACE_SECONDS * 1e3 && (!clientId || record.client_id === clientId)) {
|
|
52456
|
-
const graceUser = await getUserByEmail(record.identity);
|
|
52551
|
+
const graceUser = await withOAuthDatabaseDeadline("refresh-grace-user", getUserByEmail(record.identity));
|
|
52457
52552
|
const gracePlan = graceUser ? resolvePlan(graceUser) : "free";
|
|
52458
52553
|
const graceAudience = record.resource ?? RESOURCE();
|
|
52459
52554
|
const graceAccess = await mintAccessToken(record.identity, record.scope, gracePlan, graceAudience);
|
|
@@ -52462,12 +52557,12 @@ var init_oauth_routes = __esm({
|
|
|
52462
52557
|
if (!record || record.revoked) return tokenErrorResponse(c, "invalid_grant", "Refresh token is invalid or revoked", 400);
|
|
52463
52558
|
if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Refresh token has expired", 400);
|
|
52464
52559
|
if (clientId && record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
|
|
52465
|
-
const user = await getUserByEmail(record.identity);
|
|
52560
|
+
const user = await withOAuthDatabaseDeadline("refresh-user", getUserByEmail(record.identity));
|
|
52466
52561
|
const plan = user ? resolvePlan(user) : "free";
|
|
52467
52562
|
const audience = record.resource ?? RESOURCE();
|
|
52468
52563
|
const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
|
|
52469
52564
|
const nextRefresh = `rt_${(0, import_node_crypto28.randomBytes)(40).toString("base64url")}`;
|
|
52470
|
-
await rotateRefresh(refreshToken, {
|
|
52565
|
+
await withOAuthDatabaseDeadline("rotate-refresh-token", rotateRefresh(refreshToken, {
|
|
52471
52566
|
refresh_token: nextRefresh,
|
|
52472
52567
|
client_id: record.client_id,
|
|
52473
52568
|
identity: record.identity,
|
|
@@ -52475,7 +52570,7 @@ var init_oauth_routes = __esm({
|
|
|
52475
52570
|
resource: audience,
|
|
52476
52571
|
expires_at: new Date(Date.now() + REFRESH_TTL_SECONDS * 1e3).toISOString(),
|
|
52477
52572
|
revoked: 0
|
|
52478
|
-
});
|
|
52573
|
+
}));
|
|
52479
52574
|
return c.json({ access_token: accessToken, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: nextRefresh, scope: record.scope });
|
|
52480
52575
|
}
|
|
52481
52576
|
return tokenErrorResponse(c, "unsupported_grant_type", "grant_type must be authorization_code or refresh_token", 400);
|
|
@@ -61698,8 +61793,7 @@ var init_server = __esm({
|
|
|
61698
61793
|
const op = opForCostPath(new URL(c.req.url).pathname);
|
|
61699
61794
|
if (!op) return next();
|
|
61700
61795
|
const probeRunId = c.req.header("x-cost-probe-id") ?? null;
|
|
61701
|
-
|
|
61702
|
-
return runWithCostContext({ op, probeRunId, forceHeadless }, () => next());
|
|
61796
|
+
return runWithCostContext({ op, probeRunId }, () => next());
|
|
61703
61797
|
});
|
|
61704
61798
|
app.post("/auth/register", requireAllowedOrigin, async (c) => {
|
|
61705
61799
|
const { email, password } = await c.req.json();
|