qwenproxy-cli 1.2.9 → 1.3.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.
- package/package.json +3 -3
- package/src/api/server.ts +14 -2
- package/src/core/accounts.ts +118 -17
- package/src/core/local-auth.ts +52 -0
- package/src/core/paths.ts +3 -0
- package/src/login.ts +55 -8
- package/src/routes/upload.ts +51 -12
- package/src/services/auth-playwright.ts +5 -28
- package/src/services/media-generation.ts +51 -11
- package/src/services/playwright.ts +279 -95
- package/src/services/qwen.ts +2 -2
- package/src/sync/codex.ts +154 -65
- package/src/sync/index.ts +7 -2
- package/src/tui/server-manager.ts +6 -0
- package/src/tui/views/accounts-view.ts +54 -6
- package/src/tui/views/logs-view.ts +4 -20
- package/src/tui/views/status-view.ts +496 -3
- package/src/tui/views/sync-view.ts +22 -10
- package/src/utils/jwt.ts +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"reset": "npx tsx src/reset-cooldowns.ts",
|
|
27
27
|
"purge": "npx tsx src/delete-chats.ts",
|
|
28
28
|
"test": "npm run test:mock && npm run test:live",
|
|
29
|
-
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts",
|
|
30
|
-
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts",
|
|
29
|
+
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts",
|
|
30
|
+
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/local-auth.test.ts src/tests/safe-remote-url.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts src/tests/update-cli.test.ts src/tests/batch-accounts.test.ts src/tests/shared-browser.test.ts src/tests/auth-credential-classification.test.ts src/tests/responses-api.test.ts src/tests/media-generation.test.ts src/tests/infer-tool-name.test.ts src/tests/native-session-headers.test.ts src/tests/omni-thinking.test.ts src/tests/protect-stuck-mutex.test.ts src/tests/stream-error-notice.test.ts src/tests/parser-deep-coverage.test.ts src/tests/tui-logs-copy.test.ts src/tests/auth-jwt-and-validation.test.ts src/tests/tui-port-and-key-modal.test.ts",
|
|
31
31
|
"test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
|
|
32
32
|
"benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
package/src/api/server.ts
CHANGED
|
@@ -172,7 +172,13 @@ app.use("*", async (c, next) => {
|
|
|
172
172
|
c.req.path === "/logs" ||
|
|
173
173
|
c.req.path.startsWith("/logs") ||
|
|
174
174
|
c.req.path.startsWith("/diagnostics") ||
|
|
175
|
-
c.req.path === "/favicon.ico"
|
|
175
|
+
c.req.path === "/favicon.ico" ||
|
|
176
|
+
c.req.path === "/v1/models" ||
|
|
177
|
+
c.req.path.startsWith("/v1/models/") ||
|
|
178
|
+
c.req.path === "/models" ||
|
|
179
|
+
c.req.path.startsWith("/models/") ||
|
|
180
|
+
c.req.path === "/v1/chat/mode" ||
|
|
181
|
+
c.req.path === "/chat/mode";
|
|
176
182
|
|
|
177
183
|
if (!isProbe) {
|
|
178
184
|
metrics.increment("requests.total");
|
|
@@ -445,7 +451,13 @@ app.onError((err, c) => {
|
|
|
445
451
|
c.req.path === "/health" ||
|
|
446
452
|
c.req.path === "/metrics" ||
|
|
447
453
|
c.req.path.startsWith("/diagnostics") ||
|
|
448
|
-
c.req.path === "/favicon.ico"
|
|
454
|
+
c.req.path === "/favicon.ico" ||
|
|
455
|
+
c.req.path === "/v1/models" ||
|
|
456
|
+
c.req.path.startsWith("/v1/models/") ||
|
|
457
|
+
c.req.path === "/models" ||
|
|
458
|
+
c.req.path.startsWith("/models/") ||
|
|
459
|
+
c.req.path === "/v1/chat/mode" ||
|
|
460
|
+
c.req.path === "/chat/mode";
|
|
449
461
|
if (!isProbe) {
|
|
450
462
|
metrics.increment("requests.errors");
|
|
451
463
|
}
|
package/src/core/accounts.ts
CHANGED
|
@@ -4,7 +4,8 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { getDatabase } from "./database.ts";
|
|
6
6
|
import { decrypt, encrypt } from "./crypto-utils.ts";
|
|
7
|
-
import { getAccountProfilePath, getProfilesDir } from "./paths.ts";
|
|
7
|
+
import { getAccountProfilePath, getProfilesDir, isRunningUnderNodeTest } from "./paths.ts";
|
|
8
|
+
import { updateEnvVariable } from "./local-auth.ts";
|
|
8
9
|
|
|
9
10
|
export interface QwenAccount {
|
|
10
11
|
id: string;
|
|
@@ -14,6 +15,29 @@ export interface QwenAccount {
|
|
|
14
15
|
cooldown_reason?: string | null;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
export function isPlaceholderAccountEmail(email: string, password?: string): boolean {
|
|
19
|
+
if (!email || typeof email !== "string") return false;
|
|
20
|
+
const clean = email.trim().toLowerCase();
|
|
21
|
+
if (
|
|
22
|
+
clean === "email1@example.com" ||
|
|
23
|
+
clean === "email2@example.com" ||
|
|
24
|
+
clean === "user@example.com" ||
|
|
25
|
+
clean === "test@example.com" ||
|
|
26
|
+
clean === "your-email@example.com" ||
|
|
27
|
+
clean.startsWith("email1@") ||
|
|
28
|
+
clean.startsWith("email2@")
|
|
29
|
+
) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
password &&
|
|
34
|
+
(password === "password1" || password === "password2" || password === "changeme")
|
|
35
|
+
) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
function generateId(email: string): string {
|
|
18
42
|
return crypto
|
|
19
43
|
.createHash("md5")
|
|
@@ -59,10 +83,12 @@ function parseEnvAccounts(): QwenAccount[] {
|
|
|
59
83
|
}
|
|
60
84
|
const email = clean(trimmed.substring(0, colonIdx));
|
|
61
85
|
const password = clean(trimmed.substring(colonIdx + 1));
|
|
62
|
-
if (!email || !password) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
if (!email || !password || isPlaceholderAccountEmail(email, password)) {
|
|
87
|
+
if (!email || !password) {
|
|
88
|
+
console.warn(
|
|
89
|
+
`[Accounts] Invalid QWEN_ACCOUNTS entry at index ${index}: "${trimmed}"`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
66
92
|
return null;
|
|
67
93
|
}
|
|
68
94
|
return {
|
|
@@ -79,6 +105,17 @@ let lastSyncTime = 0;
|
|
|
79
105
|
const SYNC_INTERVAL = 30_000;
|
|
80
106
|
|
|
81
107
|
function syncEnvAccounts(): void {
|
|
108
|
+
const db = getDatabase();
|
|
109
|
+
// Automatically purge any dummy placeholder accounts that may have been seeded from .env.example
|
|
110
|
+
try {
|
|
111
|
+
db.prepare(`
|
|
112
|
+
DELETE FROM accounts
|
|
113
|
+
WHERE lower(email) IN ('email1@example.com', 'email2@example.com', 'user@example.com', 'test@example.com', 'your-email@example.com')
|
|
114
|
+
OR lower(email) LIKE 'email1@%'
|
|
115
|
+
OR lower(email) LIKE 'email2@%'
|
|
116
|
+
`).run();
|
|
117
|
+
} catch {}
|
|
118
|
+
|
|
82
119
|
const envAccounts = process.env.QWEN_ACCOUNTS || "";
|
|
83
120
|
const now = Date.now();
|
|
84
121
|
if (envAccounts === lastSyncedEnv && now - lastSyncTime < SYNC_INTERVAL)
|
|
@@ -90,7 +127,6 @@ function syncEnvAccounts(): void {
|
|
|
90
127
|
const accounts = parseEnvAccounts();
|
|
91
128
|
if (accounts.length === 0) return;
|
|
92
129
|
|
|
93
|
-
const db = getDatabase();
|
|
94
130
|
const upsert = db.prepare(`
|
|
95
131
|
INSERT INTO accounts (id, email, password) VALUES (?, ?, ?)
|
|
96
132
|
ON CONFLICT(email) DO UPDATE SET password = excluded.password, updated_at = datetime('now')
|
|
@@ -124,15 +160,17 @@ function getCachedAccounts(): QwenAccount[] {
|
|
|
124
160
|
)
|
|
125
161
|
.all() as QwenAccount[];
|
|
126
162
|
|
|
127
|
-
accountsCache = rows
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
password =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
163
|
+
accountsCache = rows
|
|
164
|
+
.filter((row) => !isPlaceholderAccountEmail(row.email))
|
|
165
|
+
.map((row) => {
|
|
166
|
+
let password = "";
|
|
167
|
+
try {
|
|
168
|
+
password = decrypt(row.password);
|
|
169
|
+
} catch {
|
|
170
|
+
password = "";
|
|
171
|
+
}
|
|
172
|
+
return { ...row, password };
|
|
173
|
+
});
|
|
136
174
|
accountsCacheTime = now;
|
|
137
175
|
return accountsCache;
|
|
138
176
|
}
|
|
@@ -316,22 +354,85 @@ export function addAccount(
|
|
|
316
354
|
return newAccount;
|
|
317
355
|
}
|
|
318
356
|
|
|
319
|
-
function wipeAccountSessionFiles(id: string): void {
|
|
357
|
+
export function wipeAccountSessionFiles(id: string): void {
|
|
320
358
|
const profilePath = getAccountProfilePath(id);
|
|
321
359
|
try {
|
|
322
360
|
fs.rmSync(profilePath, { recursive: true, force: true });
|
|
323
|
-
} catch {
|
|
361
|
+
} catch (error) {
|
|
362
|
+
const message =
|
|
363
|
+
error instanceof Error ? error.message : String(error ?? "");
|
|
364
|
+
if (
|
|
365
|
+
message.includes("EPERM") ||
|
|
366
|
+
message.includes("EBUSY") ||
|
|
367
|
+
message.includes("Permission denied")
|
|
368
|
+
) {
|
|
369
|
+
try {
|
|
370
|
+
const stalePath = `${profilePath}.stale-${Date.now()}`;
|
|
371
|
+
fs.renameSync(profilePath, stalePath);
|
|
372
|
+
} catch {}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
324
375
|
const siblingState = path.join(getProfilesDir(), `${id}_state.json`);
|
|
325
376
|
try {
|
|
326
377
|
fs.rmSync(siblingState, { force: true });
|
|
327
378
|
} catch {}
|
|
328
379
|
}
|
|
329
380
|
|
|
381
|
+
export function removeAccountFromEnv(emailToRemove: string): void {
|
|
382
|
+
const target = emailToRemove.trim().toLowerCase();
|
|
383
|
+
const rawEnv = process.env.QWEN_ACCOUNTS;
|
|
384
|
+
if (!rawEnv) return;
|
|
385
|
+
|
|
386
|
+
const clean = (s: string) => s.trim().replace(/^[,;\s"'`]+|[,;\s"'`]+$/g, "").trim();
|
|
387
|
+
const lines = rawEnv.split(/[\r\n;]+/);
|
|
388
|
+
const remaining: string[] = [];
|
|
389
|
+
|
|
390
|
+
for (const rawLine of lines) {
|
|
391
|
+
const line = clean(rawLine);
|
|
392
|
+
if (!line) continue;
|
|
393
|
+
|
|
394
|
+
if (line.includes(",") && (line.match(/@/g) || []).length > 1) {
|
|
395
|
+
for (const seg of line.split(",")) {
|
|
396
|
+
const trimmed = clean(seg);
|
|
397
|
+
if (!trimmed) continue;
|
|
398
|
+
const colonIdx = trimmed.indexOf(":");
|
|
399
|
+
const email = clean(colonIdx !== -1 ? trimmed.substring(0, colonIdx) : trimmed);
|
|
400
|
+
if (email.toLowerCase() !== target && !isPlaceholderAccountEmail(email)) {
|
|
401
|
+
remaining.push(trimmed);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
const colonIdx = line.indexOf(":");
|
|
406
|
+
const email = clean(colonIdx !== -1 ? line.substring(0, colonIdx) : line);
|
|
407
|
+
if (email.toLowerCase() !== target && !isPlaceholderAccountEmail(email)) {
|
|
408
|
+
remaining.push(line);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const newEnvValue = remaining.join(",");
|
|
414
|
+
process.env.QWEN_ACCOUNTS = newEnvValue;
|
|
415
|
+
lastSyncedEnv = newEnvValue;
|
|
416
|
+
lastSyncTime = Date.now();
|
|
417
|
+
|
|
418
|
+
if (!isRunningUnderNodeTest()) {
|
|
419
|
+
try {
|
|
420
|
+
updateEnvVariable("QWEN_ACCOUNTS", newEnvValue);
|
|
421
|
+
} catch {}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
330
425
|
export function removeAccount(id: string): boolean {
|
|
331
426
|
const db = getDatabase();
|
|
427
|
+
const account = db
|
|
428
|
+
.prepare("SELECT email FROM accounts WHERE id = ?")
|
|
429
|
+
.get(id) as { email: string } | undefined;
|
|
332
430
|
const result = db.prepare("DELETE FROM accounts WHERE id = ?").run(id);
|
|
333
431
|
if (result.changes > 0) {
|
|
334
432
|
wipeAccountSessionFiles(id);
|
|
433
|
+
if (account?.email) {
|
|
434
|
+
removeAccountFromEnv(account.email);
|
|
435
|
+
}
|
|
335
436
|
}
|
|
336
437
|
invalidateAccountsCache();
|
|
337
438
|
return result.changes > 0;
|
package/src/core/local-auth.ts
CHANGED
|
@@ -43,6 +43,58 @@ export function assertBindAllowed(host: string, apiKey: string): void {
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export function updateEnvVariable(
|
|
47
|
+
key: string,
|
|
48
|
+
value: string,
|
|
49
|
+
envPath = getEnvFilePath(),
|
|
50
|
+
): void {
|
|
51
|
+
try {
|
|
52
|
+
const existing = fs.existsSync(envPath)
|
|
53
|
+
? fs.readFileSync(envPath, "utf-8")
|
|
54
|
+
: "";
|
|
55
|
+
const regex = new RegExp(`^([ \\t]*)${key}[ \\t]*=.*$`, "m");
|
|
56
|
+
const assignment = regex.exec(existing);
|
|
57
|
+
|
|
58
|
+
let updated: string;
|
|
59
|
+
if (assignment) {
|
|
60
|
+
const start = assignment.index;
|
|
61
|
+
const end = start + assignment[0].length;
|
|
62
|
+
updated =
|
|
63
|
+
existing.slice(0, start) +
|
|
64
|
+
`${assignment[1]}${key}=${value}` +
|
|
65
|
+
existing.slice(end);
|
|
66
|
+
} else {
|
|
67
|
+
const lineEnding = existing.includes("\r\n") ? "\r\n" : "\n";
|
|
68
|
+
const prefix =
|
|
69
|
+
existing.length === 0 || existing.endsWith("\n") ? "" : lineEnding;
|
|
70
|
+
updated = `${existing}${prefix}${key}=${value}${lineEnding}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fs.writeFileSync(envPath, updated, { encoding: "utf-8", mode: 0o600 });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.warn(
|
|
76
|
+
`⚠️ [Config] Could not update ${key} in .env:`,
|
|
77
|
+
err instanceof Error ? err.message : String(err),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function persistServerPort(
|
|
83
|
+
port: number,
|
|
84
|
+
envPath = getEnvFilePath(),
|
|
85
|
+
): void {
|
|
86
|
+
updateEnvVariable("PORT", String(port), envPath);
|
|
87
|
+
process.env.PORT = String(port);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function persistCustomApiKey(
|
|
91
|
+
apiKey: string,
|
|
92
|
+
envPath = getEnvFilePath(),
|
|
93
|
+
): void {
|
|
94
|
+
updateEnvVariable("API_KEY", apiKey.trim(), envPath);
|
|
95
|
+
process.env.API_KEY = apiKey.trim();
|
|
96
|
+
}
|
|
97
|
+
|
|
46
98
|
export function persistApiKey(
|
|
47
99
|
apiKey: string,
|
|
48
100
|
envPath = getEnvFilePath(),
|
package/src/core/paths.ts
CHANGED
|
@@ -181,6 +181,9 @@ export function getTuiSettingsPath(customDataDir?: string): string {
|
|
|
181
181
|
* Path to user configuration .env file (either local or in global data dir).
|
|
182
182
|
*/
|
|
183
183
|
export function getEnvFilePath(customDataDir?: string): string {
|
|
184
|
+
if (isRunningUnderNodeTest()) {
|
|
185
|
+
return path.join(customDataDir || getDataDir(), ".env");
|
|
186
|
+
}
|
|
184
187
|
const localEnv = path.resolve(".env");
|
|
185
188
|
if (fs.existsSync(localEnv)) {
|
|
186
189
|
return localEnv;
|
package/src/login.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
1
2
|
import {
|
|
2
3
|
addAccount,
|
|
3
4
|
removeAccount,
|
|
@@ -76,30 +77,76 @@ async function addAccountFlow() {
|
|
|
76
77
|
clear();
|
|
77
78
|
console.log("=== Add New Account ===\n");
|
|
78
79
|
const email = await askQuestion("Email: ");
|
|
79
|
-
if (!email) {
|
|
80
|
+
if (!email || !email.trim()) {
|
|
80
81
|
console.log("Email is required.");
|
|
81
82
|
await askQuestion("Press Enter to continue...");
|
|
82
83
|
return;
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
const password = await askQuestion("Password: ");
|
|
86
|
-
if (!password) {
|
|
87
|
+
if (!password || !password.trim()) {
|
|
87
88
|
console.log("Password is required.");
|
|
88
89
|
await askQuestion("Press Enter to continue...");
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
const trimmedEmail = email.trim();
|
|
94
|
+
const trimmedPassword = password.trim();
|
|
95
|
+
|
|
96
|
+
const existingAccounts = listAccounts();
|
|
97
|
+
if (
|
|
98
|
+
existingAccounts.some(
|
|
99
|
+
(a) => a.email.toLowerCase() === trimmedEmail.toLowerCase(),
|
|
100
|
+
)
|
|
101
|
+
) {
|
|
102
|
+
console.log(
|
|
103
|
+
`\n❌ Error: An account with email "${trimmedEmail}" already exists.`,
|
|
104
|
+
);
|
|
105
|
+
await askQuestion("Press Enter to continue...");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const tempId = crypto.randomUUID();
|
|
110
|
+
console.log(
|
|
111
|
+
`\n⏳ Validating credentials for ${maskEmail(trimmedEmail)} with Qwen via Playwright...`,
|
|
112
|
+
);
|
|
113
|
+
|
|
92
114
|
let account: QwenAccount | null = null;
|
|
93
115
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
const { validateAccountLogin } = await import("./services/playwright.ts");
|
|
117
|
+
const { wipeAccountSessionFiles } = await import("./core/accounts.ts");
|
|
118
|
+
const { clearAccountCooldown } = await import("./core/account-manager.ts");
|
|
119
|
+
const { config } = await import("./core/config.ts");
|
|
120
|
+
|
|
121
|
+
const ok = await validateAccountLogin(
|
|
122
|
+
{ id: tempId, email: trimmedEmail, password: trimmedPassword },
|
|
123
|
+
config.playwright.headless,
|
|
124
|
+
config.playwright.browser,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (ok) {
|
|
128
|
+
account = addAccount(trimmedEmail, trimmedPassword, tempId);
|
|
129
|
+
console.log(
|
|
130
|
+
`\n✅ Account verified and saved: ${maskEmail(account.email)} (${account.id})`,
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
wipeAccountSessionFiles(tempId);
|
|
134
|
+
clearAccountCooldown(tempId);
|
|
135
|
+
console.log(
|
|
136
|
+
`\n❌ Authentication failed: Invalid credentials or unhandled login challenge.`,
|
|
137
|
+
);
|
|
138
|
+
console.log("The account was NOT saved.");
|
|
139
|
+
}
|
|
97
140
|
} catch (err: any) {
|
|
98
|
-
|
|
99
|
-
|
|
141
|
+
const { wipeAccountSessionFiles } = await import("./core/accounts.ts");
|
|
142
|
+
const { clearAccountCooldown } = await import("./core/account-manager.ts");
|
|
143
|
+
wipeAccountSessionFiles(tempId);
|
|
144
|
+
clearAccountCooldown(tempId);
|
|
145
|
+
console.log(`\n❌ Validation error: ${err?.message || String(err)}`);
|
|
146
|
+
console.log("The account was NOT saved.");
|
|
100
147
|
}
|
|
101
148
|
|
|
102
|
-
await askQuestion("
|
|
149
|
+
await askQuestion("\nPress Enter to continue...");
|
|
103
150
|
}
|
|
104
151
|
|
|
105
152
|
async function removeAccountFlow() {
|
package/src/routes/upload.ts
CHANGED
|
@@ -341,21 +341,22 @@ async function getSTSToken(
|
|
|
341
341
|
filesize: number,
|
|
342
342
|
filetype: string,
|
|
343
343
|
headers: Record<string, string>,
|
|
344
|
+
accountId?: string,
|
|
344
345
|
): Promise<STSResponse["data"]> {
|
|
345
|
-
const
|
|
346
|
-
qwenUrl("/api/v2/files/getstsToken"),
|
|
347
|
-
{
|
|
346
|
+
const doFetch = (reqHeaders: Record<string, string>) =>
|
|
347
|
+
fetch(qwenUrl("/api/v2/files/getstsToken"), {
|
|
348
348
|
method: "POST",
|
|
349
349
|
headers: buildQwenRequestHeaders({
|
|
350
|
-
cookie:
|
|
351
|
-
userAgent:
|
|
352
|
-
bxUa:
|
|
353
|
-
bxUmidtoken:
|
|
354
|
-
bxV:
|
|
350
|
+
cookie: reqHeaders.cookie,
|
|
351
|
+
userAgent: reqHeaders["user-agent"],
|
|
352
|
+
bxUa: reqHeaders["bx-ua"],
|
|
353
|
+
bxUmidtoken: reqHeaders["bx-umidtoken"],
|
|
354
|
+
bxV: reqHeaders["bx-v"],
|
|
355
355
|
}),
|
|
356
356
|
body: JSON.stringify({ filename, filesize: String(filesize), filetype }),
|
|
357
|
-
}
|
|
358
|
-
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
let response = await doFetch(headers);
|
|
359
360
|
|
|
360
361
|
if (!response.ok) {
|
|
361
362
|
const errorText = await response.text().catch(() => "");
|
|
@@ -364,8 +365,43 @@ async function getSTSToken(
|
|
|
364
365
|
);
|
|
365
366
|
}
|
|
366
367
|
|
|
367
|
-
|
|
368
|
-
|
|
368
|
+
let data = await response.json().catch(() => null);
|
|
369
|
+
const is401 =
|
|
370
|
+
data?.success === false &&
|
|
371
|
+
(data?.data?.code === "Unauthorized" ||
|
|
372
|
+
(typeof data?.data?.details === "string" &&
|
|
373
|
+
data.data.details.includes("401")));
|
|
374
|
+
|
|
375
|
+
if (is401) {
|
|
376
|
+
try {
|
|
377
|
+
const { refreshHeaders } = await import("../services/playwright.ts");
|
|
378
|
+
const { getBasicHeaders } = await import("../services/auth-playwright.ts");
|
|
379
|
+
const { loadAccounts } = await import("../core/accounts.ts");
|
|
380
|
+
const resolvedId = accountId ?? loadAccounts()[0]?.id;
|
|
381
|
+
if (resolvedId) {
|
|
382
|
+
console.warn(
|
|
383
|
+
`[Upload] STS token 401 — refreshing session with re-auth and retrying...`,
|
|
384
|
+
);
|
|
385
|
+
await refreshHeaders(resolvedId, undefined, true);
|
|
386
|
+
const fresh = await getBasicHeaders(resolvedId);
|
|
387
|
+
headers.cookie = fresh.cookie;
|
|
388
|
+
headers["user-agent"] = fresh.userAgent;
|
|
389
|
+
headers["bx-v"] = fresh.bxV;
|
|
390
|
+
if (fresh.bxUa) headers["bx-ua"] = fresh.bxUa;
|
|
391
|
+
if (fresh.bxUmidtoken) headers["bx-umidtoken"] = fresh.bxUmidtoken;
|
|
392
|
+
response = await doFetch(headers);
|
|
393
|
+
if (response.ok) {
|
|
394
|
+
data = await response.json().catch(() => null);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch (refreshErr) {
|
|
398
|
+
console.warn(
|
|
399
|
+
`[Upload] Re-auth for STS token failed: ${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!data?.success || !data?.data) {
|
|
369
405
|
throw new Error(
|
|
370
406
|
`STS token invalid: ${JSON.stringify(data).substring(0, 200)}`,
|
|
371
407
|
);
|
|
@@ -617,6 +653,7 @@ export async function processImagesForQwen(
|
|
|
617
653
|
file_url?: { url: string };
|
|
618
654
|
}>,
|
|
619
655
|
headers: Record<string, string>,
|
|
656
|
+
accountId?: string,
|
|
620
657
|
): Promise<{ text: string; files: QwenFileEntry[] }> {
|
|
621
658
|
const textParts: string[] = [];
|
|
622
659
|
const files: QwenFileEntry[] = [];
|
|
@@ -654,6 +691,7 @@ export async function processImagesForQwen(
|
|
|
654
691
|
fileSize,
|
|
655
692
|
typeInfo.qwenFileType,
|
|
656
693
|
headers,
|
|
694
|
+
accountId,
|
|
657
695
|
);
|
|
658
696
|
fileUrl = await uploadToOSS(remoteMedia.buffer, stsData, filename);
|
|
659
697
|
fileId = stsData.file_id;
|
|
@@ -686,6 +724,7 @@ export async function processImagesForQwen(
|
|
|
686
724
|
fileSize,
|
|
687
725
|
typeInfo.qwenFileType,
|
|
688
726
|
headers,
|
|
727
|
+
accountId,
|
|
689
728
|
);
|
|
690
729
|
fileUrl = await uploadToOSS(buffer, stsData, filename);
|
|
691
730
|
fileId = stsData.file_id;
|
|
@@ -94,36 +94,13 @@ export async function getBasicHeaders(accountId?: string): Promise<{
|
|
|
94
94
|
return getPlaywrightBasicHeaders(resolvedAccountId);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
minutesBeforeExpiry = 5,
|
|
100
|
-
): boolean {
|
|
101
|
-
const tokenMatch = cookie.match(/token=([^;]+)/);
|
|
102
|
-
if (!tokenMatch) return false;
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const token = decodeURIComponent(tokenMatch[1]);
|
|
106
|
-
const segments = token.split(".");
|
|
107
|
-
// Some Qwen deployments use opaque cookies. Treating those as expired
|
|
108
|
-
// forces expensive header capture on every personalization request.
|
|
109
|
-
if (segments.length !== 3 || !segments[1]) return false;
|
|
110
|
-
|
|
111
|
-
const payloadJson = Buffer.from(segments[1], "base64url").toString("utf-8");
|
|
112
|
-
const payload = JSON.parse(payloadJson);
|
|
113
|
-
const exp = payload.exp;
|
|
114
|
-
if (typeof exp !== "number" || !Number.isFinite(exp)) return false;
|
|
115
|
-
|
|
116
|
-
const nowSec = Math.floor(Date.now() / 1000);
|
|
117
|
-
const thresholdSec = minutesBeforeExpiry * 60;
|
|
118
|
-
return exp - nowSec < thresholdSec;
|
|
119
|
-
} catch {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
97
|
+
import { parseJwtExpiry, isTokenExpiringSoon } from "../utils/jwt.ts";
|
|
98
|
+
export { parseJwtExpiry, isTokenExpiringSoon };
|
|
123
99
|
|
|
124
100
|
export async function getQwenHeaders(
|
|
125
101
|
forceNew = false,
|
|
126
102
|
accountId?: string,
|
|
103
|
+
forceReauth = false,
|
|
127
104
|
): Promise<HeaderResult> {
|
|
128
105
|
if (isAuthMockEnabled()) {
|
|
129
106
|
const basic = await getBasicHeaders(accountId);
|
|
@@ -150,8 +127,8 @@ export async function getQwenHeaders(
|
|
|
150
127
|
|
|
151
128
|
await ensurePlaywrightInitialized(resolvedAccountId);
|
|
152
129
|
|
|
153
|
-
if (forceNew) {
|
|
154
|
-
await refreshHeaders(resolvedAccountId);
|
|
130
|
+
if (forceNew || forceReauth) {
|
|
131
|
+
await refreshHeaders(resolvedAccountId, config.timeouts.headers, forceReauth);
|
|
155
132
|
}
|
|
156
133
|
|
|
157
134
|
const basic = await getPlaywrightBasicHeaders(resolvedAccountId);
|