@boomerang-io/webapp-spa-server 1.2.1 → 1.2.2

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/README.md CHANGED
@@ -1,114 +1,114 @@
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
- - Instana monitoring
14
- - Google Analytics
15
- - BeeHeard survey
16
-
17
- ## Design
18
-
19
- The server can be invoked via a command line or imported as a configurable function to be executed.
20
-
21
- - CLI - for easy stand alone use that can be invoked via a script e.g. in `npm scripts`
22
- - Function - create server and run it within a `node.js` file
23
-
24
- ## CLI
25
-
26
- The server can be run via the CLI interface with configuration pass as options.
27
-
28
- Enter the following to see the manual
29
-
30
- ```shell
31
- boomerang-webapp-server --help
32
- ```
33
-
34
- Command
35
-
36
- ```shell
37
- boomerang-webapp-server serve
38
- ```
39
-
40
- Options
41
-
42
- | **Option** | **Alias** | **Description** |
43
- | :-------------------------- | :-------: | :----------------------------------------------------------------------------------------------- |
44
- | --cors | -c | CORS configuration using [cors](https://www.npmjs.com/package/cors) package. Accepts JSON string |
45
- | --disableInjectHTMLHeadData | -d | Toggle whether the app needs to inject data into the header. Defaults to `false` |
46
- | --dotenvFilePath | -p | Path to local .env file to read in. Useful for local testing |
47
-
48
- ## Use as a function
49
-
50
- ```javascript
51
- const server = require("@boomerang-io/webapp-spa-server");
52
- server({
53
- cors: {},
54
- disableInjectHTMLHeadData: true,
55
- });
56
- ```
57
-
58
- ## Environment Variables
59
-
60
- 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:
61
-
62
- | **Variable** | **Purpose** | **Type** |
63
- | :--------------------------: | :-----------------------------------------------------------------------: | :------------------: |
64
- | APP_ROOT | Root context of the application | string |
65
- | BUILD_DIR | directory relative to the exeuction where app files are located | string |
66
- | PORT | Port for server to run on | number |
67
- | HTML_HEAD_INJECTED_DATA_KEYS | Environment variables to inject into the HTML document | comma delimited list |
68
- | HTML_HEAD_INJECTED_SCRIPTS | Scripts to inject into HTML document. Files need to be in the `BUILD_DIR` | comma delimited list |
69
- | NEW_RELIC_APP_NAME | App name for New Relic monitoring | string |
70
- | NEW_RELIC_LICENSE_KEY | License key for New Relic monitoring | string |
71
- | INSTANA_REPORTING_URL | Reporting URL for Instana monitoring | string |
72
- | INSTANA_KEY | License key for Instana monitoring | string |
73
- | GA_SITE_ID | Site ID for Goolge Analytics | string |
74
- | ENABLE_BEEHEARD_SURVEY | Enable BeeHeard survey | boolean |
75
-
76
- ## Defaults
77
-
78
- Some of the values, both config and environment variables have defaults in the server for deploying to the IBM Consulting Essentials platform.
79
-
80
- APP_ROOT
81
-
82
- - "/"
83
-
84
- BUILD_DIR
85
-
86
- - "build"
87
-
88
- CORS
89
-
90
- ```json
91
- {
92
- "origin": "*",
93
- "allowedHeaders": "Content-Type, Authorization, Content-Length, X-Requested-With",
94
- "methods": "DELETE,GET,OPTIONS,PATCH,POST,PUT"
95
- }
96
- ```
97
-
98
- HTML_HEAD_INJECTED_DATA_KEYS
99
-
100
- - APP_ROOT
101
- - BASE_APPS_ENV_URL
102
- - BASE_LAUNCH_ENV_URL
103
- - BASE_SERVICE_ENV_URL
104
- - BASE_WWW_ENV_URL
105
- - CORE_APPS_ENV_URL
106
- - CORE_ENV_URL
107
- - CORE_SERVICE_ENV_URL
108
- - PRODUCT_APPS_ENV_URL
109
- - PRODUCT_ENV_URL
110
- - PRODUCT_SERVICE_ENV_URL
111
-
112
- PORT
113
-
114
- - 3000
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
+ - Instana monitoring
14
+ - Google Analytics
15
+ - BeeHeard survey
16
+
17
+ ## Design
18
+
19
+ The server can be invoked via a command line or imported as a configurable function to be executed.
20
+
21
+ - CLI - for easy stand alone use that can be invoked via a script e.g. in `npm scripts`
22
+ - Function - create server and run it within a `node.js` file
23
+
24
+ ## CLI
25
+
26
+ The server can be run via the CLI interface with configuration pass as options.
27
+
28
+ Enter the following to see the manual
29
+
30
+ ```shell
31
+ boomerang-webapp-server --help
32
+ ```
33
+
34
+ Command
35
+
36
+ ```shell
37
+ boomerang-webapp-server serve
38
+ ```
39
+
40
+ Options
41
+
42
+ | **Option** | **Alias** | **Description** |
43
+ | :-------------------------- | :-------: | :----------------------------------------------------------------------------------------------- |
44
+ | --cors | -c | CORS configuration using [cors](https://www.npmjs.com/package/cors) package. Accepts JSON string |
45
+ | --disableInjectHTMLHeadData | -d | Toggle whether the app needs to inject data into the header. Defaults to `false` |
46
+ | --dotenvFilePath | -p | Path to local .env file to read in. Useful for local testing |
47
+
48
+ ## Use as a function
49
+
50
+ ```javascript
51
+ const server = require("@boomerang-io/webapp-spa-server");
52
+ server({
53
+ cors: {},
54
+ disableInjectHTMLHeadData: true,
55
+ });
56
+ ```
57
+
58
+ ## Environment Variables
59
+
60
+ 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:
61
+
62
+ | **Variable** | **Purpose** | **Type** |
63
+ | :--------------------------: | :-----------------------------------------------------------------------: | :------------------: |
64
+ | APP_ROOT | Root context of the application | string |
65
+ | BUILD_DIR | directory relative to the exeuction where app files are located | string |
66
+ | PORT | Port for server to run on | number |
67
+ | HTML_HEAD_INJECTED_DATA_KEYS | Environment variables to inject into the HTML document | comma delimited list |
68
+ | HTML_HEAD_INJECTED_SCRIPTS | Scripts to inject into HTML document. Files need to be in the `BUILD_DIR` | comma delimited list |
69
+ | NEW_RELIC_APP_NAME | App name for New Relic monitoring | string |
70
+ | NEW_RELIC_LICENSE_KEY | License key for New Relic monitoring | string |
71
+ | INSTANA_REPORTING_URL | Reporting URL for Instana monitoring | string |
72
+ | INSTANA_KEY | License key for Instana monitoring | string |
73
+ | GA_SITE_ID | Site ID for Goolge Analytics | string |
74
+ | ENABLE_BEEHEARD_SURVEY | Enable BeeHeard survey | boolean |
75
+
76
+ ## Defaults
77
+
78
+ Some of the values, both config and environment variables have defaults in the server for deploying to the IBM Consulting Essentials platform.
79
+
80
+ APP_ROOT
81
+
82
+ - "/"
83
+
84
+ BUILD_DIR
85
+
86
+ - "build"
87
+
88
+ CORS
89
+
90
+ ```json
91
+ {
92
+ "origin": "*",
93
+ "allowedHeaders": "Content-Type, Authorization, Content-Length, X-Requested-With",
94
+ "methods": "DELETE,GET,OPTIONS,PATCH,POST,PUT"
95
+ }
96
+ ```
97
+
98
+ HTML_HEAD_INJECTED_DATA_KEYS
99
+
100
+ - APP_ROOT
101
+ - BASE_APPS_ENV_URL
102
+ - BASE_LAUNCH_ENV_URL
103
+ - BASE_SERVICE_ENV_URL
104
+ - BASE_WWW_ENV_URL
105
+ - CORE_APPS_ENV_URL
106
+ - CORE_ENV_URL
107
+ - CORE_SERVICE_ENV_URL
108
+ - PRODUCT_APPS_ENV_URL
109
+ - PRODUCT_ENV_URL
110
+ - PRODUCT_SERVICE_ENV_URL
111
+
112
+ PORT
113
+
114
+ - 3000
package/cli.js CHANGED
@@ -1,52 +1,52 @@
1
- #!/usr/bin/env node
2
-
3
- const boomerangLogger = require("@boomerang-io/logger-middleware")("webapp-spa-server/index.js");
4
- const logger = boomerangLogger.logger;
5
- /**
6
- * Import and execute app
7
- */
8
- const app = require("./index");
9
-
10
- require("yargs") // eslint-disable-line
11
- .command("serve", "start the webapp server", (yargs) => {
12
- const { cors, dotenvFilePath, disableInjectHTMLHeadData } = yargs.argv;
13
- // Import .env file
14
- if (dotenvFilePath) {
15
- require("dotenv").config({
16
- path: require("path").join(process.cwd(), dotenvFilePath),
17
- });
18
- }
19
- // Invoke server
20
- app({ cors, disableInjectHTMLHeadData });
21
- })
22
- .option("cors", {
23
- alias: "c",
24
- describe: "CORS configuration using cors package. Accepts JSON string.",
25
- type: "string",
26
- })
27
- .option("disableInjectHTMLHeadData", {
28
- alias: "d",
29
- describe: "Enable injection of data and scripts into the head of the HTML file.",
30
- default: false,
31
- type: "boolean",
32
- })
33
- .option("dotenvFilePath", {
34
- alias: "p",
35
- default: false,
36
- describe: "Path to local .env file to read in. Useful for local testing.",
37
- type: "string",
38
- })
39
- .coerce({
40
- cors: (arg) => {
41
- if (arg) {
42
- return JSON.parse(arg);
43
- }
44
- },
45
- })
46
- .fail(function (msg, err) {
47
- logger.error(msg);
48
- if (err) {
49
- throw err;
50
- }
51
- process.exit(1);
52
- }).argv;
1
+ #!/usr/bin/env node
2
+
3
+ const boomerangLogger = require("@boomerang-io/logger-middleware")("webapp-spa-server/index.js");
4
+ const logger = boomerangLogger.logger;
5
+ /**
6
+ * Import and execute app
7
+ */
8
+ const app = require("./index");
9
+
10
+ require("yargs") // eslint-disable-line
11
+ .command("serve", "start the webapp server", (yargs) => {
12
+ const { cors, dotenvFilePath, disableInjectHTMLHeadData } = yargs.argv;
13
+ // Import .env file
14
+ if (dotenvFilePath) {
15
+ require("dotenv").config({
16
+ path: require("path").join(process.cwd(), dotenvFilePath),
17
+ });
18
+ }
19
+ // Invoke server
20
+ app({ cors, disableInjectHTMLHeadData });
21
+ })
22
+ .option("cors", {
23
+ alias: "c",
24
+ describe: "CORS configuration using cors package. Accepts JSON string.",
25
+ type: "string",
26
+ })
27
+ .option("disableInjectHTMLHeadData", {
28
+ alias: "d",
29
+ describe: "Enable injection of data and scripts into the head of the HTML file.",
30
+ default: false,
31
+ type: "boolean",
32
+ })
33
+ .option("dotenvFilePath", {
34
+ alias: "p",
35
+ default: false,
36
+ describe: "Path to local .env file to read in. Useful for local testing.",
37
+ type: "string",
38
+ })
39
+ .coerce({
40
+ cors: (arg) => {
41
+ if (arg) {
42
+ return JSON.parse(arg);
43
+ }
44
+ },
45
+ })
46
+ .fail(function (msg, err) {
47
+ logger.error(msg);
48
+ if (err) {
49
+ throw err;
50
+ }
51
+ process.exit(1);
52
+ }).argv;
package/config.js CHANGED
@@ -1,20 +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
- ];
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 CHANGED
@@ -1,245 +1,245 @@
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
- } = process.env;
40
-
41
- // Monitoring
42
- if (NEW_RELIC_APP_NAME && NEW_RELIC_LICENSE_KEY) {
43
- require("newrelic");
44
- }
45
-
46
- /**
47
- * Start Express app
48
- */
49
- const express = require("express");
50
- const app = express();
51
-
52
- // Compression
53
- const compression = require("compression");
54
- app.use(compression());
55
-
56
- // Logging
57
- app.use(boomerangLogger.middleware);
58
-
59
- // Security
60
- const helmet = require("helmet");
61
- app.use(
62
- helmet({
63
- referrerPolicy: { policy: "strict-origin-when-cross-origin" },
64
- contentSecurityPolicy: false,
65
- crossOriginEmbedderPolicy: false,
66
- crossOriginOpenerPolicy: false,
67
- crossOriginResourcePolicy: false,
68
- })
69
- );
70
- app.disable("x-powered-by");
71
- app.use(cors(corsConfig));
72
-
73
- // Parsing
74
- const bodyParser = require("body-parser");
75
- app.use(bodyParser.urlencoded({ extended: true }));
76
-
77
- // Initialize healthchecker and add routes
78
- const healthchecker = new health.HealthChecker();
79
- app.use("/health", health.LivenessEndpoint(healthchecker));
80
- app.use("/ready", health.ReadinessEndpoint(healthchecker));
81
-
82
- // Create endpoint for the app serve static assets
83
- const appRouter = express.Router();
84
-
85
- /**
86
- * Next two routes are needed for serving apps with client-side routing
87
- * Do NOT return index.html file by default if `disableInjectHTMLHeadData = true`. We need append data to it.
88
- * It will be returned on the second route
89
- * https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing
90
- * Cache assets "forever" aka max recommended value of one year
91
- */
92
- if (!disableInjectHTMLHeadData) {
93
- appRouter.use(
94
- "/",
95
- express.static(path.join(process.cwd(), BUILD_DIR), {
96
- maxAge: 31536000000,
97
- index: false,
98
- })
99
- );
100
- appRouter.get("/*", (_, res) =>
101
- injectEnvDataAndScriptsIntoHTML({
102
- res,
103
- appRoot: APP_ROOT,
104
- buildDir: BUILD_DIR,
105
- injectedDataKeys: HTML_HEAD_INJECTED_DATA_KEYS,
106
- injectedScripts: HTML_HEAD_INJECTED_SCRIPTS,
107
- })
108
- );
109
- } else {
110
- appRouter.use("/", express.static(path.join(process.cwd(), BUILD_DIR)));
111
- }
112
-
113
- app.use(APP_ROOT, appRouter);
114
-
115
- // Start server on the specified port and binding host
116
- app.listen(PORT, "0.0.0.0", function () {
117
- logger.debug("server starting on", PORT);
118
- logger.debug(`serving on root context: ${APP_ROOT}`);
119
- logger.info(`View app: http://localhost:${PORT}${APP_ROOT}`);
120
- });
121
-
122
- // Return server if needed to be used in an app
123
- return app;
124
- }
125
-
126
- /**
127
- * Start utility functions
128
- */
129
-
130
- /**
131
- * Add JSON data and scripts to the html file based on environment. Enables same docker image to be used in any environment
132
- * https://medium.com/@housecor/12-rules-for-professional-javascript-in-2015-f158e7d3f0fc
133
- * https://stackoverflow.com/questions/33027089/res-sendfile-in-node-express-with-passing-data-along
134
- * @param {function} res - Express response function
135
- * @param {string} buildDir - build directory for building up path to index.html file
136
- * @param {string} injectedDataKeys - string of comma delimited values
137
- * @param {string} injectedScripts - string of comma delimited values
138
- */
139
- function injectEnvDataAndScriptsIntoHTML({ res, buildDir, appRoot, injectedDataKeys, injectedScripts }) {
140
- // Build up object of external data to append
141
- const headInjectedData = injectedDataKeys.split(",").reduce((acc, key) => {
142
- acc[key] = process.env[key];
143
- return acc;
144
- }, {});
145
-
146
- // Build up string of scripts to append, absolute path
147
- const localScriptTags = injectedScripts
148
- ? injectedScripts
149
- .split(",")
150
- .reduce((acc, currentValue) => `${acc}<script async src="${appRoot}/${currentValue}"></script>`, "")
151
- : "";
152
- // Set the response type so browser interprets it as an html file
153
- res.type(".html");
154
-
155
- // Read in HTML file and add callback functions for EventEmitter events produced by ReadStream
156
- fs.createReadStream(path.join(process.cwd(), buildDir, "index.html"))
157
- .on("end", () => {
158
- res.end();
159
- })
160
- .on("error", (e) => logger.error(e))
161
- .on("data", (chunk) => res.write(addHeadData(chunk)));
162
-
163
- /**
164
- * Convert buffer to string and replace closing head tag with env-specific data and additional scripts
165
- * Serialize data for security
166
- * https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0
167
- * @param {Buffer} chunk
168
- * @return {string} replaced string with data interopolated
169
- */
170
- function addHeadData(chunk) {
171
- return chunk.toString().replace(
172
- "</head>",
173
- `<script>
174
- window._SERVER_DATA = ${serialize(headInjectedData, {
175
- isJSON: true,
176
- })};
177
- </script>
178
- ${getBeeheardSurveyScripts()}
179
- ${getGAScripts()}
180
- ${getInstanaScripts()}
181
- ${localScriptTags}
182
- </head>`
183
- );
184
- }
185
- }
186
-
187
- // Include BeeHeard survey based on env var
188
- function getBeeheardSurveyScripts() {
189
- const enableBeeheardSurvey = process.env.ENABLE_BEEHEARD_SURVEY;
190
- return Boolean(enableBeeheardSurvey)
191
- ? '<script async src="https://beeheard.dal1a.cirrus.ibm.com/survey/preconfig/HHPxpQgN.js" crossorigin></script>'
192
- : "";
193
- }
194
-
195
- // Include Google Analytics based on env var
196
- function getGAScripts() {
197
- const gaSiteId = process.env.GA_SITE_ID;
198
- return Boolean(gaSiteId)
199
- ? `<script type="text/javascript">
200
- window.idaPageIsSPA = true;
201
- window._ibmAnalytics = {
202
- settings: {
203
- name: "IBM_Services_Essentials",
204
- isSpa: true,
205
- tealiumProfileName: "ibm-web-app",
206
- },
207
- trustarc: {
208
- isCookiePreferencesButtonAlwaysOn: false,
209
- },
210
- };
211
- digitalData = {
212
- page: {
213
- pageInfo: {
214
- ibm: {
215
- siteID: '${gaSiteId}',
216
- }
217
- },
218
- category: {
219
- primaryCategory: 'PC100'
220
- }
221
- }
222
- };
223
- </script>
224
- <script async src="https://1.www.s81c.com/common/stats/ibm-common.js" type="text/javascript" crossorigin></script>`
225
- : "";
226
- }
227
-
228
- // Include Instana monitoring based on env var
229
- function getInstanaScripts() {
230
- const instanaReportingUrl = process.env.INSTANA_REPORTING_URL;
231
- const instanaKey = process.env.INSTANA_KEY;
232
-
233
- return Boolean(instanaReportingUrl) && Boolean(instanaKey)
234
- ? `<script type="text/javascript">
235
- (function(s,t,a,n){s[t]||(s[t]=a,n=s[a]=function(){n.q.push(arguments)},
236
- n.q=[],n.v=2,n.l=1*new Date)})(window,"InstanaEumObject","ineum");
237
- ineum('reportingUrl', '${instanaReportingUrl}');
238
- ineum('key', '${instanaKey}');
239
- ineum('trackSessions');
240
- </script>
241
- <script async crossorigin="anonymous" src="https://eum.instana.io/eum.min.js"></script>`
242
- : "";
243
- }
244
-
245
- module.exports = createBoomerangServer;
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
+ } = process.env;
40
+
41
+ // Monitoring
42
+ if (NEW_RELIC_APP_NAME && NEW_RELIC_LICENSE_KEY) {
43
+ require("newrelic");
44
+ }
45
+
46
+ /**
47
+ * Start Express app
48
+ */
49
+ const express = require("express");
50
+ const app = express();
51
+
52
+ // Compression
53
+ const compression = require("compression");
54
+ app.use(compression());
55
+
56
+ // Logging
57
+ app.use(boomerangLogger.middleware);
58
+
59
+ // Security
60
+ const helmet = require("helmet");
61
+ app.use(
62
+ helmet({
63
+ referrerPolicy: { policy: "strict-origin-when-cross-origin" },
64
+ contentSecurityPolicy: false,
65
+ crossOriginEmbedderPolicy: false,
66
+ crossOriginOpenerPolicy: false,
67
+ crossOriginResourcePolicy: false,
68
+ })
69
+ );
70
+ app.disable("x-powered-by");
71
+ app.use(cors(corsConfig));
72
+
73
+ // Parsing
74
+ const bodyParser = require("body-parser");
75
+ app.use(bodyParser.urlencoded({ extended: true }));
76
+
77
+ // Initialize healthchecker and add routes
78
+ const healthchecker = new health.HealthChecker();
79
+ app.use("/health", health.LivenessEndpoint(healthchecker));
80
+ app.use("/ready", health.ReadinessEndpoint(healthchecker));
81
+
82
+ // Create endpoint for the app serve static assets
83
+ const appRouter = express.Router();
84
+
85
+ /**
86
+ * Next two routes are needed for serving apps with client-side routing
87
+ * Do NOT return index.html file by default if `disableInjectHTMLHeadData = true`. We need append data to it.
88
+ * It will be returned on the second route
89
+ * https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#serving-apps-with-client-side-routing
90
+ * Cache assets "forever" aka max recommended value of one year
91
+ */
92
+ if (!disableInjectHTMLHeadData) {
93
+ appRouter.use(
94
+ "/",
95
+ express.static(path.join(process.cwd(), BUILD_DIR), {
96
+ maxAge: 31536000000,
97
+ index: false,
98
+ })
99
+ );
100
+ appRouter.get("/*", (_, res) =>
101
+ injectEnvDataAndScriptsIntoHTML({
102
+ res,
103
+ appRoot: APP_ROOT,
104
+ buildDir: BUILD_DIR,
105
+ injectedDataKeys: HTML_HEAD_INJECTED_DATA_KEYS,
106
+ injectedScripts: HTML_HEAD_INJECTED_SCRIPTS,
107
+ })
108
+ );
109
+ } else {
110
+ appRouter.use("/", express.static(path.join(process.cwd(), BUILD_DIR)));
111
+ }
112
+
113
+ app.use(APP_ROOT, appRouter);
114
+
115
+ // Start server on the specified port and binding host
116
+ app.listen(PORT, "0.0.0.0", function () {
117
+ logger.debug("server starting on", PORT);
118
+ logger.debug(`serving on root context: ${APP_ROOT}`);
119
+ logger.info(`View app: http://localhost:${PORT}${APP_ROOT}`);
120
+ });
121
+
122
+ // Return server if needed to be used in an app
123
+ return app;
124
+ }
125
+
126
+ /**
127
+ * Start utility functions
128
+ */
129
+
130
+ /**
131
+ * Add JSON data and scripts to the html file based on environment. Enables same docker image to be used in any environment
132
+ * https://medium.com/@housecor/12-rules-for-professional-javascript-in-2015-f158e7d3f0fc
133
+ * https://stackoverflow.com/questions/33027089/res-sendfile-in-node-express-with-passing-data-along
134
+ * @param {function} res - Express response function
135
+ * @param {string} buildDir - build directory for building up path to index.html file
136
+ * @param {string} injectedDataKeys - string of comma delimited values
137
+ * @param {string} injectedScripts - string of comma delimited values
138
+ */
139
+ function injectEnvDataAndScriptsIntoHTML({ res, buildDir, appRoot, injectedDataKeys, injectedScripts }) {
140
+ // Build up object of external data to append
141
+ const headInjectedData = injectedDataKeys.split(",").reduce((acc, key) => {
142
+ acc[key] = process.env[key];
143
+ return acc;
144
+ }, {});
145
+
146
+ // Build up string of scripts to append, absolute path
147
+ const localScriptTags = injectedScripts
148
+ ? injectedScripts
149
+ .split(",")
150
+ .reduce((acc, currentValue) => `${acc}<script async src="${appRoot}/${currentValue}"></script>`, "")
151
+ : "";
152
+ // Set the response type so browser interprets it as an html file
153
+ res.type(".html");
154
+
155
+ // Read in HTML file and add callback functions for EventEmitter events produced by ReadStream
156
+ fs.createReadStream(path.join(process.cwd(), buildDir, "index.html"))
157
+ .on("end", () => {
158
+ res.end();
159
+ })
160
+ .on("error", (e) => logger.error(e))
161
+ .on("data", (chunk) => res.write(addHeadData(chunk)));
162
+
163
+ /**
164
+ * Convert buffer to string and replace closing head tag with env-specific data and additional scripts
165
+ * Serialize data for security
166
+ * https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0
167
+ * @param {Buffer} chunk
168
+ * @return {string} replaced string with data interopolated
169
+ */
170
+ function addHeadData(chunk) {
171
+ return chunk.toString().replace(
172
+ "</head>",
173
+ `<script>
174
+ window._SERVER_DATA = ${serialize(headInjectedData, {
175
+ isJSON: true,
176
+ })};
177
+ </script>
178
+ ${getBeeheardSurveyScripts()}
179
+ ${getGAScripts()}
180
+ ${getInstanaScripts()}
181
+ ${localScriptTags}
182
+ </head>`
183
+ );
184
+ }
185
+ }
186
+
187
+ // Include BeeHeard survey based on env var
188
+ function getBeeheardSurveyScripts() {
189
+ const enableBeeheardSurvey = process.env.ENABLE_BEEHEARD_SURVEY;
190
+ return Boolean(enableBeeheardSurvey)
191
+ ? '<script async src="https://beeheard.dal1a.cirrus.ibm.com/survey/preconfig/HHPxpQgN.js" crossorigin></script>'
192
+ : "";
193
+ }
194
+
195
+ // Include Google Analytics based on env var
196
+ function getGAScripts() {
197
+ const gaSiteId = process.env.GA_SITE_ID;
198
+ return Boolean(gaSiteId)
199
+ ? `<script type="text/javascript">
200
+ window.idaPageIsSPA = true;
201
+ window._ibmAnalytics = {
202
+ settings: {
203
+ name: "IBM_Services_Essentials",
204
+ isSpa: true,
205
+ tealiumProfileName: "ibm-web-app",
206
+ },
207
+ trustarc: {
208
+ isCookiePreferencesButtonAlwaysOn: false,
209
+ },
210
+ };
211
+ digitalData = {
212
+ page: {
213
+ pageInfo: {
214
+ ibm: {
215
+ siteID: '${gaSiteId}',
216
+ }
217
+ },
218
+ category: {
219
+ primaryCategory: 'PC100'
220
+ }
221
+ }
222
+ };
223
+ </script>
224
+ <script async src="https://1.www.s81c.com/common/stats/ibm-common.js" type="text/javascript" crossorigin></script>`
225
+ : "";
226
+ }
227
+
228
+ // Include Instana monitoring based on env var
229
+ function getInstanaScripts() {
230
+ const instanaReportingUrl = process.env.INSTANA_REPORTING_URL;
231
+ const instanaKey = process.env.INSTANA_KEY;
232
+
233
+ return Boolean(instanaReportingUrl) && Boolean(instanaKey)
234
+ ? `<script type="text/javascript">
235
+ (function(s,t,a,n){s[t]||(s[t]=a,n=s[a]=function(){n.q.push(arguments)},
236
+ n.q=[],n.v=2,n.l=1*new Date)})(window,"InstanaEumObject","ineum");
237
+ ineum('reportingUrl', '${instanaReportingUrl}');
238
+ ineum('key', '${instanaKey}');
239
+ ineum('trackSessions');
240
+ </script>
241
+ <script async crossorigin="anonymous" src="https://eum.instana.io/eum.min.js"></script>`
242
+ : "";
243
+ }
244
+
245
+ module.exports = createBoomerangServer;
package/newrelic.js CHANGED
@@ -1,28 +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
- };
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 CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "@boomerang-io/webapp-spa-server",
3
- "description": "Webapp Server for React-based SPA w/ client-side routing",
4
- "version": "1.2.1",
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": "1.0.0",
29
- "@cloudnative/health-connect": "^2.1.0",
30
- "body-parser": "^1.20.1",
31
- "compression": "^1.7.4",
32
- "cors": "^2.8.5",
33
- "dotenv": "^16.0.3",
34
- "express": "^4.18.2",
35
- "helmet": "^6.0.0",
36
- "newrelic": "^9.2.0",
37
- "serialize-javascript": "^6.0.0",
38
- "yargs": "^17.6.0"
39
- },
40
- "devDependencies": {
41
- "nodemon": "^2.0.15"
42
- },
43
- "keywords": [
44
- "express",
45
- "node",
46
- "server",
47
- "spa"
48
- ]
1
+ {
2
+ "name": "@boomerang-io/webapp-spa-server",
3
+ "description": "Webapp Server for React-based SPA w/ client-side routing",
4
+ "version": "1.2.2",
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": "1.0.0",
29
+ "@cloudnative/health-connect": "^2.1.0",
30
+ "body-parser": "^1.20.1",
31
+ "compression": "^1.7.4",
32
+ "cors": "^2.8.5",
33
+ "dotenv": "^16.0.3",
34
+ "express": "^4.18.2",
35
+ "helmet": "^6.0.0",
36
+ "newrelic": "^9.15.0",
37
+ "serialize-javascript": "^6.0.0",
38
+ "yargs": "^17.6.0"
39
+ },
40
+ "devDependencies": {
41
+ "nodemon": "^2.0.15"
42
+ },
43
+ "keywords": [
44
+ "express",
45
+ "node",
46
+ "server",
47
+ "spa"
48
+ ]
49
49
  }
package/tester.js CHANGED
@@ -1,5 +1,5 @@
1
- // Used for development
2
- require("dotenv").config({
3
- path: "./.env.local",
4
- });
5
- require("./index")({});
1
+ // Used for development
2
+ require("dotenv").config({
3
+ path: "./.env.local",
4
+ });
5
+ require("./index")({});
package/CHANGELOG.md DELETED
@@ -1,49 +0,0 @@
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