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.
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 +703 -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,82 +1,82 @@
1
- export const serverContentType: { [key: string]: string } = {
2
- txt: 'text/plain',
3
- htm: 'text/html',
4
- html: 'text/html',
5
- xhtml: 'application/xhtml+xml',
6
- xml: 'text/xml',
7
- css: 'text/css',
8
- js: 'text/javascript',
9
- wasm: 'application/wasm',
10
- json: 'application/json',
11
- csv: 'text/csv',
12
- yaml: 'application/x-yaml',
13
- md: 'text/markdown',
14
- yml: 'application/x-yaml',
15
- map: 'application/json',
16
- rss: 'application/rss+xml',
17
-
18
- gif: 'image/gif',
19
- jpeg: 'image/jpeg',
20
- jpg: 'image/jpeg',
21
- tif: 'image/tiff',
22
- tiff: 'image/tiff',
23
- png: 'image/png',
24
- svg: 'image/svg+xml',
25
- bmp: 'image/bmp',
26
- webp: 'image/webp',
27
- heic: 'image/heic',
28
- heif: 'image/heif',
29
- avif: 'image/avif',
30
- swf: 'application/x-shockwave-flash',
31
- ico: 'image/x-icon',
32
- cur: 'image/x-icon',
33
-
34
- pdf: 'application/pdf',
35
- doc: 'application/msword',
36
- docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
37
- xls: 'application/vnd.ms-excel',
38
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
39
- pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
40
- odt: 'application/vnd.oasis.opendocument.text',
41
- ods: 'application/vnd.oasis.opendocument.spreadsheet',
42
- odp: 'application/vnd.oasis.opendocument.presentation',
43
- ppt: 'application/vnd.ms-powerpoint',
44
- rtf: 'application/rtf',
45
-
46
- woff: 'font/woff',
47
- woff2: 'font/woff2',
48
- ttf: 'font/ttf',
49
- otf: 'font/otf',
50
- eot: 'application/vnd.ms-fontobject',
51
-
52
- mp3: 'audio/mpeg',
53
- wav: 'audio/x-wav',
54
- wma: 'audio/x-ms-wma',
55
- mp4: 'video/mp4',
56
- mpeg: 'video/mpeg',
57
- avi: 'video/x-msvideo',
58
- wmv: 'video/x-ms-wmv',
59
- ogg: 'audio/ogg',
60
- oga: 'audio/ogg',
61
- ogv: 'video/ogg',
62
- webm: 'video/webm',
63
- m4a: 'audio/mp4',
64
- aac: 'audio/aac',
65
- flac: 'audio/flac',
66
- mid: 'audio/midi',
67
- midi: 'audio/midi',
68
- '3gp': 'video/3gpp',
69
- '3g2': 'video/3gpp2',
70
- mov: 'video/quicktime',
71
- mkv: 'video/x-matroska',
72
- m4v: 'video/mp4',
73
-
74
- jar: 'application/java-archive',
75
- gz: 'application/gzip',
76
- tar: 'application/x-tar',
77
- rar: 'application/vnd.rar',
78
- zip: 'application/zip',
79
- '7z': 'application/x-7z-compressed',
80
- sh: 'application/x-sh',
81
- bat: 'application/x-msdownload',
82
- };
1
+ export const serverContentType: { [key: string]: string } = {
2
+ txt: 'text/plain',
3
+ htm: 'text/html',
4
+ html: 'text/html',
5
+ xhtml: 'application/xhtml+xml',
6
+ xml: 'text/xml',
7
+ css: 'text/css',
8
+ js: 'text/javascript',
9
+ wasm: 'application/wasm',
10
+ json: 'application/json',
11
+ csv: 'text/csv',
12
+ yaml: 'application/x-yaml',
13
+ md: 'text/markdown',
14
+ yml: 'application/x-yaml',
15
+ map: 'application/json',
16
+ rss: 'application/rss+xml',
17
+
18
+ gif: 'image/gif',
19
+ jpeg: 'image/jpeg',
20
+ jpg: 'image/jpeg',
21
+ tif: 'image/tiff',
22
+ tiff: 'image/tiff',
23
+ png: 'image/png',
24
+ svg: 'image/svg+xml',
25
+ bmp: 'image/bmp',
26
+ webp: 'image/webp',
27
+ heic: 'image/heic',
28
+ heif: 'image/heif',
29
+ avif: 'image/avif',
30
+ swf: 'application/x-shockwave-flash',
31
+ ico: 'image/x-icon',
32
+ cur: 'image/x-icon',
33
+
34
+ pdf: 'application/pdf',
35
+ doc: 'application/msword',
36
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
37
+ xls: 'application/vnd.ms-excel',
38
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
39
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
40
+ odt: 'application/vnd.oasis.opendocument.text',
41
+ ods: 'application/vnd.oasis.opendocument.spreadsheet',
42
+ odp: 'application/vnd.oasis.opendocument.presentation',
43
+ ppt: 'application/vnd.ms-powerpoint',
44
+ rtf: 'application/rtf',
45
+
46
+ woff: 'font/woff',
47
+ woff2: 'font/woff2',
48
+ ttf: 'font/ttf',
49
+ otf: 'font/otf',
50
+ eot: 'application/vnd.ms-fontobject',
51
+
52
+ mp3: 'audio/mpeg',
53
+ wav: 'audio/x-wav',
54
+ wma: 'audio/x-ms-wma',
55
+ mp4: 'video/mp4',
56
+ mpeg: 'video/mpeg',
57
+ avi: 'video/x-msvideo',
58
+ wmv: 'video/x-ms-wmv',
59
+ ogg: 'audio/ogg',
60
+ oga: 'audio/ogg',
61
+ ogv: 'video/ogg',
62
+ webm: 'video/webm',
63
+ m4a: 'audio/mp4',
64
+ aac: 'audio/aac',
65
+ flac: 'audio/flac',
66
+ mid: 'audio/midi',
67
+ midi: 'audio/midi',
68
+ '3gp': 'video/3gpp',
69
+ '3g2': 'video/3gpp2',
70
+ mov: 'video/quicktime',
71
+ mkv: 'video/x-matroska',
72
+ m4v: 'video/mp4',
73
+
74
+ jar: 'application/java-archive',
75
+ gz: 'application/gzip',
76
+ tar: 'application/x-tar',
77
+ rar: 'application/vnd.rar',
78
+ zip: 'application/zip',
79
+ '7z': 'application/x-7z-compressed',
80
+ sh: 'application/x-sh',
81
+ bat: 'application/x-msdownload',
82
+ };
@@ -1,215 +1,235 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { ServerResponse } from 'http';
4
- import { FsUtils, Logger } from '../lib';
5
- import { getWebEnv } from '../lib';
6
- import { ServerRequest } from '../models/locals-props';
7
- import { ToClientDelivery } from './to-client-delivery';
8
- import { IToClientDelivery } from '../models/to-client-delivery-props';
9
- import { JsonObject } from '../models/json-object';
10
- import { getTemplateCache } from './api-cache';
11
- import { apiStorage } from './api-shared-storage';
12
- import { RuntimeRequire } from '../lib/runtime-require';
13
-
14
- const logger = new Logger('StaticServer');
15
-
16
- export type RenderPageFunctionsType = {
17
- fetchData: (url: string, postData: string | JsonObject) => Promise<any>;
18
- [key: string]: Function;
19
- };
20
- let renderPageFunctions: RenderPageFunctionsType = {
21
- fetchData: async (url: string, postData: string | JsonObject) => {
22
- throw new Error('Method not implemented');
23
- },
24
- };
25
- export const getRenderPageFunctions = () => renderPageFunctions;
26
- // for the FE code to fetch data in SSR
27
- export const bindRenderPageFunctions = (calls: RenderPageFunctionsType) => {
28
- for (let k in calls) {
29
- renderPageFunctions[k] = calls[k];
30
- }
31
- };
32
-
33
- export type PageResultType = {
34
- content: string;
35
- title: string;
36
- metaData: string;
37
- themeName: string;
38
- globalCss: string;
39
- };
40
- type _LupineJs = {
41
- generatePage: (props: any, toClientDelivery: IToClientDelivery) => Promise<PageResultType>;
42
- };
43
-
44
- export const isServerSideRenderUrl = (urlWithoutQuery: string) => {
45
- /*
46
- "" --> ""
47
- "name" --> ""
48
- "name.txt" --> "txt"
49
- ".htpasswd" --> ""
50
- "name.with.many.dots.myext" --> "myext"
51
- */
52
- const ext = urlWithoutQuery.slice(((urlWithoutQuery.lastIndexOf('.') - 1) >>> 0) + 2);
53
- return ext === '' || ext === 'html';
54
- };
55
-
56
- // If the folder contains index.html and index.js, then the js will be used to render
57
- const findNearestRoot = async (cachedHtml: any, webRoot: string, urlWithoutQuery: string) => {
58
- if (urlWithoutQuery === '/' || urlWithoutQuery === '/index.html') {
59
- return webRoot;
60
- }
61
- if (urlWithoutQuery.endsWith('/')) {
62
- urlWithoutQuery = urlWithoutQuery.slice(0, -1);
63
- }
64
-
65
- // cache sub folders whether it has both index.html and js, or virtual path
66
- if (!cachedHtml['_sub_:' + webRoot]) {
67
- cachedHtml['_sub_:' + webRoot] = {};
68
- }
69
-
70
- const cacheRoots = cachedHtml['_sub_:' + webRoot];
71
- let nearRoot = path.join(webRoot, urlWithoutQuery);
72
- if (cacheRoots[nearRoot] === '1') {
73
- return nearRoot;
74
- }
75
-
76
- while (
77
- cacheRoots[nearRoot] === '0' ||
78
- !(await FsUtils.pathExist(path.join(nearRoot, 'index.html'))) ||
79
- !(await FsUtils.pathExist(path.join(nearRoot, 'index.js')))
80
- ) {
81
- cacheRoots[nearRoot] = '0';
82
- nearRoot = path.dirname(nearRoot);
83
- if (cacheRoots[nearRoot] === '1' || nearRoot.length <= webRoot.length) {
84
- break;
85
- }
86
- }
87
- if (nearRoot.length <= webRoot.length) {
88
- nearRoot = webRoot;
89
- } else {
90
- cacheRoots[nearRoot] = '1';
91
- }
92
- return nearRoot;
93
- };
94
-
95
- const titleText = '<!--META-TITLE-->';
96
- const metaTextStart = '<!--META-ENV-START-->';
97
- const metaTextEnd = '<!--META-ENV-END-->';
98
- const containerText = '<div class="lupine-root">'; // '</div>'
99
- type CachedHtmlProps = {
100
- content: string;
101
- webEnv: { [k: string]: string };
102
- // serverConfig: { [k: string]: any };
103
- titleIndex: number;
104
- metaIndexStart: number;
105
- metaIndexEnd: number;
106
- containerIndex: number;
107
- _lupineJs: _LupineJs;
108
- };
109
- export const serverSideRenderPage = async (
110
- appName: string,
111
- webRoot: string,
112
- urlWithoutQuery: string,
113
- urlQuery: string,
114
- req: ServerRequest,
115
- res: ServerResponse
116
- ) => {
117
- console.log(`=========SSR, root: ${webRoot}, url: ${urlWithoutQuery}`);
118
-
119
- // cache multiple folders
120
- const cachedHtml = getTemplateCache();
121
-
122
- // in order to support virtual path and also sub folders, here needs to find nearest sub folder which contains index.js
123
- const nearRoot = await findNearestRoot(cachedHtml, webRoot, urlWithoutQuery);
124
-
125
- if (!cachedHtml[nearRoot]) {
126
- // the FE code needs to export _lupineJs
127
- // const lupinJs = await import(webRoot + '/index.js');
128
- const gThis = await RuntimeRequire.loadModuleIsolated(path.join(nearRoot, 'index.js'), { _lupineJs: null });
129
- // const lupinJs = require(path.join(nearRoot, 'index.js'));
130
- if (!gThis || !gThis._lupineJs) {
131
- throw new Error('_lupineJs is not defined');
132
- }
133
-
134
- console.log(`=========load lupine: `, gThis);
135
- const _lupineJs = gThis._lupineJs() as _LupineJs;
136
-
137
- const content = await fs.promises.readFile(path.join(nearRoot, 'index.html'));
138
- const contentWithEnv = content.toString();
139
- cachedHtml[nearRoot] = {
140
- content: contentWithEnv,
141
- webEnv: getWebEnv(appName),
142
- titleIndex: contentWithEnv.indexOf(titleText),
143
- metaIndexStart: contentWithEnv.indexOf(metaTextStart),
144
- metaIndexEnd: contentWithEnv.indexOf(metaTextEnd),
145
- containerIndex: contentWithEnv.indexOf(containerText),
146
- _lupineJs: _lupineJs,
147
- } as CachedHtmlProps;
148
- }
149
-
150
- const props = {
151
- url: urlWithoutQuery,
152
- // urlSections: urlWithoutQuery.split('/').filter((i) => !!i),
153
- query: Object.fromEntries(new URLSearchParams(urlQuery || '')), //new URLSearchParams(urlQuery || ''),
154
- urlParameters: {},
155
- renderPageFunctions: renderPageFunctions,
156
- };
157
-
158
- const _lupineJs = cachedHtml[nearRoot]._lupineJs;
159
- const currentCache = cachedHtml[nearRoot] as CachedHtmlProps;
160
- const webSetting = await apiStorage.getWebAll();
161
- // const webSettingShortKey: SimpleStorageDataProps = {};
162
- // for (let item of Object.keys(webSetting)) {
163
- // const newItem = item.substring(4);
164
- // webSettingShortKey[newItem] = webSetting[item];
165
- // }
166
- // const webSetting = AppConfig.get(AppConfig.WEB_SETTINGS_KEY) || {};
167
- const clientDelivery = new ToClientDelivery(currentCache.webEnv, webSetting, req.locals.cookies());
168
- const page = await _lupineJs.generatePage(props, clientDelivery);
169
- // console.log(`=========load lupin: `, content);
170
-
171
- const allowOrigin = req.headers.origin && req.headers.origin !== 'null' ? req.headers.origin : '*';
172
- res.setHeader('Access-Control-Allow-Origin', allowOrigin);
173
- res.writeHead(200, { 'Content-Type': 'text/html' });
174
- // res.writeHead(200, { 'Content-Type': 'text/html', 'Content-Encoding': 'gzip' });
175
-
176
- // const s = zlib.createGzip();
177
- // stream.pipeline(s, res, (err) => {
178
- // s.write(cachedHtml.content.substring(0, cachedHtml.titleIndex).replace('<!--META-THEME-->', page.themeName));
179
- // s.write(page.title);
180
- // s.write(cachedHtml.content.substring(cachedHtml.titleIndex + titleText.length, cachedHtml.metaIndex));
181
- // s.write(page.metaData);
182
- // s.write(page.globalCss);
183
- // s.write(
184
- // cachedHtml.content.substring(cachedHtml.metaIndex + metaText.length, cachedHtml.containerIndex + containerText.length)
185
- // )
186
- // s.write(page.content);
187
- // s.write(cachedHtml.content.substring(cachedHtml.containerIndex + containerText.length), (err) => {
188
- // s.flush();
189
- // res.end();
190
- // });
191
- // });
192
-
193
- // data-theme and title
194
- res.write(currentCache.content.substring(0, currentCache.titleIndex).replace('<!--META-THEME-->', page.themeName));
195
- res.write(page.title);
196
- res.write(currentCache.content.substring(currentCache.titleIndex + titleText.length, currentCache.metaIndexStart));
197
- // meta data
198
- res.write(page.metaData);
199
- res.write(page.globalCss);
200
- res.write('<script id="web-env" type="application/json">' + JSON.stringify(currentCache.webEnv) + '</script>');
201
- res.write('<script id="web-setting" type="application/json">' + JSON.stringify(webSetting) + '</script>');
202
- res.write(
203
- currentCache.content.substring(
204
- currentCache.metaIndexEnd + metaTextEnd.length,
205
- currentCache.containerIndex + containerText.length
206
- )
207
- );
208
- // content
209
- res.write(page.content);
210
- res.write(currentCache.content.substring(currentCache.containerIndex + containerText.length));
211
-
212
- // const html = index.toString().replace('<div class="lupine-root"></div>', content);
213
- // handler200(res, html);
214
- res.end();
215
- };
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { ServerResponse } from 'http';
4
+ import { FsUtils, Logger } from '../lib';
5
+ import { getWebEnv } from '../lib';
6
+ import { ServerRequest } from '../models/locals-props';
7
+ import { ToClientDelivery } from './to-client-delivery';
8
+ import { IToClientDelivery } from '../models/to-client-delivery-props';
9
+ import { JsonObject } from '../models/json-object';
10
+ import { getTemplateCache } from './api-cache';
11
+ import { apiStorage } from './api-shared-storage';
12
+ import { RuntimeRequire } from '../lib/runtime-require';
13
+
14
+ const logger = new Logger('StaticServer');
15
+
16
+ export type RenderPageFunctionsType = {
17
+ fetchData: (url: string, postData: string | JsonObject) => Promise<any>;
18
+ [key: string]: Function;
19
+ };
20
+ let renderPageFunctions: RenderPageFunctionsType = {
21
+ fetchData: async (url: string, postData: string | JsonObject) => {
22
+ throw new Error('Method not implemented');
23
+ },
24
+ };
25
+ export const getRenderPageFunctions = () => renderPageFunctions;
26
+ // for the FE code to fetch data in SSR
27
+ export const bindRenderPageFunctions = (calls: RenderPageFunctionsType) => {
28
+ for (let k in calls) {
29
+ renderPageFunctions[k] = calls[k];
30
+ }
31
+ };
32
+
33
+ export type PageResultType = {
34
+ content: string;
35
+ title: string;
36
+ metaData: string;
37
+ themeName: string;
38
+ globalCss: string;
39
+ };
40
+ type _LupineJs = {
41
+ generatePage: (props: any, toClientDelivery: IToClientDelivery) => Promise<PageResultType>;
42
+ };
43
+
44
+ export const isServerSideRenderUrl = (urlWithoutQuery: string) => {
45
+ /*
46
+ "" --> ""
47
+ "name" --> ""
48
+ "name.txt" --> "txt"
49
+ ".htpasswd" --> ""
50
+ "name.with.many.dots.myext" --> "myext"
51
+ */
52
+ const ext = urlWithoutQuery.slice(((urlWithoutQuery.lastIndexOf('.') - 1) >>> 0) + 2);
53
+ return ext === '' || ext === 'html';
54
+ };
55
+
56
+ // If the folder contains index.html and index.js, then the js will be used to render
57
+ const findNearestRoot = async (cachedHtml: any, webRoot: string, urlWithoutQuery: string) => {
58
+ if (urlWithoutQuery === '/' || urlWithoutQuery === '/index.html') {
59
+ return webRoot;
60
+ }
61
+ if (urlWithoutQuery.endsWith('/')) {
62
+ urlWithoutQuery = urlWithoutQuery.slice(0, -1);
63
+ }
64
+
65
+ // cache sub folders whether it has both index.html and js, or virtual path
66
+ if (!cachedHtml['_sub_:' + webRoot]) {
67
+ cachedHtml['_sub_:' + webRoot] = {};
68
+ }
69
+
70
+ const cacheRoots = cachedHtml['_sub_:' + webRoot];
71
+ let nearRoot = path.join(webRoot, urlWithoutQuery);
72
+ if (cacheRoots[nearRoot] === '1') {
73
+ return nearRoot;
74
+ }
75
+
76
+ while (
77
+ cacheRoots[nearRoot] === '0' ||
78
+ !(await FsUtils.pathExist(path.join(nearRoot, 'index.html'))) ||
79
+ !(await FsUtils.pathExist(path.join(nearRoot, 'index.js')))
80
+ ) {
81
+ cacheRoots[nearRoot] = '0';
82
+ nearRoot = path.dirname(nearRoot);
83
+ if (cacheRoots[nearRoot] === '1' || nearRoot.length <= webRoot.length) {
84
+ break;
85
+ }
86
+ }
87
+ if (nearRoot.length <= webRoot.length) {
88
+ nearRoot = webRoot;
89
+ } else {
90
+ cacheRoots[nearRoot] = '1';
91
+ }
92
+ return nearRoot;
93
+ };
94
+
95
+ const titleText = '<!--META-TITLE-->';
96
+ const metaTextStart = '<!--META-ENV-START-->';
97
+ const metaTextEnd = '<!--META-ENV-END-->';
98
+ const containerText = '<div class="lupine-root">'; // '</div>'
99
+ type CachedHtmlProps = {
100
+ content: string;
101
+ webEnv: { [k: string]: string };
102
+ // serverConfig: { [k: string]: any };
103
+ titleIndex: number;
104
+ metaIndexStart: number;
105
+ metaIndexEnd: number;
106
+ containerIndex: number;
107
+ _lupineJs: _LupineJs;
108
+ };
109
+ export const serverSideRenderPage = async (
110
+ appName: string,
111
+ webRoot: string,
112
+ urlWithoutQuery: string,
113
+ urlQuery: string,
114
+ req: ServerRequest,
115
+ res: ServerResponse
116
+ ) => {
117
+ // console.log(`=========SSR, root: ${webRoot}, url: ${urlWithoutQuery}`);
118
+
119
+ // cache multiple folders
120
+ const cachedHtml = getTemplateCache();
121
+
122
+ // in order to support virtual path and also sub folders, here needs to find nearest sub folder which contains index.js
123
+ const nearRoot = await findNearestRoot(cachedHtml, webRoot, urlWithoutQuery);
124
+
125
+ if (!cachedHtml[nearRoot]) {
126
+ // the FE code needs to export _lupineJs
127
+ // const lupinJs = await import(webRoot + '/index.js');
128
+ // if error happens during the SSR, then send index.html
129
+ const content = await fs.promises.readFile(path.join(nearRoot, 'index.html'));
130
+ let _lupineJs;
131
+ try {
132
+ const gThis = await RuntimeRequire.loadModuleIsolated(path.join(nearRoot, 'index.js'), { _lupineJs: null });
133
+ // const lupinJs = require(path.join(nearRoot, 'index.js'));
134
+ if (!gThis || !gThis._lupineJs) {
135
+ throw new Error('_lupineJs is not defined');
136
+ }
137
+ // console.log(`=========load lupine: `, gThis);
138
+ _lupineJs = gThis._lupineJs() as _LupineJs;
139
+ } catch (e: any) {
140
+ logger.error(e.message);
141
+ }
142
+
143
+ const contentWithEnv = content.toString();
144
+ cachedHtml[nearRoot] = {
145
+ content: contentWithEnv,
146
+ webEnv: getWebEnv(appName),
147
+ titleIndex: contentWithEnv.indexOf(titleText),
148
+ metaIndexStart: contentWithEnv.indexOf(metaTextStart),
149
+ metaIndexEnd: contentWithEnv.indexOf(metaTextEnd),
150
+ containerIndex: contentWithEnv.indexOf(containerText),
151
+ _lupineJs: _lupineJs,
152
+ } as CachedHtmlProps;
153
+ }
154
+
155
+ const props = {
156
+ url: urlWithoutQuery,
157
+ // urlSections: urlWithoutQuery.split('/').filter((i) => !!i),
158
+ query: Object.fromEntries(new URLSearchParams(urlQuery || '')), //new URLSearchParams(urlQuery || ''),
159
+ urlParameters: {},
160
+ renderPageFunctions: renderPageFunctions,
161
+ };
162
+
163
+ const _lupineJs = cachedHtml[nearRoot]._lupineJs;
164
+ const currentCache = cachedHtml[nearRoot] as CachedHtmlProps;
165
+ const webSetting = await apiStorage.getWebAll();
166
+ // const webSettingShortKey: SimpleStorageDataProps = {};
167
+ // for (let item of Object.keys(webSetting)) {
168
+ // const newItem = item.substring(4);
169
+ // webSettingShortKey[newItem] = webSetting[item];
170
+ // }
171
+ // const webSetting = AppConfig.get(AppConfig.WEB_SETTINGS_KEY) || {};
172
+ const clientDelivery = new ToClientDelivery(currentCache.webEnv, webSetting, req.locals.cookies());
173
+
174
+ let page = {
175
+ content: '',
176
+ title: '',
177
+ metaData: '',
178
+ themeName: '',
179
+ globalCss: '',
180
+ };
181
+ if (_lupineJs) {
182
+ try {
183
+ page = await _lupineJs.generatePage(props, clientDelivery);
184
+ } catch (e: any) {
185
+ logger.error(e.message);
186
+ }
187
+ }
188
+
189
+ // console.log(`=========load lupin: `, content);
190
+
191
+ const allowOrigin = req.headers.origin && req.headers.origin !== 'null' ? req.headers.origin : '*';
192
+ res.setHeader('Access-Control-Allow-Origin', allowOrigin);
193
+ res.writeHead(200, { 'Content-Type': 'text/html' });
194
+ // res.writeHead(200, { 'Content-Type': 'text/html', 'Content-Encoding': 'gzip' });
195
+
196
+ // const s = zlib.createGzip();
197
+ // stream.pipeline(s, res, (err) => {
198
+ // s.write(cachedHtml.content.substring(0, cachedHtml.titleIndex).replace('<!--META-THEME-->', page.themeName));
199
+ // s.write(page.title);
200
+ // s.write(cachedHtml.content.substring(cachedHtml.titleIndex + titleText.length, cachedHtml.metaIndex));
201
+ // s.write(page.metaData);
202
+ // s.write(page.globalCss);
203
+ // s.write(
204
+ // cachedHtml.content.substring(cachedHtml.metaIndex + metaText.length, cachedHtml.containerIndex + containerText.length)
205
+ // )
206
+ // s.write(page.content);
207
+ // s.write(cachedHtml.content.substring(cachedHtml.containerIndex + containerText.length), (err) => {
208
+ // s.flush();
209
+ // res.end();
210
+ // });
211
+ // });
212
+
213
+ // data-theme and title
214
+ res.write(currentCache.content.substring(0, currentCache.titleIndex).replace('<!--META-THEME-->', page.themeName));
215
+ res.write(page.title);
216
+ res.write(currentCache.content.substring(currentCache.titleIndex + titleText.length, currentCache.metaIndexStart));
217
+ // meta data
218
+ res.write(page.metaData);
219
+ res.write(page.globalCss);
220
+ res.write('<script id="web-env" type="application/json">' + JSON.stringify(currentCache.webEnv) + '</script>');
221
+ res.write('<script id="web-setting" type="application/json">' + JSON.stringify(webSetting) + '</script>');
222
+ res.write(
223
+ currentCache.content.substring(
224
+ currentCache.metaIndexEnd + metaTextEnd.length,
225
+ currentCache.containerIndex + containerText.length
226
+ )
227
+ );
228
+ // content
229
+ res.write(page.content);
230
+ res.write(currentCache.content.substring(currentCache.containerIndex + containerText.length));
231
+
232
+ // const html = index.toString().replace('<div class="lupine-root"></div>', content);
233
+ // handler200(res, html);
234
+ res.end();
235
+ };