// Auteur : Simon-Pierre Boucher — contact@spboucher.ai // ----------------------------------------------------------------------------- // Profil membre lu depuis le HUB Groupe KA (groupe-ka.com). // Le hub est LA source de vérité du profil (bio, ville, emploi, entreprise, // site web, réseaux sociaux, photo, statut, profil public) — l'édition se // fait sur groupe-ka.com/compte, Vrai-Prix ne fait qu'afficher. // GET {hub}/api/sso/profile?client_id=vrai-prix&ka_id=…&ts=…&sig=… // avec sig = HMAC-SHA256(KA_SSO_SECRET, "vrai-prix..") en hex // (même secret que le SSO). Cache mémoire 60 s (les 404 aussi — cache // négatif) ; null sur toute erreur (réseau, 401, 5xx, vieux compte non // relié) → l'appelant retombe sur les données locales de session. // Aucune dépendance : node:crypto + fetch natif seulement. // ----------------------------------------------------------------------------- import { createHmac } from "node:crypto"; import { CLIENT_ID, KA_HUB_URL } from "@/lib/ka-auth"; const CACHE_TTL_MS = 60_000; // 60 s const TIMEOUT_MS = 5_000; // 5 s export type HubSocials = { instagram?: string; facebook?: string; x?: string; linkedin?: string; tiktok?: string; youtube?: string; }; /** Réponse de GET /api/sso/profile du hub (200). */ export type HubProfile = { ka_id: string; name: string; email: string; picture: string; role: string; role_label: string; bio: string; city: string; phone: string; website: string; job_title: string; company: string; birth_date: string; age: number | null; socials: HubSocials; public: boolean; public_url: string; created_at: string | number; }; // Cache mémoire process-wide (Map globale — survit au HMR en dev) : // 200 → profil ; 404 → null (cache négatif). Erreurs transitoires : pas de cache. type CacheEntry = { at: number; data: HubProfile | null }; const g = globalThis as unknown as { __vpHubProfileCache?: Map }; const cache: Map = g.__vpHubProfileCache ?? (g.__vpHubProfileCache = new Map()); /** Profil du membre au hub Groupe KA, ou null (inconnu ou injoignable). */ export async function fetchHubProfile(kaId: string): Promise { const secret = process.env.KA_SSO_SECRET; if (!secret || !kaId) return null; const now = Date.now(); const hit = cache.get(kaId); if (hit && now - hit.at < CACHE_TTL_MS) return hit.data; const ts = Math.floor(now / 1000); const sig = createHmac("sha256", secret) .update(`${CLIENT_ID}.${kaId}.${ts}`) .digest("hex"); const url = `${KA_HUB_URL}/api/sso/profile?client_id=${encodeURIComponent(CLIENT_ID)}` + `&ka_id=${encodeURIComponent(kaId)}&ts=${ts}&sig=${sig}`; const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS); try { const r = await fetch(url, { signal: ctrl.signal, cache: "no-store" }); if (r.status === 404) { cache.set(kaId, { at: now, data: null }); // cache négatif 60 s return null; } if (!r.ok) return null; // transitoire (401, 5xx…) : pas de cache const data = (await r.json()) as unknown; if (typeof data !== "object" || data === null) return null; const profile = data as HubProfile; cache.set(kaId, { at: now, data: profile }); return profile; } catch { return null; // réseau / timeout / JSON : pas de cache } finally { clearTimeout(timer); } }