import { metrics, trace } from "@opentelemetry/api" import { env } from "@/env/server" import { getCacheClient } from "@/services/dataCache" import type { 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 fetchServiceTokenFailCounter = meter.createCounter( "trpc.context.serviceToken.fetch-fail" ) export async function getServiceToken() { const tracer = trace.getTracer("getServiceToken") return await tracer.startActiveSpan("getServiceToken", async () => { let scopes: string[] = [] if (env.ENABLE_BOOKING_FLOW) { scopes = ["profile", "hotel", "booking", "package", "availability"] } else { scopes = ["profile"] } const cacheKey = getServiceTokenCacheKey(scopes) const cacheClient = await getCacheClient() const token = await cacheClient.get>>(cacheKey) console.log("[DEBUG] getServiceToken", typeof token, token) if (!token || token.expiresAt < Date.now()) { return await tracer.startActiveSpan("fetch new token", async () => { const newToken = await getJwt(scopes) const relativeTime = (newToken.expiresAt - Date.now()) / 1000 await cacheClient.set(cacheKey, newToken, relativeTime) return newToken.jwt }) } return token.jwt }) } async function getJwt(scopes: string[]) { fetchServiceTokenCounter.add(1) const jwt = await fetchServiceToken(scopes) const expiresAt = Date.now() + jwt.expires_in * 1000 return { expiresAt, jwt } } 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 } function getServiceTokenCacheKey(scopes: string[]): string { return `serviceToken:${scopes.join(",")}` }