@uppy/transloadit
Version:
The Transloadit plugin can be used to upload files to Transloadit for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, and more
281 lines (271 loc) • 11 kB
JavaScript
function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; }
var id = 0;
function _classPrivateFieldLooseKey(e) { return "__private_" + id++ + "_" + e; }
import Emitter from 'component-emitter';
import has from '@uppy/utils/lib/hasProperty';
import NetworkError from '@uppy/utils/lib/NetworkError';
import fetchWithNetworkError from '@uppy/utils/lib/fetchWithNetworkError';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore untyped
const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING';
const ASSEMBLY_EXECUTING = 'ASSEMBLY_EXECUTING';
const ASSEMBLY_COMPLETED = 'ASSEMBLY_COMPLETED';
const statusOrder = [ASSEMBLY_UPLOADING, ASSEMBLY_EXECUTING, ASSEMBLY_COMPLETED];
/**
* Check that an assembly status is equal to or larger than some desired status.
* It checks for things that are larger so that a comparison like this works,
* when the old assembly status is UPLOADING but the new is FINISHED:
*
* !isStatus(oldStatus, ASSEMBLY_EXECUTING) && isStatus(newState, ASSEMBLY_EXECUTING)
*
* …so that we can emit the 'executing' event even if the execution step was so
* fast that we missed it.
*/
function isStatus(status, test) {
return statusOrder.indexOf(status) >= statusOrder.indexOf(test);
}
var _rateLimitedQueue = /*#__PURE__*/_classPrivateFieldLooseKey("rateLimitedQueue");
var _fetchWithNetworkError = /*#__PURE__*/_classPrivateFieldLooseKey("fetchWithNetworkError");
var _previousFetchStatusStillPending = /*#__PURE__*/_classPrivateFieldLooseKey("previousFetchStatusStillPending");
var _sse = /*#__PURE__*/_classPrivateFieldLooseKey("sse");
var _onFinished = /*#__PURE__*/_classPrivateFieldLooseKey("onFinished");
var _connectServerSentEvents = /*#__PURE__*/_classPrivateFieldLooseKey("connectServerSentEvents");
var _onError = /*#__PURE__*/_classPrivateFieldLooseKey("onError");
var _beginPolling = /*#__PURE__*/_classPrivateFieldLooseKey("beginPolling");
var _fetchStatus = /*#__PURE__*/_classPrivateFieldLooseKey("fetchStatus");
var _diffStatus = /*#__PURE__*/_classPrivateFieldLooseKey("diffStatus");
class TransloaditAssembly extends Emitter {
constructor(assembly, rateLimitedQueue) {
super();
// The current assembly status.
/**
* Diff two assembly statuses, and emit the events necessary to go from `prev`
* to `next`.
*/
Object.defineProperty(this, _diffStatus, {
value: _diffStatus2
});
/**
* Reload assembly status. Useful if SSE doesn't work.
*
* Pass `diff: false` to avoid emitting diff events, instead only emitting
* 'status'.
*/
Object.defineProperty(this, _fetchStatus, {
value: _fetchStatus2
});
/**
* Begin polling for assembly status changes. This sends a request to the
* assembly status endpoint every so often, if SSE connection failed.
* If the SSE connection fails or takes a long time, we won't miss any
* events.
*/
Object.defineProperty(this, _beginPolling, {
value: _beginPolling2
});
Object.defineProperty(this, _onError, {
value: _onError2
});
Object.defineProperty(this, _connectServerSentEvents, {
value: _connectServerSentEvents2
});
Object.defineProperty(this, _onFinished, {
value: _onFinished2
});
Object.defineProperty(this, _rateLimitedQueue, {
writable: true,
value: void 0
});
Object.defineProperty(this, _fetchWithNetworkError, {
writable: true,
value: void 0
});
Object.defineProperty(this, _previousFetchStatusStillPending, {
writable: true,
value: false
});
Object.defineProperty(this, _sse, {
writable: true,
value: null
});
this.status = assembly;
// The interval timer for full status updates.
this.pollInterval = null;
// Whether this assembly has been closed (finished or errored)
this.closed = false;
_classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue] = rateLimitedQueue;
_classPrivateFieldLooseBase(this, _fetchWithNetworkError)[_fetchWithNetworkError] = rateLimitedQueue.wrapPromiseFunction(fetchWithNetworkError);
}
connect() {
_classPrivateFieldLooseBase(this, _connectServerSentEvents)[_connectServerSentEvents]();
_classPrivateFieldLooseBase(this, _beginPolling)[_beginPolling]();
}
update() {
return _classPrivateFieldLooseBase(this, _fetchStatus)[_fetchStatus]({
diff: true
});
}
/**
* Update this assembly's status with a full new object. Events will be
* emitted for status changes, new files, and new results.
*/
updateStatus(next) {
_classPrivateFieldLooseBase(this, _diffStatus)[_diffStatus](this.status, next);
this.status = next;
}
/**
* Stop updating this assembly.
*/
close() {
this.closed = true;
if (_classPrivateFieldLooseBase(this, _sse)[_sse]) {
_classPrivateFieldLooseBase(this, _sse)[_sse].close();
_classPrivateFieldLooseBase(this, _sse)[_sse] = null;
}
clearInterval(this.pollInterval);
this.pollInterval = null;
}
}
function _onFinished2() {
this.emit('finished');
this.close();
}
function _connectServerSentEvents2() {
_classPrivateFieldLooseBase(this, _sse)[_sse] = new EventSource(`${this.status.websocket_url}?assembly=${this.status.assembly_id}`);
_classPrivateFieldLooseBase(this, _sse)[_sse].addEventListener('open', () => {
clearInterval(this.pollInterval);
this.pollInterval = null;
});
/*
* The event "message" is a special case, as it
* will capture events without an event field
* as well as events that have the specific type
* other event type.
*/
_classPrivateFieldLooseBase(this, _sse)[_sse].addEventListener('message', e => {
if (e.data === 'assembly_finished') {
_classPrivateFieldLooseBase(this, _onFinished)[_onFinished]();
}
if (e.data === 'assembly_uploading_finished') {
this.emit('executing');
}
if (e.data === 'assembly_upload_meta_data_extracted') {
this.emit('metadata');
_classPrivateFieldLooseBase(this, _fetchStatus)[_fetchStatus]({
diff: false
});
}
});
_classPrivateFieldLooseBase(this, _sse)[_sse].addEventListener('assembly_upload_finished', e => {
const file = JSON.parse(e.data);
this.status.uploads.push(file);
this.emit('upload', file);
});
_classPrivateFieldLooseBase(this, _sse)[_sse].addEventListener('assembly_result_finished', e => {
var _this$status$results, _this$status$results$;
const [stepName, result] = JSON.parse(e.data);
((_this$status$results$ = (_this$status$results = this.status.results)[stepName]) != null ? _this$status$results$ : _this$status$results[stepName] = []).push(result);
this.emit('result', stepName, result);
});
_classPrivateFieldLooseBase(this, _sse)[_sse].addEventListener('assembly_execution_progress', e => {
const details = JSON.parse(e.data);
this.emit('execution-progress', details);
});
_classPrivateFieldLooseBase(this, _sse)[_sse].addEventListener('assembly_error', e => {
try {
_classPrivateFieldLooseBase(this, _onError)[_onError](JSON.parse(e.data));
} catch {
_classPrivateFieldLooseBase(this, _onError)[_onError](new Error(e.data));
}
// Refetch for updated status code
_classPrivateFieldLooseBase(this, _fetchStatus)[_fetchStatus]({
diff: false
});
});
}
function _onError2(assemblyOrError) {
this.emit('error', Object.assign(new Error(assemblyOrError.message), assemblyOrError));
this.close();
}
function _beginPolling2() {
this.pollInterval = setInterval(() => {
_classPrivateFieldLooseBase(this, _fetchStatus)[_fetchStatus]();
}, 2000);
}
async function _fetchStatus2(_temp) {
let {
diff = true
} = _temp === void 0 ? {} : _temp;
if (this.closed || _classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue].isPaused || _classPrivateFieldLooseBase(this, _previousFetchStatusStillPending)[_previousFetchStatusStillPending]) return;
try {
_classPrivateFieldLooseBase(this, _previousFetchStatusStillPending)[_previousFetchStatusStillPending] = true;
const response = await _classPrivateFieldLooseBase(this, _fetchWithNetworkError)[_fetchWithNetworkError](this.status.assembly_ssl_url);
_classPrivateFieldLooseBase(this, _previousFetchStatusStillPending)[_previousFetchStatusStillPending] = false;
if (this.closed) return;
if (response.status === 429) {
_classPrivateFieldLooseBase(this, _rateLimitedQueue)[_rateLimitedQueue].rateLimit(2000);
return;
}
if (!response.ok) {
_classPrivateFieldLooseBase(this, _onError)[_onError](new NetworkError(response.statusText));
return;
}
const status = await response.json();
// Avoid updating if we closed during this request's lifetime.
if (this.closed) return;
this.emit('status', status);
if (diff) {
this.updateStatus(status);
} else {
this.status = status;
}
} catch (err) {
_classPrivateFieldLooseBase(this, _onError)[_onError](err);
}
}
function _diffStatus2(prev, next) {
const prevStatus = prev.ok;
const nextStatus = next.ok;
if (next.error && !prev.error) {
return _classPrivateFieldLooseBase(this, _onError)[_onError](next);
}
// Desired emit order:
// - executing
// - (n × upload)
// - metadata
// - (m × result)
// - finished
// The below checks run in this order, that way even if we jump from
// UPLOADING straight to FINISHED all the events are emitted as expected.
const nowExecuting = isStatus(nextStatus, ASSEMBLY_EXECUTING) && !isStatus(prevStatus, ASSEMBLY_EXECUTING);
if (nowExecuting) {
// Without SSE, this is our only way to tell if uploading finished.
// Hence, we emit this just before the 'upload's and before the 'metadata'
// event for the most intuitive ordering, corresponding to the _usual_
// ordering (if not guaranteed) that you'd get on SSE.
this.emit('executing');
}
// Only emit if the upload is new (not in prev.uploads).
Object.keys(next.uploads).filter(upload => !has(prev.uploads, upload)).forEach(upload => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore either the types are wrong or the tests are wrong.
// types think next.uploads is an array, but the tests pass an object.
this.emit('upload', next.uploads[upload]);
});
if (nowExecuting) {
this.emit('metadata');
}
// Find new results.
Object.keys(next.results).forEach(stepName => {
const nextResults = next.results[stepName];
const prevResults = prev.results[stepName];
nextResults.filter(n => !prevResults || !prevResults.some(p => p.id === n.id)).forEach(result => {
this.emit('result', stepName, result);
});
});
if (isStatus(nextStatus, ASSEMBLY_COMPLETED) && !isStatus(prevStatus, ASSEMBLY_COMPLETED)) {
this.emit('finished');
}
return undefined;
}
export default TransloaditAssembly;