55import { spawn } from 'node:child_process' ;
66import * as fs from 'node:fs' ;
77import * as path from 'node:path' ;
8- import * as https from 'node:https' ;
9- import * as http from 'node:http' ;
108import * as os from 'node:os' ;
11- import { Transform } from 'node:stream' ;
9+ import { Readable , Transform } from 'node:stream' ;
10+ import type { ReadableStream as WebReadableStream } from 'node:stream/web' ;
1211import { pipeline } from 'node:stream/promises' ;
1312import { URL } from 'node:url' ;
1413import type { ProgressBar } from './progress.js' ;
1514import { isBinaryInstalled } from '../external.js' ;
1615import type { BrowserCookie } from '../types.js' ;
1716import { getErrorMessage } from '../errors.js' ;
17+ import { fetchWithNodeNetwork } from '../node-network.js' ;
1818
1919export type { BrowserCookie } from '../types.js' ;
2020
@@ -89,9 +89,6 @@ export async function httpDownload(
8989 const { cookies, headers = { } , timeout = 30000 , onProgress, maxRedirects = 10 } = options ;
9090
9191 return new Promise ( ( resolve ) => {
92- const parsedUrl = new URL ( url ) ;
93- const protocol = parsedUrl . protocol === 'https:' ? https : http ;
94-
9592 const requestHeaders : Record < string , string > = {
9693 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36' ,
9794 ...headers ,
@@ -118,37 +115,52 @@ export async function httpDownload(
118115 }
119116 } ;
120117
121- const request = protocol . get ( url , { headers : requestHeaders , timeout } , ( response ) => {
122- void ( async ( ) => {
118+ void ( async ( ) => {
119+ const controller = new AbortController ( ) ;
120+ const timer = setTimeout ( ( ) => controller . abort ( ) , timeout ) ;
121+ try {
122+ const response = await fetchWithNodeNetwork ( url , {
123+ headers : requestHeaders ,
124+ signal : controller . signal ,
125+ redirect : 'manual' ,
126+ } ) ;
127+ clearTimeout ( timer ) ;
128+
123129 // Handle redirects before creating any file handles.
124- if ( response . statusCode && response . statusCode >= 300 && response . statusCode < 400 && response . headers . location ) {
125- response . resume ( ) ;
126- if ( redirectCount >= maxRedirects ) {
127- finish ( { success : false , size : 0 , error : `Too many redirects (> ${ maxRedirects } )` } ) ;
130+ if ( response . status >= 300 && response . status < 400 ) {
131+ const location = response . headers . get ( 'location' ) ;
132+ if ( location ) {
133+ if ( redirectCount >= maxRedirects ) {
134+ finish ( { success : false , size : 0 , error : `Too many redirects (> ${ maxRedirects } )` } ) ;
135+ return ;
136+ }
137+ const redirectUrl = resolveRedirectUrl ( url , location ) ;
138+ const originalHost = new URL ( url ) . hostname ;
139+ const redirectHost = new URL ( redirectUrl ) . hostname ;
140+ const redirectOptions = originalHost === redirectHost
141+ ? options
142+ : { ...options , cookies : undefined , headers : stripCookieHeaders ( options . headers ) } ;
143+ finish ( await httpDownload (
144+ redirectUrl ,
145+ destPath ,
146+ redirectOptions ,
147+ redirectCount + 1 ,
148+ ) ) ;
128149 return ;
129150 }
130- const redirectUrl = resolveRedirectUrl ( url , response . headers . location ) ;
131- const originalHost = new URL ( url ) . hostname ;
132- const redirectHost = new URL ( redirectUrl ) . hostname ;
133- const redirectOptions = originalHost === redirectHost
134- ? options
135- : { ...options , cookies : undefined , headers : stripCookieHeaders ( options . headers ) } ;
136- finish ( await httpDownload (
137- redirectUrl ,
138- destPath ,
139- redirectOptions ,
140- redirectCount + 1 ,
141- ) ) ;
151+ }
152+
153+ if ( response . status !== 200 ) {
154+ finish ( { success : false , size : 0 , error : `HTTP ${ response . status } ` } ) ;
142155 return ;
143156 }
144157
145- if ( response . statusCode !== 200 ) {
146- response . resume ( ) ;
147- finish ( { success : false , size : 0 , error : `HTTP ${ response . statusCode } ` } ) ;
158+ if ( ! response . body ) {
159+ finish ( { success : false , size : 0 , error : 'Empty response body' } ) ;
148160 return ;
149161 }
150162
151- const totalSize = parseInt ( response . headers [ 'content-length' ] || '0' , 10 ) ;
163+ const totalSize = parseInt ( response . headers . get ( 'content-length' ) || '0' , 10 ) ;
152164 let received = 0 ;
153165 const progressStream = new Transform ( {
154166 transform ( chunk , _encoding , callback ) {
@@ -160,26 +172,23 @@ export async function httpDownload(
160172
161173 try {
162174 await fs . promises . mkdir ( path . dirname ( destPath ) , { recursive : true } ) ;
163- await pipeline ( response , progressStream , fs . createWriteStream ( tempPath ) ) ;
175+ await pipeline (
176+ Readable . fromWeb ( response . body as unknown as WebReadableStream ) ,
177+ progressStream ,
178+ fs . createWriteStream ( tempPath ) ,
179+ ) ;
164180 await fs . promises . rename ( tempPath , destPath ) ;
165181 finish ( { success : true , size : received } ) ;
166182 } catch ( err ) {
167183 await cleanupTempFile ( ) ;
168184 finish ( { success : false , size : 0 , error : getErrorMessage ( err ) } ) ;
169185 }
170- } ) ( ) ;
171- } ) ;
172-
173- request . on ( 'error' , ( err ) => {
174- void ( async ( ) => {
186+ } catch ( err ) {
187+ clearTimeout ( timer ) ;
175188 await cleanupTempFile ( ) ;
176- finish ( { success : false , size : 0 , error : err . message } ) ;
177- } ) ( ) ;
178- } ) ;
179-
180- request . on ( 'timeout' , ( ) => {
181- request . destroy ( new Error ( 'Timeout' ) ) ;
182- } ) ;
189+ finish ( { success : false , size : 0 , error : err instanceof Error ? err . message : String ( err ) } ) ;
190+ }
191+ } ) ( ) ;
183192 } ) ;
184193}
185194
0 commit comments