@proteinjs/server 1.1.1 → 1.1.3
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/.eslintrc.js +20 -0
- package/.prettierignore +3 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +11 -44
- package/LICENSE +21 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/jest.config.d.ts +1 -1
- package/dist/jest.config.js +6 -15
- package/dist/jest.config.js.map +1 -1
- package/dist/src/NodeSessionDataStorage.d.ts.map +1 -1
- package/dist/src/NodeSessionDataStorage.js +13 -7
- package/dist/src/NodeSessionDataStorage.js.map +1 -1
- package/dist/src/loadRoutes.d.ts.map +1 -1
- package/dist/src/loadRoutes.js +14 -7
- package/dist/src/loadRoutes.js.map +1 -1
- package/dist/src/routes/healthCheck.d.ts.map +1 -1
- package/dist/src/routes/healthCheck.js +1 -1
- package/dist/src/routes/healthCheck.js.map +1 -1
- package/dist/src/routes/reactApp.d.ts.map +1 -1
- package/dist/src/routes/reactApp.js +7 -4
- package/dist/src/routes/reactApp.js.map +1 -1
- package/dist/src/startServer.d.ts.map +1 -1
- package/dist/src/startServer.js +25 -12
- package/dist/src/startServer.js.map +1 -1
- package/dist/webpack.config.js +15 -15
- package/dist/webpack.config.js.map +1 -1
- package/generated/index.ts +5 -8
- package/index.ts +1 -1
- package/jest.config.js +8 -17
- package/package.json +76 -70
- package/src/NodeSessionDataStorage.ts +35 -29
- package/src/loadRoutes.ts +124 -103
- package/src/nodeModulesPath.ts +1 -1
- package/src/routes/healthCheck.ts +7 -7
- package/src/routes/reactApp.ts +43 -36
- package/src/startServer.ts +164 -132
- package/tsconfig.json +17 -17
- package/webpack.config.js +20 -20
package/src/loadRoutes.ts
CHANGED
|
@@ -1,126 +1,147 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import passport from 'passport';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ServerConfig,
|
|
5
|
+
Route,
|
|
6
|
+
getRequestListeners,
|
|
7
|
+
Session,
|
|
8
|
+
SessionData,
|
|
9
|
+
getSessionDataCaches,
|
|
10
|
+
} from '@proteinjs/server-api';
|
|
4
11
|
import { createReactApp } from './routes/reactApp';
|
|
5
12
|
import { Logger } from '@proteinjs/util';
|
|
6
13
|
|
|
7
14
|
const logger = new Logger('Server');
|
|
8
15
|
|
|
9
16
|
export function loadRoutes(routes: Route[], server: express.Express, config: ServerConfig) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (route.path.includes('*')) {
|
|
20
|
-
wildcardRoutes.push(route);
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
server[route.method](getPath(route.path), wrapRoute(route.onRequest.bind(route), config));
|
|
17
|
+
let starRoute: Route | null = null;
|
|
18
|
+
const wildcardRoutes: Route[] = [];
|
|
19
|
+
for (const route of routes) {
|
|
20
|
+
logger.info(`Loading route: ${route.path}`);
|
|
21
|
+
if (route.path == '*') {
|
|
22
|
+
starRoute = route;
|
|
23
|
+
continue;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if (route.path.includes('*')) {
|
|
27
|
+
wildcardRoutes.push(route);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
server[route.method](getPath(route.path), wrapRoute(route.onRequest.bind(route), config));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const wildcardRoute of wildcardRoutes) {
|
|
35
|
+
server[wildcardRoute.method](
|
|
36
|
+
getPath(wildcardRoute.path),
|
|
37
|
+
wrapRoute(wildcardRoute.onRequest.bind(wildcardRoute), config)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
if (starRoute) {
|
|
42
|
+
server[starRoute.method](starRoute.path, wrapRoute(starRoute.onRequest.bind(starRoute), config));
|
|
43
|
+
}
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
export function loadDefaultStarRoute(routes: Route[], server: express.Express, config: ServerConfig) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!starRouteSpecified && (config.staticContent?.bundlePaths || config.staticContent?.bundlesDir)) {
|
|
44
|
-
const reactApp = createReactApp(config);
|
|
45
|
-
server[reactApp.method](reactApp.path, wrapRoute(reactApp.onRequest, config));
|
|
47
|
+
let starRouteSpecified = false;
|
|
48
|
+
for (const route of routes) {
|
|
49
|
+
if (route.path == '*') {
|
|
50
|
+
starRouteSpecified = true;
|
|
51
|
+
break;
|
|
46
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!starRouteSpecified && (config.staticContent?.bundlePaths || config.staticContent?.bundlesDir)) {
|
|
56
|
+
const reactApp = createReactApp(config);
|
|
57
|
+
server[reactApp.method](reactApp.path, wrapRoute(reactApp.onRequest, config));
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
function getPath(path: string) {
|
|
50
|
-
|
|
62
|
+
return path.startsWith('/') ? path : `/${path}`;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
|
-
function wrapRoute(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
65
|
+
function wrapRoute(
|
|
66
|
+
route: (request: express.Request, response: express.Response) => Promise<void>,
|
|
67
|
+
config: ServerConfig
|
|
68
|
+
) {
|
|
69
|
+
return async function (request: express.Request, response: express.Response, next: express.NextFunction) {
|
|
70
|
+
if (response.locals['responseHandled']) {
|
|
71
|
+
next();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (config.authenticate) {
|
|
76
|
+
await new Promise<void>((resolve, reject) => {
|
|
77
|
+
passport.authenticate('local', function (err, user, info) {
|
|
78
|
+
if (err) {
|
|
79
|
+
reject(err);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resolve();
|
|
83
|
+
})(request, response, next);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sessionData: SessionData = { sessionId: request.sessionID, user: request.user as string, data: {} };
|
|
88
|
+
for (const sessionDataCache of getSessionDataCaches()) {
|
|
89
|
+
sessionData.data[sessionDataCache.key] = await sessionDataCache.create(sessionData.sessionId, sessionData.user);
|
|
90
|
+
}
|
|
91
|
+
Session.setData(sessionData);
|
|
92
|
+
|
|
93
|
+
const requestListeners = getRequestListeners();
|
|
94
|
+
for (const listener of requestListeners) {
|
|
95
|
+
if (!listener.beforeRequest) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await listener.beforeRequest(request, response);
|
|
101
|
+
} catch (error: any) {
|
|
102
|
+
logger.error(`Caught error when running listener before request`, error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const sixtyMinutes = 1000 * 60 * 60;
|
|
107
|
+
const timeout = typeof config.request?.timeoutMs !== 'undefined' ? config.request.timeoutMs : sixtyMinutes;
|
|
108
|
+
request.setTimeout(timeout, () => {
|
|
109
|
+
if (response.locals.requestNumber) {
|
|
110
|
+
logger.warn(`[#${response.locals.requestNumber}] Timed out ${request.originalUrl}`);
|
|
111
|
+
} else {
|
|
112
|
+
logger.warn(`Timed out ${request.originalUrl}`);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await route(request, response);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(error);
|
|
120
|
+
}
|
|
121
|
+
response.locals['responseHandled'] = true;
|
|
122
|
+
|
|
123
|
+
for (const listener of requestListeners) {
|
|
124
|
+
if (!listener.afterRequest) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await listener.afterRequest(request, response);
|
|
130
|
+
} catch (error: any) {
|
|
131
|
+
logger.error(`Caught error when running listener after request`, error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
next();
|
|
136
|
+
};
|
|
117
137
|
}
|
|
118
138
|
|
|
119
|
-
function basicAuthCredentials(request: express.Request): { username: string
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
139
|
+
function basicAuthCredentials(request: express.Request): { username: string; password: string } | null {
|
|
140
|
+
const b64auth = (request.headers.authorization || '').split(' ')[1] || '';
|
|
141
|
+
const [username, password] = Buffer.from(b64auth, 'base64').toString().split(':');
|
|
142
|
+
if (!username || !password) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
}
|
|
146
|
+
return { username, password };
|
|
147
|
+
}
|
package/src/nodeModulesPath.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Route } from '@proteinjs/server-api';
|
|
2
2
|
|
|
3
3
|
export const healthCheck: Route = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
4
|
+
path: 'health-check',
|
|
5
|
+
method: 'get',
|
|
6
|
+
useHttp: true,
|
|
7
|
+
onRequest: async (request, response): Promise<void> => {
|
|
8
|
+
response.send();
|
|
9
|
+
},
|
|
10
|
+
};
|
package/src/routes/reactApp.ts
CHANGED
|
@@ -4,18 +4,20 @@ import { ServerConfig, getServerRenderedScripts } from '@proteinjs/server-api';
|
|
|
4
4
|
import { Fs } from '@proteinjs/util-node';
|
|
5
5
|
|
|
6
6
|
export const createReactApp = (serverConfig: ServerConfig) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
return {
|
|
8
|
+
path: '*',
|
|
9
|
+
method: 'get' as 'get',
|
|
10
|
+
onRequest: async (request: any, response: any): Promise<void> => {
|
|
11
|
+
if (request.path.startsWith('/static')) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
if (!(serverConfig.staticContent?.bundlePaths || serverConfig.staticContent?.bundlesDir)) {
|
|
16
|
+
throw new Error(`ServerConfig.bundlePath or ServerConfig.bundlesDir must be provided to serve a react app`);
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
const helmet = ReactHelmet.renderStatic();
|
|
20
|
+
response.send(`<!DOCTYPE html>
|
|
19
21
|
<html ${helmet.htmlAttributes}>
|
|
20
22
|
<head>
|
|
21
23
|
<meta charset='utf-8' />
|
|
@@ -31,37 +33,42 @@ export const createReactApp = (serverConfig: ServerConfig) => {
|
|
|
31
33
|
${await serverRenderedScriptTags()}
|
|
32
34
|
${await bundleScriptTags(serverConfig)}
|
|
33
35
|
</body>
|
|
34
|
-
</html>`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
36
|
+
</html>`);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
39
40
|
|
|
40
41
|
async function bundleScriptTags(serverConfig: ServerConfig) {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (!(serverConfig.staticContent?.bundlePaths || serverConfig.staticContent?.bundlesDir)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
const scriptTags: string[] = [];
|
|
47
|
+
if (serverConfig.staticContent?.bundlePaths) {
|
|
48
|
+
for (const bundlePath of serverConfig.staticContent.bundlePaths) {
|
|
49
|
+
scriptTags.push(`<script src='${path.join('/static/', bundlePath)}'></script>`);
|
|
50
|
+
}
|
|
51
|
+
} else if (serverConfig.staticContent?.bundlesDir && serverConfig.staticContent?.staticContentDir) {
|
|
52
|
+
const resolvedBundlesDir = path.join(
|
|
53
|
+
serverConfig.staticContent.staticContentDir,
|
|
54
|
+
serverConfig.staticContent.bundlesDir
|
|
55
|
+
);
|
|
56
|
+
const filePaths = await Fs.getFilePathsMatchingGlob(resolvedBundlesDir, '**/*.js');
|
|
57
|
+
for (const filePath of filePaths) {
|
|
58
|
+
const relativePath = path.relative(serverConfig.staticContent.staticContentDir, filePath);
|
|
59
|
+
scriptTags.push(`<script src='${path.join('/static/', relativePath)}'></script>`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
return scriptTags.join('\n');
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
async function serverRenderedScriptTags() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
const scripts = getServerRenderedScripts();
|
|
68
|
+
const scriptTags: string[] = [];
|
|
69
|
+
for (const script of scripts) {
|
|
70
|
+
scriptTags.push(`<script>${await script.script()}</script>`);
|
|
71
|
+
}
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
}
|
|
73
|
+
return scriptTags.join('\n');
|
|
74
|
+
}
|