@boomerang-io/webapp-spa-server 0.0.11-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [1.7.5](https://github.ibm.com/Boomerang-Lib/boomerang.carbon.utilities/compare/@boomerang.carbon.utilities/boomerang-webapp-server@1.7.4...@boomerang.carbon.utilities/boomerang-webapp-server@1.7.5) (2020-05-26)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * config ([c8c57f8](https://github.ibm.com/Boomerang-Lib/boomerang.carbon.utilities/commit/c8c57f81ab3d70ce5d456d53b70c889cfcac8969))
12
+
13
+
14
+
15
+
16
+
17
+ ## [1.7.4](https://github.ibm.com/Boomerang-Lib/boomerang.carbon.utilities/compare/@boomerang.carbon.utilities/boomerang-webapp-server@1.7.3...@boomerang.carbon.utilities/boomerang-webapp-server@1.7.4) (2020-05-26)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * some tests ([253a0ae](https://github.ibm.com/Boomerang-Lib/boomerang.carbon.utilities/commit/253a0aee7e12d68544a66e3192c01fe65099f22a))
23
+
24
+
25
+
26
+
27
+
28
+ ## [1.7.3](https://github.ibm.com/Boomerang-Lib/boomerang.carbon.utilities/compare/@boomerang.carbon.utilities/boomerang-webapp-server@1.7.2...@boomerang.carbon.utilities/boomerang-webapp-server@1.7.3) (2020-05-26)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * some tests ([167ea7b](https://github.ibm.com/Boomerang-Lib/boomerang.carbon.utilities/commit/167ea7b951f151cba28ca547b4ad8d5aecb50a04))
34
+
35
+
36
+
37
+
38
+
39
+ ## 1.7.2 (2020-05-26)
40
+
41
+ **Note:** Version bump only for package @boomerang.carbon.utilities/boomerang-webapp-server
42
+
43
+
44
+
45
+
46
+
47
+ ## 1.7.1 (2020-05-26)
48
+
49
+ **Note:** Version bump only for package @boomerang.carbon.utilities/boomerang-webapp-server
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # Webapp SPA Server
2
+
3
+ Provide a consistent way to deploy Boomerang React SPAs with client-side routing into an IBM Cloud Private environment.
4
+
5
+ ## Features
6
+
7
+ - Serve static assets
8
+ - Client-side routing support for SPAs
9
+ - Dynamic data and script injection into HTML document at run-time
10
+ - Logging with [@boomerang-io/logger-middleware](https://github.com/boomerang-io/webapp-packages/src/packages/logger-middleware)
11
+ - Cloud native health checking with [Cloud Native Health Connect](@cloudnative/health-connect)
12
+ - New Relic monitoring
13
+
14
+ ## Design
15
+
16
+ The server can be invoked via a command line or imported as a configurable function to be executed.
17
+
18
+ - CLI - for easy stand alone use that can be invoked via a script e.g. in `npm scripts`
19
+ - Function - create server and run it within a `node.js` file
20
+
21
+ ## CLI
22
+
23
+ The server can be run via the CLI interface with configuration pass as options.
24
+
25
+ Enter the following to see the manual
26
+
27
+ ```shell
28
+ boomerang-webapp-server --help
29
+ ```
30
+
31
+ Command
32
+
33
+ ```shell
34
+ boomerang-webapp-server serve
35
+ ```
36
+
37
+ Options
38
+
39
+ | **Option** | **Alias** | **Description** |
40
+ | :-------------------------- | :-------: | :----------------------------------------------------------------------------------------------- |
41
+ | --cors | -c | CORS configuration using [cors](https://www.npmjs.com/package/cors) package. Accepts JSON string |
42
+ | --disableInjectHTMLHeadData | -d | Toggle whether the app needs to inject data into the header. Defaults to `false` |
43
+ | --dotenvFilePath | -p | Path to local .env file to read in. Useful for local testing |
44
+
45
+ ## Use as a function
46
+
47
+ ```javascript
48
+ const server = require("@boomerang-io/webapp-spa-server");
49
+ server({
50
+ cors: {},
51
+ disableInjectHTMLHeadData: true,
52
+ });
53
+ ```
54
+
55
+ ## Environment Variables
56
+
57
+ The following env variables are assumed to exist either from a local `.env` file or passed in to the container at runtime. If nothing is passed it, it will default to the following:
58
+
59
+ | **Variable** | **Purpose** | **Type** |
60
+ | :--------------------------: | :-----------------------------------------------------------------------: | :------------------: |
61
+ | APP_ROOT | Root context of the application | string |
62
+ | BUILD_DIR | directory relative to the exeuction where app files are located | string |
63
+ | HTML_HEAD_INJECTED_DATA_KEYS | Environment variables to inject into the HTML document | comma delimited list |
64
+ | HTML_HEAD_INJECTED_SCRIPTS | Scripts to inject into HTML document. Files need to be in the `BUILD_DIR` | comma delimited list |
65
+ | NEW_RELIC_APP_NAME | App name for monitoring | string |
66
+ | NEW_RELIC_LICENSE_KEY | License key for monitoring | string |
67
+ | PORT | Port for server to run on | number |
68
+
69
+ ## Defaults
70
+
71
+ Some of the values, both config and environment variables have defaults in the server that make deploying to the IBM Cloud Private work out-of-the-box.
72
+
73
+ APP_ROOT
74
+
75
+ - "/"
76
+
77
+ BUILD_DIR
78
+
79
+ - "build"
80
+
81
+ CORS
82
+
83
+ ```json
84
+ {
85
+ "origin": "*",
86
+ "allowedHeaders": "Content-Type, Authorization, Content-Length, X-Requested-With",
87
+ "methods": "DELETE,GET,OPTIONS,PATCH,POST,PUT"
88
+ }
89
+ ```
90
+
91
+ HTML_HEAD_INJECTED_DATA_KEYS
92
+
93
+ - APP_ROOT
94
+ - BASE_APPS_ENV_URL
95
+ - BASE_LAUNCH_ENV_URL
96
+ - BASE_SERVICE_ENV_URL
97
+ - BASE_WWW_ENV_URL
98
+ - CORE_APPS_ENV_URL
99
+ - CORE_ENV_URL
100
+ - CORE_SERVICE_ENV_URL
101
+ - PRODUCT_APPS_ENV_URL
102
+ - PRODUCT_ENV_URL
103
+ - PRODUCT_SERVICE_ENV_URL
104
+
105
+ PORT
106
+
107
+ - 3000
package/cli.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Import and execute app
5
+ */
6
+ const app = require("./index");
7
+
8
+ require("yargs") // eslint-disable-line
9
+ .command("serve", "start the webapp server", (yargs) => {
10
+ const { cors, dotenvFilePath, disableInjectHTMLHeadData } = yargs.argv;
11
+ // Import .env file
12
+ if (dotenvFilePath) {
13
+ require("dotenv").config({
14
+ path: require("path").join(process.cwd(), dotenvFilePath),
15
+ });
16
+ }
17
+ // Invoke server
18
+ app({ cors, disableInjectHTMLHeadData });
19
+ })
20
+ .option("cors", {
21
+ alias: "c",
22
+ describe: "CORS configuration using cors package. Accepts JSON string.",
23
+ type: "string",
24
+ })
25
+
26
+ .option("disableInjectHTMLHeadData", {
27
+ alias: "d",
28
+ describe: "Enable injection of data and scripts into the head of the HTML file.",
29
+ default: false,
30
+ type: "boolean",
31
+ })
32
+ .option("dotenvFilePath", {
33
+ alias: "p",
34
+ default: false,
35
+ describe: "Path to local .env file to read in. Useful for local testing.",
36
+ type: "string",
37
+ })
38
+ .coerce({
39
+ cors: JSON.parse,
40
+ }).argv;
package/config.js ADDED
@@ -0,0 +1,20 @@
1
+ exports.defaultHtmlHeadInjectDataKeys = [
2
+ "APP_ROOT",
3
+ "BASE_APPS_ENV_URL",
4
+ "BASE_LAUNCH_ENV_URL",
5
+ "BASE_SERVICE_ENV_URL",
6
+ "BASE_WWW_ENV_URL",
7
+ "BOSUN_PRODUCT_APP_ENV_URL",
8
+ "BOSUN_PRODUCT_SERVICE_ENV_URL",
9
+ "CICD_PRODUCT_APP_ENV_URL",
10
+ "CICD_PRODUCT_SERVICE_ENV_URL",
11
+ "CORE_APPS_ENV_URL",
12
+ "CORE_ENV_URL",
13
+ "CORE_SERVICE_ENV_URL",
14
+ "NEW_RELIC_BROWSER_ID",
15
+ "NEW_RELIC_BROWSER_KEY",
16
+ "PRODUCT_STANDALONE",
17
+ "PRODUCT_SERVICE_ENV_URL",
18
+ "PRODUCT_APPS_ENV_URL",
19
+ "PRODUCT_ENV_URL",
20
+ ];
package/index.js ADDED
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ /*eslint-env node*/
3
+
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const cors = require("cors");
7
+ const serialize = require("serialize-javascript");
8
+ const boomerangLogger = require("@boomerang-io/logger-middleware")("webapp-spa-server/index.js");
9
+ const health = require("@cloudnative/health-connect");
10
+ const defaultHtmlHeadInjectDataKeys = require("./config").defaultHtmlHeadInjectDataKeys;
11
+
12
+ // Get logger function
13
+ const logger = boomerangLogger.logger;
14
+
15
+ /**
16
+ * Begin exported module
17
+ */
18
+
19
+ function createBoomerangServer({
20
+ corsConfig = {
21
+ origin: "*",
22
+ allowedHeaders: "Content-Type, Authorization, Content-Length, X-Requested-With",
23
+ methods: "DELETE,GET,OPTIONS,PATCH,POST,PUT",
24
+ },
25
+ disableInjectHTMLHeadData,
26
+ }) {
27
+ /**
28
+ * Read in values from process.env object
29
+ * Set defaults for the platform for unprovided values
30
+ */
31
+ const {
32
+ APP_ROOT = "/",
33
+ PORT = 3000,
34
+ HTML_HEAD_INJECTED_DATA_KEYS = defaultHtmlHeadInjectDataKeys.join(),
35
+ NEW_RELIC_APP_NAME,
36
+ NEW_RELIC_LICENSE_KEY,
37
+ HTML_HEAD_INJECTED_SCRIPTS,
38
+ BUILD_DIR = "build",
39
+ BASE_LAUNCH_ENV_URL,
40
+ GA_SITE_ID,
41
+ } = process.env;
42
+ console.log("PROCESS ENV: ", process.env);
43
+
44
+ // Monitoring
45
+ if (NEW_RELIC_APP_NAME && NEW_RELIC_LICENSE_KEY) {
46
+ require("newrelic");
47
+ }
48
+
49
+ /**
50
+ * Start Express app
51
+ */
52
+ const express = require("express");
53
+ const app = express();
54
+
55
+ // Compression
56
+ const compression = require("compression");
57
+ app.use(compression());
58
+
59
+ // Logging
60
+ app.use(boomerangLogger.middleware);
61
+
62
+ // Security
63
+ const helmet = require("helmet");
64
+ app.use(helmet());
65
+ app.disable("x-powered-by");
66
+ app.use(cors(corsConfig));
67
+
68
+ // Parsing
69
+ const bodyParser = require("body-parser");
70
+ app.use(bodyParser.urlencoded({ extended: true }));
71
+
72
+ // Initialize healthchecker and add routes
73
+ const healthchecker = new health.HealthChecker();
74
+ app.use("/health", health.LivenessEndpoint(healthchecker));
75
+ app.use("/ready", health.ReadinessEndpoint(healthchecker));
76
+
77
+ // Create endpoint for the app serve static assets
78
+ const appRouter = express.Router();
79
+
80
+ /**
81
+ * Next two routes are needed for serving apps with client-side routing
82
+ * Do NOT return index.html file by default if `disableInjectHTMLHeadData = true`. We need append data to it.
83
+ * It will be returned on the second route
84
+ * https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing
85
+ */
86
+ console.log("0: ",GA_SITE_ID,BASE_LAUNCH_ENV_URL, "URL and ID");
87
+
88
+ if (!disableInjectHTMLHeadData) {
89
+ appRouter.use(
90
+ "/",
91
+ express.static(path.join(process.cwd(), BUILD_DIR), {
92
+ index: false,
93
+ })
94
+ );
95
+ appRouter.get("/*", (req, res) =>
96
+ injectEnvDataAndScriptsIntoHTML(
97
+ res,
98
+ BUILD_DIR,
99
+ HTML_HEAD_INJECTED_DATA_KEYS,
100
+ HTML_HEAD_INJECTED_SCRIPTS,
101
+ APP_ROOT,
102
+ GA_SITE_ID,
103
+ BASE_LAUNCH_ENV_URL
104
+ )
105
+ );
106
+ } else {
107
+ appRouter.use("/", express.static(path.join(process.cwd(), BUILD_DIR)));
108
+ }
109
+
110
+ app.use(APP_ROOT, appRouter);
111
+
112
+ // Start server on the specified port and binding host
113
+ app.listen(PORT, "0.0.0.0", function () {
114
+ logger.debug("server starting on", PORT);
115
+ logger.debug(`serving on root context: ${APP_ROOT}`);
116
+ logger.info(`View app: http://localhost:${PORT}${APP_ROOT}`);
117
+ });
118
+
119
+ // Return server if needed to be used in an app
120
+ return app;
121
+ }
122
+
123
+ /**
124
+ * Start utility functions
125
+ */
126
+
127
+ /**
128
+ * Add JSON data and scripts to the html file based on environment. Enables same docker image to be used in any environment
129
+ * https://medium.com/@housecor/12-rules-for-professional-javascript-in-2015-f158e7d3f0fc
130
+ * https://stackoverflow.com/questions/33027089/res-sendfile-in-node-express-with-passing-data-along
131
+ * @param {function} res - Express response function
132
+ * @param {string} buildDir - build directory for building up path to index.html file
133
+ * @param {string} injectedDataKeys - string of comma delimited values
134
+ * @param {string} injectedScripts - string of comma delimited values
135
+ * @param {string} appRoot - root context off app. Used for script injection
136
+ * @param {string} gaSiteId - siteID to be injected on scripts to support GA
137
+ * @param {string} baseLaunchUrl - base url to determine GA primaryCategory
138
+ */
139
+ function injectEnvDataAndScriptsIntoHTML(
140
+ res,
141
+ buildDir,
142
+ injectedDataKeys,
143
+ injectedScripts,
144
+ appRoot,
145
+ gaSiteId,
146
+ baseLaunchUrl
147
+ ) {
148
+ /**
149
+ * Create objects to be injected into application via the HEAD tag
150
+ */
151
+ // Build script for GA integration
152
+ console.log("1: ",gaSiteId, "GA Site ID");
153
+ const headScripstGA = Boolean(gaSiteId)
154
+ ? `<script type="text/javascript">
155
+ window.idaPageIsSPA = true;
156
+ window._ibmAnalytics = {
157
+ settings: {
158
+ name: "IBM_Services_Essentials",
159
+ isSpa: true,
160
+ tealiumProfileName: "ibm-web-app",
161
+ },
162
+ trustarc: {
163
+ isCookiePreferencesButtonAlwaysOn: false,
164
+ },
165
+ };
166
+ digitalData = {
167
+ page: {
168
+ pageInfo: {
169
+ ibm: {
170
+ siteID: '${gaSiteId}',
171
+ }
172
+ },
173
+ category: {
174
+ primaryCategory: 'PC100'
175
+ }
176
+ }
177
+ };
178
+ </script>
179
+ <script src="//1.www.s81c.com/common/stats/ibm-common.js" type="text/javascript"></script>
180
+ `
181
+ : "";
182
+ // Build up object of external data to append
183
+ const headInjectedData = injectedDataKeys.split(",").reduce((acc, key) => {
184
+ acc[key] = process.env[key];
185
+ return acc;
186
+ }, {});
187
+
188
+ // Build up string of scripts to append, absolute path
189
+ const headScriptsTags = injectedScripts
190
+ ? injectedScripts
191
+ .split(",")
192
+ .reduce((acc, currentValue) => `${acc}<script src="${appRoot}/${currentValue}"></script>`, "")
193
+ : "";
194
+ // Set the response type so browser interprets it as an html file
195
+ res.type(".html");
196
+
197
+ // Read in HTML file and add callback functions for EventEmitter events produced by ReadStream
198
+ fs.createReadStream(path.join(process.cwd(), buildDir, "index.html"))
199
+ .on("end", () => {
200
+ res.end();
201
+ })
202
+ .on("error", (e) => logger.error(e))
203
+ .on("data", (chunk) => res.write(addHeadData(chunk)));
204
+
205
+ /**
206
+ * Convert buffer to string and replace closing head tag with env-specific data and additional scripts
207
+ * Serialize data for security
208
+ * https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0
209
+ * @param {Buffer} chunk
210
+ * @return {string} replaced string with data interopolated
211
+ */
212
+ console.log("2: ", headScripstGA, "GA script");
213
+ function addHeadData(chunk) {
214
+ return chunk.toString().replace(
215
+ "</head>",
216
+ `<script>
217
+ window._SERVER_DATA = ${serialize(headInjectedData, {
218
+ isJSON: true,
219
+ })};
220
+ </script>
221
+ ${headScripstGA}
222
+ ${headScriptsTags}
223
+ </head>`
224
+ );
225
+ }
226
+ }
227
+
228
+ module.exports = createBoomerangServer;
package/newrelic.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * New Relic agent configuration.
5
+ *
6
+ * See lib/config.default.js in the agent distribution for a more complete
7
+ * description of configuration variables and their potential values.
8
+ */
9
+ exports.config = {
10
+ /**
11
+ * Array of application names.
12
+ */
13
+ app_name: process.env.NEW_RELIC_APP_NAME,
14
+ /**
15
+ * Your New Relic license key.
16
+ */
17
+ license_key: process.env.NEW_RELIC_LICENSE_KEY,
18
+ proxy_host: process.env.NEW_RELIC_PROXY_HOST,
19
+ proxy_port: process.env.NEW_RELIC_PROXY_PORT,
20
+ logging: {
21
+ /**
22
+ * Level at which to log. 'trace' is most useful to New Relic when diagnosing
23
+ * issues with the agent, 'info' and higher will impose the least overhead on
24
+ * production applications.
25
+ */
26
+ level: "info",
27
+ },
28
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@boomerang-io/webapp-spa-server",
3
+ "description": "Webapp Server for React-based SPA w/ client-side routing",
4
+ "version": "0.0.11-beta.15",
5
+ "author": {
6
+ "name": "Tim Bula",
7
+ "email": "timrbula@gmail.com"
8
+ },
9
+ "license": "Apache-2.0",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git@github.com:boomerang-io/webapp-packages",
13
+ "directory": "packages/webapp-spa-server"
14
+ },
15
+ "homepage": "https://github.com/boomerang-io/webapp-packages",
16
+ "bugs": {
17
+ "url": "https://github.com/boomerang-io/webapp-packages/issues"
18
+ },
19
+ "bin": {
20
+ "boomerang-webapp-server": "./cli.js"
21
+ },
22
+ "main": "index.js",
23
+ "scripts": {
24
+ "dev": "nodemon --exec npm run-script start",
25
+ "start": "node tester.js"
26
+ },
27
+ "dependencies": {
28
+ "@boomerang-io/logger-middleware": "0.0.1",
29
+ "@cloudnative/health-connect": "1.0.2",
30
+ "body-parser": "1.18.3",
31
+ "compression": "1.7.3",
32
+ "cors": "2.8.5",
33
+ "dotenv": "6.2.0",
34
+ "express": "4.16.4",
35
+ "helmet": "^3.23.1",
36
+ "newrelic": "^7.3.1",
37
+ "serialize-javascript": "4.0.0",
38
+ "yargs": "^16.2.0"
39
+ },
40
+ "devDependencies": {
41
+ "nodemon": "1.18.10"
42
+ },
43
+ "keywords": [
44
+ "express",
45
+ "node",
46
+ "server",
47
+ "spa"
48
+ ]
49
+ }
package/tester.js ADDED
@@ -0,0 +1,5 @@
1
+ // Used for development
2
+ require("dotenv").config({
3
+ path: "./.env.local",
4
+ });
5
+ require("./index")({});