upload
Version:
Isomorphic TypeScript file upload library.
229 lines (228 loc) • 8.34 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Upload = void 0;
const form_data_1 = __importDefault(require("form-data"));
class Upload {
constructor(options) {
this.events = {
state: new Set(),
error: new Set(),
progress: new Set(),
};
this.withCredentials = false;
this._uploadedBytes = 0;
this._totalBytes = 0;
this._state = 'new';
if (!options) {
throw new Error('Options are required.');
}
if (!options.url || typeof options.url !== 'string') {
throw new Error('Destination URL is missing or invalid.');
}
this.form = options.form;
this.url = options.url;
this.headers = options.headers;
this.withCredentials = options.withCredentials;
}
/**
* POSTs the form.
*/
upload() {
return new Promise((resolve, reject) => {
// Check if we're running in a browser.
if (typeof window !== 'undefined' &&
typeof XMLHttpRequest !== 'undefined') {
this.xhr = new XMLHttpRequest();
if (this.withCredentials) {
this.xhr.withCredentials = true;
}
this.xhr.open('POST', this.url, true);
if (typeof this.headers === 'object') {
for (const headerName of Object.keys(this.headers)) {
this.xhr.setRequestHeader(headerName, this.headers[headerName]);
}
}
this.xhr.addEventListener('loadstart', () => {
this.setState('started');
});
if (this.xhr.upload) {
this.xhr.upload.addEventListener('progress', e => {
if (this._totalBytes !== e.total) {
this.setTotalBytes(e.total);
}
this.setUploadedBytes(e.loaded);
});
}
this.xhr.addEventListener('load', () => {
if (this.xhr) {
this.setUploadedBytes(this.totalBytes);
this.setState('successful');
const response = {};
const lines = this.xhr
.getAllResponseHeaders()
.replace(/\r/g, '')
.split('\n');
const headers = {};
for (const line of lines) {
const split = line.split(':');
if (split.length != 2) {
continue;
}
headers[split[0].trim()] = split[1].trim();
}
response.headers = headers;
response.status = this.xhr.status;
response.xhr = this.xhr;
switch (this.xhr.responseType) {
case 'json':
response.data = JSON.stringify(this.xhr.response);
break;
default:
response.data = this.xhr.response;
}
resolve(response);
}
});
this.xhr.addEventListener('error', () => {
this.setState('failed');
this.emit('error');
reject();
});
this.xhr.addEventListener('abort', () => {
this.setState('aborted');
});
if (this.form instanceof FormData) {
this.xhr.send(this.form);
}
else {
const form = this.form;
const formData = new FormData();
for (const key of Object.keys(this.form)) {
formData.set(key, form[key]);
}
this.xhr.send(formData);
}
}
else {
const callback = (error, res) => {
if (error) {
this.setState('failed');
this.emit('error');
reject();
}
else {
this.setUploadedBytes(this.totalBytes);
this.setState('successful');
let body = '';
res.on('readable', () => {
const chunk = res.read();
if (chunk) {
body += chunk;
}
});
res.on('end', () => {
const response = {};
response.data = body;
response.headers = res.headers;
resolve(response);
});
}
};
const url = new URL(this.url);
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname,
method: 'POST',
headers: this.headers,
};
let formData;
if (this.form instanceof form_data_1.default) {
formData = this.form;
}
else {
const form = this.form;
formData = new form_data_1.default();
for (const key of Object.keys(this.form)) {
formData.append(key, form[key]);
}
}
formData.getLength((error, length) => {
this.setTotalBytes(length);
});
formData.on('data', chunk => {
if (this.state === 'new') {
this.setState('started');
}
if (chunk.hasOwnProperty('length')) {
this.increaseUploadedBytes(chunk.length);
}
});
formData.submit(options, callback);
}
});
}
abort() {
var _a;
(_a = this.xhr) === null || _a === void 0 ? void 0 : _a.abort();
}
get uploadedBytes() {
return this._uploadedBytes;
}
setUploadedBytes(value) {
this._uploadedBytes = value;
this.emit('progress', this.progress);
}
increaseUploadedBytes(value) {
this._uploadedBytes += value;
this.emit('progress', this.progress);
}
get totalBytes() {
return this._totalBytes;
}
setTotalBytes(value) {
this._totalBytes = value;
this.emit('progress', this.progress);
}
/**
* Current upload progress. A float between 0 and 1.
*/
get progress() {
return this._totalBytes === 0 ? 0 : this._uploadedBytes / this._totalBytes;
}
get state() {
return this._state;
}
setState(value) {
const oldState = this._state;
this._state = value;
if (oldState !== this._state) {
this.emit('state', this._state);
}
}
/**
* Adds a listener for a given event.
* @param eventType Event type.
* @param listener Listener function.
*/
on(eventType, listener) {
this.events[eventType].add(listener);
}
/**
* Removes a listener for a given event.
* @param eventType Event type.
* @param listener Listener function.
*/
off(eventType, listener) {
this.events[eventType].delete(listener);
}
emit(eventType, ...args) {
for (const listener of this.events[eventType]) {
listener.apply(this, args);
}
}
}
exports.Upload = Upload;