diff --git a/app/lib/client/methods/sendMessage.js b/app/lib/client/methods/sendMessage.js index 4842fd36aac0..3e30b754b690 100644 --- a/app/lib/client/methods/sendMessage.js +++ b/app/lib/client/methods/sendMessage.js @@ -5,9 +5,9 @@ import s from 'underscore.string'; import { ChatMessage } from '../../../models'; import { settings } from '../../../settings'; import { callbacks } from '../../../callbacks'; -import { promises } from '../../../promises/client'; import { t } from '../../../utils/client'; import { dispatchToastMessage } from '../../../../client/lib/toast'; +import { onClientMessageReceived } from '../../../../client/lib/onClientMessageReceived'; Meteor.methods({ sendMessage(message) { @@ -32,7 +32,7 @@ Meteor.methods({ message.unread = true; } message = callbacks.run('beforeSaveMessage', message); - promises.run('onClientMessageReceived', message).then(function(message) { + onClientMessageReceived(message).then(function(message) { ChatMessage.insert(message); return callbacks.run('afterSaveMessage', message); }); diff --git a/app/otr/client/rocketchat.otr.js b/app/otr/client/rocketchat.otr.js index 76d48a1bf4e6..41a54ce1b138 100644 --- a/app/otr/client/rocketchat.otr.js +++ b/app/otr/client/rocketchat.otr.js @@ -3,9 +3,10 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { Subscriptions } from '../../models'; -import { promises } from '../../promises/client'; import { Notifications } from '../../notifications'; import { t } from '../../utils'; +import { onClientMessageReceived } from '../../../client/lib/onClientMessageReceived'; +import { onClientBeforeSendMessage } from '../../../client/lib/onClientBeforeSendMessage'; class OTRClass { constructor() { @@ -54,7 +55,7 @@ Meteor.startup(function() { } }); - promises.add('onClientBeforeSendMessage', function(message) { + onClientBeforeSendMessage.use(function(message) { if (message.rid && OTR.getInstanceByRoomId(message.rid) && OTR.getInstanceByRoomId(message.rid).established.get()) { return OTR.getInstanceByRoomId(message.rid).encrypt(message) .then((msg) => { @@ -64,9 +65,9 @@ Meteor.startup(function() { }); } return Promise.resolve(message); - }, promises.priority.HIGH); + }); - promises.add('onClientMessageReceived', function(message) { + onClientMessageReceived.use(function(message) { if (message.rid && OTR.getInstanceByRoomId(message.rid) && OTR.getInstanceByRoomId(message.rid).established.get()) { if (message.notification) { message.msg = t('Encrypted_message'); @@ -105,5 +106,5 @@ Meteor.startup(function() { message.msg = ''; } return Promise.resolve(message); - }, promises.priority.HIGH); + }); }); diff --git a/app/promises/client/index.js b/app/promises/client/index.js deleted file mode 100644 index 999b1f62bc34..000000000000 --- a/app/promises/client/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { promises } from '../lib/promises'; - -export { - promises, -}; diff --git a/app/promises/lib/promises.js b/app/promises/lib/promises.js deleted file mode 100644 index 62ad6b51af37..000000000000 --- a/app/promises/lib/promises.js +++ /dev/null @@ -1,84 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { Random } from 'meteor/random'; -import _ from 'underscore'; - -/* -* Callback hooks provide an easy way to add extra steps to common operations. -* @namespace RocketChat.promises -*/ - -export const promises = {}; - - -/* -* Callback priorities -*/ - -promises.priority = { - HIGH: -1000, - MEDIUM: 0, - LOW: 1000, -}; - -const getHook = (hookName) => promises[hookName] || []; - -/* -* Add a callback function to a hook -* @param {String} hook - The name of the hook -* @param {Function} callback - The callback function -*/ - -promises.add = function(hook, callback, p = promises.priority.MEDIUM, id) { - callback.priority = _.isNumber(p) ? p : promises.priority.MEDIUM; - callback.id = id || Random.id(); - promises[hook] = getHook(hook); - if (promises[hook].find((cb) => cb.id === callback.id)) { - return; - } - promises[hook].push(callback); - promises[hook] = _.sortBy(promises[hook], (callback) => callback.priority || promises.priority.MEDIUM); -}; - - -/* -* Remove a callback from a hook -* @param {string} hook - The name of the hook -* @param {string} id - The callback's id -*/ - -promises.remove = function(hook, id) { - promises[hook] = getHook(hook).filter((callback) => callback.id !== id); -}; - - -/* -* Successively run all of a hook's callbacks on an item -* @param {String} hook - The name of the hook -* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -* @param {Object} [constant] - An optional constant that will be passed along to each callback -* @returns {Object} Returns the item after it's been through all the callbacks for this hook -*/ - -promises.run = function(hook, item, constant) { - const callbacks = promises[hook]; - if (callbacks == null || callbacks.length === 0) { - return Promise.resolve(item); - } - return callbacks.reduce((previousPromise, callback) => previousPromise.then((result) => callback(result, constant)), Promise.resolve(item)); -}; - - -/* -* Successively run all of a hook's callbacks on an item, in async mode (only works on server) -* @param {String} hook - The name of the hook -* @param {Object} item - The post, comment, modifier, etc. on which to run the callbacks -* @param {Object} [constant] - An optional constant that will be passed along to each callback -*/ - -promises.runAsync = function(hook, item, constant) { - const callbacks = promises[hook]; - if (!Meteor.isServer || callbacks == null || callbacks.length === 0) { - return item; - } - Meteor.defer(() => callbacks.forEach((callback) => callback(item, constant))); -}; diff --git a/app/promises/server/index.js b/app/promises/server/index.js deleted file mode 100644 index 999b1f62bc34..000000000000 --- a/app/promises/server/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { promises } from '../lib/promises'; - -export { - promises, -}; diff --git a/app/ui-utils/client/lib/RoomHistoryManager.js b/app/ui-utils/client/lib/RoomHistoryManager.js index 7a2e5e9af8fc..1cb95235231b 100644 --- a/app/ui-utils/client/lib/RoomHistoryManager.js +++ b/app/ui-utils/client/lib/RoomHistoryManager.js @@ -7,7 +7,6 @@ import differenceInMilliseconds from 'date-fns/differenceInMilliseconds'; import { Emitter } from '@rocket.chat/emitter'; import { escapeHTML } from '@rocket.chat/string-helpers'; -import { promises } from '../../../promises/client'; import { RoomManager } from './RoomManager'; import { readMessage } from './readMessages'; import { renderMessageBody } from '../../../../client/lib/utils/renderMessageBody'; @@ -16,6 +15,7 @@ import { ChatMessage, ChatSubscription, ChatRoom } from '../../../models'; import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling'; import { filterMarkdown } from '../../../markdown/lib/markdown'; import { getUserPreference } from '../../../utils/client'; +import { onClientMessageReceived } from '../../../../client/lib/onClientMessageReceived'; export const normalizeThreadMessage = ({ ...message }) => { if (message.msg) { @@ -69,7 +69,7 @@ export const upsertMessage = async ({ msg, subscription, uid = Tracker.nonreacti if (msg.t === 'e2e' && !msg.file) { msg.e2e = 'pending'; } - msg = await promises.run('onClientMessageReceived', msg) || msg; + msg = await onClientMessageReceived(msg) || msg; const { _id, ...messageToUpsert } = msg; diff --git a/app/ui/client/lib/chatMessages.js b/app/ui/client/lib/chatMessages.js index 97215d8bc970..dd9b66961956 100644 --- a/app/ui/client/lib/chatMessages.js +++ b/app/ui/client/lib/chatMessages.js @@ -18,7 +18,6 @@ import { } from '../../../ui-utils/client'; import { settings } from '../../../settings/client'; import { callbacks } from '../../../callbacks/client'; -import { promises } from '../../../promises/client'; import { hasAtLeastOnePermission } from '../../../authorization/client'; import { Messages, Rooms, ChatMessage, ChatSubscription } from '../../../models/client'; import { emoji } from '../../../emoji/client'; @@ -30,6 +29,7 @@ import { prependReplies } from '../../../../client/lib/utils/prependReplies'; import { callWithErrorHandling } from '../../../../client/lib/utils/callWithErrorHandling'; import { handleError } from '../../../../client/lib/utils/handleError'; import { dispatchToastMessage } from '../../../../client/lib/toast'; +import { onClientBeforeSendMessage } from '../../../../client/lib/onClientBeforeSendMessage'; const messageBoxState = { @@ -284,7 +284,7 @@ export class ChatMessages { readMessage.readNow(rid); readMessage.refreshUnreadMark(rid); - const message = await promises.run('onClientBeforeSendMessage', { + const message = await onClientBeforeSendMessage({ _id: Random.id(), rid, tshow, diff --git a/app/ui/client/lib/notification.js b/app/ui/client/lib/notification.js index c656851eb08a..29606114b012 100644 --- a/app/ui/client/lib/notification.js +++ b/app/ui/client/lib/notification.js @@ -12,9 +12,9 @@ import { e2e } from '../../../e2e/client'; import { Users, ChatSubscription } from '../../../models'; import { getUserPreference } from '../../../utils'; import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL'; -import { promises } from '../../../promises/client'; import { CustomSounds } from '../../../custom-sounds/client/lib/CustomSounds'; import { getAvatarAsPng } from '../../../../client/lib/utils/getAvatarAsPng'; +import { onClientMessageReceived } from '../../../../client/lib/onClientMessageReceived'; export const KonchatNotification = { notificationStatus: new ReactiveVar(), @@ -34,7 +34,7 @@ export const KonchatNotification = { notify(notification) { if (window.Notification && Notification.permission === 'granted') { const message = { rid: notification.payload != null ? notification.payload.rid : undefined, msg: notification.text, notification: true }; - return promises.run('onClientMessageReceived', message).then(function(message) { + return onClientMessageReceived(message).then(function(message) { const requireInteraction = getUserPreference(Meteor.userId(), 'desktopNotificationRequireInteraction'); const n = new Notification(notification.title, { icon: notification.icon || getUserAvatarURL(notification.payload.sender.username), diff --git a/app/ui/client/views/app/lib/getCommonRoomEvents.js b/app/ui/client/views/app/lib/getCommonRoomEvents.js index 869464451fa0..336c3bbd7db2 100644 --- a/app/ui/client/views/app/lib/getCommonRoomEvents.js +++ b/app/ui/client/views/app/lib/getCommonRoomEvents.js @@ -11,7 +11,6 @@ import { addMessageToList, } from '../../../../../ui-utils/client/lib/MessageAction'; import { callWithErrorHandling } from '../../../../../../client/lib/utils/callWithErrorHandling'; -import { promises } from '../../../../../promises/client'; import { isURL } from '../../../../../utils/lib/isURL'; import { openUserCard } from '../../../lib/UserCard'; import { messageArgs } from '../../../../../ui-utils/client/lib/messageArgs'; @@ -22,6 +21,7 @@ import { EmojiEvents } from '../../../../../reactions/client/init'; import { goToRoomById } from '../../../../../../client/lib/utils/goToRoomById'; import { fireGlobalEvent } from '../../../../../../client/lib/utils/fireGlobalEvent'; import { isLayoutEmbedded } from '../../../../../../client/lib/utils/isLayoutEmbedded'; +import { onClientBeforeSendMessage } from '../../../../../../client/lib/onClientBeforeSendMessage'; const mountPopover = (e, i, outerContext) => { let context = $(e.target).parents('.message').data('context'); @@ -252,7 +252,7 @@ export const getCommonRoomEvents = () => ({ return; } - msgObject = await promises.run('onClientBeforeSendMessage', msgObject); + msgObject = await onClientBeforeSendMessage(msgObject); const _chatMessages = chatMessages[rid]; if (_chatMessages && await _chatMessages.processSlashCommand(msgObject)) { diff --git a/client/importPackages.ts b/client/importPackages.ts index a23817a27237..9c26cb77c784 100644 --- a/client/importPackages.ts +++ b/client/importPackages.ts @@ -83,7 +83,6 @@ import '../app/settings/client'; import '../app/models/client'; import '../app/callbacks/client'; import '../app/notifications/client'; -import '../app/promises/client'; import '../app/ui-utils/client'; import '../app/ui-cached-collection/client'; import '../app/action-links/client'; diff --git a/client/lib/onClientBeforeSendMessage.ts b/client/lib/onClientBeforeSendMessage.ts new file mode 100644 index 000000000000..99a4e91a1e85 --- /dev/null +++ b/client/lib/onClientBeforeSendMessage.ts @@ -0,0 +1,4 @@ +import type { IMessage } from '../../definition/IMessage'; +import { createAsyncTransformChain } from '../../lib/transforms'; + +export const onClientBeforeSendMessage = createAsyncTransformChain(); diff --git a/client/lib/onClientMessageReceived.ts b/client/lib/onClientMessageReceived.ts new file mode 100644 index 000000000000..369ad7a17561 --- /dev/null +++ b/client/lib/onClientMessageReceived.ts @@ -0,0 +1,4 @@ +import type { IMessage } from '../../definition/IMessage'; +import { createAsyncTransformChain } from '../../lib/transforms'; + +export const onClientMessageReceived = createAsyncTransformChain(); diff --git a/client/startup/e2e.ts b/client/startup/e2e.ts index cabc116b617b..99ff7ce0c5a5 100644 --- a/client/startup/e2e.ts +++ b/client/startup/e2e.ts @@ -5,11 +5,12 @@ import { Tracker } from 'meteor/tracker'; import { e2e } from '../../app/e2e/client/rocketchat.e2e'; import { Subscriptions, Rooms } from '../../app/models/client'; import { Notifications } from '../../app/notifications/client'; -import { promises } from '../../app/promises/client'; import { settings } from '../../app/settings/client'; import { IMessage } from '../../definition/IMessage'; import { IRoom } from '../../definition/IRoom'; import { ISubscription } from '../../definition/ISubscription'; +import { onClientBeforeSendMessage } from '../lib/onClientBeforeSendMessage'; +import { onClientMessageReceived } from '../lib/onClientMessageReceived'; import { isLayoutEmbedded } from '../lib/utils/isLayoutEmbedded'; import { waitUntilFind } from '../lib/utils/waitUntilFind'; @@ -40,12 +41,15 @@ Meteor.startup(() => { }); let observable: Meteor.LiveQueryHandle | null = null; + let offClientMessageReceived: undefined | (() => void); + let offClientBeforeSendMessage: undefined | (() => void); Tracker.autorun(() => { if (!e2e.isReady()) { - promises.remove('onClientMessageReceived', 'e2e-decript-message'); + offClientMessageReceived?.(); Notifications.unUser('e2ekeyRequest', handle); observable?.stop(); - return promises.remove('onClientBeforeSendMessage', 'e2e'); + offClientBeforeSendMessage?.(); + return; } Notifications.onUser('e2ekeyRequest', handle); @@ -92,47 +96,37 @@ Meteor.startup(() => { }, }); - promises.add( - 'onClientMessageReceived', - async (msg: IMessage) => { - const e2eRoom = await e2e.getInstanceByRoomId(msg.rid); - if (!e2eRoom || !e2eRoom.shouldConvertReceivedMessages()) { - return msg; - } - return e2e.decryptMessage(msg); - }, - promises.priority.HIGH, - 'e2e-decript-message', - ); + offClientMessageReceived = onClientMessageReceived.use(async (msg: IMessage) => { + const e2eRoom = await e2e.getInstanceByRoomId(msg.rid); + if (!e2eRoom || !e2eRoom.shouldConvertReceivedMessages()) { + return msg; + } + return e2e.decryptMessage(msg); + }); // Encrypt messages before sending - promises.add( - 'onClientBeforeSendMessage', - async (message: IMessage) => { - const e2eRoom = await e2e.getInstanceByRoomId(message.rid); + offClientBeforeSendMessage = onClientBeforeSendMessage.use(async (message: IMessage) => { + const e2eRoom = await e2e.getInstanceByRoomId(message.rid); - if (!e2eRoom) { - return message; - } + if (!e2eRoom) { + return message; + } - const subscription = await waitUntilFind(() => Rooms.findOne({ _id: message.rid })); + const subscription = await waitUntilFind(() => Rooms.findOne({ _id: message.rid })); - subscription.encrypted ? e2eRoom.resume() : e2eRoom.pause(); + subscription.encrypted ? e2eRoom.resume() : e2eRoom.pause(); - if (!(await e2eRoom.shouldConvertSentMessages())) { - return message; - } + if (!(await e2eRoom.shouldConvertSentMessages())) { + return message; + } - // Should encrypt this message. - const msg = await e2eRoom.encrypt(message); + // Should encrypt this message. + const msg = await e2eRoom.encrypt(message); - message.msg = msg; - message.t = 'e2e'; - message.e2e = 'pending'; - return message; - }, - promises.priority.HIGH, - 'e2e', - ); + message.msg = msg; + message.t = 'e2e'; + message.e2e = 'pending'; + return message; + }); }); }); diff --git a/lib/transforms.ts b/lib/transforms.ts new file mode 100644 index 000000000000..e7e59df795c2 --- /dev/null +++ b/lib/transforms.ts @@ -0,0 +1,38 @@ +type Transform> = (x: T) => U; + +type TransformChain> = { + (x: T): U; + use(transform: Transform): (() => void); +}; + +export const createTransformChain = (...transforms: Transform[]): TransformChain => { + let chain = transforms; + + const call = (x: T): T => chain.reduce((x, transform) => transform(x), x); + + const use = (transform: Transform): (() => void) => { + chain.push(transform); + + return (): void => { + chain = chain.filter((t) => t !== transform); + }; + }; + + return Object.assign(call, { use }); +}; + +export const createAsyncTransformChain = (...transforms: Transform>[]): TransformChain> => { + let chain = transforms; + + const call = (x: T): Promise => chain.reduce((x, transform) => x.then(transform), Promise.resolve(x)); + + const use = (transform: Transform>): (() => void) => { + chain.push(transform); + + return (): void => { + chain = chain.filter((t) => t !== transform); + }; + }; + + return Object.assign(call, { use }); +}; diff --git a/server/importPackages.js b/server/importPackages.ts similarity index 99% rename from server/importPackages.js rename to server/importPackages.ts index 385a812f488f..fb5ee5b21abc 100644 --- a/server/importPackages.js +++ b/server/importPackages.ts @@ -104,7 +104,6 @@ import '../app/models'; import '../app/metrics'; import '../app/callbacks'; import '../app/notifications'; -import '../app/promises/server'; import '../app/ui-utils'; import '../app/action-links/server'; import '../app/reactions/server';