@rocket.chat/apps-engine
Advanced tools
Comparing version 0.5.11 to 0.6.0
{ | ||
"name": "@rocket.chat/apps-engine", | ||
"version": "0.5.11", | ||
"version": "0.6.0", | ||
"description": "The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.", | ||
@@ -9,4 +9,10 @@ "main": "index.js", | ||
"start": "gulp", | ||
"compile": "gulp build", | ||
"go-publish": "gulp publish" | ||
"lint": "tslint --project tsconfig.json", | ||
"fix-lint": "tslint --project tsconfig.json --fix", | ||
"compile": "gulp", | ||
"go-publish": "gulp publish", | ||
"unit-tests": "ts-node ./tests/runner.ts", | ||
"test-and-coverage": "nyc npm run unit-tests && nyc report", | ||
"view-coverage": "npm run test-and-coverage && http-server coverage -p 9082 -c-1", | ||
"submit-codecov": "codecov --file=coverage/coverage-final.json" | ||
}, | ||
@@ -36,6 +42,7 @@ "repository": { | ||
"@types/uuid": "^3.4.3", | ||
"alsatian": "^2.2.1", | ||
"codecov": "^3.0.1", | ||
"del": "^3.0.0", | ||
"eslint": "^4.19.1", | ||
"gulp": "^3.9.1", | ||
"gulp-replace": "^0.6.1", | ||
"gulp-shell": "^0.6.5", | ||
@@ -45,15 +52,39 @@ "gulp-sourcemaps": "^2.6.4", | ||
"gulp-typescript": "^4.0.2", | ||
"gulp-util": "^3.0.8", | ||
"http-server": "^0.11.1", | ||
"nedb": "^1.8.0", | ||
"nyc": "^11.7.1", | ||
"pre-commit": "^1.2.2", | ||
"tap-bark": "^1.0.0", | ||
"ts-node": "^6.0.2", | ||
"tslint": "^5.9.1" | ||
}, | ||
"dependencies": { | ||
"@rocket.chat/apps-ts-definition": "^0.9.6", | ||
"adm-zip": "^0.4.7", | ||
"@rocket.chat/apps-ts-definition": "^0.9.13", | ||
"adm-zip": "^0.4.9", | ||
"lodash.clonedeep": "^4.5.0", | ||
"semver": "^5.5.0", | ||
"stack-trace": "0.0.10", | ||
"typescript": "^2.8.1", | ||
"typescript": "^2.8.3", | ||
"uuid": "^3.2.1" | ||
} | ||
}, | ||
"nyc": { | ||
"include": [ | ||
"src/*.ts", | ||
"src/server/**/*.ts" | ||
], | ||
"extension": [ | ||
".ts" | ||
], | ||
"reporter": [ | ||
"lcov", | ||
"json", | ||
"html" | ||
], | ||
"all": true | ||
}, | ||
"pre-commit": [ | ||
"lint", | ||
"compile", | ||
"unit-tests" | ||
] | ||
} |
@@ -15,2 +15,9 @@ # Rocket.Chat Apps Engine | ||
- `src/server/storage/AppStorage` | ||
- `src/server/storage/AppLogStorage` | ||
- `src/server/bridges/*` | ||
## Testing Framework: | ||
Makes great usage of TypeScript and decorators: https://github.com/alsatian-test/alsatian/wiki | ||
* To run the tests do: `npm run unit-tests` | ||
* To generate the coverage information: `npm run check-coverage` | ||
* To view the coverage: `npm run view-coverage` |
@@ -12,13 +12,21 @@ import { IMessageBuilder } from '@rocket.chat/apps-ts-definition/accessors'; | ||
setRoom(room: IRoom): IMessageBuilder; | ||
getRoom(): IRoom; | ||
setSender(sender: IUser): IMessageBuilder; | ||
getSender(): IUser; | ||
setText(text: string): IMessageBuilder; | ||
getText(): string; | ||
setEmojiAvatar(emoji: string): IMessageBuilder; | ||
getEmojiAvatar(): string; | ||
setAvatarUrl(avatarUrl: string): IMessageBuilder; | ||
getAvatarUrl(): string; | ||
setUsernameAlias(alias: string): IMessageBuilder; | ||
getUsernameAlias(): string; | ||
addAttachment(attachment: IMessageAttachment): IMessageBuilder; | ||
setAttachments(attachments: Array<IMessageAttachment>): IMessageBuilder; | ||
getAttachments(): Array<IMessageAttachment>; | ||
replaceAttachment(position: number, attachment: IMessageAttachment): IMessageBuilder; | ||
removeAttachment(position: number): IMessageBuilder; | ||
setEditor(user: IUser): IMessageBuilder; | ||
getEditor(): IUser; | ||
getMessage(): IMessage; | ||
} |
@@ -18,2 +18,5 @@ "use strict"; | ||
} | ||
getRoom() { | ||
return this.msg.room; | ||
} | ||
setSender(sender) { | ||
@@ -23,2 +26,5 @@ this.msg.sender = sender; | ||
} | ||
getSender() { | ||
return this.msg.sender; | ||
} | ||
setText(text) { | ||
@@ -28,2 +34,5 @@ this.msg.text = text; | ||
} | ||
getText() { | ||
return this.msg.text; | ||
} | ||
setEmojiAvatar(emoji) { | ||
@@ -33,2 +42,5 @@ this.msg.emoji = emoji; | ||
} | ||
getEmojiAvatar() { | ||
return this.msg.emoji; | ||
} | ||
setAvatarUrl(avatarUrl) { | ||
@@ -38,2 +50,5 @@ this.msg.avatarUrl = avatarUrl; | ||
} | ||
getAvatarUrl() { | ||
return this.msg.avatarUrl; | ||
} | ||
setUsernameAlias(alias) { | ||
@@ -43,2 +58,5 @@ this.msg.alias = alias; | ||
} | ||
getUsernameAlias() { | ||
return this.msg.alias; | ||
} | ||
addAttachment(attachment) { | ||
@@ -55,2 +73,5 @@ if (!this.msg.attachments) { | ||
} | ||
getAttachments() { | ||
return this.msg.attachments; | ||
} | ||
replaceAttachment(position, attachment) { | ||
@@ -71,3 +92,3 @@ if (!this.msg.attachments) { | ||
if (!this.msg.attachments[position]) { | ||
throw new Error(`No attachment found at the index of "${position}" to replace.`); | ||
throw new Error(`No attachment found at the index of "${position}" to remove.`); | ||
} | ||
@@ -81,5 +102,8 @@ this.msg.attachments.splice(position, 1); | ||
} | ||
getEditor() { | ||
return this.msg.editor; | ||
} | ||
getMessage() { | ||
if (!this.msg.room) { | ||
throw new Error('The Room Value is required.'); | ||
throw new Error('The "room" property is required.'); | ||
} | ||
@@ -86,0 +110,0 @@ return this.msg; |
@@ -14,2 +14,5 @@ "use strict"; | ||
addCustomField(key, value) { | ||
if (!this.msg.customFields) { | ||
this.msg.customFields = {}; | ||
} | ||
if (this.msg.customFields[key]) { | ||
@@ -16,0 +19,0 @@ throw new Error(`The message already contains a custom field by the key: ${key}`); |
@@ -14,3 +14,3 @@ "use strict"; | ||
this.extender = new ModifyExtender_1.ModifyExtender(this.bridges, this.appId); | ||
this.notifier = new Notifier_1.Notifier(this.bridges, this.appId); | ||
this.notifier = new Notifier_1.Notifier(this.bridges.getMessageBridge(), this.appId); | ||
} | ||
@@ -17,0 +17,0 @@ getCreator() { |
@@ -36,5 +36,2 @@ "use strict"; | ||
delete result.id; | ||
if (!result.room || !result.room.id) { | ||
throw new Error('Invalid room assigned to the message.'); | ||
} | ||
if (!result.sender || !result.sender.id) { | ||
@@ -41,0 +38,0 @@ throw new Error('Invalid sender assigned to the message.'); |
@@ -41,3 +41,3 @@ "use strict"; | ||
default: | ||
throw new Error('Invalid builder passed to the ModifyExtender.finish function.'); | ||
throw new Error('Invalid extender passed to the ModifyExtender.finish function.'); | ||
} | ||
@@ -44,0 +44,0 @@ } |
@@ -43,4 +43,4 @@ "use strict"; | ||
const result = builder.getMessage(); | ||
if (!result.room || !result.room.id) { | ||
throw new Error('Invalid room assigned to the message.'); | ||
if (!result.id) { | ||
throw new Error('Invalid message, can not update a message without an id.'); | ||
} | ||
@@ -54,2 +54,5 @@ if (!result.sender || !result.sender.id) { | ||
const result = builder.getRoom(); | ||
if (!result.id) { | ||
throw new Error('Invalid room, can not update a room without an id.'); | ||
} | ||
if (!result.creator || !result.creator.id) { | ||
@@ -56,0 +59,0 @@ throw new Error('Invalid creator assigned to the room.'); |
@@ -5,7 +5,7 @@ import { IMessageBuilder, INotifier } from '@rocket.chat/apps-ts-definition/accessors'; | ||
import { IUser } from '@rocket.chat/apps-ts-definition/users'; | ||
import { AppBridges } from '../bridges'; | ||
import { IMessageBridge } from '../bridges'; | ||
export declare class Notifier implements INotifier { | ||
private readonly bridges; | ||
private readonly msgBridge; | ||
private readonly appId; | ||
constructor(bridges: AppBridges, appId: string); | ||
constructor(msgBridge: IMessageBridge, appId: string); | ||
notifyUser(user: IUser, message: IMessage): Promise<void>; | ||
@@ -12,0 +12,0 @@ notifyRoom(room: IRoom, message: IMessage): Promise<void>; |
@@ -13,4 +13,4 @@ "use strict"; | ||
class Notifier { | ||
constructor(bridges, appId) { | ||
this.bridges = bridges; | ||
constructor(msgBridge, appId) { | ||
this.msgBridge = msgBridge; | ||
this.appId = appId; | ||
@@ -20,3 +20,3 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield this.bridges.getMessageBridge().notifyUser(user, message, this.appId); | ||
yield this.msgBridge.notifyUser(user, message, this.appId); | ||
}); | ||
@@ -26,3 +26,3 @@ } | ||
return __awaiter(this, void 0, void 0, function* () { | ||
yield this.bridges.getMessageBridge().notifyRoom(room, message, this.appId); | ||
yield this.msgBridge.notifyRoom(room, message, this.appId); | ||
}); | ||
@@ -29,0 +29,0 @@ } |
@@ -8,3 +8,3 @@ import { IPersistence } from '@rocket.chat/apps-ts-definition/accessors'; | ||
constructor(persistBridge: IPersistenceBridge, appId: string); | ||
create(data: any): Promise<string>; | ||
create(data: object): Promise<string>; | ||
createWithAssociation(data: object, association: RocketChatAssociationRecord): Promise<string>; | ||
@@ -11,0 +11,0 @@ createWithAssociations(data: object, associations: Array<RocketChatAssociationRecord>): Promise<string>; |
@@ -11,10 +11,18 @@ import { IRoomBuilder } from '@rocket.chat/apps-ts-definition/accessors'; | ||
setDisplayName(name: string): IRoomBuilder; | ||
getDisplayName(): string; | ||
setSlugifiedName(name: string): IRoomBuilder; | ||
getSlugifiedName(): string; | ||
setType(type: RoomType): IRoomBuilder; | ||
getType(): RoomType; | ||
setCreator(creator: IUser): IRoomBuilder; | ||
getCreator(): IUser; | ||
addUsername(username: string): IRoomBuilder; | ||
setUsernames(usernames: Array<string>): IRoomBuilder; | ||
getUsernames(): Array<string>; | ||
setDefault(isDefault: boolean): IRoomBuilder; | ||
getIsDefault(): boolean; | ||
setReadOnly(isReadOnly: boolean): IRoomBuilder; | ||
getIsReadOnly(): boolean; | ||
setDisplayingOfSystemMessages(displaySystemMessages: boolean): IRoomBuilder; | ||
getDisplayingOfSystemMessages(): boolean; | ||
addCustomField(key: string, value: object): IRoomBuilder; | ||
@@ -24,3 +32,6 @@ setCustomFields(fields: { | ||
}): IRoomBuilder; | ||
getCustomFields(): { | ||
[key: string]: object; | ||
}; | ||
getRoom(): IRoom; | ||
} |
@@ -18,2 +18,5 @@ "use strict"; | ||
} | ||
getDisplayName() { | ||
return this.room.displayName; | ||
} | ||
setSlugifiedName(name) { | ||
@@ -23,2 +26,5 @@ this.room.slugifiedName = name; | ||
} | ||
getSlugifiedName() { | ||
return this.room.slugifiedName; | ||
} | ||
setType(type) { | ||
@@ -28,2 +34,5 @@ this.room.type = type; | ||
} | ||
getType() { | ||
return this.room.type; | ||
} | ||
setCreator(creator) { | ||
@@ -33,2 +42,5 @@ this.room.creator = creator; | ||
} | ||
getCreator() { | ||
return this.room.creator; | ||
} | ||
addUsername(username) { | ||
@@ -45,2 +57,5 @@ if (!this.room.usernames) { | ||
} | ||
getUsernames() { | ||
return this.room.usernames; | ||
} | ||
setDefault(isDefault) { | ||
@@ -50,2 +65,5 @@ this.room.isDefault = isDefault; | ||
} | ||
getIsDefault() { | ||
return this.room.isDefault; | ||
} | ||
setReadOnly(isReadOnly) { | ||
@@ -55,2 +73,5 @@ this.room.isReadOnly = isReadOnly; | ||
} | ||
getIsReadOnly() { | ||
return this.room.isReadOnly; | ||
} | ||
setDisplayingOfSystemMessages(displaySystemMessages) { | ||
@@ -60,3 +81,9 @@ this.room.displaySystemMessages = displaySystemMessages; | ||
} | ||
getDisplayingOfSystemMessages() { | ||
return this.room.displaySystemMessages; | ||
} | ||
addCustomField(key, value) { | ||
if (typeof this.room.customFields !== 'object') { | ||
this.room.customFields = {}; | ||
} | ||
this.room.customFields[key] = value; | ||
@@ -69,2 +96,5 @@ return this; | ||
} | ||
getCustomFields() { | ||
return this.room.customFields; | ||
} | ||
getRoom() { | ||
@@ -71,0 +101,0 @@ return this.room; |
@@ -11,2 +11,5 @@ "use strict"; | ||
addCustomField(key, value) { | ||
if (!this.room.customFields) { | ||
this.room.customFields = {}; | ||
} | ||
if (this.room.customFields[key]) { | ||
@@ -19,2 +22,8 @@ throw new Error(`The room already contains a custom field by the key: ${key}`); | ||
addMember(user) { | ||
if (!Array.isArray(this.room.usernames)) { | ||
this.room.usernames = new Array(); | ||
} | ||
if (this.room.usernames.includes(user.username)) { | ||
throw new Error('The user is already in the room.'); | ||
} | ||
this.room.usernames.push(user.username); | ||
@@ -21,0 +30,0 @@ return this; |
@@ -1,2 +0,2 @@ | ||
import { IIterator, IRoomRead } from '@rocket.chat/apps-ts-definition/accessors'; | ||
import { IRoomRead } from '@rocket.chat/apps-ts-definition/accessors'; | ||
import { IMessage } from '@rocket.chat/apps-ts-definition/messages'; | ||
@@ -11,5 +11,7 @@ import { IRoom } from '@rocket.chat/apps-ts-definition/rooms'; | ||
getById(id: string): Promise<IRoom>; | ||
getCreatorUserById(id: string): Promise<IUser>; | ||
getByName(name: string): Promise<IRoom>; | ||
getMessages(roomId: string): Promise<IIterator<IMessage>>; | ||
getMembers(roomId: string): Promise<IIterator<IUser>>; | ||
getCreatorUserByName(name: string): Promise<IUser>; | ||
getMessages(roomId: string): Promise<IterableIterator<IMessage>>; | ||
getMembers(roomId: string): Promise<IterableIterator<IUser>>; | ||
} |
@@ -11,5 +11,11 @@ "use strict"; | ||
} | ||
getCreatorUserById(id) { | ||
return this.roomBridge.getCreatorById(id, this.appId); | ||
} | ||
getByName(name) { | ||
return this.roomBridge.getByName(name, this.appId); | ||
} | ||
getCreatorUserByName(name) { | ||
return this.roomBridge.getCreatorByName(name, this.appId); | ||
} | ||
getMessages(roomId) { | ||
@@ -16,0 +22,0 @@ throw new Error('Method not implemented.'); |
import { IServerSettingBridge } from '../bridges/IServerSettingBridge'; | ||
import { IIterator, IServerSettingRead } from '@rocket.chat/apps-ts-definition/accessors'; | ||
import { IServerSettingRead } from '@rocket.chat/apps-ts-definition/accessors'; | ||
import { ISetting } from '@rocket.chat/apps-ts-definition/settings'; | ||
@@ -10,4 +10,4 @@ export declare class ServerSettingRead implements IServerSettingRead { | ||
getValueById(id: string): Promise<any>; | ||
getAll(): Promise<IIterator<ISetting>>; | ||
getAll(): Promise<IterableIterator<ISetting>>; | ||
isReadableById(id: string): Promise<boolean>; | ||
} |
@@ -70,3 +70,8 @@ import { AppBridges } from './bridges'; | ||
private initializeApp(storageItem, app, saveToDb?); | ||
/** | ||
* Determines if the App's required settings are set or not. | ||
* Should a packageValue be provided and not empty, then it's considered set. | ||
*/ | ||
private areRequiredSettingsSet(storageItem); | ||
private enableApp(storageItem, app, saveToDb, isManual); | ||
} |
@@ -21,3 +21,2 @@ "use strict"; | ||
constructor(rlStorage, logStorage, rlBridges) { | ||
console.log('Constructed the AppManager.'); | ||
if (rlStorage instanceof storage_1.AppStorage) { | ||
@@ -42,4 +41,4 @@ this.storage = rlStorage; | ||
this.apps = new Map(); | ||
this.parser = new compiler_1.AppPackageParser(this); | ||
this.compiler = new compiler_1.AppCompiler(this); | ||
this.parser = new compiler_1.AppPackageParser(); | ||
this.compiler = new compiler_1.AppCompiler(); | ||
this.accessorManager = new managers_1.AppAccessorManager(this); | ||
@@ -103,3 +102,3 @@ this.listenerManager = new managers_1.AppListenerManger(this); | ||
try { | ||
const result = yield this.getParser().parseZip(item.zip); | ||
const result = yield this.getParser().parseZip(this.getCompiler(), item.zip); | ||
aff.setAppInfo(result.info); | ||
@@ -112,3 +111,3 @@ aff.setImplementedInterfaces(result.implemented.getValues()); | ||
item.compiled = result.compiledFiles; | ||
const app = this.getCompiler().toSandBox(item); | ||
const app = this.getCompiler().toSandBox(this, item); | ||
this.apps.set(item.id, app); | ||
@@ -139,2 +138,11 @@ aff.setApp(app); | ||
} | ||
// Let's ensure the required settings are all set | ||
for (const rl of this.apps.values()) { | ||
if (AppStatus_1.AppStatusUtils.isDisabled(rl.getStatus())) { | ||
continue; | ||
} | ||
if (!this.areRequiredSettingsSet(rl.getStorageItem())) { | ||
yield rl.setStatus(AppStatus_1.AppStatus.INVALID_SETTINGS_DISABLED); | ||
} | ||
} | ||
// Now let's enable the apps which were once enabled | ||
@@ -254,3 +262,3 @@ // but are not currently disabled. | ||
const aff = new compiler_1.AppFabricationFulfillment(); | ||
const result = yield this.getParser().parseZip(zipContentsBase64d); | ||
const result = yield this.getParser().parseZip(this.getCompiler(), zipContentsBase64d); | ||
aff.setAppInfo(result.info); | ||
@@ -277,3 +285,3 @@ aff.setImplementedInterfaces(result.implemented.getValues()); | ||
// the App instance from the source. | ||
const app = this.getCompiler().toSandBox(created); | ||
const app = this.getCompiler().toSandBox(this, created); | ||
this.apps.set(app.getID(), app); | ||
@@ -322,3 +330,3 @@ aff.setApp(app); | ||
const aff = new compiler_1.AppFabricationFulfillment(); | ||
const result = yield this.getParser().parseZip(zipContentsBase64d); | ||
const result = yield this.getParser().parseZip(this.getCompiler(), zipContentsBase64d); | ||
aff.setAppInfo(result.info); | ||
@@ -355,3 +363,3 @@ aff.setImplementedInterfaces(result.implemented.getValues()); | ||
// the App instance from the source. | ||
const app = this.getCompiler().toSandBox(stored); | ||
const app = this.getCompiler().toSandBox(this, stored); | ||
// Store it temporarily so we can access it else where | ||
@@ -422,6 +430,9 @@ this.apps.set(app.getID(), app); | ||
} | ||
this.apps.set(item.id, this.getCompiler().toSandBox(item)); | ||
this.apps.set(item.id, this.getCompiler().toSandBox(this, item)); | ||
const rl = this.apps.get(item.id); | ||
yield this.initializeApp(item, rl, false); | ||
if (AppStatus_1.AppStatusUtils.isEnabled(rl.getPreviousStatus())) { | ||
if (!this.areRequiredSettingsSet(item)) { | ||
yield rl.setStatus(AppStatus_1.AppStatus.INVALID_SETTINGS_DISABLED); | ||
} | ||
if (!AppStatus_1.AppStatusUtils.isDisabled(rl.getStatus()) && AppStatus_1.AppStatusUtils.isEnabled(rl.getPreviousStatus())) { | ||
yield this.enableApp(item, rl, false, rl.getPreviousStatus() === AppStatus_1.AppStatus.MANUALLY_ENABLED); | ||
@@ -440,2 +451,6 @@ } | ||
} | ||
if (!this.areRequiredSettingsSet(storageItem)) { | ||
yield app.setStatus(AppStatus_1.AppStatus.INVALID_SETTINGS_DISABLED); | ||
return false; | ||
} | ||
const isEnabled = yield this.enableApp(storageItem, app, true, isManual); | ||
@@ -476,2 +491,21 @@ if (!isEnabled) { | ||
} | ||
/** | ||
* 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 || sett.packageValue) { | ||
continue; | ||
} | ||
result = false; | ||
} | ||
return result; | ||
} | ||
enableApp(storageItem, app, saveToDb = true, isManual) { | ||
@@ -478,0 +512,0 @@ return __awaiter(this, void 0, void 0, function* () { |
@@ -10,3 +10,3 @@ import { ISlashCommand } from '@rocket.chat/apps-ts-definition/slashcommands'; | ||
* system which is being bridged. This does not check if the app | ||
* registered it but it should return whether the supplied command is | ||
* registered it but it will return whether the supplied command is | ||
* already defined by something else or not. | ||
@@ -16,7 +16,10 @@ * | ||
* @param appId the id of the app calling this | ||
* @return whether the command is already in the system | ||
* @returns whether the command is already in the system | ||
*/ | ||
doesCommandExist(command: string, appId: string): boolean; | ||
/** | ||
* Enables an existing command from the bridged system. | ||
* Enables an existing command from the bridged system. The callee | ||
* must ensure that the command that's being enabled is defined by | ||
* the bridged system and not another App since the bridged system | ||
* will not check that. | ||
* | ||
@@ -28,3 +31,5 @@ * @param command the command to enable | ||
/** | ||
* Disables an existing command from the bridged system. | ||
* Disables an existing command from the bridged system, the callee must | ||
* ensure the command disabling is defined by the system and not another | ||
* App since the bridged system won't check that. | ||
* | ||
@@ -36,4 +41,4 @@ * @param command the command which to disable | ||
/** | ||
* Changes how an existing slash command behaves, so you can provide | ||
* different executor per configuration. | ||
* Changes how a system slash command behaves, allows Apps to provide | ||
* different executors per system commands. | ||
* | ||
@@ -45,2 +50,11 @@ * @param command the modified slash command | ||
/** | ||
* Restores a system slash command back to it's default behavior. | ||
* This includes "unmodifying" a command and also enabling a | ||
* command again if it was disabled. | ||
* | ||
* @param command the command to restore | ||
* @param appId the id of the app which modified it | ||
*/ | ||
restoreCommand(command: string, appId: string): void; | ||
/** | ||
* Registers a command with the system which is being bridged. | ||
@@ -47,0 +61,0 @@ * |
@@ -16,3 +16,3 @@ import { RocketChatAssociationRecord } from '@rocket.chat/apps-ts-definition/metadata'; | ||
*/ | ||
create(data: any, appId: string): Promise<string>; | ||
create(data: object, appId: string): Promise<string>; | ||
/** | ||
@@ -19,0 +19,0 @@ * Creates a new record in the App's persistent storage with the data being |
import { IRoom } from '@rocket.chat/apps-ts-definition/rooms'; | ||
import { IUser } from '@rocket.chat/apps-ts-definition/users'; | ||
export interface IRoomBridge { | ||
@@ -6,3 +7,5 @@ create(room: IRoom, appId: string): Promise<string>; | ||
getByName(roomName: string, appId: string): Promise<IRoom>; | ||
getCreatorById(roomId: string, appId: string): Promise<IUser>; | ||
getCreatorByName(roomName: string, appId: string): Promise<IUser>; | ||
update(room: IRoom, appId: string): Promise<void>; | ||
} |
@@ -8,6 +8,5 @@ import { AppManager } from '../AppManager'; | ||
export declare class AppCompiler { | ||
private readonly manager; | ||
private readonly compilerOptions; | ||
private libraryFiles; | ||
constructor(manager: AppManager); | ||
constructor(); | ||
storageFilesToCompiler(files: { | ||
@@ -30,5 +29,5 @@ [key: string]: string; | ||
}): ICompilerResult; | ||
toSandBox(storage: IAppStorageItem): ProxiedApp; | ||
toSandBox(manager: AppManager, storage: IAppStorageItem): ProxiedApp; | ||
private isValidFile(file); | ||
private buildCustomRequire(files); | ||
} |
@@ -14,4 +14,3 @@ "use strict"; | ||
class AppCompiler { | ||
constructor(manager) { | ||
this.manager = manager; | ||
constructor() { | ||
this.compilerOptions = { | ||
@@ -218,3 +217,3 @@ target: ts.ScriptTarget.ES2017, | ||
} | ||
toSandBox(storage) { | ||
toSandBox(manager, storage) { | ||
const files = this.storageFilesToCompiler(storage.compiled); | ||
@@ -262,11 +261,12 @@ if (typeof files[path.normalize(storage.info.classFile)] === 'undefined') { | ||
} | ||
const app = new ProxiedApp_1.ProxiedApp(this.manager, storage, rl, customRequire); | ||
this.manager.getLogStorage().storeEntries(app.getID(), logger); | ||
const app = new ProxiedApp_1.ProxiedApp(manager, storage, rl, customRequire); | ||
manager.getLogStorage().storeEntries(app.getID(), logger); | ||
return app; | ||
} | ||
isValidFile(file) { | ||
return file.name | ||
&& file.name.trim() !== '' | ||
if (!file || !file.name || !file.content) { | ||
return false; | ||
} | ||
return file.name.trim() !== '' | ||
&& path.normalize(file.name) | ||
&& file.content | ||
&& file.content.trim() !== ''; | ||
@@ -273,0 +273,0 @@ } |
@@ -6,2 +6,4 @@ export declare enum AppInterface { | ||
IPostMessageSent = "IPostMessageSent", | ||
IPreMessageDeletePrevent = "IPreMessageDeletePrevent", | ||
IPostMessageDeleted = "IPostMessageDeleted", | ||
IPreRoomCreatePrevent = "IPreRoomCreatePrevent", | ||
@@ -11,2 +13,4 @@ IPreRoomCreateExtend = "IPreRoomCreateExtend", | ||
IPostRoomCreate = "IPostRoomCreate", | ||
IPreRoomDeletePrevent = "IPreRoomDeletePrevent", | ||
IPostRoomDeleted = "IPostRoomDeleted", | ||
} | ||
@@ -13,0 +17,0 @@ export declare class AppImplements { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Utilities_1 = require("../misc/Utilities"); | ||
var AppInterface; | ||
@@ -10,2 +11,4 @@ (function (AppInterface) { | ||
AppInterface["IPostMessageSent"] = "IPostMessageSent"; | ||
AppInterface["IPreMessageDeletePrevent"] = "IPreMessageDeletePrevent"; | ||
AppInterface["IPostMessageDeleted"] = "IPostMessageDeleted"; | ||
// Rooms | ||
@@ -16,2 +19,4 @@ AppInterface["IPreRoomCreatePrevent"] = "IPreRoomCreatePrevent"; | ||
AppInterface["IPostRoomCreate"] = "IPostRoomCreate"; | ||
AppInterface["IPreRoomDeletePrevent"] = "IPreRoomDeletePrevent"; | ||
AppInterface["IPostRoomDeleted"] = "IPostRoomDeleted"; | ||
})(AppInterface = exports.AppInterface || (exports.AppInterface = {})); | ||
@@ -29,3 +34,3 @@ class AppImplements { | ||
getValues() { | ||
return Object.assign({}, this.implemented); | ||
return Utilities_1.Utilities.deepCloneAndFreeze(this.implemented); | ||
} | ||
@@ -32,0 +37,0 @@ } |
@@ -1,10 +0,9 @@ | ||
import { AppManager } from '../AppManager'; | ||
import { AppCompiler } from './AppCompiler'; | ||
import { IParseZipResult } from './IParseZipResult'; | ||
export declare class AppPackageParser { | ||
private readonly manager; | ||
static uuid4Regex: RegExp; | ||
private allowedIconExts; | ||
private appsTsDefVer; | ||
constructor(manager: AppManager); | ||
parseZip(zipBase64: string): Promise<IParseZipResult>; | ||
constructor(); | ||
parseZip(compiler: AppCompiler, zipBase64: string): Promise<IParseZipResult>; | ||
private getLanguageContent(zip); | ||
@@ -11,0 +10,0 @@ private getIconFile(zip, filePath); |
@@ -18,8 +18,7 @@ "use strict"; | ||
class AppPackageParser { | ||
constructor(manager) { | ||
this.manager = manager; | ||
constructor() { | ||
this.allowedIconExts = ['.png', '.jpg', '.jpeg', '.gif']; | ||
this.appsTsDefVer = this.getTsDefVersion(); | ||
} | ||
parseZip(zipBase64) { | ||
parseZip(compiler, zipBase64) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -67,3 +66,3 @@ const zip = new AdmZip(new Buffer(zipBase64, 'base64')); | ||
// Compile all the typescript files to javascript | ||
const result = this.manager.getCompiler().toJs(info, tsFiles); | ||
const result = compiler.toJs(info, tsFiles); | ||
tsFiles = result.files; | ||
@@ -70,0 +69,0 @@ const compiledFiles = {}; |
import { CommandAlreadyExistsError } from './CommandAlreadyExistsError'; | ||
import { CommandHasAlreadyBeenTouchedError } from './CommandHasAlreadyBeenTouchedError'; | ||
import { CompilerError } from './CompilerError'; | ||
@@ -7,2 +8,2 @@ import { MustContainFunctionError } from './MustContainFunctionError'; | ||
import { RequiredApiVersionError } from './RequiredApiVersionError'; | ||
export { CommandAlreadyExistsError, CompilerError, MustContainFunctionError, MustExtendAppError, NotEnoughMethodArgumentsError, RequiredApiVersionError }; | ||
export { CommandAlreadyExistsError, CommandHasAlreadyBeenTouchedError, CompilerError, MustContainFunctionError, MustExtendAppError, NotEnoughMethodArgumentsError, RequiredApiVersionError }; |
@@ -5,2 +5,4 @@ "use strict"; | ||
exports.CommandAlreadyExistsError = CommandAlreadyExistsError_1.CommandAlreadyExistsError; | ||
const CommandHasAlreadyBeenTouchedError_1 = require("./CommandHasAlreadyBeenTouchedError"); | ||
exports.CommandHasAlreadyBeenTouchedError = CommandHasAlreadyBeenTouchedError_1.CommandHasAlreadyBeenTouchedError; | ||
const CompilerError_1 = require("./CompilerError"); | ||
@@ -7,0 +9,0 @@ exports.CompilerError = CompilerError_1.CompilerError; |
@@ -71,3 +71,3 @@ "use strict"; | ||
// This should be a setting? :thinking: | ||
console.log(`${severity.toUpperCase()}:`, i); | ||
// console.log(`${ severity.toUpperCase() }:`, i); | ||
} | ||
@@ -74,0 +74,0 @@ getFunc(stack) { |
@@ -73,3 +73,3 @@ "use strict"; | ||
const user = new accessors_1.UserRead(this.bridges.getUserBridge(), appId); | ||
const noti = new accessors_1.Notifier(this.bridges, appId); | ||
const noti = new accessors_1.Notifier(this.bridges.getMessageBridge(), appId); | ||
this.readers.set(appId, new accessors_1.Reader(env, msg, persist, room, user, noti)); | ||
@@ -93,3 +93,10 @@ } | ||
if (!this.https.has(appId)) { | ||
const ext = this.configExtenders.get(appId).http; | ||
let ext; | ||
if (this.configExtenders.has(appId)) { | ||
ext = this.configExtenders.get(appId).http; | ||
} | ||
else { | ||
const cf = this.getConfigurationExtend(appId); | ||
ext = cf.http; | ||
} | ||
this.https.set(appId, new accessors_1.Http(this, this.bridges, ext, appId)); | ||
@@ -96,0 +103,0 @@ } |
@@ -20,2 +20,4 @@ import { AppManager } from '../AppManager'; | ||
private executePostMessageSent(data); | ||
private executePreMessageDeletePrevent(data); | ||
private executePostMessageDelete(data); | ||
private executePreRoomCreatePrevent(data); | ||
@@ -25,2 +27,4 @@ private executePreRoomCreateExtend(data); | ||
private executePostRoomCreate(data); | ||
private executePreRoomDeletePrevent(data); | ||
private executePostRoomDeleted(data); | ||
} |
@@ -49,2 +49,3 @@ "use strict"; | ||
switch (int) { | ||
// Messages | ||
case compiler_1.AppInterface.IPreMessageSentPrevent: | ||
@@ -59,2 +60,8 @@ return this.executePreMessageSentPrevent(data); | ||
return; | ||
case compiler_1.AppInterface.IPreMessageDeletePrevent: | ||
return this.executePreMessageDeletePrevent(data); | ||
case compiler_1.AppInterface.IPostMessageDeleted: | ||
this.executePostMessageDelete(data); | ||
return; | ||
// Rooms | ||
case compiler_1.AppInterface.IPreRoomCreatePrevent: | ||
@@ -69,2 +76,7 @@ return this.executePreRoomCreatePrevent(data); | ||
return; | ||
case compiler_1.AppInterface.IPreRoomDeletePrevent: | ||
return this.executePreRoomDeletePrevent(data); | ||
case compiler_1.AppInterface.IPostRoomDeleted: | ||
this.executePostRoomDeleted(data); | ||
return; | ||
default: | ||
@@ -76,2 +88,3 @@ console.warn('Unimplemented (or invalid) AppInterface was just tried to execute.'); | ||
} | ||
// Messages | ||
executePreMessageSentPrevent(data) { | ||
@@ -147,2 +160,38 @@ return __awaiter(this, void 0, void 0, function* () { | ||
} | ||
executePreMessageDeletePrevent(data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let prevented = false; | ||
const cfMsg = Utilities_1.Utilities.deepCloneAndFreeze(data); | ||
for (const appId of this.listeners.get(compiler_1.AppInterface.IPreMessageDeletePrevent)) { | ||
const app = this.manager.getOneById(appId); | ||
let continueOn = true; | ||
if (app.hasMethod(metadata_1.AppMethod.CHECKPREMESSAGEDELETEPREVENT)) { | ||
continueOn = (yield app.call(metadata_1.AppMethod.CHECKPREMESSAGEDELETEPREVENT, cfMsg, this.am.getReader(appId), this.am.getHttp(appId))); | ||
} | ||
if (continueOn && app.hasMethod(metadata_1.AppMethod.EXECUTEPREMESSAGEDELETEPREVENT)) { | ||
prevented = (yield app.call(metadata_1.AppMethod.EXECUTEPREMESSAGEDELETEPREVENT, cfMsg, this.am.getReader(appId), this.am.getHttp(appId), this.am.getPersistence(appId))); | ||
if (prevented) { | ||
return prevented; | ||
} | ||
} | ||
} | ||
return prevented; | ||
}); | ||
} | ||
executePostMessageDelete(data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const cfMsg = Utilities_1.Utilities.deepCloneAndFreeze(data); | ||
for (const appId of this.listeners.get(compiler_1.AppInterface.IPostMessageDeleted)) { | ||
const app = this.manager.getOneById(appId); | ||
let continueOn = true; | ||
if (app.hasMethod(metadata_1.AppMethod.CHECKPOSTMESSAGEDELETED)) { | ||
continueOn = (yield app.call(metadata_1.AppMethod.CHECKPOSTMESSAGEDELETED, cfMsg, this.am.getReader(appId), this.am.getHttp(appId))); | ||
} | ||
if (continueOn && app.hasMethod(metadata_1.AppMethod.EXECUTEPOSTMESSAGEDELETED)) { | ||
yield app.call(metadata_1.AppMethod.EXECUTEPOSTMESSAGEDELETED, cfMsg, this.am.getReader(appId), this.am.getHttp(appId), this.am.getPersistence(appId)); | ||
} | ||
} | ||
}); | ||
} | ||
// Rooms | ||
executePreRoomCreatePrevent(data) { | ||
@@ -218,2 +267,37 @@ return __awaiter(this, void 0, void 0, function* () { | ||
} | ||
executePreRoomDeletePrevent(data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const cfRoom = Utilities_1.Utilities.deepCloneAndFreeze(data); | ||
let prevented = false; | ||
for (const appId of this.listeners.get(compiler_1.AppInterface.IPreRoomDeletePrevent)) { | ||
const app = this.manager.getOneById(appId); | ||
let continueOn = true; | ||
if (app.hasMethod(metadata_1.AppMethod.CHECKPREROOMDELETEPREVENT)) { | ||
continueOn = (yield app.call(metadata_1.AppMethod.CHECKPREROOMDELETEPREVENT, cfRoom, this.am.getReader(appId), this.am.getHttp(appId))); | ||
} | ||
if (continueOn && app.hasMethod(metadata_1.AppMethod.EXECUTEPREROOMDELETEPREVENT)) { | ||
prevented = (yield app.call(metadata_1.AppMethod.EXECUTEPREROOMDELETEPREVENT, cfRoom, this.am.getReader(appId), this.am.getHttp(appId), this.am.getPersistence(appId))); | ||
if (prevented) { | ||
return prevented; | ||
} | ||
} | ||
} | ||
return prevented; | ||
}); | ||
} | ||
executePostRoomDeleted(data) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const cfRoom = Utilities_1.Utilities.deepCloneAndFreeze(data); | ||
for (const appId of this.listeners.get(compiler_1.AppInterface.IPostRoomDeleted)) { | ||
const app = this.manager.getOneById(appId); | ||
let continueOn = true; | ||
if (app.hasMethod(metadata_1.AppMethod.CHECKPOSTROOMDELETED)) { | ||
continueOn = (yield app.call(metadata_1.AppMethod.CHECKPOSTROOMDELETED, cfRoom, this.am.getReader(appId), this.am.getHttp(appId))); | ||
} | ||
if (continueOn && app.hasMethod(metadata_1.AppMethod.EXECUTEPOSTROOMDELETED)) { | ||
yield app.call(metadata_1.AppMethod.EXECUTEPOSTROOMDELETED, cfRoom, this.am.getReader(appId), this.am.getHttp(appId), this.am.getPersistence(appId)); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
@@ -220,0 +304,0 @@ exports.AppListenerManger = AppListenerManger; |
@@ -12,2 +12,3 @@ "use strict"; | ||
const metadata_1 = require("@rocket.chat/apps-ts-definition/metadata"); | ||
const Utilities_1 = require("../misc/Utilities"); | ||
class AppSettingsManager { | ||
@@ -22,3 +23,3 @@ constructor(manager) { | ||
} | ||
return Object.assign({}, rl.getStorageItem().settings); | ||
return Utilities_1.Utilities.deepCloneAndFreeze(rl.getStorageItem().settings); | ||
} | ||
@@ -30,3 +31,3 @@ getAppSetting(appId, settingId) { | ||
} | ||
return Object.assign({}, settings[settingId]); | ||
return Utilities_1.Utilities.deepCloneAndFreeze(settings[settingId]); | ||
} | ||
@@ -33,0 +34,0 @@ updateAppSetting(appId, setting) { |
@@ -1,2 +0,2 @@ | ||
import { ISlashCommand, SlashCommandContext } from '@rocket.chat/apps-ts-definition/slashcommands'; | ||
import { ISlashCommand, ISlashCommandPreview, ISlashCommandPreviewItem, SlashCommandContext } from '@rocket.chat/apps-ts-definition/slashcommands'; | ||
import { AppManager } from '../AppManager'; | ||
@@ -9,2 +9,4 @@ /** | ||
* only then will that App's commands be enabled. | ||
* | ||
* Registered means the command has been provided to the bridged system. | ||
*/ | ||
@@ -15,10 +17,36 @@ export declare class AppSlashCommandManager { | ||
private readonly accessors; | ||
private rlCommands; | ||
private commands; | ||
private commandMappingToApp; | ||
private providedCommands; | ||
private modifiedCommands; | ||
private touchedCommandsToApps; | ||
private appsTouchedCommands; | ||
constructor(manager: AppManager); | ||
/** | ||
* Adds a command to be registered. This will not register it with the | ||
* Checks whether an App can touch a command or not. There are only two ways an App can touch | ||
* a command: | ||
* 1. The command has yet to be touched | ||
* 2. The app has already touched the command | ||
* | ||
* When do we consider an App touching a command? Whenever it adds, modifies, | ||
* or removes one that it didn't provide. | ||
* | ||
* @param appId the app's id which to check for | ||
* @param command the command to check about | ||
* @returns whether or not the app can touch the command | ||
*/ | ||
canCommandBeTouchedBy(appId: string, command: string): boolean; | ||
/** | ||
* Determines whether the command is already provided by an App or not. | ||
* It is case insensitive. | ||
* | ||
* @param command the command to check if it exists or not | ||
* @returns whether or not it is already provided | ||
*/ | ||
isAlreadyDefined(command: string): boolean; | ||
/** | ||
* Adds a command to *be* registered. This will *not register* it with the | ||
* bridged system yet as this is only called on an App's | ||
* `initialize` method which the App might not be enabled. | ||
* `initialize` method and an App might not get enabled. | ||
* When adding a command, it can *not* already exist in the system | ||
* (to overwrite) and another App can *not* have already touched or provided it. | ||
* Apps are on a first come first serve basis for providing and modifying commands. | ||
* | ||
@@ -30,5 +58,7 @@ * @param appId the app's id which the command belongs to | ||
/** | ||
* Modifies an existing command. The command must either be your App's | ||
* Modifies an existing command. The command must either be the App's | ||
* own command or a system command. One App can not modify another | ||
* App's command. | ||
* App's command. Apps are on a first come first serve basis as to whether | ||
* or not they can touch or provide a command. If App "A" first provides, | ||
* or overwrites, a command then App "B" can not touch that command. | ||
* | ||
@@ -39,5 +69,16 @@ * @param appId the app's id of the command to modify | ||
modifyCommand(appId: string, command: ISlashCommand): void; | ||
/** | ||
* Goes and enables a command in the bridged system. The command | ||
* which is being enabled must either be the App's or a system | ||
* command which has yet to be touched by an App. | ||
* | ||
* @param appId the id of the app enabling the command | ||
* @param command the command which is being enabled | ||
*/ | ||
enableCommand(appId: string, command: string): void; | ||
/** | ||
* Renders an existing slash command un-usable. | ||
* Renders an existing slash command un-usable. Whether that command is provided | ||
* by the App calling this or a command provided by the bridged system, we don't care. | ||
* However, an App can not disable a command which has already been touched | ||
* by another App in some way. | ||
* | ||
@@ -55,2 +96,8 @@ * @param appId the app's id which is disabling the command | ||
registerCommands(appId: string): void; | ||
/** | ||
* Unregisters the commands from the system and restores the commands | ||
* which the app modified in the system. | ||
* | ||
* @param appId the appId for the commands to purge | ||
*/ | ||
unregisterCommands(appId: string): void; | ||
@@ -63,11 +110,28 @@ /** | ||
*/ | ||
executeCommand(command: string, context: SlashCommandContext): void; | ||
private registerCommand(appId, info); | ||
executeCommand(command: string, context: SlashCommandContext): Promise<void>; | ||
getPreviews(command: string, context: SlashCommandContext): Promise<ISlashCommandPreview>; | ||
executePreview(command: string, previewItem: ISlashCommandPreviewItem, context: SlashCommandContext): Promise<ISlashCommandPreview>; | ||
/** | ||
* Determines whether the provided command is already defined in the App system or not. | ||
* Determines if the command's functions should run, | ||
* this way the code isn't duplicated three times. | ||
* | ||
* @param command the command to check if it exists or not | ||
* @param command the lowercase and trimmed command | ||
* @returns whether or not to continue | ||
*/ | ||
private isAlreadyDefined(command); | ||
private getAppCommand(appId, command); | ||
private shouldCommandFunctionsRun(command); | ||
private retrieveCommandInfo(command, appId); | ||
/** | ||
* Sets that an App has been touched. | ||
* | ||
* @param appId the app's id which has touched the command | ||
* @param command the command, lowercase and trimmed, which has been touched | ||
*/ | ||
private setAsTouched(appId, command); | ||
/** | ||
* Actually goes and provide's the bridged system with the command information. | ||
* | ||
* @param appId the app which is providing the command | ||
* @param info the command's registration information | ||
*/ | ||
private registerCommand(appId, info); | ||
} |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const AppStatus_1 = require("@rocket.chat/apps-ts-definition/AppStatus"); | ||
const metadata_1 = require("@rocket.chat/apps-ts-definition/metadata"); | ||
const CommandAlreadyExistsError_1 = require("../errors/CommandAlreadyExistsError"); | ||
const AppSlashCommandRegistration_1 = require("./AppSlashCommandRegistration"); | ||
const errors_1 = require("../errors"); | ||
const AppSlashCommand_1 = require("./AppSlashCommand"); | ||
/** | ||
@@ -12,5 +21,6 @@ * The command manager for the Apps. | ||
* only then will that App's commands be enabled. | ||
* | ||
* Registered means the command has been provided to the bridged system. | ||
*/ | ||
class AppSlashCommandManager { | ||
// tslint:disable-next-line:max-line-length | ||
constructor(manager) { | ||
@@ -20,10 +30,48 @@ this.manager = manager; | ||
this.accessors = this.manager.getAccessorManager(); | ||
this.rlCommands = new Map(); | ||
this.commands = new Map(); | ||
this.commandMappingToApp = new Map(); | ||
this.touchedCommandsToApps = new Map(); | ||
this.appsTouchedCommands = new Map(); | ||
this.providedCommands = new Map(); | ||
this.modifiedCommands = new Map(); | ||
} | ||
/** | ||
* Adds a command to be registered. This will not register it with the | ||
* Checks whether an App can touch a command or not. There are only two ways an App can touch | ||
* a command: | ||
* 1. The command has yet to be touched | ||
* 2. The app has already touched the command | ||
* | ||
* When do we consider an App touching a command? Whenever it adds, modifies, | ||
* or removes one that it didn't provide. | ||
* | ||
* @param appId the app's id which to check for | ||
* @param command the command to check about | ||
* @returns whether or not the app can touch the command | ||
*/ | ||
canCommandBeTouchedBy(appId, command) { | ||
const cmd = command.toLowerCase().trim(); | ||
return cmd && (!this.touchedCommandsToApps.has(cmd) || this.touchedCommandsToApps.get(cmd) === appId); | ||
} | ||
/** | ||
* Determines whether the command is already provided by an App or not. | ||
* It is case insensitive. | ||
* | ||
* @param command the command to check if it exists or not | ||
* @returns whether or not it is already provided | ||
*/ | ||
isAlreadyDefined(command) { | ||
const search = command.toLowerCase().trim(); | ||
let exists = false; | ||
this.providedCommands.forEach((cmds) => { | ||
if (cmds.has(search)) { | ||
exists = true; | ||
} | ||
}); | ||
return exists; | ||
} | ||
/** | ||
* Adds a command to *be* registered. This will *not register* it with the | ||
* bridged system yet as this is only called on an App's | ||
* `initialize` method which the App might not be enabled. | ||
* `initialize` method and an App might not get enabled. | ||
* When adding a command, it can *not* already exist in the system | ||
* (to overwrite) and another App can *not* have already touched or provided it. | ||
* Apps are on a first come first serve basis for providing and modifying commands. | ||
* | ||
@@ -35,14 +83,27 @@ * @param appId the app's id which the command belongs to | ||
command.command = command.command.toLowerCase().trim(); | ||
// Ensure the app can touch this command | ||
if (!this.canCommandBeTouchedBy(appId, command.command)) { | ||
throw new errors_1.CommandHasAlreadyBeenTouchedError(command.command); | ||
} | ||
// Verify the command doesn't exist already | ||
if (this.bridge.doesCommandExist(command.command, appId) || this.isAlreadyDefined(command.command)) { | ||
throw new CommandAlreadyExistsError_1.CommandAlreadyExistsError(command.command); | ||
throw new errors_1.CommandAlreadyExistsError(command.command); | ||
} | ||
if (!this.rlCommands.has(appId)) { | ||
this.rlCommands.set(appId, new Array()); | ||
const app = this.manager.getOneById(appId); | ||
if (!app) { | ||
throw new Error('App must exist in order for a command to be added.'); | ||
} | ||
this.rlCommands.get(appId).push(new AppSlashCommandRegistration_1.AppSlashCommandRegistration(command)); | ||
if (!this.providedCommands.has(appId)) { | ||
this.providedCommands.set(appId, new Map()); | ||
} | ||
this.providedCommands.get(appId).set(command.command, new AppSlashCommand_1.AppSlashCommand(app, command)); | ||
// The app has now touched the command, so let's set it | ||
this.setAsTouched(appId, command.command); | ||
} | ||
/** | ||
* Modifies an existing command. The command must either be your App's | ||
* Modifies an existing command. The command must either be the App's | ||
* own command or a system command. One App can not modify another | ||
* App's command. | ||
* App's command. Apps are on a first come first serve basis as to whether | ||
* or not they can touch or provide a command. If App "A" first provides, | ||
* or overwrites, a command then App "B" can not touch that command. | ||
* | ||
@@ -52,40 +113,67 @@ * @param appId the app's id of the command to modify | ||
*/ | ||
// TODO: Rework this logic, as the logic inside the method does not match up to the documentation | ||
modifyCommand(appId, command) { | ||
// tslint:disable-next-line:max-line-length | ||
if (!this.rlCommands.has(appId) || this.rlCommands.get(appId).filter((r) => r.slashCommand.command === command.command).length === 0) { | ||
if (!this.bridge.doesCommandExist(command.command, appId)) { | ||
throw new Error('You must first register a command before you can modify it.'); | ||
} | ||
command.command = command.command.toLowerCase().trim(); | ||
// Ensure the app can touch this command | ||
if (!this.canCommandBeTouchedBy(appId, command.command)) { | ||
throw new errors_1.CommandHasAlreadyBeenTouchedError(command.command); | ||
} | ||
const index = this.rlCommands.get(appId).findIndex((r) => r.slashCommand.command === command.command); | ||
if (index === -1) { | ||
const app = this.manager.getOneById(appId); | ||
if (!app) { | ||
throw new Error('App must exist in order to modify a command.'); | ||
} | ||
const hasNotProvidedIt = !this.providedCommands.has(appId) || !this.providedCommands.get(appId).has(command.command); | ||
// They haven't provided (added) it and the bridged system doesn't have it, error out | ||
if (hasNotProvidedIt && !this.bridge.doesCommandExist(command.command, appId)) { | ||
throw new Error('You must first register a command before you can modify it.'); | ||
} | ||
if (hasNotProvidedIt) { | ||
this.bridge.modifyCommand(command, appId); | ||
const regInfo = new AppSlashCommand_1.AppSlashCommand(app, command); | ||
regInfo.isDisabled = false; | ||
regInfo.isEnabled = true; | ||
regInfo.isRegistered = true; | ||
this.modifiedCommands.set(command.command, regInfo); | ||
} | ||
else { | ||
this.rlCommands.get(appId)[index].slashCommand = command; | ||
this.providedCommands.get(appId).get(command.command).slashCommand = command; | ||
} | ||
this.commandMappingToApp.set(command.command, appId); | ||
this.setAsTouched(appId, command.command); | ||
} | ||
/** | ||
* Goes and enables a command in the bridged system. The command | ||
* which is being enabled must either be the App's or a system | ||
* command which has yet to be touched by an App. | ||
* | ||
* @param appId the id of the app enabling the command | ||
* @param command the command which is being enabled | ||
*/ | ||
enableCommand(appId, command) { | ||
const cmdInfo = this.getAppCommand(appId, command); | ||
if (this.isAlreadyDefined(command) && cmdInfo && !cmdInfo.isRegistered) { | ||
cmdInfo.isDisabled = false; | ||
cmdInfo.isEnabled = true; | ||
const cmd = command.toLowerCase().trim(); | ||
// Ensure the app can touch this command | ||
if (!this.canCommandBeTouchedBy(appId, cmd)) { | ||
throw new errors_1.CommandHasAlreadyBeenTouchedError(cmd); | ||
} | ||
// Handle if the App provided the command fist | ||
if (this.providedCommands.has(appId) && this.providedCommands.get(appId).has(cmd)) { | ||
const cmdInfo = this.providedCommands.get(appId).get(cmd); | ||
// A command marked as disabled can then be "enabled" but not be registered. | ||
// This happens when an App is not enabled and they change the status of | ||
// command based upon a setting they provide which a User can change. | ||
if (!cmdInfo.isRegistered) { | ||
cmdInfo.isDisabled = false; | ||
cmdInfo.isEnabled = true; | ||
} | ||
return; | ||
} | ||
const exists = this.bridge.doesCommandExist(command, appId); | ||
if (!exists && !cmdInfo) { | ||
throw new Error(`The command "${command}" does not exist to disable.`); | ||
if (!this.bridge.doesCommandExist(cmd, appId)) { | ||
throw new Error(`The command "${cmd}" does not exist to enable.`); | ||
} | ||
else if (!exists && cmdInfo) { | ||
// This means it was "disabled" when the command was registered | ||
// so now we need to register it again. | ||
this.registerCommand(appId, cmdInfo); | ||
return; | ||
} | ||
this.bridge.enableCommand(command, appId); | ||
this.bridge.enableCommand(cmd, appId); | ||
this.setAsTouched(appId, cmd); | ||
} | ||
/** | ||
* Renders an existing slash command un-usable. | ||
* Renders an existing slash command un-usable. Whether that command is provided | ||
* by the App calling this or a command provided by the bridged system, we don't care. | ||
* However, an App can not disable a command which has already been touched | ||
* by another App in some way. | ||
* | ||
@@ -96,12 +184,24 @@ * @param appId the app's id which is disabling the command | ||
disableCommand(appId, command) { | ||
const cmdInfo = this.getAppCommand(appId, command); | ||
if (this.isAlreadyDefined(command) && cmdInfo && !cmdInfo.isRegistered) { | ||
cmdInfo.isDisabled = true; | ||
cmdInfo.isEnabled = false; | ||
const cmd = command.toLowerCase().trim(); | ||
// Ensure the app can touch this command | ||
if (!this.canCommandBeTouchedBy(appId, cmd)) { | ||
throw new errors_1.CommandHasAlreadyBeenTouchedError(cmd); | ||
} | ||
// Handle if the App provided the command fist | ||
if (this.providedCommands.has(appId) && this.providedCommands.get(appId).has(cmd)) { | ||
const cmdInfo = this.providedCommands.get(appId).get(cmd); | ||
// A command marked as enabled can then be "disabled" but not yet be registered. | ||
// This happens when an App is not enabled and they change the status of | ||
// command based upon a setting they provide which a User can change. | ||
if (!cmdInfo.isRegistered) { | ||
cmdInfo.isDisabled = true; | ||
cmdInfo.isEnabled = false; | ||
} | ||
return; | ||
} | ||
if (!this.bridge.doesCommandExist(command, appId)) { | ||
throw new Error(`The command "${command}" does not exist to disable.`); | ||
if (!this.bridge.doesCommandExist(cmd, appId)) { | ||
throw new Error(`The command "${cmd}" does not exist to disable.`); | ||
} | ||
this.bridge.disableCommand(command, appId); | ||
this.bridge.disableCommand(cmd, appId); | ||
this.setAsTouched(appId, cmd); | ||
} | ||
@@ -115,7 +215,6 @@ /** | ||
registerCommands(appId) { | ||
if (!this.rlCommands.has(appId)) { | ||
if (!this.providedCommands.has(appId)) { | ||
return; | ||
} | ||
this.rlCommands.get(appId).forEach((r) => { | ||
r.isRegistered = true; | ||
this.providedCommands.get(appId).forEach((r) => { | ||
if (r.isDisabled) { | ||
@@ -127,13 +226,32 @@ return; | ||
} | ||
/** | ||
* Unregisters the commands from the system and restores the commands | ||
* which the app modified in the system. | ||
* | ||
* @param appId the appId for the commands to purge | ||
*/ | ||
unregisterCommands(appId) { | ||
if (!this.rlCommands.has(appId)) { | ||
return; | ||
if (this.providedCommands.has(appId)) { | ||
this.providedCommands.get(appId).forEach((r) => { | ||
this.bridge.unregisterCommand(r.slashCommand.command, appId); | ||
this.touchedCommandsToApps.delete(r.slashCommand.command); | ||
const ind = this.appsTouchedCommands.get(appId).indexOf(r.slashCommand.command); | ||
this.appsTouchedCommands.get(appId).splice(ind, 1); | ||
r.isRegistered = true; | ||
}); | ||
this.providedCommands.delete(appId); | ||
} | ||
this.rlCommands.get(appId).forEach((r) => { | ||
this.commands.delete(r.slashCommand.command); | ||
this.commandMappingToApp.delete(r.slashCommand.command); | ||
this.bridge.unregisterCommand(r.slashCommand.command, appId); | ||
r.isRegistered = false; | ||
}); | ||
this.rlCommands.delete(appId); | ||
if (this.appsTouchedCommands.has(appId)) { | ||
// The commands inside the appsTouchedCommands should now | ||
// only be the ones which the App has enabled, disabled, or modified. | ||
// We call restore to enable the commands provided by the bridged system | ||
// or unmodify the commands modified by the App | ||
this.appsTouchedCommands.get(appId).forEach((cmd) => { | ||
this.bridge.restoreCommand(cmd, appId); | ||
this.modifiedCommands.get(cmd).isRegistered = false; | ||
this.modifiedCommands.delete(cmd); | ||
this.touchedCommandsToApps.delete(cmd); | ||
}); | ||
this.appsTouchedCommands.delete(appId); | ||
} | ||
} | ||
@@ -147,61 +265,126 @@ /** | ||
executeCommand(command, context) { | ||
if (!this.commands.has(command) || !this.commandMappingToApp.has(command)) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const cmd = command.toLowerCase().trim(); | ||
if (!this.shouldCommandFunctionsRun(cmd)) { | ||
return; | ||
} | ||
const app = this.manager.getOneById(this.touchedCommandsToApps.get(cmd)); | ||
if (!app) { | ||
// Just in case someone decides to do something they shouldn't | ||
// let's ensure the app actually exists | ||
return; | ||
} | ||
if (AppStatus_1.AppStatusUtils.isDisabled(app.getStatus())) { | ||
return; | ||
} | ||
if (!app.hasMethod(metadata_1.AppMethod._COMMAND_EXECUTOR)) { | ||
return; | ||
} | ||
const appCmd = this.retrieveCommandInfo(cmd, app.getID()); | ||
yield appCmd.runExecutorOrPreviewer(metadata_1.AppMethod._COMMAND_EXECUTOR, context, this.manager.getLogStorage(), this.accessors); | ||
return; | ||
} | ||
const app = this.manager.getOneById(this.commandMappingToApp.get(command)); | ||
const slashCommand = this.commands.get(command).slashCommand; | ||
const runContext = app.makeContext({ | ||
slashCommand, | ||
args: [ | ||
context, | ||
this.accessors.getReader(app.getID()), | ||
this.accessors.getModifier(app.getID()), | ||
this.accessors.getHttp(app.getID()), | ||
this.accessors.getPersistence(app.getID()), | ||
], | ||
}); | ||
const logger = app.setupLogger(metadata_1.AppMethod._COMMAND_EXECUTOR); | ||
logger.debug(`${command} is being executed...`, context); | ||
try { | ||
const runCode = 'slashCommand.executor.apply(slashCommand, args)'; | ||
app.runInContext(runCode, runContext); | ||
logger.debug(`${command} was successfully executed.`); | ||
} | ||
getPreviews(command, context) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const cmd = command.toLowerCase().trim(); | ||
if (!this.shouldCommandFunctionsRun(cmd)) { | ||
return; | ||
} | ||
const app = this.manager.getOneById(this.touchedCommandsToApps.get(cmd)); | ||
if (!app) { | ||
// Just in case someone decides to do something they shouldn't | ||
// let's ensure the app actually exists | ||
return; | ||
} | ||
if (AppStatus_1.AppStatusUtils.isDisabled(app.getStatus())) { | ||
return; | ||
} | ||
if (!app.hasMethod(metadata_1.AppMethod._COMMAND_PREVIEWER)) { | ||
return; | ||
} | ||
const appCmd = this.retrieveCommandInfo(cmd, app.getID()); | ||
const result = yield appCmd.runExecutorOrPreviewer(metadata_1.AppMethod._COMMAND_EXECUTOR, context, this.manager.getLogStorage(), this.accessors); | ||
if (!result) { | ||
// Failed to get the preview, thus returning is fine | ||
return; | ||
} | ||
return result; | ||
}); | ||
} | ||
executePreview(command, previewItem, context) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const cmd = command.toLowerCase().trim(); | ||
if (!this.shouldCommandFunctionsRun(cmd)) { | ||
return; | ||
} | ||
const app = this.manager.getOneById(this.touchedCommandsToApps.get(cmd)); | ||
if (!app) { | ||
// Just in case someone decides to do something they shouldn't | ||
// let's ensure the app actually exists | ||
return; | ||
} | ||
if (AppStatus_1.AppStatusUtils.isDisabled(app.getStatus())) { | ||
return; | ||
} | ||
if (!app.hasMethod(metadata_1.AppMethod._COMMAND_PREVIEWER)) { | ||
return; | ||
} | ||
const appCmd = this.retrieveCommandInfo(cmd, app.getID()); | ||
yield appCmd.runPreviewExecutor(previewItem, context, this.manager.getLogStorage(), this.accessors); | ||
return; | ||
}); | ||
} | ||
/** | ||
* Determines if the command's functions should run, | ||
* this way the code isn't duplicated three times. | ||
* | ||
* @param command the lowercase and trimmed command | ||
* @returns whether or not to continue | ||
*/ | ||
shouldCommandFunctionsRun(command) { | ||
// None of the Apps have touched the command to execute, | ||
// thus we don't care so exit out | ||
if (!this.touchedCommandsToApps.has(command)) { | ||
return false; | ||
} | ||
catch (e) { | ||
logger.error(e); | ||
logger.debug(`${command} was unsuccessful.`); | ||
const appId = this.touchedCommandsToApps.get(command); | ||
const cmdInfo = this.retrieveCommandInfo(command, appId); | ||
// Should the command information really not exist | ||
// Or if the command hasn't been registered | ||
// Or the command is disabled on our side | ||
// then let's not execute it, as the App probably doesn't want it yet | ||
if (!cmdInfo || !cmdInfo.isRegistered || cmdInfo.isDisabled) { | ||
return false; | ||
} | ||
this.manager.getLogStorage().storeEntries(app.getID(), logger); | ||
return true; | ||
} | ||
registerCommand(appId, info) { | ||
this.commands.set(info.slashCommand.command, info); | ||
this.commandMappingToApp.set(info.slashCommand.command, appId); | ||
this.bridge.registerCommand(info.slashCommand, appId); | ||
retrieveCommandInfo(command, appId) { | ||
return this.modifiedCommands.get(command) || this.providedCommands.get(appId).get(command); | ||
} | ||
/** | ||
* Determines whether the provided command is already defined in the App system or not. | ||
* Sets that an App has been touched. | ||
* | ||
* @param command the command to check if it exists or not | ||
* @param appId the app's id which has touched the command | ||
* @param command the command, lowercase and trimmed, which has been touched | ||
*/ | ||
isAlreadyDefined(command) { | ||
let exists = false; | ||
this.rlCommands.forEach((cmds) => cmds.forEach((r) => { | ||
if (r.slashCommand.command === command) { | ||
exists = true; | ||
} | ||
})); | ||
return exists; | ||
} | ||
getAppCommand(appId, command) { | ||
if (!this.isAlreadyDefined(command)) { | ||
return undefined; | ||
setAsTouched(appId, command) { | ||
if (!this.appsTouchedCommands.has(appId)) { | ||
this.appsTouchedCommands.set(appId, new Array()); | ||
} | ||
let info; | ||
this.rlCommands.get(appId).forEach((r) => { | ||
if (r.slashCommand.command === command) { | ||
info = r; | ||
} | ||
}); | ||
return info; | ||
if (!this.appsTouchedCommands.get(appId).includes(command)) { | ||
this.appsTouchedCommands.get(appId).push(command); | ||
} | ||
this.touchedCommandsToApps.set(command, appId); | ||
} | ||
/** | ||
* Actually goes and provide's the bridged system with the command information. | ||
* | ||
* @param appId the app which is providing the command | ||
* @param info the command's registration information | ||
*/ | ||
registerCommand(appId, info) { | ||
this.bridge.registerCommand(info.slashCommand, appId); | ||
info.hasBeenRegistered(); | ||
} | ||
} | ||
@@ -208,0 +391,0 @@ exports.AppSlashCommandManager = AppSlashCommandManager; |
@@ -23,3 +23,3 @@ /// <reference types="node" /> | ||
getImplementationList(): { | ||
[int: string]: boolean; | ||
[inter: string]: boolean; | ||
}; | ||
@@ -26,0 +26,0 @@ hasMethod(method: AppMethod): boolean; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
488365
240
4859
23
23
Updatedadm-zip@^0.4.9
Updatedtypescript@^2.8.3