import type { PopoConfig, PopoToken } from "./types.js"; import { resolvePopoCredentials } from "./accounts.js"; // Token cache let cachedToken: PopoToken | null = null; let cachedAppKey: string | null = null; /** * Get a valid access token, refreshing if necessary. */ export async function getAccessToken(cfg: PopoConfig): Promise { const creds = resolvePopoCredentials(cfg); if (!creds) { throw new Error("POPO credentials not configured (appKey, appSecret required)"); } const now = Date.now(); // Check if we have a valid cached token if ( cachedToken && cachedAppKey === creds.appKey && cachedToken.accessExpiredAt > now + 60000 // 1 minute buffer ) { return cachedToken.accessToken; } // Check if we can use refresh token if ( cachedToken && cachedAppKey === creds.appKey && cachedToken.refreshExpiredAt > now + 60000 ) { try { cachedToken = await refreshAccessToken(cfg, cachedToken.refreshToken); return cachedToken.accessToken; } catch { // Fall through to get a new token } } // Get a new token cachedToken = await fetchNewToken(cfg); cachedAppKey = creds.appKey; return cachedToken.accessToken; } /** * Fetch a new token using appKey and appSecret. */ async function fetchNewToken(cfg: PopoConfig): Promise { const creds = resolvePopoCredentials(cfg); if (!creds) { throw new Error("POPO credentials not configured"); } const response = await fetch(`${creds.server}/auth/token`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ appKey: creds.appKey, appSecret: creds.appSecret, }), }); if (!response.ok) { throw new Error(`POPO token request failed: ${response.status} ${response.statusText}`); } const data = (await response.json()) as { code?: number; message?: string; result?: { accessToken: string; accessExpiredAt: number; refreshToken: string; refreshExpiredAt: number; }; }; if (data.code !== 200 || !data.result) { throw new Error(`POPO token request failed: ${data.message || "unknown error"}`); } return { accessToken: data.result.accessToken, accessExpiredAt: data.result.accessExpiredAt, refreshToken: data.result.refreshToken, refreshExpiredAt: data.result.refreshExpiredAt, }; } /** * Refresh an access token using a refresh token. */ export async function refreshAccessToken( cfg: PopoConfig, refreshToken: string ): Promise { const creds = resolvePopoCredentials(cfg); if (!creds) { throw new Error("POPO credentials not configured"); } const response = await fetch(`${creds.server}/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ appKey: creds.appKey, refreshToken, }), }); if (!response.ok) { throw new Error(`POPO token refresh failed: ${response.status} ${response.statusText}`); } const data = (await response.json()) as { code?: number; message?: string; result?: { accessToken: string; accessExpiredAt: number; refreshToken: string; refreshExpiredAt: number; }; }; if (data.code !== 200 || !data.result) { throw new Error(`POPO token refresh failed: ${data.message || "unknown error"}`); } return { accessToken: data.result.accessToken, accessExpiredAt: data.result.accessExpiredAt, refreshToken: data.result.refreshToken, refreshExpiredAt: data.result.refreshExpiredAt, }; } /** * Clear the token cache. */ export function clearTokenCache() { cachedToken = null; cachedAppKey = null; }