UNPKG

tus-js-client-stall-detection

Version:

A pure JavaScript client for the tus resumable upload protocol (fork with stall detection)

111 lines 4.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebStreamFileSource = void 0; function len(blobOrArray) { if (blobOrArray === undefined) return 0; if (blobOrArray instanceof Blob) return blobOrArray.size; return blobOrArray.length; } /* Typed arrays and blobs don't have a concat method. This function helps StreamSource accumulate data to reach chunkSize. */ function concat(a, b) { if (a instanceof Blob && b instanceof Blob) { return new Blob([a, b], { type: a.type }); } if (a instanceof Uint8Array && b instanceof Uint8Array) { const c = new Uint8Array(a.length + b.length); c.set(a); c.set(b, a.length); return c; } throw new Error('Unknown data type'); } /** * WebStreamFileSource implements FileSource for Web Streams. */ // TODO: Can we share code with NodeStreamFileSource? class WebStreamFileSource { constructor(stream) { // _bufferOffset defines at which position the content of _buffer (if it is set) // is located in the view of the entire stream. It does not mean at which offset // the content in _buffer begins. this._bufferOffset = 0; this._done = false; // Setting the size to null indicates that we have no calculation available // for how much data this stream will emit requiring the user to specify // it manually (see the `uploadSize` option). this.size = null; if (stream.locked) { throw new Error('Readable stream is already locked to reader. tus-js-client cannot obtain a new reader.'); } this._reader = stream.getReader(); } async slice(start, end) { if (start < this._bufferOffset) { throw new Error("Requested data is before the reader's current offset"); } return await this._readUntilEnoughDataOrDone(start, end); } async _readUntilEnoughDataOrDone(start, end) { const hasEnoughData = end <= this._bufferOffset + len(this._buffer); if (this._done || hasEnoughData) { const value = this._getDataFromBuffer(start, end); if (value === null) { return { value: null, size: null, done: true }; } const size = value instanceof Blob ? value.size : value.length; const done = this._done; return { value, size, done }; } const { value, done } = await this._reader.read(); if (done) { this._done = true; } else { const chunkSize = len(value); // If all of the chunk occurs before 'start' then drop it and clear the buffer. // This greatly improves performance when reading from a stream we haven't started processing yet and 'start' is near the end of the file. // Rather than buffering all of the unused data in memory just to only read a chunk near the end, rather immidiately drop data which will never be read. if (this._bufferOffset + len(this._buffer) + chunkSize < start) { this._buffer = undefined; this._bufferOffset += chunkSize; } else if (this._buffer === undefined) { this._buffer = value; } else { this._buffer = concat(this._buffer, value); } } return await this._readUntilEnoughDataOrDone(start, end); } _getDataFromBuffer(start, end) { if (this._buffer === undefined) { throw new Error('cannot _getDataFromBuffer because _buffer is unset'); } // Remove data from buffer before `start`. // Data might be reread from the buffer if an upload fails, so we can only // safely delete data when it comes *before* what is currently being read. if (start > this._bufferOffset) { this._buffer = this._buffer.slice(start - this._bufferOffset); this._bufferOffset = start; } // If the buffer is empty after removing old data, all data has been read. const hasAllDataBeenRead = len(this._buffer) === 0; if (this._done && hasAllDataBeenRead) { return null; } // We already removed data before `start`, so we just return the first // chunk from the buffer. return this._buffer.slice(0, end - start); } close() { this._reader.cancel(); } } exports.WebStreamFileSource = WebStreamFileSource; //# sourceMappingURL=WebStreamFileSource.js.map