UNPKG

@foxpage/foxpage-manager

Version:

foxpage resource manager

353 lines (352 loc) 12.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeResource = exports.ManagerImpl = void 0; const lodash_1 = __importDefault(require("lodash")); const foxpage_plugin_1 = require("@foxpage/foxpage-plugin"); const foxpage_shared_1 = require("@foxpage/foxpage-shared"); const service_1 = require("./data-service/service"); const api_1 = require("./api"); const application_1 = require("./application"); const common_1 = require("./common"); const plugin_1 = require("./plugin"); const reporter_1 = require("./reporter"); /** * manager * * @export * @class Manager */ class ManagerImpl { constructor(opt) { var _a, _b; /** * managed application * * @type {Map<string, Application>} */ this.applicationMap = new Map(); /** * application slug map * for get app by name fast * @private * @type {Map<string, string>} */ this.applicationSlugMap = new Map(); /** * application route & slug maps * for get app slug by routes * @private * @type {Map<string, RouteSlugData[]>} */ this.appRouteSlugMap = new Map(); (0, reporter_1.initReporter)(opt.reporter); this.pluginDir = opt.commonPluginDir || process.cwd(); this.pluginManager = new plugin_1.PluginManagerImpl({ plugins: opt.plugins, baseDir: this.pluginDir, api: (0, api_1.getApis)(), mode: foxpage_plugin_1.Mode.DISTRIBUTION, }); const pluginCost = (_a = opt.reporter) === null || _a === void 0 ? void 0 : _a.cost('pluginLoadCost'); this.pluginManager.loadPlugins(); const pluginCostTime = pluginCost === null || pluginCost === void 0 ? void 0 : pluginCost(); (_b = opt.reporter) === null || _b === void 0 ? void 0 : _b.appReporter('common', 'pluginLoadCost', pluginCostTime); this.options = opt; this.settings = opt.settings; this.messages = new foxpage_shared_1.Messages(); } get hooks() { return this.pluginManager.getHooks(); } /** * prepare * load plugins * * @memberof ManagerImpl */ async prepare(opt) { var _a; // update opt if (opt) { this.options = opt; this.settings = opt.settings; } // init logger: for bind logger hook await (0, common_1.initLogger)(this.hooks, (_a = this.options) === null || _a === void 0 ? void 0 : _a.loggerConfig); this.logger = (0, common_1.createLogger)('Manager', this.options); } /** * new applications * * @param {ManagerOption['apps']} appMates */ async registerApplications(appMates) { var _a, _b, _c, _d, _e; const appIds = Array.from(new Set(appMates.map(item => item.appId))); (_a = this.logger) === null || _a === void 0 ? void 0 : _a.info('will register apps:', appIds); try { const apps = await service_1.foxpageDataService.fetchApps(appIds); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.info('get apps'); (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug('get apps:', JSON.stringify(apps)); const appMateMap = new Map(); appMates.forEach(item => { appMateMap.set(item.appId, item); }); const batch = apps.map(async (app) => await this.registerApplication(app, appMateMap.get(app.id))); const result = await (await Promise.all(batch)).filter(item => !item); if (result.length > 0) { throw new Error('init applications failed'); } // succeed (_d = this.logger) === null || _d === void 0 ? void 0 : _d.info('init applications succeed'); } catch (e) { const msg = e.message; (_e = this.logger) === null || _e === void 0 ? void 0 : _e.error(msg); throw new Error(msg); } } /** * un register apps * * @param {string[]} appIds */ unRegisterApplications(appIds) { appIds.forEach(appId => { const app = this.getApplication(appId); if (app) { app.destroy(); this.applicationMap.delete(appId); this.applicationSlugMap.delete(app.slug); } }); } /** * remove applications from manager * * @param {string[]} [appIds=[]] */ removeApplications(appIds = []) { this.unRegisterApplications(appIds); } /** * if exist application * * @param {string} appId * @return {*} {boolean} */ existApplication(appId) { return this.applicationMap.has(appId); } /** * if exist application via app slug * * @param {string} slug * @return {*} {boolean} */ /** */ existApplicationBySlug(slug) { return this.applicationSlugMap.has(slug); } /** * find application via appId * * @param {string} appId * @return {(Application | undefined)} */ getApplication(appId) { return this.applicationMap.get(appId); } /** * get application via app slug * * @param {string} slug * @return {*} {(Application | undefined)} */ getApplicationBySlug(slug) { const appId = this.applicationSlugMap.get(slug); if (!appId) { return undefined; } return this.getApplication(appId); } /** * get application via path * @param {string} pathname * @return {*} {(Application | undefined)} */ getApplicationByPath(pathname) { let matches = []; Array.from(this.appRouteSlugMap.keys()).forEach(item => { if (pathname.startsWith(item)) { matches = matches.concat(this.appRouteSlugMap.get(item) || []); } }); matches = matches .filter(item => !item.route.exact || (item.route.exact && item.route.path === pathname)) .sort((pre, next) => (next.route.weight || 100) - (pre.route.weight || 100)); const matched = matches[0]; if (matched) { const app = this.getApplicationBySlug(matched.slug); return app ? { app, matchedRoute: matched.route } : undefined; } const slug = pathname.split('/')[1]; const app = this.getApplicationBySlug(slug); return app ? { app } : undefined; } /** * find application list * * @param {string[]} [appIds=[]] * @return {Application[]} */ getApplications(appIds) { if (!appIds) { return Array.from(this.applicationMap.values()); } return appIds.map(appId => this.getApplication(appId)).filter(Boolean); } /** * clear all data */ clear() { this.unRegisterApplications(Array.from(this.applicationSlugMap.values())); this.messages = new foxpage_shared_1.Messages(); } async registerApplication(app, metaData) { var _a, _b, _c; const { id: appId } = app; if (!this.existApplication(app.id) && !this.existApplicationBySlug(app.slug)) { const { plugins, hooks, configs = {}, resources = [] } = metaData || {}; const appConfigs = this.generateAppConfig(app, { plugins, pluginDir: this.pluginDir, hooks, configs, resources }); const appInstance = new application_1.ApplicationImpl(app, appConfigs); appInstance.setMaxListeners(20); this.cache(appInstance); try { // application prepare await appInstance.prepare(); (_a = this.logger) === null || _a === void 0 ? void 0 : _a.info(`application@${appId} init succeed`); return true; } catch (e) { this.unRegisterApplications([appId]); (_b = this.logger) === null || _b === void 0 ? void 0 : _b.warn(`application@${appId} init failed`, e); return false; } } else { const msg = `application@${appId} init failed: exist this appId or slug`; this.messages.push(msg); (_c = this.logger) === null || _c === void 0 ? void 0 : _c.error(msg); return false; } } initAppSourceScheduleStatus(enable) { return (enable === undefined || enable) && this.settings.openSourceUpdate; } generateAppConfig(app, options) { var _a; // merge online settings & local settings // TODO: app.settings const appConfigs = Object.assign({}, options, app.settings); if (!appConfigs.configs) { appConfigs.configs = {}; } appConfigs.configs['schedule.enable'] = this.initAppSourceScheduleStatus((_a = appConfigs.configs) === null || _a === void 0 ? void 0 : _a['schedule.enable']); // merge resource const localRes = localResFormate(appConfigs.resources || []); const mergedRes = (0, exports.mergeResource)(app.resources || [], localRes); appConfigs.resourceMap = mergedRes; // TODO: need provider hook logic if (!appConfigs.hooks) { appConfigs.hooks = {}; } appConfigs.hooks.sourceUpdateHook = this.settings.sourceUpdateHook; return appConfigs; } cache(app) { this.applicationMap.set(app.appId, app); this.applicationSlugMap.set(app.slug, app.appId); // cached by customize routes const { routes = [] } = app.configs; routes.forEach(route => { var _a; const { path, enable = true } = route || {}; if (path && enable) { if (!this.appRouteSlugMap.get(path)) { this.appRouteSlugMap.set(path, []); } (_a = this.appRouteSlugMap.get(path)) === null || _a === void 0 ? void 0 : _a.push({ route, slug: app.slug }); } }); } } exports.ManagerImpl = ManagerImpl; /** * merge resource * * @param {FPApplication['resources']} remoteRes * @param {AppResource[]} localRes * @return {*} {Map<string, AppResource>} */ const mergeResource = (remoteRes, localRes) => { const resourceMap = new Map(); // merge local & remote configs // local > remote\ lodash_1.default.unionBy(localRes, remoteRes, 'name').forEach(item => { const { name, detail, details = [] } = item; // width details if (details.length > 0) { resourceMap.set(name, { name, details: details.sort((a, b) => (b.weight || 0) - (a.weight || 0)), }); } else if (detail) { resourceMap.set(name, { name, details: [detail], }); } }); return resourceMap; }; exports.mergeResource = mergeResource; /** * formate local res * for support older version configs * * @param {AppResource[]} localRes * @return {*} {AppResource[]} */ const localResFormate = (localRes) => { const formatted = {}; // formate local res: Compatible with older versions localRes.forEach(item => { const { name, host, downloadHost, downloadProxy, weight = 0, details = [] } = item; let _details = []; if (details.length === 0) { const _detail = { host, downloadHost, downloadProxy, weight, }; _details.push(_detail); } else { _details = details; } const _local = formatted[name]; if (_local) { formatted[name].details = _details.concat(_local.details || []); } else { formatted[name] = { name, details: _details }; } }); return Object.values(formatted); };