lupine.api 1.0.41

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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/admin/admin-about.tsx +16 -0
  4. package/admin/admin-config.tsx +44 -0
  5. package/admin/admin-css.tsx +3 -0
  6. package/admin/admin-db.tsx +74 -0
  7. package/admin/admin-frame-props.tsx +9 -0
  8. package/admin/admin-frame.tsx +466 -0
  9. package/admin/admin-index.tsx +66 -0
  10. package/admin/admin-login.tsx +99 -0
  11. package/admin/admin-menu-edit.tsx +637 -0
  12. package/admin/admin-menu-list.tsx +87 -0
  13. package/admin/admin-page-edit.tsx +564 -0
  14. package/admin/admin-page-list.tsx +83 -0
  15. package/admin/admin-performance.tsx +28 -0
  16. package/admin/admin-release.tsx +320 -0
  17. package/admin/admin-resources.tsx +385 -0
  18. package/admin/admin-shell.tsx +89 -0
  19. package/admin/admin-table-data.tsx +146 -0
  20. package/admin/admin-table-list.tsx +231 -0
  21. package/admin/admin-test-animations.tsx +379 -0
  22. package/admin/admin-test-component.tsx +808 -0
  23. package/admin/admin-test-edit.tsx +319 -0
  24. package/admin/admin-test-themes.tsx +56 -0
  25. package/admin/admin-tokens.tsx +338 -0
  26. package/admin/design/admin-design.tsx +174 -0
  27. package/admin/design/block-grid.tsx +36 -0
  28. package/admin/design/block-grid1.tsx +21 -0
  29. package/admin/design/block-paragraph.tsx +19 -0
  30. package/admin/design/block-title.tsx +19 -0
  31. package/admin/design/design-block-box.tsx +140 -0
  32. package/admin/design/drag-data.tsx +24 -0
  33. package/admin/index.ts +6 -0
  34. package/admin/package.json +15 -0
  35. package/admin/tsconfig.json +127 -0
  36. package/dev/copy-folder.js +32 -0
  37. package/dev/cp-index-html.js +69 -0
  38. package/dev/file-utils.js +12 -0
  39. package/dev/index.js +19 -0
  40. package/dev/package.json +12 -0
  41. package/dev/plugin-gen-versions.js +20 -0
  42. package/dev/plugin-ifelse.js +155 -0
  43. package/dev/plugin-ifelse.test.js +37 -0
  44. package/dev/run-cmd.js +14 -0
  45. package/dev/send-request.js +12 -0
  46. package/package.json +55 -0
  47. package/src/admin-api/admin-api.ts +59 -0
  48. package/src/admin-api/admin-auth.ts +87 -0
  49. package/src/admin-api/admin-config.ts +93 -0
  50. package/src/admin-api/admin-csv.ts +81 -0
  51. package/src/admin-api/admin-db.ts +269 -0
  52. package/src/admin-api/admin-helper.ts +111 -0
  53. package/src/admin-api/admin-menu.ts +135 -0
  54. package/src/admin-api/admin-page.ts +135 -0
  55. package/src/admin-api/admin-performance.ts +128 -0
  56. package/src/admin-api/admin-release.ts +498 -0
  57. package/src/admin-api/admin-resources.ts +318 -0
  58. package/src/admin-api/admin-token-helper.ts +79 -0
  59. package/src/admin-api/admin-tokens.ts +90 -0
  60. package/src/admin-api/index.ts +2 -0
  61. package/src/api/api-cache.ts +103 -0
  62. package/src/api/api-helper.ts +44 -0
  63. package/src/api/api-module.ts +60 -0
  64. package/src/api/api-router.ts +177 -0
  65. package/src/api/api-shared-storage.ts +64 -0
  66. package/src/api/async-storage.ts +5 -0
  67. package/src/api/debug-service.ts +56 -0
  68. package/src/api/encode-html.ts +27 -0
  69. package/src/api/handle-status.ts +71 -0
  70. package/src/api/index.ts +16 -0
  71. package/src/api/mini-web-socket.ts +270 -0
  72. package/src/api/server-content-type.ts +82 -0
  73. package/src/api/server-render.ts +216 -0
  74. package/src/api/shell-service.ts +66 -0
  75. package/src/api/simple-storage.ts +80 -0
  76. package/src/api/static-server.ts +125 -0
  77. package/src/api/to-client-delivery.ts +26 -0
  78. package/src/app/app-cache.ts +55 -0
  79. package/src/app/app-loader.ts +62 -0
  80. package/src/app/app-message.ts +60 -0
  81. package/src/app/app-shared-storage.ts +317 -0
  82. package/src/app/app-start.ts +117 -0
  83. package/src/app/cleanup-exit.ts +12 -0
  84. package/src/app/host-to-path.ts +38 -0
  85. package/src/app/index.ts +11 -0
  86. package/src/app/process-dev-requests.ts +90 -0
  87. package/src/app/web-listener.ts +230 -0
  88. package/src/app/web-processor.ts +42 -0
  89. package/src/app/web-server.ts +86 -0
  90. package/src/common-js/web-env.js +104 -0
  91. package/src/index.ts +7 -0
  92. package/src/lang/api-lang-en.ts +27 -0
  93. package/src/lang/api-lang-zh-cn.ts +28 -0
  94. package/src/lang/index.ts +2 -0
  95. package/src/lang/lang-helper.ts +76 -0
  96. package/src/lang/lang-props.ts +6 -0
  97. package/src/lib/db/db-helper.ts +23 -0
  98. package/src/lib/db/db-mysql.ts +250 -0
  99. package/src/lib/db/db-sqlite.ts +101 -0
  100. package/src/lib/db/db.spec.ts +28 -0
  101. package/src/lib/db/db.ts +304 -0
  102. package/src/lib/db/index.ts +5 -0
  103. package/src/lib/index.ts +3 -0
  104. package/src/lib/logger.spec.ts +214 -0
  105. package/src/lib/logger.ts +274 -0
  106. package/src/lib/runtime-require.ts +37 -0
  107. package/src/lib/utils/cookie-util.ts +34 -0
  108. package/src/lib/utils/crypto.ts +58 -0
  109. package/src/lib/utils/date-utils.ts +317 -0
  110. package/src/lib/utils/deep-merge.ts +37 -0
  111. package/src/lib/utils/delay.ts +12 -0
  112. package/src/lib/utils/file-setting.ts +55 -0
  113. package/src/lib/utils/format-bytes.ts +11 -0
  114. package/src/lib/utils/fs-utils.ts +144 -0
  115. package/src/lib/utils/get-env.ts +27 -0
  116. package/src/lib/utils/index.ts +12 -0
  117. package/src/lib/utils/is-type.ts +48 -0
  118. package/src/lib/utils/load-env.ts +14 -0
  119. package/src/lib/utils/pad.ts +6 -0
  120. package/src/models/api-base.ts +5 -0
  121. package/src/models/api-module-props.ts +11 -0
  122. package/src/models/api-router-props.ts +26 -0
  123. package/src/models/app-cache-props.ts +33 -0
  124. package/src/models/app-data-props.ts +10 -0
  125. package/src/models/app-loader-props.ts +6 -0
  126. package/src/models/app-shared-storage-props.ts +37 -0
  127. package/src/models/app-start-props.ts +18 -0
  128. package/src/models/async-storage-props.ts +13 -0
  129. package/src/models/db-config.ts +30 -0
  130. package/src/models/host-to-path-props.ts +12 -0
  131. package/src/models/index.ts +16 -0
  132. package/src/models/json-object.ts +8 -0
  133. package/src/models/locals-props.ts +36 -0
  134. package/src/models/logger-props.ts +84 -0
  135. package/src/models/simple-storage-props.ts +14 -0
  136. package/src/models/to-client-delivery-props.ts +6 -0
  137. package/tsconfig.json +115 -0
@@ -0,0 +1,90 @@
1
+ import { Logger } from '../lib/logger';
2
+ import { ServerResponse } from 'http';
3
+ import { AddressInfo } from 'net';
4
+ import { appLoader } from './app-loader';
5
+ import { DebugService } from '../api/debug-service';
6
+ import { AppCacheGlobal, AppCacheKeys, getAppCache, ServerRequest } from '../models';
7
+ import { cleanupAndExit } from './cleanup-exit';
8
+ const logger = new Logger('process-dev-requests');
9
+
10
+ function deleteRequireCache(moduleName: string) {
11
+ var solvedName = require.resolve(moduleName),
12
+ nodeModule = require.cache[solvedName];
13
+ if (nodeModule) {
14
+ for (var i = 0; i < nodeModule.children.length; i++) {
15
+ var child = nodeModule.children[i];
16
+ deleteRequireCache(child.filename);
17
+ }
18
+ delete require.cache[solvedName];
19
+ }
20
+ }
21
+
22
+ // this is a worker and msg is from Primary
23
+ // when debug is on, it's in primary, but it shouldn't receive those msgs
24
+ export const processDebugMessage = async (msgObject: any) => {
25
+ logger.info(`processDebugMessage, id: ${msgObject && msgObject.id}, message: ${msgObject && msgObject.message}`);
26
+ if (msgObject.id === 'debug' && msgObject.message === 'refresh') {
27
+ if (msgObject.appName) {
28
+ const appConfig = getAppCache().get(msgObject.appName, AppCacheKeys.API_CONFIG);
29
+ appLoader.refreshApi(appConfig);
30
+ } else {
31
+ // refresh all in a worker (app scope)
32
+ let appList = getAppCache().get(AppCacheGlobal, AppCacheKeys.APP_LIST);
33
+ for (const appName of appList) {
34
+ const appConfig = getAppCache().get(appName, AppCacheKeys.API_CONFIG);
35
+ appLoader.refreshApi(appConfig);
36
+ }
37
+ }
38
+
39
+ // TemplateCache should be only used in api scope, so shouldn't clear it here
40
+ // apiCache.clearTemplateCache();
41
+
42
+ // this only works in debug mode (no clusters)
43
+ DebugService.broadcastRefresh();
44
+ }
45
+ if (msgObject.id === 'debug' && msgObject.message === 'suspend') {
46
+ // Only when it's debug mode, it can go here, otherwise suspend should be processed in processMessageFromWorker
47
+ console.log(`[server] Received suspend command.`);
48
+ cleanupAndExit();
49
+ }
50
+ };
51
+
52
+ export async function processRefreshCache(req: ServerRequest) {
53
+ // if this is a child process, we need to notice parent process to broadcast to all clients to refresh
54
+ if (process.send) {
55
+ const appName = req.locals.query.get('appName');
56
+ process.send({ id: 'debug', message: 'refresh', appName });
57
+ }
58
+ // if it's debug mode (only one process)
59
+ else {
60
+ // if (getAppCache().get(APP_GLOBAL, AppCacheKeys.API_DEBUG) === true)
61
+ const appName = req.locals.query.get('appName');
62
+ processDebugMessage({ id: 'debug', message: 'refresh', appName });
63
+ }
64
+ }
65
+
66
+ // this is only for local development
67
+ export async function processDevRequests(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
68
+ res.end();
69
+ const address = req.socket.address() as AddressInfo;
70
+ if (address.address !== '127.0.0.1') {
71
+ console.log(`[server] Ignore request from: `, req.url, address.address);
72
+ return true;
73
+ }
74
+ if (req.url === '/debug/suspend') {
75
+ console.log(`[server] Received suspend command.`);
76
+ if (process.send) {
77
+ // send to parent process to kill all
78
+ process.send({ id: 'debug', message: 'suspend' });
79
+ }
80
+ // if it's debug mode (only one process)
81
+ else if (getAppCache().get(AppCacheGlobal, AppCacheKeys.APP_DEBUG) === true) {
82
+ await processDebugMessage({ id: 'debug', message: 'suspend' });
83
+ }
84
+ } else if (req.url === '/debug/refresh') {
85
+ await processRefreshCache(req);
86
+ }
87
+ if (req.url === '/debug/client') {
88
+ }
89
+ return true;
90
+ }
@@ -0,0 +1,230 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ import { Logger } from '../lib/logger';
3
+ import crypto from 'crypto';
4
+ import { parseCookies } from '../lib/utils/cookie-util';
5
+ import { WebProcessor } from './web-processor';
6
+ import { handler403, handler404, handler500, SimpleStorage } from '../api';
7
+ import { JsonObject, AsyncStorageProps, ServerRequest, SetCookieProps } from '../models';
8
+ import { HostToPath } from './host-to-path';
9
+ import { serializeCookie } from '../lib/utils/cookie-util';
10
+ const logger = new Logger('listener');
11
+
12
+ let MAX_REQUEST_SIZE = 1024 * 1024 * 5;
13
+ export const setMaxRequestSize = (size: number) => {
14
+ MAX_REQUEST_SIZE = size;
15
+ };
16
+
17
+ // The maximum number of requests being processed. If there are no requests for 10 minutes, this number will be reset to 0.
18
+ let MAX_REQUEST_COUNT = 1024 * 1;
19
+ let REQUEST_COUNT = 0;
20
+ export const setMaxRequestCount = (count: number) => {
21
+ MAX_REQUEST_COUNT = count;
22
+ };
23
+ export const getRequestCount = () => {
24
+ return REQUEST_COUNT;
25
+ };
26
+
27
+ let REQUEST_TIMEOUT = 1000 * 30;
28
+ export const setRequestTimeout = (timeout: number) => {
29
+ REQUEST_TIMEOUT = timeout;
30
+ };
31
+
32
+ let accessControlAllowHosts: string[] = [];
33
+ export const setAccessControlAllowHost = (allowHosts: string[]) => {
34
+ accessControlAllowHosts = allowHosts;
35
+ };
36
+
37
+ let SERVER_NAME: string = 'nginx/1.19.2';
38
+ export const setServerName = (serverName: string) => {
39
+ SERVER_NAME = serverName;
40
+ };
41
+
42
+ let lastRequestTime = new Date().getTime();
43
+
44
+ // type ProcessRequest = (req: ServerRequest, res: ServerResponse) => void;
45
+ export class WebListener {
46
+ processor: WebProcessor;
47
+
48
+ constructor(processRequest: WebProcessor) {
49
+ this.processor = processRequest;
50
+ }
51
+
52
+ listener(reqOrigin: IncomingMessage, res: ServerResponse) {
53
+ // If there is no request in the last 10 minutes, reset the request count.
54
+ if (new Date().getTime() - lastRequestTime > 1000 * 60 * 10) {
55
+ if (REQUEST_COUNT != 0) {
56
+ // in case any errors skipped (--REQUEST_COUNT)
57
+ logger.warn(`!!!!!!!!!! ========== REQUEST_COUNT is not counted properly: ${REQUEST_COUNT}`);
58
+ }
59
+ REQUEST_COUNT = 0;
60
+ lastRequestTime = new Date().getTime();
61
+ }
62
+
63
+ if (REQUEST_COUNT > MAX_REQUEST_COUNT) {
64
+ logger.warn(`Too many requests, count: ${REQUEST_COUNT} > ${MAX_REQUEST_COUNT}`);
65
+ handler403(res, 'Too many requests');
66
+ return;
67
+ }
68
+
69
+ // const requestStart = process.hrtime.bigint();
70
+ const uuid = crypto.randomUUID();
71
+ const url = reqOrigin.url || '';
72
+ const requestInfo = `uuid: ${uuid}, Access url: ${url}`;
73
+ const req = reqOrigin as ServerRequest;
74
+
75
+ const host = (req.headers.host || '').split(':')[0]; // req.headers.host contains port
76
+ const hostPath = HostToPath.findHostPath(host);
77
+ if (!hostPath || !hostPath.webPath || !hostPath.appName) {
78
+ const msg = `Web root is not defined properly for host: ${host}.`;
79
+ logger.error(msg);
80
+ handler404(res, msg);
81
+ return true;
82
+ }
83
+
84
+ REQUEST_COUNT++;
85
+ logger.debug(
86
+ `Request started. Count: ${REQUEST_COUNT}, Log uuid: ${uuid}, access: ${
87
+ req.headers.host
88
+ }, url: ${url}, time: ${new Date().toISOString()}, from: ${req.socket.remoteAddress}`
89
+ );
90
+
91
+ const urlSplit = url.split('?');
92
+ req.setTimeout(REQUEST_TIMEOUT);
93
+ req.on('timeout', () => {
94
+ REQUEST_COUNT--;
95
+ logger.warn('timeout');
96
+ req.destroy(new Error('timeout handling'));
97
+ });
98
+
99
+ const jsonFn = (): JsonObject | undefined => {
100
+ if (!req.locals._json && req.locals.body) {
101
+ const sBody = req.locals.body.toString();
102
+ if (!sBody) {
103
+ req.locals._json = undefined;
104
+ } else {
105
+ try {
106
+ req.locals._json = JSON.parse(sBody);
107
+ } catch (err: any) {
108
+ logger.warn(`JSON.parse error: ${err.message}`);
109
+ }
110
+ }
111
+ }
112
+ return req.locals._json;
113
+ };
114
+ const cookiesFn = (): SimpleStorage => {
115
+ if (!req.locals._cookies) {
116
+ req.locals._cookies = new SimpleStorage(req.headers ? parseCookies(req.headers.cookie) : {});
117
+ }
118
+ return req.locals._cookies;
119
+ };
120
+ const setCookieFn = (name: string, value: string, options: SetCookieProps): void => {
121
+ const cookies: string[] = [];
122
+ const cookiesOld = res.getHeader('Set-Cookie');
123
+ if (cookiesOld) {
124
+ if (!Array.isArray(cookiesOld)) {
125
+ cookies.push(cookiesOld as any);
126
+ } else {
127
+ cookies.push(...cookiesOld);
128
+ }
129
+ }
130
+
131
+ const cookiePair = serializeCookie(name, value, options);
132
+ cookies.push(cookiePair);
133
+ res.setHeader('Set-Cookie', cookies);
134
+
135
+ const localCookies = req.locals.cookies();
136
+ localCookies.set(name, value);
137
+ };
138
+
139
+ req.locals = {
140
+ uuid,
141
+ host,
142
+ url,
143
+ hostPath,
144
+ // urlSections: urlSplit[0].split('/').filter((i) => !!i),
145
+ query: new URLSearchParams(urlSplit[1] || ''),
146
+ urlWithoutQuery: urlSplit[0],
147
+ urlParameters: new SimpleStorage({}),
148
+ body: undefined,
149
+ json: jsonFn,
150
+ cookies: cookiesFn,
151
+ setCookie: setCookieFn,
152
+ clearCookie: (name: string) => {
153
+ res.setHeader('Set-Cookie', `${name}=; max-age=0`);
154
+ },
155
+ };
156
+
157
+ let bigRequest = false;
158
+ let totalLength = 0;
159
+ const bodyData: any[] = [];
160
+ req.on('error', (err: any) => {
161
+ REQUEST_COUNT--;
162
+ logger.error(`${requestInfo}, count: ${REQUEST_COUNT}, Request Error: `, err);
163
+ handler500(res, `listener error: ${err && err.message}`);
164
+ });
165
+
166
+ req.on('data', (chunk: any) => {
167
+ totalLength += chunk.length;
168
+ logger.debug(`${requestInfo}, Request data length: ${chunk.length}, total: ${totalLength}`);
169
+ // Limit Request Size
170
+ if (!bigRequest && totalLength < MAX_REQUEST_SIZE) {
171
+ bodyData.push(chunk);
172
+ } else {
173
+ if (!bigRequest) {
174
+ bigRequest = true;
175
+ logger.warn(`Warn, request data is too big: ${totalLength} > ${MAX_REQUEST_SIZE}`);
176
+ }
177
+ req.socket.destroy();
178
+ }
179
+ });
180
+
181
+ req.on('end', async () => {
182
+ try {
183
+ if (bigRequest) {
184
+ logger.warn(`Request data is too big to process, url: ${req.locals.url}`);
185
+ handler403(res, `Request data is too big to process, url: ${req.locals.url}`);
186
+ return;
187
+ }
188
+
189
+ const body = Buffer.concat(bodyData);
190
+ const contentType = req.headers['content-type'];
191
+ logger.debug(`url: ${url}, Request body length: ${body.length}, contentType: ${contentType}`);
192
+ req.locals.body = body;
193
+
194
+ res.setHeader('Server', SERVER_NAME);
195
+ if (accessControlAllowHosts.includes(host)) {
196
+ const allowOrigin = req.headers.origin && req.headers.origin !== 'null' ? req.headers.origin : '*';
197
+ res.setHeader('Access-Control-Allow-Origin', allowOrigin);
198
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
199
+ }
200
+
201
+ const store: AsyncStorageProps = {
202
+ uuid: uuid,
203
+ hostPath: hostPath,
204
+ appName: hostPath.appName,
205
+ locals: req.locals,
206
+ lang: req.locals.cookies().get('lang', 'en') || 'en',
207
+ };
208
+ await this.processor.processRequest(store, req, res);
209
+ } finally {
210
+ REQUEST_COUNT--;
211
+ }
212
+ // await new Promise(resolve => setTimeout(resolve, 3000));
213
+
214
+ // asyncLocalStorage.run(store, async () => {
215
+ // try {
216
+ // await onEnd();
217
+ // } catch (error: any) {
218
+ // logger.error(`url: ${url}, Request end error: `, error.message);
219
+ // }
220
+
221
+ // lastRequestTime = new Date().getTime();
222
+ // const requestEnd = process.hrtime.bigint();
223
+ // REQUEST_COUNT--;
224
+ // logger.debug(
225
+ // `Request finished. Count: ${REQUEST_COUNT}, url: ${url}, time: ${new Date().toISOString()}, duration: ${Number(requestEnd - requestStart) / 1000000} ms`
226
+ // );
227
+ // });
228
+ });
229
+ }
230
+ }
@@ -0,0 +1,42 @@
1
+ import { ServerResponse } from 'http';
2
+ import { Logger } from '../lib/logger';
3
+ import { handler404 } from '../api';
4
+ import { ApiRouterCallback, AppCacheKeys, AsyncStorageProps, getAppCache, ServerRequest } from '../models';
5
+ const logger = new Logger('web-processor');
6
+
7
+ export class WebProcessor {
8
+ static debugPath: string | undefined;
9
+ static debugHandler: ApiRouterCallback | undefined;
10
+
11
+ static enableDebug(path: string, debugHandler: ApiRouterCallback) {
12
+ WebProcessor.debugPath = path;
13
+ WebProcessor.debugHandler = debugHandler;
14
+ }
15
+
16
+ async processRequest(store: AsyncStorageProps, req: ServerRequest, res: ServerResponse) {
17
+ if (WebProcessor.debugPath && req.locals.urlWithoutQuery.startsWith(WebProcessor.debugPath)) {
18
+ if (WebProcessor.debugHandler) {
19
+ await WebProcessor.debugHandler(req, res, req.locals.urlWithoutQuery);
20
+ return true;
21
+ }
22
+ }
23
+
24
+ // check if the request is handled by the api
25
+ try {
26
+ const _lupineApi = getAppCache().get(store.appName, AppCacheKeys.API_MODULE);
27
+ if (_lupineApi && _lupineApi.processApi) {
28
+ const result = await _lupineApi.processApi(store, store.locals.urlWithoutQuery, req, res);
29
+ if (result) {
30
+ return true;
31
+ }
32
+ } else {
33
+ logger.error(`url: ${store.locals.url}, appName: ${store.appName}, no api module found`);
34
+ }
35
+ } catch (e: any) {
36
+ logger.error(`url: ${store.locals.url}, appName: ${store.appName}, process api error: `, e.message);
37
+ }
38
+
39
+ handler404(res, `Request is not processed, url: ${req.locals.url}`);
40
+ return true;
41
+ }
42
+ }
@@ -0,0 +1,86 @@
1
+ import { ServerOptions } from 'https';
2
+ import { Logger } from '../lib/logger';
3
+ import { WebListener } from './web-listener';
4
+ import * as fs from 'fs';
5
+ import * as http from 'http';
6
+ import * as https from 'https';
7
+ import { IncomingMessage } from 'http';
8
+ import { Duplex } from 'stream';
9
+ import { WebProcessor } from './web-processor';
10
+ import { DebugService } from '../api/debug-service';
11
+ const logger = new Logger('web-server');
12
+
13
+ export class WebServer {
14
+ webListener: WebListener;
15
+ constructor(webListener?: WebListener) {
16
+ this.webListener = webListener || new WebListener(new WebProcessor());
17
+ }
18
+
19
+ handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer) {
20
+ const clientIp = `${(socket as any).remoteAddress}:${(socket as any).remotePort}`;
21
+ if (req.url?.startsWith('/debug') && socket.readable && socket.writable) {
22
+ logger.debug(`Upgrade WebSocket access: ${req.url} from ${clientIp}.`);
23
+ DebugService.handleUpgrade(req, socket, head);
24
+ } else {
25
+ logger.error(`Unexpected web socket access: ${req.url} from ${clientIp}`);
26
+ socket.write("HTTP/1.1 404 Not Found\r\n\r\n");
27
+ socket.destroy();
28
+ }
29
+ }
30
+
31
+ startHttp(httpPort: number, bindIp?: string, timeout?: number) {
32
+ const httpServer = http.createServer(this.webListener.listener.bind(this.webListener));
33
+ if (typeof timeout === 'number') httpServer.setTimeout(timeout);
34
+
35
+ httpServer.on('upgrade', this.handleUpgrade.bind(this));
36
+
37
+ httpServer.listen(httpPort, bindIp, () => {
38
+ logger.info(`Http Server is started: http://localhost:${httpPort}`);
39
+ });
40
+ httpServer.on('error', (error: any) => {
41
+ logger.error('Error occurred on http server', error);
42
+ });
43
+ return httpServer;
44
+ }
45
+
46
+ // multi domain https hosting
47
+ // https://stackoverflow.com/questions/38162077/expressjs-multi-domain-https-hosting
48
+ startHttps(httpsPort: number, bindIp?: string, sslKeyPath?: string, sslCrtPath?: string, timeout?: number) {
49
+ const httpsOptions: ServerOptions = {};
50
+ if (sslKeyPath && fs.existsSync(sslKeyPath) && sslCrtPath && fs.existsSync(sslCrtPath)) {
51
+ logger.info('Load site ssl certificate.');
52
+ // The options to https.createServer must include key and cert as they are required.
53
+ // Even though that set won't be used if SNI provides a hostname.
54
+ httpsOptions['key'] = fs.readFileSync(sslKeyPath, 'utf8');
55
+ httpsOptions['cert'] = fs.readFileSync(sslCrtPath, 'utf8');
56
+ // httpsOptions['ca'] = 'pem';
57
+ // httpsOptions['SNICallback'] = function (domain, cb) {
58
+ // if (typeof sites[domain] === "undefined") {
59
+ // cb(new Error("domain not found"), null);
60
+ // console.log("Error: domain not found: " + domain);
61
+
62
+ // } else {
63
+ // cb(null, sites[domain].context);
64
+ // }
65
+ // };
66
+ } else {
67
+ logger.warn(
68
+ `Ssl certificate is not defined or doesn't exist, key file: ${sslKeyPath}, certificate file: ${sslCrtPath}`
69
+ );
70
+ }
71
+
72
+ const httpsServer = https.createServer(httpsOptions, this.webListener.listener.bind(this.webListener));
73
+ httpsServer.on('upgrade', this.handleUpgrade.bind(this));
74
+
75
+ if (typeof timeout === 'number') {
76
+ httpsServer.setTimeout(timeout);
77
+ }
78
+ httpsServer.listen(httpsPort, bindIp, () => {
79
+ logger.info(`Https Server is started: https://localhost:${httpsPort}`);
80
+ });
81
+ httpsServer.on('error', (error: any) => {
82
+ logger.error('Error occurred on https server', error);
83
+ });
84
+ return httpsServer;
85
+ }
86
+ }
@@ -0,0 +1,104 @@
1
+ // This file is defined as CommonJS module, because it's used in dev as well
2
+ const fs = require('fs/promises');
3
+ const path = require('path');
4
+
5
+ exports.readFile = async (filePath) => {
6
+ try {
7
+ const text = await fs.readFile(filePath, 'utf-8');
8
+ return text;
9
+ } catch {
10
+ return undefined;
11
+ }
12
+ };
13
+
14
+ exports.parseEnv = async (envFile) => {
15
+ let obj = {};
16
+ const text = await exports.readFile(envFile);
17
+ if (!text) {
18
+ return obj;
19
+ }
20
+
21
+ let lines = text.replace(/\r\n?/gm, '\n').split('\n');
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i].split(/=(.*)/s);
24
+ const key = line[0].trim();
25
+
26
+ if (key && !key.startsWith('#')) {
27
+ if (key.endsWith('+')) {
28
+ // if the line is like [key+=...] then it will be added to the same key
29
+ obj[key.substring(0, key.length - 1)] += line[1] || '';
30
+ } else if (key.endsWith('*')) {
31
+ // if the line is like [key*=LINE_MARKER] then it's a multiline string, ending with "LINE_MARKER"
32
+ const LINE_MARKER = line[1].trim();
33
+ line[1] = '';
34
+ for (i++; i < lines.length; i++) {
35
+ if (lines[i].trim() === LINE_MARKER) {
36
+ break;
37
+ }
38
+ line[1] += lines[i] + '\n';
39
+ }
40
+ obj[key.substring(0, key.length - 1)] = line[1];
41
+ } else {
42
+ obj[key] = line[1] || ''; // .replace(/\\n/g, '\n').replace(/\\r/g, '\r')
43
+ }
44
+ } else if (key.startsWith('#!import ')) {
45
+ // use "#!import file_name" to import another env file
46
+ const file = key.substring(9).trim();
47
+ const newObj = await exports.parseEnv(path.join(path.dirname(envFile), file));
48
+ obj = Object.assign(obj, newObj);
49
+ }
50
+ }
51
+
52
+ return obj;
53
+ };
54
+
55
+ exports.copyToProcessEnv = (envObject, overrideEnv) => {
56
+ for (const key of Object.keys(envObject)) {
57
+ if (overrideEnv || typeof process.env[key] === 'undefined') {
58
+ process.env[key] = envObject[key];
59
+ }
60
+ }
61
+ };
62
+
63
+ exports.loadEnv = async (envFile, overrideEnv = false) => {
64
+ console.log(`Load env from: ${envFile}`);
65
+ const envObject = await exports.parseEnv(envFile);
66
+ exports.copyToProcessEnv(envObject, overrideEnv);
67
+ };
68
+
69
+ exports.getWebEnv = (appName) => {
70
+ const envWeb = {};
71
+ for (const envName in process.env) {
72
+ if (envName.startsWith(`WEB.`)) {
73
+ envWeb[envName.substring(4)] = process.env[envName];
74
+ } else if (envName.startsWith(`${appName}.WEB.`)) {
75
+ envWeb[envName.substring(appName.length + 5)] = process.env[envName];
76
+ }
77
+ }
78
+ return envWeb;
79
+ };
80
+
81
+ // defined app-shared-storage-props.ts
82
+ const AppSharedStorageWebPrefix = 'WEB.';
83
+ exports.getWebConfig = (allConfig) => {
84
+ const result = {};
85
+ for (let key in allConfig) {
86
+ if (key.startsWith(AppSharedStorageWebPrefix)) {
87
+ result[key.substring(AppSharedStorageWebPrefix.length)] = allConfig[key];
88
+ }
89
+ }
90
+ return result;
91
+ };
92
+
93
+ // // Replace <!--META-ENV-START-->...<!--META-ENV-END--> for mobile app and replace it again for web app
94
+ // exports.replaceWebEnv = (html, appName, addMetaTag) => {
95
+ // const envWeb = exports.getWebEnv(appName);
96
+ // return html.replace(
97
+ // /\<\!--META-ENV-START--\>(.*?)\<\!--META-ENV-END--\>/gm,
98
+ // addMetaTag
99
+ // ? '<!--META-ENV-START--><script id="web-env" type="application/json">' +
100
+ // JSON.stringify(envWeb) +
101
+ // '</script><!--META-ENV-END-->'
102
+ // : '<script id="web-env" type="application/json">' + JSON.stringify(envWeb) + '</script>'
103
+ // );
104
+ // };
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export * from './models';
2
+ export * from './lib';
3
+ export * from './lang';
4
+
5
+ export * from './admin-api';
6
+ export * from './api';
7
+ export * from './app';
@@ -0,0 +1,27 @@
1
+ import { OneLangProps } from './lang-props';
2
+
3
+ export const apiLangEn: OneLangProps = {
4
+ langName: 'en',
5
+ langs: {
6
+ 'shared:operation_success': 'Operation success',
7
+ 'shared:operation_error': 'Operation error',
8
+
9
+ 'shared:wrong_data': 'Wrong data.',
10
+ 'shared:permission_denied': 'Permission denied.',
11
+ 'shared:name_not_existed': '{name} not existed.',
12
+ 'shared:name_not_set': '{name} not set.',
13
+ 'shared:name_is_wrong': '{name} is wrong.',
14
+
15
+ 'shared:login_success': 'Logged in.',
16
+ 'shared:login_failed': 'Login failed.',
17
+ 'shared:not_logged_in': 'Not logged in.',
18
+
19
+ 'shared:user_not_found': 'User not found.',
20
+ 'shared:user_locked': 'User locked.',
21
+
22
+ 'shared:not_found_file': 'File {fileName} is not found.',
23
+
24
+ 'shared:wrong_hash': 'Wrong hash.',
25
+ 'shared:crypto_key_not_set': 'Crypto key [{cryptoKey}] not set',
26
+ },
27
+ };
@@ -0,0 +1,28 @@
1
+ import { OneLangProps } from './lang-props';
2
+
3
+ export const apiLangZhCn: OneLangProps = {
4
+ langName: 'zh-cn',
5
+ langs: {
6
+ 'shared:operation_success': '操作成功',
7
+ 'shared:operation_error': '操作失败',
8
+
9
+ 'shared:wrong_data': '数据错误',
10
+ 'shared:permission_denied': '权限拒绝',
11
+
12
+ 'shared:name_not_existed': '{name} 不存在',
13
+ 'shared:name_not_set': '{name} 未设置',
14
+ 'shared:name_is_wrong': '{name} 是错误的',
15
+
16
+ 'shared:login_success': '登录成功',
17
+ 'shared:login_failed': '登录失败',
18
+ 'shared:not_logged_in': '未登录',
19
+
20
+ 'shared:user_not_found': '用户未找到',
21
+ 'shared:user_locked': '用户已封锁',
22
+
23
+ 'shared:not_found_file': '文件 {fileName} 未找到',
24
+
25
+ 'shared:wrong_hash': '错误的hash',
26
+ 'shared:crypto_key_not_set': 'Crypto key [{cryptoKey}] 未设置',
27
+ },
28
+ };
@@ -0,0 +1,2 @@
1
+ export * from './lang-helper';
2
+ export * from './lang-props';