























@@ -26,6 +26,7 @@ const LOOPBACK_CALLBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
2626const CALLBACK_HOST = resolveCallbackHost();
2727const REDIRECT_URI = resolveRedirectUri(CALLBACK_HOST);
2828const MANUAL_PROMPT_FALLBACK_MS = 15_000;
29+const TOKEN_REQUEST_TIMEOUT_MS = 30_000;
2930const SCOPE = "openid profile email offline_access";
30313132type TokenSuccess = { type: "success"; access: string; refresh: string; expires: number };
@@ -40,6 +41,9 @@ type NodeOAuthRuntime = {
4041randomBytes: typeof import("node:crypto").randomBytes;
4142http: typeof import("node:http");
4243};
44+type TokenRequestOptions = {
45+timeoutMs?: number;
46+};
43474448let nodeOAuthRuntimePromise: Promise<NodeOAuthRuntime> | null = null;
4549@@ -144,14 +148,30 @@ function formatMissingTokenResponseFields(json: TokenResponseJson): string {
144148return missing.join(", ");
145149}
146150147-async function postTokenForm(body: URLSearchParams): Promise<Response> {
151+function formatTokenRequestError(
152+operation: "exchange" | "refresh",
153+error: unknown,
154+timeoutMs: number,
155+): string {
156+if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
157+return `OpenAI Codex token ${operation} timed out after ${timeoutMs}ms`;
158+}
159+return `OpenAI Codex token ${operation} error: ${error instanceof Error ? error.message : String(error)}`;
160+}
161+162+async function postTokenForm(
163+body: URLSearchParams,
164+options: TokenRequestOptions = {},
165+): Promise<Response> {
166+const timeoutMs = options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS;
148167const { response, release } = await fetchWithSsrFGuard({
149168url: TOKEN_URL,
150169init: {
151170method: "POST",
152171headers: { "Content-Type": "application/x-www-form-urlencoded" },
153172 body,
154173},
174+ timeoutMs,
155175auditContext: "openai-codex-oauth-token",
156176});
157177try {
@@ -170,16 +190,27 @@ async function exchangeAuthorizationCode(
170190code: string,
171191verifier: string,
172192redirectUri: string = REDIRECT_URI,
193+options: TokenRequestOptions = {},
173194): Promise<TokenResult> {
174-const response = await postTokenForm(
175-new URLSearchParams({
176-grant_type: "authorization_code",
177-client_id: CLIENT_ID,
178- code,
179-code_verifier: verifier,
180-redirect_uri: redirectUri,
181-}),
182-);
195+const timeoutMs = options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS;
196+let response: Response;
197+try {
198+response = await postTokenForm(
199+new URLSearchParams({
200+grant_type: "authorization_code",
201+client_id: CLIENT_ID,
202+ code,
203+code_verifier: verifier,
204+redirect_uri: redirectUri,
205+}),
206+{ timeoutMs },
207+);
208+} catch (error) {
209+return {
210+type: "failed",
211+message: formatTokenRequestError("exchange", error, timeoutMs),
212+};
213+}
183214184215if (!response.ok) {
185216const text = await response.text().catch(() => "");
@@ -207,14 +238,19 @@ async function exchangeAuthorizationCode(
207238};
208239}
209240210-async function refreshAccessToken(refreshToken: string): Promise<TokenResult> {
241+async function refreshAccessToken(
242+refreshToken: string,
243+options: TokenRequestOptions = {},
244+): Promise<TokenResult> {
211245try {
246+const timeoutMs = options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS;
212247const response = await postTokenForm(
213248new URLSearchParams({
214249grant_type: "refresh_token",
215250refresh_token: refreshToken,
216251client_id: CLIENT_ID,
217252}),
253+{ timeoutMs },
218254);
219255220256if (!response.ok) {
@@ -244,7 +280,11 @@ async function refreshAccessToken(refreshToken: string): Promise<TokenResult> {
244280} catch (error) {
245281return {
246282type: "failed",
247-message: `OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`,
283+message: formatTokenRequestError(
284+"refresh",
285+error,
286+options.timeoutMs ?? TOKEN_REQUEST_TIMEOUT_MS,
287+),
248288};
249289}
250290}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。