import fetch from "node-fetch"; import IClient from "../../../interfaces/Client/IClient"; import { DaprClientOptions } from "../../../types/DaprClientOptions"; import { THTTPExecuteParams } from "../../../types/http/THTTPExecuteParams.type"; /** * HTTP-based Dapr client implementation. * * Provides HTTP/HTTPS communication with the Dapr sidecar using node-fetch. * This is the protocol implementation layer; most applications should use DaprClient instead. * * Uses HTTP connection pooling and keep-alive for efficient resource usage. * Automatically adds User-Agent and API token headers to requests. * * @implements {IClient} * @see {@link DaprClient} for the public-facing unified client * * @internal */ export default class HTTPClient implements IClient { /** * Resolved HTTP client configuration (host, port, protocol, etc.). */ readonly options: DaprClientOptions; /** * Initialization status flag. * * @private */ private isInitialized; /** * Singleton HTTP client (node-fetch) shared across instances. * * @static * @private */ private static client; /** * Base URL for Dapr API v1.0 endpoint (http(s)://host:port/v1.0). * * @private */ private readonly clientUrl; /** * Logger instance for debug/info/error messages. * * @private */ private readonly logger; /** * HTTP connection agent for keep-alive pooling. * * @static * @private */ private static httpAgent; /** * HTTPS connection agent for keep-alive pooling. * * @static * @private */ private static httpsAgent; /** * Resolved HTTP endpoint (host:port with protocol info). * * @private */ private daprEndpoint; /** * Creates a new HTTP Dapr client instance. * * Initializes the node-fetch HTTP client with connection pooling, keep-alive configuration, * and maximum message sizes. Sets up HTTP/HTTPS agents for connection reuse. * * @param options - HTTP client configuration * @param options.daprHost - Sidecar hostname (default: localhost, env: DAPR_HOST) * @param options.daprPort - Sidecar HTTP port (default: 3500, env: DAPR_PORT) * @param options.daprApiToken - API token for authentication (env: DAPR_API_TOKEN) * @param options.isKeepAlive - Enable connection pooling (default: true) * @param options.maxBodySizeMb - Max request body size in MB (default: 4) * @param options.logger - Custom logger instance * @param options.actor - Actor configuration * * @example * ```typescript * const client = new HTTPClient({ * daprHost: "localhost", * daprPort: "3500", * daprApiToken: "secret-token" * }); * await client.start(); * const fetchClient = await client.getClient(); * ``` */ constructor(options: Partial); /** * Resolves the HTTP endpoint URL from configuration or environment variables. * * Priority: * 1. Explicit host and port parameters * 2. Full endpoint URL from DAPR_HTTP_ENDPOINT environment variable * 3. Default host (localhost) and port (3500) * * @private * @param options - Dapr client configuration * @returns Parsed HttpEndpoint with host, port, and protocol * * @internal */ private generateEndpoint; /** * Gets the HTTP client (node-fetch), initializing the sidecar if needed. * * If the client is not initialized and requiresInitialization is true, * automatically starts the sidecar before returning the client. * * @param requiresInitialization - If true, ensures sidecar is started (default: true) * @returns Promise resolving to the node-fetch client * * @throws Rejects if sidecar startup fails * * @internal */ getClient(requiresInitialization?: boolean): Promise; /** * Sets the client initialization status. * * @private * @param isInitialized - Whether the client and sidecar have been initialized * * @internal */ setIsInitialized(isInitialized: boolean): void; /** * Gets the current client initialization status. * * @private * @returns True if the client and sidecar are initialized, false otherwise * * @internal */ getIsInitialized(): boolean; /** * Waits for the HTTP sidecar to be ready for accepting connections. * * Polls the sidecar's health endpoint until it responds successfully, * with exponential backoff and maximum retry timeout. * * @private * @returns Promise resolving once sidecar health check succeeds * * @throws Rejects if sidecar startup timeout is exceeded * * @internal */ _startAwaitSidecarStarted(): Promise; /** * Stops the HTTP client and closes connection pools. * * Destroys the HTTP and HTTPS agents to release pooled connections * and clean up resources. Can be called multiple times safely. * * @returns Promise resolving when agents are destroyed * * @example * ```typescript * const client = new HTTPClient({ daprPort: "3500" }); * await client.start(); * // ... use client ... * await client.stop(); // Closes pooled connections * ``` */ stop(): Promise; /** * Initializes the HTTP client and waits for the sidecar to be ready. * * Performs health checks on the sidecar and sets the initialization flag * once communication is established. Subsequent calls after initialization * are safe but redundant. * * @returns Promise resolving once the sidecar is ready * * @throws Rejects if sidecar startup fails or timeout is exceeded * * @example * ```typescript * const client = new HTTPClient({ daprPort: "3500" }); * await client.start(); // Wait for sidecar to be ready * // Now safe to make API calls * ``` */ start(): Promise; /** * Executes an HTTP request to a specific API version endpoint. * * Allows overriding the default v1.0 API version for calls to alternate endpoint versions. * Internally delegates to execute() with the replaced URL. * * @param apiVersion - API version identifier (e.g., "v1.0", "v2.0") (default: "v1.0") * @param url - Endpoint path relative to the version root (e.g., "/state/mystore") * @param params - HTTP request configuration (method, headers, body, etc.) * @returns Promise resolving to the parsed JSON response or raw text * * @throws Rejects if the HTTP request fails or returns an error status * * @internal */ executeWithApiVersion(apiVersion: string | undefined, url: string, params?: any): Promise; /** * Executes an HTTP request to the Dapr sidecar API. * * Sends HTTP requests to the Dapr API with automatic header injection (API token, User-Agent), * connection pooling, request serialization, and response parsing. * * **Headers automatically added:** * - `dapr-api-token`: Set if API token is configured * - `user-agent`: SDK version and HTTP/1.1 * - `Content-Type`: Inferred from request body if not explicitly provided * * **Connection Pooling:** * Uses HTTP/HTTPS agents with keep-alive to reuse connections, * reducing latency and improving throughput for repeated requests. * * @param url - Target URL (e.g., "http://localhost:3500/v1.0/state/mystore" or "/state/mystore") * If relative, prepended with the configured sidecar URL * @param params - HTTP request configuration * @param params.method - HTTP method (GET, POST, PUT, DELETE, etc.) * @param params.body - Request body (string, object, or Buffer) * @param params.headers - Custom HTTP headers to include * @param requiresInitialization - If false, skips sidecar readiness check (default: true) * * @returns Promise resolving to the API response * - If response Content-Type is JSON: parsed JavaScript object * - Otherwise: raw text response * * @throws Rejects if: * - HTTP status is not in 200-399 range (success/redirect) * - Network error occurs * - URL is malformed * - Body serialization fails * * @example * ```typescript * // Save state * const response = await client.execute("/state/mystore", { * method: "POST", * body: { key: "mykey", value: "myvalue" } * }); * * // Retrieve state * const state = await client.execute("/state/mystore/mykey", { * method: "GET" * }); * * // Publish to pub/sub * await client.execute("/publish/mypubsub/mytopic", { * method: "POST", * body: { message: "hello" } * }); * ``` * * @see {@link https://docs.dapr.io/reference/api/} for Dapr API reference * * @internal */ execute(url: string, params?: THTTPExecuteParams | undefined | null, requiresInitialization?: boolean): Promise; }