UNPKG

@tinyuploader/sdk

Version:

大文件分片上传解决方案sdk, 可用于各种UI框架

967 lines (966 loc) 30.8 kB
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 };