11import retry from "async-retry" ;
22import { EventEmitter } from "../../../../utils/EventEmitter.js" ;
3- import { withLock } from "lifecycle-utils" ;
3+ import { withLock } from "lifecycle-utils" ;
44import prettyMillisecondsCompact from "../../../transfer-visualize/utils/prettyMSFast.js" ;
5- import { AvailablePrograms } from "../../download-file/download-programs/switch-program.js" ;
6- import { InputRange } from "../../engine/base-download-engine.js" ;
7- import { sleepPromise } from "../../utils/sleepPromise.js" ;
5+ import { AvailablePrograms } from "../../download-file/download-programs/switch-program.js" ;
6+ import { InputRange } from "../../engine/base-download-engine.js" ;
7+ import { sleepPromise } from "../../utils/sleepPromise.js" ;
88import HttpError from "./errors/http-error.js" ;
99import StatusCodeError from "./errors/status-code-error.js" ;
10- import { retryAsyncStatementSimple } from "./utils/retry-async-statement.js" ;
10+ import { retryAsyncStatementSimple } from "./utils/retry-async-statement.js" ;
1111
12- export const STREAM_NOT_RESPONDING_TIMEOUT = 1000 * 3 ;
1312export const MIN_LENGTH_FOR_MORE_INFO_REQUEST = 1024 * 1024 * 3 ; // 3MB
1413
1514const TOKEN_EXPIRED_ERROR_CODES = [ 401 , 403 , 419 , 440 , 498 , 499 ] ;
@@ -19,7 +18,15 @@ export type BaseDownloadEngineFetchStreamOptions = {
1918 retryFetchDownloadInfo ?: retry . Options ;
2019 range ?: InputRange ;
2120 /**
22- * Max wait for next data stream
21+ * Interval read data and check if stream is not responding (default: 1s)
22+ */
23+ streamCheckInterval ?: number ;
24+ /**
25+ * Max wait for stream to respond with data before raising stream not responding event (default: 3s)
26+ */
27+ streamWaitAlert ?: number ;
28+ /**
29+ * Max wait for next data stream before aborting and retrying (default: 15s)
2330 */
2431 maxStreamWait ?: number ;
2532 /**
@@ -41,19 +48,19 @@ export type BaseDownloadEngineFetchStreamOptions = {
4148 */
4249 progressThrottleMs ?: number ;
4350} & (
44- {
45- defaultFetchDownloadInfo ?: { length : number , acceptRange : boolean ; } ;
46- } |
47- {
48- /**
49- * Try different headers to see if any authentication is needed
50- */
51- tryHeaders ?: Record < string , string > [ ] ;
52- /**
53- * Delay between trying different headers
54- */
55- tryHeadersDelay ?: number ;
56- } ) ;
51+ {
52+ defaultFetchDownloadInfo ?: { length : number , acceptRange : boolean ; } ;
53+ } |
54+ {
55+ /**
56+ * Try different headers to see if any authentication is needed
57+ */
58+ tryHeaders ?: Record < string , string > [ ] ;
59+ /**
60+ * Delay between trying different headers
61+ */
62+ tryHeadersDelay ?: number ;
63+ } ) ;
5764
5865export type DownloadInfoResponse = {
5966 length : number ,
@@ -93,6 +100,8 @@ export type WriteCallback = (data: Uint8Array[], position: number, index: number
93100
94101const DEFAULT_OPTIONS : BaseDownloadEngineFetchStreamOptions = {
95102 retryOnServerError : true ,
103+ streamCheckInterval : 10 ,
104+ streamWaitAlert : 1000 * 3 ,
96105 maxStreamWait : 1000 * 15 ,
97106 headersTimeout : 1000 * 30 ,
98107 retry : {
@@ -111,8 +120,7 @@ const DEFAULT_OPTIONS: BaseDownloadEngineFetchStreamOptions = {
111120 range : {
112121 start : 0 ,
113122 end : - 1
114- } ,
115- progressThrottleMs : 15
123+ }
116124} ;
117125
118126export default abstract class BaseDownloadEngineFetchStream extends EventEmitter < BaseDownloadEngineFetchStreamEvents > {
@@ -127,13 +135,16 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter
127135 public aborted = false ;
128136 protected _pausedResolve ?: ( ) => void ;
129137 protected _cleanupClonedStateListeners ?: ( ) => void ;
130- public errorCount = { value : 0 } ;
138+ public errorCount = { value : 0 } ;
131139 public lastFetchTime = 0 ;
132140 private _closed = false ;
141+ private _watchDogCalls = new Set < ( ) => void > ( ) ;
142+ private _watchDogInterval ?: NodeJS . Timeout ;
133143
134144 constructor ( options : Partial < BaseDownloadEngineFetchStreamOptions > = { } ) {
135145 super ( ) ;
136- this . options = { ...DEFAULT_OPTIONS , ...options } ;
146+ this . options = { ...DEFAULT_OPTIONS , ...options } ;
147+ this . watchDog = this . watchDog . bind ( this ) ;
137148 this . initEvents ( ) ;
138149 }
139150
@@ -189,6 +200,8 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter
189200 fetchStream . _cleanupClonedStateListeners = undefined ;
190201 } ;
191202
203+ this . watchDog = fetchStream . watchDog ;
204+
192205 return fetchStream ;
193206 }
194207
@@ -338,6 +351,25 @@ export default abstract class BaseDownloadEngineFetchStream extends EventEmitter
338351 return parsed . href ;
339352 }
340353
354+ protected watchDog ( callback : ( ) => void ) {
355+ this . _watchDogCalls . add ( callback ) ;
356+ if ( ! this . _watchDogInterval ) {
357+ this . _watchDogInterval = setInterval ( ( ) => {
358+ for ( const cb of this . _watchDogCalls ) {
359+ cb ( ) ;
360+ }
361+ } , this . options . streamCheckInterval ! ) ;
362+ }
363+
364+ return ( ) => {
365+ this . _watchDogCalls . delete ( callback ) ;
366+ if ( this . _watchDogCalls . size === 0 && this . _watchDogInterval ) {
367+ clearInterval ( this . _watchDogInterval ) ;
368+ this . _watchDogInterval = undefined ;
369+ }
370+ } ;
371+ }
372+
341373 protected retryOnServerError ( error : Error ) : error is StatusCodeError {
342374 return Boolean ( this . options . retryOnServerError ) && error instanceof StatusCodeError &&
343375 ( error . statusCode >= 500 || error . statusCode === 429 ) ;
0 commit comments