UNPKG

@uploadx/core

Version:
162 lines (161 loc) 5.96 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Tus = exports.TUS_VERSION = exports.TUS_RESUMABLE = void 0; exports.serializeMetadata = serializeMetadata; exports.parseMetadata = parseMetadata; exports.tus = tus; const storages_1 = require("../storages"); const utils_1 = require("../utils"); const base_handler_1 = require("./base-handler"); exports.TUS_RESUMABLE = '1.0.0'; exports.TUS_VERSION = '1.0.0'; function serializeMetadata(obj) { return Object.entries(obj) .map(([key, value]) => `${key} ${Buffer.from(String(value)).toString('base64')}`) .toString(); } function parseMetadata(encoded = '') { const kvPairs = encoded.split(',').map(kv => kv.split(' ')); const metadata = Object.create(storages_1.Metadata.prototype); for (const [key, value] of kvPairs) { if (key) metadata[key] = value ? Buffer.from(value, 'base64').toString() : ''; } return metadata; } /** * [tus resumable upload protocol](https://github.com/tus/tus-resumable-upload-protocol/blob/master/protocol.md) */ class Tus extends base_handler_1.BaseHandler { get extension() { const _extensions = ['creation', 'creation-with-upload', 'termination', 'checksum']; if (this.storage.config.expiration) _extensions.push('expiration'); return _extensions; } /** * Sends current server configuration */ async options(req, res) { const headers = { 'Tus-Extension': this.extension.toString(), 'Tus-Max-Size': this.storage.maxUploadSize, 'Tus-Checksum-Algorithm': this.storage.checksumTypes.toString() }; this.send(res, { statusCode: 204, headers }); return {}; } /** * Create a file and send url to client */ async post(req, res) { const metadataHeader = (0, utils_1.getHeader)(req, 'upload-metadata', true); const metadata = parseMetadata(metadataHeader); const config = { metadata }; config.userId = this.getUserId(req, res); config.size = (0, utils_1.getHeader)(req, 'upload-length'); let file = await this.storage.create(req, config); // 'creation-with-upload' block if ((0, utils_1.typeis)(req, ['application/offset+octet-stream'])) { const contentLength = +(0, utils_1.getHeader)(req, 'content-length'); file = await this.storage.write({ ...file, start: 0, body: req, contentLength }); } const headers = this.buildHeaders(file, { Location: this.buildFileUrl(req, file) }); file.bytesWritten > 0 && (headers['Upload-Offset'] = file.bytesWritten); (0, utils_1.setHeaders)(res, headers); if (file.status === 'completed') return file; const statusCode = file.bytesWritten > 0 ? 200 : 201; this.send(res, { statusCode }); return file; } /** * Write a chunk to file */ async patch(req, res) { const id = await this.getAndVerifyId(req, res); const metadataHeader = (0, utils_1.getHeader)(req, 'upload-metadata', true); const metadata = metadataHeader && parseMetadata(metadataHeader); metadata && (await this.storage.update({ id }, { metadata, id })); const start = Number((0, utils_1.getHeader)(req, 'upload-offset')); const contentLength = +(0, utils_1.getHeader)(req, 'content-length'); const { checksumAlgorithm, checksum } = this.extractChecksum(req); const file = await this.storage.write({ start, id, body: req, contentLength, checksumAlgorithm, checksum }); const headers = this.buildHeaders(file, { 'Upload-Offset': file.bytesWritten }); (0, utils_1.setHeaders)(res, headers); if (file.status === 'completed') return file; this.send(res, { statusCode: 204 }); return file; } /** * Return chunk offset */ async head(req, res) { const id = await this.getAndVerifyId(req, res); const file = await this.storage.write({ id }); const headers = this.buildHeaders(file, { 'Upload-Offset': file.bytesWritten, 'Upload-Length': file.size, 'Upload-Metadata': serializeMetadata(file.metadata) }); this.send(res, { statusCode: 200, headers }); return {}; } /** * Delete upload */ async delete(req, res) { const id = await this.getAndVerifyId(req, res); const [file] = await this.storage.delete({ id }); this.send(res, { statusCode: 204 }); return file; } buildHeaders(file, headers = {}) { if (file.expiredAt) headers['Upload-Expires'] = new Date(file.expiredAt).toUTCString(); return headers; } send(res, { statusCode, headers = {}, body }) { headers['Tus-Resumable'] = exports.TUS_RESUMABLE; super.send(res, { statusCode, headers, body }); } extractChecksum(req) { const [checksumAlgorithm, checksum] = (0, utils_1.getHeader)(req, 'upload-checksum') .split(/\s+/) .filter(Boolean); return { checksumAlgorithm, checksum }; } } exports.Tus = Tus; /** * Basic express wrapper * @example * ```js * app.use('/files', tus({directory: '/tmp', maxUploadSize: '250GB'})); * ``` */ function tus(options = {}) { return new Tus(options).handle; } /** * Express wrapper * * - express ***should*** respond to the client when the upload complete and handle errors and GET requests * @example * ```js * app.all('/files', tus.upload({ storage }), (req, res, next) => { * if (req.method === 'GET') return res.sendStatus(404); * console.log('File upload complete: ', req.body.name); * return res.sendStatus(204); * }); * ``` */ tus.upload = (options = {}) => new Tus(options).upload;