lupine.api 1.1.51 → 1.1.53

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.
@@ -8,6 +8,7 @@ import {
8
8
  NotificationMessage,
9
9
  formatBytes,
10
10
  downloadStream,
11
+ ActionSheetSelectPromise,
11
12
  } from 'lupine.components';
12
13
 
13
14
  interface ReleaseListProps {
@@ -284,7 +285,7 @@ export const AdminReleasePage = () => {
284
285
  isLocal,
285
286
  });
286
287
  const dataResponse = await response.json;
287
- console.log('AdminRelease', dataResponse);
288
+ console.log('refresh-cache', dataResponse);
288
289
  if (!dataResponse || dataResponse.status !== 'ok') {
289
290
  NotificationMessage.sendMessage(dataResponse.message || 'Failed to refresh cache', NotificationColor.Error);
290
291
  return;
@@ -293,6 +294,44 @@ export const AdminReleasePage = () => {
293
294
  NotificationMessage.sendMessage('Cache refreshed successfully', NotificationColor.Success);
294
295
  };
295
296
 
297
+ const onRestartAppLocal = async () => {
298
+ return onRestartApp(true);
299
+ };
300
+ const onRestartAppRemote = async () => {
301
+ return onRestartApp(false);
302
+ };
303
+ const onRestartApp = async (isLocal?: boolean) => {
304
+ const data = getDomData();
305
+ if (!isLocal) {
306
+ if (!data.targetUrl || !data.accessToken) {
307
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
308
+ return;
309
+ }
310
+ }
311
+
312
+ const index = await ActionSheetSelectPromise({
313
+ title: 'Restart App (users may get disconnected errors) ?',
314
+ options: ['OK'],
315
+ cancelButtonText: 'Cancel',
316
+ });
317
+ if (index !== 0) {
318
+ return;
319
+ }
320
+
321
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/restart-app', {
322
+ ...data,
323
+ isLocal,
324
+ });
325
+ const dataResponse = await response.json;
326
+ console.log('restart-app', dataResponse);
327
+ if (!dataResponse || dataResponse.status !== 'ok') {
328
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to Restart App', NotificationColor.Error);
329
+ return;
330
+ }
331
+ domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
332
+ NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
333
+ };
334
+
296
335
  const ref: RefProps = {
297
336
  onLoad: async () => {
298
337
  const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
@@ -321,9 +360,15 @@ export const AdminReleasePage = () => {
321
360
  <button onClick={onRefreshCacheRemote} class='button-base mr-m'>
322
361
  Refresh Cache (Remote)
323
362
  </button>
324
- <button onClick={onRefreshCacheLocal} class='button-base'>
363
+ <button onClick={onRestartAppRemote} class='button-base mr-m color-red'>
364
+ Restart App (Remote)
365
+ </button>
366
+ <button onClick={onRefreshCacheLocal} class='button-base mr-m'>
325
367
  Refresh Cache (Local)
326
368
  </button>
369
+ <button onClick={onRestartAppLocal} class='button-base color-red'>
370
+ Restart App (Local)
371
+ </button>
327
372
  </div>
328
373
  {domUpdate.node}
329
374
  {domLog.node}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.api",
3
- "version": "1.1.51",
3
+ "version": "1.1.53",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -69,14 +69,15 @@ export const devAdminAuth = async (req: ServerRequest, res: ServerResponse) => {
69
69
  ApiHelper.sendJson(req, res, response);
70
70
  return true;
71
71
  }
72
- // if it's dev admin, then set app admin cookie as well
73
- let addLoginResponse = {};
74
- const appAdminHookSetCookie = adminApiHelper.getAppAdminHookSetCookie();
75
- if (appAdminHookSetCookie) {
76
- addLoginResponse = await appAdminHookSetCookie(req, res, devAdminSession.u);
77
- }
72
+ // on set app cookie when login, not every time
73
+ // // if it's dev admin, then set app admin cookie as well
74
+ // let addLoginResponse = {};
75
+ // const appAdminHookSetCookie = adminApiHelper.getAppAdminHookSetCookie();
76
+ // if (appAdminHookSetCookie) {
77
+ // addLoginResponse = await appAdminHookSetCookie(req, res, devAdminSession.u);
78
+ // }
78
79
  const response = {
79
- ...addLoginResponse,
80
+ // ...addLoginResponse,
80
81
  status: 'ok',
81
82
  message: langHelper.getLang('shared:login_success'),
82
83
  devLogin: CryptoUtils.encrypt(JSON.stringify(devAdminSession), cryptoKey),
@@ -109,13 +110,13 @@ export const devAdminAuth = async (req: ServerRequest, res: ServerResponse) => {
109
110
  message: langHelper.getLang('shared:login_success'),
110
111
  devLogin: tokenCookie,
111
112
  };
112
- req.locals.setCookie('_token', tokenCookie, {
113
- expireDays: 360,
114
- path: '/',
115
- httpOnly: false,
116
- secure: true,
117
- sameSite: 'none',
118
- });
113
+ // req.locals.setCookie('_token', tokenCookie, {
114
+ // expireDays: 360,
115
+ // path: '/',
116
+ // httpOnly: false,
117
+ // secure: true,
118
+ // sameSite: 'none',
119
+ // });
119
120
  ApiHelper.sendJson(req, res, response);
120
121
  return true;
121
122
  }
@@ -11,6 +11,7 @@ import {
11
11
  adminApiHelper,
12
12
  processRefreshCache,
13
13
  apiStorage,
14
+ processRestartApp,
14
15
  } from 'lupine.api';
15
16
  import path from 'path';
16
17
  import { needDevAdminSession } from './admin-auth';
@@ -36,11 +37,13 @@ export class AdminRelease implements IApiBase {
36
37
  this.router.use('/view-log', needDevAdminSession, this.viewLog.bind(this));
37
38
  // called online or by clients
38
39
  this.router.use('/refresh-cache', needDevAdminSession, this.refreshCache.bind(this));
40
+ this.router.use('/restart-app', needDevAdminSession, this.restartApp.bind(this));
39
41
 
40
42
  // ...ByClient will verify credentials from post, so it doesn't need AdminSession
41
43
  this.router.use('/byClientCheck', this.byClientCheck.bind(this));
42
44
  this.router.use('/byClientUpdate', this.byClientUpdate.bind(this));
43
45
  this.router.use('/byClientRefreshCache', this.byClientRefreshCache.bind(this));
46
+ this.router.use('/byClientRestartApp', this.byClientRestartApp.bind(this));
44
47
  this.router.use('/byClientViewLog', this.byClientViewLog.bind(this));
45
48
  }
46
49
 
@@ -126,6 +129,47 @@ export class AdminRelease implements IApiBase {
126
129
  return true;
127
130
  }
128
131
 
132
+ async restartApp(req: ServerRequest, res: ServerResponse) {
133
+ // check whether it's from online admin
134
+ const json = await adminApiHelper.getDevAdminFromCookie(req, res, false);
135
+ const jsonData = req.locals.json();
136
+ if (json && jsonData && !Array.isArray(jsonData) && jsonData.isLocal) {
137
+ await processRestartApp(req);
138
+ const response = {
139
+ status: 'ok',
140
+ message: 'Restart app successfully.',
141
+ };
142
+ ApiHelper.sendJson(req, res, response);
143
+ return true;
144
+ }
145
+
146
+ const data = await this.chkData(jsonData, req, res, true);
147
+ if (!data) return true;
148
+
149
+ let targetUrl = data.targetUrl as string;
150
+ if (targetUrl.endsWith('/')) {
151
+ targetUrl = targetUrl.slice(0, -1);
152
+ }
153
+ const remoteData = await fetch(targetUrl + '/api/admin/release/byClientRestartApp', {
154
+ method: 'POST',
155
+ body: JSON.stringify(data),
156
+ });
157
+ const resultText = await remoteData.text();
158
+ let remoteResult: any;
159
+ try {
160
+ remoteResult = JSON.parse(resultText);
161
+ } catch (e: any) {
162
+ remoteResult = { status: 'error', message: resultText };
163
+ }
164
+ const response = {
165
+ status: 'ok',
166
+ message: 'check.',
167
+ ...remoteResult,
168
+ };
169
+ ApiHelper.sendJson(req, res, response);
170
+ return true;
171
+ }
172
+
129
173
  public async chkData(data: any, req: ServerRequest, res: ServerResponse, chkCredential: boolean) {
130
174
  // add access token
131
175
  if (!data || Array.isArray(data) || typeof data !== 'object' || !data.accessToken || !data.targetUrl) {
@@ -569,4 +613,18 @@ export class AdminRelease implements IApiBase {
569
613
  ApiHelper.sendJson(req, res, response);
570
614
  return true;
571
615
  }
616
+
617
+ async byClientRestartApp(req: ServerRequest, res: ServerResponse) {
618
+ const jsonData = req.locals.json();
619
+ const data = await this.chkData(jsonData, req, res, true);
620
+ if (!data) return true;
621
+
622
+ await processRestartApp(req);
623
+ const response = {
624
+ status: 'ok',
625
+ message: 'Restart app successfully.',
626
+ };
627
+ ApiHelper.sendJson(req, res, response);
628
+ return true;
629
+ }
572
630
  }
@@ -48,7 +48,7 @@ export class DebugService {
48
48
 
49
49
  // broadcast to all frontend clients
50
50
  public static broadcastRefresh() {
51
- console.log(`broadcast refresh request to clients.`);
51
+ console.log(`${process.pid} - broadcast refresh request to clients.`);
52
52
  this.clientRefreshFlag = Date.now();
53
53
  const msg = { message: 'Refresh', flag: this.clientRefreshFlag };
54
54
  this.miniWebSocket.broadcast(JSON.stringify(msg));
@@ -71,7 +71,7 @@ export class StaticServer {
71
71
 
72
72
  // if fullPath doesn't exist, it will throw ENOENT error
73
73
  const realPath = await fs.promises.realpath(fullPath);
74
- console.log(`request: ${realPath}`);
74
+ console.log(`${process.pid} - request: ${realPath}`);
75
75
  // for security reason, the requested file should be inside of wwwRoot
76
76
  if (realPath.substring(0, hostPath.webPath.length) !== hostPath.webPath) {
77
77
  this.logger.warn(`ACCESS DENIED: ${urlSplit[0]}`);
@@ -51,7 +51,7 @@ class AppLoader {
51
51
  const apiPath = path.join(process.cwd(), 'dist/server_root', appConfig.appName + '_api/index.js');
52
52
  for (const path in require.cache) {
53
53
  if (path.endsWith('.js') && path.indexOf(apiPath) <= 0) {
54
- console.log(`clear cache: ${path}`);
54
+ console.log(`${process.pid} - clear cache: ${path}`);
55
55
  delete require.cache[path];
56
56
  }
57
57
  }
@@ -1,6 +1,6 @@
1
1
  import cluster from 'cluster';
2
2
  import { Logger, LogWriter, LogWriterMessageId } from '../lib';
3
- import { processDebugMessage } from './process-dev-requests';
3
+ import { processDebugMessage, snedRestartAppMsgToLoader } from './process-dev-requests';
4
4
  import { cleanupAndExit } from './cleanup-exit';
5
5
 
6
6
  export type AppMessageProps = {
@@ -78,15 +78,32 @@ export const processMessageFromWorker = (msgObject: AppMessageProps) => {
78
78
  logger.debug(
79
79
  `Message from worker ${cluster.worker?.id}, message: ${msgObject.message}, appName: ${msgObject.appName}`
80
80
  );
81
- broadcast(msgObject);
82
- // if it's suspend, the primary process will exit
83
- if (msgObject.message === 'suspend') {
84
- setTimeout(() => {
85
- console.log(`[server primary] Received suspend command.`, cluster.workers);
86
- cleanupAndExit();
81
+ if (msgObject.message === 'restartApp') {
82
+ snedRestartAppMsgToLoader();
83
+ return;
84
+ } else if (msgObject.message === 'refresh') {
85
+ broadcast(msgObject);
86
+ } else if (msgObject.message === 'shutdown') {
87
+ broadcast(msgObject);
88
+ // if it's shutdown, the primary process will exit
89
+ setTimeout(async () => {
90
+ console.log(`[server primary] Received shutdown command.`, cluster.workers);
91
+ await cleanupAndExit();
87
92
  }, 100);
93
+ } else {
94
+ logger.warn(`Unknown message: ${msgObject.id}`);
88
95
  }
89
96
  } else {
90
- logger.warn(`Unknown message: ${msgObject.id}`);
97
+ logger.warn(`Unknown message id: ${msgObject.id}`);
91
98
  }
92
99
  };
100
+
101
+ // this is primary, and receive messages from loader
102
+ export const receiveMessageFromLoader = () => {
103
+ process.on('message', async (msg: AppMessageProps) => {
104
+ if (msg?.id === 'debug' && msg?.message === 'shutdown') {
105
+ console.log(`App ${process.pid}: received shutdown message from loader.`);
106
+ processMessageFromWorker(msg);
107
+ }
108
+ });
109
+ };
@@ -166,7 +166,7 @@ export class AppSharedStorage implements IAppSharedStorage {
166
166
  // this can be called in primary or worker
167
167
  get(appName: string, key: string): Promise<string> {
168
168
  return new Promise((resolve, reject) => {
169
- console.log(`AppStorage get value start in ${process.pid}, for key: ${key}`);
169
+ console.log(`${process.pid} - AppStorage get value for key: ${key}`);
170
170
 
171
171
  if (!cluster.isPrimary) {
172
172
  AppSharedStorageWorker.get(appName, key, resolve, reject);
@@ -196,7 +196,7 @@ export class AppSharedStorage implements IAppSharedStorage {
196
196
  }
197
197
  getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
198
198
  return new Promise((resolve, reject) => {
199
- console.log(`AppStorage getWithPrefix start in ${process.pid}, for prefixKey: ${prefixKey}`);
199
+ console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
200
200
 
201
201
  if (!cluster.isPrimary) {
202
202
  AppSharedStorageWorker.getWithPrefix(appName, prefixKey, resolve, reject);
@@ -238,7 +238,7 @@ class AppSharedStorageWorker {
238
238
  }
239
239
 
240
240
  if (msgObject.action === 'get') {
241
- console.log(`AppStorage get value end in process: ${process.pid}, for key: ${msgObject.key}`);
241
+ console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
242
242
 
243
243
  const value = msgObject.value;
244
244
  // how to pass the value to the caller
@@ -250,7 +250,7 @@ class AppSharedStorageWorker {
250
250
  throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
251
251
  }
252
252
  } else if (msgObject.action === 'getWithPrefix') {
253
- console.log(`AppStorage get value end in process: ${process.pid}, for key: ${msgObject.key}`);
253
+ console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
254
254
  const value = JSON.parse(msgObject.value);
255
255
  // how to pass the value to the caller
256
256
  const map = this.handleMap[msgObject.uniqueKey];
@@ -8,7 +8,8 @@ import { appCache } from './app-cache';
8
8
  import { AppStartProps, InitStartProps, AppCacheGlobal, AppCacheKeys } from '../models';
9
9
  import { appStorage } from './app-shared-storage';
10
10
  import { HostToPath } from './host-to-path';
11
- import { cleanupAndExit } from './cleanup-exit';
11
+ import { _exitApp, cleanupAndExit } from './cleanup-exit';
12
+ import { receiveMessageFromLoader } from './app-message';
12
13
 
13
14
  // Don't use logger before set process message
14
15
  class AppStart {
@@ -16,10 +17,16 @@ class AppStart {
16
17
  webServer: WebServer | undefined;
17
18
 
18
19
  getWorkerId() {
19
- return cluster.worker ? cluster.worker.id : 0;
20
+ return cluster.worker ? cluster.worker.id : -1;
20
21
  }
21
22
 
22
23
  async start(props: AppStartProps, webServer?: WebServer) {
24
+ // if it's started from spawn, wait for old master to clear ports
25
+ if (cluster.isPrimary && process.env.RESTARTING === '1') {
26
+ console.log(`New app ${process.pid} RESTARTING.`);
27
+ await new Promise((r) => setTimeout(r, 100));
28
+ }
29
+
23
30
  this.debug = props.debug;
24
31
  this.bindProcess();
25
32
 
@@ -34,9 +41,9 @@ class AppStart {
34
41
 
35
42
  // call the Logger after initLog
36
43
  console.log(
37
- `${cluster.isPrimary ? 'Primary Process' : 'Worker Process'}, Starting Server - process id ${
38
- process.pid
39
- }, path: ${process.cwd()}`
44
+ `${process.pid} - ${
45
+ cluster.isPrimary ? 'Primary Process' : 'Worker Process'
46
+ }, Starting Server, path: ${process.cwd()}`
40
47
  );
41
48
 
42
49
  // when it's cluster.isPrimary or props.debug, initialize the shared storage first
@@ -47,7 +54,7 @@ class AppStart {
47
54
  }
48
55
 
49
56
  if (!cluster.isPrimary) {
50
- console.log(`Worker id ${this.getWorkerId()}`);
57
+ console.log(`${process.pid} - Worker id ${this.getWorkerId()}`);
51
58
 
52
59
  process.on('message', processMessageFromPrimary);
53
60
 
@@ -56,7 +63,9 @@ class AppStart {
56
63
  this.initServer(props.serverConfig);
57
64
  } else if (cluster.isPrimary) {
58
65
  const numCPUs = props.debug ? 1 : require('os').cpus().length;
59
- console.log(`Master Process is trying to fork ${numCPUs} processes`);
66
+ console.log(`${process.pid} - Primary Process is trying to fork ${numCPUs} processes`);
67
+
68
+ receiveMessageFromLoader();
60
69
 
61
70
  for (let i = 0; i < numCPUs; i++) {
62
71
  let worker = cluster.fork();
@@ -64,8 +73,12 @@ class AppStart {
64
73
  }
65
74
 
66
75
  cluster.on('death', (worker: any) => {
67
- console.log(`Worker ${worker.pid} died; starting a new one...`);
68
- cluster.fork();
76
+ if (!_exitApp.isExiting) {
77
+ console.log(`${worker.pid} - Worker died; starting a new one...`);
78
+ cluster.fork();
79
+ } else {
80
+ console.log(`${worker.pid} - Worker exited during restart`);
81
+ }
69
82
  });
70
83
  }
71
84
  }
@@ -82,7 +95,7 @@ class AppStart {
82
95
 
83
96
  // do something when app is closing
84
97
  process.on('beforeExit', async () => {
85
- cleanupAndExit();
98
+ await cleanupAndExit();
86
99
  });
87
100
  process.on('exit', (ret) => {
88
101
  console.log(`${process.pid} - Process on exit, code: ${ret}`);
@@ -103,14 +116,20 @@ class AppStart {
103
116
  const sslKeyPath = config.sslKeyPath || '';
104
117
  const sslCrtPath = config.sslCrtPath || '';
105
118
 
106
- console.log(`Starting Web Server, httpPort: ${httpPort}, httpsPort: ${httpsPort}`);
119
+ console.log(`${process.pid} - Starting Web Server, httpPort: ${httpPort}, httpsPort: ${httpsPort}`);
107
120
  // for dev to refresh the FE or stop the server
108
121
  if (this.debug) {
109
122
  WebProcessor.enableDebug('/debug', processDevRequests);
110
123
  }
111
124
 
112
- httpPort && this.webServer!.startHttp(httpPort, bindIp);
113
- httpsPort && this.webServer!.startHttps(httpsPort, bindIp, sslKeyPath, sslCrtPath);
125
+ const httpServer = httpPort && this.webServer!.startHttp(httpPort, bindIp);
126
+ const heepsServer = httpsPort && this.webServer!.startHttps(httpsPort, bindIp, sslKeyPath, sslCrtPath);
127
+
128
+ process.on('SIGTERM', () => {
129
+ console.log(`${process.pid} - Worker closing servers...`);
130
+ httpServer && httpServer.close();
131
+ heepsServer && heepsServer.close();
132
+ });
114
133
  }
115
134
  }
116
135
 
@@ -1,6 +1,9 @@
1
1
  import cluster from 'cluster';
2
2
  import { appStorage } from './app-shared-storage';
3
3
 
4
+ export const _exitApp = {
5
+ isExiting: false,
6
+ };
4
7
  export const cleanupAndExit = async () => {
5
8
  console.log(`${process.pid} - Process on SIGINT, exit.`);
6
9
  // save shared storage first
@@ -8,5 +11,6 @@ export const cleanupAndExit = async () => {
8
11
  // save only happens once
9
12
  await appStorage.save('', true);
10
13
  }
14
+ _exitApp.isExiting = true;
11
15
  process.exit(0);
12
16
  };
@@ -1,3 +1,4 @@
1
+ import cluster from 'cluster';
1
2
  import { Logger } from '../lib/logger';
2
3
  import { ServerResponse } from 'http';
3
4
  import { AddressInfo } from 'net';
@@ -42,20 +43,20 @@ export const processDebugMessage = async (msgObject: any) => {
42
43
  // this only works in debug mode (no clusters)
43
44
  DebugService.broadcastRefresh();
44
45
  }
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();
46
+ if (msgObject.id === 'debug' && msgObject.message === 'shutdown') {
47
+ // Only when it's debug mode, it can go here, otherwise shutdown should be processed in processMessageFromWorker
48
+ console.log(`${process.pid} - [server] Received shutdown command.`);
49
+ await cleanupAndExit();
49
50
  }
50
51
  };
51
52
 
52
53
  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) {
54
+ // if this is a child process, we need to notice parent process to broadcast to all clients
55
+ if (!cluster.isPrimary && process.send) {
55
56
  const appName = req.locals.query.get('appName');
56
57
  process.send({ id: 'debug', message: 'refresh', appName });
57
58
  }
58
- // if it's debug mode (only one process)
59
+ // in case if it's only one process (primary process)
59
60
  else {
60
61
  // if (getAppCache().get(APP_GLOBAL, AppCacheKeys.API_DEBUG) === true)
61
62
  const appName = req.locals.query.get('appName');
@@ -63,28 +64,56 @@ export async function processRefreshCache(req: ServerRequest) {
63
64
  }
64
65
  }
65
66
 
66
- // this is only for local development
67
+ export async function processRestartApp(req: ServerRequest) {
68
+ // if this is a child process, we need to notice parent process to broadcast to all clients
69
+ if (!cluster.isPrimary && process.send) {
70
+ // send message to Primary to handle it
71
+ process.send({ id: 'debug', message: 'restartApp' });
72
+ }
73
+ // in case if it's only one process (primary process)
74
+ else {
75
+ snedRestartAppMsgToLoader();
76
+ }
77
+ }
78
+
79
+ // this is called from a request in debug mode
67
80
  export async function processDevRequests(req: ServerRequest, res: ServerResponse, rootUrl?: string) {
68
81
  res.end();
69
82
  const address = req.socket.address() as AddressInfo;
70
83
  if (address.address !== '127.0.0.1') {
71
- console.log(`[server] Ignore request from: `, req.url, address.address);
84
+ console.log(`${process.pid} - [server] Ignore request from: `, req.url, address.address);
72
85
  return true;
73
86
  }
74
- if (req.url === '/debug/suspend') {
75
- console.log(`[server] Received suspend command.`);
87
+ if (req.url === '/debug/shutdown') {
88
+ console.log(`${process.pid} - [server] Received shutdown command.`);
76
89
  if (process.send) {
77
90
  // send to parent process to kill all
78
- process.send({ id: 'debug', message: 'suspend' });
91
+ process.send({ id: 'debug', message: 'shutdown' });
79
92
  }
80
93
  // if it's debug mode (only one process)
81
94
  else if (getAppCache().get(AppCacheGlobal, AppCacheKeys.APP_DEBUG) === true) {
82
- await processDebugMessage({ id: 'debug', message: 'suspend' });
95
+ await processDebugMessage({ id: 'debug', message: 'shutdown' });
83
96
  }
84
97
  } else if (req.url === '/debug/refresh') {
85
98
  await processRefreshCache(req);
86
99
  }
87
- if (req.url === '/debug/client') {
88
- }
100
+ // else if (req.url === '/debug/client') {
101
+ // }
89
102
  return true;
90
103
  }
104
+
105
+ // this is called from a request and passes the restartApp message to loader
106
+ export const snedRestartAppMsgToLoader = async () => {
107
+ if (!cluster.isPrimary) {
108
+ console.warn(`restartApp: shouldn't come here`);
109
+ return;
110
+ }
111
+
112
+ if (!process.send) {
113
+ console.log(`${process.pid} - The primary process is not focked from loader, so cannot restart.`);
114
+ } else {
115
+ console.log(`${process.pid} - Old app sends restartApp to loader (${process.execPath})`);
116
+
117
+ process.send({ id: 'debug', message: 'restartApp' });
118
+ }
119
+ };
@@ -8,6 +8,7 @@ import { IncomingMessage, ServerResponse } from 'http';
8
8
  import { Duplex } from 'stream';
9
9
  import { WebProcessor } from './web-processor';
10
10
  import { DebugService } from '../api/debug-service';
11
+ import cluster from 'cluster';
11
12
  const logger = new Logger('web-server');
12
13
 
13
14
  export class WebServer {
@@ -47,7 +48,7 @@ export class WebServer {
47
48
  httpServer.on('upgrade', this.handleUpgrade.bind(this));
48
49
 
49
50
  httpServer.listen(httpPort, bindIp, () => {
50
- logger.info(`Http Server is started: http://localhost:${httpPort}`);
51
+ logger.info(`Http Server ${cluster.worker ? cluster.worker.id : -1} is started: http://localhost:${httpPort}`);
51
52
  });
52
53
  httpServer.on('error', (error: any) => {
53
54
  logger.error('Error occurred on http server', error);
@@ -88,11 +89,12 @@ export class WebServer {
88
89
  httpsServer.setTimeout(timeout);
89
90
  }
90
91
  httpsServer.listen(httpsPort, bindIp, () => {
91
- logger.info(`Https Server is started: https://localhost:${httpsPort}`);
92
+ logger.info(`Https Server ${cluster.worker ? cluster.worker.id : -1} is started: https://localhost:${httpsPort}`);
92
93
  });
93
94
  httpsServer.on('error', (error: any) => {
94
95
  logger.error('Error occurred on https server', error);
95
96
  });
97
+
96
98
  return httpsServer;
97
99
  }
98
100
  }