lupine.api 1.1.58 → 1.1.60

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/README.md +3 -3
  2. package/admin/admin-about.tsx +12 -16
  3. package/admin/admin-config.tsx +47 -44
  4. package/admin/admin-css.tsx +3 -3
  5. package/admin/admin-db.tsx +75 -75
  6. package/admin/admin-frame-helper.tsx +364 -364
  7. package/admin/admin-frame.tsx +164 -164
  8. package/admin/admin-index.tsx +65 -65
  9. package/admin/admin-login.tsx +111 -111
  10. package/admin/admin-menu-edit.tsx +637 -637
  11. package/admin/admin-menu-list.tsx +87 -87
  12. package/admin/admin-page-edit.tsx +564 -564
  13. package/admin/admin-page-list.tsx +83 -83
  14. package/admin/admin-performance.tsx +28 -28
  15. package/admin/admin-release.tsx +427 -426
  16. package/admin/admin-resources.tsx +382 -382
  17. package/admin/admin-shell.tsx +89 -89
  18. package/admin/admin-table-data.tsx +146 -146
  19. package/admin/admin-table-list.tsx +230 -230
  20. package/admin/admin-test-animations.tsx +395 -395
  21. package/admin/admin-test-component.tsx +823 -808
  22. package/admin/admin-test-edit.tsx +319 -319
  23. package/admin/admin-test-themes.tsx +56 -56
  24. package/admin/admin-tokens.tsx +338 -338
  25. package/admin/design/admin-design.tsx +174 -174
  26. package/admin/design/block-grid.tsx +36 -36
  27. package/admin/design/block-grid1.tsx +21 -21
  28. package/admin/design/block-paragraph.tsx +19 -19
  29. package/admin/design/block-title.tsx +19 -19
  30. package/admin/design/design-block-box.tsx +140 -140
  31. package/admin/design/drag-data.tsx +24 -24
  32. package/admin/index.ts +9 -9
  33. package/admin/package.json +15 -15
  34. package/admin/tsconfig.json +127 -127
  35. package/dev/copy-folder.js +32 -32
  36. package/dev/cp-index-html.js +69 -69
  37. package/dev/file-utils.js +12 -12
  38. package/dev/index.js +18 -19
  39. package/dev/package.json +12 -12
  40. package/dev/plugin-ifelse.js +168 -168
  41. package/dev/plugin-ifelse.test.js +37 -37
  42. package/dev/run-cmd.js +14 -14
  43. package/dev/send-request.js +12 -12
  44. package/package.json +55 -55
  45. package/src/admin-api/admin-api-helper.ts +210 -205
  46. package/src/admin-api/admin-api.ts +65 -65
  47. package/src/admin-api/admin-auth.ts +152 -146
  48. package/src/admin-api/admin-config.ts +94 -84
  49. package/src/admin-api/admin-csv.ts +94 -94
  50. package/src/admin-api/admin-db.ts +269 -269
  51. package/src/admin-api/admin-menu.ts +135 -135
  52. package/src/admin-api/admin-page.ts +135 -135
  53. package/src/admin-api/admin-performance.ts +128 -128
  54. package/src/admin-api/admin-release.ts +706 -700
  55. package/src/admin-api/admin-resources.ts +318 -318
  56. package/src/admin-api/admin-token-helper.ts +82 -79
  57. package/src/admin-api/admin-tokens.ts +90 -90
  58. package/src/admin-api/index.ts +2 -2
  59. package/src/admin-api/web-config-api.ts +19 -19
  60. package/src/api/api-cache.ts +103 -103
  61. package/src/api/api-helper.ts +44 -44
  62. package/src/api/api-module.ts +67 -60
  63. package/src/api/api-router.ts +177 -177
  64. package/src/api/api-shared-storage.ts +64 -64
  65. package/src/api/async-storage.ts +5 -5
  66. package/src/api/debug-service.ts +56 -56
  67. package/src/api/encode-html.ts +27 -27
  68. package/src/api/handle-status.ts +75 -75
  69. package/src/api/index.ts +15 -16
  70. package/src/api/mini-web-socket.ts +270 -270
  71. package/src/api/server-content-type.ts +82 -82
  72. package/src/api/server-render.ts +235 -215
  73. package/src/api/shell-service.ts +74 -74
  74. package/src/api/simple-storage.ts +80 -80
  75. package/src/api/static-server.ts +128 -125
  76. package/src/api/to-client-delivery.ts +26 -26
  77. package/src/app/app-cache.ts +55 -55
  78. package/src/app/app-helper.ts +62 -62
  79. package/src/app/app-message.ts +109 -109
  80. package/src/app/app-shared-storage.ts +363 -363
  81. package/src/app/app-start.ts +136 -136
  82. package/src/app/cleanup-exit.ts +16 -16
  83. package/src/app/host-to-path.ts +38 -38
  84. package/src/app/index.ts +11 -11
  85. package/src/app/process-dev-requests.ts +130 -130
  86. package/src/app/web-listener.ts +294 -294
  87. package/src/app/web-processor.ts +47 -42
  88. package/src/app/web-server.ts +100 -100
  89. package/src/common-js/web-env.js +104 -104
  90. package/src/index.ts +7 -7
  91. package/src/lang/api-lang-en.ts +26 -26
  92. package/src/lang/api-lang-zh-cn.ts +27 -27
  93. package/src/lang/index.ts +2 -2
  94. package/src/lang/lang-helper.ts +76 -76
  95. package/src/lang/lang-props.ts +6 -6
  96. package/src/lib/db/db-helper.ts +23 -23
  97. package/src/lib/db/db-mysql.ts +249 -250
  98. package/src/lib/db/db-sqlite.ts +101 -101
  99. package/src/lib/db/db.spec.ts +28 -28
  100. package/src/lib/db/db.ts +325 -325
  101. package/src/lib/db/index.ts +5 -5
  102. package/src/lib/index.ts +3 -3
  103. package/src/lib/logger.spec.ts +214 -214
  104. package/src/lib/logger.ts +281 -281
  105. package/src/lib/runtime-require.ts +37 -37
  106. package/src/lib/utils/cookie-util.ts +34 -34
  107. package/src/lib/utils/crypto.ts +58 -58
  108. package/src/lib/utils/date-utils.ts +317 -317
  109. package/src/lib/utils/deep-merge.ts +37 -37
  110. package/src/lib/utils/delay.ts +12 -12
  111. package/src/lib/utils/file-setting.ts +55 -55
  112. package/src/lib/utils/format-bytes.ts +11 -11
  113. package/src/lib/utils/fs-utils.ts +158 -158
  114. package/src/lib/utils/get-env.ts +27 -27
  115. package/src/lib/utils/index.ts +12 -12
  116. package/src/lib/utils/is-type.ts +48 -48
  117. package/src/lib/utils/load-env.ts +14 -14
  118. package/src/lib/utils/pad.ts +6 -6
  119. package/src/models/api-base.ts +5 -5
  120. package/src/models/api-module-props.ts +10 -11
  121. package/src/models/api-router-props.ts +26 -26
  122. package/src/models/app-cache-props.ts +33 -33
  123. package/src/models/app-data-props.ts +10 -10
  124. package/src/models/app-helper-props.ts +6 -6
  125. package/src/models/app-shared-storage-props.ts +38 -38
  126. package/src/models/app-start-props.ts +18 -18
  127. package/src/models/async-storage-props.ts +13 -13
  128. package/src/models/db-config.ts +30 -30
  129. package/src/models/host-to-path-props.ts +12 -12
  130. package/src/models/index.ts +16 -16
  131. package/src/models/json-object.ts +8 -8
  132. package/src/models/locals-props.ts +36 -36
  133. package/src/models/logger-props.ts +84 -84
  134. package/src/models/simple-storage-props.ts +13 -14
  135. package/src/models/to-client-delivery-props.ts +6 -6
  136. package/tsconfig.json +115 -115
  137. package/dev/plugin-gen-versions.js +0 -20
@@ -1,74 +1,74 @@
1
- import { spawn, exec, ChildProcessWithoutNullStreams } from 'child_process';
2
- import os from 'os';
3
- import { MiniWebSocket } from './mini-web-socket';
4
- import { Duplex } from 'stream';
5
-
6
- // This is only used in debug mode (no clusters)
7
- export class ShellService {
8
- private _shell?: ChildProcessWithoutNullStreams;
9
- private _socket: Duplex;
10
- private _miniWebSocket: MiniWebSocket;
11
-
12
- constructor(socket: Duplex, miniWebSocket: MiniWebSocket) {
13
- this._socket = socket;
14
- this._miniWebSocket = miniWebSocket;
15
- try {
16
- const shellCmd: string = ShellService.getDefaultShell();
17
- this._shell = spawn(shellCmd, [], {
18
- stdio: ['pipe', 'pipe', 'pipe'],
19
- });
20
- } catch (error) {
21
- console.error(error);
22
- this._miniWebSocket.sendMessage(this._socket!, JSON.stringify({ error: error }));
23
- return;
24
- }
25
-
26
- this._shell.stdout.on('data', (data) => {
27
- this._miniWebSocket.sendMessage(this._socket!, data.toString());
28
- });
29
- this._shell.stderr.on('data', (data) => {
30
- this._miniWebSocket.sendMessage(this._socket!, data.toString());
31
- });
32
- this._shell.on('exit', (code, signal) => {
33
- this._miniWebSocket.sendMessage(this._socket!, `Shell exited with code ${code}, signal ${signal}`);
34
- this._shell = undefined;
35
- });
36
- }
37
-
38
- static getDefaultShell() {
39
- const platform = os.platform();
40
- if (platform === 'win32') {
41
- return process.env.COMSPEC || 'cmd.exe';
42
- }
43
- return process.env.SHELL || 'bash';
44
- }
45
-
46
- public stop() {
47
- this._shell?.kill();
48
- this._shell = undefined;
49
- }
50
-
51
- public isRunning() {
52
- return this._shell !== undefined;
53
- }
54
-
55
- public getShell() {
56
- return this._shell;
57
- }
58
-
59
- public cmd(cmd: string) {
60
- if (this._shell && this._shell.stdin.writable) {
61
- this._shell.stdin.write(cmd + '\n');
62
- } else {
63
- this._miniWebSocket.sendMessage(this._socket!, 'Shell is not available.');
64
- }
65
- }
66
-
67
- public static async directCmd(cmd: string): Promise<string> {
68
- return new Promise((resolve) => {
69
- exec(cmd, { shell: this.getDefaultShell() }, (error, stdout, stderr) => {
70
- resolve(stdout + stderr);
71
- });
72
- });
73
- }
74
- }
1
+ import { spawn, exec, ChildProcessWithoutNullStreams } from 'child_process';
2
+ import os from 'os';
3
+ import { MiniWebSocket } from './mini-web-socket';
4
+ import { Duplex } from 'stream';
5
+
6
+ // This is only used in debug mode (no clusters)
7
+ export class ShellService {
8
+ private _shell?: ChildProcessWithoutNullStreams;
9
+ private _socket: Duplex;
10
+ private _miniWebSocket: MiniWebSocket;
11
+
12
+ constructor(socket: Duplex, miniWebSocket: MiniWebSocket) {
13
+ this._socket = socket;
14
+ this._miniWebSocket = miniWebSocket;
15
+ try {
16
+ const shellCmd: string = ShellService.getDefaultShell();
17
+ this._shell = spawn(shellCmd, [], {
18
+ stdio: ['pipe', 'pipe', 'pipe'],
19
+ });
20
+ } catch (error) {
21
+ console.error(error);
22
+ this._miniWebSocket.sendMessage(this._socket!, JSON.stringify({ error: error }));
23
+ return;
24
+ }
25
+
26
+ this._shell.stdout.on('data', (data) => {
27
+ this._miniWebSocket.sendMessage(this._socket!, data.toString());
28
+ });
29
+ this._shell.stderr.on('data', (data) => {
30
+ this._miniWebSocket.sendMessage(this._socket!, data.toString());
31
+ });
32
+ this._shell.on('exit', (code, signal) => {
33
+ this._miniWebSocket.sendMessage(this._socket!, `Shell exited with code ${code}, signal ${signal}`);
34
+ this._shell = undefined;
35
+ });
36
+ }
37
+
38
+ static getDefaultShell() {
39
+ const platform = os.platform();
40
+ if (platform === 'win32') {
41
+ return process.env.COMSPEC || 'cmd.exe';
42
+ }
43
+ return process.env.SHELL || 'bash';
44
+ }
45
+
46
+ public stop() {
47
+ this._shell?.kill();
48
+ this._shell = undefined;
49
+ }
50
+
51
+ public isRunning() {
52
+ return this._shell !== undefined;
53
+ }
54
+
55
+ public getShell() {
56
+ return this._shell;
57
+ }
58
+
59
+ public cmd(cmd: string) {
60
+ if (this._shell && this._shell.stdin.writable) {
61
+ this._shell.stdin.write(cmd + '\n');
62
+ } else {
63
+ this._miniWebSocket.sendMessage(this._socket!, 'Shell is not available.');
64
+ }
65
+ }
66
+
67
+ public static async directCmd(cmd: string): Promise<string> {
68
+ return new Promise((resolve) => {
69
+ exec(cmd, { shell: this.getDefaultShell() }, (error, stdout, stderr) => {
70
+ resolve(stdout + stderr);
71
+ });
72
+ });
73
+ }
74
+ }
@@ -1,80 +1,80 @@
1
- import * as fs from 'fs/promises';
2
- import { ISimpleStorage, SimpleStorageDataProps } from '../models/simple-storage-props';
3
-
4
- // This class is used by both BE and FE (cookie for SSR).
5
- export class SimpleStorage implements ISimpleStorage {
6
- private settings: SimpleStorageDataProps = {};
7
- private dirty: boolean = false;
8
-
9
- constructor(settings: SimpleStorageDataProps) {
10
- this.settings = settings;
11
- }
12
-
13
- setContent(settings: SimpleStorageDataProps) {
14
- this.settings = settings;
15
- this.dirty = true;
16
- }
17
- async saveContent(filePath: string) {
18
- await fs.writeFile(filePath, JSON.stringify(this.settings));
19
- this.dirty = false;
20
- }
21
-
22
- set Dirty(dirty: boolean) {
23
- this.dirty = dirty;
24
- }
25
- get Dirty(): boolean {
26
- return this.dirty;
27
- }
28
-
29
- contains(key: string): boolean {
30
- return key in this.settings;
31
- }
32
- size(): number {
33
- return Object.keys(this.settings).length;
34
- }
35
- set(key: string, value: string) {
36
- this.dirty = true;
37
- if (typeof value === 'undefined') {
38
- delete this.settings[key];
39
- } else {
40
- this.settings[key] = value;
41
- }
42
- }
43
-
44
- getWithPrefix(prefixKey: string): SimpleStorageDataProps {
45
- // get all key startswith prefixKey
46
- const result: SimpleStorageDataProps = {};
47
- for (let key in this.settings) {
48
- if (key.startsWith(prefixKey)) {
49
- result[key] = this.settings[key];
50
- }
51
- }
52
- return result;
53
- }
54
-
55
- get(key: string, defaultValue: string): string {
56
- return key in this.settings ? this.settings[key] : defaultValue;
57
- }
58
- getInt(key: string, defaultValue: number): number {
59
- if (key in this.settings) {
60
- const i = parseInt(this.settings[key]);
61
- if (!isNaN(i)) {
62
- return i;
63
- }
64
- }
65
- return defaultValue;
66
- }
67
- getBoolean(key: string, defaultValue: boolean): boolean {
68
- return key in this.settings
69
- ? this.settings[key] === '1' || this.settings[key].toLowerCase() === 'true'
70
- : defaultValue;
71
- }
72
- getJson(key: string, defaultValue: object): object {
73
- if (key in this.settings) {
74
- try {
75
- return JSON.parse(this.settings[key]);
76
- } catch (error) {}
77
- }
78
- return defaultValue;
79
- }
80
- }
1
+ import * as fs from 'fs/promises';
2
+ import { ISimpleStorage, SimpleStorageDataProps } from '../models/simple-storage-props';
3
+
4
+ // This class is used by both BE and FE (cookie for SSR).
5
+ export class SimpleStorage implements ISimpleStorage {
6
+ private settings: SimpleStorageDataProps = {};
7
+ private dirty: boolean = false;
8
+
9
+ constructor(settings: SimpleStorageDataProps) {
10
+ this.settings = settings;
11
+ }
12
+
13
+ setContent(settings: SimpleStorageDataProps) {
14
+ this.settings = settings;
15
+ this.dirty = true;
16
+ }
17
+ async saveContent(filePath: string) {
18
+ await fs.writeFile(filePath, JSON.stringify(this.settings));
19
+ this.dirty = false;
20
+ }
21
+
22
+ set Dirty(dirty: boolean) {
23
+ this.dirty = dirty;
24
+ }
25
+ get Dirty(): boolean {
26
+ return this.dirty;
27
+ }
28
+
29
+ contains(key: string): boolean {
30
+ return key in this.settings;
31
+ }
32
+ size(): number {
33
+ return Object.keys(this.settings).length;
34
+ }
35
+ set(key: string, value: string) {
36
+ this.dirty = true;
37
+ if (typeof value === 'undefined') {
38
+ delete this.settings[key];
39
+ } else {
40
+ this.settings[key] = value;
41
+ }
42
+ }
43
+
44
+ getWithPrefix(prefixKey: string): SimpleStorageDataProps {
45
+ // get all key startswith prefixKey
46
+ const result: SimpleStorageDataProps = {};
47
+ for (let key in this.settings) {
48
+ if (key.startsWith(prefixKey)) {
49
+ result[key] = this.settings[key];
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+
55
+ get(key: string, defaultValue: string): string {
56
+ return key in this.settings ? this.settings[key] : defaultValue;
57
+ }
58
+ getInt(key: string, defaultValue: number): number {
59
+ if (key in this.settings) {
60
+ const i = parseInt(this.settings[key]);
61
+ if (!isNaN(i)) {
62
+ return i;
63
+ }
64
+ }
65
+ return defaultValue;
66
+ }
67
+ getBoolean(key: string, defaultValue: boolean): boolean {
68
+ return key in this.settings
69
+ ? this.settings[key] === '1' || this.settings[key].toLowerCase() === 'true'
70
+ : defaultValue;
71
+ }
72
+ getJson(key: string, defaultValue: object): object {
73
+ if (key in this.settings) {
74
+ try {
75
+ return JSON.parse(this.settings[key]);
76
+ } catch (error) {}
77
+ }
78
+ return defaultValue;
79
+ }
80
+ }
@@ -1,125 +1,128 @@
1
- // const request = require('request');
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import { ServerResponse } from 'http';
5
- import { Logger } from '../lib';
6
- import { ServerRequest } from '../models/locals-props';
7
- import { handler200, handler404, handler500 } from './handle-status';
8
- import { isServerSideRenderUrl, serverSideRenderPage } from './server-render';
9
- import { serverContentType } from './server-content-type';
10
- import { apiCache } from './api-cache';
11
-
12
- export class StaticServer {
13
- logger = new Logger('StaticServer');
14
-
15
- private async sendFile(realPath: string, requestPath: string, res: ServerResponse) {
16
- try {
17
- // const text = fs.readFileSync(realPath);
18
- // createReadStream has default autoClose(true)
19
- // https://nodejs.org/api/fs.html#fscreatereadstreampath-options
20
- const fileStream = fs.createReadStream(realPath);
21
- fileStream.on('error', (error) => {
22
- this.logger.warn(`File not found: ${realPath}`);
23
- handler404(res);
24
- return true;
25
- });
26
- fileStream.on('open', () => {
27
- let ext = path.extname(realPath);
28
- ext = ext ? ext.slice(1) : 'unknown';
29
- const contentType = serverContentType[ext] || 'application/octet-stream';
30
- res.writeHead(200, {
31
- 'Content-Type': contentType + '; charset=UTF-8',
32
- });
33
- });
34
- fileStream.on('end', function () {
35
- res.end();
36
- });
37
- // res.write(text);
38
- // res.end();
39
- fileStream.pipe(res);
40
-
41
- return true;
42
- } catch (err: any) {
43
- if (err.code === 'ENOENT') {
44
- this.logger.warn(`File not found: ${realPath}`);
45
- handler200(res, `File not found: ${requestPath}`);
46
- } else {
47
- this.logger.error(`Error for: ${realPath}`, err);
48
- handler200(res, 'Service failed: ' + err.message);
49
- }
50
- return true;
51
- }
52
- }
53
-
54
- async processRequest(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
55
- this.logger.info(`StaticServer, url: ${req.locals.url}, host: ${req.locals.host}, rootUrl: ${rootUrl}`);
56
-
57
- const hostPath = apiCache.getAsyncStore().hostPath;
58
- const urlSplit = (rootUrl || req.locals.urlWithoutQuery).split('?');
59
- const fullPath = path.join(hostPath.webPath, urlSplit[0]);
60
-
61
- const jumpToServerSideRender = () => {
62
- const error = new Error();
63
- (error as any).code = 'ENOENT';
64
- // jump to serverSideRenderPage
65
- throw error;
66
- };
67
- try {
68
- if (urlSplit[0] === '/' || urlSplit[0] === '/index.html') {
69
- jumpToServerSideRender();
70
- }
71
-
72
- // if fullPath doesn't exist, it will throw ENOENT error
73
- const realPath = await fs.promises.realpath(fullPath);
74
- console.log(`${process.pid} - request: ${realPath}`);
75
- // for security reason, the requested file should be inside of wwwRoot
76
- if (realPath.substring(0, hostPath.webPath.length) !== hostPath.webPath) {
77
- this.logger.warn(`ACCESS DENIED: ${urlSplit[0]}`);
78
- handler200(res, `ACCESS DENIED: ${urlSplit[0]}`);
79
- return true;
80
- }
81
-
82
- let finalPath = '';
83
- if ((await fs.promises.lstat(realPath)).isDirectory()) {
84
- if ((await fs.promises.lstat(path.join(realPath, 'index.js'))).isFile()) {
85
- // because it's directory, it means index.html, and if it has index.js, it will jump to serverSideRenderPage
86
- jumpToServerSideRender();
87
- }
88
- // if index.js doesn't exist, it will send index.html
89
- finalPath = path.join(realPath, 'index.html');
90
- } else {
91
- // it's a file, and if it's index.html and the same directory has index.js, it will jump to serverSideRenderPage
92
- if (realPath.endsWith('/index.html') && (await fs.promises.lstat(path.join(path.dirname(realPath), 'index.js'))).isFile()) {
93
- jumpToServerSideRender();
94
- }
95
- finalPath = realPath;
96
- }
97
-
98
- // now we need to send finalPath file. If finalPath doesn't exist, it will cause error and jump to serverSideRenderPage
99
- try {
100
- const allowOrigin = (req.headers.origin && req.headers.origin !== 'null') ? req.headers.origin : '*';
101
- res.setHeader('Access-Control-Allow-Origin', allowOrigin);
102
-
103
- await this.sendFile(finalPath, urlSplit[0], res);
104
- } catch (err: any) {
105
- this.logger.warn(`File not found: ${urlSplit[0]}`);
106
- handler200(res, `File not found: ${urlSplit[0]}`);
107
- }
108
- return true;
109
- } catch (err: any) {
110
- // file doesn't exist
111
- if (err.code === 'ENOENT') {
112
- if (isServerSideRenderUrl(urlSplit[0])) {
113
- serverSideRenderPage(hostPath.appName, hostPath.webPath, urlSplit[0], urlSplit[1], req, res);
114
- } else {
115
- this.logger.error(`File not found: ${urlSplit[0]}`);
116
- handler404(res, `File not found: ${urlSplit[0]}`);
117
- }
118
- } else {
119
- this.logger.error(`Error for: ${urlSplit[0]}`, err);
120
- handler500(res, `processRequest error: ${err.message}`);
121
- }
122
- return true;
123
- }
124
- }
125
- }
1
+ // const request = require('request');
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { ServerResponse } from 'http';
5
+ import { Logger } from '../lib';
6
+ import { ServerRequest } from '../models/locals-props';
7
+ import { handler200, handler404, handler500 } from './handle-status';
8
+ import { isServerSideRenderUrl, serverSideRenderPage } from './server-render';
9
+ import { serverContentType } from './server-content-type';
10
+ import { apiCache } from './api-cache';
11
+
12
+ export class StaticServer {
13
+ logger = new Logger('StaticServer');
14
+
15
+ private async sendFile(realPath: string, requestPath: string, res: ServerResponse) {
16
+ try {
17
+ // const text = fs.readFileSync(realPath);
18
+ // createReadStream has default autoClose(true)
19
+ // https://nodejs.org/api/fs.html#fscreatereadstreampath-options
20
+ const fileStream = fs.createReadStream(realPath);
21
+ fileStream.on('error', (error) => {
22
+ this.logger.warn(`File not found: ${realPath}`);
23
+ handler404(res);
24
+ return true;
25
+ });
26
+ fileStream.on('open', () => {
27
+ let ext = path.extname(realPath);
28
+ ext = ext ? ext.slice(1) : 'unknown';
29
+ const contentType = serverContentType[ext] || 'application/octet-stream';
30
+ res.writeHead(200, {
31
+ 'Content-Type': contentType + '; charset=UTF-8',
32
+ });
33
+ });
34
+ fileStream.on('end', function () {
35
+ res.end();
36
+ });
37
+ // res.write(text);
38
+ // res.end();
39
+ fileStream.pipe(res);
40
+
41
+ return true;
42
+ } catch (err: any) {
43
+ if (err.code === 'ENOENT') {
44
+ this.logger.warn(`File not found: ${realPath}`);
45
+ handler200(res, `File not found: ${requestPath}`);
46
+ } else {
47
+ this.logger.error(`Error for: ${realPath}`, err);
48
+ handler200(res, 'Service failed: ' + err.message);
49
+ }
50
+ return true;
51
+ }
52
+ }
53
+
54
+ async processRequest(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
55
+ this.logger.info(`StaticServer, url: ${req.locals.url}, host: ${req.locals.host}, rootUrl: ${rootUrl}`);
56
+
57
+ const hostPath = apiCache.getAsyncStore().hostPath;
58
+ const urlSplit = (rootUrl || req.locals.urlWithoutQuery).split('?');
59
+ const fullPath = path.join(hostPath.webPath, urlSplit[0]);
60
+
61
+ const jumpToServerSideRender = () => {
62
+ const error = new Error();
63
+ (error as any).code = 'ENOENT';
64
+ // jump to serverSideRenderPage
65
+ throw error;
66
+ };
67
+ try {
68
+ if (urlSplit[0] === '/' || urlSplit[0] === '/index.html') {
69
+ jumpToServerSideRender();
70
+ }
71
+
72
+ // if fullPath doesn't exist, it will throw ENOENT error
73
+ const realPath = await fs.promises.realpath(fullPath);
74
+ console.log(`${process.pid} - request: ${realPath}`);
75
+ // for security reason, the requested file should be inside of wwwRoot
76
+ if (realPath.substring(0, hostPath.webPath.length) !== hostPath.webPath) {
77
+ this.logger.warn(`ACCESS DENIED: ${urlSplit[0]}`);
78
+ handler200(res, `ACCESS DENIED: ${urlSplit[0]}`);
79
+ return true;
80
+ }
81
+
82
+ let finalPath = '';
83
+ if ((await fs.promises.lstat(realPath)).isDirectory()) {
84
+ if ((await fs.promises.lstat(path.join(realPath, 'index.js'))).isFile()) {
85
+ // because it's directory, it means index.html, and if it has index.js, it will jump to serverSideRenderPage
86
+ jumpToServerSideRender();
87
+ }
88
+ // if index.js doesn't exist, it will send index.html
89
+ finalPath = path.join(realPath, 'index.html');
90
+ } else {
91
+ // it's a file, and if it's index.html and the same directory has index.js, it will jump to serverSideRenderPage
92
+ if (
93
+ realPath.endsWith('/index.html') &&
94
+ (await fs.promises.lstat(path.join(path.dirname(realPath), 'index.js'))).isFile()
95
+ ) {
96
+ jumpToServerSideRender();
97
+ }
98
+ finalPath = realPath;
99
+ }
100
+
101
+ // now we need to send finalPath file. If finalPath doesn't exist, it will cause error and jump to serverSideRenderPage
102
+ try {
103
+ const allowOrigin = req.headers.origin && req.headers.origin !== 'null' ? req.headers.origin : '*';
104
+ res.setHeader('Access-Control-Allow-Origin', allowOrigin);
105
+
106
+ await this.sendFile(finalPath, urlSplit[0], res);
107
+ } catch (err: any) {
108
+ this.logger.warn(`File not found: ${urlSplit[0]}`);
109
+ handler200(res, `File not found: ${urlSplit[0]}`);
110
+ }
111
+ return true;
112
+ } catch (err: any) {
113
+ // file doesn't exist
114
+ if (err.code === 'ENOENT') {
115
+ if (isServerSideRenderUrl(urlSplit[0])) {
116
+ serverSideRenderPage(hostPath.appName, hostPath.webPath, urlSplit[0], urlSplit[1], req, res);
117
+ } else {
118
+ this.logger.error(`File not found: ${urlSplit[0]}`);
119
+ handler404(res, `File not found: ${urlSplit[0]}`);
120
+ }
121
+ } else {
122
+ this.logger.error(`Error for: ${urlSplit[0]}`, err);
123
+ handler500(res, `processRequest error: ${err.message}`);
124
+ }
125
+ return true;
126
+ }
127
+ }
128
+ }
@@ -1,26 +1,26 @@
1
- import { IToClientDelivery } from '../models/to-client-delivery-props';
2
- import { ISimpleStorage } from '../models/simple-storage-props';
3
-
4
- export class ToClientDelivery implements IToClientDelivery {
5
- private webEnv: { [k: string]: string };
6
- private webSetting: { [k: string]: string };
7
- private cookies: ISimpleStorage;
8
-
9
- constructor(webEnv: { [k: string]: string }, webSetting: { [k: string]: string }, cookies: ISimpleStorage) {
10
- this.webEnv = webEnv;
11
- this.webSetting = webSetting;
12
- this.cookies = cookies;
13
- }
14
-
15
- public getWebEnv() {
16
- return this.webEnv;
17
- }
18
-
19
- public getWebSetting() {
20
- return this.webSetting;
21
- }
22
-
23
- public getServerCookie() {
24
- return this.cookies;
25
- }
26
- }
1
+ import { IToClientDelivery } from '../models/to-client-delivery-props';
2
+ import { ISimpleStorage } from '../models/simple-storage-props';
3
+
4
+ export class ToClientDelivery implements IToClientDelivery {
5
+ private webEnv: { [k: string]: string };
6
+ private webSetting: { [k: string]: string };
7
+ private cookies: ISimpleStorage;
8
+
9
+ constructor(webEnv: { [k: string]: string }, webSetting: { [k: string]: string }, cookies: ISimpleStorage) {
10
+ this.webEnv = webEnv;
11
+ this.webSetting = webSetting;
12
+ this.cookies = cookies;
13
+ }
14
+
15
+ public getWebEnv() {
16
+ return this.webEnv;
17
+ }
18
+
19
+ public getWebSetting() {
20
+ return this.webSetting;
21
+ }
22
+
23
+ public getServerCookie() {
24
+ return this.cookies;
25
+ }
26
+ }