11import { useCallback , useMemo , useRef , useState } from 'react' ;
2- import { IResolverProps , IUseDownloader , TError } from './types' ;
2+ import { DownloadFunction , IResolverProps , IUseDownloader , IWindowDownloaderEmbedded , TError } from './types' ;
33
44export const resolver =
55 ( {
@@ -8,66 +8,66 @@ export const resolver =
88 setPercentageCallback,
99 setErrorCallback,
1010 } : IResolverProps ) =>
11- ( response : Response ) : Response => {
12- if ( ! response . ok ) {
13- throw Error ( `${ response . status } ${ response . type } ${ response . statusText } ` ) ;
14- }
11+ ( response : Response ) : Response => {
12+ if ( ! response . ok ) {
13+ throw Error ( `${ response . status } ${ response . type } ${ response . statusText } ` ) ;
14+ }
1515
16- if ( ! response . body ) {
17- throw Error ( 'ReadableStream not yet supported in this browser.' ) ;
18- }
16+ if ( ! response . body ) {
17+ throw Error ( 'ReadableStream not yet supported in this browser.' ) ;
18+ }
1919
20- const responseBody = response . body ;
20+ const responseBody = response . body ;
2121
22- const contentEncoding = response . headers . get ( 'content-encoding' ) ;
23- const contentLength = response . headers . get (
24- contentEncoding ? 'x-file-size' : 'content-length'
25- ) ;
22+ const contentEncoding = response . headers . get ( 'content-encoding' ) ;
23+ const contentLength = response . headers . get (
24+ contentEncoding ? 'x-file-size' : 'content-length'
25+ ) ;
2626
27- const total = parseInt ( contentLength || '0' , 10 ) ;
27+ const total = parseInt ( contentLength || '0' , 10 ) ;
2828
29- setSize ( ( ) => total ) ;
29+ setSize ( ( ) => total ) ;
3030
31- let loaded = 0 ;
31+ let loaded = 0 ;
3232
33- const stream = new ReadableStream < Uint8Array > ( {
34- start ( controller ) {
35- setControllerCallback ( controller ) ;
33+ const stream = new ReadableStream < Uint8Array > ( {
34+ start ( controller ) {
35+ setControllerCallback ( controller ) ;
3636
37- const reader = responseBody . getReader ( ) ;
37+ const reader = responseBody . getReader ( ) ;
3838
39- async function read ( ) : Promise < void > {
40- return reader
41- . read ( )
42- . then ( ( { done, value } ) => {
43- if ( done ) {
44- return controller . close ( ) ;
45- }
39+ async function read ( ) : Promise < void > {
40+ return reader
41+ . read ( )
42+ . then ( ( { done, value } ) => {
43+ if ( done ) {
44+ return controller . close ( ) ;
45+ }
4646
47- loaded += value ?. byteLength || 0 ;
47+ loaded += value ?. byteLength || 0 ;
4848
49- if ( value ) {
50- controller . enqueue ( value ) ;
51- }
49+ if ( value ) {
50+ controller . enqueue ( value ) ;
51+ }
5252
53- setPercentageCallback ( { loaded, total } ) ;
53+ setPercentageCallback ( { loaded, total } ) ;
5454
55- return read ( ) ;
56- } )
57- . catch ( ( error : Error ) => {
58- setErrorCallback ( error ) ;
59- reader . cancel ( 'Cancelled' ) ;
55+ return read ( ) ;
56+ } )
57+ . catch ( ( error : Error ) => {
58+ setErrorCallback ( error ) ;
59+ reader . cancel ( 'Cancelled' ) ;
6060
61- return controller . error ( error ) ;
62- } ) ;
63- }
61+ return controller . error ( error ) ;
62+ } ) ;
63+ }
6464
65- return read ( ) ;
66- } ,
67- } ) ;
65+ return read ( ) ;
66+ } ,
67+ } ) ;
6868
69- return new Response ( stream ) ;
70- } ;
69+ return new Response ( stream ) ;
70+ } ;
7171
7272export const jsDownload = (
7373 data : Blob ,
@@ -79,8 +79,8 @@ export const jsDownload = (
7979 type : mime || 'application/octet-stream' ,
8080 } ) ;
8181
82- if ( typeof window . navigator . msSaveBlob !== 'undefined' ) {
83- return window . navigator . msSaveBlob ( blob , filename ) ;
82+ if ( typeof ( window as unknown as IWindowDownloaderEmbedded ) . navigator . msSaveBlob !== 'undefined' ) {
83+ return ( window as unknown as IWindowDownloaderEmbedded ) . navigator . msSaveBlob ( blob , filename ) ;
8484 }
8585
8686 const blobURL =
@@ -128,6 +128,7 @@ export default function useDownloader(): IUseDownloader {
128128 const errorMap = {
129129 "Failed to execute 'enqueue' on 'ReadableStreamDefaultController': Cannot enqueue a chunk into an errored readable stream" :
130130 'Download canceled' ,
131+ 'The user aborted a request.' : 'Download timed out' ,
131132 } ;
132133 setError ( ( ) => {
133134 const resolvedError = errorMap [ err . message ]
@@ -138,7 +139,7 @@ export default function useDownloader(): IUseDownloader {
138139 } ) ;
139140 } , [ ] ) ;
140141
141- const setControllerCallback = useCallback ( ( controller ) => {
142+ const setControllerCallback = useCallback ( ( controller : ReadableStreamController < Uint8Array > | null ) => {
142143 controllerRef . current = controller ;
143144 } , [ ] ) ;
144145
@@ -157,15 +158,15 @@ export default function useDownloader(): IUseDownloader {
157158 setIsInProgress ( ( ) => false ) ;
158159 } , [ setControllerCallback ] ) ;
159160
160- const handleDownload = useCallback (
161- async ( downloadUrl , filename ) => {
161+ const handleDownload : DownloadFunction = useCallback (
162+ async ( downloadUrl , filename , timeout = 0 ) => {
162163 if ( isInProgress ) return null ;
163164
164165 clearAllStateCallback ( ) ;
165166 setError ( ( ) => null ) ;
166167 setIsInProgress ( ( ) => true ) ;
167168
168- const interval = setInterval (
169+ const intervalId = setInterval (
169170 ( ) => setElapsed ( ( prevValue ) => prevValue + 1 ) ,
170171 debugMode ? 1 : 1000
171172 ) ;
@@ -176,8 +177,14 @@ export default function useDownloader(): IUseDownloader {
176177 setErrorCallback,
177178 } ) ;
178179
180+ const fetchController = new AbortController ( ) ;
181+ const timeoutId = setTimeout ( ( ) => {
182+ if ( timeout > 0 ) fetchController . abort ( ) ;
183+ } , timeout ) ;
184+
179185 return fetch ( downloadUrl , {
180186 method : 'GET' ,
187+ signal : fetchController . signal
181188 } )
182189 . then ( resolverWithProgress )
183190 . then ( ( data ) => {
@@ -187,7 +194,7 @@ export default function useDownloader(): IUseDownloader {
187194 . then ( ( ) => {
188195 clearAllStateCallback ( ) ;
189196
190- return clearInterval ( interval ) ;
197+ return clearInterval ( intervalId ) ;
191198 } )
192199 . catch ( ( err ) => {
193200 clearAllStateCallback ( ) ;
@@ -203,7 +210,8 @@ export default function useDownloader(): IUseDownloader {
203210 return prevValue ;
204211 } ) ;
205212
206- return clearInterval ( interval ) ;
213+ clearTimeout ( timeoutId ) ;
214+ return clearInterval ( intervalId ) ;
207215 } ) ;
208216 } ,
209217 [
0 commit comments