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 <noreply@anthropic.com>
This commit is contained in:
Kacper
2026-01-09 22:25:06 +01:00
parent 5d0fb08651
commit 254e4f630c

View File

@@ -139,24 +139,29 @@ export class CodexUsageService {
return 'unknown'; return 'unknown';
} }
// Extract plan type from nested OpenAI auth object // Extract plan type from nested OpenAI auth object with type validation
const openaiAuth = claims['https://api.openai.com/auth'] as const openaiAuthClaim = claims['https://api.openai.com/auth'];
| {
chatgpt_plan_type?: string;
chatgpt_subscription_active_until?: string;
}
| undefined;
let accountType: string | undefined; let accountType: string | undefined;
let isSubscriptionExpired = false; let isSubscriptionExpired = false;
if (openaiAuth) { if (
accountType = openaiAuth.chatgpt_plan_type; openaiAuthClaim &&
typeof openaiAuthClaim === 'object' &&
!Array.isArray(openaiAuthClaim)
) {
const openaiAuth = openaiAuthClaim as Record<string, unknown>;
if (typeof openaiAuth.chatgpt_plan_type === 'string') {
accountType = openaiAuth.chatgpt_plan_type;
}
// Check if subscription has expired // 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); const expiryDate = new Date(openaiAuth.chatgpt_subscription_active_until);
isSubscriptionExpired = expiryDate < new Date(); if (!isNaN(expiryDate.getTime())) {
isSubscriptionExpired = expiryDate < new Date();
}
} }
} else { } else {
// Fallback: try top-level claim names // Fallback: try top-level claim names
@@ -168,8 +173,9 @@ export class CodexUsageService {
]; ];
for (const claimName of possibleClaimNames) { for (const claimName of possibleClaimNames) {
if (claims[claimName]) { const claimValue = claims[claimName];
accountType = claims[claimName]; if (claimValue && typeof claimValue === 'string') {
accountType = claimValue;
break; break;
} }
} }
@@ -334,38 +340,16 @@ export class CodexUsageService {
/** /**
* Try to extract usage info from the Codex auth file * Try to extract usage info from the Codex auth file
* Reuses getPlanTypeFromAuthFile to avoid code duplication
*/ */
private async fetchFromAuthFile(): Promise<CodexUsageData | null> { private async fetchFromAuthFile(): Promise<CodexUsageData | null> {
try { try {
const authFilePath = getCodexAuthPath(); const planType = await this.getPlanTypeFromAuthFile();
if (!(await systemPathExists(authFilePath))) { if (planType === 'unknown') {
return null; 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'; const isFreePlan = planType === 'free';
return { return {
@@ -373,7 +357,7 @@ export class CodexUsageService {
planType, planType,
credits: { credits: {
hasCredits: true, hasCredits: true,
unlimited: !isFreePlan && planType !== 'unknown', unlimited: !isFreePlan,
}, },
}, },
lastUpdated: new Date().toISOString(), lastUpdated: new Date().toISOString(),