Files
codexbar-mobile/lib/api/claudeApi.ts
T
space 2c31be11c4
CodexBar Mobile Build / build-android (push) Successful in 21m9s
CodexBar Mobile Build / release (push) Successful in 11s
by codex: feat. General Improvements
2026-06-25 14:27:58 +02:00

128 lines
3.9 KiB
TypeScript

import type { ClaudeAuth, ClaudeOrg, ClaudeUsageResponse } from "@/types/claude";
const BASE_URL = "https://claude.ai/api";
const BROWSER_UA =
"Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36";
function buildHeaders(auth: ClaudeAuth): Record<string, string> {
const cookies = [`sessionKey=${auth.sessionKey}`];
if (auth.lastActiveOrg) cookies.push(`lastActiveOrg=${auth.lastActiveOrg}`);
return {
Cookie: cookies.join("; "),
Accept: "application/json, text/plain, */*",
"Accept-Language": "en-US,en;q=0.9",
"User-Agent": BROWSER_UA,
Referer: "https://claude.ai/",
Origin: "https://claude.ai",
"anthropic-client-platform": "web_claude_to_cc_migration",
};
}
function isClaudeWindow(value: unknown): boolean {
if (!value || typeof value !== "object") return false;
const window = value as Record<string, unknown>;
return (
typeof window.utilization === "number" &&
Number.isFinite(window.utilization) &&
(window.resets_at === undefined ||
window.resets_at === null ||
typeof window.resets_at === "string")
);
}
export function parseClaudeOrgsResponse(data: unknown): ClaudeOrg[] {
if (
!Array.isArray(data) ||
data.some(
(org) =>
!org ||
typeof org !== "object" ||
typeof (org as Record<string, unknown>).uuid !== "string" ||
typeof (org as Record<string, unknown>).name !== "string"
)
) {
throw new Error("INVALID_CLAUDE_ORGS_RESPONSE");
}
return data.map((org) => {
const value = org as Record<string, unknown>;
return { uuid: value.uuid as string, name: value.name as string };
});
}
export function parseClaudeUsageResponse(data: unknown): ClaudeUsageResponse {
if (!data || typeof data !== "object") {
throw new Error("INVALID_CLAUDE_RESPONSE");
}
const value = data as Record<string, unknown>;
if (
!isClaudeWindow(value.five_hour) ||
!isClaudeWindow(value.seven_day) ||
(value.seven_day_sonnet !== undefined &&
value.seven_day_sonnet !== null &&
!isClaudeWindow(value.seven_day_sonnet)) ||
(value.seven_day_opus !== undefined &&
value.seven_day_opus !== null &&
!isClaudeWindow(value.seven_day_opus))
) {
throw new Error("INVALID_CLAUDE_RESPONSE");
}
const toWindow = (window: unknown) => {
const item = window as Record<string, unknown>;
return {
utilization: item.utilization as number,
...(typeof item.resets_at === "string"
? { resets_at: item.resets_at }
: {}),
};
};
return {
five_hour: toWindow(value.five_hour),
seven_day: toWindow(value.seven_day),
...(value.seven_day_sonnet
? { seven_day_sonnet: toWindow(value.seven_day_sonnet) }
: {}),
...(value.seven_day_opus
? { seven_day_opus: toWindow(value.seven_day_opus) }
: {}),
};
}
export async function fetchClaudeOrgs(auth: ClaudeAuth): Promise<ClaudeOrg[]> {
const response = await fetch(`${BASE_URL}/organizations`, {
headers: buildHeaders(auth),
credentials: "omit",
});
if (!response.ok) {
await response.text().catch(() => null);
if (response.status === 401 || response.status === 403) throw new Error("TOKEN_EXPIRED");
throw new Error(`HTTP_ERROR_${response.status}`);
}
const data: unknown = await response.json();
return parseClaudeOrgsResponse(data);
}
export async function fetchClaudeUsage(
auth: ClaudeAuth,
orgUuid: string
): Promise<ClaudeUsageResponse> {
const response = await fetch(`${BASE_URL}/organizations/${orgUuid}/usage`, {
headers: buildHeaders(auth),
credentials: "omit",
});
if (!response.ok) {
await response.text().catch(() => null);
if (response.status === 401 || response.status === 403) throw new Error("TOKEN_EXPIRED");
throw new Error(`HTTP_ERROR_${response.status}`);
}
const data: unknown = await response.json();
return parseClaudeUsageResponse(data);
}