| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  | import * as plugins from './plugins.js'; | 
					
						
							|  |  |  | import * as types from './types.js'; | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  | import { CoreResponse } from './response.js'; | 
					
						
							| 
									
										
										
										
											2025-07-28 15:12:11 +00:00
										 |  |  | import { CoreRequest as AbstractCoreRequest } from '../core_base/request.js'; | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Keep-alive agents for connection pooling
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  | const httpAgent = new plugins.agentkeepalive.HttpAgent({ | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |   keepAlive: true, | 
					
						
							|  |  |  |   maxFreeSockets: 10, | 
					
						
							|  |  |  |   maxSockets: 100, | 
					
						
							|  |  |  |   maxTotalSockets: 1000, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  | const httpAgentKeepAliveFalse = new plugins.agentkeepalive.HttpAgent({ | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |   keepAlive: false, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const httpsAgent = new plugins.agentkeepalive.HttpsAgent({ | 
					
						
							|  |  |  |   keepAlive: true, | 
					
						
							|  |  |  |   maxFreeSockets: 10, | 
					
						
							|  |  |  |   maxSockets: 100, | 
					
						
							|  |  |  |   maxTotalSockets: 1000, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const httpsAgentKeepAliveFalse = new plugins.agentkeepalive.HttpsAgent({ | 
					
						
							|  |  |  |   keepAlive: false, | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-28 15:12:11 +00:00
										 |  |  |  * Node.js implementation of Core Request class that handles all HTTP/HTTPS requests | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  | export class CoreRequest extends AbstractCoreRequest< | 
					
						
							|  |  |  |   types.ICoreRequestOptions, | 
					
						
							|  |  |  |   CoreResponse | 
					
						
							|  |  |  | > { | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |   private requestDataFunc: ((req: plugins.http.ClientRequest) => void) | null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   constructor( | 
					
						
							|  |  |  |     url: string, | 
					
						
							|  |  |  |     options: types.ICoreRequestOptions = {}, | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |     requestDataFunc: ((req: plugins.http.ClientRequest) => void) | null = null, | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2025-07-28 15:12:11 +00:00
										 |  |  |     super(url, options); | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     this.requestDataFunc = requestDataFunc; | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 22:37:36 +00:00
										 |  |  |     // Check for unsupported fetch-specific options
 | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |     if ( | 
					
						
							|  |  |  |       options.credentials || | 
					
						
							|  |  |  |       options.mode || | 
					
						
							|  |  |  |       options.cache || | 
					
						
							|  |  |  |       options.redirect || | 
					
						
							|  |  |  |       options.referrer || | 
					
						
							|  |  |  |       options.referrerPolicy || | 
					
						
							|  |  |  |       options.integrity | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'Fetch API specific options (credentials, mode, cache, redirect, referrer, referrerPolicy, integrity) are not supported in Node.js implementation', | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2025-07-28 22:37:36 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |   /** | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |    * Fire the request and return a CoreResponse | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |   async fire(): Promise<CoreResponse> { | 
					
						
							|  |  |  |     const incomingMessage = await this.fireCore(); | 
					
						
							| 
									
										
										
										
											2025-07-29 15:44:04 +00:00
										 |  |  |     return new CoreResponse(incomingMessage, this.url, this.options); | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |   /** | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |    * Fire the request and return the raw IncomingMessage | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |   async fireCore(): Promise<plugins.http.IncomingMessage> { | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     const done = plugins.smartpromise.defer<plugins.http.IncomingMessage>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Parse URL
 | 
					
						
							|  |  |  |     const parsedUrl = plugins.smarturl.Smarturl.createFromUrl(this.url, { | 
					
						
							|  |  |  |       searchParams: this.options.queryParams || {}, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     this.options.hostname = parsedUrl.hostname; | 
					
						
							|  |  |  |     if (parsedUrl.port) { | 
					
						
							|  |  |  |       this.options.port = parseInt(parsedUrl.port, 10); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.options.path = parsedUrl.path; | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     // Handle unix socket URLs
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |     if (CoreRequest.isUnixSocket(this.url)) { | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |       const { socketPath, path } = CoreRequest.parseUnixSocketUrl( | 
					
						
							|  |  |  |         this.options.path, | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |       this.options.socketPath = socketPath; | 
					
						
							|  |  |  |       this.options.path = path; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     // Determine agent based on protocol and keep-alive setting
 | 
					
						
							|  |  |  |     if (!this.options.agent) { | 
					
						
							|  |  |  |       // Only use keep-alive agents if explicitly requested
 | 
					
						
							|  |  |  |       if (this.options.keepAlive === true) { | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |         this.options.agent = | 
					
						
							|  |  |  |           parsedUrl.protocol === 'https:' ? httpsAgent : httpAgent; | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |       } else if (this.options.keepAlive === false) { | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |         this.options.agent = | 
					
						
							|  |  |  |           parsedUrl.protocol === 'https:' | 
					
						
							|  |  |  |             ? httpsAgentKeepAliveFalse | 
					
						
							|  |  |  |             : httpAgentKeepAliveFalse; | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |       // If keepAlive is undefined, don't set any agent (more fetch-like behavior)
 | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     // Determine request module
 | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |     const requestModule = | 
					
						
							|  |  |  |       parsedUrl.protocol === 'https:' ? plugins.https : plugins.http; | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     if (!requestModule) { | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |       throw new Error( | 
					
						
							|  |  |  |         `The request to ${this.url} is missing a viable protocol. Must be http or https`, | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Perform the request
 | 
					
						
							| 
									
										
										
										
											2025-08-19 01:36:44 +00:00
										 |  |  |     let timeoutId: NodeJS.Timeout | null = null; | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     const request = requestModule.request(this.options, async (response) => { | 
					
						
							|  |  |  |       // Handle hard timeout
 | 
					
						
							|  |  |  |       if (this.options.hardDataCuttingTimeout) { | 
					
						
							| 
									
										
										
										
											2025-08-19 01:36:44 +00:00
										 |  |  |         timeoutId = setTimeout(() => { | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |           response.destroy(); | 
					
						
							|  |  |  |           done.reject(new Error('Request timed out')); | 
					
						
							|  |  |  |         }, this.options.hardDataCuttingTimeout); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Always return the raw stream
 | 
					
						
							|  |  |  |       done.resolve(response); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-19 01:36:44 +00:00
										 |  |  |     // Set request timeout (Node.js built-in timeout)
 | 
					
						
							|  |  |  |     if (this.options.timeout) { | 
					
						
							|  |  |  |       request.setTimeout(this.options.timeout, () => { | 
					
						
							|  |  |  |         request.destroy(); | 
					
						
							|  |  |  |         done.reject(new Error('Request timed out')); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     // Write request body
 | 
					
						
							|  |  |  |     if (this.options.requestBody) { | 
					
						
							|  |  |  |       if (this.options.requestBody instanceof plugins.formData) { | 
					
						
							|  |  |  |         this.options.requestBody.pipe(request).on('finish', () => { | 
					
						
							|  |  |  |           request.end(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         // Write body as-is - caller is responsible for serialization
 | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |         const bodyData = | 
					
						
							|  |  |  |           typeof this.options.requestBody === 'string' | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |             ? this.options.requestBody | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |             : this.options.requestBody instanceof Buffer | 
					
						
							|  |  |  |               ? this.options.requestBody | 
					
						
							|  |  |  |               : JSON.stringify(this.options.requestBody); // Still stringify for backward compatibility
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |         request.write(bodyData); | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |         request.end(); | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |       } | 
					
						
							|  |  |  |     } else if (this.requestDataFunc) { | 
					
						
							|  |  |  |       this.requestDataFunc(request); | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       request.end(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Handle request errors
 | 
					
						
							|  |  |  |     request.on('error', (e) => { | 
					
						
							|  |  |  |       console.error(e); | 
					
						
							|  |  |  |       request.destroy(); | 
					
						
							| 
									
										
										
										
											2025-08-19 01:36:44 +00:00
										 |  |  |       // Clear timeout on error
 | 
					
						
							|  |  |  |       if (timeoutId) { | 
					
						
							|  |  |  |         clearTimeout(timeoutId); | 
					
						
							|  |  |  |         timeoutId = null; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |       done.reject(e); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Get response and handle response errors
 | 
					
						
							|  |  |  |     const response = await done.promise; | 
					
						
							| 
									
										
										
										
											2025-08-19 01:36:44 +00:00
										 |  |  |      | 
					
						
							|  |  |  |     // Clear timeout on successful response
 | 
					
						
							|  |  |  |     if (timeoutId) { | 
					
						
							|  |  |  |       clearTimeout(timeoutId); | 
					
						
							|  |  |  |       timeoutId = null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |     response.on('error', (err) => { | 
					
						
							|  |  |  |       console.error(err); | 
					
						
							|  |  |  |       response.destroy(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return response; | 
					
						
							| 
									
										
										
										
											2025-07-27 21:23:20 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |   /** | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |    * Static factory method to create and fire a request | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |    */ | 
					
						
							|  |  |  |   static async create( | 
					
						
							|  |  |  |     url: string, | 
					
						
							| 
									
										
										
										
											2025-08-18 00:21:14 +00:00
										 |  |  |     options: types.ICoreRequestOptions = {}, | 
					
						
							| 
									
										
										
										
											2025-07-28 14:45:47 +00:00
										 |  |  |   ): Promise<CoreResponse> { | 
					
						
							|  |  |  |     const request = new CoreRequest(url, options); | 
					
						
							|  |  |  |     return request.fire(); | 
					
						
							| 
									
										
										
										
											2025-07-28 14:30:27 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | } |