UNPKG

@coolio/http

Version:
231 lines 8.14 kB
import * as mime from 'mime'; import CombinedStream from 'combined-stream'; import { Readable, Writable } from 'stream'; export const SUPPORTS_BROWSER_FORM_DATA = (() => { try { return !({} instanceof FormData); } catch (_a) { return false; } })(); export const isBrowserFormData = (value) => SUPPORTS_BROWSER_FORM_DATA && value instanceof FormData; export const isBlob = (value) => { try { return value instanceof Blob; } catch (_a) { return false; } }; export const readBlob = (blob) => { const reader = new FileReader(); return new Promise((resolve, reject) => { const onEnd = (e) => { reader.removeEventListener('loadend', onEnd); if (e.error) { reject(e.error); } else { resolve(Buffer.from(reader.result)); } }; reader.addEventListener('loadend', onEnd); reader.readAsArrayBuffer(blob); }); }; export const getFileMeta = (value, existingMeta) => { const anyValue = value; const isHttpRequest = anyValue.readable && anyValue.hasOwnProperty('httpVersion'); const path = ((existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.filename) || (existingMeta === null || existingMeta === void 0 ? void 0 : existingMeta.filepath) || anyValue.name || anyValue.path || (isHttpRequest && anyValue.client._httpMessage.path) || ''); const filename = path.substring(Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')) + 1); const contentType = anyValue.type || mime.getType(path) || (isHttpRequest && anyValue.headers['content-type']) || CFormData.DEFAULT_CONTENT_TYPE; return Object.assign(Object.assign({ contentType, knownLength: anyValue.size }, existingMeta), { filename }); }; export class CFormData { constructor(data) { this.data = new Map(); if (!data) { return; } if (CFormData.isFormData(data)) { data.forEach((value, key) => this.append(key, value)); return; } for (const key in data) { if (data.hasOwnProperty(key)) { this.append(key, data[key]); } } } static from(data, { forceImplementation } = {}) { if ((isBrowserFormData(data) && forceImplementation !== 'custom') || (data instanceof CFormData && forceImplementation !== 'native')) { return data; } const formData = new CFormData(data); if (!SUPPORTS_BROWSER_FORM_DATA || forceImplementation === 'custom') { return formData; } const browserFormData = new FormData(); formData.forEach((value, name) => browserFormData.append(name, value)); return browserFormData; } static isFormData(data) { return isBrowserFormData(data) || data instanceof CFormData; } append(name, value, meta) { this.insert(name, value, true, meta); } delete(name) { this.data.delete(name); } getAll(name) { var _a; return ((_a = this.data.get(name)) === null || _a === void 0 ? void 0 : _a.map(e => e.value)) || []; } set(name, value, meta) { this.insert(name, value, false, meta); } get(name) { var _a, _b, _c; return (_c = (_b = (_a = this.data.get(name)) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : null; } has(name) { return this.data.has(name); } forEach(callbackfn) { this.data.forEach((value, key) => value.forEach(entry => callbackfn(entry.value, key, this))); } getBoundary() { let boundary = this.boundary; if (!boundary) { boundary = '--------------------------'; for (let i = 0; i < 24; i++) { boundary += Math.floor(Math.random() * 10).toString(16); } this.boundary = boundary; } return boundary; } ; getHeaders() { return { 'content-type': 'multipart/form-data; boundary=' + this.getBoundary(), }; } pipe(writable) { const stream = CombinedStream.create(); this.data.forEach((entries, name) => { for (const entry of entries) { stream.append(this.multiPartHeader(name, entry === null || entry === void 0 ? void 0 : entry.meta)); const value = entry.value; if (value instanceof Readable) { stream.append(writable); } else if (isBlob(value)) { stream.append(next => readBlob(value).then(next)); } else { stream.append(value); } stream.append(CFormData.LINE_BREAK); } }); stream.append(this.lastBoundary()); stream.pipe(writable); } getBuffer() { return new Promise((resolve, reject) => { let buffer = Buffer.alloc(0); const writable = new Writable({ write: (chunk, encoding, callback) => { buffer = Buffer.concat([buffer, Buffer.from(chunk, encoding)]); callback(); } }); writable.on('error', reject); writable.on('finish', () => resolve(buffer)); this.pipe(writable); }); } toString() { return '[object FormData]'; } insert(name, value, append, meta) { if (value === undefined) { return; } if (value instanceof Date) { value = value.toISOString(); } if (Array.isArray(value)) { for (let index = 0; index < value.length; index++) { this.insert(`${name}[${index}]`, value[index], append); } return; } if (typeof value === 'object' && !isBlob(value)) { for (const objectKey in value) { if (!value.hasOwnProperty(objectKey) || value[objectKey] === undefined) { continue; } this.append(`${name}[${objectKey}]`, value[objectKey]); } return; } if (isBlob(value)) { meta = getFileMeta(value, meta); } if (!append) { this.data.set(name, [{ value, meta }]); return; } const existingEntries = this.data.get(name); const newEntry = { value, meta }; this.data.set(name, existingEntries ? [...existingEntries, newEntry] : [newEntry]); } multiPartHeader(name, meta = {}) { if (typeof meta.header === 'string') { return meta.header; } const contentDisposition = meta.filename ? `filename="${meta.filename}"` : ''; const contentType = meta.contentType; let contents = ''; const headers = { 'Content-Disposition': ['form-data', `name="${name}"`, contentDisposition], 'Content-Type': [contentType], }; if (typeof meta.header === 'object') { Object.assign(headers, meta.header); } for (const prop in headers) { let header = headers[prop]; if (!headers.hasOwnProperty(prop) || !header) { continue; } if (!Array.isArray(header)) { header = [header]; } header = header.filter(Boolean); if (header.length) { contents += prop + ': ' + header.join('; ') + CFormData.LINE_BREAK; } } return '--' + this.getBoundary() + CFormData.LINE_BREAK + contents + CFormData.LINE_BREAK; } ; lastBoundary() { return '--' + this.getBoundary() + '--' + CFormData.LINE_BREAK; } } CFormData.LINE_BREAK = '\r\n'; CFormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream'; //# sourceMappingURL=formData.js.map