15815213711
2024-08-26 67b8b6731811983447e053d4396b3708c14dfe3c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
const path = require('path');
const fs = require('fs');
const assert = require('assert');
const is = require('is-type-of');
const Koa = require('koa');
const koaServe = require('koa-static');
const https = require('https');
const BaseApp = require('./baseApp');
const Log = require('../log');
const CoreElectron = require('../electron');
const CoreElectronApp = require('../electron/app');
const CoreElectronWindow = require('../electron/window');
const Conf = require('../config');
const Ps = require('../ps');
const Socket = require('../socket');
const GetPort = require('../utils/get-port');
const UtilsHelper = require('../utils/helper');
const HttpClient = require('../httpclient');
const Cross = require('../cross');
const Html = require('../html');
 
class EeApp extends BaseApp {
  constructor(options = {}) {
    super(options);
 
    // 兼容旧的api
    this.electron = CoreElectron;
    this.mainWindow;
  }
 
  /**
   * 生成端口
   */
  async createPorts() {
    if (Ps.isFrameworkMode() && Conf.isWebProtocol(this.config.mainServer)) {
      const mainPort = await GetPort({port: parseInt(this.config.mainServer.port)});
      process.env.EE_MAIN_PORT = mainPort;
      this.config.mainServer.port = mainPort;
    }
 
    if (this.config.socketServer.enable) {
      const socketPort = await GetPort({port: parseInt(this.config.socketServer.port)});
      process.env.EE_SOCKET_PORT = socketPort;
      this.config.socketServer.port = socketPort;
    }
    
    if (this.config.httpServer.enable) {
      const httpPort = await GetPort({port: parseInt(this.config.httpServer.port)});
      process.env.EE_HTTP_PORT = httpPort;
      this.config.httpServer.port = httpPort;
    }
    
    // [todo] 更新db配置 (system.json 不再主进程中使用了,后续可能在子进程中使用)
    Conf.setAll(this.config);
  }
 
  /**
   * 启动通信模块
   */
  async startSocket() {
    Socket.startAll(this);
  }
  
  /**
   * 启动跨语言服务
   */
  async crossService() {
    Cross.create();
  }
 
  /**
   * 创建electron应用
   */
  async createElectronApp() {
    if (!Ps.isFrameworkMode()) return;
    const newApp = CoreElectronApp.create();
    if (!newApp) {
      return
    }
 
    await this.crossService();
    
    await this.electronAppReady();
  }
    
  /**
   * 创建应用主窗口
   */
  async createWindow() {
 
    // 初始化一个主窗口
    this.mainWindow = CoreElectronWindow.getMainWindow();
 
    await this.windowReady();
  
    await this._loderAddons();
 
    await this._loderPreload();
 
    this.selectAppType();
  }
 
  /**
   * 应用类型 (远程、html、单页应用)
   */
  async selectAppType() {
    let type = '';
    let url = '';
 
    // 远程模式
    const remoteConfig = this.config.remoteUrl;
    if (remoteConfig.enable == true) {
      type = 'remote_web';
      url = remoteConfig.url;
      this.loadMainUrl(type, url);
      return;
    }
 
    const mainServer = this.config.mainServer;
 
    // 开发环境
    if (Ps.isDev()) {
      let modeInfo;
      let url;
      let load = 'url';
      const configFile = './electron/config/bin.js';
      let electronCfg = {};
 
      const isBin = UtilsHelper.checkConfig(configFile);
      if (isBin) {
        const binConfig = UtilsHelper.loadConfig(configFile);
        modeInfo = binConfig.dev.frontend;
        electronCfg = binConfig.dev.electron;
      } else {
        // 兼容旧的 developmentMode
        const developmentModeConfig = this.config.developmentMode;
        const selectMode = developmentModeConfig.default;
        modeInfo = developmentModeConfig.mode[selectMode];
      }
 
      url = modeInfo.protocol + modeInfo.hostname + ':' + modeInfo.port;
      if (Conf.isFileProtocol(modeInfo)) {
        url = path.join(this.config.homeDir, modeInfo.directory, modeInfo.indexPath);
        load = 'file';
      }
 
      // 检查 UI serve是否启动,先加载一个boot page
      if (load == 'url') {
        // loading page
        let lp = Html.getFilepath('boot.html');
        if (electronCfg.hasOwnProperty('loadingPage') && electronCfg.loadingPage != '') {
          lp = path.join(this.config.homeDir, electronCfg.loadingPage);
        }
        this._loadingPage(lp);
 
        const retryTimes = modeInfo.force === true ? 3 : 60;
        let count = 0;
        let frontendReady = false;
        const hc = new HttpClient();
        while(!frontendReady && count < retryTimes){
          await UtilsHelper.sleep(1 * 1000);
          try {
            await hc.request(url, {
              method: 'GET',
              timeout: 1000,
            });
            frontendReady = true;
          } catch(err) {
            // console.log('The frontend service is starting');
          }
 
          count++;
        }
 
        if (frontendReady == false && modeInfo.force !== true) {
          const bootFailurePage = Html.getFilepath('failure.html');
          this.mainWindow.loadFile(bootFailurePage);
          Log.coreLogger.error(`[ee-core] Please check the ${url} !`);
          return;
        }
      }
 
      this.loadMainUrl('spa', url, load);
      return;
    }
 
    // 生产环境
    // cross service takeover web
    if (mainServer.hasOwnProperty('takeover')) {
      await this._crossTakeover(mainServer)
      return
    }
 
    // 主进程
    if (mainServer.protocol == "") {
      return
    }
    if (Conf.isFileProtocol(mainServer)) {
      url = path.join(this.config.homeDir, mainServer.indexPath);
      this.loadMainUrl('spa', url, 'file');
    } else {
      this.loadLocalWeb('spa');
    }
  }
 
  /**
   * cross service takeover web
   */
  async _crossTakeover(mainCfg = {}) {
    const crossConfig = this.config.cross;
 
    // loading page
    if (mainCfg.hasOwnProperty('loadingPage')) {
      const lp = path.join(this.config.homeDir, mainCfg.loadingPage);
      this._loadingPage(lp);
    }
 
    // cross service url
    const service = mainCfg.takeover;
    if (!crossConfig.hasOwnProperty(service)) {
      throw new Error(`[ee-core] Please Check the value of mainServer.takeover in the config file !`);
    }
    // check service
    if (crossConfig[service].enable != true) {
      throw new Error(`[ee-core] Please Check the value of cross.${service} enable is true !`);
    }
 
    const entityName = crossConfig[service].name;
    const url = Cross.getUrl(entityName);
 
    let count = 0;
    let serviceReady = false;
    const hc = new HttpClient();
 
    // 循环检查
    const times = Ps.isDev() ? 20 : 100;
    const sleeptime = Ps.isDev() ? 1000 : 100;
    while(!serviceReady && count < times){
      await UtilsHelper.sleep(sleeptime);
      try {
        await hc.request(url, {
          method: 'GET',
          timeout: 100,
        });
        serviceReady = true;
      } catch(err) {
        //console.log('The cross service is starting');
      }
      count++;
    }
    //console.log('count:', count)
    if (serviceReady == false) {
      const bootFailurePage = Html.getFilepath('cross-failure.html');
      this.mainWindow.loadFile(bootFailurePage);
      throw new Error(`[ee-core] Please check cross service [${service}] ${url} !`)
    }
 
    Log.coreLogger.info(`[ee-core] cross service [${service}] is started successfully`);
    this.loadMainUrl('spa', url);
  }  
 
  /**
   * 加载本地前端资源
   */
  loadLocalWeb(mode, staticDir) {
    if (!staticDir) {
      staticDir = path.join(this.config.homeDir, 'public', 'dist')
    }
 
    const koaApp = new Koa();    
    koaApp.use(koaServe(staticDir));
 
    const mainServer = this.config.mainServer;
    let url = mainServer.protocol + mainServer.host + ':' + mainServer.port;
 
    const isHttps = mainServer.protocol == 'https://' ? true : false;
    if (isHttps) {
      const keyFile = path.join(this.config.homeDir, mainServer.ssl.key);
      const certFile = path.join(this.config.homeDir, mainServer.ssl.cert);
      assert(fs.existsSync(keyFile), 'ssl key file is required');
      assert(fs.existsSync(certFile), 'ssl cert file is required');
 
      const sslOpt = {
        key: fs.readFileSync(keyFile),
        cert: fs.readFileSync(certFile)
      };
      https.createServer(sslOpt, koaApp.callback()).listen(mainServer.port, (err) => {
        if (err) {
          Log.coreLogger.info('[ee-core] [lib/eeApp] createServer error: ', err);
          return
        }
        this.loadMainUrl(mode, url);
      });
    } else {
      // 使用 host port 
      const koaOpt = {
        host: mainServer.open ? undefined : mainServer.host, // 根据配置是否开放0.0.0.0,默认关闭,避免绑定到0.0.0.0
        port: mainServer.port
      }
      koaApp.listen(koaOpt, () => {
        this.loadMainUrl(mode, url);
      });
    }
  }
 
  /**
   * 主服务
   * @params load <string> value: "url" 、 "file"
   */
  loadMainUrl(type, url, load  =  'url') {
    const mainServer = this.config.mainServer;
    Log.coreLogger.info('[ee-core] Env: %s, Type: %s', this.config.env, type);
    Log.coreLogger.info('[ee-core] App running at: %s', url);
    if (load ==  'file')  {
      this.mainWindow.loadFile(url, mainServer.options)
      .then()
      .catch((err)=>{
        Log.coreLogger.error(`[ee-core] Please check the ${url} !`);
      });
    } else {
      this.mainWindow.loadURL(url, mainServer.options)
      .then()
      .catch((err)=>{
        Log.coreLogger.error(`[ee-core] Please check the ${url} !`);
      });
    }
  }
 
  /**
   * loading page
   */  
  _loadingPage(name) {
    if (!UtilsHelper.fileIsExist(name)) {
      return
    }
    this.mainWindow.loadFile(name);
  }
 
  /**
   * electron app退出
   */  
  async appQuit() {
    await this.beforeClose();
    CoreElectronApp.quit();
  }
 
  /**
   * 加载插件
   */
  async _loderAddons() {
    this.loader.loadAddons();
 
    // 注册主窗口Contents id
    const addonsCfg = this.config.addons;
    if (addonsCfg.window.enable && Ps.isFrameworkMode()) {
      const win = this.mainWindow;
      const addonWindow = this.addon.window;
      addonWindow.registerWCid('main', win.webContents.id);
    }
  }
 
  /**
   * 预加载模块
   */
  async _loderPreload() {
    let filepath = this.loader.resolveModule(path.join(this.config.baseDir, 'preload', 'index'));
    if (!filepath) return; 
    const fileObj = this.loader.loadFile(filepath);
    if (is.function(fileObj) && !is.generatorFunction(fileObj) && !is.asyncFunction(fileObj)) {
      fileObj();
    } else if (is.asyncFunction(fileObj)) {
      await fileObj();
    }
  }
 
  /**
   * module模式初始化
   */
  async InitModuleMode() {
    if (!Ps.isModuleMode()) return;
 
    await this._loderAddons();
 
    await this._loderPreload();
  }
 
  /**
   * electron app已经准备好,主窗口还未创建
   */
  async electronAppReady() {
    // do some things
  }
 
  /**
   * 主应用窗口已经创建
   */
  async windowReady() {
    // do some things
  }
 
  /**
   * app关闭之前
   */  
  async beforeClose() {
    // do some things
  }  
}
 
module.exports = EeApp;