From 254e4f630c3481ac1a3bf486ec56c66420beeb00 Mon Sep 17 00:00:00 2001 From: Kacper Date: Fri, 9 Jan 2026 22:25:06 +0100 Subject: [PATCH] fix: address PR review comments for type safety - Add typeof checks for fallback claim values to prevent runtime errors - Make openaiAuth parsing more robust with proper type validation - Add isNaN check for date parsing to handle invalid dates - Refactor fetchFromAuthFile to reuse getPlanTypeFromAuthFile (DRY) Co-Authored-By: Claude Opus 4.5 --- .../src/services/codex-usage-service.ts | 62 +++++++------------ 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/apps/server/src/services/codex-usage-service.ts b/apps/server/src/services/codex-usage-service.ts index 97f21e2b..bf8aff99 100644 --- a/apps/server/src/services/codex-usage-service.ts +++ b/apps/server/src/services/codex-usage-service.ts @@ -139,24 +139,29 @@ export class CodexUsageService { return 'unknown'; } - // Extract plan type from nested OpenAI auth object - const openaiAuth = claims['https://api.openai.com/auth'] as - | { - chatgpt_plan_type?: string; - chatgpt_subscription_active_until?: string; - } - | undefined; + // Extract plan type from nested OpenAI auth object with type validation + const openaiAuthClaim = claims['https://api.openai.com/auth']; let accountType: string | undefined; let isSubscriptionExpired = false; - if (openaiAuth) { - accountType = openaiAuth.chatgpt_plan_type; + if ( + openaiAuthClaim && + typeof openaiAuthClaim === 'object' && + !Array.isArray(openaiAuthClaim) + ) { + const openaiAuth = openaiAuthClaim as Record; + + if (typeof openaiAuth.chatgpt_plan_type === 'string') { + accountType = openaiAuth.chatgpt_plan_type; + } // Check if subscription has expired - if (openaiAuth.chatgpt_subscription_active_until) { + if (typeof openaiAuth.chatgpt_subscription_active_until === 'string') { const expiryDate = new Date(openaiAuth.chatgpt_subscription_active_until); - isSubscriptionExpired = expiryDate < new Date(); + if (!isNaN(expiryDate.getTime())) { + isSubscriptionExpired = expiryDate < new Date(); + } } } else { // Fallback: try top-level claim names @@ -168,8 +173,9 @@ export class CodexUsageService { ]; for (const claimName of possibleClaimNames) { - if (claims[claimName]) { - accountType = claims[claimName]; + const claimValue = claims[claimName]; + if (claimValue && typeof claimValue === 'string') { + accountType = claimValue; break; } } @@ -334,38 +340,16 @@ export class CodexUsageService { /** * Try to extract usage info from the Codex auth file + * Reuses getPlanTypeFromAuthFile to avoid code duplication */ private async fetchFromAuthFile(): Promise { try { - const authFilePath = getCodexAuthPath(); + const planType = await this.getPlanTypeFromAuthFile(); - if (!(await systemPathExists(authFilePath))) { + if (planType === 'unknown') { return null; } - const authContent = await systemPathReadFile(authFilePath); - const authData = JSON.parse(authContent); - - if (!authData.tokens?.id_token) { - return null; - } - - const claims = this.parseJwt(authData.tokens.id_token); - if (!claims) { - return null; - } - - const accountType = claims?.['https://chatgpt.com/account_type']; - - // Normalize to our plan types - let planType: CodexPlanType = 'unknown'; - if (accountType) { - const normalizedType = accountType.toLowerCase(); - if (['free', 'plus', 'pro', 'team', 'enterprise', 'edu'].includes(normalizedType)) { - planType = normalizedType as CodexPlanType; - } - } - const isFreePlan = planType === 'free'; return { @@ -373,7 +357,7 @@ export class CodexUsageService { planType, credits: { hasCredits: true, - unlimited: !isFreePlan && planType !== 'unknown', + unlimited: !isFreePlan, }, }, lastUpdated: new Date().toISOString(),