File size: 3,560 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
'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,
        percent: 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 percent & remaining only if we know the total size
    if (state.size.total != null) {
        state.percent = Math.min(state.size.transferred, state.size.total) / state.size.total;

        if (state.speed != null) {
            state.time.remaining = state.percent !== 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', function handleResponse(response) {
        response.on('data', onData.bind(null, context));
        
        return onResponse(context, response);
    })
    .on('end', onEnd.bind(null, context));

    request.progressContext = context;

    return request;
}

module.exports = requestProgress;