@foxpage/foxpage-manager
Version:
foxpage resource manager
353 lines (352 loc) • 12.2 kB
JavaScript
"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);
};