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 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;