diff --git a/src/definition/accessors/IHttp.ts b/src/definition/accessors/IHttp.ts index 7652eae45..6c2becaf8 100644 --- a/src/definition/accessors/IHttp.ts +++ b/src/definition/accessors/IHttp.ts @@ -37,6 +37,20 @@ export interface IHttpRequest { [key: string]: string, }; timeout?: number; + /** + * if `true`, requires SSL certificates be valid. + * + * Defaul: `true`; + */ + strictSSL?: boolean; + /** + * If `true`, the server certificate is verified against the list of supplied CAs. + * + * Default: `true`. + * + * https://nodejs.org/api/tls.html#tls_tls_connect_options_callback + */ + rejectUnauthorized?: boolean; } export interface IHttpResponse { diff --git a/src/definition/accessors/ILivechatRead.ts b/src/definition/accessors/ILivechatRead.ts new file mode 100644 index 000000000..4d1c0a54a --- /dev/null +++ b/src/definition/accessors/ILivechatRead.ts @@ -0,0 +1,7 @@ +import { ILivechatRoom } from '../livechat/ILivechatRoom'; +import { IVisitor } from '../livechat/IVisitor'; + +export interface ILivechatRead { + getLivechatRooms(visitor: IVisitor, departmentId?: string): Promise>; + getLivechatVisitors(query: object): Promise>; +} diff --git a/src/definition/accessors/IModify.ts b/src/definition/accessors/IModify.ts index 8191e5788..9d96b897c 100644 --- a/src/definition/accessors/IModify.ts +++ b/src/definition/accessors/IModify.ts @@ -1,3 +1,5 @@ +import { ILivechatMessage, ILivechatRoom, ILivechatTransferData } from '../livechat'; +import { IVisitor } from '../livechat/IVisitor'; import { IMessage, IMessageAttachment } from '../messages'; import { RocketChatAssociationModel } from '../metadata'; import { IRoom, RoomType } from '../rooms'; @@ -46,6 +48,11 @@ export interface INotifier { } export interface IModifyUpdater { + /** + * Get the updater object responsible for the + * Livechat integrations + */ + getLivechatUpdater(): ILivechatUpdater; /** * Modifies an existing message. * Raises an exception if a non-existent messageId is supplied @@ -104,6 +111,11 @@ export interface IModifyExtender { } export interface IModifyCreator { + /** + * Get the creator object responsible for the + * Livechat integrations + */ + getLivechatCreator(): ILivechatCreator; /** * Starts the process for building a new message object. * @@ -113,6 +125,15 @@ export interface IModifyCreator { */ startMessage(data?: IMessage): IMessageBuilder; + /** + * Starts the process for building a new livechat message object. + * + * @param data (optional) the initial data to pass into the builder, + * the `id` property will be ignored + * @return an IMessageBuilder instance + */ + startLivechatMessage(data?: ILivechatMessage): ILivechatMessageBuilder; + /** * Starts the process for building a new room. * @@ -128,7 +149,49 @@ export interface IModifyCreator { * @param builder the builder instance * @return the resulting `id` of the resulting object */ - finish(builder: IMessageBuilder | IRoomBuilder): Promise; + finish(builder: IMessageBuilder | ILivechatMessageBuilder | IRoomBuilder): Promise; +} + +export interface ILivechatCreator { + /** + * Creates a room to connect the `visitor` to an `agent`. + * + * This method uses the Livechat routing method configured + * in the server + * + * @param visitor The Livechat Visitor that started the conversation + * @param agent The agent responsible for the room + */ + createRoom(visitor: IVisitor, agent: IUser): Promise; + /** + * Creates a Livechat visitor + * + * @param visitor Data of the visitor to be created + */ + createVisitor(visitor: IVisitor): Promise; + + /** + * Creates a token to be used when + * creating a new livechat visitor + */ + createToken(): string; +} + +export interface ILivechatUpdater { + /** + * Transfer a Livechat visitor to another room + * + * @param visitor Visitor to be transfered + * @param transferData The data to execute the transfering + */ + transferVisitor(visitor: IVisitor, transferData: ILivechatTransferData): Promise; + /** + * Closes a Livechat room + * + * @param room The room to be closed + * @param comment The comment explaining the reason for closing the room + */ + closeRoom(room: IRoom, comment: string): Promise; } export interface IMessageExtender { @@ -377,6 +440,219 @@ export interface IMessageBuilder { getMessage(): IMessage; } +/** + * Interface for building out a livechat message. + * Please note, that a room and sender must be associated otherwise you will NOT + * be able to successfully save the message object. + */ +export interface ILivechatMessageBuilder { + kind: RocketChatAssociationModel.LIVECHAT_MESSAGE; + + /** + * Provides a convient way to set the data for the message. + * Note: Providing an "id" field here will be ignored. + * + * @param message the message data to set + */ + setData(message: ILivechatMessage): ILivechatMessageBuilder; + + /** + * Sets the room where this message should be sent to. + * + * @param room the room where to send + */ + setRoom(room: IRoom): ILivechatMessageBuilder; + + /** + * Gets the room where this message was sent to. + */ + getRoom(): IRoom; + + /** + * Sets the sender of this message. + * + * @param sender the user sending the message + */ + setSender(sender: IUser): ILivechatMessageBuilder; + + /** + * Gets the User which sent the message. + */ + getSender(): IUser; + + /** + * Sets the text of the message. + * + * @param text the actual text + */ + setText(text: string): ILivechatMessageBuilder; + + /** + * Gets the message text. + */ + getText(): string; + + /** + * Sets the emoji to use for the avatar, this overwrites the current avatar + * whether it be the user's or the avatar url provided. + * + * @param emoji the emoji code + */ + setEmojiAvatar(emoji: string): ILivechatMessageBuilder; + + /** + * Gets the emoji used for the avatar. + */ + getEmojiAvatar(): string; + + /** + * Sets the url which to display for the avatar, this overwrites the current + * avatar whether it be the user's or an emoji one. + * + * @param avatarUrl image url to use as the avatar + */ + setAvatarUrl(avatarUrl: string): ILivechatMessageBuilder; + + /** + * Gets the url used for the avatar. + */ + getAvatarUrl(): string; + + /** + * Sets the display text of the sender's username that is visible. + * + * @param alias the username alias to display + */ + setUsernameAlias(alias: string): ILivechatMessageBuilder; + + /** + * Gets the display text of the sender's username that is visible. + */ + getUsernameAlias(): string; + + /** + * Adds one attachment to the message's list of attachments, this will not + * overwrite any existing ones but just adds. + * + * @param attachment the attachment to add + */ + addAttachment(attachment: IMessageAttachment): ILivechatMessageBuilder; + + /** + * Sets the attachments for the message, replacing and destroying all of the current attachments. + * + * @param attachments array of the attachments + */ + setAttachments(attachments: Array): ILivechatMessageBuilder; + + /** + * Gets the attachments array for the message + */ + getAttachments(): Array; + + /** + * Replaces an attachment at the given position (index). + * If there is no attachment at that position, there will be an error thrown. + * + * @param position the index of the attachment to replace + * @param attachment the attachment to replace with + */ + replaceAttachment(position: number, attachment: IMessageAttachment): ILivechatMessageBuilder; + + /** + * Removes an attachment at the given position (index). + * If there is no attachment at that position, there will be an error thrown. + * + * @param position the index of the attachment to remove + */ + removeAttachment(position: number): ILivechatMessageBuilder; + + /** + * Sets the user who is editing this message. + * This is required if you are modifying an existing message. + * + * @param user the editor + */ + setEditor(user: IUser): ILivechatMessageBuilder; + + /** + * Gets the user who edited the message + */ + getEditor(): IUser; + + /** + * Sets whether this message can group with others. + * This is desirable if you want to avoid confusion with other integrations. + * + * @param groupable whether this message can group with others + */ + setGroupable(groupable: boolean): ILivechatMessageBuilder; + + /** + * Gets whether this message can group with others. + */ + getGroupable(): boolean; + + /** + * Sets whether this message should have any URLs in the text + * parsed by Rocket.Chat and get the details added to the message's + * attachments. + * + * @param parseUrls whether URLs should be parsed in this message + */ + setParseUrls(parseUrls: boolean): ILivechatMessageBuilder; + + /** + * Gets whether this message should have its URLs parsed + */ + getParseUrls(): boolean; + + /** + * Set the token of the livechat visitor that + * sent the message + * + * @param token The Livechat visitor's token + */ + setToken(token: string): ILivechatMessageBuilder; + + /** + * Gets the token of the livechat visitor that + * sent the message + */ + getToken(): string; + + /** + * If the sender of the message is a Livechat Visitor, + * set the visitor who sent the message. + * + * If you set the visitor property of a message, the + * sender will be emptied + * + * @param visitor The visitor who sent the message + */ + setVisitor(visitor: IVisitor): ILivechatMessageBuilder; + + /** + * Get the visitor who sent the message, + * if any + */ + getVisitor(): IVisitor; + + /** + * Gets the resulting message that has been built up to the point of calling it. + * + * *Note:* This will error out if the Room has not been defined OR if the room + * is not of type RoomType.LIVE_CHAT. + */ + getMessage(): ILivechatMessage; + + /** + * Returns a message builder based on the + * livechat message of this builder + */ + getMessageBuilder(): IMessageBuilder; +} + /** * Interface for building out a room. * Please note, a room creator, name, and type must be set otherwise you will NOT diff --git a/src/definition/accessors/IRead.ts b/src/definition/accessors/IRead.ts index a08571f53..bea6850db 100644 --- a/src/definition/accessors/IRead.ts +++ b/src/definition/accessors/IRead.ts @@ -1,8 +1,10 @@ import { IEnvironmentRead } from './IEnvironmentRead'; +import { ILivechatRead } from './ILivechatRead'; import { IMessageRead } from './IMessageRead'; import { INotifier } from './IModify'; import { IPersistenceRead } from './IPersistenceRead'; import { IRoomRead } from './IRoomRead'; +import { IUploadRead } from './IUploadRead'; import { IUserRead } from './IUserRead'; /** @@ -28,4 +30,7 @@ export interface IRead { /** Gets the INotifier for notifying users/rooms. */ getNotifier(): INotifier; + + getLivechatReader(): ILivechatRead; + getUploadReader(): IUploadRead; } diff --git a/src/definition/accessors/IUploadRead.ts b/src/definition/accessors/IUploadRead.ts new file mode 100644 index 000000000..4d57b06ca --- /dev/null +++ b/src/definition/accessors/IUploadRead.ts @@ -0,0 +1,8 @@ + +import { IUpload } from '../uploads'; + +export interface IUploadRead { + getById(id: string): Promise; + getBufferById(id: string): Promise; + getBuffer(upload: IUpload): Promise; +} diff --git a/src/definition/accessors/index.ts b/src/definition/accessors/index.ts index d7907e15a..364931a1e 100644 --- a/src/definition/accessors/index.ts +++ b/src/definition/accessors/index.ts @@ -14,10 +14,13 @@ import { IHttpResponse, RequestMethod, } from './IHttp'; +import { ILivechatRead } from './ILivechatRead'; import { ILogEntry, LogMessageSeverity } from './ILogEntry'; import { ILogger } from './ILogger'; import { IMessageRead } from './IMessageRead'; import { + ILivechatMessageBuilder, + ILivechatUpdater, IMessageBuilder, IMessageExtender, IModify, @@ -38,6 +41,7 @@ import { ISettingRead } from './ISettingRead'; import { ISettingsExtend } from './ISettingsExtend'; import { ISlashCommandsExtend } from './ISlashCommandsExtend'; import { ISlashCommandsModify } from './ISlashCommandsModify'; +import { IUploadRead } from './IUploadRead'; import { IUserRead } from './IUserRead'; export { @@ -53,6 +57,9 @@ export { IHttpPreResponseHandler, IHttpRequest, IHttpResponse, + ILivechatMessageBuilder, + ILivechatRead, + ILivechatUpdater, ILogEntry, ILogger, IMessageBuilder, @@ -75,6 +82,7 @@ export { ISettingsExtend, ISlashCommandsExtend, ISlashCommandsModify, + IUploadRead, IUserRead, LogMessageSeverity, RequestMethod, diff --git a/src/definition/livechat/ILivechatMessage.ts b/src/definition/livechat/ILivechatMessage.ts new file mode 100644 index 000000000..f001feb21 --- /dev/null +++ b/src/definition/livechat/ILivechatMessage.ts @@ -0,0 +1,7 @@ +import { IMessage } from '../messages/IMessage'; +import { IVisitor } from './IVisitor'; + +export interface ILivechatMessage extends IMessage { + visitor?: IVisitor; + token?: string; +} diff --git a/src/definition/livechat/ILivechatRoom.ts b/src/definition/livechat/ILivechatRoom.ts new file mode 100644 index 000000000..e873fbb72 --- /dev/null +++ b/src/definition/livechat/ILivechatRoom.ts @@ -0,0 +1,12 @@ +import { IRoom } from '../rooms/IRoom'; +import { IUser } from '../users'; +import { IVisitor } from './IVisitor'; + +export interface ILivechatRoom extends IRoom { + visitor: IVisitor; + servedBy?: IUser; + responseBy?: IUser; + isWaitingResponse: boolean; + isOpen: boolean; + closedAt?: Date; +} diff --git a/src/definition/livechat/ILivechatTransferData.ts b/src/definition/livechat/ILivechatTransferData.ts new file mode 100644 index 000000000..98ebcb018 --- /dev/null +++ b/src/definition/livechat/ILivechatTransferData.ts @@ -0,0 +1,8 @@ +import { IUser } from '../users'; +import { ILivechatRoom } from './ILivechatRoom'; + +export interface ILivechatTransferData { + currentRoom: ILivechatRoom; + targetAgent?: IUser; + targetDepartment?: string; +} diff --git a/src/definition/livechat/IVisitor.ts b/src/definition/livechat/IVisitor.ts new file mode 100644 index 000000000..8fed79b72 --- /dev/null +++ b/src/definition/livechat/IVisitor.ts @@ -0,0 +1,14 @@ +import { IVisitorEmail } from './IVisitorEmail'; +import { IVisitorPhone } from './IVisitorPhone'; + +export interface IVisitor { + id?: string; + token: string; + username: string; + updatedAt: Date; + name: string; + department?: string; + phone?: Array; + visitorEmails: Array; + customFields?: { [key: string]: any }; +} diff --git a/src/definition/livechat/IVisitorEmail.ts b/src/definition/livechat/IVisitorEmail.ts new file mode 100644 index 000000000..2a9bfe27e --- /dev/null +++ b/src/definition/livechat/IVisitorEmail.ts @@ -0,0 +1,4 @@ + +export interface IVisitorEmail { + address: string; +} diff --git a/src/definition/livechat/IVisitorPhone.ts b/src/definition/livechat/IVisitorPhone.ts new file mode 100644 index 000000000..81b2491a7 --- /dev/null +++ b/src/definition/livechat/IVisitorPhone.ts @@ -0,0 +1,4 @@ + +export interface IVisitorPhone { + phoneNumber: string; +} diff --git a/src/definition/livechat/index.ts b/src/definition/livechat/index.ts new file mode 100644 index 000000000..632731ca5 --- /dev/null +++ b/src/definition/livechat/index.ts @@ -0,0 +1,15 @@ +import { ILivechatMessage } from './ILivechatMessage'; +import { ILivechatRoom } from './ILivechatRoom'; +import { ILivechatTransferData } from './ILivechatTransferData'; +import { IVisitor } from './IVisitor'; +import { IVisitorEmail } from './IVisitorEmail'; +import { IVisitorPhone } from './IVisitorPhone'; + +export { + ILivechatMessage, + ILivechatRoom, + ILivechatTransferData, + IVisitor, + IVisitorEmail, + IVisitorPhone, +}; diff --git a/src/definition/messages/IMessage.ts b/src/definition/messages/IMessage.ts index 8da2a1cd9..510e7dc4b 100644 --- a/src/definition/messages/IMessage.ts +++ b/src/definition/messages/IMessage.ts @@ -1,6 +1,7 @@ import { IRoom } from '../rooms'; import { IUser } from '../users'; import { IMessageAttachment } from './IMessageAttachment'; +import { IMessageFile } from './IMessageFile'; import { IMessageReactions } from './IMessageReaction'; export interface IMessage { @@ -15,6 +16,7 @@ export interface IMessage { emoji?: string; avatarUrl?: string; alias?: string; + file?: IMessageFile; attachments?: Array; reactions?: IMessageReactions; groupable?: boolean; diff --git a/src/definition/messages/IMessageFile.ts b/src/definition/messages/IMessageFile.ts new file mode 100644 index 000000000..10bdd8911 --- /dev/null +++ b/src/definition/messages/IMessageFile.ts @@ -0,0 +1,5 @@ +export interface IMessageFile { + _id: string; + name: string; + type: string; +} diff --git a/src/definition/messages/index.ts b/src/definition/messages/index.ts index 3bfd5990c..cfcf745b9 100644 --- a/src/definition/messages/index.ts +++ b/src/definition/messages/index.ts @@ -4,6 +4,7 @@ import { IMessageAttachment } from './IMessageAttachment'; import { IMessageAttachmentAuthor } from './IMessageAttachmentAuthor'; import { IMessageAttachmentField } from './IMessageAttachmentField'; import { IMessageAttachmentTitle } from './IMessageAttachmentTitle'; +import { IMessageFile } from './IMessageFile'; import { IMessageReaction, IMessageReactions } from './IMessageReaction'; import { IPostMessageDeleted } from './IPostMessageDeleted'; import { IPostMessageSent } from './IPostMessageSent'; @@ -26,6 +27,7 @@ export { IMessageAttachmentTitle, IMessageAttachmentField, IMessageAction, + IMessageFile, IMessageReactions, IMessageReaction, IPostMessageDeleted, diff --git a/src/definition/metadata/RocketChatAssociations.ts b/src/definition/metadata/RocketChatAssociations.ts index 78b8f39da..820a91a8a 100644 --- a/src/definition/metadata/RocketChatAssociations.ts +++ b/src/definition/metadata/RocketChatAssociations.ts @@ -1,6 +1,7 @@ export enum RocketChatAssociationModel { ROOM = 'room', MESSAGE = 'message', + LIVECHAT_MESSAGE = 'livechat-message', USER = 'user', FILE = 'file', MISC = 'misc', diff --git a/src/definition/uploads/IUpload.ts b/src/definition/uploads/IUpload.ts new file mode 100644 index 000000000..647191647 --- /dev/null +++ b/src/definition/uploads/IUpload.ts @@ -0,0 +1,25 @@ +import { IVisitor } from '../livechat'; +import { IRoom } from '../rooms'; +import { IUser } from '../users'; +import { StoreType } from './StoreType'; + +export interface IUpload { + id: string; + name: string; + size: string; + type: string; + extension: string; + etag: string; + path: string; + token: string; + url: string; + progress: number; + uploading: boolean; + complete: boolean; + updatedAt: Date; + uploadedAt: Date; + store: StoreType; + room: IRoom; + visitor?: IVisitor; + user?: IUser; +} diff --git a/src/definition/uploads/StoreType.ts b/src/definition/uploads/StoreType.ts new file mode 100644 index 000000000..7d417ec79 --- /dev/null +++ b/src/definition/uploads/StoreType.ts @@ -0,0 +1,8 @@ + +export enum StoreType { + GridFS = 'GridFS:Uploads', + AmazonS3 = 'AmazonS3', + GoogleCloudStorage = 'GoogleCloudStorage', + Webdav = 'Webdav', + FileSystem = 'FileSystem', +} diff --git a/src/definition/uploads/index.ts b/src/definition/uploads/index.ts new file mode 100644 index 000000000..3cdfc7217 --- /dev/null +++ b/src/definition/uploads/index.ts @@ -0,0 +1,7 @@ +import { IUpload } from './IUpload'; +import { StoreType } from './StoreType'; + +export { + IUpload, + StoreType, +}; diff --git a/src/server/accessors/LivechatCreator.ts b/src/server/accessors/LivechatCreator.ts new file mode 100644 index 000000000..e0cf461b6 --- /dev/null +++ b/src/server/accessors/LivechatCreator.ts @@ -0,0 +1,22 @@ +import { ILivechatCreator } from '../../definition/accessors/IModify'; + +import { ILivechatRoom } from '../../definition/livechat/ILivechatRoom'; +import { IVisitor } from '../../definition/livechat/IVisitor'; +import { IUser } from '../../definition/users'; +import { AppBridges } from '../bridges'; + +export class LivechatCreator implements ILivechatCreator { + constructor(private readonly bridges: AppBridges, private readonly appId: string) { } + + public createRoom(visitor: IVisitor, agent: IUser): Promise { + return this.bridges.getLivechatBridge().createRoom(visitor, agent, this.appId); + } + + public createVisitor(visitor: IVisitor): Promise { + return this.bridges.getLivechatBridge().createVisitor(visitor, this.appId); + } + + public createToken(): string { + return (Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)); + } +} diff --git a/src/server/accessors/LivechatMessageBuilder.ts b/src/server/accessors/LivechatMessageBuilder.ts new file mode 100644 index 000000000..bf0c7c0df --- /dev/null +++ b/src/server/accessors/LivechatMessageBuilder.ts @@ -0,0 +1,189 @@ +import { ILivechatMessageBuilder, IMessageBuilder } from '../../definition/accessors'; +import { ILivechatMessage } from '../../definition/livechat/ILivechatMessage'; +import { IVisitor } from '../../definition/livechat/IVisitor'; +import { IMessage, IMessageAttachment } from '../../definition/messages'; +import { RocketChatAssociationModel } from '../../definition/metadata'; +import { IRoom, RoomType } from '../../definition/rooms'; +import { IUser } from '../../definition/users'; +import { MessageBuilder } from './MessageBuilder'; + +export class LivechatMessageBuilder implements ILivechatMessageBuilder { + public kind: RocketChatAssociationModel.LIVECHAT_MESSAGE; + private msg: ILivechatMessage; + + constructor(message?: ILivechatMessage) { + this.kind = RocketChatAssociationModel.LIVECHAT_MESSAGE; + this.msg = message ? message : ({} as ILivechatMessage); + } + + public setData(data: ILivechatMessage): ILivechatMessageBuilder { + delete data.id; + this.msg = data; + + return this; + } + + public setRoom(room: IRoom): ILivechatMessageBuilder { + this.msg.room = room; + return this; + } + + public getRoom(): IRoom { + return this.msg.room; + } + + public setSender(sender: IUser): ILivechatMessageBuilder { + this.msg.sender = sender; + delete this.msg.visitor; + + return this; + } + + public getSender(): IUser { + return this.msg.sender; + } + + public setText(text: string): ILivechatMessageBuilder { + this.msg.text = text; + return this; + } + + public getText(): string { + return this.msg.text; + } + + public setEmojiAvatar(emoji: string): ILivechatMessageBuilder { + this.msg.emoji = emoji; + return this; + } + + public getEmojiAvatar(): string { + return this.msg.emoji; + } + + public setAvatarUrl(avatarUrl: string): ILivechatMessageBuilder { + this.msg.avatarUrl = avatarUrl; + return this; + } + + public getAvatarUrl(): string { + return this.msg.avatarUrl; + } + + public setUsernameAlias(alias: string): ILivechatMessageBuilder { + this.msg.alias = alias; + return this; + } + + public getUsernameAlias(): string { + return this.msg.alias; + } + + public addAttachment(attachment: IMessageAttachment): ILivechatMessageBuilder { + if (!this.msg.attachments) { + this.msg.attachments = new Array(); + } + + this.msg.attachments.push(attachment); + return this; + } + + public setAttachments(attachments: Array): ILivechatMessageBuilder { + this.msg.attachments = attachments; + return this; + } + + public getAttachments(): Array { + return this.msg.attachments; + } + + public replaceAttachment(position: number, attachment: IMessageAttachment): ILivechatMessageBuilder { + if (!this.msg.attachments) { + this.msg.attachments = new Array(); + } + + if (!this.msg.attachments[position]) { + throw new Error(`No attachment found at the index of "${ position }" to replace.`); + } + + this.msg.attachments[position] = attachment; + return this; + } + + public removeAttachment(position: number): ILivechatMessageBuilder { + if (!this.msg.attachments) { + this.msg.attachments = new Array(); + } + + if (!this.msg.attachments[position]) { + throw new Error(`No attachment found at the index of "${ position }" to remove.`); + } + + this.msg.attachments.splice(position, 1); + + return this; + } + + public setEditor(user: IUser): ILivechatMessageBuilder { + this.msg.editor = user; + return this; + } + + public getEditor(): IUser { + return this.msg.editor; + } + + public setGroupable(groupable: boolean): ILivechatMessageBuilder { + this.msg.groupable = groupable; + return this; + } + + public getGroupable(): boolean { + return this.msg.groupable; + } + + public setParseUrls(parseUrls: boolean): ILivechatMessageBuilder { + this.msg.parseUrls = parseUrls; + return this; + } + + public getParseUrls(): boolean { + return this.msg.parseUrls; + } + + public setToken(token: string): ILivechatMessageBuilder { + this.msg.token = token; + return this; + } + + public getToken(): string { + return this.msg.token; + } + + public setVisitor(visitor: IVisitor): ILivechatMessageBuilder { + this.msg.visitor = visitor; + delete this.msg.sender; + + return this; + } + + public getVisitor(): IVisitor { + return this.msg.visitor; + } + + public getMessage(): ILivechatMessage { + if (!this.msg.room) { + throw new Error('The "room" property is required.'); + } + + if (this.msg.room.type !== RoomType.LIVE_CHAT) { + throw new Error('The room is not a Livechat room'); + } + + return this.msg; + } + + public getMessageBuilder(): IMessageBuilder { + return new MessageBuilder(this.msg as IMessage); + } +} diff --git a/src/server/accessors/LivechatRead.ts b/src/server/accessors/LivechatRead.ts new file mode 100644 index 000000000..a51f708b1 --- /dev/null +++ b/src/server/accessors/LivechatRead.ts @@ -0,0 +1,15 @@ +import { ILivechatRead } from '../../definition/accessors/ILivechatRead'; +import { ILivechatRoom } from '../../definition/livechat/ILivechatRoom'; +import { IVisitor } from '../../definition/livechat/IVisitor'; +import { ILivechatBridge } from '../bridges/ILivechatBridge'; +export class LivechatRead implements ILivechatRead { + constructor(private readonly livechatBridge: ILivechatBridge, private readonly appId: string) { } + + public getLivechatRooms(visitor: IVisitor, departmentId?: string): Promise> { + return this.livechatBridge.findRooms(visitor, departmentId, this.appId); + } + + public getLivechatVisitors(query: object): Promise> { + return this.livechatBridge.findVisitors(query, this.appId); + } +} diff --git a/src/server/accessors/LivechatUpdater.ts b/src/server/accessors/LivechatUpdater.ts new file mode 100644 index 000000000..ca962be50 --- /dev/null +++ b/src/server/accessors/LivechatUpdater.ts @@ -0,0 +1,15 @@ +import { ILivechatUpdater } from '../../definition/accessors'; +import { ILivechatRoom, ILivechatTransferData, IVisitor } from '../../definition/livechat'; +import { AppBridges } from '../bridges'; + +export class LivechatUpdater implements ILivechatUpdater { + constructor(private readonly bridges: AppBridges, private readonly appId: string) { } + + public transferVisitor(visitor: IVisitor, transferData: ILivechatTransferData): Promise { + return this.bridges.getLivechatBridge().transferVisitor(visitor, transferData, this.appId); + } + + public closeRoom(room: ILivechatRoom, comment: string): Promise { + return this.bridges.getLivechatBridge().closeRoom(room, comment, this.appId); + } +} diff --git a/src/server/accessors/ModifyCreator.ts b/src/server/accessors/ModifyCreator.ts index 29851196e..85e44a913 100644 --- a/src/server/accessors/ModifyCreator.ts +++ b/src/server/accessors/ModifyCreator.ts @@ -1,14 +1,26 @@ -import { IMessageBuilder, IModifyCreator, IRoomBuilder } from '../../definition/accessors'; +import { ILivechatMessageBuilder, IMessageBuilder, IModifyCreator, IRoomBuilder } from '../../definition/accessors'; import { IMessage } from '../../definition/messages'; import { RocketChatAssociationModel } from '../../definition/metadata'; import { IRoom, RoomType } from '../../definition/rooms'; +import { ILivechatCreator } from '../../definition/accessors/IModify'; +import { ILivechatMessage } from '../../definition/livechat/ILivechatMessage'; import { AppBridges } from '../bridges'; +import { LivechatCreator } from './LivechatCreator'; +import { LivechatMessageBuilder } from './LivechatMessageBuilder'; import { MessageBuilder } from './MessageBuilder'; import { RoomBuilder } from './RoomBuilder'; export class ModifyCreator implements IModifyCreator { - constructor(private readonly bridges: AppBridges, private readonly appId: string) { } + private livechatCreator: LivechatCreator; + + constructor(private readonly bridges: AppBridges, private readonly appId: string) { + this.livechatCreator = new LivechatCreator(bridges, appId); + } + + public getLivechatCreator(): ILivechatCreator { + return this.livechatCreator; + } public startMessage(data?: IMessage): IMessageBuilder { if (data) { @@ -18,6 +30,14 @@ export class ModifyCreator implements IModifyCreator { return new MessageBuilder(data); } + public startLivechatMessage(data?: ILivechatMessage): ILivechatMessageBuilder { + if (data) { + delete data.id; + } + + return new LivechatMessageBuilder(data); + } + public startRoom(data?: IRoom): IRoomBuilder { if (data) { delete data.id; @@ -26,10 +46,12 @@ export class ModifyCreator implements IModifyCreator { return new RoomBuilder(data); } - public finish(builder: IMessageBuilder | IRoomBuilder): Promise { + public finish(builder: IMessageBuilder | ILivechatMessageBuilder | IRoomBuilder): Promise { switch (builder.kind) { case RocketChatAssociationModel.MESSAGE: return this._finishMessage(builder); + case RocketChatAssociationModel.LIVECHAT_MESSAGE: + return this._finishLivechatMessage(builder); case RocketChatAssociationModel.ROOM: return this._finishRoom(builder); default: @@ -48,6 +70,23 @@ export class ModifyCreator implements IModifyCreator { return this.bridges.getMessageBridge().create(result, this.appId); } + private _finishLivechatMessage(builder: ILivechatMessageBuilder): Promise { + if (builder.getSender() && !builder.getVisitor()) { + return this._finishMessage(builder.getMessageBuilder()); + } + + const result = builder.getMessage(); + delete result.id; + + if (!result.token && (!result.visitor || !result.visitor.token)) { + throw new Error('Invalid visitor sending the message'); + } + + result.token = result.visitor ? result.visitor.token : result.token; + + return this.bridges.getLivechatBridge().createMessage(result, this.appId); + } + private _finishRoom(builder: IRoomBuilder): Promise { const result = builder.getRoom(); delete result.id; diff --git a/src/server/accessors/ModifyUpdater.ts b/src/server/accessors/ModifyUpdater.ts index 50f86155c..27d7a877d 100644 --- a/src/server/accessors/ModifyUpdater.ts +++ b/src/server/accessors/ModifyUpdater.ts @@ -1,13 +1,22 @@ -import { IMessageBuilder, IModifyUpdater, IRoomBuilder } from '../../definition/accessors'; +import { ILivechatUpdater, IMessageBuilder, IModifyUpdater, IRoomBuilder } from '../../definition/accessors'; import { RocketChatAssociationModel } from '../../definition/metadata'; import { IUser } from '../../definition/users'; import { AppBridges } from '../bridges'; +import { LivechatUpdater } from './LivechatUpdater'; import { MessageBuilder } from './MessageBuilder'; import { RoomBuilder } from './RoomBuilder'; export class ModifyUpdater implements IModifyUpdater { - constructor(private readonly bridges: AppBridges, private readonly appId: string) { } + private livechatUpdater: ILivechatUpdater; + + constructor(private readonly bridges: AppBridges, private readonly appId: string) { + this.livechatUpdater = new LivechatUpdater(this.bridges, this.appId); + } + + public getLivechatUpdater(): ILivechatUpdater { + return this.livechatUpdater; + } public async message(messageId: string, updater: IUser): Promise { const msg = await this.bridges.getMessageBridge().getById(messageId, this.appId); diff --git a/src/server/accessors/Reader.ts b/src/server/accessors/Reader.ts index 774087e3f..ad490b061 100644 --- a/src/server/accessors/Reader.ts +++ b/src/server/accessors/Reader.ts @@ -1,17 +1,26 @@ import { IEnvironmentRead, + ILivechatRead, IMessageRead, INotifier, IPersistenceRead, IRead, IRoomRead, + IUploadRead, IUserRead, } from '../../definition/accessors'; export class Reader implements IRead { - constructor(private env: IEnvironmentRead, private message: IMessageRead, - private persist: IPersistenceRead, private room: IRoomRead, - private user: IUserRead, private noti: INotifier) { } + constructor( + private env: IEnvironmentRead, + private message: IMessageRead, + private persist: IPersistenceRead, + private room: IRoomRead, + private user: IUserRead, + private noti: INotifier, + private livechat: ILivechatRead, + private upload: IUploadRead, + ) { } public getEnvironmentReader(): IEnvironmentRead { return this.env; @@ -36,4 +45,12 @@ export class Reader implements IRead { public getNotifier(): INotifier { return this.noti; } + + public getLivechatReader(): ILivechatRead { + return this.livechat; + } + + public getUploadReader(): IUploadRead { + return this.upload; + } } diff --git a/src/server/accessors/UploadRead.ts b/src/server/accessors/UploadRead.ts new file mode 100644 index 000000000..7018a5493 --- /dev/null +++ b/src/server/accessors/UploadRead.ts @@ -0,0 +1,22 @@ + +import { IUploadRead } from '../../definition/accessors'; +import { IUpload } from '../../definition/uploads'; +import { IUploadBridge } from '../bridges/IUploadBridge'; + +export class UploadRead implements IUploadRead { + constructor(private readonly uploadBridge: IUploadBridge, private readonly appId: string) { } + + public getById(id: string): Promise { + return this.uploadBridge.getById(id, this.appId); + } + + public getBuffer(upload: IUpload): Promise { + return this.uploadBridge.getBuffer(upload, this.appId); + } + + public async getBufferById(id: string): Promise { + const upload = await this.uploadBridge.getById(id, this.appId); + + return this.uploadBridge.getBuffer(upload, this.appId); + } +} diff --git a/src/server/accessors/index.ts b/src/server/accessors/index.ts index 7d3bacfa0..7a1490650 100644 --- a/src/server/accessors/index.ts +++ b/src/server/accessors/index.ts @@ -5,6 +5,7 @@ import { EnvironmentalVariableRead } from './EnvironmentalVariableRead'; import { EnvironmentRead } from './EnvironmentRead'; import { Http } from './Http'; import { HttpExtend } from './HttpExtend'; +import { LivechatRead } from './LivechatRead'; import { MessageBuilder } from './MessageBuilder'; import { MessageExtender } from './MessageExtender'; import { MessageRead } from './MessageRead'; @@ -25,6 +26,7 @@ import { SettingRead } from './SettingRead'; import { SettingsExtend } from './SettingsExtend'; import { SlashCommandsExtend } from './SlashCommandsExtend'; import { SlashCommandsModify } from './SlashCommandsModify'; +import { UploadRead } from './UploadRead'; import { UserRead } from './UserRead'; export { @@ -35,6 +37,7 @@ export { EnvironmentRead, Http, HttpExtend, + LivechatRead, MessageBuilder, MessageExtender, MessageRead, @@ -55,5 +58,6 @@ export { SettingsExtend, SlashCommandsExtend, SlashCommandsModify, + UploadRead, UserRead, }; diff --git a/src/server/bridges/AppBridges.ts b/src/server/bridges/AppBridges.ts index 65edaddf2..3729c8dfc 100644 --- a/src/server/bridges/AppBridges.ts +++ b/src/server/bridges/AppBridges.ts @@ -6,10 +6,12 @@ import { IEnvironmentalVariableBridge } from './IEnvironmentalVariableBridge'; import { IHttpBridge } from './IHttpBridge'; import { IInternalBridge } from './IInternalBridge'; import { IListenerBridge } from './IListenerBridge'; +import { ILivechatBridge } from './ILivechatBridge'; import { IMessageBridge } from './IMessageBridge'; import { IPersistenceBridge } from './IPersistenceBridge'; import { IRoomBridge } from './IRoomBridge'; import { IServerSettingBridge } from './IServerSettingBridge'; +import { IUploadBridge } from './IUploadBridge'; import { IUserBridge } from './IUserBridge'; export abstract class AppBridges { @@ -19,11 +21,13 @@ export abstract class AppBridges { public abstract getEnvironmentalVariableBridge(): IEnvironmentalVariableBridge; public abstract getHttpBridge(): IHttpBridge; public abstract getListenerBridge(): IListenerBridge; + public abstract getLivechatBridge(): ILivechatBridge; public abstract getMessageBridge(): IMessageBridge; public abstract getPersistenceBridge(): IPersistenceBridge; public abstract getAppActivationBridge(): IAppActivationBridge; public abstract getRoomBridge(): IRoomBridge; public abstract getInternalBridge(): IInternalBridge; public abstract getServerSettingBridge(): IServerSettingBridge; + public abstract getUploadBridge(): IUploadBridge; public abstract getUserBridge(): IUserBridge; } diff --git a/src/server/bridges/ILivechatBridge.ts b/src/server/bridges/ILivechatBridge.ts new file mode 100644 index 000000000..c00360ab3 --- /dev/null +++ b/src/server/bridges/ILivechatBridge.ts @@ -0,0 +1,14 @@ +import { ILivechatMessage, ILivechatRoom, ILivechatTransferData, IVisitor } from '../../definition/livechat'; +import { IUser } from '../../definition/users'; + +export interface ILivechatBridge { + createMessage(message: ILivechatMessage, appId: string): Promise; + getMessageById(messageId: string, appId: string): Promise; + updateMessage(message: ILivechatMessage, appId: string): Promise; + createVisitor(visitor: IVisitor, appId: string): Promise; + findVisitors(query: object, appId: string): Promise>; + transferVisitor(visitor: IVisitor, transferData: ILivechatTransferData, appId: string): Promise; + createRoom(visitor: IVisitor, agent: IUser, appId: string): Promise; + closeRoom(room: ILivechatRoom, comment: string, appId: string): Promise; + findRooms(visitor: IVisitor, departmentId: string | null, appId: string): Promise>; +} diff --git a/src/server/bridges/IUploadBridge.ts b/src/server/bridges/IUploadBridge.ts new file mode 100644 index 000000000..6dd4a56eb --- /dev/null +++ b/src/server/bridges/IUploadBridge.ts @@ -0,0 +1,6 @@ +import { IUpload } from '../../definition/uploads'; + +export interface IUploadBridge { + getById(id: string, appId: string): Promise; + getBuffer(upload: IUpload, appId: string): Promise; +} diff --git a/src/server/bridges/index.ts b/src/server/bridges/index.ts index 41236ba81..81a871d31 100644 --- a/src/server/bridges/index.ts +++ b/src/server/bridges/index.ts @@ -7,10 +7,12 @@ import { IEnvironmentalVariableBridge } from './IEnvironmentalVariableBridge'; import { IHttpBridge, IHttpBridgeRequestInfo } from './IHttpBridge'; import { IInternalBridge } from './IInternalBridge'; import { IListenerBridge } from './IListenerBridge'; +import { ILivechatBridge } from './ILivechatBridge'; import { IMessageBridge } from './IMessageBridge'; import { IPersistenceBridge } from './IPersistenceBridge'; import { IRoomBridge } from './IRoomBridge'; import { IServerSettingBridge } from './IServerSettingBridge'; +import { IUploadBridge } from './IUploadBridge'; import { IUserBridge } from './IUserBridge'; export { @@ -18,6 +20,7 @@ export { IHttpBridge, IHttpBridgeRequestInfo, IListenerBridge, + ILivechatBridge, IMessageBridge, IPersistenceBridge, IAppActivationBridge, @@ -28,5 +31,6 @@ export { IInternalBridge, IServerSettingBridge, IUserBridge, + IUploadBridge, AppBridges, }; diff --git a/src/server/compiler/AppCompiler.ts b/src/server/compiler/AppCompiler.ts index 53c69781d..6ec7154f9 100644 --- a/src/server/compiler/AppCompiler.ts +++ b/src/server/compiler/AppCompiler.ts @@ -33,6 +33,7 @@ export class AppCompiler { noImplicitReturns: true, emitDecoratorMetadata: true, experimentalDecorators: true, + types: ['node'], // Set this to true if you would like to see the module resolution process traceResolution: false, }; diff --git a/src/server/managers/AppAccessorManager.ts b/src/server/managers/AppAccessorManager.ts index 8a716bcfc..7c4de6c6f 100644 --- a/src/server/managers/AppAccessorManager.ts +++ b/src/server/managers/AppAccessorManager.ts @@ -4,6 +4,7 @@ import { EnvironmentRead, Http, HttpExtend, + LivechatRead, MessageRead, Modify, Notifier, @@ -16,6 +17,7 @@ import { SettingsExtend, SlashCommandsExtend, SlashCommandsModify, + UploadRead, UserRead, } from '../accessors'; import { ApiExtend } from '../accessors/ApiExtend'; @@ -127,8 +129,10 @@ export class AppAccessorManager { const room = new RoomRead(this.bridges.getRoomBridge(), appId); const user = new UserRead(this.bridges.getUserBridge(), appId); const noti = new Notifier(this.bridges.getMessageBridge(), appId); + const livechat = new LivechatRead(this.bridges.getLivechatBridge(), appId); + const upload = new UploadRead(this.bridges.getUploadBridge(), appId); - this.readers.set(appId, new Reader(env, msg, persist, room, user, noti)); + this.readers.set(appId, new Reader(env, msg, persist, room, user, noti, livechat, upload)); } return this.readers.get(appId); diff --git a/tests/server/accessors/Reader.spec.ts b/tests/server/accessors/Reader.spec.ts index 5f21ca9be..d51db7656 100644 --- a/tests/server/accessors/Reader.spec.ts +++ b/tests/server/accessors/Reader.spec.ts @@ -1,5 +1,5 @@ import { Expect, SetupFixture, Test } from 'alsatian'; -import { IEnvironmentRead, IMessageRead, INotifier, IPersistenceRead, IRoomRead, IUserRead } from '../../../src/definition/accessors'; +import { IEnvironmentRead, ILivechatRead, IMessageRead, INotifier, IPersistenceRead, IRoomRead, IUploadRead, IUserRead } from '../../../src/definition/accessors'; import { Reader } from '../../../src/server/accessors'; @@ -10,6 +10,8 @@ export class ReaderAccessorTestFixture { private rm: IRoomRead; private ur: IUserRead; private ni: INotifier; + private livechat: ILivechatRead; + private upload: IUploadRead; @SetupFixture public setupFixture() { @@ -19,18 +21,22 @@ export class ReaderAccessorTestFixture { this.rm = {} as IRoomRead; this.ur = {} as IUserRead; this.ni = {} as INotifier; + this.livechat = {} as ILivechatRead; + this.upload = {} as IUploadRead; } @Test() public useReader() { - Expect(() => new Reader(this.env, this.msg, this.pr, this.rm, this.ur, this.ni)).not.toThrow(); + Expect(() => new Reader(this.env, this.msg, this.pr, this.rm, this.ur, this.ni, this.livechat, this.upload)).not.toThrow(); - const rd = new Reader(this.env, this.msg, this.pr, this.rm, this.ur, this.ni); + const rd = new Reader(this.env, this.msg, this.pr, this.rm, this.ur, this.ni, this.livechat, this.upload); Expect(rd.getEnvironmentReader()).toBeDefined(); Expect(rd.getMessageReader()).toBeDefined(); Expect(rd.getNotifier()).toBeDefined(); Expect(rd.getPersistenceReader()).toBeDefined(); Expect(rd.getRoomReader()).toBeDefined(); Expect(rd.getUserReader()).toBeDefined(); + Expect(rd.getLivechatReader()).toBeDefined(); + Expect(rd.getUploadReader()).toBeDefined(); } } diff --git a/tests/server/compiler/AppCompiler.spec.ts b/tests/server/compiler/AppCompiler.spec.ts index 58cc8aa23..7a5eb31e3 100644 --- a/tests/server/compiler/AppCompiler.spec.ts +++ b/tests/server/compiler/AppCompiler.spec.ts @@ -14,6 +14,7 @@ export class AppCompilerTestFixture { target: ts.ScriptTarget.ES2017, module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.NodeJs, + types: ['node'], declaration: false, noImplicitAny: false, removeComments: true, diff --git a/tests/test-data/bridges/appBridges.ts b/tests/test-data/bridges/appBridges.ts index 76611a324..e7d7d2893 100644 --- a/tests/test-data/bridges/appBridges.ts +++ b/tests/test-data/bridges/appBridges.ts @@ -6,10 +6,12 @@ import { IHttpBridge, IInternalBridge, IListenerBridge, + ILivechatBridge, IMessageBridge, IPersistenceBridge, IRoomBridge, IServerSettingBridge, + IUploadBridge, IUserBridge, } from '../../../src/server/bridges'; import { TestsActivationBridge } from './activationBridge'; @@ -19,10 +21,12 @@ import { TestsCommandBridge } from './commandBridge'; import { TestsEnvironmentalVariableBridge } from './environmentalVariableBridge'; import { TestsHttpBridge } from './httpBridge'; import { TestsInternalBridge } from './internalBridge'; +import { TestLivechatBridge } from './livechatBridge'; import { TestsMessageBridge } from './messageBridge'; import { TestsPersisBridge } from './persisBridge'; import { TestsRoomBridge } from './roomBridge'; import { TestsServerSettingBridge } from './serverSettingBridge'; +import { TestUploadBridge } from './uploadBridge'; import { TestsUserBridge } from './userBridge'; export class TestsAppBridges extends AppBridges { @@ -38,6 +42,8 @@ export class TestsAppBridges extends AppBridges { private readonly internalBridge: TestsInternalBridge; private readonly userBridge: TestsUserBridge; private readonly httpBridge: TestsHttpBridge; + private readonly livechatBridge: TestLivechatBridge; + private readonly uploadBridge: TestUploadBridge; constructor() { super(); @@ -53,6 +59,8 @@ export class TestsAppBridges extends AppBridges { this.internalBridge = new TestsInternalBridge(); this.userBridge = new TestsUserBridge(); this.httpBridge = new TestsHttpBridge(); + this.livechatBridge = new TestLivechatBridge(); + this.uploadBridge = new TestUploadBridge(); } public getCommandBridge(): TestsCommandBridge { @@ -106,4 +114,12 @@ export class TestsAppBridges extends AppBridges { public getUserBridge(): IUserBridge { return this.userBridge; } + + public getLivechatBridge(): ILivechatBridge { + return this.livechatBridge; + } + + public getUploadBridge(): IUploadBridge { + return this.uploadBridge; + } } diff --git a/tests/test-data/bridges/livechatBridge.ts b/tests/test-data/bridges/livechatBridge.ts new file mode 100644 index 000000000..19c80c6ab --- /dev/null +++ b/tests/test-data/bridges/livechatBridge.ts @@ -0,0 +1,33 @@ +import { ILivechatMessage, ILivechatRoom, ILivechatTransferData, IVisitor } from '../../../src/definition/livechat'; +import { IUser } from '../../../src/definition/users'; +import { ILivechatBridge } from '../../../src/server/bridges/ILivechatBridge'; + +export class TestLivechatBridge implements ILivechatBridge { + public createMessage(message: ILivechatMessage, appId: string): Promise { + throw new Error('Method not implemented'); + } + public getMessageById(messageId: string, appId: string): Promise { + throw new Error('Method not implemented'); + } + public updateMessage(message: ILivechatMessage, appId: string): Promise { + throw new Error('Method not implemented'); + } + public createVisitor(visitor: IVisitor, appId: string): Promise { + throw new Error('Method not implemented'); + } + public transferVisitor(visitor: IVisitor, transferData: ILivechatTransferData, appId: string): Promise { + throw new Error('Method not implemented'); + } + public findVisitors(query: object, appId: string): Promise> { + throw new Error('Method not implemented'); + } + public createRoom(visitor: IVisitor, agent: IUser, appId: string): Promise { + throw new Error('Method not implemented'); + } + public closeRoom(room: ILivechatRoom, comment: string, appId: string): Promise { + throw new Error('Method not implemented'); + } + public findRooms(visitor: IVisitor, departmentId: string | null, appId: string): Promise> { + throw new Error('Method not implemented'); + } +} diff --git a/tests/test-data/bridges/uploadBridge.ts b/tests/test-data/bridges/uploadBridge.ts new file mode 100644 index 000000000..db7f494b4 --- /dev/null +++ b/tests/test-data/bridges/uploadBridge.ts @@ -0,0 +1,10 @@ +import { IUpload } from '../../../src/definition/uploads'; +import { IUploadBridge } from '../../../src/server/bridges/IUploadBridge'; +export class TestUploadBridge implements IUploadBridge { + public getById(id: string, appId: string): Promise { + throw new Error('Method not implemented'); + } + public getBuffer(upload: IUpload, appId: string): Promise { + throw new Error('Method not implemented'); + } +} diff --git a/tsconfig.json b/tsconfig.json index b151fc9c9..b2266c9b5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "moduleResolution": "node", + "types": ["node"], "lib": ["es2017", "dom"], "outDir": "." },