UNPKG

@rocket.chat/apps-engine

Version:

The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.

947 lines 42.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPermissionsByAppId = exports.AppManager = void 0; const buffer_1 = require("buffer"); const ProxiedApp_1 = require("./ProxiedApp"); const bridges_1 = require("./bridges"); const AppStatus_1 = require("../definition/AppStatus"); const metadata_1 = require("../definition/metadata"); const users_1 = require("../definition/users"); const compiler_1 = require("./compiler"); const errors_1 = require("./errors"); const InvalidInstallationError_1 = require("./errors/InvalidInstallationError"); const managers_1 = require("./managers"); const AppRuntimeManager_1 = require("./managers/AppRuntimeManager"); const AppSignatureManager_1 = require("./managers/AppSignatureManager"); const UIActionButtonManager_1 = require("./managers/UIActionButtonManager"); const AppPermissions_1 = require("./permissions/AppPermissions"); const storage_1 = require("./storage"); const AppSourceStorage_1 = require("./storage/AppSourceStorage"); const IAppStorageItem_1 = require("./storage/IAppStorageItem"); class AppManager { constructor({ metadataStorage, logStorage, bridges, sourceStorage }) { // Singleton style. There can only ever be one AppManager instance if (typeof AppManager.Instance !== 'undefined') { throw new Error('There is already a valid AppManager instance'); } if (metadataStorage instanceof storage_1.AppMetadataStorage) { this.appMetadataStorage = metadataStorage; } else { throw new Error('Invalid instance of the AppMetadataStorage'); } if (logStorage instanceof storage_1.AppLogStorage) { this.logStorage = logStorage; } else { throw new Error('Invalid instance of the AppLogStorage'); } if (bridges instanceof bridges_1.AppBridges) { this.bridges = bridges; } else { throw new Error('Invalid instance of the AppBridges'); } if (sourceStorage instanceof AppSourceStorage_1.AppSourceStorage) { this.appSourceStorage = sourceStorage; } else { throw new Error('Invalid instance of the AppSourceStorage'); } this.apps = new Map(); this.parser = new compiler_1.AppPackageParser(); this.compiler = new compiler_1.AppCompiler(); this.accessorManager = new managers_1.AppAccessorManager(this); this.listenerManager = new managers_1.AppListenerManager(this); this.commandManager = new managers_1.AppSlashCommandManager(this); this.apiManager = new managers_1.AppApiManager(this); this.externalComponentManager = new managers_1.AppExternalComponentManager(); this.settingsManager = new managers_1.AppSettingsManager(this); this.licenseManager = new managers_1.AppLicenseManager(this); this.schedulerManager = new managers_1.AppSchedulerManager(this); this.uiActionButtonManager = new UIActionButtonManager_1.UIActionButtonManager(this); this.videoConfProviderManager = new managers_1.AppVideoConfProviderManager(this); this.signatureManager = new AppSignatureManager_1.AppSignatureManager(this); this.runtime = new AppRuntimeManager_1.AppRuntimeManager(this); this.isLoaded = false; AppManager.Instance = this; } /** Gets the instance of the storage connector. */ getStorage() { return this.appMetadataStorage; } /** Gets the instance of the log storage connector. */ getLogStorage() { return this.logStorage; } /** Gets the instance of the App package parser. */ getParser() { return this.parser; } /** Gets the compiler instance. */ getCompiler() { return this.compiler; } /** Gets the accessor manager instance. */ getAccessorManager() { return this.accessorManager; } /** Gets the instance of the Bridge manager. */ getBridges() { return this.bridges; } /** Gets the instance of the listener manager. */ getListenerManager() { return this.listenerManager; } /** Gets the command manager's instance. */ getCommandManager() { return this.commandManager; } getVideoConfProviderManager() { return this.videoConfProviderManager; } getLicenseManager() { return this.licenseManager; } /** Gets the api manager's instance. */ getApiManager() { return this.apiManager; } /** Gets the external component manager's instance. */ getExternalComponentManager() { return this.externalComponentManager; } /** Gets the manager of the settings, updates and getting. */ getSettingsManager() { return this.settingsManager; } getSchedulerManager() { return this.schedulerManager; } getUIActionButtonManager() { return this.uiActionButtonManager; } getSignatureManager() { return this.signatureManager; } getRuntime() { return this.runtime; } /** Gets whether the Apps have been loaded or not. */ areAppsLoaded() { return this.isLoaded; } setSourceStorage(storage) { this.appSourceStorage = storage; } /** * Goes through the entire loading up process. * Expect this to take some time, as it goes through a very * long process of loading all the Apps up. */ load() { return __awaiter(this, void 0, void 0, function* () { // You can not load the AppManager system again // if it has already been loaded. if (this.isLoaded) { return true; } const items = yield this.appMetadataStorage.retrieveAll(); for (const item of items.values()) { try { const appPackage = yield this.appSourceStorage.fetch(item); const unpackageResult = yield this.getParser().unpackageApp(appPackage); const app = yield this.getCompiler().toSandBox(this, item, unpackageResult); this.apps.set(item.id, app); } catch (e) { console.warn(`Error while compiling the App "${item.info.name} (${item.id})":`); console.error(e); const prl = new ProxiedApp_1.ProxiedApp(this, item, { // Maybe we should have an "EmptyRuntime" class for this? getStatus() { return Promise.resolve(AppStatus_1.AppStatus.COMPILER_ERROR_DISABLED); }, }); this.apps.set(item.id, prl); } } this.isLoaded = true; return true; }); } enableAll() { return __awaiter(this, void 0, void 0, function* () { const affs = []; // Let's initialize them for (const rl of this.apps.values()) { const aff = new compiler_1.AppFabricationFulfillment(); aff.setAppInfo(rl.getInfo()); aff.setImplementedInterfaces(rl.getImplementationList()); aff.setApp(rl); affs.push(aff); if (AppStatus_1.AppStatusUtils.isDisabled(yield rl.getStatus())) { // Usually if an App is disabled before it's initialized, // then something (such as an error) occured while // it was compiled or something similar. // We still have to validate its license, though yield rl.validateLicense(); continue; } yield this.initializeApp(rl.getStorageItem(), rl, false, true).catch(console.error); } // Let's ensure the required settings are all set for (const rl of this.apps.values()) { if (AppStatus_1.AppStatusUtils.isDisabled(yield rl.getStatus())) { continue; } if (!this.areRequiredSettingsSet(rl.getStorageItem())) { yield rl.setStatus(AppStatus_1.AppStatus.INVALID_SETTINGS_DISABLED).catch(console.error); } } // Now let's enable the apps which were once enabled // but are not currently disabled. for (const app of this.apps.values()) { const status = yield app.getStatus(); if (!AppStatus_1.AppStatusUtils.isDisabled(status) && AppStatus_1.AppStatusUtils.isEnabled(app.getPreviousStatus())) { yield this.enableApp(app.getStorageItem(), app, true, app.getPreviousStatus() === AppStatus_1.AppStatus.MANUALLY_ENABLED).catch(console.error); } else if (!AppStatus_1.AppStatusUtils.isError(status)) { this.listenerManager.lockEssentialEvents(app); this.uiActionButtonManager.clearAppActionButtons(app.getID()); } } return affs; }); } unload(isManual) { return __awaiter(this, void 0, void 0, function* () { // If the AppManager hasn't been loaded yet, then // there is nothing to unload if (!this.isLoaded) { return; } for (const app of this.apps.values()) { const status = yield app.getStatus(); if (status === AppStatus_1.AppStatus.INITIALIZED) { yield this.purgeAppConfig(app); } else if (!AppStatus_1.AppStatusUtils.isDisabled(status)) { yield this.disable(app.getID(), isManual ? AppStatus_1.AppStatus.MANUALLY_DISABLED : AppStatus_1.AppStatus.DISABLED); } this.listenerManager.releaseEssentialEvents(app); app.getDenoRuntime().stopApp(); } // Remove all the apps from the system now that we have unloaded everything this.apps.clear(); this.isLoaded = false; }); } /** Gets the Apps which match the filter passed in. */ get(filter) { return __awaiter(this, void 0, void 0, function* () { let rls = []; if (typeof filter === 'undefined') { this.apps.forEach((rl) => rls.push(rl)); return rls; } let nothing = true; if (typeof filter.enabled === 'boolean' && filter.enabled) { for (const rl of this.apps.values()) { if (AppStatus_1.AppStatusUtils.isEnabled(yield rl.getStatus())) { rls.push(rl); } } nothing = false; } if (typeof filter.disabled === 'boolean' && filter.disabled) { for (const rl of this.apps.values()) { if (AppStatus_1.AppStatusUtils.isDisabled(yield rl.getStatus())) { rls.push(rl); } } nothing = false; } if (nothing) { this.apps.forEach((rl) => rls.push(rl)); } if (typeof filter.ids !== 'undefined') { rls = rls.filter((rl) => filter.ids.includes(rl.getID())); } if (typeof filter.installationSource !== 'undefined') { rls = rls.filter((rl) => rl.getInstallationSource() === filter.installationSource); } if (typeof filter.name === 'string') { rls = rls.filter((rl) => rl.getName() === filter.name); } else if (filter.name instanceof RegExp) { rls = rls.filter((rl) => filter.name.test(rl.getName())); } return rls; }); } /** Gets a single App by the id passed in. */ getOneById(appId) { return this.apps.get(appId); } getPermissionsById(appId) { const app = this.apps.get(appId); if (!app) { return []; } const { permissionsGranted } = app.getStorageItem(); return permissionsGranted || AppPermissions_1.defaultPermissions; } enable(id) { return __awaiter(this, void 0, void 0, function* () { const rl = this.apps.get(id); if (!rl) { throw new Error(`No App by the id "${id}" exists.`); } const status = yield rl.getStatus(); if (AppStatus_1.AppStatusUtils.isEnabled(status)) { return true; } if (status === AppStatus_1.AppStatus.COMPILER_ERROR_DISABLED) { throw new Error('The App had compiler errors, can not enable it.'); } const storageItem = yield this.appMetadataStorage.retrieveOne(id); if (!storageItem) { throw new Error(`Could not enable an App with the id of "${id}" as it doesn't exist.`); } const isSetup = yield this.runStartUpProcess(storageItem, rl, true, false); if (isSetup) { storageItem.status = yield rl.getStatus(); // This is async, but we don't care since it only updates in the database // and it should not mutate any properties we care about yield this.appMetadataStorage.update(storageItem).catch(); } return isSetup; }); } disable(id_1) { return __awaiter(this, arguments, void 0, function* (id, status = AppStatus_1.AppStatus.DISABLED, silent) { if (!AppStatus_1.AppStatusUtils.isDisabled(status)) { throw new Error('Invalid disabled status'); } const app = this.apps.get(id); if (!app) { throw new Error(`No App by the id "${id}" exists.`); } if (AppStatus_1.AppStatusUtils.isEnabled(yield app.getStatus())) { yield app.call(metadata_1.AppMethod.ONDISABLE).catch((e) => console.warn('Error while disabling:', e)); } yield this.purgeAppConfig(app, { keepScheduledJobs: true }); yield app.setStatus(status, silent); const storageItem = yield this.appMetadataStorage.retrieveOne(id); app.getStorageItem().marketplaceInfo = storageItem.marketplaceInfo; yield app.validateLicense().catch(); storageItem.status = yield app.getStatus(); // This is async, but we don't care since it only updates in the database // and it should not mutate any properties we care about yield this.appMetadataStorage.update(storageItem).catch(); return true; }); } migrate(id) { return __awaiter(this, void 0, void 0, function* () { const app = this.apps.get(id); if (!app) { throw new Error(`No App by the id "${id}" exists.`); } yield app.call(metadata_1.AppMethod.ONUPDATE).catch((e) => console.warn('Error while migrating:', e)); yield this.purgeAppConfig(app, { keepScheduledJobs: true }); const storageItem = yield this.appMetadataStorage.retrieveOne(id); app.getStorageItem().marketplaceInfo = storageItem.marketplaceInfo; yield app.validateLicense().catch(); storageItem.migrated = true; storageItem.signature = yield this.getSignatureManager().signApp(storageItem); // This is async, but we don't care since it only updates in the database // and it should not mutate any properties we care about const stored = yield this.appMetadataStorage.update(storageItem).catch(); yield this.updateLocal(stored, app); yield this.bridges .getAppActivationBridge() .doAppUpdated(app) .catch(() => { }); return true; }); } addLocal(appId) { return __awaiter(this, void 0, void 0, function* () { const storageItem = yield this.appMetadataStorage.retrieveOne(appId); if (!storageItem) { throw new Error(`App with id ${appId} couldn't be found`); } const appPackage = yield this.appSourceStorage.fetch(storageItem); if (!appPackage) { throw new Error(`Package file for app "${storageItem.info.name}" (${appId}) couldn't be found`); } const parsedPackage = yield this.getParser().unpackageApp(appPackage); const app = yield this.getCompiler().toSandBox(this, storageItem, parsedPackage); this.apps.set(app.getID(), app); yield this.loadOne(appId); }); } add(appPackage, installationParameters) { return __awaiter(this, void 0, void 0, function* () { const { enable = true, marketplaceInfo, permissionsGranted, user } = installationParameters; const aff = new compiler_1.AppFabricationFulfillment(); const result = yield this.getParser().unpackageApp(appPackage); const undoSteps = []; aff.setAppInfo(result.info); aff.setImplementedInterfaces(result.implemented.getValues()); const descriptor = { id: result.info.id, info: result.info, status: AppStatus_1.AppStatus.UNKNOWN, settings: {}, implemented: result.implemented.getValues(), installationSource: marketplaceInfo ? IAppStorageItem_1.AppInstallationSource.MARKETPLACE : IAppStorageItem_1.AppInstallationSource.PRIVATE, marketplaceInfo, permissionsGranted, languageContent: result.languageContent, }; try { descriptor.sourcePath = yield this.appSourceStorage.store(descriptor, appPackage); undoSteps.push(() => this.appSourceStorage.remove(descriptor)); } catch (error) { aff.setStorageError('Failed to store app package'); return aff; } // Now that is has all been compiled, let's get the // the App instance from the source. const app = yield this.getCompiler().toSandBox(this, descriptor, result); undoSteps.push(() => this.getRuntime() .stopRuntime(app.getDenoRuntime()) .catch(() => { })); // Create a user for the app try { yield this.createAppUser(result.info); undoSteps.push(() => this.removeAppUser(app)); } catch (err) { aff.setAppUserError({ username: `${result.info.nameSlug}.bot`, message: 'Failed to create an app user for this app.', }); yield Promise.all(undoSteps.map((undoer) => undoer())); return aff; } descriptor.signature = yield this.getSignatureManager().signApp(descriptor); const created = yield this.appMetadataStorage.create(descriptor); if (!created) { aff.setStorageError('Failed to create the App, the storage did not return it.'); yield Promise.all(undoSteps.map((undoer) => undoer())); return aff; } app.getStorageItem()._id = created._id; this.apps.set(app.getID(), app); aff.setApp(app); // Let everyone know that the App has been added yield this.bridges .getAppActivationBridge() .doAppAdded(app) .catch(() => { // If an error occurs during this, oh well. }); yield this.installApp(created, app, user); // Should enable === true, then we go through the entire start up process // Otherwise, we only initialize it. if (enable) { // Start up the app yield this.runStartUpProcess(created, app, false, false); } else { yield this.initializeApp(created, app, true); } return aff; }); } /** * Uninstalls specified app from the server and remove * all database records regarding it * * @returns the instance of the removed ProxiedApp */ remove(id, uninstallationParameters) { return __awaiter(this, void 0, void 0, function* () { const app = this.apps.get(id); const { user } = uninstallationParameters; // First remove the app yield this.uninstallApp(app, user); yield this.removeLocal(id); // Then let everyone know that the App has been removed yield this.bridges.getAppActivationBridge().doAppRemoved(app).catch(); return app; }); } /** * Removes the app instance from the local Apps container * and every type of data associated with it */ removeLocal(id) { return __awaiter(this, void 0, void 0, function* () { const app = this.apps.get(id); if (AppStatus_1.AppStatusUtils.isEnabled(yield app.getStatus())) { yield this.disable(id); } yield this.purgeAppConfig(app); this.listenerManager.releaseEssentialEvents(app); yield this.removeAppUser(app); yield this.bridges.getPersistenceBridge().purge(app.getID()); yield this.appMetadataStorage.remove(app.getID()); yield this.appSourceStorage.remove(app.getStorageItem()).catch(() => { }); // Errors here don't really prevent the process from dying, so we don't really need to do anything on the catch yield this.getRuntime() .stopRuntime(app.getDenoRuntime()) .catch(() => { }); this.apps.delete(app.getID()); }); } update(appPackage_1, permissionsGranted_1) { return __awaiter(this, arguments, void 0, function* (appPackage, permissionsGranted, updateOptions = { loadApp: true }) { var _a; const aff = new compiler_1.AppFabricationFulfillment(); const result = yield this.getParser().unpackageApp(appPackage); aff.setAppInfo(result.info); aff.setImplementedInterfaces(result.implemented.getValues()); const old = yield this.appMetadataStorage.retrieveOne(result.info.id); if (!old) { throw new Error('Can not update an App that does not currently exist.'); } // If there is any error during disabling, it doesn't really matter yield this.disable(old.id).catch(() => { }); const descriptor = Object.assign(Object.assign(Object.assign(Object.assign({}, old), { createdAt: old.createdAt, id: result.info.id, info: result.info, status: (yield ((_a = this.apps.get(old.id)) === null || _a === void 0 ? void 0 : _a.getStatus())) || old.status, languageContent: result.languageContent, settings: old.settings, implemented: result.implemented.getValues() }), (old.marketplaceInfo && { marketplaceInfo: old.marketplaceInfo })), (old.sourcePath && { sourcePath: old.sourcePath })); if (!permissionsGranted) { delete descriptor.permissionsGranted; } else { descriptor.permissionsGranted = permissionsGranted; } try { descriptor.sourcePath = yield this.appSourceStorage.update(descriptor, appPackage); } catch (error) { aff.setStorageError('Failed to storage app package'); return aff; } descriptor.signature = yield this.signatureManager.signApp(descriptor); const stored = yield this.appMetadataStorage.update(descriptor); // Errors here don't really prevent the process from dying, so we don't really need to do anything on the catch yield this.getRuntime() .stopRuntime(this.apps.get(old.id).getDenoRuntime()) .catch(() => { }); const app = yield this.getCompiler().toSandBox(this, descriptor, result); // Ensure there is an user for the app try { yield this.createAppUser(result.info); } catch (err) { aff.setAppUserError({ username: `${result.info.nameSlug}.bot`, message: 'Failed to create an app user for this app.', }); return aff; } aff.setApp(app); if (updateOptions.loadApp) { const shouldEnableApp = AppStatus_1.AppStatusUtils.isEnabled(old.status); if (shouldEnableApp) { yield this.updateAndStartupLocal(stored, app); } else { yield this.updateAndInitializeLocal(stored, app); } yield this.bridges .getAppActivationBridge() .doAppUpdated(app) .catch(() => { }); } yield this.updateApp(app, updateOptions.user, old.info.version); return aff; }); } /** * Updates the local instance of an app. * * If the second parameter is a Buffer of an app package, * unpackage and instantiate the app's main class * * With an instance of a ProxiedApp, start it up and replace * the reference in the local app collection */ updateLocal(stored, appPackageOrInstance) { return __awaiter(this, void 0, void 0, function* () { const app = yield (() => __awaiter(this, void 0, void 0, function* () { if (appPackageOrInstance instanceof buffer_1.Buffer) { const parseResult = yield this.getParser().unpackageApp(appPackageOrInstance); // Errors here don't really prevent the process from dying, so we don't really need to do anything on the catch yield this.getRuntime() .stopRuntime(this.apps.get(stored.id).getDenoRuntime()) .catch(() => { }); return this.getCompiler().toSandBox(this, stored, parseResult); } if (appPackageOrInstance instanceof ProxiedApp_1.ProxiedApp) { return appPackageOrInstance; } }))(); yield this.purgeAppConfig(app, { keepScheduledJobs: true }); this.apps.set(app.getID(), app); return app; }); } updateAndStartupLocal(stored, appPackageOrInstance) { return __awaiter(this, void 0, void 0, function* () { const app = yield this.updateLocal(stored, appPackageOrInstance); yield this.runStartUpProcess(stored, app, false, true); }); } updateAndInitializeLocal(stored, appPackageOrInstance) { return __awaiter(this, void 0, void 0, function* () { const app = yield this.updateLocal(stored, appPackageOrInstance); yield this.initializeApp(stored, app, true, true); }); } getLanguageContent() { const langs = {}; this.apps.forEach((rl) => { const content = rl.getStorageItem().languageContent; Object.keys(content).forEach((key) => { langs[key] = Object.assign(langs[key] || {}, content[key]); }); }); return langs; } changeStatus(appId, status) { return __awaiter(this, void 0, void 0, function* () { switch (status) { case AppStatus_1.AppStatus.MANUALLY_DISABLED: case AppStatus_1.AppStatus.MANUALLY_ENABLED: break; default: throw new Error('Invalid status to change an App to, must be manually disabled or enabled.'); } const rl = this.apps.get(appId); if (!rl) { throw new Error('Can not change the status of an App which does not currently exist.'); } if (AppStatus_1.AppStatusUtils.isEnabled(status)) { // Then enable it if (AppStatus_1.AppStatusUtils.isEnabled(yield rl.getStatus())) { throw new Error('Can not enable an App which is already enabled.'); } yield this.enable(rl.getID()); } else { if (!AppStatus_1.AppStatusUtils.isEnabled(yield rl.getStatus())) { throw new Error('Can not disable an App which is not enabled.'); } yield this.disable(rl.getID(), AppStatus_1.AppStatus.MANUALLY_DISABLED); } return rl; }); } updateAppsMarketplaceInfo(appsOverview) { return __awaiter(this, void 0, void 0, function* () { yield Promise.all(appsOverview.map((_a) => __awaiter(this, [_a], void 0, function* ({ latest: appInfo }) { var _b; if (!appInfo.subscriptionInfo) { return; } const app = this.apps.get(appInfo.id); if (!app) { return; } const appStorageItem = app.getStorageItem(); const { subscriptionInfo } = ((_b = appStorageItem.marketplaceInfo) === null || _b === void 0 ? void 0 : _b[0]) || {}; if (subscriptionInfo && subscriptionInfo.license.license === appInfo.subscriptionInfo.license.license) { return; } appStorageItem.marketplaceInfo[0].subscriptionInfo = appInfo.subscriptionInfo; return this.appMetadataStorage.update(appStorageItem); }))).catch(); const queue = []; this.apps.forEach((app) => queue.push(app .validateLicense() .then(() => __awaiter(this, void 0, void 0, function* () { if ((yield app.getStatus()) !== AppStatus_1.AppStatus.INVALID_LICENSE_DISABLED) { return; } return app.setStatus(AppStatus_1.AppStatus.DISABLED); })) .catch((error) => __awaiter(this, void 0, void 0, function* () { if (!(error instanceof errors_1.InvalidLicenseError)) { console.error(error); return; } yield this.purgeAppConfig(app); return app.setStatus(AppStatus_1.AppStatus.INVALID_LICENSE_DISABLED); })) .then(() => __awaiter(this, void 0, void 0, function* () { const status = yield app.getStatus(); if (status === app.getPreviousStatus()) { return; } const storageItem = app.getStorageItem(); storageItem.status = status; return this.appMetadataStorage.update(storageItem).catch(console.error); })))); yield Promise.all(queue); }); } /** * Goes through the entire loading up process. * * @param appId the id of the application to load */ loadOne(appId_1) { return __awaiter(this, arguments, void 0, function* (appId, silenceStatus = false) { const rl = this.apps.get(appId); if (!rl) { throw new Error(`No App found by the id of: "${appId}"`); } const item = rl.getStorageItem(); yield this.initializeApp(item, rl, false, silenceStatus); if (!this.areRequiredSettingsSet(item)) { yield rl.setStatus(AppStatus_1.AppStatus.INVALID_SETTINGS_DISABLED); } if (!AppStatus_1.AppStatusUtils.isDisabled(yield rl.getStatus()) && AppStatus_1.AppStatusUtils.isEnabled(rl.getPreviousStatus())) { yield this.enableApp(item, rl, false, rl.getPreviousStatus() === AppStatus_1.AppStatus.MANUALLY_ENABLED, silenceStatus); } return this.apps.get(item.id); }); } runStartUpProcess(storageItem, app, isManual, silenceStatus) { return __awaiter(this, void 0, void 0, function* () { if ((yield app.getStatus()) !== AppStatus_1.AppStatus.INITIALIZED) { const isInitialized = yield this.initializeApp(storageItem, app, true, silenceStatus); if (!isInitialized) { return false; } } if (!this.areRequiredSettingsSet(storageItem)) { yield app.setStatus(AppStatus_1.AppStatus.INVALID_SETTINGS_DISABLED, silenceStatus); return false; } return this.enableApp(storageItem, app, true, isManual, silenceStatus); }); } installApp(_storageItem, app, user) { return __awaiter(this, void 0, void 0, function* () { let result; const context = { user }; try { yield app.call(metadata_1.AppMethod.ONINSTALL, context); result = true; } catch (e) { const status = AppStatus_1.AppStatus.ERROR_DISABLED; result = false; yield app.setStatus(status); } return result; }); } updateApp(app, user, oldAppVersion) { return __awaiter(this, void 0, void 0, function* () { let result; try { yield app.call(metadata_1.AppMethod.ONUPDATE, { oldAppVersion, user }); result = true; } catch (e) { const status = AppStatus_1.AppStatus.ERROR_DISABLED; result = false; yield app.setStatus(status); } return result; }); } initializeApp(storageItem_1, app_1) { return __awaiter(this, arguments, void 0, function* (storageItem, app, saveToDb = true, silenceStatus = false) { let result; try { yield app.validateLicense(); yield app.validateInstallation(); yield app.call(metadata_1.AppMethod.INITIALIZE); yield app.setStatus(AppStatus_1.AppStatus.INITIALIZED, silenceStatus); result = true; } catch (e) { let status = AppStatus_1.AppStatus.ERROR_DISABLED; if (e instanceof errors_1.InvalidLicenseError) { status = AppStatus_1.AppStatus.INVALID_LICENSE_DISABLED; } if (e instanceof InvalidInstallationError_1.InvalidInstallationError) { status = AppStatus_1.AppStatus.INVALID_INSTALLATION_DISABLED; } yield this.purgeAppConfig(app); result = false; yield app.setStatus(status, silenceStatus); // If some error has happened in initialization, like license or installations invalidation // we need to store this on the DB regardless of what the parameter requests saveToDb = true; } if (saveToDb) { // This is async, but we don't care since it only updates in the database // and it should not mutate any properties we care about storageItem.status = yield app.getStatus(); yield this.appMetadataStorage.update(storageItem).catch(); } return result; }); } purgeAppConfig(app_1) { return __awaiter(this, arguments, void 0, function* (app, opts = {}) { if (!opts.keepScheduledJobs) { yield this.schedulerManager.cleanUp(app.getID()); } this.listenerManager.unregisterListeners(app); this.listenerManager.lockEssentialEvents(app); yield this.commandManager.unregisterCommands(app.getID()); this.externalComponentManager.unregisterExternalComponents(app.getID()); yield this.apiManager.unregisterApis(app.getID()); this.accessorManager.purifyApp(app.getID()); this.uiActionButtonManager.clearAppActionButtons(app.getID()); this.videoConfProviderManager.unregisterProviders(app.getID()); }); } /** * Determines if the App's required settings are set or not. * Should a packageValue be provided and not empty, then it's considered set. */ areRequiredSettingsSet(storageItem) { let result = true; for (const setk of Object.keys(storageItem.settings)) { const sett = storageItem.settings[setk]; // If it's not required, ignore if (!sett.required) { continue; } if (sett.value !== 'undefined' || sett.packageValue !== 'undefined') { continue; } result = false; } return result; } enableApp(storageItem_1, app_1) { return __awaiter(this, arguments, void 0, function* (storageItem, app, saveToDb = true, isManual, silenceStatus = false) { let enable; let status = AppStatus_1.AppStatus.ERROR_DISABLED; try { yield app.validateLicense(); yield app.validateInstallation(); enable = (yield app.call(metadata_1.AppMethod.ONENABLE)); if (enable) { status = isManual ? AppStatus_1.AppStatus.MANUALLY_ENABLED : AppStatus_1.AppStatus.AUTO_ENABLED; } else { status = AppStatus_1.AppStatus.DISABLED; console.warn(`The App (${app.getID()}) disabled itself when being enabled. \nCheck the "onEnable" implementation for details.`); } } catch (e) { enable = false; if (e instanceof errors_1.InvalidLicenseError) { status = AppStatus_1.AppStatus.INVALID_LICENSE_DISABLED; } if (e instanceof InvalidInstallationError_1.InvalidInstallationError) { status = AppStatus_1.AppStatus.INVALID_INSTALLATION_DISABLED; } console.error(e); // If some error has happened during enabling, like license or installations invalidation // we need to store this on the DB regardless of what the parameter requests saveToDb = true; } if (enable) { yield this.commandManager.registerCommands(app.getID()); this.externalComponentManager.registerExternalComponents(app.getID()); yield this.apiManager.registerApis(app.getID()); this.listenerManager.registerListeners(app); this.listenerManager.releaseEssentialEvents(app); this.videoConfProviderManager.registerProviders(app.getID()); } else { yield this.purgeAppConfig(app); } if (saveToDb) { storageItem.status = status; // This is async, but we don't care since it only updates in the database // and it should not mutate any properties we care about yield this.appMetadataStorage.update(storageItem).catch(); } yield app.setStatus(status, silenceStatus); return enable; }); } createAppUser(appInfo) { return __awaiter(this, void 0, void 0, function* () { const appUser = yield this.bridges.getUserBridge().getAppUser(appInfo.id); if (appUser) { return appUser.id; } const userData = { username: `${appInfo.nameSlug}.bot`, name: appInfo.name, roles: ['app'], appId: appInfo.id, type: users_1.UserType.APP, status: 'online', isEnabled: true, }; return this.bridges.getUserBridge().create(userData, appInfo.id, { avatarUrl: appInfo.iconFileContent || appInfo.iconFile, joinDefaultChannels: true, sendWelcomeEmail: false, }); }); } removeAppUser(app) { return __awaiter(this, void 0, void 0, function* () { const appUser = yield this.bridges.getUserBridge().getAppUser(app.getID()); if (!appUser) { return true; } return this.bridges.getUserBridge().remove(appUser, app.getID()); }); } uninstallApp(app, user) { return __awaiter(this, void 0, void 0, function* () { let result; const context = { user }; try { yield app.call(metadata_1.AppMethod.ONUNINSTALL, context); result = true; } catch (e) { const status = AppStatus_1.AppStatus.ERROR_DISABLED; result = false; yield app.setStatus(status); } return result; }); } } exports.AppManager = AppManager; const getPermissionsByAppId = (appId) => { if (!AppManager.Instance) { console.error('AppManager should be instantiated first'); return []; } return AppManager.Instance.getPermissionsById(appId); }; exports.getPermissionsByAppId = getPermissionsByAppId; //# sourceMappingURL=AppManager.js.map