import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; import { TokenManager } from "../token-manager/token-manager"; import { BROKER_EVENTS } from "../broker/broker-events"; import { PrimariaBroker } from "../broker/primaria-broker"; import { jwtDecode } from "jwt-decode"; const mpidHeader = "x-catsalut-mpid"; const obtainMpid = (token: string) => { const mpid = (jwtDecode(token) as any).access_info?.mpi_patient_id; return mpid; }; export class MissingMpidHeaderError extends Error { constructor(message: string) { super(message); this.name = "MissingMpidHeaderError"; } } export class InvalidMpidHeaderError extends Error { constructor(message: string) { super(message); this.name = "InvalidMpidHeaderError"; } } const validateMpidHeader = (response: AxiosResponse, tokenManager: TokenManager): boolean => { const mpidHeaderValue = response.headers[mpidHeader]; if (!mpidHeaderValue) { throw new MissingMpidHeaderError("Mpid header is missing"); } if (mpidHeaderValue !== obtainMpid(tokenManager.getToken())) { throw new InvalidMpidHeaderError("Mpid header value is invalid"); } return true; }; export const createAxiosInstance = ( tokenManager: TokenManager, broker: PrimariaBroker, validateMpid: boolean = false, ) => { const instance = axios.create(); instance.interceptors.request.use((config) => { config.headers.Authorization = `Bearer ${tokenManager.getToken()}`; return config; }); instance.interceptors.response.use( (response: AxiosResponse) => { try { if (validateMpid) validateMpidHeader(response, tokenManager); return response; } catch (error) { if (error instanceof InvalidMpidHeaderError) { broker.publish(BROKER_EVENTS.shell.mpidHeaderInvalid, { request: response.config, }); } return Promise.reject(error); } }, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { const newToken = await tokenManager.refreshToken(); originalRequest.headers.Authorization = `Bearer ${newToken}`; return instance(originalRequest); } catch (refreshError) { console.error("Error refreshing token:", refreshError); return Promise.reject(error); } } return Promise.reject(error); }, ); return instance; }; let instance; //TODO: Test concurrent request that get intercepted by expired token and // An option is to use subscribers to enque unauth requests and only refresh one token and retry the others with the new token export interface HttpClient { request, D = any>(config: AxiosRequestConfig): Promise; } export const createHttpClient = ( tokenManager: TokenManager, broker: PrimariaBroker, ): HttpClient => { if (!instance) { instance = createAxiosInstance(tokenManager, broker); } return { request: instance.request }; };