























@@ -4,22 +4,11 @@ import { resolveGoogleOAuthIdentity, resolveGooglePersonalOAuthIdentity } from "
44import { isGeminiCliPersonalOAuth } from "./oauth.settings.js";
55import { REDIRECT_URI, TOKEN_URL, type GeminiCliOAuthCredentials } from "./oauth.shared.js";
667-export async function exchangeCodeForTokens(
8-code: string,
9-verifier: string,
10-): Promise<GeminiCliOAuthCredentials> {
11-const { clientId, clientSecret } = resolveOAuthClientConfig();
12-const body = new URLSearchParams({
13-client_id: clientId,
14- code,
15-grant_type: "authorization_code",
16-redirect_uri: REDIRECT_URI,
17-code_verifier: verifier,
18-});
19-if (clientSecret) {
20-body.set("client_secret", clientSecret);
21-}
22-7+async function requestTokenGrant(body: URLSearchParams): Promise<{
8+access_token?: string;
9+refresh_token?: string;
10+expires_in?: number;
11+}> {
2312const response = await fetchWithTimeout(TOKEN_URL, {
2413method: "POST",
2514headers: {
@@ -35,26 +24,110 @@ export async function exchangeCodeForTokens(
3524throw new Error(`Token exchange failed: ${errorText}`);
3625}
372638-const data = (await response.json()) as {
39-access_token: string;
40-refresh_token: string;
41-expires_in: number;
27+return (await response.json()) as {
28+access_token?: string;
29+refresh_token?: string;
30+expires_in?: number;
4231};
32+}
433344-if (!data.refresh_token) {
45-throw new Error("No refresh token received. Please try again.");
34+async function buildGeminiCliCredentials(params: {
35+tokenResponse: {
36+access_token?: string;
37+refresh_token?: string;
38+expires_in?: number;
39+};
40+refreshTokenFallback?: string;
41+existing?: Pick<GeminiCliOAuthCredentials, "email" | "projectId">;
42+}): Promise<GeminiCliOAuthCredentials> {
43+const accessToken = params.tokenResponse.access_token;
44+if (!accessToken) {
45+throw new Error("No access token received. Please try again.");
46+}
47+48+let identity: { email?: string; projectId?: string } = params.existing ?? {};
49+try {
50+if (!identity.email || !identity.projectId) {
51+const discovered = await resolveGeminiCliIdentity(accessToken);
52+identity = {
53+email: identity.email ?? discovered.email,
54+projectId: identity.projectId ?? discovered.projectId,
55+};
56+}
57+} catch {
58+// If identity discovery is temporarily unavailable during refresh, keep the
59+// already-stored identity binding instead of failing token renewal.
4660}
476148-const identity = isGeminiCliPersonalOAuth()
49- ? await resolveGooglePersonalOAuthIdentity(data.access_token)
50- : await resolveGoogleOAuthIdentity(data.access_token);
51-const expiresAt = Date.now() + data.expires_in * 1000 - 5 * 60 * 1000;
62+const expiresInMs =
63+typeof params.tokenResponse.expires_in === "number"
64+ ? params.tokenResponse.expires_in * 1000
65+ : 0;
66+const expiresAt = Date.now() + expiresInMs - 5 * 60 * 1000;
52675368return {
54-refresh: data.refresh_token,
55-access: data.access_token,
69+refresh: params.tokenResponse.refresh_token ?? params.refreshTokenFallback ?? "",
70+access: accessToken,
5671expires: expiresAt,
5772projectId: identity.projectId,
5873email: identity.email,
5974};
6075}
76+77+async function resolveGeminiCliIdentity(
78+accessToken: string,
79+): Promise<{ email?: string; projectId?: string }> {
80+return isGeminiCliPersonalOAuth()
81+ ? await resolveGooglePersonalOAuthIdentity(accessToken)
82+ : await resolveGoogleOAuthIdentity(accessToken);
83+}
84+85+export async function exchangeCodeForTokens(
86+code: string,
87+verifier: string,
88+): Promise<GeminiCliOAuthCredentials> {
89+const { clientId, clientSecret } = resolveOAuthClientConfig();
90+const body = new URLSearchParams({
91+client_id: clientId,
92+ code,
93+grant_type: "authorization_code",
94+redirect_uri: REDIRECT_URI,
95+code_verifier: verifier,
96+});
97+if (clientSecret) {
98+body.set("client_secret", clientSecret);
99+}
100+101+const refreshed = await buildGeminiCliCredentials({
102+tokenResponse: await requestTokenGrant(body),
103+});
104+if (!refreshed.refresh) {
105+throw new Error("No refresh token received. Please try again.");
106+}
107+return refreshed;
108+}
109+110+export async function refreshTokensForGeminiCli(credentials: {
111+refresh: string;
112+email?: string;
113+projectId?: string;
114+}): Promise<GeminiCliOAuthCredentials> {
115+const { clientId, clientSecret } = resolveOAuthClientConfig();
116+const body = new URLSearchParams({
117+client_id: clientId,
118+grant_type: "refresh_token",
119+refresh_token: credentials.refresh,
120+});
121+if (clientSecret) {
122+body.set("client_secret", clientSecret);
123+}
124+125+return await buildGeminiCliCredentials({
126+tokenResponse: await requestTokenGrant(body),
127+refreshTokenFallback: credentials.refresh,
128+existing: {
129+email: credentials.email,
130+projectId: credentials.projectId,
131+},
132+});
133+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。