index.js 3.41 KB
'use strict';

var throttle = require('throttleit');

function onRequest(context) {
    // Reset dynamic stuff
    context.startedAt = null;

    context.state = context.request.progressState = null;

    context.delayTimer && clearTimeout(context.delayTimer);
    context.delayTimer = null;
}

function onResponse(context, response) {
    // Mark start timestamp
    context.startedAt = Date.now();

    // Create state
    // Also expose the state throught the request
    // See https://github.com/IndigoUnited/node-request-progress/pull/2/files
    context.state = context.request.progressState = {
        time: {
            elapsed: 0,
            remaining: null
        },
        speed: null,
        percentage: null,
        size: {
            total: Number(response.headers[context.options.lengthHeader]) || null,
            transferred: 0
        }
    };

    // Delay the progress report
    context.delayTimer = setTimeout(function () {
        context.delayTimer = null;
    }, context.options.delay);
}

function onData(context, data) {
    context.state.size.transferred += data.length;

    !context.delayTimer && context.reportState();
}

function onEnd(context) {
    /* istanbul ignore if */
    if (context.delayTimer) {
        clearTimeout(context.delayTimer);
        context.delayTimer = null;
    }

    context.request.progressState = context.request.progressContext = null;
}

function reportState(context) {
    var state;

    // Do nothing if still within the initial delay or if already finished
    if (context.delayTimer || !context.request.progressState) {
        return;
    }

    state = context.state;
    state.time.elapsed = (Date.now() - context.startedAt) / 1000;

    // Calculate speed only if 1s has passed
    if (state.time.elapsed >= 1) {
        state.speed = state.size.transferred / state.time.elapsed;
    }

    // Calculate percentage & remaining only if we know the total size
    if (state.size.total != null) {
        state.percentage = Math.min(state.size.transferred, state.size.total) / state.size.total;

        if (state.speed != null) {
            state.time.remaining = state.percentage !== 1 ? (state.size.total / state.speed) - state.time.elapsed : 0;
            state.time.remaining = Math.round(state.time.remaining * 1000) / 1000;  // Round to 4 decimals
        }
    }

    context.request.emit('progress', state);
}


function requestProgress(request, options) {
    var context;

    if (request.progressContext) {
        return request;
    }

    if (request.response) {
        throw new Error('Already got response, it\'s too late to track progress');
    }

    // Parse options
    options = options || {};
    options.throttle = options.throttle == null ? 1000 : options.throttle;
    options.delay = options.delay || 0;
    options.lengthHeader = options.lengthHeader || 'content-length';

    // Create context
    context = {};
    context.request = request;
    context.options = options;
    context.reportState = throttle(reportState.bind(null, context), options.throttle);
    // context.startedAt = null;
    // context.state = null;
    // context.delayTimer = null;

    // Attach listeners
    request
    .on('request', onRequest.bind(null, context))
    .on('response', onResponse.bind(null, context))
    .on('data', onData.bind(null, context))
    .on('end', onEnd.bind(null, context));

    request.progressContext = context;

    return request;
}

module.exports = requestProgress;