import "server-only"; import { db } from "@/lib/db"; import { xeroConnections } from "@/lib/schema/xeroConnections"; import { eq } from "drizzle-orm"; /** * Returns a valid Xero access token for the given user. * Refreshes and persists tokens automatically if expired. */ export async function getValidXeroAccessToken(userId: string): Promise { const conn = await db.query.xeroConnections.findFirst({ where: (t, { eq }) => eq(t.userId, userId), }); if (!conn) { throw new Error("No Xero connection"); } const now = Date.now(); // Access token still valid (60s safety buffer) if (now < conn.expiresAt.getTime() - 60_000) { return conn.accessToken; } // Refresh token const res = await fetch("https://identity.xero.com/connect/token", { method: "POST", headers: { Authorization: "Basic " + Buffer.from( `${process.env.XERO_CLIENT_ID}:${process.env.XERO_CLIENT_SECRET}` ).toString("base64"), "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: conn.refreshToken, }), }); if (!res.ok) { const text = await res.text(); throw new Error(`Failed to refresh Xero token: ${text}`); } const tokens: { access_token: string; refresh_token: string; expires_in: number; } = await res.json(); await db .update(xeroConnections) .set({ accessToken: tokens.access_token, refreshToken: tokens.refresh_token, // 🔥 must overwrite expiresAt: new Date(Date.now() + tokens.expires_in * 1000), updatedAt: new Date(), }) .where(eq(xeroConnections.id, conn.id)); return tokens.access_token; }