(opts: UploadViaPutOptions)
| 221 | } |
| 222 | |
| 223 | const uploadViaPresignedPut = (opts: UploadViaPutOptions): Promise<void> => { |
| 224 | const { file, presignedUrl, uploadHeaders, signal, onProgress } = opts |
| 225 | |
| 226 | return new Promise((resolve, reject) => { |
| 227 | const xhr = new XMLHttpRequest() |
| 228 | let isCompleted = false |
| 229 | const timeoutMs = calculateUploadTimeoutMs(file.size) |
| 230 | |
| 231 | const timeoutId = setTimeout(() => { |
| 232 | if (isCompleted) return |
| 233 | isCompleted = true |
| 234 | signal?.removeEventListener('abort', abortHandler) |
| 235 | xhr.abort() |
| 236 | reject(new DirectUploadError(`Upload timeout for ${file.name}`, 'DIRECT_UPLOAD_ERROR')) |
| 237 | }, timeoutMs) |
| 238 | |
| 239 | const abortHandler = () => { |
| 240 | if (isCompleted) return |
| 241 | isCompleted = true |
| 242 | clearTimeout(timeoutId) |
| 243 | xhr.abort() |
| 244 | reject(new DirectUploadError(`Upload aborted for ${file.name}`, 'ABORTED')) |
| 245 | } |
| 246 | |
| 247 | if (signal) { |
| 248 | if (signal.aborted) { |
| 249 | abortHandler() |
| 250 | return |
| 251 | } |
| 252 | signal.addEventListener('abort', abortHandler) |
| 253 | } |
| 254 | |
| 255 | xhr.upload.addEventListener('progress', (event) => { |
| 256 | if (event.lengthComputable && !isCompleted) { |
| 257 | onProgress?.({ |
| 258 | loaded: event.loaded, |
| 259 | total: event.total, |
| 260 | percent: Math.round((event.loaded / event.total) * 100), |
| 261 | }) |
| 262 | } |
| 263 | }) |
| 264 | |
| 265 | xhr.addEventListener('load', () => { |
| 266 | if (isCompleted) return |
| 267 | isCompleted = true |
| 268 | clearTimeout(timeoutId) |
| 269 | signal?.removeEventListener('abort', abortHandler) |
| 270 | |
| 271 | if (xhr.status >= 200 && xhr.status < 300) { |
| 272 | onProgress?.({ loaded: file.size, total: file.size, percent: 100 }) |
| 273 | resolve() |
| 274 | } else { |
| 275 | reject( |
| 276 | new DirectUploadError( |
| 277 | `Direct upload failed for ${file.name}: ${xhr.status} ${xhr.statusText}`, |
| 278 | 'DIRECT_UPLOAD_ERROR', |
| 279 | undefined, |
| 280 | xhr.status |
no test coverage detected