@tinyuploader/sdk
Version:
大文件分片上传解决方案sdk, 可用于各种UI框架
967 lines (966 loc) • 30.8 kB
JavaScript
var H = Object.defineProperty;
var O = (r, e, s) => e in r ? H(r, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : r[e] = s;
var n = (r, e, s) => O(r, typeof e != "symbol" ? e + "" : e, s);
import j from "spark-md5";
const w = (r, e) => toString.call(r).slice(8, -1).toLowerCase() === e, W = function(r) {
return typeof r < "u";
}, m = function(r) {
return typeof r == "function";
}, M = (r) => w(r, "object"), P = (r) => M(r) && r !== null, N = (r) => w(r, "blob"), U = (r) => w(r, "array"), q = (r) => r && m(r.then), B = function(r) {
return w(r, "string");
}, I = function(r) {
return w(r, "boolean");
};
let $ = 0;
const E = (r = "id") => `${r}-${+/* @__PURE__ */ new Date()}-${$++}`;
function R(r, e, s) {
if (U(r)) {
const a = r;
for (var t = 0, i = a.length; t < i && e.call(s, a[t], t, a) !== !1; t++)
;
} else {
const a = r;
for (const h in a)
if (Object.prototype.hasOwnProperty.call(a, h) && e.call(s, a[h], h, a) === !1)
break;
}
}
function v() {
var r, e, s, t, i, a, h = arguments[0] || {}, o = 1, c = arguments.length, p = !1;
for (typeof h == "boolean" && (p = h, h = arguments[1] || {}, o++), typeof h != "object" && !m(h) && (h = {}), o === c && (h = this, o--); o < c; o++)
if ((r = arguments[o]) != null)
for (e in r)
s = h[e], t = r[e], h !== t && (p && t && (P(t) || (i = U(t))) ? (i ? (i = !1, a = s && U(s) ? s : []) : a = s && P(s) ? s : {}, h[e] = v(p, a, t)) : t !== void 0 && (h[e] = t));
return h;
}
const k = (r) => typeof r == "function" ? r() || {} : P(r) ? r : {}, G = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, _ = (r) => {
if (!r || isNaN(Number(r)) || Number(r) <= 0 || r === "") return "0 B";
const e = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], s = 1024, t = Number(r), i = Math.floor(Math.log(t) / Math.log(s));
return `${(t / Math.pow(s, i)).toFixed(2)} ${e[Math.min(i, e.length - 1)]}`;
}, K = (r = 600, e = !1) => new Promise((s, t) => {
const i = setTimeout(() => {
clearTimeout(i), e ? t(!1) : s(!0);
}, r);
}), T = (r, e = 300) => {
let s = 0;
return function(...t) {
let i = +/* @__PURE__ */ new Date();
i - s > e && (s = i, r.apply(this, t));
};
}, he = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
each: R,
extend: v,
generateUid: E,
isArray: U,
isBlob: N,
isBoolean: I,
isDefined: W,
isFunction: m,
isObject: M,
isPlainObject: P,
isPromise: q,
isString: B,
parseData: k,
renderSize: _,
sleep: K,
slice: G,
throttle: T
}, Symbol.toStringTag, { value: "Module" }));
class Q {
constructor(e) {
n(this, "uploader");
n(this, "listeners");
n(this, "inputs");
this.uploader = e, this.listeners = [], this.inputs = [];
}
assignBrowse(e, s = {}) {
if (!e) {
console.warn("DOM 节点不存在");
return;
}
const t = this.createOrGetInput(e);
this.setInputAttributes(t, s), this.attachBrowseEvents(e, t);
}
assignDrop(e) {
if (!e) {
console.warn("DOM 节点不存在");
return;
}
const s = (i) => i.preventDefault(), t = {
dragover: s,
dragenter: s,
dragleave: s,
drop: this.handleDrop.bind(this)
};
R(t, (i, a) => {
e.addEventListener(a, i, { passive: !1 }), this.listeners.push({ node: e, event: a, handler: i });
});
}
createOrGetInput(e) {
if (e instanceof HTMLInputElement && e.tagName === "INPUT" && e.type === "file")
return e;
const s = document.createElement("input");
return s.type = "file", v(s.style, {
visibility: "hidden",
position: "absolute",
width: "1px",
height: "1px"
}), e.appendChild(s), this.inputs.push(s), s;
}
setInputAttributes(e, s) {
R(s, (t, i) => e.setAttribute(i, t)), e.toggleAttribute("multiple", !!s.multiple);
}
attachBrowseEvents(e, s) {
const t = () => s.click(), i = (a) => {
const h = a.target;
this.uploader.addFiles(h.files), h.value = "";
};
e.addEventListener("click", t, { passive: !0 }), s.addEventListener("change", i, { passive: !0 }), this.listeners.push(
{ node: e, event: "click", handler: t },
{ node: s, event: "change", handler: i }
);
}
handleDrop(e) {
var s;
e.preventDefault(), e.stopPropagation(), e instanceof DragEvent && this.uploader.addFiles((s = e.dataTransfer) == null ? void 0 : s.files);
}
destroy() {
return this.listeners.forEach(({ node: e, event: s, handler: t }) => {
e.removeEventListener(s, t);
}), this.listeners = [], this.inputs.forEach((e) => {
e.parentNode && e.parentNode.removeChild(e);
}), this.inputs = [], this;
}
}
class V {
constructor() {
n(this, "events");
this.events = /* @__PURE__ */ new Map();
}
on(e, s) {
if (!m(s)) return;
const t = this.events.get(e) || [];
t.includes(s) || (t.push(s), this.events.set(e, t));
}
off(e, s) {
var i;
if (!this.events.has(e)) return;
if (!s) {
this.events.set(e, []);
return;
}
const t = (i = this.events.get(e)) == null ? void 0 : i.filter((a) => a !== s);
this.events.set(e, t);
}
emit(e, ...s) {
const t = this.events.get(e);
t && t.length && t.forEach((i) => i(...s));
}
once(e, s) {
if (!m(s)) return;
const t = (...i) => {
s(...i), this.off(e, t);
};
this.on(e, t);
}
clear(e) {
if (!e) {
this.events.clear();
return;
}
this.events.has(e) && this.events.set(e, []);
}
}
const l = {
/** 文件初始化状态 */
Init: "init",
/** 文件添加失败, 添加文件时允许beforeAdd中失败的文件添加到列表,但是状态为AddFail */
AddFail: "addFail",
/** 文件读取中(计算hash中) */
Reading: "reading",
/** 文件hash计算完成;准备上传 */
Ready: "ready",
/** checkRequest 存在时,切checkRequest失败 */
CheckFail: "checkFail",
/** 文件上传中 */
Uploading: "uploading",
/** 文件上传完成;所有chunk上传完成,准备合并文件 */
UploadSuccess: "uploadSuccess",
/** 文件上传失败;有chunk上传失败 */
UploadFail: "uploadFail",
/** 文件上传成功 且 合并成功 */
Success: "success",
/** 文件合并失败 */
Fail: "fail",
/** 文件暂停上传 */
Pause: "pause",
/** 文件恢复上传 */
Resume: "resume",
/** 文件删除 */
Removed: "removed"
}, f = {
/** chunk初始化状态是Ready */
Ready: "ready",
/** chunk创建请求成功,Promise处于Pending状态 */
Pending: "pending",
/** chunk上传中 */
Uploading: "uploading",
/** chunk上传成功 */
Success: "success",
/** chunk上传失败(所有重试次数完成后 都不成功) */
Fail: "fail"
}, d = {
/** 文件超出limit限制 */
Exceed: "exceed",
/** 单个文件添加成功 */
FileAdded: "fileAdded",
/** 文件添加失败 */
FileAddFail: "fileAddFail",
/** 所有文件添加成功 */
FilesAdded: "filesAdded",
/** 文件状态改变 */
FileChange: "fileChange",
/** 文件删除 */
FileRemove: "fileRemove",
/** 文件开始计算hash */
FileReadStart: "fileReadStart",
/** 文件计算进度 */
FileReadProgress: "fileReadProgress",
/** 文件hash计算完成 */
FileReadEnd: "fileReadEnd",
FileReadFail: "fileReadFail",
/** 暂停操作 */
FilePause: "filePause",
/** 重新上传操作 */
FileResume: "fileResume",
/** 文件上传进度 */
FileProgress: "fileProgress",
/** 文件上传成功 */
FileUploadSuccess: "fileUploadSuccess",
/** 文件上传失败 */
FileUploadFail: "fileUploadFail",
/** 文件合并成功 */
FileSuccess: "fileSuccess",
/** 文件上传失败合并失败 */
FileFail: "fileFail",
/** 所有文件上传成功 */
AllFileSuccess: "allFileSuccess"
}, b = {
/** 文件还没上传 */
None: "none",
/** 部分上传成功 */
Part: "part",
/** 准备合并 */
WaitMerge: "waitMerge",
/** 上传成功 */
Success: "success"
}, L = {
/** check接口 */
Check: "check",
/** upload chunk接口 */
Upload: "upload",
/** merge接口 */
Merge: "merge"
};
function X(r) {
const {
method: e = "POST",
withCredentials: s = !0,
responseType: t = "json",
action: i,
data: a,
// query,
headers: h,
onSuccess: o,
onFail: c,
onProgress: p
} = r;
let u = new XMLHttpRequest();
u.responseType = t, u.withCredentials = s, u.open(e, i, !0);
const y = new FormData();
return Object.entries(a).forEach(([g, S]) => y.append(g, S)), "setRequestHeader" in u && Object.entries(h).forEach(([g, S]) => u.setRequestHeader(g, S)), u.addEventListener("timeout", () => {
c && c(new Error("Request timed out"), u);
}), u.upload.addEventListener("progress", (g) => {
p && p(g);
}), u.addEventListener(
"error",
(g) => {
c && c(g, u);
},
!1
), u.addEventListener("readystatechange", (g) => {
if (u.readyState === 4) {
if (u.status < 200 || u.status >= 300) {
c && c(new Error(`xhr: status === ${u.status}`), u);
return;
}
o && o(g, u);
}
}), u.send(y), {
abort() {
u.abort(), u = null;
}
};
}
class Y {
constructor(e, s) {
/** Uploader实例 */
n(this, "uploader");
/** Uploader配置 */
n(this, "options");
/** FileContext实例 */
n(this, "file");
/** 文件唯一ID */
n(this, "fileId");
/** 文件二进制数据 */
n(this, "rawFile");
/** 文件hash值 */
n(this, "fileHash");
/** 文件名称 */
n(this, "filename");
/** 文件大小 */
n(this, "totalSize");
/** 分片大小 */
n(this, "chunkSize");
/** 分片总数 */
n(this, "totalChunks");
/** chunk唯一id */
n(this, "uid");
/** chunk在索引值 */
n(this, "chunkIndex");
/** chunk状态 */
n(this, "status");
/** chunk bit 起始位置 */
n(this, "startByte");
/** chunk bit 结束位置 */
n(this, "endByte");
/** chunk 大小 */
n(this, "size");
/** chunk最大重试次数 */
n(this, "maxRetries");
/** chunk 真实上传进度 */
n(this, "progress");
/** chunk fake进度 */
n(this, "fakeProgress");
/** timer */
n(this, "timer");
/** 上传请求 */
n(this, "request");
/** 自定义上传请求 */
n(this, "customRequest");
this.uploader = e.uploader, this.options = e.uploader.options, this.file = e, this.rawFile = e.rawFile, this.fileId = e.uid, this.fileHash = e.hash, this.filename = e.name, this.totalSize = e.size, this.chunkSize = this.options.chunkSize, this.totalChunks = e.totalChunks, this.uid = E(), this.chunkIndex = s, this.status = f.Ready, this.startByte = this.chunkSize * s, this.endByte = Math.min(this.startByte + this.chunkSize, this.totalSize), this.size = this.endByte - this.startByte, this.maxRetries = this.options.maxRetries, this.progress = 0, this.fakeProgress = 0, this.timer = null, this.request = null, this.customRequest = this.options.customRequest || X;
}
onSuccess(e, s, t, i) {
this.options.requestSucceed(s) ? (this.status = f.Success, this.file.removeUploadingChunk(this), this.file.isUploading() && this.file.upload(), t(this)) : this.onFail(e, i);
}
onFail(e, s) {
var t;
this.progress = 0, this.file.setProgress(), !((t = this.request) != null && t.canceled) && (this.maxRetries <= 0 ? (this.file.removeUploadingChunk(this), this.status = f.Fail, this.file.isUploading() && this.file.upload(), s(e, this)) : this.timer = setTimeout(() => {
this.send(), this.maxRetries--, clearTimeout(this.timer);
}, this.options.retryInterval));
}
onProgress(e) {
this.progress = Math.min(1, e.loaded / e.total), this.fakeProgress = Math.max(this.progress, this.fakeProgress), this.status = f.Uploading, this.file.changeStatus(l.Uploading), this.file.setProgress();
}
prepare() {
const { name: e, data: s, processData: t } = this.options, { data: i } = this.file, a = {
[e]: this.file.rawFile.slice(this.startByte, this.endByte),
hash: this.fileHash,
id: this.uid,
fileId: this.fileId,
index: this.chunkIndex,
filename: this.filename,
size: this.size,
totalSize: this.totalSize,
totalChunks: this.totalChunks,
...k(s),
...i
};
return m(t) && t(a, L.Upload) || a;
}
send() {
this.status = f.Pending;
const { action: e, headers: s, withCredentials: t, name: i } = this.options;
return new Promise((a, h) => {
this.request = this.customRequest({
action: e,
name: i,
withCredentials: t,
headers: k(s),
data: this.prepare(),
query: {
...k(this.options.data),
...this.file.data
},
onSuccess: (o, c) => this.onSuccess(o, c, a, h),
onFail: (o) => this.onFail(o, h),
onProgress: (o) => this.onProgress(o)
}), this.request.canceled = !1;
});
}
abort() {
this.status = f.Ready, this.request && (this.request.canceled = !0, this.request.abort()), this.timer && clearTimeout(this.timer);
}
}
class z {
constructor(e, s, t) {
/** uploader实例 */
n(this, "uploader");
/** uploader配置项 */
n(this, "options");
/** 计算hash的方法 */
n(this, "hasher");
/** 文件ID */
n(this, "id");
/** 文件唯一ID */
n(this, "uid");
/** 文件状态 */
n(this, "status");
/** 文件状态变更记录 */
n(this, "prevStatusLastRecord");
/** 文件二进制 */
n(this, "rawFile");
/** 文件名称 */
n(this, "name");
/** 文件大小 */
n(this, "size");
/** 文件类型 */
n(this, "type");
/** 文件hash值 */
n(this, "hash");
/** 文件http地址 */
n(this, "url");
/** 文件上传进度 */
n(this, "progress");
/** 分片大小 */
n(this, "chunkSize");
/** 分块chunk集合 */
n(this, "chunks");
/** 分片总数 */
n(this, "totalChunks");
/** 上传中chunk集合 */
n(this, "uploadingChunks");
/** 文件读取进度(hash计算进度) */
n(this, "readProgress");
/** 错误信息 */
n(this, "errorMessage");
/** 文件自定义data */
n(this, "data");
/** abortRead */
n(this, "abortRead");
this.uploader = s, this.options = this.uploader.options, this.hasher = this.uploader.hasher, this.uid = this.generateId(), this.prevStatusLastRecord = [], this.rawFile = e, this.name = e.name, this.size = e.size, this.type = e.type, this.hash = "", this.url = "", this.status = "", this.progress = 0, this.chunkSize = this.options.chunkSize, this.chunks = [], this.totalChunks = 0, this.uploadingChunks = /* @__PURE__ */ new Set(), this.readProgress = 0, this.errorMessage = "", this.data = {}, t ? (Object.keys(t).forEach((i) => {
this[i] = t[i];
}), this.name = t.name, this.url = t.url, this.readProgress = 1, this.progress = 1, this.changeStatus(l.Success)) : this.changeStatus(l.Init);
}
generateId() {
const { customGenerateUid: e } = this.options;
return !e || !m(e) ? E() : e(this) || E();
}
setErrorMessage(e) {
return this.errorMessage = String(e), this;
}
setData(e) {
return this.data = { ...this.data, ...e }, this;
}
get renderSize() {
return _(this.size);
}
changeStatus(e) {
(e !== this.status || e === l.Reading) && (this.prevStatusLastRecord.push(this.status), this.status = e, this.uploader && this.uploader.emitCallback && this.uploader.emitCallback(d.FileChange, this));
}
isInit() {
return this.status === l.Init;
}
isAddFail() {
return this.status === l.AddFail;
}
isReading() {
return this.status === l.Reading;
}
isReady() {
return this.status === l.Ready;
}
isCheckFail() {
return this.status === l.CheckFail;
}
isUploading() {
return this.status === l.Uploading;
}
isUploadSuccess() {
return this.status === l.UploadSuccess;
}
isUploadFail() {
return this.status === l.UploadFail;
}
isSuccess() {
return this.status === l.Success;
}
isFail() {
return this.status === l.Fail;
}
isPause() {
return this.status === l.Pause;
}
isResume() {
return this.status === l.Resume;
}
createChunks() {
this.totalChunks = Math.ceil(this.size / this.chunkSize) || 1, this.chunks = Array.from({ length: this.totalChunks }, (e, s) => new Y(this, s));
}
async read() {
if (!this.options.withHash) {
this.createChunks(), this.changeStatus(l.Ready);
return;
}
this.uploader.emitCallback(d.FileReadStart, this), this.changeStatus(l.Reading);
try {
const e = Date.now(), { hash: s, progress: t } = await this._computeHash();
this.hash = s, this.readProgress = t, this.uploader.emitCallback(d.FileReadEnd, this), console.log(
`${this.options.useWebWoker ? "Web Worker" : "Main Thread"} read file took`,
(Date.now() - e) / 1e3,
"s"
), this.abortRead = null;
} catch {
throw this.setErrorMessage("File read failed"), this.changeStatus(l.Init), this.uploader.emitCallback(d.FileReadFail, this), new Error("File read failed");
} finally {
}
this.createChunks(), this.changeStatus(l.Ready);
}
async _computeHash() {
try {
const e = T((a) => {
this.readProgress = a, this.uploader.emitCallback(d.FileReadProgress, this);
}, 200);
if (this.options.useWebWoker && this.hasher.hashionName !== "sparkMd5Webworker")
throw new Error(`
Please install "SparkWorker" plugin -> npm i hashion;
https://www.npmjs.com/package/hashion
`);
const { promise: s, abort: t } = this.hasher.computedHash(
{
file: this.rawFile,
chunkSize: this.chunkSize
// useWebWoker: this.options.useWebWoker
},
({ progress: a }) => e(a)
);
this.abortRead = t;
const i = await s;
return e(i.progress), i;
} catch (e) {
throw e;
}
}
_processData(e) {
const { data: s, processData: t } = this.options, i = { ...k(s), ...this.data };
return m(t) && t(i, e) || i;
}
async checkRequest() {
const { checkRequest: e } = this.options;
if (!m(e))
return Promise.resolve();
const s = (i) => {
this.chunks.forEach((a) => {
a.status = i, i === f.Success && (a.progress = 1, a.fakeProgress = 1);
});
}, t = {
[b.Part]: (i) => {
this.chunks.forEach((a) => {
i.includes(a.chunkIndex) && (a.status = f.Success, a.progress = 1, a.fakeProgress = 1);
});
},
[b.WaitMerge]: () => {
this.changeStatus(l.UploadSuccess), s(f.Success);
},
[b.Success]: (i) => {
this.changeStatus(l.Success), s(f.Success), this.url = i;
},
[b.None]: () => {
}
};
try {
const i = await Promise.resolve(
e(this, this._processData(L.Check), k(this.options.headers))
);
if (!i || !i.status)
throw new Error("Invalid check response format");
const a = t[i.status];
if (!a)
throw new Error(`Unknown check status: ${i.status}`);
return a(i.data), Promise.resolve();
} catch (i) {
this.changeStatus(l.CheckFail), this.uploader.upload();
const a = new Error(`Check request failed: ${i.message}`);
throw a.originalError = i, a;
}
}
addUploadingChunk(e) {
this.uploadingChunks.add(e);
}
removeUploadingChunk(e) {
this.uploadingChunks.delete(e);
}
async upload() {
if (this.isInit() && await this.read(), this.isReady() && this.options.checkRequest && (await this.checkRequest(), this.status === l.Pause))
return;
if (this.isUploadSuccess())
return this.merge();
if (this.isSuccess())
return this.success();
const e = this.chunks.filter((t) => t.status === f.Ready);
if (R(e, () => {
if (this.uploadingChunks.size >= this.options.maxConcurrency)
return !1;
const t = e.shift();
if (t)
this.addUploadingChunk(t);
else
return !1;
}), this.uploadingChunks.size > 0) {
const t = [...this.uploadingChunks].filter(
(i) => i.status === f.Ready
);
Promise.race(t.map((i) => i.send()));
return;
}
this.chunks.some((t) => t.status === f.Fail) ? this.uploadFail() : (this.uploadSuccess(), this.setProgress(), this.merge());
}
setProgress() {
const e = this.chunks.reduce((s, t) => {
const i = this.options.fakeProgress ? t.fakeProgress : t.progress;
return s += i * (t.size / this.size);
}, 0);
this.progress = Math.min(1, e), (this.isUploadSuccess() || this.isSuccess()) && (this.progress = 1), this.uploader.emitCallback(d.FileProgress, this);
}
uploadFail() {
this.changeStatus(l.UploadFail), this.uploader.emitCallback(d.FileUploadFail, this), this._continueUpload();
}
uploadSuccess() {
this.changeStatus(l.UploadSuccess), this.uploader.emitCallback(d.FileUploadSuccess, this);
}
async merge() {
const { mergeRequest: e } = this.options;
if (!m(e))
return this.success();
try {
const s = e(
this,
this._processData(L.Merge),
k(this.options.headers)
), t = await Promise.resolve(s);
I(t) ? t ? this.success() : this.mergeFail() : (this.url = t, this.success());
} catch (s) {
console.log(s), this.mergeFail();
}
}
mergeFail() {
this.changeStatus(l.Fail), this.uploader.emitCallback(d.FileFail, this), this._continueUpload();
}
success() {
this.changeStatus(l.Success), this.progress = 1, this.uploader.emitCallback(d.FileSuccess, this), this._continueUpload();
}
_continueUpload() {
const e = this.uploader.fileList.find((s) => s.isPause());
e && e.resume(), this.uploader.upload();
}
cancel() {
this.uploadingChunks.forEach((e) => e.abort()), this.uploadingChunks.clear();
}
async remove() {
this.abortRead && this.abortRead(), setTimeout(() => {
this.cancel(), this.chunks = [], this.changeStatus("removed");
const e = this.uploader.fileList.indexOf(this);
e > -1 && this.uploader.fileList.splice(e, 1), this.uploader.emitCallback(d.FileRemove, this), this.uploader.upload();
}, 0);
}
pause() {
this.abortRead && this.abortRead(), setTimeout(() => {
this.cancel(), this.changeStatus(l.Pause), this.uploader.emitCallback(d.FilePause, this), this.uploader.upload();
}, 0);
}
resume() {
this.isPause() && (this.changeStatus(l.Resume), this.uploader.emitCallback(d.FileResume, this), this.uploader.upload());
}
retry() {
if (!this.isAddFail()) {
if (this.isCheckFail()) {
this.changeStatus(l.Ready), this.upload();
return;
}
if (this.isUploadSuccess() || this.isFail()) {
this.merge();
return;
}
this.isUploadFail() && (R(this.chunks, (e) => {
e.status === f.Fail && (e.status = f.Ready, e.maxRetries = e.options.maxRetries);
}), this.upload());
}
}
}
const Z = {
// input 属性相关
accept: "*",
multiple: !0,
// 文件相关
fileList: [],
limit: 10,
autoUpload: !0,
customGenerateUid: void 0,
beforeAdd: (r) => !0,
beforeRemove: (r) => !0,
addFailToRemove: !0,
chunkSize: 2 * 1024 * 1024,
// 2M
fakeProgress: !0,
withHash: !0,
useWebWoker: !1,
// 上传逻辑相关
name: "file",
action: "",
customRequest: null,
withCredentials: !0,
headers: {},
data: {},
requestSucceed: (r) => [200, 201, 202, 206].includes(r.status),
maxConcurrency: 6,
maxRetries: 3,
retryInterval: 1e3,
checkRequest: (r) => ({ status: b.None }),
mergeRequest: (r) => !0,
processData: (r, e) => r
};
var J = Object.defineProperty, ee = (r, e, s) => e in r ? J(r, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : r[e] = s, D = (r, e, s) => ee(r, typeof e != "symbol" ? e + "" : e, s);
class se {
constructor(e, s) {
D(this, "hashCarrier"), D(this, "hashionName"), this.hashionName = e.name, this.hashCarrier = new e(s);
}
computedHash({ file: e, chunkSize: s }, t) {
let i;
return {
promise: new Promise((a, h) => {
Promise.resolve(
this.hashCarrier.computeHash(
{
file: e,
chunkSize: s
},
(o, { progress: c, hash: p, time: u }) => {
o && h(o), c === 100 && a({ progress: c, hash: p, time: u }), t && t({ progress: c });
}
)
).then((o) => {
i = { abort: o == null ? void 0 : o.abort, reject: h };
});
}),
abort: () => {
i && (i.abort && i.abort(), i.reject(new Error("Canceled promise to rejected")));
}
};
}
}
var te = Object.defineProperty, ie = (r, e, s) => e in r ? te(r, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : r[e] = s, A = (r, e, s) => ie(r, typeof e != "symbol" ? e + "" : e, s);
const re = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
class x {
constructor() {
A(this, "name"), this.name = "spark-md5";
}
async computeHash(e, s) {
const { file: t, chunkSize: i } = e;
let a = !1;
const h = new j.ArrayBuffer(), o = new FileReader(), c = Math.ceil(t.size / i), p = Date.now();
let u = 0;
const y = new AbortController(), g = y.signal;
g.addEventListener("abort", () => {
a || (o.abort(), s(new Error("Hash calculation cancelled"), { progress: 0 }));
}), o.onload = function(F) {
var C;
if (!g.aborted)
if (h.append((C = F.target) == null ? void 0 : C.result), u++, u < c)
S(), s(null, {
progress: u / c * 100
});
else {
a = !0, s(null, {
hash: h.end(),
time: Date.now() - p,
progress: 100
});
return;
}
}, o.onerror = function(F) {
g.aborted || (console.warn("spark-md5: Hash calculation error"), s(F, { progress: 0 }));
};
function S() {
if (g.aborted) return;
const F = u * i, C = F + i >= t.size ? t.size : F + i;
o.readAsArrayBuffer(re.call(t, F, C));
}
return S(), {
abort: () => y.abort()
};
}
}
A(x, "pluginName", "hash-plugin"), A(x, "name", "spark-md5");
class ae {
constructor(e) {
/** 挂载事件容器实例 */
n(this, "container");
/** 事件派发监听 */
n(this, "event");
/** 配置项 */
n(this, "options");
/** 计算hash方法实例 */
n(this, "hasher");
/** 文件列表 */
n(this, "fileList");
this.container = new Q(this), this.event = new V(), this.options = v(Z, e), this.hasher = null, this.fileList = (this.options.fileList || []).map((s) => new z(s, this, s)), this._setupFileListeners(), this.use(x);
}
on(e, s) {
this.event.on(e, s);
}
emit(e, ...s) {
this.event.emit(e, ...s);
}
emitCallback(e, ...s) {
this.emit(e, ...s, this.fileList);
}
updateData(e) {
this.options.data = e;
}
updateHeaders(e) {
this.options.headers = e;
}
setOption(e) {
this.options = v(this.options, e);
}
use(e) {
e.pluginName === "hash-plugin" && (this.hasher = new se(e));
}
formatAccept(e) {
return B(e) ? e : Array.isArray(e) ? e.join(",") : "";
}
assignBrowse(e, s = {}) {
const { accept: t, ...i } = s, a = {
multiple: this.options.multiple,
accept: this.formatAccept(t || this.options.accept)
};
this.container.assignBrowse(e, v({}, a, i));
}
assignDrop(e) {
this.container.assignDrop(e);
}
_setupFileListeners() {
const e = (s, t) => {
if (!t.length)
return;
t.every((a) => a.isSuccess()) && this.emit(d.AllFileSuccess, this.fileList);
};
this.on(d.FileSuccess, e), this.on(d.FileRemove, e);
}
setDefaultFileList(e) {
e.forEach((s) => {
this.fileList.push(new z(s, this, s));
});
}
async addFiles(e) {
const { limit: s, multiple: t, addFailToRemove: i, beforeAdd: a, autoUpload: h } = this.options;
let o = [...e];
if (o.length === 0) return;
if (s > 0 && o.length + this.fileList.length > s) {
this.emitCallback(d.Exceed, o);
return;
}
t || (o = o.slice(0, 1));
const c = o.map((p) => new z(p, this, null));
await Promise.all(c.map((p) => this._handleFileAdd(p, a))), this.fileList = this.fileList.filter((p) => p.isAddFail() && i ? (this.doRemove(p), !1) : !0), c.length > 0 && this.emitCallback(d.FilesAdded, this.fileList), h && this.submit();
}
async _handleFileAdd(e, s) {
try {
if (m(s) && await s(e) === !1)
throw new Error("Before add rejected");
this.emitCallback(d.FileAdded, e);
} catch {
e.changeStatus(l.AddFail), this.emitCallback(d.FileAddFail, e);
}
this.fileList.push(e);
}
async upload() {
if (this.fileList.length !== 0)
for (let e = 0; e < this.fileList.length; e++) {
const s = this.fileList[e];
if (!(s.isAddFail() || s.isCheckFail())) {
if (s.isUploading() || s.isReading())
return;
if (s.isResume()) {
const t = s.prevStatusLastRecord[s.prevStatusLastRecord.length - 2];
t && s.changeStatus(t), s.upload();
return;
}
if (s.isReady() || s.isInit()) {
s.upload();
return;
}
}
}
}
submit() {
this.upload();
}
remove(e) {
const { beforeRemove: s } = this.options;
if (!s)
this.doRemove(e);
else if (m(s)) {
const t = s(e);
q(t) ? t.then(() => {
this.doRemove(e);
}) : t !== !1 && this.doRemove(e);
}
}
clear() {
for (let e = this.fileList.length - 1; e >= 0; e--)
this.fileList[e].remove();
this.fileList = [];
}
doRemove(e) {
if (!e) {
this.clear();
return;
}
e.remove();
}
pause(e) {
if (!e) return;
this.fileList.indexOf(e) > -1 && e.pause();
}
resume(e) {
if (!e) return;
this.fileList.filter((t) => t.isUploading() || t.isReading()).forEach((t) => {
t.pause();
}), e.resume();
}
retry(e) {
if (!e) return;
this.fileList.filter((i) => i.isUploading() || i.isReading()).forEach((i) => {
i.pause();
}), this.fileList.indexOf(e) > -1 && e.retry();
}
destroy() {
this.clear(), this.event.clear(), this.container.destroy();
}
}
const le = (r) => new ae(r);
export {
d as Callbacks,
b as CheckStatus,
Y as Chunk,
f as ChunkStatus,
z as FileContext,
l as FileStatus,
L as ProcessType,
ae as Uploader,
he as Utils,
le as create,
Z as defaultOptions
};