index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. 'use strict';
  2. var throttle = require('throttleit');
  3. function onRequest(context) {
  4. // Reset dynamic stuff
  5. context.startedAt = null;
  6. context.state = context.request.progressState = null;
  7. context.delayTimer && clearTimeout(context.delayTimer);
  8. context.delayTimer = null;
  9. }
  10. function onResponse(context, response) {
  11. // Mark start timestamp
  12. context.startedAt = Date.now();
  13. // Create state
  14. // Also expose the state throught the request
  15. // See https://github.com/IndigoUnited/node-request-progress/pull/2/files
  16. context.state = context.request.progressState = {
  17. time: {
  18. elapsed: 0,
  19. remaining: null
  20. },
  21. speed: null,
  22. percent: null,
  23. size: {
  24. total: Number(response.headers[context.options.lengthHeader]) || null,
  25. transferred: 0
  26. }
  27. };
  28. // Delay the progress report
  29. context.delayTimer = setTimeout(function () {
  30. context.delayTimer = null;
  31. }, context.options.delay);
  32. }
  33. function onData(context, data) {
  34. context.state.size.transferred += data.length;
  35. !context.delayTimer && context.reportState();
  36. }
  37. function onEnd(context) {
  38. /* istanbul ignore if */
  39. if (context.delayTimer) {
  40. clearTimeout(context.delayTimer);
  41. context.delayTimer = null;
  42. }
  43. context.request.progressState = context.request.progressContext = null;
  44. }
  45. function reportState(context) {
  46. var state;
  47. // Do nothing if still within the initial delay or if already finished
  48. if (context.delayTimer || !context.request.progressState) {
  49. return;
  50. }
  51. state = context.state;
  52. state.time.elapsed = (Date.now() - context.startedAt) / 1000;
  53. // Calculate speed only if 1s has passed
  54. if (state.time.elapsed >= 1) {
  55. state.speed = state.size.transferred / state.time.elapsed;
  56. }
  57. // Calculate percent & remaining only if we know the total size
  58. if (state.size.total != null) {
  59. state.percent = Math.min(state.size.transferred, state.size.total) / state.size.total;
  60. if (state.speed != null) {
  61. state.time.remaining = state.percent !== 1 ? (state.size.total / state.speed) - state.time.elapsed : 0;
  62. state.time.remaining = Math.round(state.time.remaining * 1000) / 1000; // Round to 4 decimals
  63. }
  64. }
  65. context.request.emit('progress', state);
  66. }
  67. function requestProgress(request, options) {
  68. var context;
  69. if (request.progressContext) {
  70. return request;
  71. }
  72. if (request.response) {
  73. throw new Error('Already got response, it\'s too late to track progress');
  74. }
  75. // Parse options
  76. options = options || {};
  77. options.throttle = options.throttle == null ? 1000 : options.throttle;
  78. options.delay = options.delay || 0;
  79. options.lengthHeader = options.lengthHeader || 'content-length';
  80. // Create context
  81. context = {};
  82. context.request = request;
  83. context.options = options;
  84. context.reportState = throttle(reportState.bind(null, context), options.throttle);
  85. // context.startedAt = null;
  86. // context.state = null;
  87. // context.delayTimer = null;
  88. // Attach listeners
  89. request
  90. .on('request', onRequest.bind(null, context))
  91. .on('response', function handleResponse(response) {
  92. response.on('data', onData.bind(null, context));
  93. return onResponse(context, response);
  94. })
  95. .on('end', onEnd.bind(null, context));
  96. request.progressContext = context;
  97. return request;
  98. }
  99. module.exports = requestProgress;