diff --git a/lib/helpers/progressEventReducer.js b/lib/helpers/progressEventReducer.js index d09d753c..85f109b7 100644 --- a/lib/helpers/progressEventReducer.js +++ b/lib/helpers/progressEventReducer.js @@ -7,13 +7,13 @@ export const progressEventReducer = (listener, isDownloadStream, freq = 3) => { const _speedometer = speedometer(50, 250); return throttle((e) => { - const loaded = e.loaded; + const rawLoaded = e.loaded; const total = e.lengthComputable ? e.total : undefined; - const progressBytes = loaded - bytesNotified; + const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded; + const progressBytes = Math.max(0, loaded - bytesNotified); const rate = _speedometer(progressBytes); - const inRange = loaded <= total; - bytesNotified = loaded; + bytesNotified = Math.max(bytesNotified, loaded); const data = { loaded, @@ -21,7 +21,7 @@ export const progressEventReducer = (listener, isDownloadStream, freq = 3) => { progress: total ? loaded / total : undefined, bytes: progressBytes, rate: rate ? rate : undefined, - estimated: rate && total && inRange ? (total - loaded) / rate : undefined, + estimated: rate && total ? (total - loaded) / rate : undefined, event: e, lengthComputable: total != null, [isDownloadStream ? 'download' : 'upload']: true, diff --git a/test/unit/helpers/progressEventReducer.js b/test/unit/helpers/progressEventReducer.js new file mode 100644 index 00000000..73b3a9e0 --- /dev/null +++ b/test/unit/helpers/progressEventReducer.js @@ -0,0 +1,27 @@ +import assert from 'assert'; +import { progressEventReducer } from '../../../lib/helpers/progressEventReducer.js'; + +describe('helpers::progressEventReducer', () => { + it('should clamp loaded/progress and avoid negative bytes for out-of-order events', () => { + const events = []; + const [onProgress, flush] = progressEventReducer((data) => { + events.push(data); + }, false, Number.POSITIVE_INFINITY); + + onProgress({ lengthComputable: true, loaded: 80, total: 100 }); + onProgress({ lengthComputable: true, loaded: 60, total: 100 }); + onProgress({ lengthComputable: true, loaded: 180, total: 100 }); + flush(); + + assert.strictEqual(events.length, 3); + assert.strictEqual(events[0].bytes, 80); + assert.strictEqual(events[1].bytes, 0); + + const last = events[events.length - 1]; + assert.strictEqual(last.loaded, 100); + assert.strictEqual(last.total, 100); + assert.strictEqual(last.progress, 1); + assert.strictEqual(last.upload, true); + assert.strictEqual(last.bytes, 20); + }); +});