import { HttpService } from "@nestjs/axios"; import { Injectable, Logger } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { firstValueFrom } from "rxjs"; import { EimsConfig } from "../../config/eims.config"; import { EimsAuthService } from "./eims-auth.service"; import { EimsSignerService, toSignedBody } from "./eims-signer.service"; import { toEimsApiException } from "./eims.errors"; /** * Foundation for EIMS's bearer-authenticated endpoints (`/v1/register`, `/v1/verify`, …). * * Login is not routed through here: `/auth/login` carries no bearer token and lives in * `EimsAuthService`. Nothing calls `postSigned` yet — invoice registration is a later phase. */ @Injectable() export class EimsClientService { private readonly logger = new Logger(EimsClientService.name); constructor( private readonly http: HttpService, private readonly config: ConfigService, private readonly auth: EimsAuthService, private readonly signer: EimsSignerService, ) {} private get cfg(): EimsConfig { return this.config.get("eims")!; } /** * Sign `request`, POST it to `path` with a valid bearer token, and return the parsed response. * A 401 invalidates the cached token and retries exactly once. */ async postSigned(path: string, request: TRequest): Promise { return this.send(path, request, false, true); } /** * POST `request` verbatim — bearer-authenticated but **not** wrapped in a signed envelope. * * `/v1/verify` is the only endpoint observed to work this way: the supplied collection sends a * raw `{"irn":"…"}` body with no `signature`/`certificate` siblings. Kept as its own entry point * so that if the live gateway turns out to require signing after all, exactly one call site * changes — `postSigned` is already the alternative. */ async postBearer(path: string, request: TRequest): Promise { return this.send(path, request, false, false); } private async send( path: string, request: TRequest, isRetry: boolean, signed: boolean, ): Promise { const cfg = this.cfg; const token = await this.auth.getValidAccessToken(); const body = signed ? toSignedBody(this.signer.signRequest(request)) : request; try { const res = await firstValueFrom( this.http.post(`${cfg.baseUrl}${path}`, body, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, timeout: cfg.httpTimeoutMs, }), ); return res.data; } catch (err) { const mapped = toEimsApiException(err, `POST ${path}`); if (mapped.kind === "AUTH" && !isRetry) { this.logger.warn(`EIMS rejected the token on ${path}; re-authenticating once`); this.auth.invalidate(); return this.send(path, request, true, signed); } this.logger.error(mapped.message); throw mapped; } } }