import http from 'http'; import { Readable } from 'stream'; import Logger from 'bunyan'; import { HttpOptions, HttpsOptions } from 'agentkeepalive'; import superagent, { Response } from 'superagent'; declare class AgentConfig { timeout: number; constructor(timeout?: number); } type AgentOptions = AgentConfig | HttpOptions | HttpsOptions; interface ApiConfig { url: string; timeout: { response: number; deadline: number; }; /** * Configuration for the keepalive agent created by hmpps-rest-client. */ agent: AgentOptions; } interface User { username: string; token: string; } /** * Represents how we want to authenticate the call: * - tokenType: Are we using a system or user token? * - user: For system calls, we only need username; for user calls, we only need token. */ interface AuthOptions { tokenType: TokenType; user: Partial; } declare enum TokenType { USER_TOKEN = "USER_TOKEN", SYSTEM_TOKEN = "SYSTEM_TOKEN" } /** * An error that may be safe to log as it omits sensitive request headers */ declare class SanitisedError extends Error { text?: string; responseStatus?: number; code?: string; errno?: string | number; headers?: Record; data?: ErrorData; } interface ErrorHandler { (path: string, method: string, error: SanitisedError): Response; } interface ErrorLogger { (path: string, method: string, error: SanitisedError): void; } interface Request { path: string; query?: object | string; headers?: Record; responseType?: string; retries?: number; timeout?: ApiConfig['timeout']; raw?: boolean; errorHandler?: ErrorHandler; retryHandler?: (retry?: boolean) => (err: Error, res: Response) => boolean | undefined; } type JsonBody = { data?: Record | string | Array | undefined; multipartData?: never; files?: never; }; type MultipartBody = { data?: never; multipartData: object | string[]; files: { [key: string]: { buffer: Buffer; originalname: string; }; }; } | { data?: never; multipartData?: never; files: { [key: string]: { buffer: Buffer; originalname: string; }; }; } | { data?: never; multipartData: object | string[]; files?: never; }; type RequestWithBody = Request & { retry?: boolean; } & (JsonBody | MultipartBody); interface StreamRequest { path: string; headers?: Record; errorLogger?: ErrorLogger; timeout?: ApiConfig['timeout']; } interface CallContext { superagent: superagent.SuperAgent; token: string | undefined; agent: http.Agent; } interface Call { (callContex: CallContext): Promise; } interface AuthenticationClient { getToken: (username?: string) => Promise; } /** * Base class for REST API clients. */ declare class RestClient { protected readonly name: string; protected readonly config: ApiConfig; protected readonly logger: Logger | Console; private readonly authenticationClient?; private readonly agent; /** * Creates an instance of RestClient. * * @param name - The name of the API client. * @param config - The API configuration, including URL, timeout, and agent options. * @param logger - A logger instance for logging. * @param authenticationClient - (Optional) The client responsible for retrieving system authentication tokens. */ constructor(name: string, config: ApiConfig, logger: Logger | Console, authenticationClient?: AuthenticationClient | undefined); /** * Creates a keepalive agent based on the API configuration. If the API URL starts with 'https', an HttpsAgent is created; otherwise, an HttpAgent is used. * Proxy information will be read from the environment if present. * * protected to allow clients to override to provide their own agent inst * * @param config * @returns a http agent */ protected createAgent(config: ApiConfig): http.Agent; /** * Retrieves the base API URL. * * @returns The base API URL. */ private apiUrl; /** * Retrieves the timeout configuration. * * @returns The timeout configuration. */ private timeoutConfig; /** * Provides a default mechanism for handling errors * @param path - path of current request * @param method - verb of HTTP request * @param error - the sanitised error * * @returns an optional custom response * @throws an optional error */ protected handleError(path: string, method: string, error: SanitisedError): Response; /** * Provides a default mechanism for logging errors * @param path - path of current request * @param method - verb of HTTP request * @param error - the sanitised error */ protected logError(path: string, method: string, error: SanitisedError): void; private readonly ERROR_CODES; private readonly STATUS_CODES; /** * Returns a retry handler function. * * @param retry - Indicates whether to retry the request. * @returns A function that handles retries. */ private handleRetry; /** * Sends a GET request to the API. * * @param request - The request options including path, query, headers, responseType, and raw flag. * @param authOptions - (Optional) Either an AuthOptions object, a raw JWT string, or undefined for no auth. * @returns The response body or the full response if raw is true. * @throws {SanitisedError} if the request fails. Note that this is the default behaviour, but can be * changed to a different type by specifying a custom errorHandler. */ get({ path, query, headers, responseType, raw, retries, timeout, errorHandler, retryHandler, }: Request, authOptions?: AuthOptions | string): Promise; /** * Sends a request with a body (PATCH, POST, or PUT) to the API. * * @param method - The HTTP method ('patch', 'post', or 'put'). * @param request - The request options including path, query, headers, responseType, data, raw flag, and retry flag. * @param authOptions - (Optional) Either an AuthOptions object, a raw JWT string, or undefined for no auth. * @returns The response body or the full response if raw is true. * @throws {SanitisedError} if the request fails. Note that this is the default behaviour, but can be * changed to a different type by specifying a custom errorHandler. */ private requestWithBody; /** * Sends a PATCH request to the API. * * @param request - The PATCH request options. * @param authOptions - The authentication options. * @returns The response body. * @throws {SanitisedError} if the request fails. Note that this is the default behaviour, but can be * changed to a different type by specifying a custom errorHandler. */ patch(request: RequestWithBody, authOptions?: AuthOptions | string): Promise; /** * Sends a POST request to the API. * * @param request - The POST request options. * @param authOptions - The authentication options. * @returns The response body. * @throws {SanitisedError} if the request fails. Note that this is the default behaviour, but can be * changed to a different type by specifying a custom errorHandler. */ post(request: RequestWithBody, authOptions?: AuthOptions | string): Promise; /** * Sends a PUT request to the API. * * @param request - The PUT request options. * @param authOptions - The authentication options. * @returns The response body. * @throws {SanitisedError} if the request fails. Note that this is the default behaviour, but can be * changed to a different type by specifying a custom errorHandler. */ put(request: RequestWithBody, authOptions?: AuthOptions | string): Promise; /** * Sends a DELETE request to the API. * * @param request - The DELETE request options including path, query, headers, responseType, and raw flag. * @param authOptions - (Optional) Either an AuthOptions object, a raw JWT string, or undefined for no auth. * @returns The response body. * @throws {SanitisedError} if the request fails. Note that this is the default behaviour, but can be * changed to a different type by specifying a custom errorHandler. */ delete({ path, query, headers, responseType, raw, retries, timeout, errorHandler, retryHandler, }: Request, authOptions?: AuthOptions | string): Promise; /** * Streams data from the API. * * @param request - The stream request options including path and headers. * @param authOptions - (Optional) Either an AuthOptions object, a raw JWT string, or undefined for no auth. * @returns A Readable stream containing the response data. If this request fails then the promise is rejected with * type SanitisedError. */ stream({ path, headers, errorLogger, timeout }: StreamRequest, authOptions?: AuthOptions | string): Promise; /** * Make a request using underlying superagent instance, potentially getting a token. * * In an ideal world, this would rarely be used but provides an escape hatch to cater for bespoke usecases. * * @param call - A callback that provides the client a token and the underlying superagent instance. * @param authOptions - The authentication options for this call. * @returns The response body. */ makeRestClientCall(authOptions: AuthOptions | string, call: Call): Promise; /** * Resolves an authentication token from several possible inputs: * 1) A raw JWT string * 2) An AuthOptions object referencing a system or user token * 3) Undefined (no token) * * @param authOptions - AuthOptions, a raw token string, or undefined (no auth). * @returns A promise that resolves to the authentication token or undefined if no auth. * @throws An error if no user token is provided for user-token calls. * @throws An error if no authentication client is provided for system token calls. */ private resolveToken; } /** * Generate authentication options for making a request with a user token. * * Use this function to authenticate API requests on behalf of a specific user. * The provided token represents the user's identity and permissions, ensuring * the request is authorized based on their role/access level. * * @param token - The JWT access token for the user. * @returns AuthOptions - Authentication options configured for a user token. * * @example * restClient.get('/some-api', asUser('jwt_token_here')) */ declare function asUser(token: string): AuthOptions; /** * Generate authentication options for making a request with a system token. * * Use this function to authenticate API requests with a system token. * - If a username is provided, the token will be associated with that user * (useful for auditing or system-initiated actions on behalf of a user). * - If no username is provided, an anonymous system token is used * (useful for service-to-service authorization). * * @param username - (Optional) The username to associate with the system token. * @returns AuthOptions - Authentication options configured for a system token. * * @example * // System request associated with a specific user * restClient.get('/some-api', asSystem('username')) * * @example * // Anonymous system request * restClient.get('/some-api', asSystem()) */ declare function asSystem(username?: string): AuthOptions; export { AgentConfig, RestClient, SanitisedError, asSystem, asUser }; export type { AgentOptions, ApiConfig, AuthOptions, AuthenticationClient };