@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9
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 +88 -88
- package/dist/opencode-anthropic-auth-cli.mjs +804 -507
- package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
- package/package.json +67 -59
- package/src/__tests__/billing-edge-cases.test.ts +59 -59
- package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
- package/src/__tests__/cc-comparison.test.ts +87 -87
- package/src/__tests__/cc-credentials.test.ts +254 -250
- package/src/__tests__/cch-drift-checker.test.ts +51 -51
- package/src/__tests__/cch-native-style.test.ts +56 -56
- package/src/__tests__/debug-gating.test.ts +42 -42
- package/src/__tests__/decomposition-smoke.test.ts +68 -68
- package/src/__tests__/fingerprint-regression.test.ts +575 -566
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
- package/src/__tests__/helpers/conversation-history.ts +119 -119
- package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
- package/src/__tests__/helpers/deferred.ts +69 -69
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
- package/src/__tests__/helpers/in-memory-storage.ts +88 -88
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
- package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
- package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
- package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
- package/src/__tests__/helpers/sse.ts +209 -209
- package/src/__tests__/index.parallel.test.ts +605 -595
- package/src/__tests__/sanitization-regex.test.ts +112 -112
- package/src/__tests__/state-bounds.test.ts +90 -90
- package/src/account-identity.test.ts +197 -192
- package/src/account-identity.ts +69 -67
- package/src/account-state.test.ts +86 -86
- package/src/account-state.ts +25 -25
- package/src/accounts/matching.test.ts +335 -0
- package/src/accounts/matching.ts +167 -0
- package/src/accounts/persistence.test.ts +345 -0
- package/src/accounts/persistence.ts +432 -0
- package/src/accounts/repair.test.ts +276 -0
- package/src/accounts/repair.ts +407 -0
- package/src/accounts.dedup.test.ts +621 -621
- package/src/accounts.test.ts +933 -929
- package/src/accounts.ts +633 -989
- package/src/backoff.test.ts +345 -345
- package/src/backoff.ts +219 -219
- package/src/betas.ts +124 -124
- package/src/bun-fetch.test.ts +345 -342
- package/src/bun-fetch.ts +424 -424
- package/src/bun-proxy.test.ts +25 -25
- package/src/bun-proxy.ts +209 -209
- package/src/cc-credentials.ts +111 -111
- package/src/circuit-breaker.test.ts +184 -184
- package/src/circuit-breaker.ts +169 -169
- package/src/cli/commands/auth.ts +963 -0
- package/src/cli/commands/config.ts +547 -0
- package/src/cli/formatting.test.ts +406 -0
- package/src/cli/formatting.ts +219 -0
- package/src/cli.ts +255 -2022
- package/src/commands/handlers/betas.ts +100 -0
- package/src/commands/handlers/config.ts +99 -0
- package/src/commands/handlers/files.ts +375 -0
- package/src/commands/oauth-flow.ts +181 -166
- package/src/commands/prompts.ts +61 -61
- package/src/commands/router.test.ts +421 -0
- package/src/commands/router.ts +143 -635
- package/src/config.test.ts +482 -482
- package/src/config.ts +412 -404
- package/src/constants.ts +48 -48
- package/src/drift/cch-constants.ts +95 -95
- package/src/env.ts +111 -105
- package/src/headers/billing.ts +33 -33
- package/src/headers/builder.ts +130 -130
- package/src/headers/cch.ts +75 -75
- package/src/headers/stainless.ts +25 -25
- package/src/headers/user-agent.ts +23 -23
- package/src/index.ts +436 -828
- package/src/models.ts +27 -27
- package/src/oauth.test.ts +102 -102
- package/src/oauth.ts +178 -178
- package/src/parent-pid-watcher.test.ts +148 -148
- package/src/parent-pid-watcher.ts +69 -69
- package/src/plugin-helpers.ts +82 -82
- package/src/refresh-helpers.ts +145 -139
- package/src/refresh-lock.test.ts +94 -94
- package/src/refresh-lock.ts +93 -93
- package/src/request/body.history.test.ts +579 -571
- package/src/request/body.ts +255 -255
- package/src/request/metadata.ts +65 -65
- package/src/request/retry.test.ts +156 -156
- package/src/request/retry.ts +67 -67
- package/src/request/url.ts +21 -21
- package/src/request-orchestration-helpers.ts +648 -0
- package/src/response/index.ts +5 -5
- package/src/response/mcp.ts +58 -58
- package/src/response/streaming.test.ts +313 -311
- package/src/response/streaming.ts +412 -410
- package/src/rotation.test.ts +304 -301
- package/src/rotation.ts +205 -205
- package/src/storage.test.ts +547 -547
- package/src/storage.ts +315 -291
- package/src/system-prompt/builder.ts +38 -38
- package/src/system-prompt/index.ts +5 -5
- package/src/system-prompt/normalize.ts +60 -60
- package/src/system-prompt/sanitize.ts +30 -30
- package/src/thinking.ts +21 -20
- package/src/token-refresh.test.ts +265 -265
- package/src/token-refresh.ts +219 -214
- package/src/types.ts +30 -30
- package/dist/bun-proxy.mjs +0 -291
package/src/plugin-helpers.ts
CHANGED
|
@@ -12,101 +12,101 @@ import type { OpenCodeClient } from "./token-refresh.js";
|
|
|
12
12
|
const DEBOUNCE_TOAST_MAP_MAX_SIZE = 50;
|
|
13
13
|
|
|
14
14
|
export interface PluginHelperDeps {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- OpenCode plugin client API boundary; accepts arbitrary extension methods
|
|
16
|
+
client: OpenCodeClient & Record<string, any>;
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- plugin config accepts forward-compatible arbitrary keys
|
|
18
|
+
config: AnthropicAuthConfig & Record<string, any>;
|
|
19
|
+
debugLog: (...args: unknown[]) => void;
|
|
20
|
+
getAccountManager: () => AccountManager | null;
|
|
21
|
+
setAccountManager: (accountManager: AccountManager | null) => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export function createPluginHelpers({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
client,
|
|
26
|
+
config,
|
|
27
|
+
debugLog,
|
|
28
|
+
getAccountManager,
|
|
29
|
+
setAccountManager,
|
|
30
30
|
}: PluginHelperDeps) {
|
|
31
|
-
|
|
31
|
+
const debouncedToastTimestamps = new Map<string, number>();
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
async function reloadAccountManagerFromDisk() {
|
|
41
|
-
if (!getAccountManager()) return;
|
|
42
|
-
setAccountManager(await AccountManager.load(config, null));
|
|
43
|
-
}
|
|
33
|
+
async function sendCommandMessage(sessionID: string, text: string) {
|
|
34
|
+
await client.session?.prompt({
|
|
35
|
+
path: { id: sessionID },
|
|
36
|
+
body: { noReply: true, parts: [{ type: "text", text, ignored: true }] },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
}
|
|
40
|
+
async function reloadAccountManagerFromDisk() {
|
|
41
|
+
if (!getAccountManager()) return;
|
|
42
|
+
setAccountManager(await AccountManager.load(config, null));
|
|
43
|
+
}
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const { main: cliMain } = await import("./cli.js");
|
|
58
|
-
code = await cliMain(argv, {
|
|
59
|
-
io: {
|
|
60
|
-
log: (...args: unknown[]) => logs.push(args.map(String).join(" ")),
|
|
61
|
-
error: (...args: unknown[]) => errors.push(args.map(String).join(" ")),
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
} catch (err) {
|
|
65
|
-
errors.push(err instanceof Error ? err.message : String(err));
|
|
45
|
+
async function persistOpenCodeAuth(refresh: string, access: string | undefined, expires: number | undefined) {
|
|
46
|
+
await client.auth?.set({
|
|
47
|
+
path: { id: "anthropic" },
|
|
48
|
+
body: { type: "oauth", refresh, access, expires },
|
|
49
|
+
});
|
|
66
50
|
}
|
|
67
|
-
return {
|
|
68
|
-
code,
|
|
69
|
-
stdout: stripAnsi(logs.join("\n")).trim(),
|
|
70
|
-
stderr: stripAnsi(errors.join("\n")).trim(),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
51
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
debouncedToastTimestamps.size >= DEBOUNCE_TOAST_MAP_MAX_SIZE
|
|
89
|
-
) {
|
|
90
|
-
const oldestKey = debouncedToastTimestamps.keys().next().value;
|
|
91
|
-
if (oldestKey !== undefined) debouncedToastTimestamps.delete(oldestKey);
|
|
52
|
+
async function runCliCommand(argv: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
53
|
+
const logs: string[] = [];
|
|
54
|
+
const errors: string[] = [];
|
|
55
|
+
let code = 1;
|
|
56
|
+
try {
|
|
57
|
+
const { main: cliMain } = await import("./cli.js");
|
|
58
|
+
code = await cliMain(argv, {
|
|
59
|
+
io: {
|
|
60
|
+
log: (...args: unknown[]) => logs.push(args.map(String).join(" ")),
|
|
61
|
+
error: (...args: unknown[]) => errors.push(args.map(String).join(" ")),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
92
66
|
}
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
return {
|
|
68
|
+
code,
|
|
69
|
+
stdout: stripAnsi(logs.join("\n")).trim(),
|
|
70
|
+
stderr: stripAnsi(errors.join("\n")).trim(),
|
|
71
|
+
};
|
|
95
72
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
73
|
+
|
|
74
|
+
async function toast(
|
|
75
|
+
message: string,
|
|
76
|
+
variant: "info" | "success" | "warning" | "error" = "info",
|
|
77
|
+
options: { debounceKey?: string } = {},
|
|
78
|
+
) {
|
|
79
|
+
if (config.toasts.quiet && variant !== "error") return;
|
|
80
|
+
if (variant !== "error" && options.debounceKey) {
|
|
81
|
+
const minGapMs = Math.max(0, config.toasts.debounce_seconds) * 1000;
|
|
82
|
+
if (minGapMs > 0) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const lastAt = debouncedToastTimestamps.get(options.debounceKey) ?? 0;
|
|
85
|
+
if (now - lastAt < minGapMs) return;
|
|
86
|
+
if (
|
|
87
|
+
!debouncedToastTimestamps.has(options.debounceKey) &&
|
|
88
|
+
debouncedToastTimestamps.size >= DEBOUNCE_TOAST_MAP_MAX_SIZE
|
|
89
|
+
) {
|
|
90
|
+
const oldestKey = debouncedToastTimestamps.keys().next().value;
|
|
91
|
+
if (oldestKey !== undefined) debouncedToastTimestamps.delete(oldestKey);
|
|
92
|
+
}
|
|
93
|
+
debouncedToastTimestamps.set(options.debounceKey, now);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await client.tui?.showToast({ body: { message, variant } });
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (!(err instanceof TypeError)) debugLog("toast failed:", err);
|
|
100
|
+
}
|
|
100
101
|
}
|
|
101
|
-
}
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
return {
|
|
104
|
+
toast,
|
|
105
|
+
sendCommandMessage,
|
|
106
|
+
runCliCommand,
|
|
107
|
+
reloadAccountManagerFromDisk,
|
|
108
|
+
persistOpenCodeAuth,
|
|
109
|
+
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export type PluginHelpers = ReturnType<typeof createPluginHelpers>;
|
package/src/refresh-helpers.ts
CHANGED
|
@@ -6,164 +6,170 @@ import { markTokenStateUpdated, readDiskAccountAuth, refreshAccountToken } from
|
|
|
6
6
|
type RefreshSource = "foreground" | "idle";
|
|
7
7
|
|
|
8
8
|
type RefreshInFlightEntry = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
promise: Promise<string>;
|
|
10
|
+
source: RefreshSource;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export interface RefreshDeps {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
client: OpenCodeClient;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- plugin config accepts forward-compatible arbitrary keys
|
|
16
|
+
config: AnthropicAuthConfig & Record<string, any>;
|
|
17
|
+
getAccountManager: () => AccountManager | null;
|
|
18
|
+
debugLog: (...args: unknown[]) => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function createRefreshHelpers({ client, config, getAccountManager, debugLog }: RefreshDeps) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const refreshInFlight = new Map<string, RefreshInFlightEntry>();
|
|
23
|
+
const idleRefreshLastAttempt = new Map<string, number>();
|
|
24
|
+
const idleRefreshInFlight = new Set<string>();
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const IDLE_REFRESH_ENABLED = config.idle_refresh.enabled;
|
|
27
|
+
const IDLE_REFRESH_WINDOW_MS = config.idle_refresh.window_minutes * 60 * 1000;
|
|
28
|
+
const IDLE_REFRESH_MIN_INTERVAL_MS = config.idle_refresh.min_interval_minutes * 60 * 1000;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
30
|
+
function parseRefreshFailure(refreshError: unknown) {
|
|
31
|
+
const message = refreshError instanceof Error ? refreshError.message : String(refreshError);
|
|
32
|
+
const status =
|
|
33
|
+
typeof refreshError === "object" && refreshError && "status" in refreshError
|
|
34
|
+
? Number((refreshError as Record<string, unknown>).status)
|
|
35
|
+
: NaN;
|
|
36
|
+
const errorCode =
|
|
37
|
+
typeof refreshError === "object" && refreshError && ("errorCode" in refreshError || "code" in refreshError)
|
|
38
|
+
? String(
|
|
39
|
+
(refreshError as Record<string, unknown>).errorCode ||
|
|
40
|
+
(refreshError as Record<string, unknown>).code ||
|
|
41
|
+
"",
|
|
42
|
+
)
|
|
43
|
+
: "";
|
|
44
|
+
const msgLower = message.toLowerCase();
|
|
45
|
+
const isInvalidGrant =
|
|
46
|
+
errorCode === "invalid_grant" || errorCode === "invalid_request" || msgLower.includes("invalid_grant");
|
|
47
|
+
const isTerminalStatus = status === 400 || status === 401 || status === 403;
|
|
48
|
+
return { message, status, errorCode, isInvalidGrant, isTerminalStatus };
|
|
49
|
+
}
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
async function refreshAccountTokenSingleFlight(
|
|
52
|
+
account: ManagedAccount,
|
|
53
|
+
source: RefreshSource = "foreground",
|
|
54
|
+
): Promise<string> {
|
|
55
|
+
const key = account.id;
|
|
56
|
+
const existing = refreshInFlight.get(key);
|
|
57
|
+
if (existing) {
|
|
58
|
+
if (source === "foreground" && existing.source === "idle") {
|
|
59
|
+
try {
|
|
60
|
+
await existing.promise;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
void err;
|
|
63
|
+
}
|
|
64
|
+
if (account.access && account.expires && account.expires > Date.now()) return account.access;
|
|
65
|
+
const retried = refreshInFlight.get(key);
|
|
66
|
+
if (retried && retried !== existing) {
|
|
67
|
+
return retried.promise;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
return existing.promise;
|
|
71
|
+
}
|
|
66
72
|
}
|
|
67
|
-
} else {
|
|
68
|
-
return existing.promise;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
return await refreshAccountToken(account, client, source, {
|
|
79
|
-
onTokensUpdated: async () => {
|
|
74
|
+
const entry: RefreshInFlightEntry = {
|
|
75
|
+
source,
|
|
76
|
+
promise: Promise.resolve(""),
|
|
77
|
+
};
|
|
78
|
+
const p = (async () => {
|
|
80
79
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
return await refreshAccountToken(account, client, source, {
|
|
81
|
+
onTokensUpdated: async () => {
|
|
82
|
+
try {
|
|
83
|
+
await getAccountManager()!.saveToDisk();
|
|
84
|
+
} catch {
|
|
85
|
+
getAccountManager()!.requestSaveToDisk();
|
|
86
|
+
throw new Error("save failed, debounced retry scheduled");
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
debugLog,
|
|
90
|
+
});
|
|
91
|
+
} finally {
|
|
92
|
+
if (refreshInFlight.get(key) === entry) refreshInFlight.delete(key);
|
|
85
93
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
})();
|
|
93
|
-
entry.promise = p;
|
|
94
|
-
refreshInFlight.set(key, entry);
|
|
95
|
-
return p;
|
|
96
|
-
}
|
|
94
|
+
})();
|
|
95
|
+
entry.promise = p;
|
|
96
|
+
refreshInFlight.set(key, entry);
|
|
97
|
+
return p;
|
|
98
|
+
}
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
try {
|
|
105
|
-
await refreshAccountTokenSingleFlight(account, "idle");
|
|
106
|
-
return;
|
|
107
|
-
} catch (err) {
|
|
108
|
-
let details = parseRefreshFailure(err);
|
|
109
|
-
if (!(details.isInvalidGrant || details.isTerminalStatus)) {
|
|
110
|
-
debugLog("idle refresh skipped after transient failure", {
|
|
111
|
-
accountIndex: account.index,
|
|
112
|
-
status: details.status,
|
|
113
|
-
errorCode: details.errorCode,
|
|
114
|
-
message: details.message,
|
|
115
|
-
});
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const diskAuth = await readDiskAccountAuth(account.id);
|
|
119
|
-
const retryToken = diskAuth?.refreshToken;
|
|
120
|
-
if (retryToken && retryToken !== attemptedRefreshToken && account.refreshToken === attemptedRefreshToken) {
|
|
121
|
-
account.refreshToken = retryToken;
|
|
122
|
-
if (diskAuth?.tokenUpdatedAt) account.tokenUpdatedAt = diskAuth.tokenUpdatedAt;
|
|
123
|
-
else markTokenStateUpdated(account);
|
|
124
|
-
}
|
|
100
|
+
async function refreshIdleAccount(account: ManagedAccount) {
|
|
101
|
+
if (!getAccountManager()) return;
|
|
102
|
+
if (idleRefreshInFlight.has(account.id)) return;
|
|
103
|
+
idleRefreshInFlight.add(account.id);
|
|
104
|
+
const attemptedRefreshToken = account.refreshToken;
|
|
125
105
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
106
|
+
try {
|
|
107
|
+
await refreshAccountTokenSingleFlight(account, "idle");
|
|
108
|
+
return;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
let details = parseRefreshFailure(err);
|
|
111
|
+
if (!(details.isInvalidGrant || details.isTerminalStatus)) {
|
|
112
|
+
debugLog("idle refresh skipped after transient failure", {
|
|
113
|
+
accountIndex: account.index,
|
|
114
|
+
status: details.status,
|
|
115
|
+
errorCode: details.errorCode,
|
|
116
|
+
message: details.message,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const diskAuth = await readDiskAccountAuth(account.id);
|
|
121
|
+
const retryToken = diskAuth?.refreshToken;
|
|
122
|
+
if (
|
|
123
|
+
retryToken &&
|
|
124
|
+
retryToken !== attemptedRefreshToken &&
|
|
125
|
+
account.refreshToken === attemptedRefreshToken
|
|
126
|
+
) {
|
|
127
|
+
account.refreshToken = retryToken;
|
|
128
|
+
if (diskAuth?.tokenUpdatedAt) account.tokenUpdatedAt = diskAuth.tokenUpdatedAt;
|
|
129
|
+
else markTokenStateUpdated(account);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await refreshAccountTokenSingleFlight(account, "idle");
|
|
133
|
+
} catch (retryErr) {
|
|
134
|
+
details = parseRefreshFailure(retryErr);
|
|
135
|
+
debugLog("idle refresh retry failed", {
|
|
136
|
+
accountIndex: account.index,
|
|
137
|
+
status: details.status,
|
|
138
|
+
errorCode: details.errorCode,
|
|
139
|
+
message: details.message,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
idleRefreshInFlight.delete(account.id);
|
|
135
145
|
}
|
|
136
|
-
}
|
|
137
|
-
} finally {
|
|
138
|
-
idleRefreshInFlight.delete(account.id);
|
|
139
146
|
}
|
|
140
|
-
}
|
|
141
147
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
function maybeRefreshIdleAccounts(activeAccount: ManagedAccount) {
|
|
149
|
+
const accountManager = getAccountManager();
|
|
150
|
+
if (!IDLE_REFRESH_ENABLED || !accountManager) return;
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
const excluded = new Set([activeAccount.index]);
|
|
153
|
+
const candidates = accountManager
|
|
154
|
+
.getEnabledAccounts(excluded)
|
|
155
|
+
.filter((acc) => !acc.expires || acc.expires <= now + IDLE_REFRESH_WINDOW_MS)
|
|
156
|
+
.filter((acc) => {
|
|
157
|
+
const last = idleRefreshLastAttempt.get(acc.id) ?? 0;
|
|
158
|
+
return now - last >= IDLE_REFRESH_MIN_INTERVAL_MS;
|
|
159
|
+
})
|
|
160
|
+
.sort((a, b) => (a.expires ?? 0) - (b.expires ?? 0));
|
|
161
|
+
const target = candidates[0];
|
|
162
|
+
if (!target) return;
|
|
163
|
+
idleRefreshLastAttempt.set(target.id, now);
|
|
164
|
+
void refreshIdleAccount(target);
|
|
165
|
+
}
|
|
160
166
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
return {
|
|
168
|
+
parseRefreshFailure,
|
|
169
|
+
refreshAccountTokenSingleFlight,
|
|
170
|
+
refreshIdleAccount,
|
|
171
|
+
maybeRefreshIdleAccounts,
|
|
172
|
+
};
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
export type RefreshHelpers = ReturnType<typeof createRefreshHelpers>;
|