@azure/msal-node
Version:
Microsoft Authentication Library for Node
173 lines (170 loc) • 7.5 kB
JavaScript
/*! @azure/msal-node v5.0.3 2026-01-28 */
;
import { createAuthError, ClientAuthErrorCodes, createNetworkError } from '@azure/msal-common/node';
import { HttpMethod } from '../utils/Constants.mjs';
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
/**
* HTTP client implementation using Node.js native fetch API.
*
* This class provides a clean interface for making HTTP requests using the modern
* fetch API available in Node.js 18+. It replaces the previous implementation that
* relied on custom proxy handling and the legacy http/https modules.
*/
class HttpClient {
/**
* Sends an HTTP GET request to the specified URL.
*
* This method handles GET requests with optional timeout support. The timeout
* is implemented using AbortController, which provides a clean way to cancel
* fetch requests that take too long to complete.
*
* @param url - The target URL for the GET request
* @param options - Optional request configuration including headers
* @param timeout - Optional timeout in milliseconds. If specified, the request
* will be aborted if it doesn't complete within this time
* @returns Promise that resolves to a NetworkResponse containing headers, body, and status
* @throws {AuthError} When the request times out or response parsing fails
* @throws {NetworkError} When the network request fails
*/
async sendGetRequestAsync(url, options, timeout) {
return this.sendRequest(url, HttpMethod.GET, options, timeout);
}
/**
* Sends an HTTP POST request to the specified URL.
*
* This method handles POST requests with request body support. Currently,
* timeout functionality is not exposed for POST requests, but the underlying
* implementation supports it through the shared sendRequest method.
*
* @param url - The target URL for the POST request
* @param options - Optional request configuration including headers and body
* @returns Promise that resolves to a NetworkResponse containing headers, body, and status
* @throws {AuthError} When the request times out or response parsing fails
* @throws {NetworkError} When the network request fails
*/
async sendPostRequestAsync(url, options) {
return this.sendRequest(url, HttpMethod.POST, options);
}
/**
* Core HTTP request implementation using native fetch API.
*
* This method handles GET and POST HTTP requests with comprehensive
* timeout support and error handling. The timeout mechanism works as follows:
*
* 1. An AbortController is created for each request
* 2. If a timeout is specified, setTimeout is used to call abort() after the delay
* 3. The abort signal is passed to fetch, which will reject the promise if aborted
* 4. Cleanup occurs in both success and error cases to prevent timer leaks
*
* Error handling priority:
* 1. Timeout errors (AbortError) are converted to "Request timeout" messages
* 2. Network/connection errors are wrapped with "Network request failed" prefix
* 3. JSON parsing errors are wrapped with "Failed to parse response" prefix
*
* @param url - The target URL for the request
* @param method - HTTP method (GET or POST)
* @param options - Optional request configuration (headers, body)
* @param timeout - Optional timeout in milliseconds for request cancellation
* @returns Promise resolving to NetworkResponse with parsed JSON body
* @throws {AuthError} For timeouts or JSON parsing errors
* @throws {NetworkError} For network failures
*/
async sendRequest(url, method, options, timeout) {
/*
* Setup timeout mechanism using AbortController
* This provides a standard way to cancel fetch requests
*/
const controller = new AbortController();
let timeoutId;
/*
* Configure timeout if specified
* The setTimeout will trigger abort() if the request takes too long
*/
if (timeout) {
timeoutId = setTimeout(() => {
// Calling abort() will cause fetch to reject with AbortError
controller.abort();
}, timeout);
}
const fetchOptions = {
method: method,
headers: getFetchHeaders(options),
signal: controller.signal, // Enable cancellation via AbortController
};
if (method === HttpMethod.POST) {
fetchOptions.body = options?.body || "";
}
let response;
try {
response = await fetch(url, fetchOptions);
}
catch (error) {
// Clean up timeout to prevent memory leaks
if (timeoutId) {
clearTimeout(timeoutId);
}
if (error instanceof Error && error.name === "AbortError") {
throw createAuthError(ClientAuthErrorCodes.networkError, "Request timeout");
}
const baseAuthError = createAuthError(ClientAuthErrorCodes.networkError, `Network request failed: ${error instanceof Error ? error.message : "unknown"}`);
throw createNetworkError(baseAuthError, undefined, undefined, error instanceof Error ? error : undefined);
}
// Clean up timeout to prevent memory leaks
if (timeoutId) {
clearTimeout(timeoutId);
}
try {
return {
headers: getHeaderDict(response.headers),
body: (await response.json()),
status: response.status,
};
}
catch (error) {
throw createAuthError(ClientAuthErrorCodes.tokenParsingError, `Failed to parse response: ${error instanceof Error ? error.message : "unknown"}`);
}
}
}
/**
* Converts a fetch Headers object to a plain JavaScript object.
*
* The fetch API returns headers as a Headers object with methods like get(), has(),
* etc. However, the rest of the MSAL codebase expects headers as a simple key-value
* object. This function performs that conversion.
*
* @param headers - The Headers object returned by fetch response
* @returns A plain object with header names as keys and values as strings
*/
function getHeaderDict(headers) {
const headerDict = {};
headers.forEach((value, key) => {
headerDict[key] = value;
});
return headerDict;
}
/**
* Converts NetworkRequestOptions headers to a fetch-compatible Headers object.
*
* The MSAL library uses plain objects for headers in NetworkRequestOptions,
* but the fetch API expects either a Headers object, plain object, or array
* of arrays. Using the Headers constructor provides better compatibility
* and validation.
*
* @param options - Optional NetworkRequestOptions containing headers
* @returns A Headers object ready for use with fetch API
*/
function getFetchHeaders(options) {
const headers = new Headers();
if (!(options && options.headers)) {
return headers;
}
Object.entries(options.headers).forEach(([key, value]) => {
headers.append(key, value);
});
return headers;
}
export { HttpClient };
//# sourceMappingURL=HttpClient.mjs.map