import { createHash, createHmac, randomUUID } from "node:crypto" import { secrets } from "../utils/secrets" type PushServerDevicePlatform = "web" | "ios" | "android" export type RegisterPushServerDeviceInput = { localDeviceId: string platform: PushServerDevicePlatform providerToken?: string subscription?: Record meta?: Record } export type RegisterPushServerDeviceResult = { centralDeviceId: string status: string } export type SendPushServerMessageInput = { idempotencyKey: string devices: string[] priority?: "normal" | "high" ttlSeconds?: number collapseKey?: string notification?: { title?: string body?: string } data?: Record } export type CentralServicesHeartbeatInput = { fedeoVersion?: string baseUrl?: string capabilities?: string[] } function configured() { return Boolean(secrets.PUSH_SERVER_URL && secrets.PUSH_SERVER_INSTANCE_ID && secrets.PUSH_SERVER_SECRET) } function normalizeBaseUrl() { return String(secrets.PUSH_SERVER_URL || "").replace(/\/+$/, "") } function bodyHash(body: string) { return createHash("sha256").update(body).digest("hex") } function signature(method: string, path: string, timestamp: string, body: string) { const canonical = [ method.toUpperCase(), path, timestamp, bodyHash(body), secrets.PUSH_SERVER_INSTANCE_ID, ].join("\n") return createHmac("sha256", secrets.PUSH_SERVER_SECRET).update(canonical).digest("hex") } async function requestPushServer(method: "GET" | "POST" | "DELETE", path: string, payload?: unknown): Promise { if (!configured()) { throw new Error("Zentraler Push-Server ist nicht konfiguriert") } const body = payload === undefined ? "" : JSON.stringify(payload) const timestamp = new Date().toISOString() const signedPath = path.split("?")[0] const response = await fetch(`${normalizeBaseUrl()}${path}`, { method, headers: { Accept: "application/json", "Content-Type": "application/json", "X-Fedeo-Instance-Id": secrets.PUSH_SERVER_INSTANCE_ID, "X-Fedeo-Timestamp": timestamp, "X-Fedeo-Signature": signature(method, signedPath, timestamp, body), "X-Fedeo-Request-Id": randomUUID(), }, body: body || undefined, }) const text = await response.text() const data = text ? JSON.parse(text) : null if (!response.ok) { const message = data?.message || data?.error || `Push-Server Anfrage fehlgeschlagen (${response.status})` throw new Error(message) } return data as T } export const pushServerClient = { configured, registerDevice(input: RegisterPushServerDeviceInput) { return requestPushServer("POST", "/v1/devices", input) }, sendPush(input: SendPushServerMessageInput) { return requestPushServer<{ accepted: number; rejected: number; deliveryJobId: string }>("POST", "/v1/push", { priority: "normal", ttlSeconds: 3600, ...input, }) }, } export const centralServicesClient = { configured, heartbeat(input: CentralServicesHeartbeatInput) { return requestPushServer<{ status: string instanceId: string payloadMode: string capabilities: string[] }>("POST", "/v1/instances/heartbeat", input) }, aiChatCompletions(payload: Record) { return requestPushServer("POST", "/v1/services/ai/chat-completions", payload) }, bankingInstitutions(country = "DE") { return requestPushServer("GET", `/v1/services/banking/institutions?country=${encodeURIComponent(country)}`) }, createBankingRequisition(input: { institutionId: string; redirect: string; userLanguage?: string }) { return requestPushServer("POST", "/v1/services/banking/requisitions", input) }, getBankingRequisition(id: string) { return requestPushServer("GET", `/v1/services/banking/requisitions/${encodeURIComponent(id)}`) }, getBankingAccount(id: string) { return requestPushServer("GET", `/v1/services/banking/accounts/${encodeURIComponent(id)}`) }, getBankingBalances(id: string) { return requestPushServer("GET", `/v1/services/banking/accounts/${encodeURIComponent(id)}/balances`) }, getBankingTransactions(id: string) { return requestPushServer("GET", `/v1/services/banking/accounts/${encodeURIComponent(id)}/transactions`) }, }