@toolisticon/ssl-hostinfo-prometheus-exporter
Version:
[](LICENSE) [](https://github.com/toolisticon/ssl-hostinfo-prometheus-export
229 lines (180 loc) • 23.5 kB
JavaScript
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else {
var a = factory();
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(global, () => {
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./app.js":
/*!****************!*\
!*** ./app.js ***!
\****************/
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
"use strict";
eval("//#!/usr/bin/env node\n\n\nconst CronJob = (__webpack_require__(/*! cron */ \"cron\").CronJob);\nconst pkg = __webpack_require__(/*! ./package.json */ \"./package.json\");\nconst log = __webpack_require__(/*! ./lib/logger */ \"./lib/logger.js\");\nconst config = __webpack_require__(/*! ./lib/config */ \"./lib/config.js\");\nconst updateRoutesInfo = (__webpack_require__(/*! ./. */ \"./index.js\").updateRoutesInfo);\nconst startPrometheusListener = (__webpack_require__(/*! ./. */ \"./index.js\").startPrometheusListener);\nlog.info('ssl-hostinfo-prometheus-exporter info', {\n version: pkg.version\n});\nasync function triggerUpdate() {\n log.info('Start reading route information.');\n // start with provided url list\n updateRoutesInfo(config.urlsToCheck);\n}\n\n/* eslint no-new: \"off\" */\nnew CronJob(config.cron, () => {\n log.info('Triggering check');\n triggerUpdate();\n}, null, true, 'UTC');\n\n// trigger one update immediatly\ntriggerUpdate();\nstartPrometheusListener();\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./app.js?");
/***/ }),
/***/ "./index.js":
/*!******************!*\
!*** ./index.js ***!
\******************/
/***/ ((module, exports, __webpack_require__) => {
eval("const http = __webpack_require__(/*! http */ \"http\");\nconst config = __webpack_require__(/*! ./lib/config */ \"./lib/config.js\");\nconst log = __webpack_require__(/*! ./lib/logger */ \"./lib/logger.js\");\nconst mozilla = __webpack_require__(/*! ./lib/mozilla */ \"./lib/mozilla.js\");\nconst prometheus = __webpack_require__(/*! ./lib/prometheus */ \"./lib/prometheus.js\");\nconst url = __webpack_require__(/*! ./lib/url */ \"./lib/url.js\");\n\n/**\n * Checks a single url\n *\n * @param {String} hostname - hostname to check\n * @param {Number} port - port to use\n * @param {Object} additionalMetadata - additional key-value based metadata\n */\nasync function updateRouteInfo(hostname, port, additionalMetadata) {\n mozilla.triggerScan(hostname, port);\n // defer read results\n setTimeout(() => mozilla.receiveScanResult(hostname, additionalMetadata), 200);\n}\n/**\n * Checks a list of urls\n *\n * @param {Array} hostEntries - hostnames to check (may include ports ... e.g. example.com:8443)\n * @param {Object} additionalMetadata - additional key-value based metadata\n */\nasync function updateRoutesInfo(hostEntries, additionalMetadata) {\n log.info(`Triggering scan for ${hostEntries}`);\n // reset data on route update\n const domainList = [];\n url.extractHostnamesAndPort(hostEntries).forEach(hostEntry => {\n domainList.push(hostEntry.domain);\n });\n prometheus.updateHosts(domainList);\n url.extractHostnamesAndPort(hostEntries).forEach(hostEntry => {\n mozilla.triggerScan(hostEntry.domain, hostEntry.port);\n // defer read results\n setTimeout(() => mozilla.receiveScanResult(hostEntry.domain, additionalMetadata), 200);\n });\n}\n\n// start http server\nfunction startPrometheusListener() {\n const server = http.createServer((req, res) => {\n switch (req.url) {\n case '/':\n return res.end(prometheus.renderMetrics());\n default:\n return res.end('404');\n }\n });\n server.listen(config.port);\n log.info(`prometheus-exporter listening at ${config.port}`);\n}\nmodule.exports = exports = {\n config,\n updateRouteInfo,\n updateRoutesInfo,\n updateHosts: prometheus.updateHosts,\n resetRouteInfo: prometheus.reset,\n triggerScan: mozilla.triggerScan,\n startPrometheusListener,\n logger: log\n};\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./index.js?");
/***/ }),
/***/ "./lib/config.js":
/*!***********************!*\
!*** ./lib/config.js ***!
\***********************/
/***/ ((module, exports) => {
eval("let config = {};\nfunction init() {\n let urls = [];\n try {\n urls = process.env.URLS_TO_CHECK.split(',');\n } catch (ignored) {}\n config = {\n port: process.env.SERVER_PORT || 9000,\n cron: process.env.CRON || '0 0 * * * *',\n urlsToCheck: urls\n };\n}\ninit();\nmodule.exports = exports = config;\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./lib/config.js?");
/***/ }),
/***/ "./lib/logger.js":
/*!***********************!*\
!*** ./lib/logger.js ***!
\***********************/
/***/ ((module, exports, __webpack_require__) => {
eval("const Logger = __webpack_require__(/*! log4bro */ \"log4bro\");\nconst options = {\n productionMode: process.env.LOG_LEVEL !== 'INFO',\n // switches loglevel between DEBUG and WARN\n logDir: 'logs',\n // relative directory to write log file to\n silence: false,\n // silences logger\n loggerName: 'dev',\n // ignore\n dockerMode: process.env.CONSOLE_LOG || false,\n // disables output to logfile\n varKey: 'LOG' // name of global variable\n};\nconst LOG = new Logger(options);\nfunction logInfo(message, additionalFields = {}) {\n LOG.info(message, additionalFields);\n}\nfunction logError(message, additionalFields = {}) {\n LOG.error(message, additionalFields);\n}\nmodule.exports = exports = {\n info: logInfo,\n error: logError\n};\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./lib/logger.js?");
/***/ }),
/***/ "./lib/mozilla.js":
/*!************************!*\
!*** ./lib/mozilla.js ***!
\************************/
/***/ ((module, exports, __webpack_require__) => {
eval("const fetch = __webpack_require__(/*! node-fetch */ \"node-fetch\");\nconst log = __webpack_require__(/*! ./logger */ \"./lib/logger.js\");\nconst sslChecker = __webpack_require__(/*! ssl-checker */ \"ssl-checker\");\nconst prometheus = __webpack_require__(/*! ./prometheus */ \"./lib/prometheus.js\");\nconst baseUrl = 'https://http-observatory.security.mozilla.org/api/v1/analyze';\nasync function triggerScan(hostname, port) {\n log.info(`Triggering scan for ${hostname}`);\n return fetch(`${baseUrl}?host=${hostname}&rescan=true&hidden=true`, {\n method: 'POST',\n port\n });\n}\nasync function receiveScanResult(hostname, additionalMetadata = {}) {\n log.info(`Reading scan results for ${hostname}`);\n const response = await fetch(`${baseUrl}?host=${hostname}`, {\n method: 'GET',\n json: true\n });\n const json = await response.json();\n if (response && response.ok && json && json.score) {\n response.url = hostname;\n response.quantile = json.score;\n prometheus.addMozillaMetric(response, additionalMetadata);\n } else {\n log.info('Skipping invalid response for mozilla scoring');\n }\n // add cert metric\n sslChecker(hostname).then(result => {\n result.url = hostname;\n result.status = 200;\n result.quantile = result.status;\n prometheus.addDetailsMetric(result, additionalMetadata);\n prometheus.addExpireMetric({\n url: result.url,\n quantile: result.daysRemaining\n }, additionalMetadata);\n }).catch(err => {\n const result = {\n url: hostname,\n valid: false\n };\n if (err.code === 'ENOTFOUND') {\n result.status = 404;\n result.quantile = result.status;\n prometheus.addExpireMetric(result, additionalMetadata);\n } else {\n result.status = 400;\n result.quantile = result.status;\n prometheus.addExpireMetric(result, additionalMetadata);\n }\n });\n}\nmodule.exports = exports = {\n triggerScan,\n receiveScanResult\n};\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./lib/mozilla.js?");
/***/ }),
/***/ "./lib/prometheus.js":
/*!***************************!*\
!*** ./lib/prometheus.js ***!
\***************************/
/***/ ((module, exports, __webpack_require__) => {
eval("const isValid = __webpack_require__(/*! date-fns/isValid */ \"date-fns/isValid\");\nconst format = __webpack_require__(/*! date-fns/format */ \"date-fns/format\");\nconst parse = __webpack_require__(/*! date-fns/parse */ \"date-fns/parse\");\n\n// CONFIG\nconst mozillaMetricName = 'security_ssl_mozilla_observatory';\nconst sslDetailsMetricName = 'security_ssl_details';\nconst sslExpireMetricName = 'security_ssl_expire_days_remaining';\nlet resultStore = {};\nfunction init() {\n resultStore = {};\n}\nfunction convertKeyValuePair(key, value) {\n if (value instanceof Object) {\n let result = '';\n /* eslint array-callback-return: \"warn\" */\n Object.keys(value).map(subKey => {\n const subValue = value[subKey];\n try {\n result += `${key.toLowerCase()}_${convertKeyValuePair(subKey, JSON.parse(subValue))}`;\n } catch (ignored) {\n result += `${key.toLowerCase()}_${convertKeyValuePair(subKey, subValue)}`;\n }\n });\n return result;\n } else {\n let validDate = false;\n let date = parse(value, 'EEE, dd MMM yyyy HH:mm:ss \\'GMT\\'', new Date());\n if (isValid(date)) {\n validDate = true;\n } else {\n date = parse(value, \"yyyy-MM-dd'T'HH:mm:ss.SSSxxx\", new Date());\n if (isValid(date)) {\n validDate = true;\n }\n }\n const labelKey = key.toLowerCase().split('-').join('_').split('.').join('_');\n if (validDate) {\n return `${labelKey}=\"${format(date, \"yyyy-MM-dd'T'HH:mm:ss.SSSxxx\")}\",`;\n } else {\n return `${labelKey}=\"${value}\",`;\n }\n }\n}\nfunction json2prom(metricName, jsonObject) {\n if (jsonObject) {\n // TODO parse response headers\n delete jsonObject.response_headers;\n // TODO parse annotations\n delete jsonObject.annotations;\n // Drop private data\n delete jsonObject.resourceVersion;\n delete jsonObject.creationTimestamp;\n delete jsonObject.selflink;\n // build metris object entry\n let promObject = `${metricName}{`;\n Object.keys(jsonObject).map(key => {\n const value = jsonObject[key];\n promObject += convertKeyValuePair(key, value);\n });\n promObject += '}';\n if (jsonObject.quantile || jsonObject.quantile === 0) {\n promObject += ` ${jsonObject.quantile}.0`;\n } else {\n promObject += ' NaN';\n }\n return promObject;\n } else {\n return `${metricName} NaN`;\n }\n}\n\n/**\n * Add a valid http observatory data as metrics, e.g:\n *\n * {\n \"algorithm_version\":2,\n \"end_time\":\"Fri, 18 Jan 2019 09:46:07 GMT\",\n \"grade\":\"D\",\n \"hidden\":false,\n \"likelihood_indicator\":\"MEDIUM\",\n \"response_headers\":{\n \"Cache-Control\":\"no-cache, no-store, max-age=0, must-revalidate\",\n \"Content-Type\":\"application/json;charset=UTF-8\",\n \"Date\":\"Fri, 18 Jan 2019 09:46:05 GMT\",\n \"Expires\":\"0\",\n \"Pragma\":\"no-cache\",\n \"Set-Cookie\":\"556448b8f044ea9c0fe56ec8eabb3577=6dda08a289298b570c8daa5a12e94408; path=/; HttpOnly; Secure\",\n \"Transfer-Encoding\":\"chunked\",\n \"X-Application-Context\":\"application:prod:8087\",\n \"X-Content-Type-Options\":\"nosniff\",\n \"X-XSS-Protection\":\"1; mode=block\"\n },\n \"scan_id\":9783173,\n \"score\":35,\n \"start_time\":\"Fri, 18 Jan 2019 09:46:02 GMT\",\n \"state\":\"FINISHED\",\n \"status_code\":404,\n \"tests_failed\":3,\n \"tests_passed\":9,\n \"tests_quantity\":12,\n \"url\":\"sub.domain-sample.com\"\n}\n *\n *\n * @param {Object} dataAsJson\n * @param {Object} additionalMetadata - additional key-value based metadata\n */\nfunction addMozillaMetric(dataAsJson, additionalMetadata) {\n const data = Object.assign({}, dataAsJson, additionalMetadata);\n if (resultStore[dataAsJson.url]) {\n resultStore[dataAsJson.url].moz = json2prom(mozillaMetricName, data);\n } else {\n resultStore[dataAsJson.url] = {\n moz: json2prom(mozillaMetricName, data)\n };\n }\n}\n/**\n *\n{\n \"status\": 200|400|404,\n \"valid\": true,\n \"days_remaining\" : 90,\n \"valid_from\" : \"issue date\",\n \"valid_to\" : \"expiry date\"\n}\n * @param {Object} dataAsJson - from ssl-checker\n * @param {Object} additionalMetadata - additional key-value based metadata\n */\nfunction addDetailsMetric(dataAsJson, additionalMetadata) {\n const data = Object.assign({}, dataAsJson.valid ? {\n days_remaining: dataAsJson.daysRemaining,\n valid_from: dataAsJson.validFrom,\n valid_to: dataAsJson.validTo,\n valid: dataAsJson.valid\n } : dataAsJson, additionalMetadata);\n if (resultStore[dataAsJson.url]) {\n resultStore[dataAsJson.url].details = json2prom(sslDetailsMetricName, data);\n } else {\n resultStore[dataAsJson.url] = {\n details: json2prom(sslDetailsMetricName, data)\n };\n }\n}\n/**\n *\n{\n \"status\": 200|400|404,\n \"valid\": true,\n \"days_remaining\" : 90,\n \"valid_from\" : \"issue date\",\n \"valid_to\" : \"expiry date\"\n}\n * @param {Object} dataAsJson\n * @param {Object} additionalMetadata - additional key-value based metadata\n */\nfunction addExpireMetric(dataAsJson, additionalMetadata) {\n const data = Object.assign({}, dataAsJson, additionalMetadata);\n if (resultStore[dataAsJson.url]) {\n resultStore[dataAsJson.url].expire = json2prom(sslExpireMetricName, data);\n } else {\n resultStore[dataAsJson.url] = {\n expire: json2prom(sslExpireMetricName, data)\n };\n }\n}\n\n/**\n *\n */\nfunction updateHosts(updatedHosts) {\n // loop and update results with hosts\n Object.keys(resultStore).map(host => {\n let found = false;\n updatedHosts.forEach(knownHost => {\n if (host === knownHost) {\n found = true;\n }\n });\n if (!found) {\n delete resultStore[host];\n }\n });\n}\n\n/**\n * Check for unallowed raw data contains, e.g. undefined data and normalize it\n *\n * @param {*} rawData\n */\nfunction normalizePrometheusDate(rawData) {\n if (rawData) {\n return `${rawData}\\n`;\n } else {\n return '';\n }\n}\nfunction renderMetrics() {\n let response = '';\n response += `# HELP ${mozillaMetricName} Mozilla Observatory SSL stats\\n`;\n Object.keys(resultStore).map(host => {\n response += `${normalizePrometheusDate(resultStore[host].moz)}`;\n });\n response += `# HELP ${sslDetailsMetricName} Generic SSL Details\\n`;\n Object.keys(resultStore).map(host => {\n response += `${normalizePrometheusDate(resultStore[host].details)}`;\n });\n response += `# HELP ${sslExpireMetricName} Remaining Days before certificate expiry\\n`;\n Object.keys(resultStore).map(host => {\n response += `${normalizePrometheusDate(resultStore[host].expire)}`;\n });\n return response;\n}\ninit();\nmodule.exports = exports = {\n init,\n reset: init,\n updateHosts,\n addDetailsMetric,\n addExpireMetric,\n addMozillaMetric,\n renderMetrics\n};\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./lib/prometheus.js?");
/***/ }),
/***/ "./lib/url.js":
/*!********************!*\
!*** ./lib/url.js ***!
\********************/
/***/ ((module, exports) => {
eval("function extractHostnameAndPort(url) {\n const urlParts = url.split(':');\n if (urlParts.length > 1) {\n return {\n domain: urlParts[0],\n port: urlParts[1]\n };\n } else {\n return {\n domain: url,\n port: '443'\n };\n }\n}\nfunction extractHostnamesAndPort(urlList) {\n const result = [];\n if (urlList) {\n urlList.forEach(url => {\n result.push(extractHostnameAndPort(url));\n });\n }\n return result;\n}\nmodule.exports = exports = {\n extractHostnameAndPort,\n extractHostnamesAndPort\n};\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./lib/url.js?");
/***/ }),
/***/ "cron":
/*!***********************!*\
!*** external "cron" ***!
\***********************/
/***/ ((module) => {
"use strict";
module.exports = require("cron");
/***/ }),
/***/ "date-fns/format":
/*!**********************************!*\
!*** external "date-fns/format" ***!
\**********************************/
/***/ ((module) => {
"use strict";
module.exports = require("date-fns/format");
/***/ }),
/***/ "date-fns/isValid":
/*!***********************************!*\
!*** external "date-fns/isValid" ***!
\***********************************/
/***/ ((module) => {
"use strict";
module.exports = require("date-fns/isValid");
/***/ }),
/***/ "date-fns/parse":
/*!*********************************!*\
!*** external "date-fns/parse" ***!
\*********************************/
/***/ ((module) => {
"use strict";
module.exports = require("date-fns/parse");
/***/ }),
/***/ "log4bro":
/*!**************************!*\
!*** external "log4bro" ***!
\**************************/
/***/ ((module) => {
"use strict";
module.exports = require("log4bro");
/***/ }),
/***/ "node-fetch":
/*!*****************************!*\
!*** external "node-fetch" ***!
\*****************************/
/***/ ((module) => {
"use strict";
module.exports = require("node-fetch");
/***/ }),
/***/ "ssl-checker":
/*!******************************!*\
!*** external "ssl-checker" ***!
\******************************/
/***/ ((module) => {
"use strict";
module.exports = require("ssl-checker");
/***/ }),
/***/ "http":
/*!***********************!*\
!*** external "http" ***!
\***********************/
/***/ ((module) => {
"use strict";
module.exports = require("http");
/***/ }),
/***/ "./package.json":
/*!**********************!*\
!*** ./package.json ***!
\**********************/
/***/ ((module) => {
"use strict";
eval("module.exports = /*#__PURE__*/JSON.parse('{\"name\":\"@toolisticon/ssl-hostinfo-prometheus-exporter\",\"version\":\"2.1.13\",\"description\":\"\",\"main\":\"index.js\",\"bin\":{\"ssl-hostinfo-prom\":\"app.js\"},\"scripts\":{\"clean\":\"npm i\",\"build\":\"webpack --config webpack.config.js --progress\",\"debug\":\"node --inspect-brk index.js\",\"start\":\"node index.js\",\"watch\":\"nodemon node index.js\",\"lint\":\"eslint --fix --ignore-pattern=dist .\",\"pretest\":\"npm run lint\",\"test\":\"npm run jasmine-test && npm run end2end-test\",\"jasmine-test\":\"jasmine JASMINE_CONFIG_PATH=test/jasmine.json\",\"jasmine-test:watch\":\"nodemon jasmine JASMINE_CONFIG_PATH=test/jasmine.json\",\"jasmine-test:debug\":\"node --inspect-brk -i node_modules/jasmine/bin/jasmine.js spec/prometheus.spec.js\",\"preend2end-test\":\"docker build -t toolisticon/ssl-hostinfo-prometheus-exporter . && cd test/setup && docker-compose up -d --force-recreate && sleep 30\",\"end2end-test\":\"jasmine JASMINE_CONFIG_PATH=test/jasmine-e2e.json\",\"postend2end-test\":\"cd test/setup && docker-compose stop && docker-compose rm -f -s -v && docker volume prune -f\",\"changelog\":\"conventional-changelog -p angular -i CHANGELOG.md -s -r 0\",\"changelog:add\":\"git add CHANGELOG.md && git commit -m \\'updated CHANGELOG.md\\'\",\"update-env\":\"echo \\\\\"VERSION=$(node -p \\\\\"require(\\'./package.json\\').version\\\\\")\\\\\" > .env\",\"update-env:add\":\"git add .env && git commit -m \\'updated .env\\'\",\"release\":\"npm run test\",\"release:major\":\"npm run release && npm version major && npm run version-and-push\",\"release:minor\":\"npm run release && npm version minor && npm run version-and-push\",\"release:patch\":\"npm run release && npm version patch && npm run version-and-push\",\"version-and-push\":\"npm run changelog && npm run changelog:add && npm run update-env && npm run update-env:add && git push origin && git push origin --tags && git checkout master && git merge develop && git push && git checkout develop\"},\"repository\":{\"type\":\"git\",\"url\":\"git+https://github.com/toolisticon/ssl-hostinfo-prometheus-exporter.git\"},\"apps\":[{\"merge_logs\":true,\"max_memory_restart\":\"200M\",\"script\":\"dist/app.js\"}],\"keywords\":[\"ssl\",\"prometheus\",\"metrics\",\"pmx\"],\"author\":\"Martin Reinhardt\",\"contributor\":[],\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/toolisticon/ssl-hostinfo-prometheus-exporter/issues\"},\"homepage\":\"https://github.com/toolisticon/ssl-hostinfo-prometheus-exporter\",\"dependencies\":{\"cron\":\"^3.0.0\",\"date-fns\":\"^2.29.3\",\"log4bro\":\"^3.18.0\",\"node-fetch\":\"^2.6.9\",\"ssl-checker\":\"^2.0.7\"},\"devDependencies\":{\"@babel/cli\":\"^7.4.3\",\"@babel/core\":\"^7.4.3\",\"@babel/preset-env\":\"^7.4.3\",\"@babel/preset-react\":\"^7.0.0\",\"axios\":\"^1.1.3\",\"babel-loader\":\"^9.0.1\",\"babel-plugin-transform-class-properties\":\"^6.24.1\",\"clean-webpack-plugin\":\"^4.0.0\",\"conventional-changelog-cli\":\"^5.0.0\",\"eslint\":\"^8.1.0\",\"eslint-config-semistandard\":\"^17.0.0\",\"eslint-config-standard\":\"^17.0.0\",\"eslint-plugin-import\":\"^2.25.2\",\"eslint-plugin-jasmine\":\"^4.1.2\",\"eslint-plugin-node\":\"^11.0.0\",\"eslint-plugin-promise\":\"^6.0.0\",\"eslint-plugin-react\":\"^7.26.1\",\"jasmine\":\"^5.0.0\",\"nodemon\":\"^3.0.0\",\"webpack\":\"^5.60.0\",\"webpack-bundle-analyzer\":\"^4.5.0\",\"webpack-cli\":\"^5.0.0\",\"webpack-node-externals\":\"^3.0.0\"}}');\n\n//# sourceURL=webpack://@toolisticon/ssl-hostinfo-prometheus-exporter/./package.json?");
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module can't be inlined because the eval devtool is used.
/******/ var __webpack_exports__ = __webpack_require__("./app.js");
/******/ __webpack_exports__ = __webpack_exports__["default"];
/******/
/******/ return __webpack_exports__;
/******/ })()
;
});