UNPKG

@uploadx/core

Version:
151 lines (150 loc) 5.93 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Uploadx = void 0; exports.rangeParser = rangeParser; exports.uploadx = uploadx; const url_1 = __importDefault(require("url")); const utils_1 = require("../utils"); const base_handler_1 = require("./base-handler"); function rangeParser(rangeHeader = '') { const parts = rangeHeader.split(/\s+|\//); const size = parseInt(parts[2]); const start = parseInt(parts[1]); return { start, size }; } /** * [X-headers protocol implementation](https://github.com/kukhariev/node-uploadx/blob/master/proto.md#requests-overview) */ class Uploadx extends base_handler_1.BaseHandler { /** * Create File from request and send a file url to client */ async post(req, res) { const metadata = await this.getMetadata(req); const config = { metadata }; config.userId = this.getUserId(req, res); config.size = (0, utils_1.getHeader)(req, 'x-upload-content-length'); config.contentType = (0, utils_1.getHeader)(req, 'x-upload-content-type'); const file = await this.storage.create(req, config); const headers = this.buildHeaders(file, { Location: this.buildFileUrl(req, file) }); file.bytesWritten > 0 && (headers['Range'] = `bytes=0-${file.bytesWritten - 1}`); (0, utils_1.setHeaders)(res, headers); if (file.status === 'completed') return file; const response = await this.storage.onCreate(file); response.statusCode = file.bytesWritten > 0 ? 200 : 201; this.send(res, response); return file; } async patch(req, res) { const id = await this.getAndVerifyId(req, res); const metadata = await this.getMetadata(req); const file = await this.storage.update({ id }, metadata); const headers = this.buildHeaders(file, { Location: this.buildFileUrl(req, file) }); (0, utils_1.setHeaders)(res, headers); if (file.status === 'completed') return file; const response = await this.storage.onUpdate(file); this.send(res, response); return file; } /** * Write a chunk to file or/and return chunk offset */ async put(req, res) { const id = await this.getAndVerifyId(req, res); const contentRange = (0, utils_1.getHeader)(req, 'content-range'); const contentLength = +(0, utils_1.getHeader)(req, 'content-length'); const { start, size = NaN } = contentRange ? rangeParser(contentRange) : { start: 0 }; const { checksumAlgorithm, checksum } = this.extractChecksum(req); const file = await this.storage.write({ start, id, body: req, size, contentLength, checksumAlgorithm, checksum }); const headers = this.buildHeaders(file); if (file.status === 'completed') return file; headers['Range'] = `bytes=0-${file.bytesWritten - 1}`; res.statusMessage = 'Resume Incomplete'; this.send(res, { statusCode: Uploadx.RESUME_STATUS_CODE, headers }); return file; } /** * Delete upload */ async delete(req, res) { const id = await this.getAndVerifyId(req, res); const [file] = await this.storage.delete({ id }); const response = await this.storage.onDelete(file); this.send(res, { statusCode: 204, ...response }); return file; } getId(req) { const { query } = url_1.default.parse(req.url || '', true); return (query.upload_id || query.prefix || super.getId(req)); } buildHeaders(file, headers = {}) { if (file.expiredAt) headers['X-Upload-Expires'] = new Date(file.expiredAt).toISOString(); return headers; } /** * Build file url from request */ buildFileUrl(req, file) { if (file.GCSUploadURI) return file.GCSUploadURI; const { query, pathname } = url_1.default.parse(req.originalUrl || req.url, true); query.upload_id = file.id; const relative = url_1.default.format({ pathname, query }); return this.storage.config.useRelativeLocation ? relative : (0, utils_1.getBaseUrl)(req) + relative; } async getMetadata(req) { const metadata = await (0, utils_1.getJsonBody)(req, this.storage.maxMetadataSize).catch(err => (0, utils_1.fail)(utils_1.ERRORS.BAD_REQUEST, err)); if (Object.keys(metadata).length) return metadata; const { query } = url_1.default.parse(decodeURI(req.url || ''), true); return { ...query }; } extractChecksum(req) { const contentMD5 = (0, utils_1.getHeader)(req, 'content-md5'); if (contentMD5) return { checksumAlgorithm: 'md5', checksum: contentMD5 }; const [type, checksum] = (0, utils_1.getHeader)(req, 'digest').split(/=(.*)/s); return { checksumAlgorithm: { sha: 'sha1', 'sha-256': 'sha256' }[type] || type, checksum }; } } exports.Uploadx = Uploadx; Uploadx.RESUME_STATUS_CODE = 308; /** * Basic express wrapper * @example * ```ts * app.use('/files', uploadx({directory: '/tmp', maxUploadSize: '250GB'})); * ``` */ function uploadx(options = {}) { return new Uploadx(options).handle; } /** * Express wrapper * * - express ***should*** respond to the client when the upload complete and handle errors and GET requests * @example * ```ts * app.use('/files', uploadx.upload({ storage }), (req, res, next) => { * if (req.method === 'GET') return res.sendStatus(404); * console.log('File upload complete: ', req.body.name); * return res.json(req.body); * }); * ``` */ uploadx.upload = (options = {}) => new Uploadx(options).upload;