qwenproxy-cli 1.4.3 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clean-cache.js +188 -70
- package/dist/delete-chats.js +241 -81
- package/dist/index.js +242 -82
- package/dist/login.js +241 -81
- package/dist/reset-cooldowns.js +188 -70
- package/package.json +1 -1
- package/src/core/version.ts +1 -1
- package/src/services/playwright.ts +288 -85
|
@@ -995,8 +995,29 @@ export function getStealthScript(profile: FingerprintProfile): string {
|
|
|
995
995
|
const _toBlob = HTMLCanvasElement.prototype.toBlob;
|
|
996
996
|
const _getImageData = CanvasRenderingContext2D.prototype.getImageData;
|
|
997
997
|
|
|
998
|
+
function isCaptchaCanvas(canvas) {
|
|
999
|
+
try {
|
|
1000
|
+
if (!canvas) return false;
|
|
1001
|
+
const id = (canvas.id || "").toLowerCase();
|
|
1002
|
+
const cls = (canvas.className || "").toLowerCase();
|
|
1003
|
+
return (
|
|
1004
|
+
id.includes("baxia") ||
|
|
1005
|
+
id.includes("nc_") ||
|
|
1006
|
+
id.includes("captcha") ||
|
|
1007
|
+
id.includes("punish") ||
|
|
1008
|
+
cls.includes("baxia") ||
|
|
1009
|
+
cls.includes("nc_") ||
|
|
1010
|
+
cls.includes("captcha") ||
|
|
1011
|
+
Boolean(canvas.closest?.("#baxia-dialog, #baxia-punish, [class*='baxia'], [class*='nc_'], .auth-layout"))
|
|
1012
|
+
);
|
|
1013
|
+
} catch(e) {
|
|
1014
|
+
return false;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
998
1018
|
function addNoise(canvas) {
|
|
999
1019
|
try {
|
|
1020
|
+
if (isCaptchaCanvas(canvas)) return;
|
|
1000
1021
|
const ctx = canvas.getContext('2d');
|
|
1001
1022
|
if (!ctx) return;
|
|
1002
1023
|
const style = ctx.fillStyle;
|
|
@@ -1020,6 +1041,9 @@ export function getStealthScript(profile: FingerprintProfile): string {
|
|
|
1020
1041
|
|
|
1021
1042
|
CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
|
|
1022
1043
|
const imageData = _getImageData.apply(this, arguments);
|
|
1044
|
+
if (isCaptchaCanvas(this.canvas)) {
|
|
1045
|
+
return imageData;
|
|
1046
|
+
}
|
|
1023
1047
|
const data = imageData.data;
|
|
1024
1048
|
const maxPixels = Math.min(data.length / 4, 2500);
|
|
1025
1049
|
for (let i = 0; i < maxPixels * 4; i += 4) {
|
|
@@ -2206,41 +2230,65 @@ async function loginViaApi(
|
|
|
2206
2230
|
.createHash("sha256")
|
|
2207
2231
|
.update(password)
|
|
2208
2232
|
.digest("hex");
|
|
2209
|
-
const signinUrl = qwenUrl("/api/v2/auths/signin");
|
|
2210
|
-
|
|
2211
|
-
let signinSuccess = false;
|
|
2212
|
-
let data: any = null;
|
|
2213
2233
|
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
},
|
|
2222
|
-
headers: {
|
|
2223
|
-
"content-type": "application/json",
|
|
2224
|
-
accept: "application/json, text/plain, */*",
|
|
2225
|
-
referer: qwenUrl("/auth"),
|
|
2226
|
-
origin: qwenOrigin(),
|
|
2227
|
-
source: "web",
|
|
2228
|
-
},
|
|
2229
|
-
timeout: 10_000,
|
|
2230
|
-
});
|
|
2231
|
-
data = await response.json().catch(() => null);
|
|
2232
|
-
signinSuccess = Boolean(data && (data.success === true || data.token));
|
|
2233
|
-
} catch {}
|
|
2234
|
+
// Ensure the page is loaded at /auth so that same-site/CORS credentials and Baxia WAF headers are active
|
|
2235
|
+
if (!page.url().includes("/auth")) {
|
|
2236
|
+
await page.goto(qwenUrl("/auth"), {
|
|
2237
|
+
waitUntil: "domcontentloaded",
|
|
2238
|
+
timeout: config.timeouts.navigation,
|
|
2239
|
+
}).catch(() => {});
|
|
2240
|
+
await sleep(1000);
|
|
2234
2241
|
}
|
|
2235
2242
|
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2243
|
+
const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
|
|
2244
|
+
const requestId = crypto.randomUUID();
|
|
2245
|
+
|
|
2246
|
+
const evalRes = await page
|
|
2247
|
+
.evaluate(
|
|
2248
|
+
async ({ email, password, signinUrl, requestId }) => {
|
|
2249
|
+
try {
|
|
2250
|
+
const response = await fetch(signinUrl, {
|
|
2251
|
+
method: "POST",
|
|
2252
|
+
signal: AbortSignal.timeout(15_000),
|
|
2253
|
+
credentials: "include",
|
|
2254
|
+
headers: {
|
|
2255
|
+
accept: "application/json, text/plain, */*",
|
|
2256
|
+
"content-type": "application/json",
|
|
2257
|
+
source: "web",
|
|
2258
|
+
version: "0.3.11",
|
|
2259
|
+
"x-request-origin": "https://chat.qwen.ai",
|
|
2260
|
+
timezone: new Date().toString().split(" (")[0],
|
|
2261
|
+
"x-request-id": requestId,
|
|
2262
|
+
},
|
|
2263
|
+
body: JSON.stringify({ email, password }),
|
|
2264
|
+
});
|
|
2265
|
+
const json = await response.json().catch(() => null);
|
|
2266
|
+
return { ok: response.ok, status: response.status, data: json };
|
|
2267
|
+
} catch (e: any) {
|
|
2268
|
+
return { ok: false, error: e.message };
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2271
|
+
{ email, password: hashedPassword, signinUrl, requestId },
|
|
2272
|
+
)
|
|
2273
|
+
.catch(() => null);
|
|
2274
|
+
|
|
2275
|
+
let data: any = evalRes?.data;
|
|
2276
|
+
let signinSuccess = Boolean(
|
|
2277
|
+
data &&
|
|
2278
|
+
(data.success === true ||
|
|
2279
|
+
data.token ||
|
|
2280
|
+
data.data?.access_token ||
|
|
2281
|
+
data.data?.token),
|
|
2282
|
+
);
|
|
2283
|
+
|
|
2284
|
+
// Fallback to legacy chat.qwen.ai endpoint if auth.qwen.ai did not succeed
|
|
2285
|
+
if (!signinSuccess && !data?.data?.code) {
|
|
2286
|
+
const fallbackUrl = qwenUrl("/api/v2/auths/signin");
|
|
2287
|
+
const fallbackRes = await page
|
|
2240
2288
|
.evaluate(
|
|
2241
|
-
async ({ email, password,
|
|
2289
|
+
async ({ email, password, fallbackUrl, requestId }) => {
|
|
2242
2290
|
try {
|
|
2243
|
-
const response = await fetch(
|
|
2291
|
+
const response = await fetch(fallbackUrl, {
|
|
2244
2292
|
method: "POST",
|
|
2245
2293
|
signal: AbortSignal.timeout(10_000),
|
|
2246
2294
|
credentials: "include",
|
|
@@ -2248,10 +2296,11 @@ async function loginViaApi(
|
|
|
2248
2296
|
accept: "application/json, text/plain, */*",
|
|
2249
2297
|
"content-type": "application/json",
|
|
2250
2298
|
source: "web",
|
|
2299
|
+
version: "0.3.11",
|
|
2251
2300
|
timezone: new Date().toString().split(" (")[0],
|
|
2252
2301
|
"x-request-id": requestId,
|
|
2253
2302
|
},
|
|
2254
|
-
body: JSON.stringify({ email, password
|
|
2303
|
+
body: JSON.stringify({ email, password }),
|
|
2255
2304
|
});
|
|
2256
2305
|
const json = await response.json().catch(() => null);
|
|
2257
2306
|
return { ok: response.ok, status: response.status, data: json };
|
|
@@ -2259,13 +2308,19 @@ async function loginViaApi(
|
|
|
2259
2308
|
return { ok: false, error: e.message };
|
|
2260
2309
|
}
|
|
2261
2310
|
},
|
|
2262
|
-
{ email, password: hashedPassword,
|
|
2311
|
+
{ email, password: hashedPassword, fallbackUrl, requestId },
|
|
2263
2312
|
)
|
|
2264
2313
|
.catch(() => null);
|
|
2265
2314
|
|
|
2266
|
-
if (
|
|
2267
|
-
data =
|
|
2268
|
-
signinSuccess = Boolean(
|
|
2315
|
+
if (fallbackRes?.data) {
|
|
2316
|
+
data = fallbackRes.data;
|
|
2317
|
+
signinSuccess = Boolean(
|
|
2318
|
+
data &&
|
|
2319
|
+
(data.success === true ||
|
|
2320
|
+
data.token ||
|
|
2321
|
+
data.data?.access_token ||
|
|
2322
|
+
data.data?.token),
|
|
2323
|
+
);
|
|
2269
2324
|
}
|
|
2270
2325
|
}
|
|
2271
2326
|
|
|
@@ -2281,7 +2336,14 @@ async function loginViaApi(
|
|
|
2281
2336
|
}
|
|
2282
2337
|
|
|
2283
2338
|
if (signinSuccess) {
|
|
2284
|
-
const token =
|
|
2339
|
+
const token =
|
|
2340
|
+
data?.data?.access_token ||
|
|
2341
|
+
data?.data?.token ||
|
|
2342
|
+
data?.access_token ||
|
|
2343
|
+
data?.token;
|
|
2344
|
+
const refreshToken =
|
|
2345
|
+
data?.data?.refresh_token || data?.refresh_token;
|
|
2346
|
+
|
|
2285
2347
|
if (token) {
|
|
2286
2348
|
try {
|
|
2287
2349
|
await page.context().addCookies([
|
|
@@ -2308,13 +2370,18 @@ async function loginViaApi(
|
|
|
2308
2370
|
|
|
2309
2371
|
if (token) {
|
|
2310
2372
|
await page
|
|
2311
|
-
.evaluate(
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2373
|
+
.evaluate(
|
|
2374
|
+
({ tok, rTok }) => {
|
|
2375
|
+
try {
|
|
2376
|
+
localStorage.removeItem("qwen_token_logged_out_marker");
|
|
2377
|
+
localStorage.setItem("token", tok);
|
|
2378
|
+
localStorage.setItem("access_token", tok);
|
|
2379
|
+
if (rTok) localStorage.setItem("refresh_token", rTok);
|
|
2380
|
+
document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
|
|
2381
|
+
} catch {}
|
|
2382
|
+
},
|
|
2383
|
+
{ tok: token, rTok: refreshToken },
|
|
2384
|
+
)
|
|
2318
2385
|
.catch(() => {});
|
|
2319
2386
|
await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {});
|
|
2320
2387
|
return { success: true };
|
|
@@ -2346,11 +2413,28 @@ async function loginViaUi(
|
|
|
2346
2413
|
password: string,
|
|
2347
2414
|
): Promise<LoginAttemptResult> {
|
|
2348
2415
|
try {
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
}
|
|
2353
|
-
|
|
2416
|
+
// 0. If the page is ALREADY logged in, avoid touching /auth altogether!
|
|
2417
|
+
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 2000).catch(() => false))) {
|
|
2418
|
+
return { success: true };
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
try {
|
|
2422
|
+
await page.goto(qwenUrl("/auth"), {
|
|
2423
|
+
waitUntil: "domcontentloaded",
|
|
2424
|
+
timeout: Math.min(config.timeouts.navigation, 15_000),
|
|
2425
|
+
});
|
|
2426
|
+
await sleep(1000);
|
|
2427
|
+
} catch (gotoErr: any) {
|
|
2428
|
+
// If navigation was aborted because the page redirected to / (because already logged in):
|
|
2429
|
+
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 3000).catch(() => false))) {
|
|
2430
|
+
return { success: true };
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// Check if redirect occurred after navigation
|
|
2435
|
+
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 3000).catch(() => false))) {
|
|
2436
|
+
return { success: true };
|
|
2437
|
+
}
|
|
2354
2438
|
|
|
2355
2439
|
// Wait for email input
|
|
2356
2440
|
const emailSelector = [
|
|
@@ -2362,7 +2446,7 @@ async function loginViaUi(
|
|
|
2362
2446
|
].join(", ");
|
|
2363
2447
|
try {
|
|
2364
2448
|
await page.waitForSelector(emailSelector, {
|
|
2365
|
-
timeout: Math.min(config.timeouts.page,
|
|
2449
|
+
timeout: Math.min(config.timeouts.page, 5_000),
|
|
2366
2450
|
});
|
|
2367
2451
|
} catch {
|
|
2368
2452
|
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 3000))) {
|
|
@@ -2379,35 +2463,77 @@ async function loginViaUi(
|
|
|
2379
2463
|
// (supporting English, Portuguese, Spanish, Chinese) to reveal the standard email + password form.
|
|
2380
2464
|
await clearVisibleChallenge(page);
|
|
2381
2465
|
|
|
2382
|
-
const
|
|
2383
|
-
return page
|
|
2384
|
-
|
|
2466
|
+
const isPasswordInputPresent = async () => {
|
|
2467
|
+
return (await page.locator('input[type="password"], input[name="password"]').count().catch(() => 0)) > 0;
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
const revealPasswordMode = async () => {
|
|
2471
|
+
// If already on /forget, navigate back to /auth
|
|
2472
|
+
if (page.url().includes("/forget")) {
|
|
2473
|
+
await page.goto(qwenUrl("/auth"), { waitUntil: "domcontentloaded", timeout: 10_000 }).catch(() => {});
|
|
2474
|
+
await sleep(1000);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// If password input is already in DOM, do NOT click any switch buttons!
|
|
2478
|
+
if (await isPasswordInputPresent()) return;
|
|
2479
|
+
|
|
2480
|
+
// 1. Playwright button locator with multilingual matching (strictly excluding "Esqueci a senha" / "Forgot password")
|
|
2481
|
+
const btn = page
|
|
2482
|
+
.locator("button")
|
|
2385
2483
|
.filter({
|
|
2386
2484
|
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i,
|
|
2387
2485
|
})
|
|
2486
|
+
.filter({
|
|
2487
|
+
hasNotText: /(?:esqueci|forgot|olvid|reset|recover)/i,
|
|
2488
|
+
})
|
|
2388
2489
|
.first();
|
|
2389
|
-
};
|
|
2390
2490
|
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
if (await pwdModeBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
2394
|
-
await pwdModeBtn.click({ timeout: 2000 }).catch(() => {});
|
|
2491
|
+
if (await btn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
2492
|
+
await btn.click({ timeout: 2000 }).catch(() => {});
|
|
2395
2493
|
await sleep(500);
|
|
2396
2494
|
}
|
|
2397
|
-
} catch {}
|
|
2398
2495
|
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2496
|
+
if (await isPasswordInputPresent()) return;
|
|
2497
|
+
|
|
2498
|
+
// 2. In-page evaluate fail-safe to trigger direct DOM click on the password button
|
|
2499
|
+
await page
|
|
2500
|
+
.evaluate(() => {
|
|
2501
|
+
try {
|
|
2502
|
+
const buttons = Array.from(document.querySelectorAll("button, [role='button'], a"));
|
|
2503
|
+
for (const b of buttons) {
|
|
2504
|
+
const txt = ((b as HTMLElement).innerText || "").trim().toLowerCase();
|
|
2505
|
+
if (
|
|
2506
|
+
/(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i.test(txt) &&
|
|
2507
|
+
!txt.includes("esqueci") && !txt.includes("forgot") && !txt.includes("olvid") && !txt.includes("reset")
|
|
2508
|
+
) {
|
|
2509
|
+
(b as HTMLElement).click();
|
|
2510
|
+
return true;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
} catch {}
|
|
2514
|
+
return false;
|
|
2515
|
+
})
|
|
2516
|
+
.catch(() => false);
|
|
2517
|
+
await sleep(500);
|
|
2518
|
+
};
|
|
2402
2519
|
|
|
2403
|
-
|
|
2520
|
+
await revealPasswordMode();
|
|
2521
|
+
|
|
2522
|
+
// Fill email
|
|
2404
2523
|
try {
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2524
|
+
await page.fill(emailSelector, email, { timeout: 8_000 });
|
|
2525
|
+
await sleep(300);
|
|
2526
|
+
} catch {
|
|
2527
|
+
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 3000))) {
|
|
2528
|
+
return { success: true };
|
|
2409
2529
|
}
|
|
2410
|
-
|
|
2530
|
+
throw new Error(`Campo de e-mail não encontrado na tela de autenticação`);
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// If password field is still not visible after typing email, trigger reveal again
|
|
2534
|
+
if (!(await isPasswordInputPresent())) {
|
|
2535
|
+
await revealPasswordMode();
|
|
2536
|
+
}
|
|
2411
2537
|
|
|
2412
2538
|
// In Qwen Web, the password field is present on the same form.
|
|
2413
2539
|
// NEVER press Enter after filling email alone, as Qwen interprets that
|
|
@@ -2432,21 +2558,54 @@ async function loginViaUi(
|
|
|
2432
2558
|
}
|
|
2433
2559
|
|
|
2434
2560
|
// Fill password
|
|
2435
|
-
|
|
2436
|
-
|
|
2561
|
+
try {
|
|
2562
|
+
await page.fill(passwordSelector, password, { timeout: 8_000 });
|
|
2563
|
+
await sleep(500);
|
|
2564
|
+
} catch {
|
|
2565
|
+
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 3000))) {
|
|
2566
|
+
return { success: true };
|
|
2567
|
+
}
|
|
2568
|
+
throw new Error(`Campo de senha não encontrado na tela de autenticação`);
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// Dispatch input/change/blur events so Ant Design / React enables the submit button
|
|
2572
|
+
await page
|
|
2573
|
+
.evaluate(() => {
|
|
2574
|
+
try {
|
|
2575
|
+
const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
|
|
2576
|
+
const passEl = document.querySelector('input[type="password"], input[name="password"]');
|
|
2577
|
+
if (emailEl) {
|
|
2578
|
+
emailEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2579
|
+
emailEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2580
|
+
emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
|
|
2581
|
+
}
|
|
2582
|
+
if (passEl) {
|
|
2583
|
+
passEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2584
|
+
passEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
2585
|
+
passEl.dispatchEvent(new Event("blur", { bubbles: true }));
|
|
2586
|
+
}
|
|
2587
|
+
} catch {}
|
|
2588
|
+
})
|
|
2589
|
+
.catch(() => {});
|
|
2437
2590
|
|
|
2438
|
-
// Prefer clicking the submit button;
|
|
2439
|
-
// The button starts disabled and only enables once both fields are filled.
|
|
2591
|
+
// Prefer clicking the submit button; do NOT press Enter if disabled
|
|
2440
2592
|
const submitSelector =
|
|
2441
2593
|
'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesión"), button:has-text("登录")';
|
|
2442
2594
|
const submitButton = page.locator(submitSelector).first();
|
|
2443
2595
|
try {
|
|
2444
2596
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
2445
|
-
timeout:
|
|
2597
|
+
timeout: 4_000,
|
|
2446
2598
|
});
|
|
2447
2599
|
await submitButton.click();
|
|
2448
2600
|
} catch {
|
|
2449
|
-
await
|
|
2601
|
+
const isDisabled = await submitButton.isDisabled().catch(() => true);
|
|
2602
|
+
if (!isDisabled) {
|
|
2603
|
+
await submitButton.click({ force: true }).catch(() => {});
|
|
2604
|
+
} else {
|
|
2605
|
+
if (!page.url().includes("/auth") && (await isPageLoggedIn(page, 2000))) {
|
|
2606
|
+
return { success: true };
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2450
2609
|
}
|
|
2451
2610
|
// Post-submit verification loop: wait up to 15s for captcha, redirect, cookies, or errors
|
|
2452
2611
|
const postSubmitDeadline = Date.now() + 15_000;
|
|
@@ -3251,6 +3410,18 @@ async function refreshHeadersInternal(
|
|
|
3251
3410
|
};
|
|
3252
3411
|
|
|
3253
3412
|
if (forceReauth) {
|
|
3413
|
+
// If the page is already logged in, do NOT execute destructive password re-login!
|
|
3414
|
+
const alreadyIn = await isPageLoggedIn(page, 2000).catch(() => false);
|
|
3415
|
+
if (alreadyIn) {
|
|
3416
|
+
const liveCookies = await page.context().cookies();
|
|
3417
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
3418
|
+
const cookieStr = liveCookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
3419
|
+
cache.headers.cookie = cookieStr;
|
|
3420
|
+
cache.lastRefresh = Date.now();
|
|
3421
|
+
markAccountHeadersReady(accountId);
|
|
3422
|
+
return;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3254
3425
|
await executeReauth();
|
|
3255
3426
|
} else {
|
|
3256
3427
|
try {
|
|
@@ -3848,21 +4019,53 @@ export async function keepAlivePlaywrightAccount(
|
|
|
3848
4019
|
const now = Date.now();
|
|
3849
4020
|
const currentUrl = page.url();
|
|
3850
4021
|
|
|
3851
|
-
// Proactive session check: if token expires within
|
|
4022
|
+
// Proactive session check: if token expires within 2 minutes, refresh in-page silently
|
|
3852
4023
|
const cookie = await getCookies(accountId);
|
|
3853
|
-
if (cookie && isTokenExpiringSoon(cookie,
|
|
3854
|
-
console.log(
|
|
3855
|
-
`💓 [SessionKeeper] Account ${accountId} token expires within 45m; proactively renewing session...`,
|
|
3856
|
-
);
|
|
4024
|
+
if (cookie && isTokenExpiringSoon(cookie, 2)) {
|
|
3857
4025
|
try {
|
|
3858
|
-
await
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
4026
|
+
await page
|
|
4027
|
+
.evaluate(async () => {
|
|
4028
|
+
await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
|
|
4029
|
+
method: "GET",
|
|
4030
|
+
credentials: "include",
|
|
4031
|
+
}).catch(() => {});
|
|
4032
|
+
await fetch("/api/v1/auths/", {
|
|
4033
|
+
method: "GET",
|
|
4034
|
+
credentials: "include",
|
|
4035
|
+
}).catch(() => {});
|
|
4036
|
+
})
|
|
4037
|
+
.catch(() => {});
|
|
4038
|
+
|
|
4039
|
+
const liveCookies = await page.context().cookies();
|
|
4040
|
+
const tokenCookie = liveCookies.find((c) => c.name === "token");
|
|
4041
|
+
if (tokenCookie) {
|
|
4042
|
+
const cookieStr = liveCookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
4043
|
+
const cache = getHeaderCache(accountId);
|
|
4044
|
+
cache.headers.cookie = cookieStr;
|
|
4045
|
+
cache.lastRefresh = Date.now();
|
|
4046
|
+
markAccountHeadersReady(accountId);
|
|
4047
|
+
lastKeepAliveNavigation.set(accountId, now);
|
|
4048
|
+
touchAccountActivity(accountId);
|
|
4049
|
+
return true;
|
|
4050
|
+
}
|
|
4051
|
+
} catch {}
|
|
4052
|
+
|
|
4053
|
+
// If token refresh probe didn't restore cookies and the page is genuinely logged out:
|
|
4054
|
+
const loggedIn = await isPageLoggedIn(page, 2000).catch(() => false);
|
|
4055
|
+
if (!loggedIn) {
|
|
4056
|
+
console.log(
|
|
4057
|
+
`💓 [SessionKeeper] Account ${accountId} session expired; renewing session...`,
|
|
3865
4058
|
);
|
|
4059
|
+
try {
|
|
4060
|
+
await refreshHeadersInternal(accountId, config.timeouts.headers, true);
|
|
4061
|
+
lastKeepAliveNavigation.set(accountId, now);
|
|
4062
|
+
touchAccountActivity(accountId);
|
|
4063
|
+
return true;
|
|
4064
|
+
} catch (err) {
|
|
4065
|
+
console.warn(
|
|
4066
|
+
`[SessionKeeper] Proactive session renewal failed for ${accountId}: ${getErrorMessage(err)}`,
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
3866
4069
|
}
|
|
3867
4070
|
}
|
|
3868
4071
|
|