@uploadx/client
Version:
Resumable upload client for browser and Node.js
931 lines (909 loc) • 47 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('axios')) :
typeof define === 'function' && define.amd ? define(['axios'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.UploadxClient = factory(global.axios));
})(this, (function (axios) { 'use strict';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var isRetryAllowed$1;
var hasRequiredIsRetryAllowed;
function requireIsRetryAllowed () {
if (hasRequiredIsRetryAllowed) return isRetryAllowed$1;
hasRequiredIsRetryAllowed = 1;
const denyList = new Set([
'ENOTFOUND',
'ENETUNREACH',
// SSL errors from https://github.com/nodejs/node/blob/fc8e3e2cdc521978351de257030db0076d79e0ab/src/crypto/crypto_common.cc#L301-L328
'UNABLE_TO_GET_ISSUER_CERT',
'UNABLE_TO_GET_CRL',
'UNABLE_TO_DECRYPT_CERT_SIGNATURE',
'UNABLE_TO_DECRYPT_CRL_SIGNATURE',
'UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY',
'CERT_SIGNATURE_FAILURE',
'CRL_SIGNATURE_FAILURE',
'CERT_NOT_YET_VALID',
'CERT_HAS_EXPIRED',
'CRL_NOT_YET_VALID',
'CRL_HAS_EXPIRED',
'ERROR_IN_CERT_NOT_BEFORE_FIELD',
'ERROR_IN_CERT_NOT_AFTER_FIELD',
'ERROR_IN_CRL_LAST_UPDATE_FIELD',
'ERROR_IN_CRL_NEXT_UPDATE_FIELD',
'OUT_OF_MEM',
'DEPTH_ZERO_SELF_SIGNED_CERT',
'SELF_SIGNED_CERT_IN_CHAIN',
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
'CERT_CHAIN_TOO_LONG',
'CERT_REVOKED',
'INVALID_CA',
'PATH_LENGTH_EXCEEDED',
'INVALID_PURPOSE',
'CERT_UNTRUSTED',
'CERT_REJECTED',
'HOSTNAME_MISMATCH'
]);
// TODO: Use `error?.code` when targeting Node.js 14
isRetryAllowed$1 = error => !denyList.has(error && error.code);
return isRetryAllowed$1;
}
var isRetryAllowedExports = requireIsRetryAllowed();
var isRetryAllowed = /*@__PURE__*/getDefaultExportFromCjs(isRetryAllowedExports);
const namespace = 'axios-retry';
function isNetworkError(error) {
const CODE_EXCLUDE_LIST = ['ERR_CANCELED', 'ECONNABORTED'];
if (error.response) {
return false;
}
if (!error.code) {
return false;
}
// Prevents retrying timed out & cancelled requests
if (CODE_EXCLUDE_LIST.includes(error.code)) {
return false;
}
// Prevents retrying unsafe errors
return isRetryAllowed(error);
}
const SAFE_HTTP_METHODS = ['get', 'head', 'options'];
const IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(['put', 'delete']);
function isRetryableError(error) {
return (error.code !== 'ECONNABORTED' &&
(!error.response ||
error.response.status === 429 ||
(error.response.status >= 500 && error.response.status <= 599)));
}
function isSafeRequestError(error) {
if (!error.config?.method) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && SAFE_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
function isIdempotentRequestError(error) {
if (!error.config?.method) {
// Cannot determine if the request can be retried
return false;
}
return isRetryableError(error) && IDEMPOTENT_HTTP_METHODS.indexOf(error.config.method) !== -1;
}
function isNetworkOrIdempotentRequestError(error) {
return isNetworkError(error) || isIdempotentRequestError(error);
}
function retryAfter(error = undefined) {
const retryAfterHeader = error?.response?.headers['retry-after'];
if (!retryAfterHeader) {
return 0;
}
// if the retry after header is a number, convert it to milliseconds
let retryAfterMs = (Number(retryAfterHeader) || 0) * 1000;
// If the retry after header is a date, get the number of milliseconds until that date
if (retryAfterMs === 0) {
retryAfterMs = (new Date(retryAfterHeader).valueOf() || 0) - Date.now();
}
return Math.max(0, retryAfterMs);
}
function noDelay(_retryNumber = 0, error = undefined) {
return Math.max(0, retryAfter(error));
}
function exponentialDelay(retryNumber = 0, error = undefined, delayFactor = 100) {
const calculatedDelay = 2 ** retryNumber * delayFactor;
const delay = Math.max(calculatedDelay, retryAfter(error));
const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
return delay + randomSum;
}
/**
* Linear delay
* @param {number | undefined} delayFactor - delay factor in milliseconds (default: 100)
* @returns {function} (retryNumber: number, error: AxiosError | undefined) => number
*/
function linearDelay(delayFactor = 100) {
return (retryNumber = 0, error = undefined) => {
const delay = retryNumber * delayFactor;
return Math.max(delay, retryAfter(error));
};
}
const DEFAULT_OPTIONS = {
retries: 3,
retryCondition: isNetworkOrIdempotentRequestError,
retryDelay: noDelay,
shouldResetTimeout: false,
onRetry: () => { },
onMaxRetryTimesExceeded: () => { },
validateResponse: null
};
function getRequestOptions(config, defaultOptions) {
return { ...DEFAULT_OPTIONS, ...defaultOptions, ...config[namespace] };
}
function setCurrentState(config, defaultOptions, resetLastRequestTime = false) {
const currentState = getRequestOptions(config, defaultOptions || {});
currentState.retryCount = currentState.retryCount || 0;
if (!currentState.lastRequestTime || resetLastRequestTime) {
currentState.lastRequestTime = Date.now();
}
config[namespace] = currentState;
return currentState;
}
function fixConfig(axiosInstance, config) {
// @ts-ignore
if (axiosInstance.defaults.agent === config.agent) {
// @ts-ignore
delete config.agent;
}
if (axiosInstance.defaults.httpAgent === config.httpAgent) {
delete config.httpAgent;
}
if (axiosInstance.defaults.httpsAgent === config.httpsAgent) {
delete config.httpsAgent;
}
}
async function shouldRetry(currentState, error) {
const { retries, retryCondition } = currentState;
const shouldRetryOrPromise = (currentState.retryCount || 0) < retries && retryCondition(error);
// This could be a promise
if (typeof shouldRetryOrPromise === 'object') {
try {
const shouldRetryPromiseResult = await shouldRetryOrPromise;
// keep return true unless shouldRetryPromiseResult return false for compatibility
return shouldRetryPromiseResult !== false;
}
catch (_err) {
return false;
}
}
return shouldRetryOrPromise;
}
async function handleRetry(axiosInstance, currentState, error, config) {
currentState.retryCount += 1;
const { retryDelay, shouldResetTimeout, onRetry } = currentState;
const delay = retryDelay(currentState.retryCount, error);
// Axios fails merging this configuration to the default configuration because it has an issue
// with circular structures: https://github.com/mzabriskie/axios/issues/370
fixConfig(axiosInstance, config);
if (!shouldResetTimeout && config.timeout && currentState.lastRequestTime) {
const lastRequestDuration = Date.now() - currentState.lastRequestTime;
const timeout = config.timeout - lastRequestDuration - delay;
if (timeout <= 0) {
return Promise.reject(error);
}
config.timeout = timeout;
}
config.transformRequest = [(data) => data];
await onRetry(currentState.retryCount, error, config);
if (config.signal?.aborted) {
return Promise.resolve(axiosInstance(config));
}
return new Promise((resolve) => {
const abortListener = () => {
clearTimeout(timeout);
resolve(axiosInstance(config));
};
const timeout = setTimeout(() => {
resolve(axiosInstance(config));
if (config.signal?.removeEventListener) {
config.signal.removeEventListener('abort', abortListener);
}
}, delay);
if (config.signal?.addEventListener) {
config.signal.addEventListener('abort', abortListener, { once: true });
}
});
}
async function handleMaxRetryTimesExceeded(currentState, error) {
if (currentState.retryCount >= currentState.retries)
await currentState.onMaxRetryTimesExceeded(error, currentState.retryCount);
}
const axiosRetry = (axiosInstance, defaultOptions) => {
const requestInterceptorId = axiosInstance.interceptors.request.use((config) => {
setCurrentState(config, defaultOptions, true);
if (config[namespace]?.validateResponse) {
// by setting this, all HTTP responses will be go through the error interceptor first
config.validateStatus = () => false;
}
return config;
});
const responseInterceptorId = axiosInstance.interceptors.response.use(null, async (error) => {
const { config } = error;
// If we have no information to retry the request
if (!config) {
return Promise.reject(error);
}
const currentState = setCurrentState(config, defaultOptions);
if (error.response && currentState.validateResponse?.(error.response)) {
// no issue with response
return error.response;
}
if (await shouldRetry(currentState, error)) {
return handleRetry(axiosInstance, currentState, error, config);
}
await handleMaxRetryTimesExceeded(currentState, error);
return Promise.reject(error);
});
return { requestInterceptorId, responseInterceptorId };
};
// Compatibility with CommonJS
axiosRetry.isNetworkError = isNetworkError;
axiosRetry.isSafeRequestError = isSafeRequestError;
axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
axiosRetry.exponentialDelay = exponentialDelay;
axiosRetry.linearDelay = linearDelay;
axiosRetry.isRetryableError = isRetryableError;
var isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
var AbortError = /** @class */ (function (_super) {
__extends(AbortError, _super);
function AbortError(message) {
if (message === void 0) { message = 'Operation aborted'; }
var _this = _super.call(this, message) || this;
_this.name = 'AbortError';
return _this;
}
return AbortError;
}(Error));
/**
* Client for handling resumable file uploads
*
* Supports chunked uploads with progress tracking, resuming interrupted uploads,
* and works in both browser and Node.js environments.
*/
var UploadxClient = /** @class */ (function () {
/**
* Creates a new UploadxClient instance
* @param config - Configuration options for chunk size, retry behavior, and axios settings
*/
function UploadxClient(config) {
if (config === void 0) { config = {}; }
this.abortController = new AbortController();
this.chunkSize = config.chunkSize || 5 * 1024 * 1024;
this.client = axios.create(__assign({ maxBodyLength: Number.POSITIVE_INFINITY, maxContentLength: Number.POSITIVE_INFINITY, timeout: 60000, validateStatus: function (status) { return (status >= 200 && status < 400) || status === 308; }, signal: this.abortController.signal }, config.requestConfig));
axiosRetry(this.client, __assign({ retries: 5, retryDelay: exponentialDelay, retryCondition: function (error) {
var _a, _b;
var isNetworkError = axiosRetry.isNetworkError(error) || error.code === 'ECONNRESET';
return (isNetworkError || axiosRetry.isRetryableError(error) || ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 429 || (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) || 0) >= 500);
} }, config.retryConfig));
}
/**
* Aborts all ongoing upload operations
* Cancels any in-progress uploads initiated by this client instance
*/
UploadxClient.prototype.abort = function () {
this.abortController.abort();
// Create a new controller for future operations
this.abortController = new AbortController();
};
/**
* Deletes an existing upload from the server
*/
UploadxClient.prototype.deleteUpload = function (url, signal) {
return __awaiter(this, void 0, void 0, function () {
var error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.client.delete(url, { signal: signal })];
case 1:
_a.sent();
return [3 /*break*/, 3];
case 2:
error_1 = _a.sent();
throw this.handleError(error_1, 'Failed to delete upload');
case 3: return [2 /*return*/];
}
});
});
};
/**
*
* Updates the metadata of an existing upload
*/
UploadxClient.prototype.updateUpload = function (url, metadata, signal) {
return __awaiter(this, void 0, void 0, function () {
var error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.client.patch(url, metadata, {
headers: {
'Content-Type': 'application/json',
},
signal: signal,
})];
case 1:
_a.sent();
return [3 /*break*/, 3];
case 2:
error_2 = _a.sent();
throw this.handleError(error_2, 'Failed to update metadata');
case 3: return [2 /*return*/];
}
});
});
};
/**
* Creates a new upload session on the server
*/
UploadxClient.prototype.createUpload = function (endpoint, metadata, signal) {
return __awaiter(this, void 0, void 0, function () {
var response, rangeHeader, error_3;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 2, , 3]);
return [4 /*yield*/, this.client.post(endpoint, metadata, {
headers: {
'X-Upload-Content-Length': metadata.size,
'X-Upload-Content-Type': metadata.mimeType || 'application/octet-stream',
'Content-Type': 'application/json',
},
signal: signal,
})];
case 1:
response = _b.sent();
if (!response.headers.location) {
throw new Error('Missing Location header in response');
}
rangeHeader = (_a = response === null || response === void 0 ? void 0 : response.headers) === null || _a === void 0 ? void 0 : _a.range;
return [2 /*return*/, {
url: new URL(response.headers.location, endpoint).toString(),
uploadedBytes: this.parseRangeHeader(rangeHeader),
}];
case 2:
error_3 = _b.sent();
throw this.handleError(error_3, 'Session creation failed');
case 3: return [2 /*return*/];
}
});
});
};
/**
* Creates an upload session for a file in Node.js environment
*/
UploadxClient.prototype.createFileUpload = function (filePath, metadata, endpoint, signal) {
return __awaiter(this, void 0, void 0, function () {
var fsPromises, stats, fileSize, actualMetadata;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!isNode) {
throw new Error('uploadAsFile is only available in Node.js environment');
}
return [4 /*yield*/, import('node:fs/promises')];
case 1:
fsPromises = _a.sent();
return [4 /*yield*/, fsPromises.stat(filePath)];
case 2:
stats = _a.sent();
fileSize = stats.size;
actualMetadata = __assign(__assign({}, metadata), { size: fileSize, lastModified: metadata.lastModified || stats.mtimeMs });
return [2 /*return*/, this.createUpload(endpoint, actualMetadata, signal)];
}
});
});
};
/**
* Uploads a file from disk (Node.js only)
*/
UploadxClient.prototype.fileUpload = function (endpoint, filePath, metadata, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var session, sessionUrl, start, totalSize, error_4;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.createFileUpload(filePath, metadata, endpoint, signal)];
case 1:
session = _a.sent();
sessionUrl = session.url;
start = session.uploadedBytes || 0;
totalSize = metadata.size;
return [4 /*yield*/, this.uploadFileInChunks(sessionUrl, filePath, start, totalSize, onProgress, signal)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
error_4 = _a.sent();
// Don't throw if the operation was aborted
if (error_4 instanceof AbortError) {
return [2 /*return*/];
}
throw this.handleError(error_4, "File upload failed for ".concat(filePath));
case 4: return [2 /*return*/];
}
});
});
};
/**
* Uploads data from various sources (Blob, File, Stream, Buffer)
*/
UploadxClient.prototype.upload = function (endpoint, data, metadata, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var _a, url, uploadedBytes, start, totalSize, error_5;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_b.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.createUpload(endpoint, metadata, signal)];
case 1:
_a = _b.sent(), url = _a.url, uploadedBytes = _a.uploadedBytes;
start = uploadedBytes || 0;
totalSize = metadata.size;
return [4 /*yield*/, this.uploadDataInChunks(data, url, start, totalSize, onProgress, signal)];
case 2:
_b.sent();
return [3 /*break*/, 4];
case 3:
error_5 = _b.sent();
// Don't throw if the operation was aborted
if (error_5 instanceof AbortError) {
return [2 /*return*/];
}
throw this.handleError(error_5, 'Upload failed');
case 4: return [2 /*return*/];
}
});
});
};
/**
* Resumes an upload from a previously created session
*/
UploadxClient.prototype.resumeUpload = function (url, data, metadata, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var response, start, totalSize, error_6;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
return [4 /*yield*/, this.getUploadStatus(url, metadata, signal)];
case 1:
response = _a.sent();
start = response.uploadedBytes;
totalSize = metadata.size;
return [4 /*yield*/, this.uploadDataInChunks(data, url, start, totalSize, onProgress, signal)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
error_6 = _a.sent();
// Don't throw if the operation was aborted
if (error_6 instanceof AbortError) {
return [2 /*return*/];
}
throw this.handleError(error_6, 'Upload failed');
case 4: return [2 /*return*/];
}
});
});
};
/**
* Resumes a file upload from disk (Node.js only)
*/
UploadxClient.prototype.resumeFileUpload = function (url, filePath, metadata, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var response, start, totalSize, error_7;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!isNode) {
throw new Error('uploadAsFile is only available in Node.js environment');
}
_a.label = 1;
case 1:
_a.trys.push([1, 4, , 5]);
return [4 /*yield*/, this.getUploadStatus(url, metadata, signal)];
case 2:
response = _a.sent();
start = response.uploadedBytes;
totalSize = metadata.size;
return [4 /*yield*/, this.uploadFileInChunks(url, filePath, start, totalSize, onProgress, signal)];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
error_7 = _a.sent();
// Don't throw if the operation was aborted
if (error_7 instanceof AbortError) {
return [2 /*return*/];
}
throw this.handleError(error_7, "File upload failed for ".concat(filePath));
case 5: return [2 /*return*/];
}
});
});
};
/**
* Gets the current upload progress from the server
*/
UploadxClient.prototype.getUploadStatus = function (url, metadata, signal) {
return __awaiter(this, void 0, void 0, function () {
var headers, response, rangeHeader, uploadedBytes;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
headers = {
'Content-Type': 'application/octet-stream',
'Content-Range': "bytes */".concat((metadata === null || metadata === void 0 ? void 0 : metadata.size) || '*'),
};
return [4 /*yield*/, this.client.put(url, null, {
headers: headers,
signal: signal,
})];
case 1:
response = _b.sent();
rangeHeader = (_a = response === null || response === void 0 ? void 0 : response.headers) === null || _a === void 0 ? void 0 : _a.range;
uploadedBytes = this.parseRangeHeader(rangeHeader);
return [2 /*return*/, { uploadedBytes: uploadedBytes }];
}
});
});
};
UploadxClient.prototype.uploadChunk = function (url, data, start, end, totalSize, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var headers, response, rangeHeader, uploadedBytes;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
headers = {
'Content-Type': 'application/octet-stream',
'Content-Range': "bytes ".concat(start, "-").concat(end - 1, "/").concat(totalSize),
};
return [4 /*yield*/, this.client.put(url, data, {
headers: headers,
signal: signal,
onUploadProgress: onProgress
? function (progressEvent) {
var chunkUploaded = progressEvent.loaded;
var totalUploaded = start + chunkUploaded;
var progress = totalUploaded / totalSize;
onProgress(Math.min(progress, 1));
}
: undefined,
})];
case 1:
response = _b.sent();
rangeHeader = (_a = response === null || response === void 0 ? void 0 : response.headers) === null || _a === void 0 ? void 0 : _a.range;
uploadedBytes = this.parseRangeHeader(rangeHeader);
return [2 /*return*/, { uploadedBytes: uploadedBytes }];
}
});
});
};
UploadxClient.prototype.uploadDataInChunks = function (data, url, start, totalSize, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.isBlob(data)) return [3 /*break*/, 2];
return [4 /*yield*/, this.uploadBlobInChunks(url, data, start, totalSize, onProgress, signal)];
case 1:
_a.sent();
return [3 /*break*/, 6];
case 2:
if (!this.isStream(data)) return [3 /*break*/, 4];
return [4 /*yield*/, this.uploadStreamInChunks(url, data, start, totalSize, onProgress, signal)];
case 3:
_a.sent();
return [3 /*break*/, 6];
case 4: return [4 /*yield*/, this.uploadBufferInChunks(url, data, start, totalSize, onProgress, signal)];
case 5:
_a.sent();
_a.label = 6;
case 6: return [2 /*return*/];
}
});
});
};
UploadxClient.prototype.uploadFileInChunks = function (url, filePath, start, totalSize, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var createReadStream, position, end, fileChunk, result, newPosition;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!isNode) {
throw new Error('uploadAsFile is only available in Node.js environment');
}
return [4 /*yield*/, import('node:fs')];
case 1:
createReadStream = (_a.sent()).createReadStream;
position = start;
_a.label = 2;
case 2:
if (!(position < totalSize)) return [3 /*break*/, 4];
// Check if the operation was aborted
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new AbortError();
}
end = Math.min(position + this.chunkSize, totalSize);
fileChunk = createReadStream(filePath, {
start: position,
end: end,
});
return [4 /*yield*/, this.uploadChunk(url, fileChunk, position, end, totalSize, onProgress, signal)];
case 3:
result = _a.sent();
position = end;
if (result.uploadedBytes !== undefined) {
newPosition = result.uploadedBytes;
if (newPosition > position) {
position = newPosition;
}
}
return [3 /*break*/, 2];
case 4: return [2 /*return*/];
}
});
});
};
UploadxClient.prototype.uploadBlobInChunks = function (url, blob, start, totalSize, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var position, end, chunk;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
position = start;
_a.label = 1;
case 1:
if (!(position < totalSize)) return [3 /*break*/, 3];
// Check if the operation was aborted
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new AbortError();
}
end = Math.min(position + this.chunkSize, totalSize);
chunk = blob.slice(position, end);
return [4 /*yield*/, this.uploadChunk(url, chunk, position, end, totalSize, onProgress, signal)];
case 2:
_a.sent();
position = end;
return [3 /*break*/, 1];
case 3: return [2 /*return*/];
}
});
});
};
UploadxClient.prototype.uploadStreamInChunks = function (url, stream, start, totalSize, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var position, chunks, chunksSize;
var _this = this;
return __generator(this, function (_a) {
if (!isNode) {
throw new Error('Stream uploads are only available in Node.js environment');
}
position = start;
chunks = [];
chunksSize = 0;
return [2 /*return*/, new Promise(function (resolve, reject) {
var abortHandler = function () {
reject(new AbortError());
};
if (signal) {
if (signal.aborted) {
abortHandler();
return;
}
signal.addEventListener('abort', abortHandler);
}
stream.on('data', function (chunk) {
chunks.push(chunk);
chunksSize += chunk.length;
var _loop_1 = function () {
var end = Math.min(position + _this.chunkSize, totalSize);
var chunkBuffer = Buffer.concat(chunks);
chunks = [chunkBuffer.slice(end - position)];
chunksSize = chunkBuffer.length - (end - position);
stream.pause();
_this.uploadChunk(url, chunkBuffer.slice(0, end - position), position, end, totalSize, onProgress, signal)
.then(function () {
position = end;
stream.resume();
})
.catch(function (error) {
reject(error);
});
};
while (chunksSize >= _this.chunkSize) {
_loop_1();
}
});
stream.on('end', function () { return __awaiter(_this, void 0, void 0, function () {
var end, chunkBuffer, error_8;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (signal) {
signal.removeEventListener('abort', abortHandler);
}
if (!(chunks.length > 0 && position < totalSize)) return [3 /*break*/, 4];
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
reject(new AbortError());
return [2 /*return*/];
}
end = Math.min(position + chunksSize, totalSize);
chunkBuffer = Buffer.concat(chunks);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.uploadChunk(url, chunkBuffer, position, end, totalSize, onProgress, signal)];
case 2:
_a.sent();
position = end;
return [3 /*break*/, 4];
case 3:
error_8 = _a.sent();
reject(error_8);
return [2 /*return*/];
case 4:
resolve();
return [2 /*return*/];
}
});
}); });
stream.on('error', function (error) {
if (signal) {
signal.removeEventListener('abort', abortHandler);
}
reject(error);
});
})];
});
});
};
UploadxClient.prototype.uploadBufferInChunks = function (url, buffer, start, totalSize, onProgress, signal) {
return __awaiter(this, void 0, void 0, function () {
var view, position, end, chunk;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
view = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
position = start;
_a.label = 1;
case 1:
if (!(position < totalSize)) return [3 /*break*/, 3];
// Check if the operation was aborted
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
throw new AbortError();
}
end = Math.min(position + this.chunkSize, totalSize);
chunk = view.subarray(position, end);
return [4 /*yield*/, this.uploadChunk(url, chunk, position, end, totalSize, onProgress, signal)];
case 2:
_a.sent();
position = end;
return [3 /*break*/, 1];
case 3: return [2 /*return*/];
}
});
});
};
UploadxClient.prototype.isBlob = function (data) {
return typeof Blob !== 'undefined' && data instanceof Blob;
};
UploadxClient.prototype.isStream = function (data) {
return !!data && typeof data.pipe === 'function';
};
UploadxClient.prototype.parseRangeHeader = function (range) {
if (!range)
return 0;
var matches = range.match(/bytes=\d+-(\d+)/);
return matches ? Number.parseInt(matches[1], 10) + 1 : 0;
};
UploadxClient.prototype.handleError = function (error, context) {
var _a, _b, _c;
if (error instanceof AbortError) {
return error;
}
if (axios.isAxiosError(error)) {
return new Error("".concat(context, ": ").concat(error.message, " ").concat(((_c = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.message) || error.cause, " "));
}
if (error instanceof Error) {
return new Error("".concat(context, ": ").concat(error.message));
}
return new Error(context);
};
return UploadxClient;
}());
// biome-ignore lint/suspicious/noExplicitAny: make public
window.UploadxClient = UploadxClient;
return UploadxClient;
}));
//# sourceMappingURL=index.js.map