<repo_name>Oblium/performance-timings<file_sep>/lib/getTimeFor.js
import getPerformanceTimeBetween from './common/getPerformanceTimeBetween';

export default (event, cb) => getPerformanceTimeBetween('fetchStart', event, cb)<file_sep>/test/lib/getNetworkLatency.js
import getNetworkLatency from '../../lib/getNetworkLatency';

describe('The getNetworkLatency method', () => {
  it('should return correctly the network latency', (done) => {
    getNetworkLatency((t) => {
      expect(t).toBe(window.performance.timing.responseEnd - window.performance.timing.fetchStart);
      done();
    });
  });
});<file_sep>/lib/getTotalTime.js
import getPerformanceTimeBetween from './common/getPerformanceTimeBetween';

export default (cb) => getPerformanceTimeBetween('navigationStart', 'loadEventEnd', cb)<file_sep>/public/performances.js
(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define("performances", [], factory);
	else if(typeof exports === 'object')
		exports["performances"] = factory();
	else
		root["performances"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";


Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var NOT_AVAILABLE_VALUE = -1;
var DEFAULT_POLLING_MS = 100;

var Timings = function () {
  function Timings(performance) {
    _classCallCheck(this, Timings);

    this._performaceObject = performance || window.performance;
    this._pollingMs = DEFAULT_POLLING_MS;
  }

  _createClass(Timings, [{
    key: 'isAvailable',
    value: function isAvailable() {
      return typeof this._performaceObject !== 'undefined';
    }
  }, {
    key: '_onLoad',
    value: function _onLoad(cb) {
      var _this = this;

      if (this._performaceObject.timing.loadEventEnd) {
        cb();
      } else {
        setTimeout(function () {
          return _this._onLoad(cb);
        }, this._pollingMs);
      }
    }
  }, {
    key: 'setPollingMs',
    value: function setPollingMs(ms) {
      this._pollingMs = ms;
    }
  }, {
    key: 'getPageLoad',
    value: function getPageLoad(cb) {
      var _this2 = this;

      return this._checkAvailability(function () {
        return cb(_this2._diffBetween('loadEventEnd', 'responseEnd'));
      });
    }
  }, {
    key: 'getNetworkLatency',
    value: function getNetworkLatency(cb) {
      var _this3 = this;

      return this._checkAvailability(function () {
        return cb(_this3._diffBetween('responseEnd', 'fetchStart'));
      });
    }
  }, {
    key: 'getTotalTime',
    value: function getTotalTime(cb) {
      var _this4 = this;

      return this._checkAvailability(function () {
        return cb(_this4._diffBetween('loadEventEnd', 'navigationStart'));
      });
    }
  }, {
    key: 'asJson',
    value: function asJson(cb) {
      var _this5 = this;

      return this._checkAvailability(function () {
        return cb({
          networkLatency: _this5._diffBetween('responseEnd', 'fetchStart'),
          pageLoad: _this5._diffBetween('loadEventEnd', 'responseEnd'),
          totalTime: _this5._diffBetween('loadEventEnd', 'navigationStart')
        });
      }, 0);
    }
  }, {
    key: '_diffBetween',
    value: function _diffBetween(a, b) {
      return this._performaceObject.timing[a] - this._performaceObject.timing[b];
    }
  }, {
    key: '_checkAvailability',
    value: function _checkAvailability(cb) {
      var def = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : NOT_AVAILABLE_VALUE;

      return this.isAvailable() ? this._onLoad(cb) : cb(def);
    }
  }]);

  return Timings;
}();

exports.default = Timings;

/***/ })
/******/ ]);
});<file_sep>/src/index.js
const NOT_AVAILABLE_VALUE = -1;
const DEFAULT_POLLING_MS = 100;

export default class Timings {
  constructor(performance) {
    this._performaceObject = performance || window.performance;
    this._pollingMs = DEFAULT_POLLING_MS;
  }

  isAvailable() {
    return typeof this._performaceObject !== 'undefined';
  }

  _onLoad(cb) {
    if (this._performaceObject.timing.loadEventEnd) {
      cb();
    } else {
      setTimeout(() => this._onLoad(cb), this._pollingMs);
    }
  }

  setPollingMs(ms) {
    this._pollingMs = ms;
  }

  getPageLoad(cb) {
    return this._checkAvailability(() => cb(this._diffBetween('loadEventEnd', 'responseEnd')));
  }

  getNetworkLatency(cb) {
    return this._checkAvailability(() => cb(this._diffBetween('responseEnd', 'fetchStart')));
  }

  getTotalTime(cb) {
    return this._checkAvailability(() => cb(this._diffBetween('loadEventEnd', 'navigationStart')));
  }

  getTimeFor(event, cb) {
    return this._checkAvailability(() => cb(this._diffBetween(event, 'fetchStart')));
  }

  getFrontendTimeFor(event, cb) {
    return this._checkAvailability(() => cb(this._diffBetween(event, 'navigationStart')));
  }

  asJson(cb) {
    return this._checkAvailability(() => cb({
      networkLatency: this._diffBetween('responseEnd', 'fetchStart'),
      pageLoad: this._diffBetween('loadEventEnd', 'responseEnd'),
      totalTime: this._diffBetween('loadEventEnd', 'navigationStart')
    }), 0);
  }

  _diffBetween(a, b) {
    return this._performaceObject.timing[a] - this._performaceObject.timing[b];
  }

  _checkAvailability(cb, def = NOT_AVAILABLE_VALUE) {
    return this.isAvailable() ? this._onLoad(cb) : cb(def);
  }
}

export const EVENTS = {

};<file_sep>/karma.conf.js
const path = require('path');
const webpackConfig = require('./webpack.config');
webpackConfig.entry = {};
webpackConfig.isparta = {
  embedSource: true,
  noAutoWrap: true,
  babel: {
    presets: ['es2015']
  }
};
webpackConfig.module.preLoaders = [{
  test: /\.js$/,
  include: [path.resolve('./src'), path.resolve('./lib')],
  loader: 'isparta'
}];

module.exports = function(config) {
  config.set({
    frameworks: ['jasmine'],
    reporters: ['spec', 'coverage-istanbul'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['ChromeCanaryHeadless'],
    singleRun: true,
    autoWatchBatchDelay: 300,


    files: [
      './test/**/*.js'
    ],

    preprocessors: {
      './test/**/*.js': ['babel', 'webpack']
    },

    webpack: {
      module: {
        rules: [
          {
            test: /\.js$/,
            use: [{loader: 'babel-loader'}]
          },
          {
            test: /\.js$/,
            include: [path.resolve('src'), path.resolve('lib')],
            loader: 'istanbul-instrumenter-loader',
            options: {
              esModules: true
            }
          }
        ]
      }
    },

    webpackMiddleware: {
      noInfo: true
    },
    coverageIstanbulReporter: {
      reports: ['text-summary', 'html', 'lcov'],
      fixWebpackSourcePaths: true,
      dir: 'target/'
    },
    thresholdReporter: {
      statements: 80,
      branches: 75,
      functions: 75,
      lines: 80
    },
    specReporter: {
      suppressErrorSummary: false,
      suppressFailed: false,
      suppressPassed: false,
      suppressSkipped: true,
      showSpecTiming: true,
      failFast: false
    }
  });
};<file_sep>/test/lib/getFrontendTimeFor.js
import getFrontendTimeFor from '../../lib/getFrontendTimeFor';

describe('The getFrontendTimeFor method', () => {
  it('should return correctly the event timing', (done) => {
    getFrontendTimeFor('responseEnd', (t) => {
      expect(t).toBe(window.performance.timing.responseEnd - window.performance.timing.navigationStart);
      done();
    });
  });
});<file_sep>/test/librarySpec.js
import lib from '../src/index';

describe('The library', () => {
  let libInstance;
  const fakePerformanceObject = {timing: {}};
  const now = +new Date();
  const fakeTiming = {
    navigationStart: now,
    fetchStart: now + 2,
    responseEnd: now + 10,
    loadEventEnd: now + 200,
    customEvent: now + 300
  };

  beforeEach(() => {
    jasmine.clock().install();
    resetPerformanceObject();
    libInstance = new lib(fakePerformanceObject);
  });

  afterEach(() => {
    jasmine.clock().uninstall();
  });

  const setPerformanceObject = () => {
    fakePerformanceObject.timing.navigationStart = fakeTiming.navigationStart;
    fakePerformanceObject.timing.fetchStart = fakeTiming.fetchStart;
    fakePerformanceObject.timing.responseEnd = fakeTiming.responseEnd;
    fakePerformanceObject.timing.loadEventEnd = fakeTiming.loadEventEnd;
    fakePerformanceObject.timing.customEvent = fakeTiming.customEvent;
  };
  const resetPerformanceObject = () => {
    fakePerformanceObject.timing.navigationStart = 0;
    fakePerformanceObject.timing.fetchStart = 0;
    fakePerformanceObject.timing.responseEnd = 0;
    fakePerformanceObject.timing.loadEventEnd = 0;
    fakePerformanceObject.timing.customEvent = 0;
  };

  it('should exist', () => {
    expect(lib).not.toBeUndefined();
  });

  it('should instantiate correctly', () => {
    expect(libInstance).not.toBeUndefined();
  });

  it('should not respond if it\'s not ready', () => {
    const spy = jasmine.createSpy('cb');
    libInstance.getPageLoad(spy);
    expect(spy).not.toHaveBeenCalled();
  });

  it('should call after it\'s ready', () => {
    const spy = jasmine.createSpy('cb');
    libInstance.getPageLoad(spy);
    expect(spy).not.toHaveBeenCalled();
    setPerformanceObject();

    jasmine.clock().tick(101);
    expect(spy).toHaveBeenCalled();
  });

  it('should call only after some time, decided by setPolling', () => {
    const spy = jasmine.createSpy('cb');
    libInstance.setPollingMs(1000);
    libInstance.getPageLoad(spy);
    setPerformanceObject();

    jasmine.clock().tick(500);
    expect(spy).not.toHaveBeenCalled();

    jasmine.clock().tick(901);
    expect(spy).toHaveBeenCalled();
  });

  describe('with a stubbed timing', () => {
    it('should return the correct net latency', () => {
      const spy = jasmine.createSpy('cb');
      setPerformanceObject();
      libInstance.getNetworkLatency(spy);

      expect(spy).toHaveBeenCalledWith(fakeTiming.responseEnd - fakeTiming.fetchStart);
    });

    it('should return the correct page load time', () => {
      const spy = jasmine.createSpy('cb');
      setPerformanceObject();
      libInstance.getPageLoad(spy);

      expect(spy).toHaveBeenCalledWith(fakeTiming.loadEventEnd - fakeTiming.responseEnd);
    });

    it('should return the correct total time', () => {
      const spy = jasmine.createSpy('cb');
      setPerformanceObject();
      libInstance.getTotalTime(spy);

      expect(spy).toHaveBeenCalledWith(fakeTiming.loadEventEnd - fakeTiming.navigationStart);
    });

    it('should return the correct time with custom event', () => {
      const spy = jasmine.createSpy('cb');
      setPerformanceObject();
      libInstance.getTimeFor('customEvent', spy);

      expect(spy).toHaveBeenCalledWith(fakeTiming.customEvent - fakeTiming.fetchStart);
    });

    it('should return the correct time with custom event', () => {
      const spy = jasmine.createSpy('cb');
      setPerformanceObject();
      libInstance.getFrontendTimeFor('customEvent', spy);

      expect(spy).toHaveBeenCalledWith(fakeTiming.customEvent - fakeTiming.navigationStart);
    });

    it('should return the correct json with timings', () => {
      const spy = jasmine.createSpy('cb');
      setPerformanceObject();
      libInstance.asJson(spy);

      expect(spy).toHaveBeenCalledWith({
        networkLatency: fakeTiming.responseEnd - fakeTiming.fetchStart,
        pageLoad: fakeTiming.loadEventEnd - fakeTiming.responseEnd,
        totalTime: fakeTiming.loadEventEnd - fakeTiming.navigationStart
      });
    })
  });

});<file_sep>/README.md
# PERFORMANCES

## Description
A simple wrapper for [Performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance/timing)

## Usage
Basic usage is:

1. Import the library from 'performances'
  - `import Performances from 'performances'`;
2. Instantiate the Performances object providing the window.performance object (optional) and invoke the method you want to use on it
  - `var perf = new Performances(window.performance);`
  - `perf.getTotalTime(time => /* do stuffs with time */)`;
3. You can use also the asJson method to get all info together:
  - `perf.asJson(timeData => /*do stuffs with timeData*/)`
  - The method will provide an object with networkLatency, pageLoad and totalTime keys

Additionaly you can import single methods to have a lower impact on your bundle size:

1. Import single methods you want to use
  - `import getTotalTime from 'performances/lib/getTotalTime';`
2. Call the method providing the callback to call on retrieve
  - `getTotalTime(time => /*do stuffs with time*/);`

Note: using the single methods implies a compilation of the imported code. 

## Available methods
- getPageLoad
- getNetworkLatency
- getTotalTime<file_sep>/lib/common/onPerformanceLoad.js
export default function onPerformanceLoad(cb) {
  if (window.performance.timing.loadEventEnd === 0) {
    return setTimeout(() => onPerformanceLoad(cb), 100);
  }
  return cb();
}<file_sep>/public/demo.js
var timings = new lmnUiPerformances.default(window.performance);
timings.getNetworkLatency(function(t) {
  document.getElementById('target').innerHTML += "netLatency: " + t + "<br />";
});
timings.getPageLoad(function(t) {
  document.getElementById('target').innerHTML += "loadTime: " + t + "<br />";
});
timings.getTotalTime(function(t) {
  document.getElementById('target').innerHTML += "totalTime: " + t + "<br />";
});
timings.asJson(function(t) {
  document.getElementById('target').innerHTML += "asJson: " + JSON.stringify(t) + "<br />";
});<file_sep>/test/lib/unavailablePerformance.js
import getTotalTime from '../../lib/getTotalTime';

describe('the library, when performance is not available', () => {
  it('should send back -1', (d) => {
    window.performance = undefined;
    getTotalTime(t => {
      expect(t).toBe(-1);
      d();
    })
  })
});<file_sep>/lib/getNetworkLatency.js
import getPerformanceTimeBetween from './common/getPerformanceTimeBetween';

export default (cb) => getPerformanceTimeBetween('fetchStart', 'responseEnd', cb)<file_sep>/webpack.config.js
const project = require('./package.json');
const webpack = require('webpack');
const path = require('path');
const params = require('minimist')(process.argv.slice(2));


module.exports = {
  entry: {
    app: './src/index.js'
  },
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: project.name + '.js',
    library: "performances",
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
  module: {
    rules: [
      {
        test: /(\.js)$/,
        use: 'babel-loader',
        exclude: /(node_modules)/
      }
    ]
  },
  devServer: {
    hot: true,
    port: 3333,
    contentBase: './public/'
  },
  plugins: params.production ? [new webpack.DefinePlugin({
    'process.env': {
      'NODE_ENV': JSON.stringify('production')
    }
  }), new webpack.optimize.UglifyJsPlugin({
    compress: {warnings: false},
    sourceMap: true
  }), new webpack.optimize.AggressiveMergingPlugin()] : []
};