UNPKG

@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

156 lines (155 loc) 5.87 kB
import { fetchWithNetworkError } from '@uppy/utils'; import { getAssemblyUrlSsl, } from './index.js'; const ASSEMBLIES_ENDPOINT = '/assemblies'; export class AssemblyError extends Error { details; assembly; constructor(message, details, assembly) { super(message); this.details = details; this.assembly = assembly; } } /** * A Barebones HTTP API client for Transloadit. */ export default class Client { #headers = {}; #fetchWithNetworkError; opts; constructor(opts) { this.opts = opts; if (this.opts.client != null) { this.#headers['Transloadit-Client'] = this.opts.client; } this.#fetchWithNetworkError = this.opts.rateLimitedQueue.wrapPromiseFunction(fetchWithNetworkError); } async #fetchJSON(...args) { const response = await this.#fetchWithNetworkError(...args); if (response.status === 429) { this.opts.rateLimitedQueue.rateLimit(2_000); return this.#fetchJSON(...args); } if (!response.ok) { const serverError = new Error(response.statusText); // @ts-expect-error statusCode is not a standard property serverError.statusCode = response.status; if (!`${args[0]}`.endsWith(ASSEMBLIES_ENDPOINT)) return Promise.reject(serverError); // Failed assembly requests should return a more detailed error in JSON. return response.json().then((assembly) => { if (!assembly.error) throw serverError; const error = new AssemblyError(assembly.error, assembly.message, assembly); if (assembly.assembly_id) { error.details += ` Assembly ID: ${assembly.assembly_id}`; } throw error; }, (err) => { err.cause = serverError; throw err; }); } return response.json(); } async createAssembly({ params, fields, signature, expectedFiles, }) { const data = new FormData(); data.append('params', typeof params === 'string' ? params : JSON.stringify(params)); if (signature) { data.append('signature', signature); } Object.keys(fields).forEach((key) => { data.append(key, String(fields[key])); }); data.append('num_expected_upload_files', String(expectedFiles)); const url = new URL(ASSEMBLIES_ENDPOINT, `${this.opts.service}`).href; return this.#fetchJSON(url, { method: 'POST', headers: this.#headers, body: data, }).catch((err) => this.#reportError(err, { url, type: 'API_ERROR' })); } /** * Reserve resources for a file in an Assembly. Then addFile can be used later. */ async reserveFile(assembly, file) { const size = encodeURIComponent(file.size); const assemblyUrl = getAssemblyUrlSsl(assembly); const url = `${assemblyUrl}/reserve_file?size=${size}`; return this.#fetchJSON(url, { method: 'POST', headers: this.#headers, }).catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' })); } /** * Import a remote file to an Assembly. */ async addFile(assembly, file) { if (!file.uploadURL) { return Promise.reject(new Error('File does not have an `uploadURL`.')); } const size = encodeURIComponent(file.size); const uploadUrl = encodeURIComponent(file.uploadURL); const filename = encodeURIComponent(file.name ?? 'Unnamed'); const fieldname = 'file'; const qs = `size=${size}&filename=${filename}&fieldname=${fieldname}&s3Url=${uploadUrl}`; const assemblyUrl = getAssemblyUrlSsl(assembly); const url = `${assemblyUrl}/add_file?${qs}`; return this.#fetchJSON(url, { method: 'POST', headers: this.#headers, }).catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' })); } /** * Cancel a running Assembly. */ async cancelAssembly(assembly) { const url = getAssemblyUrlSsl(assembly); await this.#fetchWithNetworkError(url, { method: 'DELETE', headers: this.#headers, }).catch((err) => this.#reportError(err, { url, type: 'API_ERROR' })); } /** * Get the current status for an assembly. */ async getAssemblyStatus(url) { return this.#fetchJSON(url, { headers: this.#headers }).catch((err) => this.#reportError(err, { url, type: 'STATUS_ERROR' })); } async submitError(err, { endpoint, instance, assembly, } = {}) { const message = err.details ? `${err.message} (${err.details})` : err.message; return this.#fetchJSON('https://transloaditstatus.com/client_error', { method: 'POST', body: JSON.stringify({ endpoint, instance, assembly_id: assembly, agent: typeof navigator !== 'undefined' ? navigator.userAgent : '', client: this.opts.client, error: message, }), }); } #reportError = (err, params) => { if (this.opts.errorReporting === false) { throw err; } const opts = { type: params.type, }; if (params.assembly) { opts.assembly = params.assembly.assembly_id; opts.instance = params.assembly.instance; } if (params.url) { opts.endpoint = params.url; } this.submitError(err, opts).catch(() => { // not much we can do then is there }); throw err; }; }