You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
668 lines
20 KiB
668 lines
20 KiB
|
2 years ago
|
"use strict";
|
||
|
|
|
||
|
|
var _nonSecure = require("nanoid/non-secure");
|
||
|
|
|
||
|
|
var _companionClient = require("@uppy/companion-client");
|
||
|
|
|
||
|
|
var _RateLimitedQueue = require("@uppy/utils/lib/RateLimitedQueue");
|
||
|
|
|
||
|
|
const BasePlugin = require("@uppy/core/lib/BasePlugin");
|
||
|
|
|
||
|
|
const emitSocketProgress = require("@uppy/utils/lib/emitSocketProgress");
|
||
|
|
|
||
|
|
const getSocketHost = require("@uppy/utils/lib/getSocketHost");
|
||
|
|
|
||
|
|
const settle = require("@uppy/utils/lib/settle");
|
||
|
|
|
||
|
|
const EventTracker = require("@uppy/utils/lib/EventTracker");
|
||
|
|
|
||
|
|
const ProgressTimeout = require("@uppy/utils/lib/ProgressTimeout");
|
||
|
|
|
||
|
|
const NetworkError = require("@uppy/utils/lib/NetworkError");
|
||
|
|
|
||
|
|
const isNetworkError = require("@uppy/utils/lib/isNetworkError");
|
||
|
|
|
||
|
|
const packageJson = {
|
||
|
|
"version": "2.1.3"
|
||
|
|
};
|
||
|
|
|
||
|
|
const locale = require("./locale.js");
|
||
|
|
|
||
|
|
function buildResponseError(xhr, err) {
|
||
|
|
let error = err; // No error message
|
||
|
|
|
||
|
|
if (!error) error = new Error('Upload error'); // Got an error message string
|
||
|
|
|
||
|
|
if (typeof error === 'string') error = new Error(error); // Got something else
|
||
|
|
|
||
|
|
if (!(error instanceof Error)) {
|
||
|
|
error = Object.assign(new Error('Upload error'), {
|
||
|
|
data: error
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isNetworkError(xhr)) {
|
||
|
|
error = new NetworkError(error, xhr);
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
error.request = xhr;
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Set `data.type` in the blob to `file.meta.type`,
|
||
|
|
* because we might have detected a more accurate file type in Uppy
|
||
|
|
* https://stackoverflow.com/a/50875615
|
||
|
|
*
|
||
|
|
* @param {object} file File object with `data`, `size` and `meta` properties
|
||
|
|
* @returns {object} blob updated with the new `type` set from `file.meta.type`
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
function setTypeInBlob(file) {
|
||
|
|
const dataWithUpdatedType = file.data.slice(0, file.data.size, file.meta.type);
|
||
|
|
return dataWithUpdatedType;
|
||
|
|
}
|
||
|
|
|
||
|
|
class XHRUpload extends BasePlugin {
|
||
|
|
// eslint-disable-next-line global-require
|
||
|
|
constructor(uppy, opts) {
|
||
|
|
super(uppy, opts);
|
||
|
|
this.type = 'uploader';
|
||
|
|
this.id = this.opts.id || 'XHRUpload';
|
||
|
|
this.title = 'XHRUpload';
|
||
|
|
this.defaultLocale = locale; // Default options
|
||
|
|
|
||
|
|
const defaultOptions = {
|
||
|
|
formData: true,
|
||
|
|
fieldName: opts.bundle ? 'files[]' : 'file',
|
||
|
|
method: 'post',
|
||
|
|
metaFields: null,
|
||
|
|
responseUrlFieldName: 'url',
|
||
|
|
bundle: false,
|
||
|
|
headers: {},
|
||
|
|
timeout: 30 * 1000,
|
||
|
|
limit: 5,
|
||
|
|
withCredentials: false,
|
||
|
|
responseType: '',
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param {string} responseText the response body string
|
||
|
|
*/
|
||
|
|
getResponseData(responseText) {
|
||
|
|
let parsedResponse = {};
|
||
|
|
|
||
|
|
try {
|
||
|
|
parsedResponse = JSON.parse(responseText);
|
||
|
|
} catch (err) {
|
||
|
|
uppy.log(err);
|
||
|
|
}
|
||
|
|
|
||
|
|
return parsedResponse;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param {string} _ the response body string
|
||
|
|
* @param {XMLHttpRequest | respObj} response the response object (XHR or similar)
|
||
|
|
*/
|
||
|
|
getResponseError(_, response) {
|
||
|
|
let error = new Error('Upload error');
|
||
|
|
|
||
|
|
if (isNetworkError(response)) {
|
||
|
|
error = new NetworkError(error, response);
|
||
|
|
}
|
||
|
|
|
||
|
|
return error;
|
||
|
|
},
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if the response from the upload endpoint indicates that the upload was successful.
|
||
|
|
*
|
||
|
|
* @param {number} status the response status code
|
||
|
|
*/
|
||
|
|
validateStatus(status) {
|
||
|
|
return status >= 200 && status < 300;
|
||
|
|
}
|
||
|
|
|
||
|
|
};
|
||
|
|
this.opts = { ...defaultOptions,
|
||
|
|
...opts
|
||
|
|
};
|
||
|
|
this.i18nInit();
|
||
|
|
this.handleUpload = this.handleUpload.bind(this); // Simultaneous upload limiting is shared across all uploads with this plugin.
|
||
|
|
|
||
|
|
if (_RateLimitedQueue.internalRateLimitedQueue in this.opts) {
|
||
|
|
this.requests = this.opts[_RateLimitedQueue.internalRateLimitedQueue];
|
||
|
|
} else {
|
||
|
|
this.requests = new _RateLimitedQueue.RateLimitedQueue(this.opts.limit);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.opts.bundle && !this.opts.formData) {
|
||
|
|
throw new Error('`opts.formData` must be true when `opts.bundle` is enabled.');
|
||
|
|
}
|
||
|
|
|
||
|
|
this.uploaderEvents = Object.create(null);
|
||
|
|
}
|
||
|
|
|
||
|
|
getOptions(file) {
|
||
|
|
const overrides = this.uppy.getState().xhrUpload;
|
||
|
|
const {
|
||
|
|
headers
|
||
|
|
} = this.opts;
|
||
|
|
const opts = { ...this.opts,
|
||
|
|
...(overrides || {}),
|
||
|
|
...(file.xhrUpload || {}),
|
||
|
|
headers: {}
|
||
|
|
}; // Support for `headers` as a function, only in the XHRUpload settings.
|
||
|
|
// Options set by other plugins in Uppy state or on the files themselves are still merged in afterward.
|
||
|
|
//
|
||
|
|
// ```js
|
||
|
|
// headers: (file) => ({ expires: file.meta.expires })
|
||
|
|
// ```
|
||
|
|
|
||
|
|
if (typeof headers === 'function') {
|
||
|
|
opts.headers = headers(file);
|
||
|
|
} else {
|
||
|
|
Object.assign(opts.headers, this.opts.headers);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (overrides) {
|
||
|
|
Object.assign(opts.headers, overrides.headers);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (file.xhrUpload) {
|
||
|
|
Object.assign(opts.headers, file.xhrUpload.headers);
|
||
|
|
}
|
||
|
|
|
||
|
|
return opts;
|
||
|
|
} // eslint-disable-next-line class-methods-use-this
|
||
|
|
|
||
|
|
|
||
|
|
addMetadata(formData, meta, opts) {
|
||
|
|
const metaFields = Array.isArray(opts.metaFields) ? opts.metaFields : Object.keys(meta); // Send along all fields by default.
|
||
|
|
|
||
|
|
metaFields.forEach(item => {
|
||
|
|
formData.append(item, meta[item]);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
createFormDataUpload(file, opts) {
|
||
|
|
const formPost = new FormData();
|
||
|
|
this.addMetadata(formPost, file.meta, opts);
|
||
|
|
const dataWithUpdatedType = setTypeInBlob(file);
|
||
|
|
|
||
|
|
if (file.name) {
|
||
|
|
formPost.append(opts.fieldName, dataWithUpdatedType, file.meta.name);
|
||
|
|
} else {
|
||
|
|
formPost.append(opts.fieldName, dataWithUpdatedType);
|
||
|
|
}
|
||
|
|
|
||
|
|
return formPost;
|
||
|
|
}
|
||
|
|
|
||
|
|
createBundledUpload(files, opts) {
|
||
|
|
const formPost = new FormData();
|
||
|
|
const {
|
||
|
|
meta
|
||
|
|
} = this.uppy.getState();
|
||
|
|
this.addMetadata(formPost, meta, opts);
|
||
|
|
files.forEach(file => {
|
||
|
|
const options = this.getOptions(file);
|
||
|
|
const dataWithUpdatedType = setTypeInBlob(file);
|
||
|
|
|
||
|
|
if (file.name) {
|
||
|
|
formPost.append(options.fieldName, dataWithUpdatedType, file.name);
|
||
|
|
} else {
|
||
|
|
formPost.append(options.fieldName, dataWithUpdatedType);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return formPost;
|
||
|
|
}
|
||
|
|
|
||
|
|
upload(file, current, total) {
|
||
|
|
const opts = this.getOptions(file);
|
||
|
|
this.uppy.log(`uploading ${current} of ${total}`);
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
this.uppy.emit('upload-started', file);
|
||
|
|
const data = opts.formData ? this.createFormDataUpload(file, opts) : file.data;
|
||
|
|
const xhr = new XMLHttpRequest();
|
||
|
|
this.uploaderEvents[file.id] = new EventTracker(this.uppy);
|
||
|
|
let queuedRequest;
|
||
|
|
const timer = new ProgressTimeout(opts.timeout, () => {
|
||
|
|
xhr.abort();
|
||
|
|
queuedRequest.done();
|
||
|
|
const error = new Error(this.i18n('timedOut', {
|
||
|
|
seconds: Math.ceil(opts.timeout / 1000)
|
||
|
|
}));
|
||
|
|
this.uppy.emit('upload-error', file, error);
|
||
|
|
reject(error);
|
||
|
|
});
|
||
|
|
const id = (0, _nonSecure.nanoid)();
|
||
|
|
xhr.upload.addEventListener('loadstart', () => {
|
||
|
|
this.uppy.log(`[XHRUpload] ${id} started`);
|
||
|
|
});
|
||
|
|
xhr.upload.addEventListener('progress', ev => {
|
||
|
|
this.uppy.log(`[XHRUpload] ${id} progress: ${ev.loaded} / ${ev.total}`); // Begin checking for timeouts when progress starts, instead of loading,
|
||
|
|
// to avoid timing out requests on browser concurrency queue
|
||
|
|
|
||
|
|
timer.progress();
|
||
|
|
|
||
|
|
if (ev.lengthComputable) {
|
||
|
|
this.uppy.emit('upload-progress', file, {
|
||
|
|
uploader: this,
|
||
|
|
bytesUploaded: ev.loaded,
|
||
|
|
bytesTotal: ev.total
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
xhr.addEventListener('load', () => {
|
||
|
|
this.uppy.log(`[XHRUpload] ${id} finished`);
|
||
|
|
timer.done();
|
||
|
|
queuedRequest.done();
|
||
|
|
|
||
|
|
if (this.uploaderEvents[file.id]) {
|
||
|
|
this.uploaderEvents[file.id].remove();
|
||
|
|
this.uploaderEvents[file.id] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (opts.validateStatus(xhr.status, xhr.responseText, xhr)) {
|
||
|
|
const body = opts.getResponseData(xhr.responseText, xhr);
|
||
|
|
const uploadURL = body[opts.responseUrlFieldName];
|
||
|
|
const uploadResp = {
|
||
|
|
status: xhr.status,
|
||
|
|
body,
|
||
|
|
uploadURL
|
||
|
|
};
|
||
|
|
this.uppy.emit('upload-success', file, uploadResp);
|
||
|
|
|
||
|
|
if (uploadURL) {
|
||
|
|
this.uppy.log(`Download ${file.name} from ${uploadURL}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return resolve(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
const body = opts.getResponseData(xhr.responseText, xhr);
|
||
|
|
const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr));
|
||
|
|
const response = {
|
||
|
|
status: xhr.status,
|
||
|
|
body
|
||
|
|
};
|
||
|
|
this.uppy.emit('upload-error', file, error, response);
|
||
|
|
return reject(error);
|
||
|
|
});
|
||
|
|
xhr.addEventListener('error', () => {
|
||
|
|
this.uppy.log(`[XHRUpload] ${id} errored`);
|
||
|
|
timer.done();
|
||
|
|
queuedRequest.done();
|
||
|
|
|
||
|
|
if (this.uploaderEvents[file.id]) {
|
||
|
|
this.uploaderEvents[file.id].remove();
|
||
|
|
this.uploaderEvents[file.id] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const error = buildResponseError(xhr, opts.getResponseError(xhr.responseText, xhr));
|
||
|
|
this.uppy.emit('upload-error', file, error);
|
||
|
|
return reject(error);
|
||
|
|
});
|
||
|
|
xhr.open(opts.method.toUpperCase(), opts.endpoint, true); // IE10 does not allow setting `withCredentials` and `responseType`
|
||
|
|
// before `open()` is called.
|
||
|
|
|
||
|
|
xhr.withCredentials = opts.withCredentials;
|
||
|
|
|
||
|
|
if (opts.responseType !== '') {
|
||
|
|
xhr.responseType = opts.responseType;
|
||
|
|
}
|
||
|
|
|
||
|
|
queuedRequest = this.requests.run(() => {
|
||
|
|
this.uppy.emit('upload-started', file); // When using an authentication system like JWT, the bearer token goes as a header. This
|
||
|
|
// header needs to be fresh each time the token is refreshed so computing and setting the
|
||
|
|
// headers just before the upload starts enables this kind of authentication to work properly.
|
||
|
|
// Otherwise, half-way through the list of uploads the token could be stale and the upload would fail.
|
||
|
|
|
||
|
|
const currentOpts = this.getOptions(file);
|
||
|
|
Object.keys(currentOpts.headers).forEach(header => {
|
||
|
|
xhr.setRequestHeader(header, currentOpts.headers[header]);
|
||
|
|
});
|
||
|
|
xhr.send(data);
|
||
|
|
return () => {
|
||
|
|
timer.done();
|
||
|
|
xhr.abort();
|
||
|
|
};
|
||
|
|
});
|
||
|
|
this.onFileRemove(file.id, () => {
|
||
|
|
queuedRequest.abort();
|
||
|
|
reject(new Error('File removed'));
|
||
|
|
});
|
||
|
|
this.onCancelAll(file.id, _ref => {
|
||
|
|
let {
|
||
|
|
reason
|
||
|
|
} = _ref;
|
||
|
|
|
||
|
|
if (reason === 'user') {
|
||
|
|
queuedRequest.abort();
|
||
|
|
}
|
||
|
|
|
||
|
|
reject(new Error('Upload cancelled'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
uploadRemote(file) {
|
||
|
|
const opts = this.getOptions(file);
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
this.uppy.emit('upload-started', file);
|
||
|
|
const fields = {};
|
||
|
|
const metaFields = Array.isArray(opts.metaFields) ? opts.metaFields // Send along all fields by default.
|
||
|
|
: Object.keys(file.meta);
|
||
|
|
metaFields.forEach(name => {
|
||
|
|
fields[name] = file.meta[name];
|
||
|
|
});
|
||
|
|
const Client = file.remote.providerOptions.provider ? _companionClient.Provider : _companionClient.RequestClient;
|
||
|
|
const client = new Client(this.uppy, file.remote.providerOptions);
|
||
|
|
client.post(file.remote.url, { ...file.remote.body,
|
||
|
|
endpoint: opts.endpoint,
|
||
|
|
size: file.data.size,
|
||
|
|
fieldname: opts.fieldName,
|
||
|
|
metadata: fields,
|
||
|
|
httpMethod: opts.method,
|
||
|
|
useFormData: opts.formData,
|
||
|
|
headers: opts.headers
|
||
|
|
}).then(res => {
|
||
|
|
const {
|
||
|
|
token
|
||
|
|
} = res;
|
||
|
|
const host = getSocketHost(file.remote.companionUrl);
|
||
|
|
const socket = new _companionClient.Socket({
|
||
|
|
target: `${host}/api/${token}`,
|
||
|
|
autoOpen: false
|
||
|
|
});
|
||
|
|
this.uploaderEvents[file.id] = new EventTracker(this.uppy);
|
||
|
|
let queuedRequest;
|
||
|
|
this.onFileRemove(file.id, () => {
|
||
|
|
socket.send('cancel', {});
|
||
|
|
queuedRequest.abort();
|
||
|
|
resolve(`upload ${file.id} was removed`);
|
||
|
|
});
|
||
|
|
this.onCancelAll(file.id, function (_temp) {
|
||
|
|
let {
|
||
|
|
reason
|
||
|
|
} = _temp === void 0 ? {} : _temp;
|
||
|
|
|
||
|
|
if (reason === 'user') {
|
||
|
|
socket.send('cancel', {});
|
||
|
|
queuedRequest.abort();
|
||
|
|
}
|
||
|
|
|
||
|
|
resolve(`upload ${file.id} was canceled`);
|
||
|
|
});
|
||
|
|
this.onRetry(file.id, () => {
|
||
|
|
socket.send('pause', {});
|
||
|
|
socket.send('resume', {});
|
||
|
|
});
|
||
|
|
this.onRetryAll(file.id, () => {
|
||
|
|
socket.send('pause', {});
|
||
|
|
socket.send('resume', {});
|
||
|
|
});
|
||
|
|
socket.on('progress', progressData => emitSocketProgress(this, progressData, file));
|
||
|
|
socket.on('success', data => {
|
||
|
|
const body = opts.getResponseData(data.response.responseText, data.response);
|
||
|
|
const uploadURL = body[opts.responseUrlFieldName];
|
||
|
|
const uploadResp = {
|
||
|
|
status: data.response.status,
|
||
|
|
body,
|
||
|
|
uploadURL
|
||
|
|
};
|
||
|
|
this.uppy.emit('upload-success', file, uploadResp);
|
||
|
|
queuedRequest.done();
|
||
|
|
|
||
|
|
if (this.uploaderEvents[file.id]) {
|
||
|
|
this.uploaderEvents[file.id].remove();
|
||
|
|
this.uploaderEvents[file.id] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return resolve();
|
||
|
|
});
|
||
|
|
socket.on('error', errData => {
|
||
|
|
const resp = errData.response;
|
||
|
|
const error = resp ? opts.getResponseError(resp.responseText, resp) : Object.assign(new Error(errData.error.message), {
|
||
|
|
cause: errData.error
|
||
|
|
});
|
||
|
|
this.uppy.emit('upload-error', file, error);
|
||
|
|
queuedRequest.done();
|
||
|
|
|
||
|
|
if (this.uploaderEvents[file.id]) {
|
||
|
|
this.uploaderEvents[file.id].remove();
|
||
|
|
this.uploaderEvents[file.id] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
reject(error);
|
||
|
|
});
|
||
|
|
queuedRequest = this.requests.run(() => {
|
||
|
|
socket.open();
|
||
|
|
|
||
|
|
if (file.isPaused) {
|
||
|
|
socket.send('pause', {});
|
||
|
|
}
|
||
|
|
|
||
|
|
return () => socket.close();
|
||
|
|
});
|
||
|
|
}).catch(err => {
|
||
|
|
this.uppy.emit('upload-error', file, err);
|
||
|
|
reject(err);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
uploadBundle(files) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const {
|
||
|
|
endpoint
|
||
|
|
} = this.opts;
|
||
|
|
const {
|
||
|
|
method
|
||
|
|
} = this.opts;
|
||
|
|
const optsFromState = this.uppy.getState().xhrUpload;
|
||
|
|
const formData = this.createBundledUpload(files, { ...this.opts,
|
||
|
|
...(optsFromState || {})
|
||
|
|
});
|
||
|
|
const xhr = new XMLHttpRequest();
|
||
|
|
|
||
|
|
const emitError = error => {
|
||
|
|
files.forEach(file => {
|
||
|
|
this.uppy.emit('upload-error', file, error);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
const timer = new ProgressTimeout(this.opts.timeout, () => {
|
||
|
|
xhr.abort();
|
||
|
|
const error = new Error(this.i18n('timedOut', {
|
||
|
|
seconds: Math.ceil(this.opts.timeout / 1000)
|
||
|
|
}));
|
||
|
|
emitError(error);
|
||
|
|
reject(error);
|
||
|
|
});
|
||
|
|
xhr.upload.addEventListener('loadstart', () => {
|
||
|
|
this.uppy.log('[XHRUpload] started uploading bundle');
|
||
|
|
timer.progress();
|
||
|
|
});
|
||
|
|
xhr.upload.addEventListener('progress', ev => {
|
||
|
|
timer.progress();
|
||
|
|
if (!ev.lengthComputable) return;
|
||
|
|
files.forEach(file => {
|
||
|
|
this.uppy.emit('upload-progress', file, {
|
||
|
|
uploader: this,
|
||
|
|
bytesUploaded: ev.loaded / ev.total * file.size,
|
||
|
|
bytesTotal: file.size
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
xhr.addEventListener('load', ev => {
|
||
|
|
timer.done();
|
||
|
|
|
||
|
|
if (this.opts.validateStatus(ev.target.status, xhr.responseText, xhr)) {
|
||
|
|
const body = this.opts.getResponseData(xhr.responseText, xhr);
|
||
|
|
const uploadResp = {
|
||
|
|
status: ev.target.status,
|
||
|
|
body
|
||
|
|
};
|
||
|
|
files.forEach(file => {
|
||
|
|
this.uppy.emit('upload-success', file, uploadResp);
|
||
|
|
});
|
||
|
|
return resolve();
|
||
|
|
}
|
||
|
|
|
||
|
|
const error = this.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error');
|
||
|
|
error.request = xhr;
|
||
|
|
emitError(error);
|
||
|
|
return reject(error);
|
||
|
|
});
|
||
|
|
xhr.addEventListener('error', () => {
|
||
|
|
timer.done();
|
||
|
|
const error = this.opts.getResponseError(xhr.responseText, xhr) || new Error('Upload error');
|
||
|
|
emitError(error);
|
||
|
|
return reject(error);
|
||
|
|
});
|
||
|
|
this.uppy.on('cancel-all', function (_temp2) {
|
||
|
|
let {
|
||
|
|
reason
|
||
|
|
} = _temp2 === void 0 ? {} : _temp2;
|
||
|
|
if (reason !== 'user') return;
|
||
|
|
timer.done();
|
||
|
|
xhr.abort();
|
||
|
|
});
|
||
|
|
xhr.open(method.toUpperCase(), endpoint, true); // IE10 does not allow setting `withCredentials` and `responseType`
|
||
|
|
// before `open()` is called.
|
||
|
|
|
||
|
|
xhr.withCredentials = this.opts.withCredentials;
|
||
|
|
|
||
|
|
if (this.opts.responseType !== '') {
|
||
|
|
xhr.responseType = this.opts.responseType;
|
||
|
|
}
|
||
|
|
|
||
|
|
Object.keys(this.opts.headers).forEach(header => {
|
||
|
|
xhr.setRequestHeader(header, this.opts.headers[header]);
|
||
|
|
});
|
||
|
|
xhr.send(formData);
|
||
|
|
files.forEach(file => {
|
||
|
|
this.uppy.emit('upload-started', file);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
uploadFiles(files) {
|
||
|
|
const promises = files.map((file, i) => {
|
||
|
|
const current = parseInt(i, 10) + 1;
|
||
|
|
const total = files.length;
|
||
|
|
|
||
|
|
if (file.error) {
|
||
|
|
return Promise.reject(new Error(file.error));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (file.isRemote) {
|
||
|
|
return this.uploadRemote(file, current, total);
|
||
|
|
}
|
||
|
|
|
||
|
|
return this.upload(file, current, total);
|
||
|
|
});
|
||
|
|
return settle(promises);
|
||
|
|
}
|
||
|
|
|
||
|
|
onFileRemove(fileID, cb) {
|
||
|
|
this.uploaderEvents[fileID].on('file-removed', file => {
|
||
|
|
if (fileID === file.id) cb(file.id);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
onRetry(fileID, cb) {
|
||
|
|
this.uploaderEvents[fileID].on('upload-retry', targetFileID => {
|
||
|
|
if (fileID === targetFileID) {
|
||
|
|
cb();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
onRetryAll(fileID, cb) {
|
||
|
|
this.uploaderEvents[fileID].on('retry-all', () => {
|
||
|
|
if (!this.uppy.getFile(fileID)) return;
|
||
|
|
cb();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
onCancelAll(fileID, eventHandler) {
|
||
|
|
var _this = this;
|
||
|
|
|
||
|
|
this.uploaderEvents[fileID].on('cancel-all', function () {
|
||
|
|
if (!_this.uppy.getFile(fileID)) return;
|
||
|
|
eventHandler(...arguments);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
handleUpload(fileIDs) {
|
||
|
|
if (fileIDs.length === 0) {
|
||
|
|
this.uppy.log('[XHRUpload] No files to upload!');
|
||
|
|
return Promise.resolve();
|
||
|
|
} // No limit configured by the user, and no RateLimitedQueue passed in by a "parent" plugin
|
||
|
|
// (basically just AwsS3) using the internal symbol
|
||
|
|
|
||
|
|
|
||
|
|
if (this.opts.limit === 0 && !this.opts[_RateLimitedQueue.internalRateLimitedQueue]) {
|
||
|
|
this.uppy.log('[XHRUpload] When uploading multiple files at once, consider setting the `limit` option (to `10` for example), to limit the number of concurrent uploads, which helps prevent memory and network issues: https://uppy.io/docs/xhr-upload/#limit-0', 'warning');
|
||
|
|
}
|
||
|
|
|
||
|
|
this.uppy.log('[XHRUpload] Uploading...');
|
||
|
|
const files = fileIDs.map(fileID => this.uppy.getFile(fileID));
|
||
|
|
|
||
|
|
if (this.opts.bundle) {
|
||
|
|
// if bundle: true, we don’t support remote uploads
|
||
|
|
const isSomeFileRemote = files.some(file => file.isRemote);
|
||
|
|
|
||
|
|
if (isSomeFileRemote) {
|
||
|
|
throw new Error('Can’t upload remote files when the `bundle: true` option is set');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof this.opts.headers === 'function') {
|
||
|
|
throw new TypeError('`headers` may not be a function when the `bundle: true` option is set');
|
||
|
|
}
|
||
|
|
|
||
|
|
return this.uploadBundle(files);
|
||
|
|
}
|
||
|
|
|
||
|
|
return this.uploadFiles(files).then(() => null);
|
||
|
|
}
|
||
|
|
|
||
|
|
install() {
|
||
|
|
if (this.opts.bundle) {
|
||
|
|
const {
|
||
|
|
capabilities
|
||
|
|
} = this.uppy.getState();
|
||
|
|
this.uppy.setState({
|
||
|
|
capabilities: { ...capabilities,
|
||
|
|
individualCancellation: false
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
this.uppy.addUploader(this.handleUpload);
|
||
|
|
}
|
||
|
|
|
||
|
|
uninstall() {
|
||
|
|
if (this.opts.bundle) {
|
||
|
|
const {
|
||
|
|
capabilities
|
||
|
|
} = this.uppy.getState();
|
||
|
|
this.uppy.setState({
|
||
|
|
capabilities: { ...capabilities,
|
||
|
|
individualCancellation: true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
this.uppy.removeUploader(this.handleUpload);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
XHRUpload.VERSION = packageJson.version;
|
||
|
|
module.exports = XHRUpload;
|