diff --git a/.babelrc b/.babelrc index e4b307e90b..49378940ef 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "presets": [ "es2015", "react", "stage-0" ], "plugins": [ + "add-react-displayname", "transform-runtime", "babel-plugin-add-module-exports", "lodash", diff --git a/client/index.js b/client/index.js index a6939e186b..969dab1366 100644 --- a/client/index.js +++ b/client/index.js @@ -1,3 +1,5 @@ +import initOpbeat from 'opbeat-react'; +import { createOpbeatMiddleware } from 'opbeat-react/redux'; import Rx from 'rx'; import debug from 'debug'; import { render } from 'redux-epic'; @@ -17,6 +19,24 @@ import { saveToColdStorage } from './cold-reload'; +const { + __OPBEAT__ORG_ID, + __OPBEAT__APP_ID, + NODE_ENV +} = process.env; + +const enableOpbeat = NODE_ENV !== 'development'; + +if (enableOpbeat) { + if (!__OPBEAT__ORG_ID || !__OPBEAT__APP_ID) { + console.error('OpBeat credentials not found in .env'); + } + initOpbeat({ + orgId: __OPBEAT__ORG_ID, + appId: __OPBEAT__APP_ID + }); +} + const isDev = Rx.config.longStackSupport = debug.enabled('fcc:*'); const log = debug('fcc:client'); const hotReloadTimeout = 2000; @@ -41,6 +61,7 @@ const epicOptions = { history: _history }; + const DOMContainer = document.getElementById('fcc'); const defaultState = isColdStored() ? getColdStorage() : @@ -64,7 +85,8 @@ createApp({ defaultState, epics, epicOptions, - enhancers: isDev && devToolsExtension && [ devToolsExtension() ] + enhancers: isDev && devToolsExtension && [ devToolsExtension() ], + middlewares: enableOpbeat && [ createOpbeatMiddleware() ] }) .doOnNext(({ store }) => { if (module.hot && typeof module.hot.accept === 'function') { diff --git a/gulpfile.js b/gulpfile.js index 51997e317d..2f91190f20 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -107,6 +107,7 @@ const paths = { ], vendorMain: [ + resolve('opbeat-react'), resolve('jquery', '.js', '.min.js'), resolve('bootstrap', 'npm.js', 'bootstrap.min.js'), resolve('d3', '.js', '.min.js'), diff --git a/package-lock.json b/package-lock.json index beb66c890d..58c9f2d347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1119,6 +1119,11 @@ "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", "dev": true }, + "babel-plugin-add-react-displayname": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", + "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=" + }, "babel-plugin-check-es2015-constants": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", @@ -4765,6 +4770,14 @@ "is-arrayish": "0.2.1" } }, + "error-stack-parser": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-1.3.6.tgz", + "integrity": "sha1-4Oc7k+QXE40c18C3RrGkoUhUwpI=", + "requires": { + "stackframe": "0.3.1" + } + }, "es-abstract": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", @@ -10589,6 +10602,11 @@ } } }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" + }, "loglevelnext": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.2.tgz", @@ -12896,6 +12914,27 @@ "fast-safe-stringify": "1.2.0" } }, + "opbeat-js-core": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/opbeat-js-core/-/opbeat-js-core-0.3.2.tgz", + "integrity": "sha1-/hIMzCF0j4gz2lMLZAspvOX98iU=", + "requires": { + "error-stack-parser": "1.3.6", + "loglevel": "1.6.1", + "stack-generator": "1.1.0", + "url-parse": "1.2.0" + } + }, + "opbeat-react": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/opbeat-react/-/opbeat-react-3.4.0.tgz", + "integrity": "sha1-ivOLnjIq3JBQltk7aCJ8hBdddf0=", + "requires": { + "loglevel": "1.6.1", + "opbeat-js-core": "0.3.2", + "opbeat-zone": "0.8.12-prm" + } + }, "opbeat-release-tracker": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/opbeat-release-tracker/-/opbeat-release-tracker-1.1.1.tgz", @@ -12905,6 +12944,11 @@ "opbeat-http-client": "1.2.2" } }, + "opbeat-zone": { + "version": "0.8.12-prm", + "resolved": "https://registry.npmjs.org/opbeat-zone/-/opbeat-zone-0.8.12-prm.tgz", + "integrity": "sha1-76aLeSlygV+mghn5sKmS6qWb5hY=" + }, "open": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", @@ -13776,6 +13820,11 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "querystringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", + "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=" + }, "raf": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", @@ -14655,8 +14704,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "reselect": { "version": "3.0.1", @@ -16156,6 +16204,26 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz", "integrity": "sha1-kQ9dKu17Ugxud3SZwfMuE5/eyxA=" }, + "stack-generator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-1.1.0.tgz", + "integrity": "sha1-NvapIHUabBD0maE8Msu19RoLiyU=", + "requires": { + "stackframe": "1.0.4" + }, + "dependencies": { + "stackframe": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz", + "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==" + } + } + }, + "stackframe": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz", + "integrity": "sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ=" + }, "stackman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stackman/-/stackman-2.0.1.tgz", @@ -17798,6 +17866,15 @@ "integrity": "sha1-wHJ1aWetJLi1nldBVRyqx49QuLc=", "dev": true }, + "url-parse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", + "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", + "requires": { + "querystringify": "1.0.0", + "requires-port": "1.0.0" + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", diff --git a/package.json b/package.json index 52781b29bd..fdccfc1124 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "accepts": "^1.3.0", "babel-core": "^6.18.1", + "babel-plugin-add-react-displayname": "0.0.5", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-register": "^6.3.0", @@ -97,6 +98,7 @@ "normalize-url": "^1.3.1", "normalizr": "2.2.1", "opbeat": "^4.14.0", + "opbeat-react": "^3.4.0", "passport": "^0.4.0", "passport-facebook": "^2.0.0", "passport-github": "^1.0.0", diff --git a/sample.env b/sample.env index 3486cd5d14..2d4f82a7ad 100644 --- a/sample.env +++ b/sample.env @@ -1,5 +1,8 @@ MONGOHQ_URL='mongodb://localhost:27017/freecodecamp' +OPBEAT_FRONTEND_ORG_ID=stuff +OPBEAT_FRONTEND_APP_ID=stuff + FACEBOOK_ID=stuff FACEBOOK_SECRET=stuff diff --git a/server/middlewares/csp.js b/server/middlewares/csp.js index 0464b336e1..868a0c6bc3 100644 --- a/server/middlewares/csp.js +++ b/server/middlewares/csp.js @@ -2,7 +2,8 @@ import helmet from 'helmet'; let trusted = [ "'self'", - 'https://search.freecodecamp.org' + 'https://search.freecodecamp.org', + 'https://*.opbeat.com' ]; const host = process.env.HOST || 'localhost'; diff --git a/webpack.config.js b/webpack.config.js index af0e03f015..fe134849ba 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -53,7 +53,9 @@ module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env': { - NODE_ENV: JSON.stringify(__DEV__ ? 'development' : 'production') + NODE_ENV: JSON.stringify(__DEV__ ? 'development' : 'production'), + __OPBEAT__ORG_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_ORG_ID), + __OPBEAT__APP_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_APP_ID) }, __DEVTOOLS__: !__DEV__ }),