UNPKG

@rpldy/chunked-sender

Version:

adds chunked upload capabilities on top of the regular XHR uploads

86 lines 3.07 kB
import { triggerUpdater, createBatchItem, logger, getMerge, pick, FILE_STATES } from "@rpldy/shared"; import { unwrap } from "@rpldy/simple-state"; import xhrSend from "@rpldy/sender"; import { getChunkDataFromFile } from "../utils"; import { CHUNK_EVENTS } from "../consts"; import ChunkedSendError from "./ChunkedSendError"; const getContentRangeValue = (chunk, data, item) => data && `bytes ${chunk.start}-${chunk.start + data.size - 1}/${item.file.size}`; const mergeWithUndefined = getMerge({ undefinedOverwrites: true }); const getSkippedResult = () => ({ request: Promise.resolve({ state: FILE_STATES.FINISHED, response: "skipping chunk as instructed by CHUNK_START handler", status: 200 }), abort: () => true, senderType: "chunk-skipped-sender" }); const uploadChunkWithUpdatedData = (chunk, chunkedState, item, onProgress, trigger) => { const state = chunkedState.getState(); const unwrappedOptions = unwrap(state.sendOptions); const sendOptions = { ...unwrappedOptions, headers: { ...unwrappedOptions.headers, "Content-Range": getContentRangeValue(chunk, chunk.data, item) } }; const chunkItem = createBatchItem(chunk.data, chunk.id); const onChunkProgress = e => { onProgress(e, [chunk]); }; const chunkIndex = state.chunks.indexOf(chunk); return triggerUpdater(trigger, CHUNK_EVENTS.CHUNK_START, { item: unwrap(item), chunk: pick(chunk, ["id", "start", "end", "index", "attempt"]), chunkItem: { ...chunkItem }, sendOptions, url: state.url, chunkIndex, remainingCount: state.chunks.length, totalCount: state.chunkCount, onProgress }).then(response => { let result; const updatedData = typeof response === "boolean" ? response === false ? { stop: true } : {} : response || {}; if (updatedData.stop) { logger.debugLog(`chunkedSender.sendChunk: received false from CHUNK_START handler - skipping chunk ${chunkIndex}, item ${item.id}`); result = getSkippedResult(); } else { result = xhrSend([chunkItem], updatedData?.url || state.url, mergeWithUndefined({}, sendOptions, updatedData?.sendOptions), onChunkProgress); } return result; }); }; const sendChunk = (chunk, chunkedState, item, onProgress, trigger) => { if (!chunk.data) { chunkedState.updateState(() => { chunk.data = getChunkDataFromFile(item.file, chunk.start, chunk.end); }); } if (!chunk.data) { throw new ChunkedSendError("chunk failure - failed to slice"); } const url = chunkedState.getState().url; logger.debugLog(`chunkedSender.sendChunk: about to send chunk ${chunk.id} [${chunk.start}-${chunk.end}] to: ${url || ""}`); const chunkXhrRequest = uploadChunkWithUpdatedData(chunk, chunkedState, item, onProgress, trigger); return { request: chunkXhrRequest.then(({ request }) => request), abort: () => { chunkXhrRequest.then(({ abort }) => abort()); return true; }, senderType: "chunk-passthrough-sender" }; }; export default sendChunk;