1
+ import type { Uppy } from '@uppy/core'
1
2
import { AbortController } from '@uppy/utils/lib/AbortController'
3
+ import type { Meta , UppyFile } from '@uppy/utils/lib/UppyFile'
4
+ import type { HTTPCommunicationQueue } from './HTTPCommunicationQueue'
5
+ import type { Body } from './utils'
2
6
3
7
const MB = 1024 * 1024
4
8
9
+ interface MultipartUploaderOptions < M extends Meta , B extends Body > {
10
+ getChunkSize ?: ( file : { size : number } ) => number
11
+ onProgress ?: ( bytesUploaded : number , bytesTotal : number ) => void
12
+ onPartComplete ?: ( part : { PartNumber : number ; ETag : string } ) => void
13
+ shouldUseMultipart ?: boolean | ( ( file : UppyFile < M , B > ) => boolean )
14
+ onSuccess ?: ( result : B ) => void
15
+ onError ?: ( err : unknown ) => void
16
+ companionComm : HTTPCommunicationQueue < M , B >
17
+ file : UppyFile < M , B >
18
+ log : Uppy < M , B > [ 'log' ]
19
+
20
+ uploadId : string
21
+ key : string
22
+ }
23
+
5
24
const defaultOptions = {
6
- getChunkSize ( file ) {
25
+ getChunkSize ( file : { size : number } ) {
7
26
return Math . ceil ( file . size / 10000 )
8
27
} ,
9
- onProgress ( ) { } ,
10
- onPartComplete ( ) { } ,
11
- onSuccess ( ) { } ,
12
- onError ( err ) {
28
+ onProgress ( ) { } ,
29
+ onPartComplete ( ) { } ,
30
+ onSuccess ( ) { } ,
31
+ onError ( err : unknown ) {
13
32
throw err
14
33
} ,
34
+ } satisfies Partial < MultipartUploaderOptions < any , any > >
35
+
36
+ export interface Chunk {
37
+ getData : ( ) => Blob
38
+ onProgress : ( ev : ProgressEvent ) => void
39
+ onComplete : ( etag : string ) => void
40
+ shouldUseMultipart : boolean
41
+ setAsUploaded ?: ( ) => void
15
42
}
16
43
17
- function ensureInt ( value ) {
44
+ function ensureInt < T > ( value : T ) : T extends number | string ? number : never {
18
45
if ( typeof value === 'string' ) {
46
+ // @ts -expect-error TS is not able to recognize it's fine.
19
47
return parseInt ( value , 10 )
20
48
}
21
49
if ( typeof value === 'number' ) {
50
+ // @ts -expect-error TS is not able to recognize it's fine.
22
51
return value
23
52
}
24
53
throw new TypeError ( 'Expected a number' )
@@ -32,47 +61,41 @@ export const pausingUploadReason = Symbol('pausing upload, not an actual error')
32
61
* (based on the user-provided `shouldUseMultipart` option value) and to manage
33
62
* the chunk splitting.
34
63
*/
35
- class MultipartUploader {
64
+ class MultipartUploader < M extends Meta , B extends Body > {
65
+ options : MultipartUploaderOptions < M , B > &
66
+ Required < Pick < MultipartUploaderOptions < M , B > , keyof typeof defaultOptions > >
67
+
36
68
#abortController = new AbortController ( )
37
69
38
- /** @type {import("../types/chunk").Chunk[] } */
39
- #chunks
70
+ #chunks: Array < Chunk | null >
40
71
41
- /** @type {{ uploaded: number, etag?: string, done?: boolean }[] } */
42
- #chunkState
72
+ #chunkState: { uploaded : number ; etag ?: string ; done ?: boolean } [ ]
43
73
44
74
/**
45
75
* The (un-chunked) data to upload.
46
- *
47
- * @type {Blob }
48
76
*/
49
- #data
77
+ #data: Blob
50
78
51
- /** @type {import("@uppy/core").UppyFile } */
52
- #file
79
+ #file: UppyFile < M , B >
53
80
54
- /** @type {boolean } */
55
81
#uploadHasStarted = false
56
82
57
- /** @type {(err?: Error | any) => void } */
58
- #onError
83
+ #onError: ( err : unknown ) => void
59
84
60
- /** @type {() => void } */
61
- #onSuccess
85
+ #onSuccess: ( result : B ) => void
62
86
63
- /** @type {import('../types/index').AwsS3MultipartOptions["shouldUseMultipart"] } */
64
- #shouldUseMultipart
87
+ #shouldUseMultipart: MultipartUploaderOptions < M , B > [ 'shouldUseMultipart' ]
65
88
66
- /** @type {boolean } */
67
- #isRestoring
89
+ #isRestoring: boolean
68
90
69
- #onReject = ( err ) => ( err ?. cause === pausingUploadReason ? null : this . #onError( err ) )
91
+ #onReject = ( err : unknown ) =>
92
+ ( err as any ) ?. cause === pausingUploadReason ? null : this . #onError( err )
70
93
71
94
#maxMultipartParts = 10_000
72
95
73
96
#minPartSize = 5 * MB
74
97
75
- constructor ( data , options ) {
98
+ constructor ( data : Blob , options : MultipartUploaderOptions < M , B > ) {
76
99
this . options = {
77
100
...defaultOptions ,
78
101
...options ,
@@ -89,7 +112,7 @@ class MultipartUploader {
89
112
// When we are restoring an upload, we already have an UploadId and a Key. Otherwise
90
113
// we need to call `createMultipartUpload` to get an `uploadId` and a `key`.
91
114
// Non-multipart uploads are not restorable.
92
- this . #isRestoring = options . uploadId && options . key
115
+ this . #isRestoring = ( options . uploadId && options . key ) as any as boolean
93
116
94
117
this . #initChunks( )
95
118
}
@@ -98,15 +121,19 @@ class MultipartUploader {
98
121
// and calculates the optimal part size. When using multipart part uploads every part except for the last has
99
122
// to be at least 5 MB and there can be no more than 10K parts.
100
123
// This means we sometimes need to change the preferred part size from the user in order to meet these requirements.
101
- #initChunks ( ) {
124
+ #initChunks( ) {
102
125
const fileSize = this . #data. size
103
- const shouldUseMultipart = typeof this . #shouldUseMultipart === 'function'
104
- ? this . #shouldUseMultipart( this . #file)
126
+ const shouldUseMultipart =
127
+ typeof this . #shouldUseMultipart === 'function' ?
128
+ this . #shouldUseMultipart( this . #file)
105
129
: Boolean ( this . #shouldUseMultipart)
106
130
107
131
if ( shouldUseMultipart && fileSize > this . #minPartSize) {
108
132
// At least 5MB per request:
109
- let chunkSize = Math . max ( this . options . getChunkSize ( this . #data) , this . #minPartSize)
133
+ let chunkSize = Math . max (
134
+ this . options . getChunkSize ( this . #data) ,
135
+ this . #minPartSize,
136
+ )
110
137
let arraySize = Math . floor ( fileSize / chunkSize )
111
138
112
139
// At most 10k requests per file:
@@ -132,41 +159,48 @@ class MultipartUploader {
132
159
shouldUseMultipart,
133
160
}
134
161
if ( this . #isRestoring) {
135
- const size = offset + chunkSize > fileSize ? fileSize - offset : chunkSize
162
+ const size =
163
+ offset + chunkSize > fileSize ? fileSize - offset : chunkSize
136
164
// setAsUploaded is called by listPart, to keep up-to-date the
137
165
// quantity of data that is left to actually upload.
138
- this . #chunks[ j ] . setAsUploaded = ( ) => {
166
+ this . #chunks[ j ] ! . setAsUploaded = ( ) => {
139
167
this . #chunks[ j ] = null
140
168
this . #chunkState[ j ] . uploaded = size
141
169
}
142
170
}
143
171
}
144
172
} else {
145
- this . #chunks = [ {
146
- getData : ( ) => this . #data,
147
- onProgress : this . #onPartProgress( 0 ) ,
148
- onComplete : this . #onPartComplete( 0 ) ,
149
- shouldUseMultipart,
150
- } ]
173
+ this . #chunks = [
174
+ {
175
+ getData : ( ) => this . #data,
176
+ onProgress : this . #onPartProgress( 0 ) ,
177
+ onComplete : this . #onPartComplete( 0 ) ,
178
+ shouldUseMultipart,
179
+ } ,
180
+ ]
151
181
}
152
182
153
183
this . #chunkState = this . #chunks. map ( ( ) => ( { uploaded : 0 } ) )
154
184
}
155
185
156
- #createUpload ( ) {
157
- this
158
- . options . companionComm . uploadFile ( this . #file, this . #chunks, this . #abortController. signal )
186
+ #createUpload( ) {
187
+ this . options . companionComm
188
+ . uploadFile (
189
+ this . #file,
190
+ this . #chunks as Chunk [ ] ,
191
+ this . #abortController. signal ,
192
+ )
159
193
. then ( this . #onSuccess, this . #onReject)
160
194
this . #uploadHasStarted = true
161
195
}
162
196
163
- #resumeUpload ( ) {
164
- this
165
- . options . companionComm . resumeUploadFile ( this . #file, this . #chunks, this . #abortController. signal )
197
+ #resumeUpload( ) {
198
+ this . options . companionComm
199
+ . resumeUploadFile ( this . #file, this . #chunks, this . #abortController. signal )
166
200
. then ( this . #onSuccess, this . #onReject)
167
201
}
168
202
169
- #onPartProgress = ( index ) => ( ev ) => {
203
+ #onPartProgress = ( index : number ) => ( ev : ProgressEvent ) => {
170
204
if ( ! ev . lengthComputable ) return
171
205
172
206
this . #chunkState[ index ] . uploaded = ensureInt ( ev . loaded )
@@ -175,7 +209,7 @@ class MultipartUploader {
175
209
this . options . onProgress ( totalUploaded , this . #data. size )
176
210
}
177
211
178
- #onPartComplete = ( index ) => ( etag ) => {
212
+ #onPartComplete = ( index : number ) => ( etag : string ) => {
179
213
// This avoids the net::ERR_OUT_OF_MEMORY in Chromium Browsers.
180
214
this . #chunks[ index ] = null
181
215
this . #chunkState[ index ] . etag = etag
@@ -188,37 +222,44 @@ class MultipartUploader {
188
222
this . options . onPartComplete ( part )
189
223
}
190
224
191
- #abortUpload ( ) {
225
+ #abortUpload( ) {
192
226
this . #abortController. abort ( )
193
- this . options . companionComm . abortFileUpload ( this . #file) . catch ( ( err ) => this . options . log ( err ) )
227
+ this . options . companionComm
228
+ . abortFileUpload ( this . #file)
229
+ . catch ( ( err : unknown ) => this . options . log ( err as Error ) )
194
230
}
195
231
196
- start ( ) {
232
+ start ( ) : void {
197
233
if ( this . #uploadHasStarted) {
198
- if ( ! this . #abortController. signal . aborted ) this . #abortController. abort ( pausingUploadReason )
234
+ if ( ! this . #abortController. signal . aborted )
235
+ this . #abortController. abort ( pausingUploadReason )
199
236
this . #abortController = new AbortController ( )
200
237
this . #resumeUpload( )
201
238
} else if ( this . #isRestoring) {
202
- this . options . companionComm . restoreUploadFile ( this . #file, { uploadId : this . options . uploadId , key : this . options . key } )
239
+ this . options . companionComm . restoreUploadFile ( this . #file, {
240
+ uploadId : this . options . uploadId ,
241
+ key : this . options . key ,
242
+ } )
203
243
this . #resumeUpload( )
204
244
} else {
205
245
this . #createUpload( )
206
246
}
207
247
}
208
248
209
- pause ( ) {
249
+ pause ( ) : void {
210
250
this . #abortController. abort ( pausingUploadReason )
211
251
// Swap it out for a new controller, because this instance may be resumed later.
212
252
this . #abortController = new AbortController ( )
213
253
}
214
254
215
- abort ( opts = undefined ) {
255
+ abort ( opts ?: { really ?: boolean } ) : void {
216
256
if ( opts ?. really ) this . #abortUpload( )
217
257
else this . pause ( )
218
258
}
219
259
220
260
// TODO: remove this in the next major
221
- get chunkState ( ) {
261
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
262
+ get chunkState ( ) {
222
263
return this . #chunkState
223
264
}
224
265
}
0 commit comments