lupine.api 1.1.58 → 1.1.59
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 +3 -3
- package/admin/admin-about.tsx +12 -16
- package/admin/admin-config.tsx +47 -44
- package/admin/admin-css.tsx +3 -3
- package/admin/admin-db.tsx +75 -75
- package/admin/admin-frame-helper.tsx +364 -364
- package/admin/admin-frame.tsx +164 -164
- package/admin/admin-index.tsx +65 -65
- package/admin/admin-login.tsx +111 -111
- package/admin/admin-menu-edit.tsx +637 -637
- package/admin/admin-menu-list.tsx +87 -87
- package/admin/admin-page-edit.tsx +564 -564
- package/admin/admin-page-list.tsx +83 -83
- package/admin/admin-performance.tsx +28 -28
- package/admin/admin-release.tsx +427 -426
- package/admin/admin-resources.tsx +382 -382
- package/admin/admin-shell.tsx +89 -89
- package/admin/admin-table-data.tsx +146 -146
- package/admin/admin-table-list.tsx +230 -230
- package/admin/admin-test-animations.tsx +395 -395
- package/admin/admin-test-component.tsx +823 -808
- package/admin/admin-test-edit.tsx +319 -319
- package/admin/admin-test-themes.tsx +56 -56
- package/admin/admin-tokens.tsx +338 -338
- package/admin/design/admin-design.tsx +174 -174
- package/admin/design/block-grid.tsx +36 -36
- package/admin/design/block-grid1.tsx +21 -21
- package/admin/design/block-paragraph.tsx +19 -19
- package/admin/design/block-title.tsx +19 -19
- package/admin/design/design-block-box.tsx +140 -140
- package/admin/design/drag-data.tsx +24 -24
- package/admin/index.ts +9 -9
- package/admin/package.json +15 -15
- package/admin/tsconfig.json +127 -127
- package/dev/copy-folder.js +32 -32
- package/dev/cp-index-html.js +69 -69
- package/dev/file-utils.js +12 -12
- package/dev/index.js +18 -19
- package/dev/package.json +12 -12
- package/dev/plugin-ifelse.js +168 -168
- package/dev/plugin-ifelse.test.js +37 -37
- package/dev/run-cmd.js +14 -14
- package/dev/send-request.js +12 -12
- package/package.json +55 -55
- package/src/admin-api/admin-api-helper.ts +210 -205
- package/src/admin-api/admin-api.ts +65 -65
- package/src/admin-api/admin-auth.ts +152 -146
- package/src/admin-api/admin-config.ts +94 -84
- package/src/admin-api/admin-csv.ts +94 -94
- package/src/admin-api/admin-db.ts +269 -269
- package/src/admin-api/admin-menu.ts +135 -135
- package/src/admin-api/admin-page.ts +135 -135
- package/src/admin-api/admin-performance.ts +128 -128
- package/src/admin-api/admin-release.ts +703 -700
- package/src/admin-api/admin-resources.ts +318 -318
- package/src/admin-api/admin-token-helper.ts +82 -79
- package/src/admin-api/admin-tokens.ts +90 -90
- package/src/admin-api/index.ts +2 -2
- package/src/admin-api/web-config-api.ts +19 -19
- package/src/api/api-cache.ts +103 -103
- package/src/api/api-helper.ts +44 -44
- package/src/api/api-module.ts +67 -60
- package/src/api/api-router.ts +177 -177
- package/src/api/api-shared-storage.ts +64 -64
- package/src/api/async-storage.ts +5 -5
- package/src/api/debug-service.ts +56 -56
- package/src/api/encode-html.ts +27 -27
- package/src/api/handle-status.ts +75 -75
- package/src/api/index.ts +15 -16
- package/src/api/mini-web-socket.ts +270 -270
- package/src/api/server-content-type.ts +82 -82
- package/src/api/server-render.ts +235 -215
- package/src/api/shell-service.ts +74 -74
- package/src/api/simple-storage.ts +80 -80
- package/src/api/static-server.ts +128 -125
- package/src/api/to-client-delivery.ts +26 -26
- package/src/app/app-cache.ts +55 -55
- package/src/app/app-helper.ts +62 -62
- package/src/app/app-message.ts +109 -109
- package/src/app/app-shared-storage.ts +363 -363
- package/src/app/app-start.ts +136 -136
- package/src/app/cleanup-exit.ts +16 -16
- package/src/app/host-to-path.ts +38 -38
- package/src/app/index.ts +11 -11
- package/src/app/process-dev-requests.ts +130 -130
- package/src/app/web-listener.ts +294 -294
- package/src/app/web-processor.ts +47 -42
- package/src/app/web-server.ts +100 -100
- package/src/common-js/web-env.js +104 -104
- package/src/index.ts +7 -7
- package/src/lang/api-lang-en.ts +26 -26
- package/src/lang/api-lang-zh-cn.ts +27 -27
- package/src/lang/index.ts +2 -2
- package/src/lang/lang-helper.ts +76 -76
- package/src/lang/lang-props.ts +6 -6
- package/src/lib/db/db-helper.ts +23 -23
- package/src/lib/db/db-mysql.ts +249 -250
- package/src/lib/db/db-sqlite.ts +101 -101
- package/src/lib/db/db.spec.ts +28 -28
- package/src/lib/db/db.ts +325 -325
- package/src/lib/db/index.ts +5 -5
- package/src/lib/index.ts +3 -3
- package/src/lib/logger.spec.ts +214 -214
- package/src/lib/logger.ts +281 -281
- package/src/lib/runtime-require.ts +37 -37
- package/src/lib/utils/cookie-util.ts +34 -34
- package/src/lib/utils/crypto.ts +58 -58
- package/src/lib/utils/date-utils.ts +317 -317
- package/src/lib/utils/deep-merge.ts +37 -37
- package/src/lib/utils/delay.ts +12 -12
- package/src/lib/utils/file-setting.ts +55 -55
- package/src/lib/utils/format-bytes.ts +11 -11
- package/src/lib/utils/fs-utils.ts +158 -158
- package/src/lib/utils/get-env.ts +27 -27
- package/src/lib/utils/index.ts +12 -12
- package/src/lib/utils/is-type.ts +48 -48
- package/src/lib/utils/load-env.ts +14 -14
- package/src/lib/utils/pad.ts +6 -6
- package/src/models/api-base.ts +5 -5
- package/src/models/api-module-props.ts +10 -11
- package/src/models/api-router-props.ts +26 -26
- package/src/models/app-cache-props.ts +33 -33
- package/src/models/app-data-props.ts +10 -10
- package/src/models/app-helper-props.ts +6 -6
- package/src/models/app-shared-storage-props.ts +38 -38
- package/src/models/app-start-props.ts +18 -18
- package/src/models/async-storage-props.ts +13 -13
- package/src/models/db-config.ts +30 -30
- package/src/models/host-to-path-props.ts +12 -12
- package/src/models/index.ts +16 -16
- package/src/models/json-object.ts +8 -8
- package/src/models/locals-props.ts +36 -36
- package/src/models/logger-props.ts +84 -84
- package/src/models/simple-storage-props.ts +13 -14
- package/src/models/to-client-delivery-props.ts +6 -6
- package/tsconfig.json +115 -115
- package/dev/plugin-gen-versions.js +0 -20
package/src/api/shell-service.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/api/static-server.ts
CHANGED
|
@@ -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 (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
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
|
+
}
|