Files
edr-platform/apps/edr-freight-api/src/modules/eims/eims-client.service.ts
2026-09-02 07:17:01 +03:00

82 lines
2.8 KiB
TypeScript

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 authenticated endpoints (`/v1/register`, `/v1/verify`, …).
*
* Login is not routed through here: `/auth/login` carries no bearer token and lives in
* `EimsAuthService`.
*/
@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<EimsConfig>("eims")!;
}
/**
* Sign `request`, POST it to `path` with the shared protected-endpoint headers, and return the
* parsed response.
* A 401 invalidates the cached token and retries exactly once.
*/
async postSigned<TRequest, TResponse>(path: string, request: TRequest): Promise<TResponse> {
return this.send<TRequest, TResponse>(path, request, false, true);
}
/**
* POST `request` verbatim with the shared protected-endpoint headers, but **not** wrapped in a
* signed envelope. This is the wire contract for verify, cancel and receipt calls.
*/
async postBearer<TRequest, TResponse>(path: string, request: TRequest): Promise<TResponse> {
return this.send<TRequest, TResponse>(path, request, false, false);
}
private async send<TRequest, TResponse>(
path: string,
request: TRequest,
isRetry: boolean,
signed: boolean,
): Promise<TResponse> {
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<TResponse>(`${cfg.baseUrl}${path}`, body, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
apikey: cfg.apiKey,
},
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<TRequest, TResponse>(path, request, true, signed);
}
this.logger.error(mapped.message);
throw mapped;
}
}
}