@@ -164,13 +164,28 @@ export default class Tus<M extends Meta, B extends Body> extends BasePlugin<
164164 * Clean up all references for a file's upload: the tus.Upload instance,
165165 * any events related to the file, and the Companion WebSocket connection.
166166 */
167- resetUploaderReferences ( fileID : string , opts ?: { abort : boolean } ) : void {
167+ resetUploaderReferences (
168+ fileID : string ,
169+ opts ?: {
170+ /** Terminate the upload on the server (sends a `DELETE` request). */
171+ abort ?: boolean
172+ /**
173+ * Abort the underlying request. Defaults to `true`. Set to `false` when
174+ * the request has already completed (e.g. in the error handler), so the
175+ * underlying `xhr` — and thus the server response — is preserved instead
176+ * of being reset by `abort()`.
177+ */
178+ abortRequest ?: boolean
179+ } ,
180+ ) : void {
168181 const uploader = this . uploaders [ fileID ]
169182 if ( uploader ) {
170- uploader . abort ( )
183+ if ( opts ?. abortRequest !== false ) {
184+ uploader . abort ( )
171185
172- if ( opts ?. abort ) {
173- uploader . abort ( true )
186+ if ( opts ?. abort ) {
187+ uploader . abort ( true )
188+ }
174189 }
175190
176191 this . uploaders [ fileID ] = null
@@ -219,6 +234,12 @@ export default class Tus<M extends Meta, B extends Body> extends BasePlugin<
219234 ) : Promise < tus . Upload | string > {
220235 this . resetUploaderReferences ( file . id )
221236
237+ // Captured in `onError` and forwarded to the `upload-error` event in the
238+ // `.catch` below, so consumers can read the failing server response.
239+ let errorResponse :
240+ | Omit < NonNullable < UppyFile < M , B > [ 'response' ] > , 'uploadURL' >
241+ | undefined
242+
222243 // Create a new tus upload
223244 return new Promise < tus . Upload | string > ( ( resolve , reject ) => {
224245 let queuedRequest : ReturnType < RateLimitedQueue [ 'run' ] >
@@ -291,6 +312,23 @@ export default class Tus<M extends Meta, B extends Body> extends BasePlugin<
291312 uploadOptions . onError = ( err ) => {
292313 this . uppy . log ( err )
293314
315+ // tus-js-client only calls `onError` once it has given up retrying, so
316+ // the request has already completed. Capture the server response (status
317+ // + body) and forward it to the `upload-error` event and `file.response`,
318+ // mirroring the shape emitted by `onSuccess`.
319+ const originalResponse = ( err as tus . DetailedError ) . originalResponse
320+ if ( originalResponse != null ) {
321+ errorResponse = {
322+ status : originalResponse . getStatus ( ) ,
323+ body : {
324+ // We have to put `as XMLHttpRequest` because tus-js-client
325+ // returns `any`, as the type differs in Node.js and the browser.
326+ // In the browser it's always `XMLHttpRequest`.
327+ xhr : originalResponse . getUnderlyingObject ( ) as XMLHttpRequest ,
328+ } as unknown as B ,
329+ }
330+ }
331+
294332 const xhr =
295333 ( err as tus . DetailedError ) . originalRequest != null
296334 ? ( err as tus . DetailedError ) . originalRequest . getUnderlyingObject ( )
@@ -299,7 +337,11 @@ export default class Tus<M extends Meta, B extends Body> extends BasePlugin<
299337 err = new NetworkError ( err , xhr )
300338 }
301339
302- this . resetUploaderReferences ( file . id )
340+ // Do not abort the request here: it has already completed, and aborting
341+ // it would reset the underlying `xhr` (status `0`, empty body) and
342+ // discard the response we just captured. We still drop our references
343+ // and remove the event listeners.
344+ this . resetUploaderReferences ( file . id , { abortRequest : false } )
303345 queuedRequest ?. abort ( )
304346
305347 if ( typeof opts . onError === 'function' ) {
@@ -516,7 +558,10 @@ export default class Tus<M extends Meta, B extends Body> extends BasePlugin<
516558 queuedRequest = this . requests . run ( qRequest )
517559 } )
518560 } ) . catch ( ( err ) => {
519- this . uppy . emit ( 'upload-error' , file , err )
561+ // `errorResponse` is captured in the `onError` handler above (the request
562+ // is intentionally not aborted there), so the server response is still
563+ // available here to forward to the `upload-error` event.
564+ this . uppy . emit ( 'upload-error' , file , err , errorResponse )
520565 throw err
521566 } )
522567 }
0 commit comments