UNPKG

@platform/cell.client

Version:

A strongly typed HTTP client for operating with a CellOS service end-point.

115 lines (114 loc) 4.7 kB
import { defaultValue, util, ERROR, FormData, Schema } from '../common'; export async function upload(args) { const { http, urls, cellUri } = args; const input = Array.isArray(args.input) ? args.input : [args.input]; const sendChanges = defaultValue(args.changes, false); let changes; const addChanges = (input) => { if (sendChanges && input && input.length > 0) { changes = changes || []; changes = [...changes, ...input]; } }; const cellUrls = urls.cell(cellUri); const url = { start: cellUrls.files.upload.query({ changes: sendChanges }).toString(), complete: cellUrls.files.uploaded.query({ changes: sendChanges }).toString(), }; const uploadStartBody = { expires: undefined, files: input.map(({ filename, data, mimetype }) => { const filehash = Schema.hash.sha256(data); return { filename, filehash, mimetype }; }), }; const res1 = await http.post(url.start, uploadStartBody); if (!res1.ok) { const type = ERROR.HTTP.SERVER; const message = `Failed during initial file-upload step to '${cellUri}'.`; return util.toError(res1.status, type, message); } const uploadStart = res1.json; addChanges(uploadStart.data.changes); const uploadUrls = uploadStart.urls.uploads; const fileUploadWait = uploadUrls .map(upload => { const file = input.find(item => item.filename === upload.filename); const data = file ? file.data : undefined; return { data, upload }; }) .filter(({ data }) => Boolean(data)) .map(async ({ upload, data }) => { const { url } = upload; const uploadToS3 = async () => { const props = upload.props; const contentType = props['content-type']; const form = new FormData(); Object.keys(props) .map(key => ({ key, value: props[key] })) .forEach(({ key, value }) => form.append(key, value)); form.append('file', data, { contentType }); const headers = form.getHeaders(); return http.post(url, form, { headers }); }; const uploadToLocal = async () => { const path = upload.props.key; const headers = { 'content-type': 'multipart/form-data', path }; return http.post(url, data, { headers }); }; const isLocal = url.startsWith('http://localhost'); const res = await (isLocal ? uploadToLocal() : uploadToS3()); const { ok, status } = res; const { uri, filename, expiresAt } = upload; return { ok, status, uri, filename, expiresAt }; }); const res2 = await Promise.all(fileUploadWait); const fileUploadSuccesses = res2.filter(item => item.ok); const fileUploadFails = res2.filter(item => !item.ok); const fileUploadErrors = fileUploadFails.map(item => { const { filename } = item; const message = `Failed while uploading file '${filename}'`; const error = { type: 'FILE/upload', filename, message }; return error; }); const res3 = await Promise.all(fileUploadSuccesses.map(async (item) => { const url = urls .file(item.uri) .uploaded.query({ changes: sendChanges }) .toString(); const { filename } = item; const body = { filename }; const res = await http.post(url, body); const { status, ok } = res; const json = res.json; const file = json.data; return { status, ok, json, file, filename }; })); res3.forEach(res => addChanges(res.json.changes)); const fileCompleteFails = res3.filter(res => !res.ok); const fileCompleteFailErrors = fileCompleteFails.map(res => { const filename = res.filename || 'UNKNOWN'; const message = `Failed while completing upload of file '${filename}'`; const error = { type: 'FILE/upload', filename, message }; return error; }); const cellUploadCompleteBody = {}; const res4 = await http.post(url.complete, cellUploadCompleteBody); const cellUploadComplete = res4.json; const files = cellUploadComplete.data.files; addChanges(cellUploadComplete.data.changes); const errors = [ ...uploadStart.data.errors, ...fileUploadErrors, ...fileCompleteFailErrors, ]; const status = errors.length === 0 ? 200 : 500; const responseBody = { uri: cellUri, cell: cellUploadComplete.data.cell, files, errors, changes, }; return util.toClientResponse(status, responseBody); }