...
 
Commits (2)
{
"name": "@rocket.chat/apps-engine",
"version": "1.5.0",
"version": "1.5.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "@rocket.chat/apps-engine",
"version": "1.5.0",
"version": "1.5.1",
"description": "The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.",
"main": "index",
"typings": "index",
......
......@@ -195,7 +195,7 @@ export class AppManager {
continue;
}
await this.initializeApp(items.get(rl.getID()), rl, true);
await this.initializeApp(items.get(rl.getID()), rl, true).catch(console.error);
}
// Let's ensure the required settings are all set
......@@ -205,7 +205,7 @@ export class AppManager {
}
if (!this.areRequiredSettingsSet(rl.getStorageItem())) {
await rl.setStatus(AppStatus.INVALID_SETTINGS_DISABLED);
await rl.setStatus(AppStatus.INVALID_SETTINGS_DISABLED).catch(console.error);
}
}
......@@ -213,7 +213,7 @@ export class AppManager {
// but are not currently disabled.
for (const rl of this.apps.values()) {
if (!AppStatusUtils.isDisabled(rl.getStatus()) && AppStatusUtils.isEnabled(rl.getPreviousStatus())) {
await this.enableApp(items.get(rl.getID()), rl, true, rl.getPreviousStatus() === AppStatus.MANUALLY_ENABLED);
await this.enableApp(items.get(rl.getID()), rl, true, rl.getPreviousStatus() === AppStatus.MANUALLY_ENABLED).catch(console.error);
}
}
......@@ -360,6 +360,17 @@ export class AppManager {
this.apiManager.unregisterApis(storageItem.id);
this.accessorManager.purifyApp(storageItem.id);
if (storageItem.status === AppStatus.INVALID_LICENSE_DISABLED) {
await rl.validateLicense().catch(() => {
/**
* This case would happen when the app has been disabled due to
* license validation errors in another instance of a cluster.
* We need to validate the license in this instance as well so
* we get the error messages to show in the UI
*/
});
}
if (isManual) {
await rl.setStatus(AppStatus.MANUALLY_DISABLED);
}
......@@ -564,7 +575,7 @@ export class AppManager {
public async updateAppsMarketplaceInfo(appsOverview: Array<{ latest: IMarketplaceInfo }>): Promise<void> {
try {
appsOverview.forEach(({ latest: appInfo }) => {
await Promise.all(appsOverview.map(async ({ latest: appInfo }) => {
if (!appInfo.subscriptionInfo) {
return;
}
......@@ -578,31 +589,50 @@ export class AppManager {
const appStorageItem = app.getStorageItem();
const subscriptionInfo = appStorageItem.marketplaceInfo && appStorageItem.marketplaceInfo.subscriptionInfo;
if (subscriptionInfo && subscriptionInfo.startDate === appInfo.subscriptionInfo.startDate) {
if (subscriptionInfo && subscriptionInfo.license.license === appInfo.subscriptionInfo.license.license) {
return;
}
appStorageItem.marketplaceInfo.subscriptionInfo = appInfo.subscriptionInfo;
this.storage.update(appStorageItem).catch(console.error); // TODO: Figure out something better
});
return this.storage.update(appStorageItem).catch(console.error); // TODO: Figure out something better
}));
} catch (err) {
// Errors here are not important
}
const queue = [] as Array<Promise<void>>;
this.apps.forEach((app) => queue.push(app.validateLicense().catch((error) => {
if (!(error instanceof InvalidLicenseError)) {
console.error(error);
return;
}
this.apps.forEach((app) => queue.push(app.validateLicense()
.then(() => {
if (app.getStatus() !== AppStatus.INVALID_LICENSE_DISABLED) {
return;
}
this.commandManager.unregisterCommands(app.getID());
this.apiManager.unregisterApis(app.getID());
return app.setStatus(AppStatus.DISABLED);
})
.catch((error) => {
if (!(error instanceof InvalidLicenseError)) {
console.error(error);
return;
}
this.commandManager.unregisterCommands(app.getID());
this.apiManager.unregisterApis(app.getID());
return app.setStatus(AppStatus.INVALID_LICENSE_DISABLED);
})));
return app.setStatus(AppStatus.INVALID_LICENSE_DISABLED);
})
.then(() => {
if (app.getStatus() === app.getPreviousStatus()) {
return;
}
const storageItem = app.getStorageItem();
storageItem.status = app.getStatus();
return this.storage.update(storageItem).catch(console.error) as Promise<void>;
}),
));
await Promise.all(queue);
}
......@@ -691,7 +721,11 @@ export class AppManager {
// 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 = app.getStatus();
this.storage.update(storageItem);
this.storage.update(storageItem).catch(() => {
/**
* Avoiding some UNHANDLED_PROMISE_REJECTION
*/
});
}
return result;
......
......@@ -4,6 +4,7 @@ import { InvalidLicenseError } from '../errors';
import { IMarketplaceInfo } from '../marketplace';
import { AppLicenseValidationResult } from '../marketplace/license';
import { Crypto } from '../marketplace/license';
import { MarketplacePurchaseType } from '../marketplace/MarketplacePurchaseType';
enum LicenseVersion {
v1 = 1,
......@@ -18,27 +19,21 @@ export class AppLicenseManager {
}
public async validate(validationResult: AppLicenseValidationResult, appMarketplaceInfo?: IMarketplaceInfo): Promise<void> {
if (!appMarketplaceInfo || !appMarketplaceInfo.subscriptionInfo) {
if (!appMarketplaceInfo || appMarketplaceInfo.purchaseType !== MarketplacePurchaseType.PurchaseTypeSubscription) {
return;
}
validationResult.setValidated(true);
const { id: appId, subscriptionInfo } = appMarketplaceInfo;
let license;
try {
license = await this.crypto.decryptLicense(subscriptionInfo.license.license) as any;
license = await this.crypto.decryptLicense(appMarketplaceInfo.subscriptionInfo.license.license) as any;
} catch (err) {
validationResult.addError('publicKey', err.message);
throw new InvalidLicenseError(validationResult);
}
if (license.appId !== appId) {
validationResult.addError('appId', `License hasn't been issued for this app`);
}
switch (license.version) {
case LicenseVersion.v1:
await this.validateV1(appMarketplaceInfo, license, validationResult);
......@@ -47,18 +42,24 @@ export class AppLicenseManager {
}
private async validateV1(appMarketplaceInfo: IMarketplaceInfo, license: any, validationResult: AppLicenseValidationResult): Promise<void> {
if (license.isBundle && (!appMarketplaceInfo.bundledIn || !appMarketplaceInfo.bundledIn.find((value) => value.bundleId === license.appId))) {
validationResult.addError('bundle', 'License issued for a bundle that does not contain the app');
} else if (!license.isBundle && license.appId !== appMarketplaceInfo.id) {
validationResult.addError('appId', `License hasn't been issued for this app`);
}
const renewal = new Date(license.renewalDate);
const expire = new Date(license.expireDate);
const now = new Date();
if (expire < now) {
validationResult.addError('expire', 'License is no longer valid');
validationResult.addError('expire', 'License is no longer valid and needs to be renewed');
}
const currentActiveUsers = await this.userBridge.getActiveUserCount();
if (license.maxSeats < currentActiveUsers) {
validationResult.addError('maxSeats', 'License does not accomodate the currently active users');
validationResult.addError('maxSeats', 'License does not accomodate the current amount of active users. Please increase the number of seats');
}
if (validationResult.hasErrors) {
......@@ -72,9 +73,7 @@ export class AppLicenseManager {
if (license.seats < currentActiveUsers) {
validationResult.addWarning(
'seats',
`The license for the app "${
appMarketplaceInfo.name
}" does not have enough seats to accommodate the current amount of active users. Please increase the number of seats`,
'License does not have enough seats to accommodate the current amount of active users. Please increase the number of seats',
);
}
}
......
......@@ -17,7 +17,7 @@ export interface IMarketplaceInfo extends IAppInfo {
modifiedDate: string;
price: number;
subscriptionInfo?: IMarketplaceSubscriptionInfo;
puchaseType: MarketplacePurchaseType;
purchaseType: MarketplacePurchaseType;
pricingPlans?: Array<IMarketplacePricingPlan>;
bundledIn?: Array<IMarketplaceSimpleBundleInfo>;
}