js-uploader
Version:
A JavaScript library for file upload
605 lines • 29.5 kB
JavaScript
import { __awaiter, __extends, __generator, __read, __spread } from "tslib";
import { StatusCode, EventType } from '../interface';
import { FileStore, FileDragger, FilePicker, getStorage } from './modules';
import { handle as handleTask } from './handlers';
import { tap, map, concatMap, mapTo, mergeMap, filter, first, switchMap, takeUntil, last, bufferCount, } from 'rxjs/operators';
import { from, Observable, throwError, Subscription, merge, Subject, fromEvent, race, of, scheduled, asapScheduler, asyncScheduler, iif, } from 'rxjs';
import Base from './Base';
import { isElectron } from '../utils';
import { taskFactory, fileFactory } from './helpers';
import { Logger } from '../shared';
// import * as packageJson from '../../package.json'
var defaultOptions = {
requestOptions: {
url: '/',
timeout: 0,
},
autoUpload: false,
computeFileHash: false,
computeChunkHash: false,
maxRetryTimes: 3,
retryInterval: 5000,
chunked: true,
chunkSize: 1024 * 1024 * 4,
chunkConcurrency: 1,
taskConcurrency: 1,
resumable: true,
};
var Uploader = /** @class */ (function (_super) {
__extends(Uploader, _super);
// static readonly version = (packageJson as any).version
function Uploader(options) {
var _this = _super.call(this, options === null || options === void 0 ? void 0 : options.id) || this;
_this.taskQueue = [];
_this.filePickers = [];
_this.fileDraggers = [];
_this.taskHandlerMap = new Map();
_this.upload$ = null;
_this.subscription = new Subscription();
_this.uploadSubscription = null;
_this.taskSubject = new Subject();
_this.action = new Subject();
_this.pause$ = _this.action.pipe(filter(function (v) { return v === 'pause'; }));
_this.clear$ = _this.action.pipe(filter(function (v) { return v === 'clear'; }));
var opt = _this.mergeOptions(options);
_this.validateOptions(opt);
_this.options = opt;
_this.id = _this.options.id;
_this.initFilePickersAndDraggers();
_this.initEventHandler();
_this.options.resumable && _this.restoreTask();
return _this;
}
Uploader.create = function (options) {
return new Uploader(options);
};
Uploader.prototype.mergeOptions = function (options) {
var opt = Object.assign({}, defaultOptions, options);
Object.keys(defaultOptions).forEach(function (key) {
var _a;
var k = key;
if (typeof defaultOptions[k] === 'object') {
Object.assign(defaultOptions[k], opt[k]);
var val = Object.assign({}, defaultOptions[k], opt[k]);
Object.assign(opt, (_a = {}, _a[k] = val, _a));
}
});
return opt;
};
Uploader.prototype.validateOptions = function (options) {
Logger.info('🚀 ~ file: Uploader.ts ~ line 84 ~ Uploader ~ validateOptions ~ options', options);
// TODO
if (typeof options !== 'object') {
throw new Error('');
}
};
Uploader.prototype.upload = function (task, action) {
var _this = this;
var _a, _b;
if (!this.uploadSubscription || ((_a = this.uploadSubscription) === null || _a === void 0 ? void 0 : _a.closed)) {
var filteredTaskStatus_1 = [StatusCode.Waiting, StatusCode.Uploading, StatusCode.Complete];
this.upload$ = this.taskSubject.pipe(filter(function (task) {
return !filteredTaskStatus_1.includes(task.status);
}), tap(function (task) {
Logger.info('🚀 ~ file: 等待上传', task);
_this.changeUploadTaskStatus(task, StatusCode.Waiting);
_this.emit(EventType.TaskWaiting, task);
}), mergeMap(function (task) {
return _this.executeForResult(task, action);
}, this.options.taskConcurrency || 1));
(_b = this.uploadSubscription) === null || _b === void 0 ? void 0 : _b.unsubscribe();
this.uploadSubscription = this.upload$.subscribe({
next: function (v) {
Logger.info('任务结束', v);
_this.checkComplete();
},
error: function (e) {
Logger.info('任务出错', e);
},
complete: function () {
Logger.info('所有任务完成');
},
});
this.subscription.add(this.uploadSubscription);
}
this.putNextTask(task);
};
Uploader.prototype.checkComplete = function () {
if (this.isComplete()) {
this.emit(EventType.Complete);
}
};
Uploader.prototype.executeForResult = function (task, action) {
var _this = this;
return of(task).pipe(filter(function (task) { return task.status === StatusCode.Waiting; }), concatMap(function () { return _this.getTaskHandler(task); }), tap(function (handler) {
handler = _this.rebindTaskHandlerEvent(handler);
action === 'resume' ? handler.resume() : action === 'retry' ? handler.retry() : handler.handle();
}), concatMap(function (handler) {
return race(fromEvent(handler, EventType.TaskPause).pipe(tap(function () {
// 暂停
Logger.info('任务暂停');
})), fromEvent(handler, EventType.TaskCancel).pipe(tap(function () {
// 取消
_this.freeHandler(task);
})), fromEvent(handler, EventType.TaskComplete).pipe(tap(function () {
// 完成
_this.freeHandler(task);
})), fromEvent(handler, EventType.TaskError).pipe(switchMap(function (err) {
// 出错 跳过错误的任务?
if (_this.options.skipTaskWhenUploadError) {
_this.freeHandler(task);
return of(null);
}
return throwError(err);
}))).pipe(mapTo(task), first());
}));
};
Uploader.prototype.resume = function (task) {
this.upload(task, 'resume');
};
Uploader.prototype.retry = function (task) {
this.upload(task, 'retry');
};
Uploader.prototype.pause = function (task) {
var _this = this;
this.action.next('pause');
var fn = function (task) {
var _a;
var handler = (_a = _this.taskHandlerMap.get(task.id)) === null || _a === void 0 ? void 0 : _a.pause();
if (!handler) {
task.status = StatusCode.Pause;
// 任务暂停事件
_this.emit(EventType.TaskPause, task);
}
};
var tasks = task ? [task] : this.taskQueue;
var filteredStatus = [StatusCode.Pause, StatusCode.Complete, StatusCode.Error];
from(tasks)
.pipe(filter(function (tsk) { return !filteredStatus.includes(tsk.status); }), tap(function (tsk) { return fn(tsk); }))
.subscribe({
complete: function () {
_this.emit(EventType.TasksPause);
},
});
};
Uploader.prototype.cancel = function (task) {
if (task) {
this.action.next('cancel');
return this.removeTask(task);
}
else {
return this.clear();
}
};
Uploader.prototype.clear = function () {
var _this = this;
return new Promise(function (resolve) {
_this.action.next('clear');
var unsubscribe = function () {
var _a;
(_a = _this.uploadSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
_this.uploadSubscription = null;
_this.upload$ = null;
};
unsubscribe();
if (_this.taskQueue.length === 0) {
return resolve();
}
of(_this.taskQueue.splice(0, _this.taskQueue.length))
.pipe(tap(function () { return _this.emit(EventType.Clear); }), concatMap(function (list) { return _this.removeTask(list, true); }), concatMap(function () { return _this.clearStorage(_this.id); }), tap(function () { return FileStore.clear(); }))
.subscribe({
complete: function () {
unsubscribe();
resolve();
},
});
});
};
Uploader.prototype.cancelFile = function (item) {
var _this = this;
var _a;
var task = item.task, files = item.files;
var handler = (_a = this.taskHandlerMap.get(task.id)) === null || _a === void 0 ? void 0 : _a.abortFile.apply(_a, __spread(files));
var rawTask = this.taskQueue.find(function (i) { return i.id === task.id; });
if (!handler && rawTask) {
files.forEach(function (file) {
var idIndex = rawTask === null || rawTask === void 0 ? void 0 : rawTask.fileIDList.indexOf(file.id);
if (idIndex > -1) {
rawTask === null || rawTask === void 0 ? void 0 : rawTask.fileIDList.splice(idIndex, 1);
rawTask.fileSize -= file.size;
_this.emit(EventType.FileCancel, rawTask, FileStore.get(file.id));
}
var fileIndex = rawTask === null || rawTask === void 0 ? void 0 : rawTask.fileList.findIndex(function (i) { return i.id === file.id; });
if (fileIndex !== -1) {
rawTask === null || rawTask === void 0 ? void 0 : rawTask.fileList.splice(fileIndex, 1);
}
_this.emit(EventType.TaskUpdate, rawTask);
});
this.emit(EventType.FilesCancel, rawTask, files);
}
this.once(EventType.FilesCancel, function () {
if (_this.options.resumable) {
_this.presistTaskOnly(task);
_this.removeFileFromStroage.apply(_this, __spread(files));
_this.removeChunkFromStroage.apply(_this, __spread(files.reduce(function (arr, i) { return arr.concat(i.chunkIDList); }, [])));
}
_this.removeFileFromFileStore.apply(_this, __spread(files.map(function (i) { return i.id; })));
});
};
Uploader.prototype.isUploading = function () {
var _a;
if ((_a = this.uploadSubscription) === null || _a === void 0 ? void 0 : _a.closed) {
return false;
}
return this.taskQueue.some(function (task) { return task.status === StatusCode.Uploading || task.status === StatusCode.Waiting; });
};
Uploader.prototype.hasError = function () {
return this.taskQueue.some(function (task) { return task.status === StatusCode.Error; });
};
Uploader.prototype.getErrorTasks = function () {
return this.taskQueue.filter(function (task) { return task.status === StatusCode.Error; });
};
Uploader.prototype.isComplete = function () {
var status = [StatusCode.Uploading, StatusCode.Waiting, StatusCode.Pause];
return !this.taskQueue.some(function (task) { return status.includes(task.status); });
};
Uploader.prototype.destory = function () {
this.subscription.unsubscribe();
};
Uploader.prototype.removeTask = function (tasks, clear) {
var _this = this;
if (!Array.isArray(tasks)) {
tasks = [tasks];
}
return scheduled(tasks || [], asyncScheduler)
.pipe(tap(function (task) {
var _a;
var index = _this.taskQueue.findIndex(function (i) { return i.id === (task === null || task === void 0 ? void 0 : task.id); });
index > -1 && _this.taskQueue.splice(index, 1);
(_a = _this.taskHandlerMap.get(task.id)) === null || _a === void 0 ? void 0 : _a.abort();
_this.taskHandlerMap.delete(task.id);
!clear && _this.removeTaskFromStroage(task);
// 任务取消事件
_this.emit(EventType.TaskCancel, task);
}))
.toPromise();
};
Uploader.prototype.rebindTaskHandlerEvent = function (handler) {
var _this = this;
var e = [];
for (var _i = 1; _i < arguments.length; _i++) {
e[_i - 1] = arguments[_i];
}
var events = (e === null || e === void 0 ? void 0 : e.length) ? e : Object.values(EventType);
events.forEach(function (e) {
handler === null || handler === void 0 ? void 0 : handler.off(e);
handler === null || handler === void 0 ? void 0 : handler.on(e, function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return _this.taskHandlerEventCallback.apply(_this, __spread([e], args));
});
});
return handler;
};
Uploader.prototype.getTaskHandler = function (task) {
var _this = this;
return iif(function () { return _this.taskHandlerMap.has(task.id); }, of(this.taskHandlerMap.get(task.id)), handleTask(task, this.options)).pipe(tap(function (handler) {
_this.taskHandlerMap.set(task.id, handler);
}));
};
Uploader.prototype.freeHandler = function (task) {
var id = task.id;
var handler = this.taskHandlerMap.get(id) || null;
if (handler) {
Object.values(EventType).forEach(function (e) { return handler.off(e); });
this.taskHandlerMap.delete(id);
handler = null;
}
};
Uploader.prototype.putNextTask = function (task) {
var _this = this;
if (task) {
if (typeof task === 'object') {
this.taskSubject.next(task);
}
else {
var tsk = this.taskQueue.find(function (tsk) { return tsk.id === task; });
tsk && this.taskSubject.next(tsk);
}
}
else {
var sub_1 = scheduled(this.taskQueue, asapScheduler)
.pipe(filter(function (tsk) { return tsk.status !== StatusCode.Complete; }), takeUntil(this.pause$))
.subscribe({
next: function (tsk) { return _this.taskSubject.next(tsk); },
complete: function () {
sub_1 === null || sub_1 === void 0 ? void 0 : sub_1.unsubscribe();
sub_1 = null;
},
});
}
};
Uploader.prototype.taskHandlerEventCallback = function (e) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
this.emit.apply(this, __spread([e], args));
};
Uploader.prototype.changeUploadTaskStatus = function (task, status) {
task.status = status;
};
Uploader.prototype.restoreTask = function () {
return __awaiter(this, void 0, void 0, function () {
var taskList, recoverableTaskStatus;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, getStorage(this.id).UploadTask.values().toPromise()];
case 1:
taskList = _a.sent();
recoverableTaskStatus = this.options.recoverableTaskStatus;
if (!(taskList === null || taskList === void 0 ? void 0 : taskList.length)) {
return [2 /*return*/, []];
}
return [2 /*return*/, scheduled(taskList || [], asyncScheduler)
.pipe(filter(function (task) {
if (recoverableTaskStatus === null || recoverableTaskStatus === void 0 ? void 0 : recoverableTaskStatus.length) {
return recoverableTaskStatus.includes(task.status);
}
return true;
}), tap(function (task) {
if (task.status !== StatusCode.Complete) {
task.status = task.status === StatusCode.Error ? task.status : StatusCode.Pause;
task.progress = task.progress >= 100 ? 99 : task.progress;
}
_this.taskQueue.push(task);
if (_this.options.autoUpload && task.status === StatusCode.Pause) {
_this.upload(task);
}
// 任务恢复事件
_this.emit(EventType.TaskRestore, task);
}), last(), mapTo(taskList), takeUntil(this.clear$))
.toPromise()
// return new Promise((resolve, reject) => {
// Storage.UploadTask.list()
// .then((list: unknown[]) => {
// Logger.info('Uploader -> restoreTask -> list', list)
// const taskList: UploadTask[] = []
// const fn = (timeRemaining?: () => number) => {
// while (list.length) {
// const arr = list.splice(0, 20) as UploadTask[]
// arr.forEach((task) => {
// if (task.status === StatusCode.Complete) {
// return this.removeTaskFromStroage(task)
// }
// task.status = StatusCode.Pause
// task.progress = task.progress >= 100 ? 99 : task.progress
// this.taskQueue.push(task)
// taskList.push(task)
// this.options.autoUpload && this.upload(task)
// // 任务恢复事件
// this.emit(EventType.TaskRestore, task)
// })
// if (!timeRemaining || !timeRemaining?.()) {
// break
// }
// }
// list.length ? scheduleWork(fn) : resolve(taskList)
// }
// scheduleWork(fn)
// })
// .catch((e) => reject(e))
// })
];
}
});
});
};
Uploader.prototype.initFilePickersAndDraggers = function () {
var _this = this;
var _a = this.options, filePicker = _a.filePicker, fileDragger = _a.fileDragger;
var filePickers = Array.isArray(filePicker) ? filePicker : filePicker ? [filePicker] : null;
var fileDraggers = Array.isArray(fileDragger) ? fileDragger : fileDragger ? [fileDragger] : null;
var obs = [];
if (filePickers === null || filePickers === void 0 ? void 0 : filePickers.length) {
obs.push.apply(obs, __spread(filePickers.map(function (opts) {
var picker = new FilePicker(opts);
_this.filePickers.push(picker);
return picker.file$;
})));
}
if (fileDraggers === null || fileDraggers === void 0 ? void 0 : fileDraggers.length) {
obs.push.apply(obs, __spread(fileDraggers.map(function (opts) {
var dragger = new FileDragger(opts, _this.options);
_this.fileDraggers.push(dragger);
return dragger.file$;
})));
}
if (obs.length) {
this.subscription.add(merge.apply(void 0, __spread(obs)).pipe(concatMap(function (files) { return _this.add(files); }))
.subscribe());
}
};
Uploader.prototype.initEventHandler = function () {
var _this = this;
this.on(EventType.TaskCreated, function (task) {
_this.options.autoUpload && _this.upload(task);
});
};
Uploader.prototype.add = function (files) {
var _this = this;
return of(files).pipe(concatMap(function (files) {
var _a, _b;
// 选择文件后添加文件前hook
var beforeAdd = _this.hookWrap((_b = (_a = _this.options).beforeFilesAdd) === null || _b === void 0 ? void 0 : _b.call(_a, files));
return from(beforeAdd).pipe(mapTo(files));
}), concatMap(function (files) {
return from(_this.addFilesAsync.apply(_this, __spread(files))).pipe(map(function (tasks) { return ({ files: files, tasks: tasks }); }));
}), takeUntil(this.clear$));
};
Uploader.prototype.addFilesAsync = function () {
var _this = this;
var files = [];
for (var _i = 0; _i < arguments.length; _i++) {
files[_i] = arguments[_i];
}
return new Promise(function (resolve, reject) {
Logger.info('Uploader -> addFile -> files', files);
var resolveTasks = function (tasks) {
resolve(tasks);
_this.emit(EventType.TasksAdded, tasks);
};
var finish = function (tasks) {
if (_this.options.resumable) {
var ob$ = isElectron()
? _this.presistTaskWithoutBlob(tasks, _this.clear$)
: _this.presistTask(tasks, _this.clear$);
var sub_2 = ob$.pipe(takeUntil(_this.clear$)).subscribe({
error: function (e) { return reject(e); },
complete: function () {
// 任务持久化事件
_this.emit(EventType.TasksPresist, tasks);
sub_2 === null || sub_2 === void 0 ? void 0 : sub_2.unsubscribe();
sub_2 = null;
resolveTasks(tasks);
console.timeEnd('addFilesAsync');
},
});
}
else {
resolveTasks(tasks);
console.timeEnd('addFilesAsync');
}
};
if (!files.length) {
resolveTasks([]);
return;
}
console.time('addFilesAsync');
var fileFilter = _this.options.fileFilter;
var tasks = new Set();
scheduled(files || [], asapScheduler)
.pipe(filter(function (file) {
var accept = true;
if (fileFilter instanceof RegExp) {
accept = fileFilter.test(file.name);
}
else if (typeof fileFilter === 'function') {
accept = fileFilter(file.name, file);
}
return !!accept;
}), bufferCount(1000), map(function (files) {
return files.map(fileFactory);
}), concatMap(function (files) { return _this.generateTask.apply(_this, __spread(files)); }), tap(function (data) { return data === null || data === void 0 ? void 0 : data.forEach(function (i) { return i && tasks.add(i); }); }))
.subscribe({
complete: function () { return finish(__spread(tasks)); },
});
// const scheduleWork = (cb: Noop, timeout?: number) => cb()
// const fn = async (timeRemaining?: () => number) => {
// while (files.length) {
// Logger.info(files.length)
// const filelist: UploadFile[] = []
// files.splice(0, 100).forEach((file) => {
// let ignored = false
// if (fileFilter instanceof RegExp) {
// ignored = !fileFilter.test(file.name)
// } else if (typeof fileFilter === 'function') {
// ignored = !fileFilter(file.name, file)
// }
// if (!ignored) {
// filelist.push(fileFactory(file))
// }
// })
// const currentTasks: UploadTask[] = await this.generateTask(...filelist).toPromise()
// currentTasks.map((i) => tasks.add(i))
// // if (!timeRemaining?.()) {
// // break
// // }
// }
// files.length ? scheduleWork(fn, 1000) : finish([...tasks])
// }
// scheduleWork(fn)
});
};
Uploader.prototype.generateTask = function () {
var _this = this;
var fileList = [];
for (var _i = 0; _i < arguments.length; _i++) {
fileList[_i] = arguments[_i];
}
return new Observable(function (subscriber) {
console.time('generateTask');
var _a = _this.options, ossOptions = _a.ossOptions, singleFileTask = _a.singleFileTask;
var updateTasks = new Set();
var newTasks = [];
var notifier = new Subject();
var existsTaskMap = new Map();
var sub = from(fileList)
.pipe(tap(function (file) {
var pos = file.relativePath.indexOf('/');
var newTask = null;
var inFolder = !singleFileTask && pos !== -1;
if (!inFolder) {
newTask = taskFactory(file, singleFileTask);
}
else {
var parentPath_1 = file.relativePath.substring(0, pos + 1);
var existsTask = existsTaskMap.get(parentPath_1);
if (!existsTask) {
existsTask = _this.taskQueue.concat(newTasks).find(function (tsk) {
return tsk.fileIDList.some(function (id) { var _a; return (_a = FileStore.get(id)) === null || _a === void 0 ? void 0 : _a.relativePath.startsWith(parentPath_1); });
});
existsTask && existsTaskMap.set(parentPath_1, existsTask);
}
if (existsTask) {
var existsFile = existsTask.fileIDList.find(function (id) { var _a; return ((_a = FileStore.get(id)) === null || _a === void 0 ? void 0 : _a.relativePath) === file.relativePath; });
if (!existsFile) {
existsTask.fileIDList.push(file.id);
existsTask.fileList.push(file);
existsTask.fileSize += file.size;
updateTasks.add(existsTask);
}
else {
Logger.info('existsTask:', existsTask, 'existsFile:', existsFile);
}
}
else {
newTask = taskFactory(file, singleFileTask);
}
}
if (newTask) {
newTask.oss = (ossOptions === null || ossOptions === void 0 ? void 0 : ossOptions.enable) ? ossOptions === null || ossOptions === void 0 ? void 0 : ossOptions.provider : newTask.oss;
newTask.type = singleFileTask ? 'file' : newTask.type;
newTasks.push(newTask);
}
}), last(), concatMap(function () { var _a, _b; return (fileList.length ? from(_this.hookWrap((_b = (_a = _this.options).filesAdded) === null || _b === void 0 ? void 0 : _b.call(_a, fileList))) : of(null)); }), concatMap(function () { var _a, _b; return (newTasks.length ? from(_this.hookWrap((_b = (_a = _this.options).beforeTasksAdd) === null || _b === void 0 ? void 0 : _b.call(_a, newTasks))) : of(null)); }), tap(function () {
// 任务创建事件
newTasks.forEach(function (task) {
_this.emit(EventType.TaskCreated, task);
_this.taskQueue.push(task);
});
// 任务更新事件
updateTasks.forEach(function (task) { return _this.emit(EventType.TaskUpdate, task); });
}), takeUntil(notifier))
.subscribe({
complete: function () {
subscriber.next(newTasks);
subscriber.complete();
console.timeEnd('generateTask');
},
error: function (e) { return subscriber.error(e); },
});
return function () {
notifier === null || notifier === void 0 ? void 0 : notifier.next();
notifier === null || notifier === void 0 ? void 0 : notifier.unsubscribe();
sub === null || sub === void 0 ? void 0 : sub.unsubscribe();
};
});
};
return Uploader;
}(Base));
export { Uploader };
//# sourceMappingURL=Uploader.js.map