import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs" import { Temporal } from "temporal-polyfill" import { CONFIG_DIR, TOKENS_PATH } from "./config" import type { Sb1Tokens } from "./types" type StoredTokens = Sb1Tokens & { accessTokenCreated: number refreshTokenCreated: number } export function loadTokens(): StoredTokens | null { if (!existsSync(TOKENS_PATH)) return null return JSON.parse(readFileSync(TOKENS_PATH, "utf8")) } export function saveTokens(tokens: Sb1Tokens): void { mkdirSync(CONFIG_DIR, { recursive: true }) const now = Temporal.Now.instant().epochMilliseconds const stored: StoredTokens = { ...tokens, accessTokenCreated: now, refreshTokenCreated: now } writeFileSync(TOKENS_PATH, JSON.stringify(stored, null, 2)) } export async function getAccessToken(clientId: string, clientSecret: string): Promise { const stored = loadTokens() if (!stored) throw new Error("Not authenticated. Run `sb1-actual auth` first.") const now = Temporal.Now.instant() const accessExpiry = Temporal.Instant.fromEpochMilliseconds(stored.accessTokenCreated) .add({ seconds: stored.expires_in }) if (Temporal.Instant.compare(now, accessExpiry) < 0) { return stored.access_token } const refreshExpiry = Temporal.Instant.fromEpochMilliseconds(stored.refreshTokenCreated) .add({ seconds: stored.refresh_token_expires_in }) if (Temporal.Instant.compare(now, refreshExpiry) >= 0) { throw new Error("Session expired. Run `sb1-actual auth` again.") } return refreshAccessToken(stored.refresh_token, clientId, clientSecret) } async function refreshAccessToken(refreshToken: string, clientId: string, clientSecret: string): Promise { console.log("Refreshing access token...") const params = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, refresh_token: refreshToken, grant_type: "refresh_token", }) const res = await fetch("https://api.sparebank1.no/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params, }) if (!res.ok) throw new Error(`Token refresh failed: ${await res.text()}`) const tokens = await res.json() as Sb1Tokens saveTokens(tokens) return tokens.access_token }