@@ -19,10 +19,6 @@ import {
1919} from './options.js'
2020import { uuid } from './uuid.js'
2121
22- interface ExtendedHttpRequest extends HttpRequest {
23- _upload ?: BaseUpload
24- }
25-
2622export const defaultOptions = {
2723 endpoint : undefined ,
2824
@@ -120,9 +116,6 @@ export class BaseUpload {
120116 // upload options or HEAD response)
121117 private _uploadLengthDeferred : boolean
122118
123- // Stall detector instance
124- private _stallDetector ?: StallDetector
125-
126119 constructor ( file : UploadInput , options : UploadOptions ) {
127120 // Warn about removed options from previous versions
128121 if ( 'resume' in options ) {
@@ -229,9 +222,6 @@ export class BaseUpload {
229222 }
230223 }
231224
232- // Setup stall detection before starting the upload
233- this . _setupStallDetection ( )
234-
235225 // Note: `start` does not return a Promise or await the preparation on purpose.
236226 // Its supposed to return immediately and start the upload in the background.
237227 this . _prepareAndStartUpload ( ) . catch ( ( err ) => {
@@ -361,11 +351,6 @@ export class BaseUpload {
361351 throw new Error ( 'tus: Expected totalSize to be set' )
362352 }
363353
364- // Update progress timestamp for the parallel upload to track stalls
365- if ( upload . _stallDetector ) {
366- upload . _stallDetector . updateProgress ( )
367- }
368-
369354 this . _emitProgress ( totalProgress , totalSize )
370355 } ,
371356 // Wait until every partial upload has an upload URL, so we can add
@@ -486,11 +471,6 @@ export class BaseUpload {
486471 // Set the aborted flag before any `await`s, so no new requests are started.
487472 this . _aborted = true
488473
489- // Clear any stall detection
490- if ( this . _stallDetector ) {
491- this . _stallDetector . stop ( )
492- }
493-
494474 // Stop any parallel partial uploads, that have been started in _startParallelUploads.
495475 if ( this . _parallelUploads != null ) {
496476 for ( const upload of this . _parallelUploads ) {
@@ -585,11 +565,6 @@ export class BaseUpload {
585565 * @api private
586566 */
587567 private _emitProgress ( bytesSent : number , bytesTotal : number | null ) : void {
588- // Update stall detection state if progress has been made
589- if ( this . _stallDetector ) {
590- this . _stallDetector . updateProgress ( )
591- }
592-
593568 if ( typeof this . options . onProgress === 'function' ) {
594569 this . options . onProgress ( bytesSent , bytesTotal )
595570 }
@@ -868,7 +843,32 @@ export class BaseUpload {
868843 const start = this . _offset
869844 let end = this . _offset + this . options . chunkSize
870845
846+ // Create stall detector for this request if stall detection is enabled and supported
847+ // but don't start it yet - we'll start it after onBeforeRequest completes
848+ let stallDetector : StallDetector | undefined
849+ if ( this . options . stallDetection ?. enabled ) {
850+ // Only enable stall detection if the HTTP stack supports progress events
851+ if ( this . options . httpStack . supportsProgressEvents ?.( ) ) {
852+ stallDetector = new StallDetector (
853+ this . options . stallDetection ,
854+ this . options . httpStack ,
855+ ( reason : string ) => {
856+ // Handle stall by aborting the current request and triggering retry
857+ if ( this . _req ) {
858+ this . _req . abort ( )
859+ }
860+ this . _retryOrEmitError ( new Error ( `Upload stalled: ${ reason } ` ) )
861+ } ,
862+ )
863+ // Don't start yet - will be started after onBeforeRequest
864+ }
865+ }
866+
871867 req . setProgressHandler ( ( bytesSent ) => {
868+ // Update per-request stall detector if active
869+ if ( stallDetector ) {
870+ stallDetector . updateProgress ( )
871+ }
872872 this . _emitProgress ( start + bytesSent , this . _size )
873873 } )
874874
@@ -911,22 +911,42 @@ export class BaseUpload {
911911 // See https://community.transloadit.com/t/how-to-abort-hanging-companion-uploads/16488/13
912912 const newSize = this . _offset + sizeOfValue
913913 if ( ! this . _uploadLengthDeferred && done && newSize !== this . _size ) {
914+ // Clean up stall detector before throwing error
915+ if ( stallDetector ) {
916+ stallDetector . stop ( )
917+ }
914918 throw new Error (
915919 `upload was configured with a size of ${ this . _size } bytes, but the source is done after ${ newSize } bytes` ,
916920 )
917921 }
918922
919- if ( value == null ) {
920- return await this . _sendRequest ( req )
921- }
923+ try {
924+ let response : HttpResponse
925+ if ( value == null ) {
926+ response = await this . _sendRequest ( req , undefined , stallDetector )
927+ } else {
928+ if (
929+ this . options . protocol === PROTOCOL_IETF_DRAFT_03 ||
930+ this . options . protocol === PROTOCOL_IETF_DRAFT_05
931+ ) {
932+ req . setHeader ( 'Upload-Complete' , done ? '?1' : '?0' )
933+ }
934+ response = await this . _sendRequest ( req , value , stallDetector )
935+ }
922936
923- if (
924- this . options . protocol === PROTOCOL_IETF_DRAFT_03 ||
925- this . options . protocol === PROTOCOL_IETF_DRAFT_05
926- ) {
927- req . setHeader ( 'Upload-Complete' , done ? '?1' : '?0' )
937+ // Clean up stall detector on successful completion
938+ if ( stallDetector ) {
939+ stallDetector . stop ( )
940+ }
941+
942+ return response
943+ } catch ( error ) {
944+ // Clean up stall detector on error
945+ if ( stallDetector ) {
946+ stallDetector . stop ( )
947+ }
948+ throw error
928949 }
929- return await this . _sendRequest ( req , value )
930950 }
931951
932952 /**
@@ -965,8 +985,6 @@ export class BaseUpload {
965985 private _openRequest ( method : string , url : string ) : HttpRequest {
966986 const req = openRequest ( method , url , this . options )
967987 this . _req = req
968- // Store reference to this upload instance for stall detection management
969- ; ( req as ExtendedHttpRequest ) . _upload = this
970988 return req
971989 }
972990
@@ -1026,64 +1044,12 @@ export class BaseUpload {
10261044 *
10271045 * @api private
10281046 */
1029- _sendRequest ( req : HttpRequest , body ?: SliceType ) : Promise < HttpResponse > {
1030- return sendRequest ( req , body , this . options )
1031- }
1032-
1033- /**
1034- * Setup stall detection monitoring
1035- */
1036- private _setupStallDetection ( ) : void {
1037- const stallDetection = this . options . stallDetection
1038-
1039- // Early return if disabled or undefined
1040- if ( ! stallDetection || ! stallDetection . enabled ) {
1041- return
1042- }
1043-
1044- // Create stall detector instance
1045- this . _stallDetector = new StallDetector (
1046- stallDetection ,
1047- this . options . httpStack ,
1048- ( reason : string ) => this . _handleStall ( reason ) ,
1049- )
1050-
1051- // Start monitoring
1052- this . _stallDetector . start ( )
1053- }
1054-
1055- /**
1056- * Handle a detected stall by forcing a retry
1057- */
1058- private _handleStall ( reason : string ) : void {
1059- // Just abort the current request, not the entire upload
1060- // Each parallel upload instance has its own stall detection
1061- if ( this . _req ) {
1062- this . _req . abort ( )
1063- }
1064-
1065- // Force a retry via the error mechanism
1066- this . _retryOrEmitError ( new Error ( `Upload stalled: ${ reason } ` ) )
1067- }
1068-
1069- /**
1070- * Pause stall detection temporarily
1071- * @api private
1072- */
1073- _pauseStallDetection ( ) : void {
1074- if ( this . _stallDetector ) {
1075- this . _stallDetector . pause ( )
1076- }
1077- }
1078-
1079- /**
1080- * Resume stall detection
1081- * @api private
1082- */
1083- _resumeStallDetection ( ) : void {
1084- if ( this . _stallDetector ) {
1085- this . _stallDetector . resume ( )
1086- }
1047+ _sendRequest (
1048+ req : HttpRequest ,
1049+ body ?: SliceType ,
1050+ stallDetector ?: StallDetector ,
1051+ ) : Promise < HttpResponse > {
1052+ return sendRequest ( req , body , this . options , stallDetector )
10871053 }
10881054}
10891055
@@ -1144,20 +1110,15 @@ async function sendRequest(
11441110 req : HttpRequest ,
11451111 body : SliceType | undefined ,
11461112 options : UploadOptions ,
1113+ stallDetector ?: StallDetector ,
11471114) : Promise < HttpResponse > {
1148- // Pause stall detection during onBeforeRequest callback to avoid false positives
1149- const upload = ( req as ExtendedHttpRequest ) . _upload
1150- if ( upload ) {
1151- upload . _pauseStallDetection ( )
1152- }
1153-
11541115 if ( typeof options . onBeforeRequest === 'function' ) {
11551116 await options . onBeforeRequest ( req )
11561117 }
11571118
1158- // Resume stall detection after callback
1159- if ( upload ) {
1160- upload . _resumeStallDetection ( )
1119+ // Start stall detection after onBeforeRequest completes but before the actual network request
1120+ if ( stallDetector ) {
1121+ stallDetector . start ( )
11611122 }
11621123
11631124 const res = await req . send ( body )
0 commit comments