import * as plugins from './plugins.js'; import * as types from './types.js'; import { SmartResponse } from './response.js'; // Keep-alive agents for connection pooling const httpAgent = new plugins.agentkeepalive({ keepAlive: true, maxFreeSockets: 10, maxSockets: 100, maxTotalSockets: 1000, }); const httpAgentKeepAliveFalse = new plugins.agentkeepalive({ keepAlive: false, }); const httpsAgent = new plugins.agentkeepalive.HttpsAgent({ keepAlive: true, maxFreeSockets: 10, maxSockets: 100, maxTotalSockets: 1000, }); const httpsAgentKeepAliveFalse = new plugins.agentkeepalive.HttpsAgent({ keepAlive: false, }); /** * Tests if a URL is a unix socket */ export const isUnixSocket = (url: string): boolean => { const unixRegex = /^(http:\/\/|https:\/\/|)unix:/; return unixRegex.test(url); }; /** * Parses socket path and route from unix socket URL */ export const parseUnixSocketUrl = (url: string): { socketPath: string; path: string } => { const parseRegex = /(.*):(.*)/; const result = parseRegex.exec(url); return { socketPath: result[1], path: result[2], }; }; /** * Core request function that handles all HTTP/HTTPS requests */ export async function coreRequest( urlArg: string, optionsArg: types.ICoreRequestOptions = {}, requestDataFunc: ((req: plugins.http.ClientRequest) => void) | null = null ): Promise { const done = plugins.smartpromise.defer(); // No defaults - let users explicitly set options to match fetch behavior // Parse URL const parsedUrl = plugins.smarturl.Smarturl.createFromUrl(urlArg, { searchParams: optionsArg.queryParams || {}, }); optionsArg.hostname = parsedUrl.hostname; if (parsedUrl.port) { optionsArg.port = parseInt(parsedUrl.port, 10); } optionsArg.path = parsedUrl.path; // Handle unix socket URLs if (isUnixSocket(urlArg)) { const { socketPath, path } = parseUnixSocketUrl(optionsArg.path); optionsArg.socketPath = socketPath; optionsArg.path = path; } // Determine agent based on protocol and keep-alive setting if (!optionsArg.agent) { // Only use keep-alive agents if explicitly requested if (optionsArg.keepAlive === true) { optionsArg.agent = parsedUrl.protocol === 'https:' ? httpsAgent : httpAgent; } else if (optionsArg.keepAlive === false) { optionsArg.agent = parsedUrl.protocol === 'https:' ? httpsAgentKeepAliveFalse : httpAgentKeepAliveFalse; } // If keepAlive is undefined, don't set any agent (more fetch-like behavior) } // Determine request module const requestModule = parsedUrl.protocol === 'https:' ? plugins.https : plugins.http; if (!requestModule) { throw new Error(`The request to ${urlArg} is missing a viable protocol. Must be http or https`); } // Perform the request const request = requestModule.request(optionsArg, async (response) => { // Handle hard timeout if (optionsArg.hardDataCuttingTimeout) { setTimeout(() => { response.destroy(); done.reject(new Error('Request timed out')); }, optionsArg.hardDataCuttingTimeout); } // Always return the raw stream done.resolve(response); }); // Write request body if (optionsArg.requestBody) { if (optionsArg.requestBody instanceof plugins.formData) { optionsArg.requestBody.pipe(request).on('finish', () => { request.end(); }); } else { // Write body as-is - caller is responsible for serialization const bodyData = typeof optionsArg.requestBody === 'string' ? optionsArg.requestBody : optionsArg.requestBody instanceof Buffer ? optionsArg.requestBody : JSON.stringify(optionsArg.requestBody); // Still stringify for backward compatibility request.write(bodyData); request.end(); } } else if (requestDataFunc) { requestDataFunc(request); } else { request.end(); } // Handle request errors request.on('error', (e) => { console.error(e); request.destroy(); done.reject(e); }); // Get response and handle response errors const response = await done.promise; response.on('error', (err) => { console.error(err); response.destroy(); }); return response; } /** * Modern request function that returns a SmartResponse */ export async function request( urlArg: string, optionsArg: types.ICoreRequestOptions = {} ): Promise { const response = await coreRequest(urlArg, optionsArg); return new SmartResponse(response, urlArg); }