import { metrics } from "@opentelemetry/api" import { revalidateTag, unstable_cache } from "next/cache" import { env } from "@/env/server" import { generateServiceTokenTag } from "@/utils/generateTag" import { ServiceTokenResponse } from "@/types/tokens" // OpenTelemetry metrics: Service token const meter = metrics.getMeter("trpc.context.serviceToken") const fetchServiceTokenCounter = meter.createCounter( "trpc.context.serviceToken.fetch-new-token" ) const fetchTempServiceTokenCounter = meter.createCounter( "trpc.context.serviceToken.fetch-temporary" ) const fetchServiceTokenFailCounter = meter.createCounter( "trpc.context.serviceToken.fetch-fail" ) async function fetchServiceToken(scopes: string[]) { fetchServiceTokenCounter.add(1) const response = await fetch(`${env.CURITY_ISSUER_USER}/oauth/v2/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: env.CURITY_CLIENT_ID_SERVICE, client_secret: env.CURITY_CLIENT_SECRET_SERVICE, scope: scopes.join(" "), }), }) if (!response.ok) { const text = await response.text() const error = { status: response.status, statusText: response.statusText, text, } fetchServiceTokenFailCounter.add(1, { error_type: "http_error", error: JSON.stringify(error), }) console.error( "fetchServiceToken error", JSON.stringify({ query: { grant_type: "client_credentials", client_id: env.CURITY_CLIENT_ID_SERVICE, scope: scopes.join(" "), }, error, }) ) throw new Error( `[fetchServiceToken] Failed to obtain service token: ${JSON.stringify(error)}` ) } return response.json() as Promise } export async function getServiceToken() { let scopes: string[] = [] if (env.HIDE_FOR_NEXT_RELEASE) { scopes = ["profile"] } else { scopes = ["profile", "hotel", "booking"] } const tag = generateServiceTokenTag(scopes) const getCachedJwt = unstable_cache( async (scopes) => { const jwt = await fetchServiceToken(scopes) const expiresAt = Date.now() + jwt.expires_in * 1000 return { expiresAt, jwt } }, [tag], { tags: [tag] } ) const cachedJwt = await getCachedJwt(scopes) if (cachedJwt.expiresAt < Date.now()) { console.log( "trpc.context.serviceToken: Service token expired, revalidating tag" ) revalidateTag(tag) console.log( "trpc.context.serviceToken: Fetching new temporary service token." ) fetchTempServiceTokenCounter.add(1) const newToken = await fetchServiceToken(scopes) return newToken } return cachedJwt.jwt }