@rpldy/chunked-sender
Version:
adds chunked upload capabilities on top of the regular XHR uploads
86 lines • 3.07 kB
JavaScript
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;