diff --git a/.changeset/afraid-guests-jog.md b/.changeset/afraid-guests-jog.md deleted file mode 100644 index 420b9bb5d329..000000000000 --- a/.changeset/afraid-guests-jog.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/livechat": minor ---- - -Created a `transferChat` Livechat API endpoint for transferring chats programmatically, the endpoint has all the limitations & permissions required that transferring via UI has diff --git a/.changeset/bright-humans-cross.md b/.changeset/bright-humans-cross.md new file mode 100644 index 000000000000..aa0c4c658994 --- /dev/null +++ b/.changeset/bright-humans-cross.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Federation actions like sending message in a federated DM, reacting in a federated chat, etc, will no longer work if the configuration is invalid. diff --git a/.changeset/brown-crabs-chew.md b/.changeset/brown-crabs-chew.md new file mode 100644 index 000000000000..3291f18bf225 --- /dev/null +++ b/.changeset/brown-crabs-chew.md @@ -0,0 +1,13 @@ +--- +'@rocket.chat/uikit-playground': patch +'@rocket.chat/fuselage-ui-kit': patch +'@rocket.chat/ui-theming': patch +'@rocket.chat/ui-video-conf': patch +'@rocket.chat/ui-composer': patch +'@rocket.chat/gazzodown': patch +'@rocket.chat/ui-avatar': patch +'@rocket.chat/ui-client': patch +'@rocket.chat/meteor': patch +--- + +Bumped @rocket.chat/fuselage that fixes the Menu onPointerUp event behavior diff --git a/.changeset/calm-tigers-peel.md b/.changeset/calm-tigers-peel.md new file mode 100644 index 000000000000..32feed6f7107 --- /dev/null +++ b/.changeset/calm-tigers-peel.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/ui-kit": patch +--- + +fix UiKit error message: Failed to resolve module: @rocket.chat/icons diff --git a/.changeset/chatty-hounds-hammer.md b/.changeset/chatty-hounds-hammer.md deleted file mode 100644 index 1a2d3a7de559..000000000000 --- a/.changeset/chatty-hounds-hammer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/fuselage-ui-kit": patch ---- - -Fix validations from "UiKit" modal component diff --git a/.changeset/chilled-yaks-beg.md b/.changeset/chilled-yaks-beg.md deleted file mode 100644 index 670fa24887b7..000000000000 --- a/.changeset/chilled-yaks-beg.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed issue in Marketplace that caused a subscription app to show incorrect modals when subscribing diff --git a/.changeset/chilly-papayas-march.md b/.changeset/chilly-papayas-march.md deleted file mode 100644 index a7724b126695..000000000000 --- a/.changeset/chilly-papayas-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed SAML users' full names being updated on login regardless of the "Overwrite user fullname (use idp attribute)" setting diff --git a/.changeset/cool-rocks-remember.md b/.changeset/cool-rocks-remember.md new file mode 100644 index 000000000000..97af36e94320 --- /dev/null +++ b/.changeset/cool-rocks-remember.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Fixed login with third-party apps not working without the "Manage OAuth Apps" permission diff --git a/.changeset/cuddly-brooms-approve.md b/.changeset/cuddly-brooms-approve.md deleted file mode 100644 index 24905bb91c62..000000000000 --- a/.changeset/cuddly-brooms-approve.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/i18n": minor ---- - -Allows admins to customize the `Subject` field of Omnichannel email transcripts via setting. By passing a value to the setting `Custom email subject for transcript`, system will use it as the `Subject` field, unless a custom subject is passed when requesting a transcript. If there's no custom subject and setting value is empty, the current default value will be used diff --git a/.changeset/dry-pumas-draw.md b/.changeset/dry-pumas-draw.md deleted file mode 100644 index b66ca5157cd5..000000000000 --- a/.changeset/dry-pumas-draw.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/livechat": patch ---- - -Fixed an issue that caused the widget to set the wrong department when using the setDepartment Livechat api endpoint in conjunction with a Livechat Trigger diff --git a/.changeset/empty-readers-teach.md b/.changeset/empty-readers-teach.md deleted file mode 100644 index b4bd075ef654..000000000000 --- a/.changeset/empty-readers-teach.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/tools": patch -"@rocket.chat/account-service": patch ---- - -Fixed an inconsistent evaluation of the `Accounts_LoginExpiration` setting over the codebase. In some places, it was being used as milliseconds while in others as days. Invalid values produced different results. A helper function was created to centralize the setting validation and the proper value being returned to avoid edge cases. -Negative values may be saved on the settings UI panel but the code will interpret any negative, NaN or 0 value to the default expiration which is 90 days. diff --git a/.changeset/empty-toys-smell.md b/.changeset/empty-toys-smell.md new file mode 100644 index 000000000000..043d9c19567d --- /dev/null +++ b/.changeset/empty-toys-smell.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Federated users can no longer be deleted. diff --git a/.changeset/fast-buttons-shake.md b/.changeset/fast-buttons-shake.md deleted file mode 100644 index 6281fc9941ec..000000000000 --- a/.changeset/fast-buttons-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Fixed an issue where FCM actions did not respect environment's proxy settings diff --git a/.changeset/fast-lobsters-turn.md b/.changeset/fast-lobsters-turn.md new file mode 100644 index 000000000000..ff1d97ea7289 --- /dev/null +++ b/.changeset/fast-lobsters-turn.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed an issue due to an endpoint pagination that was causing that when an agent have assigned more than 50 departments, the departments have a blank space instead of the name. diff --git a/.changeset/fifty-mails-admire.md b/.changeset/fifty-mails-admire.md deleted file mode 100644 index b87fd11d47ee..000000000000 --- a/.changeset/fifty-mails-admire.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@rocket.chat/web-ui-registration': patch -"@rocket.chat/meteor": minor ---- - -Login services button was not respecting the button color and text color settings. Implemented a fix to respect these settings and change the button colors accordingly. - -Added a warning on all settings which allow admins to change OAuth button colors, so that they can be alerted about WCAG (Web Content Accessibility Guidelines) compliance. diff --git a/.changeset/funny-boats-guess.md b/.changeset/funny-boats-guess.md new file mode 100644 index 000000000000..076acff98329 --- /dev/null +++ b/.changeset/funny-boats-guess.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/i18n": minor +--- + +Added a new Audit endpoint `audit/rooms.members` that allows users with `view-members-list-all-rooms` to fetch a list of the members of any room even if the user is not part of it. diff --git a/.changeset/funny-snails-promise.md b/.changeset/funny-snails-promise.md deleted file mode 100644 index bdd74a60b1e9..000000000000 --- a/.changeset/funny-snails-promise.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/livechat": patch ---- - -livechat `setDepartment` livechat api fixes: -- Changing department didn't reflect on the registration form in real time -- Changing the department mid conversation didn't transfer the chat -- Depending on the state of the department, it couldn't be set as default - diff --git a/.changeset/funny-wolves-tie.md b/.changeset/funny-wolves-tie.md deleted file mode 100644 index e2364ccb05e5..000000000000 --- a/.changeset/funny-wolves-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed issue where bad word filtering was not working in the UI for messages diff --git a/.changeset/gentle-bugs-think.md b/.changeset/gentle-bugs-think.md new file mode 100644 index 000000000000..fc4738f3043a --- /dev/null +++ b/.changeset/gentle-bugs-think.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Prevent `processRoomAbandonment` callback from erroring out when a room was inactive during a day Business Hours was not configured for. diff --git a/.changeset/gentle-news-wonder.md b/.changeset/gentle-news-wonder.md new file mode 100644 index 000000000000..dd3218003d7a --- /dev/null +++ b/.changeset/gentle-news-wonder.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": minor +--- + +Added a new 'Deactivated' tab to the users page, this tab lists users who have logged in for the first time but have been deactivated for any reason. Also added the UI code for the Active tab; diff --git a/.changeset/giant-spiders-pay.md b/.changeset/giant-spiders-pay.md new file mode 100644 index 000000000000..1798cd2baaee --- /dev/null +++ b/.changeset/giant-spiders-pay.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where the Announcement modal with long words was adding a horizontal scrollbar diff --git a/.changeset/gorgeous-hotels-attend.md b/.changeset/gorgeous-hotels-attend.md new file mode 100644 index 000000000000..fd858d7ace86 --- /dev/null +++ b/.changeset/gorgeous-hotels-attend.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Stopped non channel members from dragging and dropping files in a channel they do not belong diff --git a/.changeset/grumpy-worms-appear.md b/.changeset/grumpy-worms-appear.md deleted file mode 100644 index fb9fab77b24c..000000000000 --- a/.changeset/grumpy-worms-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/i18n": patch ---- - -Fixed wrong wording on a federation setting diff --git a/.changeset/happy-peaches-nail.md b/.changeset/happy-peaches-nail.md deleted file mode 100644 index 2dfb2151ced0..000000000000 --- a/.changeset/happy-peaches-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed issue with livechat agents not being able to leave omnichannel rooms if joining after a room has been closed by the visitor (due to race conditions) diff --git a/.changeset/hip-queens-taste.md b/.changeset/hip-queens-taste.md deleted file mode 100644 index f1d7bb6f3f0e..000000000000 --- a/.changeset/hip-queens-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": minor ---- - -Added the possibility for apps to remove users from a room diff --git a/.changeset/hungry-wombats-act.md b/.changeset/hungry-wombats-act.md deleted file mode 100644 index 4e50b172e17e..000000000000 --- a/.changeset/hungry-wombats-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed an issue where non-encrypted attachments were not being downloaded diff --git a/.changeset/large-geese-ring.md b/.changeset/large-geese-ring.md new file mode 100644 index 000000000000..9b36edf1c02d --- /dev/null +++ b/.changeset/large-geese-ring.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Replaces an outdated banner with the Bubble component in order to display retention policy warning diff --git a/.changeset/large-vans-attack.md b/.changeset/large-vans-attack.md deleted file mode 100644 index c1008b2ca06f..000000000000 --- a/.changeset/large-vans-attack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -fixed the contextual bar closing when editing thread messages instead of cancelling the message edit diff --git a/.changeset/lemon-steaks-provide.md b/.changeset/lemon-steaks-provide.md new file mode 100644 index 000000000000..29f0289419a0 --- /dev/null +++ b/.changeset/lemon-steaks-provide.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Added an accordion for advanced settings on Create teams and channels diff --git a/.changeset/lucky-beds-glow.md b/.changeset/lucky-beds-glow.md deleted file mode 100644 index 3e23797025e1..000000000000 --- a/.changeset/lucky-beds-glow.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@rocket.chat/ui-client': minor -'@rocket.chat/i18n': minor -'@rocket.chat/meteor': minor ---- - -Feature Preview: New Navigation - `Header` and `Contextualbar` size improvements consistent with the new global `NavBar` diff --git a/.changeset/lucky-countries-look.md b/.changeset/lucky-countries-look.md deleted file mode 100644 index 79deda53edfc..000000000000 --- a/.changeset/lucky-countries-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed the disappearance of some settings after navigation under network latency. diff --git a/.changeset/many-tables-love.md b/.changeset/many-tables-love.md deleted file mode 100644 index 8f37283c6a96..000000000000 --- a/.changeset/many-tables-love.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/model-typings": minor ---- - -Fixed Livechat rooms being displayed in the Engagement Dashboard's "Channels" tab diff --git a/.changeset/mean-hairs-move.md b/.changeset/mean-hairs-move.md deleted file mode 100644 index c92293d6ae95..000000000000 --- a/.changeset/mean-hairs-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Fixed an issue where adding `OVERWRITE_SETTING_` for any setting wasn't immediately taking effect sometimes, and needed a server restart to reflect. diff --git a/.changeset/nasty-windows-smile.md b/.changeset/nasty-windows-smile.md new file mode 100644 index 000000000000..e80ec3db27a9 --- /dev/null +++ b/.changeset/nasty-windows-smile.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Allow apps to react/unreact to messages via bridge diff --git a/.changeset/nervous-rockets-impress.md b/.changeset/nervous-rockets-impress.md deleted file mode 100644 index 26e9276193de..000000000000 --- a/.changeset/nervous-rockets-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixes Missing line breaks on Omnichannel Room Info Panel diff --git a/.changeset/new-balloons-speak.md b/.changeset/new-balloons-speak.md deleted file mode 100644 index 7d4e7cd3a57e..000000000000 --- a/.changeset/new-balloons-speak.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed web client crashing on Firefox private window. Firefox disables access to service workers inside private windows. Rocket.Chat needs service workers to process E2EE encrypted files on rooms. These types of files won't be available inside private windows, but the rest of E2EE encrypted features should work normally diff --git a/.changeset/new-mayflies-wait.md b/.changeset/new-mayflies-wait.md new file mode 100644 index 000000000000..832db68cecd4 --- /dev/null +++ b/.changeset/new-mayflies-wait.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Deactivating users who federated will now be permanent. diff --git a/.changeset/new-scissors-love.md b/.changeset/new-scissors-love.md deleted file mode 100644 index fb962407b353..000000000000 --- a/.changeset/new-scissors-love.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@rocket.chat/omnichannel-services': minor -'@rocket.chat/pdf-worker': minor -'@rocket.chat/core-services': minor -'@rocket.chat/model-typings': minor -'@rocket.chat/i18n': minor -'@rocket.chat/meteor': minor ---- - -Added system messages support for Omnichannel PDF transcripts and email transcripts. Currently these transcripts don't render system messages and is shown as an empty message in PDF/email. This PR adds this support for all valid livechat system messages. - -Also added a new setting under transcripts, to toggle the inclusion of system messages in email and PDF transcripts. diff --git a/.changeset/nice-laws-eat.md b/.changeset/nice-laws-eat.md deleted file mode 100644 index e99e4f219ef9..000000000000 --- a/.changeset/nice-laws-eat.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'rocketchat-services': minor -'@rocket.chat/core-services': minor -'@rocket.chat/model-typings': minor -'@rocket.chat/ui-video-conf': minor -'@rocket.chat/core-typings': minor -'@rocket.chat/ui-contexts': minor -'@rocket.chat/models': minor -'@rocket.chat/ui-kit': minor -'@rocket.chat/i18n': minor -'@rocket.chat/meteor': minor ---- - -New Feature: Video Conference Persistent Chat. -This feature provides a discussion id for conference provider apps to store the chat messages exchanged during the conferences, so that those users may then access those messages again at any time through Rocket.Chat. \ No newline at end of file diff --git a/.changeset/ninety-hounds-exist.md b/.changeset/ninety-hounds-exist.md new file mode 100644 index 000000000000..99882de12018 --- /dev/null +++ b/.changeset/ninety-hounds-exist.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/rest-typings': patch +'@rocket.chat/meteor': patch +'@rocket.chat/i18n': patch +--- + +Fix: Show correct user info actions for non-members in channels. diff --git a/.changeset/perfect-coins-camp.md b/.changeset/perfect-coins-camp.md deleted file mode 100644 index 4dbddf965742..000000000000 --- a/.changeset/perfect-coins-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -fixed an issue in the "Create discussion" form, that would have the "Create" action button disabled even though the form is prefilled when opening it from the message action diff --git a/.changeset/polite-foxes-repair.md b/.changeset/polite-foxes-repair.md deleted file mode 100644 index 2f524c7e5f10..000000000000 --- a/.changeset/polite-foxes-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Added a method to the Apps-Engine that allows apps to read multiple messages from a room diff --git a/.changeset/popular-bottles-visit.md b/.changeset/popular-bottles-visit.md new file mode 100644 index 000000000000..9e44e9dd7144 --- /dev/null +++ b/.changeset/popular-bottles-visit.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed an issue that caused UI to show an error when the call to get the Business Hour type from settings returned `undefined`. diff --git a/.changeset/popular-trees-lay.md b/.changeset/popular-trees-lay.md deleted file mode 100644 index f38ef1f92367..000000000000 --- a/.changeset/popular-trees-lay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Removed 'Hide' option in the room menu for Omnichannel conversations. diff --git a/.changeset/proud-waves-bathe.md b/.changeset/proud-waves-bathe.md deleted file mode 100644 index 556fa3af80e1..000000000000 --- a/.changeset/proud-waves-bathe.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/model-typings": minor ---- - -Improved Engagement Dashboard's "Channels" tab performance by not returning rooms that had no activity in the analyzed period diff --git a/.changeset/proud-years-buy.md b/.changeset/proud-years-buy.md new file mode 100644 index 000000000000..94f4ab0df736 --- /dev/null +++ b/.changeset/proud-years-buy.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/i18n': patch +--- + +Fixes a typo in german translation and fixes the broken hyperlink for Resend and Change Email diff --git a/.changeset/purple-dolls-serve.md b/.changeset/purple-dolls-serve.md new file mode 100644 index 000000000000..fc44faa60a38 --- /dev/null +++ b/.changeset/purple-dolls-serve.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/web-ui-registration': patch +'@rocket.chat/i18n': patch +'@rocket.chat/meteor': patch +--- + +Fixes an issue where creating a new user with an invalid username (containing special characters) resulted in an error message, but the user was still created. The user creation process now properly aborts when an invalid username is provided. diff --git a/.changeset/quick-ducks-live.md b/.changeset/quick-ducks-live.md deleted file mode 100644 index ad628c13d087..000000000000 --- a/.changeset/quick-ducks-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed LDAP rooms, teams and roles syncs not being triggered on login even when the "Update User Data on Login" setting is enabled diff --git a/.changeset/rare-penguins-hope.md b/.changeset/rare-penguins-hope.md deleted file mode 100644 index 187bd9d09ddc..000000000000 --- a/.changeset/rare-penguins-hope.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/core-typings": patch ---- - -Allow customFields on livechat creation bridge diff --git a/.changeset/red-numbers-happen.md b/.changeset/red-numbers-happen.md deleted file mode 100644 index 61cb0d2b7586..000000000000 --- a/.changeset/red-numbers-happen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed "Copy link" message action enabled in Starred and Pinned list for End to End Encrypted channels, this action is disabled now diff --git a/.changeset/red-vans-shave.md b/.changeset/red-vans-shave.md deleted file mode 100644 index ddf76535087e..000000000000 --- a/.changeset/red-vans-shave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Fixed issue that caused unintentional clicks when scrolling the channels sidebar on safari/chrome in iOS diff --git a/.changeset/rich-carpets-brush.md b/.changeset/rich-carpets-brush.md deleted file mode 100644 index 16741e31e54a..000000000000 --- a/.changeset/rich-carpets-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed some anomalies related to disabled E2EE rooms. Earlier there are some weird issues with disabled E2EE rooms, this PR fixes these anomalies. diff --git a/.changeset/rich-pillows-hang.md b/.changeset/rich-pillows-hang.md new file mode 100644 index 000000000000..b714a5e6acd9 --- /dev/null +++ b/.changeset/rich-pillows-hang.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes the `expanded` prop being accidentally forwarded to `ContextualbarHeader` diff --git a/.changeset/rooms-table-ts.md b/.changeset/rooms-table-ts.md new file mode 100644 index 000000000000..b5055ad26f69 --- /dev/null +++ b/.changeset/rooms-table-ts.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Add "Created at" column to admin rooms table diff --git a/.changeset/rotten-camels-pretend.md b/.changeset/rotten-camels-pretend.md new file mode 100644 index 000000000000..5145bbaa5050 --- /dev/null +++ b/.changeset/rotten-camels-pretend.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/core-typings": patch +--- + +Fixed issue with system messages being counted as agents' first responses in livechat rooms (which caused the "best first response time" and "average first response time" metrics to be unreliable for all agents) diff --git a/.changeset/rotten-eggs-end.md b/.changeset/rotten-eggs-end.md deleted file mode 100644 index 7d0ad6ee5047..000000000000 --- a/.changeset/rotten-eggs-end.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/i18n": patch -"@rocket.chat/ui-client": patch ---- - -Implemented a new tab to the users page called 'Active', this tab lists all users who have logged in for the first time and are active. diff --git a/.changeset/rude-dogs-burn.md b/.changeset/rude-dogs-burn.md new file mode 100644 index 000000000000..e81f00782083 --- /dev/null +++ b/.changeset/rude-dogs-burn.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed a behavior when updating messages that prevented the `customFields` prop from being updated if there were no changes to the `msg` property. Now, `customFields` will be always updated on message update even if `msg` doesn't change diff --git a/.changeset/selfish-emus-sing.md b/.changeset/selfish-emus-sing.md deleted file mode 100644 index 315d674a1857..000000000000 --- a/.changeset/selfish-emus-sing.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/i18n": minor ---- - -Added account setting `Accounts_Default_User_Preferences_sidebarSectionsOrder` to allow users to reorganize sidebar sections diff --git a/.changeset/shaggy-hats-raise.md b/.changeset/shaggy-hats-raise.md deleted file mode 100644 index 40ee9f8fbb55..000000000000 --- a/.changeset/shaggy-hats-raise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": minor ---- - -Added a new setting `Livechat_transcript_send_always` that allows admins to decide if email transcript should be sent all the times when a conversation is closed. This setting bypasses agent's preferences. For this setting to work, `Livechat_enable_transcript` should be off, meaning that visitors will no longer receive the option to decide if they want a transcript or not. diff --git a/.changeset/six-beers-fry.md b/.changeset/six-beers-fry.md new file mode 100644 index 000000000000..48409c2f8de5 --- /dev/null +++ b/.changeset/six-beers-fry.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +New button added to validate Matrix Federation configuration. A new field inside admin settings will reflect the configuration status being either 'Valid' or 'Invalid'. diff --git a/.changeset/sixty-nails-clean.md b/.changeset/sixty-nails-clean.md deleted file mode 100644 index 7d13e02f0bd3..000000000000 --- a/.changeset/sixty-nails-clean.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixed an issue that prevented the option to start a discussion from being shown on the message actions diff --git a/.changeset/sixty-spoons-own.md b/.changeset/sixty-spoons-own.md new file mode 100644 index 000000000000..0b717c3965ef --- /dev/null +++ b/.changeset/sixty-spoons-own.md @@ -0,0 +1,9 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": minor +"@rocket.chat/model-typings": minor +"@rocket.chat/models": minor +"@rocket.chat/rest-typings": minor +--- + +Introduced "create contacts" endpoint to omnichannel diff --git a/.changeset/smart-mice-attack.md b/.changeset/smart-mice-attack.md new file mode 100644 index 000000000000..3ca47060ce5c --- /dev/null +++ b/.changeset/smart-mice-attack.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed an issue where teams were being created with no room associated with it. diff --git a/.changeset/smooth-lobsters-flash.md b/.changeset/smooth-lobsters-flash.md deleted file mode 100644 index 541d5069ee9c..000000000000 --- a/.changeset/smooth-lobsters-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fix show correct user roles after updating user roles on admin edit user panel. diff --git a/.changeset/soft-donkeys-thank.md b/.changeset/soft-donkeys-thank.md deleted file mode 100644 index 7273ddcffca4..000000000000 --- a/.changeset/soft-donkeys-thank.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/mock-providers": patch -"@rocket.chat/ui-contexts": patch -"@rocket.chat/web-ui-registration": patch ---- - -Fixed an issue with blocked login when dismissed 2FA modal by clicking outside of it or pressing the escape key diff --git a/.changeset/sour-forks-breathe.md b/.changeset/sour-forks-breathe.md deleted file mode 100644 index 2d1076845fa9..000000000000 --- a/.changeset/sour-forks-breathe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": minor ---- - -Extended apps-engine events for users leaving a room to also fire when being removed by another user. Also added the triggering user's information to the event's context payload. diff --git a/.changeset/spicy-kings-think.md b/.changeset/spicy-kings-think.md new file mode 100644 index 000000000000..9e8f3648b28c --- /dev/null +++ b/.changeset/spicy-kings-think.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixes multiple problems with the `processRoomAbandonment` hook. This hook is in charge of calculating the time a room has been abandoned (this means, the time that elapsed since a room was last answered by an agent until it was closed). However, when business hours were enabled and the user didn't open on one day, if an abandoned room happened to be abandoned _over_ the day there was no business hour configuration, then the process will error out. +Additionally, the values the code was calculating were not right. When business hours are enabled, this code should only count the abandonment time _while a business hour was open_. When rooms were left abandoned for days or weeks, this will also throw an error or output an invalid count. diff --git a/.changeset/strong-swans-double.md b/.changeset/strong-swans-double.md new file mode 100644 index 000000000000..db521aeeef0f --- /dev/null +++ b/.changeset/strong-swans-double.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/uikit-playground': minor +'@rocket.chat/meteor': minor +--- + +Upgrades fuselage-toastbar version in order to add pause on hover functionality diff --git a/.changeset/strong-terms-love.md b/.changeset/strong-terms-love.md new file mode 100644 index 000000000000..2535a466eb8e --- /dev/null +++ b/.changeset/strong-terms-love.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Fixed issue with livechat analytics in a given date range considering conversation data from the following day diff --git a/.changeset/stupid-fishes-relate.md b/.changeset/stupid-fishes-relate.md new file mode 100644 index 000000000000..82bfaa1cfd28 --- /dev/null +++ b/.changeset/stupid-fishes-relate.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/core-typings': minor +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Added a new setting to enable/disable file encryption in an end to end encrypted room. diff --git a/.changeset/stupid-pigs-share.md b/.changeset/stupid-pigs-share.md new file mode 100644 index 000000000000..55d68c66d587 --- /dev/null +++ b/.changeset/stupid-pigs-share.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Wraps some room settings in an accordion advanced settings section in room edit contextual bar to improve organization diff --git a/.changeset/swift-maps-tickle.md b/.changeset/swift-maps-tickle.md new file mode 100644 index 000000000000..076ead1cea4c --- /dev/null +++ b/.changeset/swift-maps-tickle.md @@ -0,0 +1,9 @@ +--- +'@rocket.chat/core-services': minor +'@rocket.chat/model-typings': minor +'@rocket.chat/core-typings': minor +'@rocket.chat/rest-typings': minor +'@rocket.chat/meteor': minor +--- + +Added `sidepanel` field to `teams.create` and `rooms.saveRoomSettings` endpoints diff --git a/.changeset/ten-bulldogs-clap.md b/.changeset/ten-bulldogs-clap.md new file mode 100644 index 000000000000..15f88bb6bd97 --- /dev/null +++ b/.changeset/ten-bulldogs-clap.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fixed an issue with the "follow message" button not changing state after click diff --git a/.changeset/thin-windows-reply.md b/.changeset/thin-windows-reply.md deleted file mode 100644 index 1a32e1ddebfb..000000000000 --- a/.changeset/thin-windows-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Fixes an issue not displaying all groups in settings list diff --git a/.changeset/thirty-dryers-help.md b/.changeset/thirty-dryers-help.md new file mode 100644 index 000000000000..6d9a7dc205ae --- /dev/null +++ b/.changeset/thirty-dryers-help.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/livechat": patch +--- + +Fixed issue where `after-registration-triggers` would show up in a page when the user was not yet registered diff --git a/.changeset/twelve-windows-train.md b/.changeset/twelve-windows-train.md new file mode 100644 index 000000000000..4c6ef548e650 --- /dev/null +++ b/.changeset/twelve-windows-train.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed: Custom fields in extraData now correctly added to extraRoomInfo by livechat.beforeRoom callback during livechat room creation. diff --git a/.changeset/two-bikes-crash.md b/.changeset/two-bikes-crash.md new file mode 100644 index 000000000000..a120435e4a48 --- /dev/null +++ b/.changeset/two-bikes-crash.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed an issue related to setting Accounts_ForgetUserSessionOnWindowClose, this setting was not working as expected. + +The new meteor 2.16 release introduced a new option to configure the Accounts package and choose between the local storage or session storage. They also changed how Meteor.\_localstorage works internally. Due to these changes in Meteor, our setting to use session storage wasn't working as expected. This PR fixes this issue and configures the Accounts package according to the workspace settings. diff --git a/.changeset/violet-brooms-press.md b/.changeset/violet-brooms-press.md deleted file mode 100644 index 632026d6fe2e..000000000000 --- a/.changeset/violet-brooms-press.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': patch ---- - -Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) diff --git a/.changeset/violet-radios-begin.md b/.changeset/violet-radios-begin.md new file mode 100644 index 000000000000..d11f23b47478 --- /dev/null +++ b/.changeset/violet-radios-begin.md @@ -0,0 +1,15 @@ +--- +'@rocket.chat/core-typings': minor +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Fixed a bug related to uploading end to end encrypted file. + +E2EE files and uploads are uploaded as files of mime type `application/octet-stream` as we can't reveal the mime type of actual content since it is encrypted and has to be kept confidential. + +The server resolves the mime type of encrypted file as `application/octet-stream` but it wasn't playing nicely with existing settings related to whitelisted and blacklisted media types. + +E2EE files upload was getting blocked if `application/octet-stream` is not a whitelisted media type. + +Now this PR solves this issue by always accepting E2EE uploads even if `application/octet-stream` is not whitelisted but it will block the upload if `application/octet-stream` is black listed. diff --git a/.changeset/weak-insects-sort.md b/.changeset/weak-insects-sort.md deleted file mode 100644 index cbbe7c4aa08c..000000000000 --- a/.changeset/weak-insects-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rocket.chat/meteor": patch ---- - -Improving UX by change the position of room info actions buttons and menu order to avoid missclick in destructive actions. diff --git a/.changeset/weak-pets-talk.md b/.changeset/weak-pets-talk.md deleted file mode 100644 index abaa9c683d65..000000000000 --- a/.changeset/weak-pets-talk.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@rocket.chat/omnichannel-services': patch -'@rocket.chat/core-services': patch -'@rocket.chat/meteor': patch ---- - -Reduced time on generation of PDF transcripts. Earlier Rocket.Chat was fetching the required translations everytime a PDF transcript was requested, this process was async and was being unnecessarily being performed on every pdf transcript request. This PR improves this and now the translations are loaded at the start and kept in memory to process further pdf transcripts requests. This reduces the time of asynchronously fetching translations again and again. diff --git a/.changeset/weak-taxis-design.md b/.changeset/weak-taxis-design.md deleted file mode 100644 index a2d435495cd7..000000000000 --- a/.changeset/weak-taxis-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@rocket.chat/meteor': minor ---- - -Added handling of attachments in Omnichannel email transcripts. Earlier attachments were being skipped and were being shown as empty space, now it should render the image attachments and should show relevant error message for unsupported attachments. diff --git a/.changeset/weak-tigers-suffer.md b/.changeset/weak-tigers-suffer.md deleted file mode 100644 index 91748a43c677..000000000000 --- a/.changeset/weak-tigers-suffer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/model-typings": minor -"@rocket.chat/rest-typings": minor ---- - -Added the ability to filter chats by `queued` on the Current Chats Omnichannel page diff --git a/.changeset/witty-bats-develop.md b/.changeset/witty-bats-develop.md deleted file mode 100644 index 42c9409d9ef3..000000000000 --- a/.changeset/witty-bats-develop.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@rocket.chat/meteor": patch -"@rocket.chat/apps": patch -"@rocket.chat/core-services": patch -"@rocket.chat/core-typings": patch -"@rocket.chat/fuselage-ui-kit": patch -"@rocket.chat/rest-typings": patch -"@rocket.chat/ddp-streamer": patch -"@rocket.chat/presence": patch -"rocketchat-services": patch ---- - -Added the `user` param to apps-engine update method call, allowing apps' new `onUpdate` hook to know who triggered the update. diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 364957ecdf01..5af39b924057 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -17,13 +17,28 @@ inputs: required: false description: 'Containers to build along with Rocket.Chat' type: string + turbo-cache: + required: false + description: 'Enable turbo cache' + default: 'true' + publish-image: + required: false + description: 'Publish image' + default: 'true' + setup: + required: false + description: 'Setup node.js' + default: 'true' + NPM_TOKEN: + required: false + description: 'NPM token' runs: using: composite steps: - name: Login to GitHub Container Registry - if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') + if: inputs.publish-image == 'true' &&(github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: docker/login-action@v2 with: registry: ghcr.io @@ -31,7 +46,7 @@ runs: password: ${{ inputs.CR_PAT }} - name: Restore build - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build path: /tmp/build @@ -42,17 +57,21 @@ runs: cd /tmp/build tar xzf Rocket.Chat.tar.gz rm Rocket.Chat.tar.gz - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 + # if we are testing a PR from a fork, we already called the turbo cache at this point, so it should be false + if: inputs.turbo-cache == 'true' - name: Setup NodeJS uses: ./.github/actions/setup-node + if: inputs.setup == 'true' with: node-version: ${{ inputs.node-version }} cache-modules: true install: true + NPM_TOKEN: ${{ inputs.NPM_TOKEN }} - run: yarn build + if: inputs.setup == 'true' shell: bash - name: Build Docker images @@ -63,9 +82,14 @@ runs: docker compose -f docker-compose-ci.yml build "${args[@]}" - name: Publish Docker images to GitHub Container Registry - if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') + if: inputs.publish-image == 'true' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') shell: bash run: | args=(rocketchat ${{ inputs.build-containers }}) docker compose -f docker-compose-ci.yml push "${args[@]}" + + - name: Clean up temporary files + shell: bash + run: | + sudo rm -rf /tmp/bundle diff --git a/.github/actions/meteor-build/action.yml b/.github/actions/meteor-build/action.yml index d261000ceb87..525595146700 100644 --- a/.github/actions/meteor-build/action.yml +++ b/.github/actions/meteor-build/action.yml @@ -13,6 +13,9 @@ inputs: required: true description: 'Node version' type: string + NPM_TOKEN: + required: false + description: 'NPM token' runs: using: composite @@ -29,6 +32,7 @@ runs: node-version: ${{ inputs.node-version }} cache-modules: true install: true + NPM_TOKEN: ${{ inputs.NPM_TOKEN }} # - name: Free disk space # run: | @@ -91,7 +95,7 @@ runs: meteor node -v git version - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Translation check shell: bash @@ -124,7 +128,8 @@ runs: tar czf /tmp/Rocket.Chat.tar.gz bundle - name: Store build - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: build path: /tmp/Rocket.Chat.tar.gz + overwrite: true diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index caa3c63e00f0..1035e2835792 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -1,22 +1,27 @@ name: 'Setup Node' +description: 'Setup NodeJS' inputs: node-version: required: true - type: string + description: 'Node version' cache-modules: required: false - type: boolean + description: 'Cache node_modules' install: required: false - type: boolean + description: 'Install dependencies' deno-dir: required: false - type: string + description: 'Deno directory' default: ~/.deno-cache + NPM_TOKEN: + required: false + description: 'NPM token' outputs: node-version: + description: 'Node version' value: ${{ steps.node-version.outputs.node-version }} runs: @@ -32,6 +37,7 @@ runs: uses: actions/cache@v3 with: path: | + .turbo/cache node_modules ${{ env.DENO_DIR }} apps/meteor/node_modules @@ -48,6 +54,13 @@ runs: node-version: ${{ inputs.node-version }} cache: 'yarn' + - name: yarn login + shell: bash + if: inputs.NPM_TOKEN + run: | + echo "//registry.npmjs.org/:_authToken=${{ inputs.NPM_TOKEN }}" > ~/.npmrc + - name: yarn install + if: inputs.install shell: bash run: yarn diff --git a/.github/workflows/ci-code-check.yml b/.github/workflows/ci-code-check.yml index 75deb399d2f2..af50b3230ba7 100644 --- a/.github/workflows/ci-code-check.yml +++ b/.github/workflows/ci-code-check.yml @@ -35,6 +35,7 @@ jobs: node-version: ${{ inputs.node-version }} cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # - name: Free disk space # run: | @@ -42,7 +43,7 @@ jobs: # docker rmi $(docker image ls -aq) # df -h - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Cache TypeCheck uses: actions/cache@v3 diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 920aea0aa308..e6c02b7b6417 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -99,6 +99,15 @@ jobs: job_summary: true comment_on_pr: false + - name: Setup kernel limits + run: | + sudo sysctl -w net.ipv4.ip_local_port_range="500 65535" + sudo sysctl -w net.ipv4.tcp_mem="383865 511820 2303190" + + echo fs.file-max=20000500 | sudo tee -a /etc/sysctl.conf + echo fs.nr_open=20000500 | sudo tee -a /etc/sysctl.conf + sudo sysctl -p + - name: Login to GitHub Container Registry if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: docker/login-action@v2 @@ -121,16 +130,24 @@ jobs: node-version: ${{ inputs.node-version }} cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - uses: rharkor/caching-for-turbo@v1.5 + - run: yarn build # if we are testing a PR from a fork, we need to build the docker image at this point - uses: ./.github/actions/build-docker - if: github.event.pull_request.head.repo.full_name != github.repository + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} node-version: ${{ inputs.node-version }} - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + # we already called the turbo cache at this point, so it should be false + turbo-cache: false + # the same reason we need to rebuild the docker image at this point is the reason we dont want to publish it + publish-image: false + setup: false + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Start httpbin container and wait for it to be ready if: inputs.type == 'api' @@ -150,8 +167,6 @@ jobs: exit 1 fi - - run: yarn build - - name: Prepare code coverage directory if: inputs.release == 'ee' run: | @@ -212,6 +227,8 @@ jobs: sleep 10 done; + - name: Remove unused Docker images + run: docker system prune -af - name: E2E Test API if: inputs.type == 'api' working-directory: ./apps/meteor @@ -272,9 +289,9 @@ jobs: - name: Store playwright test trace if: inputs.type == 'ui' && always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: playwright-test-trace${{ inputs.release }} + name: playwright-test-trace-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: ./apps/meteor/tests/e2e/.playwright* - name: Show server logs if E2E test failed @@ -305,14 +322,14 @@ jobs: - name: Store e2e-api-ee-coverage if: inputs.type == 'api' && inputs.release == 'ee' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: e2e-api-ee-coverage + name: e2e-api-ee-coverage-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: /tmp/coverage - name: Store e2e-ee-coverage if: inputs.type == 'ui' && inputs.release == 'ee' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: e2e-ee-coverage + name: e2e-ee-coverage-${{ matrix.mongodb-version }}-${{ matrix.shard }} path: ./apps/meteor/coverage* diff --git a/.github/workflows/ci-test-unit.yml b/.github/workflows/ci-test-unit.yml index bfb22ffa4e73..840808ff5e31 100644 --- a/.github/workflows/ci-test-unit.yml +++ b/.github/workflows/ci-test-unit.yml @@ -39,8 +39,9 @@ jobs: node-version: ${{ inputs.node-version }} cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Unit Test run: yarn testunit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 411aa2cc5b1a..514dd6d1c518 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,6 +152,7 @@ jobs: node-version: ${{ needs.release-versions.outputs.node-version }} cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Cache vite uses: actions/cache@v3 @@ -161,7 +162,7 @@ jobs: restore-keys: | vite-local-cache-${{ runner.os }}- - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Build Rocket.Chat Packages run: yarn build @@ -253,6 +254,7 @@ jobs: node-version: ${{ needs.release-versions.outputs.node-version }} platform: ${{ matrix.platform }} build-containers: ${{ matrix.platform == 'alpine' && 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service queue-worker-service omnichannel-transcript-service' || '' }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} build-gh-docker: name: đŸšĸ Build Docker Images for Production @@ -280,6 +282,7 @@ jobs: node-version: ${{ needs.release-versions.outputs.node-version }} platform: ${{ matrix.platform }} build-containers: ${{ matrix.platform == 'alpine' && 'authorization-service account-service ddp-streamer-service presence-service stream-hub-service queue-worker-service omnichannel-transcript-service' || '' }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Rename official Docker tag to GitHub Container Registry if: matrix.platform == 'official' @@ -492,7 +495,7 @@ jobs: ref: ${{ github.ref }} - name: Restore build - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build path: /tmp/build @@ -540,7 +543,7 @@ jobs: - uses: actions/checkout@v4 - name: Restore build - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: build path: /tmp/build @@ -560,6 +563,7 @@ jobs: release: preview username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} docker-image-publish: name: 🚀 Publish Docker Image (main) @@ -576,13 +580,13 @@ jobs: steps: - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ secrets.CR_USER }} @@ -683,13 +687,13 @@ jobs: steps: - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ secrets.CR_USER }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 483b404a6dc8..202a02dd7785 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,7 +26,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages with: languages: javascript @@ -34,7 +34,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -48,4 +48,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/new-release.yml b/.github/workflows/new-release.yml index f10578d5879f..b2eae5d90b92 100644 --- a/.github/workflows/new-release.yml +++ b/.github/workflows/new-release.yml @@ -37,8 +37,9 @@ jobs: node-version: 14.21.3 cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Build packages run: yarn build diff --git a/.github/workflows/pr-title-checker.yml b/.github/workflows/pr-title-checker.yml index bc9d1f042d58..d8f6db97c455 100644 --- a/.github/workflows/pr-title-checker.yml +++ b/.github/workflows/pr-title-checker.yml @@ -12,6 +12,6 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: thehanimo/pr-title-checker@v1.4.1 + - uses: thehanimo/pr-title-checker@v1.4.2 with: GITHUB_TOKEN: ${{ secrets.RC_TITLE_CHECKER }} diff --git a/.github/workflows/pr-update-description.yml b/.github/workflows/pr-update-description.yml index 71b4ffeda801..084f2a383480 100644 --- a/.github/workflows/pr-update-description.yml +++ b/.github/workflows/pr-update-description.yml @@ -24,8 +24,9 @@ jobs: node-version: 14.21.3 cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Build packages run: yarn build @@ -36,4 +37,3 @@ jobs: action: update-pr-description env: GITHUB_TOKEN: ${{ secrets.CI_PAT }} - diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index e133a3153722..3f2067ac7ec3 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -27,8 +27,9 @@ jobs: node-version: 14.21.3 cache-modules: true install: true + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 + - uses: rharkor/caching-for-turbo@v1.5 - name: Build packages run: yarn build diff --git a/.github/workflows/update-version-durability.yml b/.github/workflows/update-version-durability.yml index e52b4870b369..90c835577dc1 100644 --- a/.github/workflows/update-version-durability.yml +++ b/.github/workflows/update-version-durability.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - name: Use Node.js - uses: actions/setup-node@v3.7.0 + uses: actions/setup-node@v4.0.3 with: node-version: '20.15.1' diff --git a/.gitignore b/.gitignore index fcf2b8cd07c7..4e6e4bb29da9 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ yarn-error.log* *.sublime-workspace **/.vim/ + +data/ +registration.yaml diff --git a/_templates/package/new/jest.config.ts.t b/_templates/package/new/jest.config.ts.t new file mode 100644 index 000000000000..c18c8ae02465 --- /dev/null +++ b/_templates/package/new/jest.config.ts.t @@ -0,0 +1,6 @@ +import server from '@rocket.chat/jest-presets/server'; +import type { Config } from 'jest'; + +export default { + preset: server.preset, +} satisfies Config; diff --git a/_templates/package/new/package.json.ejs.t b/_templates/package/new/package.json.ejs.t index 950e5cb2bf62..6bee52f55927 100644 --- a/_templates/package/new/package.json.ejs.t +++ b/_templates/package/new/package.json.ejs.t @@ -7,11 +7,11 @@ to: packages/<%= name %>/package.json "version": "0.0.1", "private": true, "devDependencies": { - "@types/jest": "~29.5.3", + "@rocket.chat/jest-presets": "workspace:~", + "@types/jest": "~29.5.12", "eslint": "~8.45.0", - "jest": "~29.6.1", - "ts-jest": "~29.0.5", - "typescript": "~5.1.6" + "jest": "~29.7.0", + "typescript": "~5.3.3" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/_templates/package/new/tsconfig.json.ejs.t b/_templates/package/new/tsconfig.json.ejs.t index 3e192c674d1b..399544502ed0 100644 --- a/_templates/package/new/tsconfig.json.ejs.t +++ b/_templates/package/new/tsconfig.json.ejs.t @@ -2,10 +2,10 @@ to: packages/<%= name %>/tsconfig.json --- { - "extends": "../../tsconfig.base.client.json", + "extends": "../../tsconfig.base.server.json", "compilerOptions": { "rootDir": "./src", "outDir": "./dist" }, - "include": ["./src/**/*"] + "include": ["./src/**/*"], } diff --git a/apps/meteor/.meteorMocks/index.ts b/apps/meteor/.meteorMocks/index.ts deleted file mode 100644 index e70ffa7f7c46..000000000000 --- a/apps/meteor/.meteorMocks/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import sinon from 'sinon'; - -export const Meteor = { - loginWithSamlToken: sinon.stub(), -}; diff --git a/apps/meteor/.mocharc.client.js b/apps/meteor/.mocharc.client.js deleted file mode 100644 index cf339a420378..000000000000 --- a/apps/meteor/.mocharc.client.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -/** - * Mocha configuration for client-side unit and integration tests. - */ - -const base = require('./.mocharc.base.json'); - -/** - * Mocha will run `ts-node` without doing type checking to speed-up the tests. It should be fine as `npm run typecheck` - * covers test files too. - */ - -Object.assign( - process.env, - { - TS_NODE_FILES: true, - TS_NODE_TRANSPILE_ONLY: true, - }, - process.env, -); - -module.exports = { - ...base, // see https://github.com/mochajs/mocha/issues/3916 - require: [ - ...base.require, - './tests/setup/registerWebApiMocks.ts', - './tests/setup/hoistedReact.ts', - './tests/setup/cleanupTestingLibrary.ts', - ], - reporter: 'dot', - timeout: 5000, - exit: false, - slow: 200, - spec: [ - 'tests/unit/client/sidebar/**/*.spec.{ts,tsx}', - 'tests/unit/client/components/**/*.spec.{ts,tsx}', - 'tests/unit/client/lib/**/*.spec.{ts,tsx}', - 'tests/unit/lib/**/*.tests.ts', - 'tests/unit/client/**/*.test.ts', - ], -}; diff --git a/apps/meteor/CHANGELOG.md b/apps/meteor/CHANGELOG.md index 75ffb7f02d7a..f466c34da838 100644 --- a/apps/meteor/CHANGELOG.md +++ b/apps/meteor/CHANGELOG.md @@ -1,5 +1,516 @@ # @rocket.chat/meteor +## 6.11.0 + +### Minor Changes + +- ([#32498](https://github.com/RocketChat/Rocket.Chat/pull/32498)) Created a `transferChat` Livechat API endpoint for transferring chats programmatically, the endpoint has all the limitations & permissions required that transferring via UI has + +- ([#32792](https://github.com/RocketChat/Rocket.Chat/pull/32792)) Allows admins to customize the `Subject` field of Omnichannel email transcripts via setting. By passing a value to the setting `Custom email subject for transcript`, system will use it as the `Subject` field, unless a custom subject is passed when requesting a transcript. If there's no custom subject and setting value is empty, the current default value will be used + +- ([#32739](https://github.com/RocketChat/Rocket.Chat/pull/32739)) Fixed an issue where FCM actions did not respect environment's proxy settings + +- ([#32706](https://github.com/RocketChat/Rocket.Chat/pull/32706)) Added the possibility for apps to remove users from a room + +- ([#32517](https://github.com/RocketChat/Rocket.Chat/pull/32517)) Feature Preview: New Navigation - `Header` and `Contextualbar` size improvements consistent with the new global `NavBar` + +- ([#32493](https://github.com/RocketChat/Rocket.Chat/pull/32493)) Fixed Livechat rooms being displayed in the Engagement Dashboard's "Channels" tab + +- ([#32742](https://github.com/RocketChat/Rocket.Chat/pull/32742)) Fixed an issue where adding `OVERWRITE_SETTING_` for any setting wasn't immediately taking effect sometimes, and needed a server restart to reflect. + +- ([#32752](https://github.com/RocketChat/Rocket.Chat/pull/32752)) Added system messages support for Omnichannel PDF transcripts and email transcripts. Currently these transcripts don't render system messages and is shown as an empty message in PDF/email. This PR adds this support for all valid livechat system messages. + + Also added a new setting under transcripts, to toggle the inclusion of system messages in email and PDF transcripts. + +- ([#32793](https://github.com/RocketChat/Rocket.Chat/pull/32793)) New Feature: Video Conference Persistent Chat. + This feature provides a discussion id for conference provider apps to store the chat messages exchanged during the conferences, so that those users may then access those messages again at any time through Rocket.Chat. +- ([#32176](https://github.com/RocketChat/Rocket.Chat/pull/32176)) Added a method to the Apps-Engine that allows apps to read multiple messages from a room + +- ([#32493](https://github.com/RocketChat/Rocket.Chat/pull/32493)) Improved Engagement Dashboard's "Channels" tab performance by not returning rooms that had no activity in the analyzed period + +- ([#32024](https://github.com/RocketChat/Rocket.Chat/pull/32024)) Implemented a new tab to the users page called 'Active', this tab lists all users who have logged in for the first time and are active. + +- ([#32744](https://github.com/RocketChat/Rocket.Chat/pull/32744)) Added account setting `Accounts_Default_User_Preferences_sidebarSectionsOrder` to allow users to reorganize sidebar sections + +- ([#32820](https://github.com/RocketChat/Rocket.Chat/pull/32820)) Added a new setting `Livechat_transcript_send_always` that allows admins to decide if email transcript should be sent all the times when a conversation is closed. This setting bypasses agent's preferences. For this setting to work, `Livechat_enable_transcript` should be off, meaning that visitors will no longer receive the option to decide if they want a transcript or not. + +- ([#32724](https://github.com/RocketChat/Rocket.Chat/pull/32724)) Extended apps-engine events for users leaving a room to also fire when being removed by another user. Also added the triggering user's information to the event's context payload. + +- ([#32777](https://github.com/RocketChat/Rocket.Chat/pull/32777)) Added handling of attachments in Omnichannel email transcripts. Earlier attachments were being skipped and were being shown as empty space, now it should render the image attachments and should show relevant error message for unsupported attachments. + +- ([#32800](https://github.com/RocketChat/Rocket.Chat/pull/32800)) Added the ability to filter chats by `queued` on the Current Chats Omnichannel page + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- Bump @rocket.chat/meteor version. + +- ([#32679](https://github.com/RocketChat/Rocket.Chat/pull/32679)) Fix validations from "UiKit" modal component + +- ([#32730](https://github.com/RocketChat/Rocket.Chat/pull/32730)) Fixed issue in Marketplace that caused a subscription app to show incorrect modals when subscribing + +- ([#32628](https://github.com/RocketChat/Rocket.Chat/pull/32628)) Fixed SAML users' full names being updated on login regardless of the "Overwrite user fullname (use idp attribute)" setting + +- ([#32692](https://github.com/RocketChat/Rocket.Chat/pull/32692)) Fixed an issue that caused the widget to set the wrong department when using the setDepartment Livechat api endpoint in conjunction with a Livechat Trigger + +- ([#32527](https://github.com/RocketChat/Rocket.Chat/pull/32527)) Fixed an inconsistent evaluation of the `Accounts_LoginExpiration` setting over the codebase. In some places, it was being used as milliseconds while in others as days. Invalid values produced different results. A helper function was created to centralize the setting validation and the proper value being returned to avoid edge cases. + Negative values may be saved on the settings UI panel but the code will interpret any negative, NaN or 0 value to the default expiration which is 90 days. +- ([#32626](https://github.com/RocketChat/Rocket.Chat/pull/32626)) livechat `setDepartment` livechat api fixes: + - Changing department didn't reflect on the registration form in real time + - Changing the department mid conversation didn't transfer the chat + - Depending on the state of the department, it couldn't be set as default +- ([#32810](https://github.com/RocketChat/Rocket.Chat/pull/32810)) Fixed issue where bad word filtering was not working in the UI for messages + +- ([#32707](https://github.com/RocketChat/Rocket.Chat/pull/32707)) Fixed issue with livechat agents not being able to leave omnichannel rooms if joining after a room has been closed by the visitor (due to race conditions) + +- ([#32837](https://github.com/RocketChat/Rocket.Chat/pull/32837)) Fixed an issue where non-encrypted attachments were not being downloaded + +- ([#32861](https://github.com/RocketChat/Rocket.Chat/pull/32861)) fixed the contextual bar closing when editing thread messages instead of cancelling the message edit + +- ([#32713](https://github.com/RocketChat/Rocket.Chat/pull/32713)) Fixed the disappearance of some settings after navigation under network latency. + +- ([#32592](https://github.com/RocketChat/Rocket.Chat/pull/32592)) Fixes Missing line breaks on Omnichannel Room Info Panel + +- ([#32807](https://github.com/RocketChat/Rocket.Chat/pull/32807)) Fixed web client crashing on Firefox private window. Firefox disables access to service workers inside private windows. Rocket.Chat needs service workers to process E2EE encrypted files on rooms. These types of files won't be available inside private windows, but the rest of E2EE encrypted features should work normally + +- ([#32864](https://github.com/RocketChat/Rocket.Chat/pull/32864)) fixed an issue in the "Create discussion" form, that would have the "Create" action button disabled even though the form is prefilled when opening it from the message action + +- ([#32691](https://github.com/RocketChat/Rocket.Chat/pull/32691)) Removed 'Hide' option in the room menu for Omnichannel conversations. + +- ([#32445](https://github.com/RocketChat/Rocket.Chat/pull/32445)) Fixed LDAP rooms, teams and roles syncs not being triggered on login even when the "Update User Data on Login" setting is enabled + +- ([#32328](https://github.com/RocketChat/Rocket.Chat/pull/32328)) Allow customFields on livechat creation bridge + +- ([#32803](https://github.com/RocketChat/Rocket.Chat/pull/32803)) Fixed "Copy link" message action enabled in Starred and Pinned list for End to End Encrypted channels, this action is disabled now + +- ([#32769](https://github.com/RocketChat/Rocket.Chat/pull/32769)) Fixed issue that caused unintentional clicks when scrolling the channels sidebar on safari/chrome in iOS + +- ([#32857](https://github.com/RocketChat/Rocket.Chat/pull/32857)) Fixed some anomalies related to disabled E2EE rooms. Earlier there are some weird issues with disabled E2EE rooms, this PR fixes these anomalies. + +- ([#32765](https://github.com/RocketChat/Rocket.Chat/pull/32765)) Fixed an issue that prevented the option to start a discussion from being shown on the message actions + +- ([#32671](https://github.com/RocketChat/Rocket.Chat/pull/32671)) Fix show correct user roles after updating user roles on admin edit user panel. + +- ([#32482](https://github.com/RocketChat/Rocket.Chat/pull/32482)) Fixed an issue with blocked login when dismissed 2FA modal by clicking outside of it or pressing the escape key + +- ([#32804](https://github.com/RocketChat/Rocket.Chat/pull/32804)) Fixes an issue not displaying all groups in settings list + +- ([#32815](https://github.com/RocketChat/Rocket.Chat/pull/32815)) Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) + +- ([#32632](https://github.com/RocketChat/Rocket.Chat/pull/32632)) Improving UX by change the position of room info actions buttons and menu order to avoid missclick in destructive actions. + +- ([#32752](https://github.com/RocketChat/Rocket.Chat/pull/32752)) Reduced time on generation of PDF transcripts. Earlier Rocket.Chat was fetching the required translations everytime a PDF transcript was requested, this process was async and was being unnecessarily being performed on every pdf transcript request. This PR improves this and now the translations are loaded at the start and kept in memory to process further pdf transcripts requests. This reduces the time of asynchronously fetching translations again and again. + +- ([#32719](https://github.com/RocketChat/Rocket.Chat/pull/32719)) Added the `user` param to apps-engine update method call, allowing apps' new `onUpdate` hook to know who triggered the update. + +-
Updated dependencies [88e5219bd2, b4bbcbfc9a, 8fc6ca8b4e, 25da5280a5, 1b7b1161cf, 439faa87d3, 03c8b066f9, 2d89a0c448, 439faa87d3, 24f7df4894, 3ffe4a2944, 3b4b19cfc5, 4e8aa575a6, 03c8b066f9, 264d7d5496, b8e5887fb9]: + + - @rocket.chat/fuselage-ui-kit@9.0.0 + - @rocket.chat/i18n@0.6.0 + - @rocket.chat/tools@0.2.2 + - @rocket.chat/ui-client@9.0.0 + - @rocket.chat/model-typings@0.6.0 + - @rocket.chat/omnichannel-services@0.3.0 + - @rocket.chat/pdf-worker@0.2.0 + - @rocket.chat/core-services@0.5.0 + - @rocket.chat/ui-video-conf@9.0.0 + - @rocket.chat/core-typings@6.11.0 + - @rocket.chat/ui-contexts@9.0.0 + - @rocket.chat/models@0.2.0 + - @rocket.chat/ui-kit@0.36.0 + - @rocket.chat/web-ui-registration@9.0.0 + - @rocket.chat/rest-typings@6.11.0 + - @rocket.chat/apps@0.1.3 + - @rocket.chat/presence@0.2.3 + - @rocket.chat/gazzodown@9.0.0 + - @rocket.chat/api-client@0.2.3 + - @rocket.chat/license@0.2.3 + - @rocket.chat/cron@0.1.3 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0 + - @rocket.chat/instance-status@0.1.3 + - @rocket.chat/server-cloud-communication@0.0.2 +
+ +## 6.11.0-rc.6 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.0-rc.6 + - @rocket.chat/rest-typings@6.11.0-rc.6 + - @rocket.chat/api-client@0.2.3-rc.6 + - @rocket.chat/license@0.2.3-rc.6 + - @rocket.chat/omnichannel-services@0.3.0-rc.6 + - @rocket.chat/pdf-worker@0.2.0-rc.6 + - @rocket.chat/presence@0.2.3-rc.6 + - @rocket.chat/apps@0.1.3-rc.6 + - @rocket.chat/core-services@0.5.0-rc.6 + - @rocket.chat/cron@0.1.3-rc.6 + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.6 + - @rocket.chat/gazzodown@9.0.0-rc.6 + - @rocket.chat/model-typings@0.6.0-rc.6 + - @rocket.chat/ui-contexts@9.0.0-rc.6 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.0-rc.6 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.6 + - @rocket.chat/ui-client@9.0.0-rc.6 + - @rocket.chat/ui-video-conf@9.0.0-rc.6 + - @rocket.chat/web-ui-registration@9.0.0-rc.6 + - @rocket.chat/instance-status@0.1.3-rc.6 +
+ +## 6.11.0-rc.5 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.0-rc.5 + - @rocket.chat/rest-typings@6.11.0-rc.5 + - @rocket.chat/api-client@0.2.3-rc.5 + - @rocket.chat/license@0.2.3-rc.5 + - @rocket.chat/omnichannel-services@0.3.0-rc.5 + - @rocket.chat/pdf-worker@0.2.0-rc.5 + - @rocket.chat/presence@0.2.3-rc.5 + - @rocket.chat/apps@0.1.3-rc.5 + - @rocket.chat/core-services@0.5.0-rc.5 + - @rocket.chat/cron@0.1.3-rc.5 + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.5 + - @rocket.chat/gazzodown@9.0.0-rc.5 + - @rocket.chat/model-typings@0.6.0-rc.5 + - @rocket.chat/ui-contexts@9.0.0-rc.5 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.0-rc.5 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.5 + - @rocket.chat/ui-client@9.0.0-rc.5 + - @rocket.chat/ui-video-conf@9.0.0-rc.5 + - @rocket.chat/web-ui-registration@9.0.0-rc.5 + - @rocket.chat/instance-status@0.1.3-rc.5 +
+ +## 6.11.0-rc.4 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.0-rc.4 + - @rocket.chat/rest-typings@6.11.0-rc.4 + - @rocket.chat/api-client@0.2.3-rc.4 + - @rocket.chat/license@0.2.3-rc.4 + - @rocket.chat/omnichannel-services@0.3.0-rc.4 + - @rocket.chat/pdf-worker@0.2.0-rc.4 + - @rocket.chat/presence@0.2.3-rc.4 + - @rocket.chat/apps@0.1.3-rc.4 + - @rocket.chat/core-services@0.5.0-rc.4 + - @rocket.chat/cron@0.1.3-rc.4 + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.4 + - @rocket.chat/gazzodown@9.0.0-rc.4 + - @rocket.chat/model-typings@0.6.0-rc.4 + - @rocket.chat/ui-contexts@9.0.0-rc.4 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.0-rc.4 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.4 + - @rocket.chat/ui-client@9.0.0-rc.4 + - @rocket.chat/ui-video-conf@9.0.0-rc.4 + - @rocket.chat/web-ui-registration@9.0.0-rc.4 + - @rocket.chat/instance-status@0.1.3-rc.4 +
+ +## 6.11.0-rc.3 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.0-rc.3 + - @rocket.chat/rest-typings@6.11.0-rc.3 + - @rocket.chat/api-client@0.2.3-rc.3 + - @rocket.chat/license@0.2.3-rc.3 + - @rocket.chat/omnichannel-services@0.3.0-rc.3 + - @rocket.chat/pdf-worker@0.2.0-rc.3 + - @rocket.chat/presence@0.2.3-rc.3 + - @rocket.chat/apps@0.1.3-rc.3 + - @rocket.chat/core-services@0.5.0-rc.3 + - @rocket.chat/cron@0.1.3-rc.3 + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.3 + - @rocket.chat/gazzodown@9.0.0-rc.3 + - @rocket.chat/model-typings@0.6.0-rc.3 + - @rocket.chat/ui-contexts@9.0.0-rc.3 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.0-rc.3 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.3 + - @rocket.chat/ui-client@9.0.0-rc.3 + - @rocket.chat/ui-video-conf@9.0.0-rc.3 + - @rocket.chat/web-ui-registration@9.0.0-rc.3 + - @rocket.chat/instance-status@0.1.3-rc.3 +
+ +## 6.11.0-rc.2 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.0-rc.2 + - @rocket.chat/rest-typings@6.11.0-rc.2 + - @rocket.chat/api-client@0.2.3-rc.2 + - @rocket.chat/license@0.2.3-rc.2 + - @rocket.chat/omnichannel-services@0.3.0-rc.2 + - @rocket.chat/pdf-worker@0.2.0-rc.2 + - @rocket.chat/presence@0.2.3-rc.2 + - @rocket.chat/apps@0.1.3-rc.2 + - @rocket.chat/core-services@0.5.0-rc.2 + - @rocket.chat/cron@0.1.3-rc.2 + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.2 + - @rocket.chat/gazzodown@9.0.0-rc.2 + - @rocket.chat/model-typings@0.6.0-rc.2 + - @rocket.chat/ui-contexts@9.0.0-rc.2 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.0-rc.2 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.2 + - @rocket.chat/ui-client@9.0.0-rc.2 + - @rocket.chat/ui-video-conf@9.0.0-rc.2 + - @rocket.chat/web-ui-registration@9.0.0-rc.2 + - @rocket.chat/instance-status@0.1.3-rc.2 +
+ +## 6.11.0-rc.1 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +-
Updated dependencies []: + + - @rocket.chat/core-typings@6.11.0-rc.1 + - @rocket.chat/rest-typings@6.11.0-rc.1 + - @rocket.chat/api-client@0.2.2-rc.1 + - @rocket.chat/license@0.2.2-rc.1 + - @rocket.chat/omnichannel-services@0.3.0-rc.1 + - @rocket.chat/pdf-worker@0.2.0-rc.1 + - @rocket.chat/presence@0.2.2-rc.1 + - @rocket.chat/apps@0.1.2-rc.1 + - @rocket.chat/core-services@0.5.0-rc.1 + - @rocket.chat/cron@0.1.2-rc.1 + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.1 + - @rocket.chat/gazzodown@9.0.0-rc.1 + - @rocket.chat/model-typings@0.6.0-rc.1 + - @rocket.chat/ui-contexts@9.0.0-rc.1 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.2.0-rc.1 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.1 + - @rocket.chat/ui-client@9.0.0-rc.1 + - @rocket.chat/ui-video-conf@9.0.0-rc.1 + - @rocket.chat/web-ui-registration@9.0.0-rc.1 + - @rocket.chat/instance-status@0.1.2-rc.1 +
+ +## 6.11.0-rc.0 + +### Minor Changes + +- ([#32498](https://github.com/RocketChat/Rocket.Chat/pull/32498)) Created a `transferChat` Livechat API endpoint for transferring chats programmatically, the endpoint has all the limitations & permissions required that transferring via UI has + +- ([#32792](https://github.com/RocketChat/Rocket.Chat/pull/32792)) Allows admins to customize the `Subject` field of Omnichannel email transcripts via setting. By passing a value to the setting `Custom email subject for transcript`, system will use it as the `Subject` field, unless a custom subject is passed when requesting a transcript. If there's no custom subject and setting value is empty, the current default value will be used + +- ([#32739](https://github.com/RocketChat/Rocket.Chat/pull/32739)) Fixed an issue where FCM actions did not respect environment's proxy settings + +- ([#32570](https://github.com/RocketChat/Rocket.Chat/pull/32570)) Login services button was not respecting the button color and text color settings. Implemented a fix to respect these settings and change the button colors accordingly. + + Added a warning on all settings which allow admins to change OAuth button colors, so that they can be alerted about WCAG (Web Content Accessibility Guidelines) compliance. + +- ([#32706](https://github.com/RocketChat/Rocket.Chat/pull/32706)) Added the possibility for apps to remove users from a room + +- ([#32517](https://github.com/RocketChat/Rocket.Chat/pull/32517)) Feature Preview: New Navigation - `Header` and `Contextualbar` size improvements consistent with the new global `NavBar` + +- ([#32493](https://github.com/RocketChat/Rocket.Chat/pull/32493)) Fixed Livechat rooms being displayed in the Engagement Dashboard's "Channels" tab + +- ([#32742](https://github.com/RocketChat/Rocket.Chat/pull/32742)) Fixed an issue where adding `OVERWRITE_SETTING_` for any setting wasn't immediately taking effect sometimes, and needed a server restart to reflect. + +- ([#32752](https://github.com/RocketChat/Rocket.Chat/pull/32752)) Added system messages support for Omnichannel PDF transcripts and email transcripts. Currently these transcripts don't render system messages and is shown as an empty message in PDF/email. This PR adds this support for all valid livechat system messages. + + Also added a new setting under transcripts, to toggle the inclusion of system messages in email and PDF transcripts. + +- ([#32793](https://github.com/RocketChat/Rocket.Chat/pull/32793)) New Feature: Video Conference Persistent Chat. + This feature provides a discussion id for conference provider apps to store the chat messages exchanged during the conferences, so that those users may then access those messages again at any time through Rocket.Chat. +- ([#32176](https://github.com/RocketChat/Rocket.Chat/pull/32176)) Added a method to the Apps-Engine that allows apps to read multiple messages from a room + +- ([#32493](https://github.com/RocketChat/Rocket.Chat/pull/32493)) Improved Engagement Dashboard's "Channels" tab performance by not returning rooms that had no activity in the analyzed period + +- ([#32024](https://github.com/RocketChat/Rocket.Chat/pull/32024)) Implemented a new tab to the users page called 'Active', this tab lists all users who have logged in for the first time and are active. + +- ([#32744](https://github.com/RocketChat/Rocket.Chat/pull/32744)) Added account setting `Accounts_Default_User_Preferences_sidebarSectionsOrder` to allow users to reorganize sidebar sections + +- ([#32820](https://github.com/RocketChat/Rocket.Chat/pull/32820)) Added a new setting `Livechat_transcript_send_always` that allows admins to decide if email transcript should be sent all the times when a conversation is closed. This setting bypasses agent's preferences. For this setting to work, `Livechat_enable_transcript` should be off, meaning that visitors will no longer receive the option to decide if they want a transcript or not. + +- ([#32724](https://github.com/RocketChat/Rocket.Chat/pull/32724)) Extended apps-engine events for users leaving a room to also fire when being removed by another user. Also added the triggering user's information to the event's context payload. + +- ([#32777](https://github.com/RocketChat/Rocket.Chat/pull/32777)) Added handling of attachments in Omnichannel email transcripts. Earlier attachments were being skipped and were being shown as empty space, now it should render the image attachments and should show relevant error message for unsupported attachments. + +- ([#32800](https://github.com/RocketChat/Rocket.Chat/pull/32800)) Added the ability to filter chats by `queued` on the Current Chats Omnichannel page + +### Patch Changes + +- ([#32679](https://github.com/RocketChat/Rocket.Chat/pull/32679)) Fix validations from "UiKit" modal component + +- ([#32730](https://github.com/RocketChat/Rocket.Chat/pull/32730)) Fixed issue in Marketplace that caused a subscription app to show incorrect modals when subscribing + +- ([#32628](https://github.com/RocketChat/Rocket.Chat/pull/32628)) Fixed SAML users' full names being updated on login regardless of the "Overwrite user fullname (use idp attribute)" setting + +- ([#32692](https://github.com/RocketChat/Rocket.Chat/pull/32692)) Fixed an issue that caused the widget to set the wrong department when using the setDepartment Livechat api endpoint in conjunction with a Livechat Trigger + +- ([#32527](https://github.com/RocketChat/Rocket.Chat/pull/32527)) Fixed an inconsistent evaluation of the `Accounts_LoginExpiration` setting over the codebase. In some places, it was being used as milliseconds while in others as days. Invalid values produced different results. A helper function was created to centralize the setting validation and the proper value being returned to avoid edge cases. + Negative values may be saved on the settings UI panel but the code will interpret any negative, NaN or 0 value to the default expiration which is 90 days. +- ([#32626](https://github.com/RocketChat/Rocket.Chat/pull/32626)) livechat `setDepartment` livechat api fixes: + - Changing department didn't reflect on the registration form in real time + - Changing the department mid conversation didn't transfer the chat + - Depending on the state of the department, it couldn't be set as default +- ([#32810](https://github.com/RocketChat/Rocket.Chat/pull/32810)) Fixed issue where bad word filtering was not working in the UI for messages + +- ([#32707](https://github.com/RocketChat/Rocket.Chat/pull/32707)) Fixed issue with livechat agents not being able to leave omnichannel rooms if joining after a room has been closed by the visitor (due to race conditions) + +- ([#32837](https://github.com/RocketChat/Rocket.Chat/pull/32837)) Fixed an issue where non-encrypted attachments were not being downloaded + +- ([#32861](https://github.com/RocketChat/Rocket.Chat/pull/32861)) fixed the contextual bar closing when editing thread messages instead of cancelling the message edit + +- ([#32713](https://github.com/RocketChat/Rocket.Chat/pull/32713)) Fixed the disappearance of some settings after navigation under network latency. + +- ([#32592](https://github.com/RocketChat/Rocket.Chat/pull/32592)) Fixes Missing line breaks on Omnichannel Room Info Panel + +- ([#32807](https://github.com/RocketChat/Rocket.Chat/pull/32807)) Fixed web client crashing on Firefox private window. Firefox disables access to service workers inside private windows. Rocket.Chat needs service workers to process E2EE encrypted files on rooms. These types of files won't be available inside private windows, but the rest of E2EE encrypted features should work normally + +- ([#32864](https://github.com/RocketChat/Rocket.Chat/pull/32864)) fixed an issue in the "Create discussion" form, that would have the "Create" action button disabled even though the form is prefilled when opening it from the message action + +- ([#32691](https://github.com/RocketChat/Rocket.Chat/pull/32691)) Removed 'Hide' option in the room menu for Omnichannel conversations. + +- ([#32445](https://github.com/RocketChat/Rocket.Chat/pull/32445)) Fixed LDAP rooms, teams and roles syncs not being triggered on login even when the "Update User Data on Login" setting is enabled + +- ([#32328](https://github.com/RocketChat/Rocket.Chat/pull/32328)) Allow customFields on livechat creation bridge + +- ([#32803](https://github.com/RocketChat/Rocket.Chat/pull/32803)) Fixed "Copy link" message action enabled in Starred and Pinned list for End to End Encrypted channels, this action is disabled now + +- ([#32769](https://github.com/RocketChat/Rocket.Chat/pull/32769)) Fixed issue that caused unintentional clicks when scrolling the channels sidebar on safari/chrome in iOS + +- ([#32857](https://github.com/RocketChat/Rocket.Chat/pull/32857)) Fixed some anomalies related to disabled E2EE rooms. Earlier there are some weird issues with disabled E2EE rooms, this PR fixes these anomalies. + +- ([#32765](https://github.com/RocketChat/Rocket.Chat/pull/32765)) Fixed an issue that prevented the option to start a discussion from being shown on the message actions + +- ([#32671](https://github.com/RocketChat/Rocket.Chat/pull/32671)) Fix show correct user roles after updating user roles on admin edit user panel. + +- ([#32482](https://github.com/RocketChat/Rocket.Chat/pull/32482)) Fixed an issue with blocked login when dismissed 2FA modal by clicking outside of it or pressing the escape key + +- ([#32804](https://github.com/RocketChat/Rocket.Chat/pull/32804)) Fixes an issue not displaying all groups in settings list + +- ([#32815](https://github.com/RocketChat/Rocket.Chat/pull/32815)) Security Hotfix (https://docs.rocket.chat/guides/security/security-updates) + +- ([#32632](https://github.com/RocketChat/Rocket.Chat/pull/32632)) Improving UX by change the position of room info actions buttons and menu order to avoid missclick in destructive actions. + +- ([#32752](https://github.com/RocketChat/Rocket.Chat/pull/32752)) Reduced time on generation of PDF transcripts. Earlier Rocket.Chat was fetching the required translations everytime a PDF transcript was requested, this process was async and was being unnecessarily being performed on every pdf transcript request. This PR improves this and now the translations are loaded at the start and kept in memory to process further pdf transcripts requests. This reduces the time of asynchronously fetching translations again and again. + +- ([#32719](https://github.com/RocketChat/Rocket.Chat/pull/32719)) Added the `user` param to apps-engine update method call, allowing apps' new `onUpdate` hook to know who triggered the update. + +-
Updated dependencies [88e5219bd2, b4bbcbfc9a, 8fc6ca8b4e, 15664127be, 25da5280a5, 1b7b1161cf, 439faa87d3, 03c8b066f9, 2d89a0c448, 439faa87d3, 24f7df4894, 3ffe4a2944, 3b4b19cfc5, 4e8aa575a6, 03c8b066f9, 264d7d5496, b8e5887fb9]: + + - @rocket.chat/fuselage-ui-kit@9.0.0-rc.0 + - @rocket.chat/i18n@0.6.0-rc.0 + - @rocket.chat/tools@0.2.2-rc.0 + - @rocket.chat/web-ui-registration@9.0.0-rc.0 + - @rocket.chat/ui-client@9.0.0-rc.0 + - @rocket.chat/model-typings@0.6.0-rc.0 + - @rocket.chat/omnichannel-services@0.3.0-rc.0 + - @rocket.chat/pdf-worker@0.2.0-rc.0 + - @rocket.chat/core-services@0.5.0-rc.0 + - @rocket.chat/ui-video-conf@9.0.0-rc.0 + - @rocket.chat/core-typings@6.11.0-rc.0 + - @rocket.chat/ui-contexts@9.0.0-rc.0 + - @rocket.chat/models@0.2.0-rc.0 + - @rocket.chat/ui-kit@0.36.0-rc.0 + - @rocket.chat/rest-typings@6.11.0-rc.0 + - @rocket.chat/apps@0.1.2-rc.0 + - @rocket.chat/presence@0.2.2-rc.0 + - @rocket.chat/gazzodown@9.0.0-rc.0 + - @rocket.chat/api-client@0.2.2-rc.0 + - @rocket.chat/license@0.2.2-rc.0 + - @rocket.chat/cron@0.1.2-rc.0 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@5.0.0-rc.0 + - @rocket.chat/instance-status@0.1.2-rc.0 + - @rocket.chat/server-cloud-communication@0.0.2 + +## 6.10.2 + +### Patch Changes + +- Bump @rocket.chat/meteor version. + +- ([#32935](https://github.com/RocketChat/Rocket.Chat/pull/32935)) Fixed an issue that prevented apps from being updated or uninstalled in some cases + +- ([#32935](https://github.com/RocketChat/Rocket.Chat/pull/32935)) Fixed an issue that prevented apps from handling errors during execution in some cases + +- ([#32935](https://github.com/RocketChat/Rocket.Chat/pull/32935)) Improved Apps-Engine installation to prevent start up errors on manual installation setups + +- ([#32950](https://github.com/RocketChat/Rocket.Chat/pull/32950) by [@dionisio-bot](https://github.com/dionisio-bot)) Fixed a crash on web client due to service workers not being available, this can happen in multiple scenarios like on Firefox's private window or if the connection is not secure (non-HTTPS), [see more details](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts). + + Rocket.Chat needs service workers to process E2EE encrypted files on rooms. These types of files won't be available inside private windows, but the rest of E2EE encrypted features should work normally + +- ([#32935](https://github.com/RocketChat/Rocket.Chat/pull/32935)) Fixed an issue that caused the video conference button on rooms to not recognize a video conference provider app in some cases + +-
Updated dependencies [ca6a9d8de8, ca6a9d8de8, ca6a9d8de8, ca6a9d8de8]: + + - @rocket.chat/fuselage-ui-kit@8.0.2 + - @rocket.chat/core-services@0.4.2 + - @rocket.chat/core-typings@6.10.2 + - @rocket.chat/rest-typings@6.10.2 + - @rocket.chat/presence@0.2.2 + - @rocket.chat/apps@0.1.2 + - @rocket.chat/omnichannel-services@0.2.2 + - @rocket.chat/api-client@0.2.2 + - @rocket.chat/license@0.2.2 + - @rocket.chat/pdf-worker@0.1.2 + - @rocket.chat/cron@0.1.2 + - @rocket.chat/gazzodown@8.0.2 + - @rocket.chat/model-typings@0.5.2 + - @rocket.chat/ui-contexts@8.0.2 + - @rocket.chat/server-cloud-communication@0.0.2 + - @rocket.chat/models@0.1.2 + - @rocket.chat/ui-theming@0.2.0 + - @rocket.chat/ui-avatar@4.0.2 + - @rocket.chat/ui-client@8.0.2 + - @rocket.chat/ui-video-conf@8.0.2 + - @rocket.chat/web-ui-registration@8.0.2 + - @rocket.chat/instance-status@0.1.2 +
+ ## 6.10.1 ### Patch Changes diff --git a/apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts b/apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts index 219a78483cea..f5f5b0e1f275 100644 --- a/apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts +++ b/apps/meteor/app/2fa/server/methods/checkCodesRemaining.ts @@ -1,7 +1,7 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { '2fa:checkCodesRemaining': () => { remaining: number }; diff --git a/apps/meteor/app/2fa/server/methods/disable.ts b/apps/meteor/app/2fa/server/methods/disable.ts index 0927b3f854ac..d2c71febdc35 100644 --- a/apps/meteor/app/2fa/server/methods/disable.ts +++ b/apps/meteor/app/2fa/server/methods/disable.ts @@ -1,10 +1,10 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { TOTP } from '../lib/totp'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { '2fa:disable': (code: string) => Promise; diff --git a/apps/meteor/app/2fa/server/methods/enable.ts b/apps/meteor/app/2fa/server/methods/enable.ts index 6b786c0743e9..dea859d27b62 100644 --- a/apps/meteor/app/2fa/server/methods/enable.ts +++ b/apps/meteor/app/2fa/server/methods/enable.ts @@ -1,10 +1,10 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { TOTP } from '../lib/totp'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { '2fa:enable': () => Promise<{ secret: string; url: string }>; diff --git a/apps/meteor/app/2fa/server/methods/regenerateCodes.ts b/apps/meteor/app/2fa/server/methods/regenerateCodes.ts index e0f4cfaa4797..6968abfb93c2 100644 --- a/apps/meteor/app/2fa/server/methods/regenerateCodes.ts +++ b/apps/meteor/app/2fa/server/methods/regenerateCodes.ts @@ -1,10 +1,10 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { TOTP } from '../lib/totp'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { '2fa:regenerateCodes': (userToken: string) => { codes: string[] } | undefined; diff --git a/apps/meteor/app/2fa/server/methods/validateTempToken.ts b/apps/meteor/app/2fa/server/methods/validateTempToken.ts index cc2b44372a04..840fadc8cbf7 100644 --- a/apps/meteor/app/2fa/server/methods/validateTempToken.ts +++ b/apps/meteor/app/2fa/server/methods/validateTempToken.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { notifyOnUserChangeAsync } from '../../../lib/server/lib/notifyListener'; import { TOTP } from '../lib/totp'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { '2fa:validateTempToken': (userToken: string) => { codes: string[] } | undefined; diff --git a/apps/meteor/app/api/server/v1/emoji-custom.ts b/apps/meteor/app/api/server/v1/emoji-custom.ts index a61149c5e66e..9cbf202896e1 100644 --- a/apps/meteor/app/api/server/v1/emoji-custom.ts +++ b/apps/meteor/app/api/server/v1/emoji-custom.ts @@ -3,6 +3,8 @@ import { EmojiCustom } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { insertOrUpdateEmoji } from '../../../emoji-custom/server/lib/insertOrUpdateEmoji'; +import { uploadEmojiCustomWithBuffer } from '../../../emoji-custom/server/lib/uploadEmojiCustom'; import { settings } from '../../../settings/server'; import { API } from '../api'; import { getPaginationItems } from '../helpers/getPaginationItems'; @@ -148,9 +150,19 @@ API.v1.addRoute( fields.extension = emojiToUpdate.extension; } - await Meteor.callAsync('insertOrUpdateEmoji', { ...fields, newFile }); + const emojiData = { + name: fields.name, + _id: fields._id, + aliases: fields.aliases, + extension: fields.extension, + previousName: fields.previousName, + previousExtension: fields.previousExtension, + newFile, + }; + + await insertOrUpdateEmoji(this.userId, emojiData); if (fields.newFile) { - await Meteor.callAsync('uploadEmojiCustom', fileBuffer, mimetype, { ...fields, newFile }); + await uploadEmojiCustomWithBuffer(this.userId, fileBuffer, mimetype, emojiData); } return API.v1.success(); }, diff --git a/apps/meteor/app/api/server/v1/federation.ts b/apps/meteor/app/api/server/v1/federation.ts index 7be5b1fc13fe..5f998546cf3e 100644 --- a/apps/meteor/app/api/server/v1/federation.ts +++ b/apps/meteor/app/api/server/v1/federation.ts @@ -22,3 +22,21 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'federation/configuration.verify', + { authRequired: true, permissionsRequired: ['view-privileged-setting'] }, + { + async get() { + const service = License.hasValidLicense() ? FederationEE : Federation; + + const status = await service.configurationStatus(); + + if (!status.externalReachability.ok || !status.appservice.ok) { + return API.v1.failure(status); + } + + return API.v1.success(status); + }, + }, +); diff --git a/apps/meteor/app/api/server/v1/oauthapps.ts b/apps/meteor/app/api/server/v1/oauthapps.ts index 034a73f54104..4113b945a4db 100644 --- a/apps/meteor/app/api/server/v1/oauthapps.ts +++ b/apps/meteor/app/api/server/v1/oauthapps.ts @@ -27,11 +27,12 @@ API.v1.addRoute( { authRequired: true, validateParams: isOauthAppsGetParams }, { async get() { - if (!(await hasPermissionAsync(this.userId, 'manage-oauth-apps'))) { - return API.v1.unauthorized(); - } + const isOAuthAppsManager = await hasPermissionAsync(this.userId, 'manage-oauth-apps'); - const oauthApp = await OAuthApps.findOneAuthAppByIdOrClientId(this.queryParams); + const oauthApp = await OAuthApps.findOneAuthAppByIdOrClientId( + this.queryParams, + !isOAuthAppsManager ? { projection: { clientSecret: 0 } } : {}, + ); if (!oauthApp) { return API.v1.failure('OAuth app not found.'); diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index e3296b98ef17..17ef75b74574 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1,8 +1,14 @@ import { Media } from '@rocket.chat/core-services'; import type { IRoom, IUpload } from '@rocket.chat/core-typings'; -import { Messages, Rooms, Users, Uploads } from '@rocket.chat/models'; +import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models'; import type { Notifications } from '@rocket.chat/rest-typings'; -import { isGETRoomsNameExists, isRoomsImagesProps, isRoomsMuteUnmuteUserProps, isRoomsExportProps } from '@rocket.chat/rest-typings'; +import { + isGETRoomsNameExists, + isRoomsImagesProps, + isRoomsMuteUnmuteUserProps, + isRoomsExportProps, + isRoomsIsMemberProps, +} from '@rocket.chat/rest-typings'; import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../../lib/isTruthy'; @@ -783,6 +789,36 @@ API.v1.addRoute( }, ); +API.v1.addRoute( + 'rooms.isMember', + { + authRequired: true, + validateParams: isRoomsIsMemberProps, + }, + { + async get() { + const { roomId, userId, username } = this.queryParams; + const [room, user] = await Promise.all([ + findRoomByIdOrName({ + params: { roomId }, + }) as Promise, + Users.findOneByIdOrUsername(userId || username), + ]); + + if (!user?._id) { + return API.v1.failure('error-user-not-found'); + } + + if (await canAccessRoomAsync(room, { _id: this.user._id })) { + return API.v1.success({ + isMember: (await Subscriptions.countByRoomIdAndUserId(room._id, user._id)) > 0, + }); + } + return API.v1.unauthorized(); + }, + }, +); + API.v1.addRoute( 'rooms.muteUser', { authRequired: true, validateParams: isRoomsMuteUnmuteUserProps }, diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts index 9d81fe6bef65..b92d9ba572fd 100644 --- a/apps/meteor/app/api/server/v1/subscriptions.ts +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -82,6 +82,7 @@ API.v1.addRoute( async post() { const { readThreads = false } = this.bodyParams; const roomId = 'rid' in this.bodyParams ? this.bodyParams.rid : this.bodyParams.roomId; + await readMessages(roomId, this.userId, readThreads); return API.v1.success(); diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts index 4ea8f2a48f38..f64f8c820575 100644 --- a/apps/meteor/app/api/server/v1/teams.ts +++ b/apps/meteor/app/api/server/v1/teams.ts @@ -1,6 +1,6 @@ import { Team } from '@rocket.chat/core-services'; import type { ITeam, UserStatus } from '@rocket.chat/core-typings'; -import { TEAM_TYPE } from '@rocket.chat/core-typings'; +import { TEAM_TYPE, isValidSidepanel } from '@rocket.chat/core-typings'; import { Users, Rooms } from '@rocket.chat/models'; import { isTeamsConvertToChannelProps, @@ -85,7 +85,11 @@ API.v1.addRoute( }), ); - const { name, type, members, room, owner } = this.bodyParams; + const { name, type, members, room, owner, sidepanel } = this.bodyParams; + + if (sidepanel?.items && !isValidSidepanel(sidepanel)) { + throw new Error('error-invalid-sidepanel'); + } const team = await Team.create(this.userId, { team: { @@ -95,6 +99,7 @@ API.v1.addRoute( room, members, owner, + sidepanel, }); return API.v1.success({ team }); diff --git a/apps/meteor/app/api/server/v1/users.ts b/apps/meteor/app/api/server/v1/users.ts index 26ef2fa30ff2..9c56ecac01cb 100644 --- a/apps/meteor/app/api/server/v1/users.ts +++ b/apps/meteor/app/api/server/v1/users.ts @@ -45,6 +45,7 @@ import { setUserAvatar } from '../../../lib/server/functions/setUserAvatar'; import { setUsernameWithValidation } from '../../../lib/server/functions/setUsername'; import { validateCustomFields } from '../../../lib/server/functions/validateCustomFields'; import { validateNameChars } from '../../../lib/server/functions/validateNameChars'; +import { validateUsername } from '../../../lib/server/functions/validateUsername'; import { notifyOnUserChange, notifyOnUserChangeAsync } from '../../../lib/server/lib/notifyListener'; import { generateAccessToken } from '../../../lib/server/methods/createToken'; import { settings } from '../../../settings/server'; @@ -651,6 +652,10 @@ API.v1.addRoute( return API.v1.failure('Name contains invalid characters'); } + if (!validateUsername(this.bodyParams.username)) { + return API.v1.failure(`The username provided is not valid`); + } + if (!(await checkUsernameAvailability(this.bodyParams.username))) { return API.v1.failure('Username is already in use'); } @@ -1216,7 +1221,7 @@ API.v1.addRoute( throw new Meteor.Error('error-invalid-user-id', 'Invalid user id'); } - void notifyOnUserChange({ clientAction: 'updated', id: this.userId, diff: { 'services.resume.loginTokens': [] } }); + void notifyOnUserChange({ clientAction: 'updated', id: userId, diff: { 'services.resume.loginTokens': [] } }); return API.v1.success({ message: `User ${userId} has been logged out!`, diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index ec5cff29a99b..4f4794591e02 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -1,7 +1,7 @@ -import type { IAppServerOrchestrator, IAppsLivechatMessage } from '@rocket.chat/apps'; +import type { IAppServerOrchestrator, IAppsLivechatMessage, IAppsMessage } from '@rocket.chat/apps'; import type { IExtraRoomParams } from '@rocket.chat/apps-engine/definition/accessors/ILivechatCreator'; import type { IVisitor, ILivechatRoom, ILivechatTransferData, IDepartment } from '@rocket.chat/apps-engine/definition/livechat'; -import type { IMessage as IAppsEngineMesage } from '@rocket.chat/apps-engine/definition/messages'; +import type { IMessage as IAppsEngineMessage } from '@rocket.chat/apps-engine/definition/messages'; import type { IUser } from '@rocket.chat/apps-engine/definition/users'; import { LivechatBridge } from '@rocket.chat/apps-engine/server/bridges/LivechatBridge'; import type { ILivechatDepartment, IOmnichannelRoom, SelectedAgent, IMessage, ILivechatVisitor } from '@rocket.chat/core-typings'; @@ -13,6 +13,12 @@ import { deasyncPromise } from '../../../../server/deasync/deasync'; import { type ILivechatMessage, Livechat as LivechatTyped } from '../../../livechat/server/lib/LivechatTyped'; import { settings } from '../../../settings/server'; +declare module '@rocket.chat/apps/dist/converters/IAppMessagesConverter' { + export interface IAppMessagesConverter { + convertMessage(message: IMessage, cacheObj?: object): Promise; + } +} + declare module '@rocket.chat/apps-engine/definition/accessors/ILivechatCreator' { interface IExtraRoomParams { customFields?: Record; @@ -337,7 +343,7 @@ export class AppLivechatBridge extends LivechatBridge { return Promise.all((await LivechatDepartment.findEnabledWithAgents().toArray()).map(boundConverter)); } - protected async _fetchLivechatRoomMessages(appId: string, roomId: string): Promise> { + protected async _fetchLivechatRoomMessages(appId: string, roomId: string): Promise> { this.orch.debugLog(`The App ${appId} is getting the transcript for livechat room ${roomId}.`); const messageConverter = this.orch.getConverters()?.get('messages'); @@ -346,8 +352,7 @@ export class AppLivechatBridge extends LivechatBridge { } const livechatMessages = await LivechatTyped.getRoomMessages({ rid: roomId }); - - return Promise.all(livechatMessages.map((message) => messageConverter.convertMessage(message) as Promise)); + return Promise.all(await livechatMessages.map((message) => messageConverter.convertMessage(message, livechatMessages)).toArray()); } protected async setCustomFields( diff --git a/apps/meteor/app/apps/server/bridges/messages.ts b/apps/meteor/app/apps/server/bridges/messages.ts index 18a68220998f..824a9d5c15af 100644 --- a/apps/meteor/app/apps/server/bridges/messages.ts +++ b/apps/meteor/app/apps/server/bridges/messages.ts @@ -1,4 +1,5 @@ import type { IAppServerOrchestrator, IAppsMessage, IAppsUser } from '@rocket.chat/apps'; +import type { Reaction } from '@rocket.chat/apps-engine/definition/messages'; import type { IRoom } from '@rocket.chat/apps-engine/definition/rooms'; import type { ITypingDescriptor } from '@rocket.chat/apps-engine/server/bridges/MessageBridge'; import { MessageBridge } from '@rocket.chat/apps-engine/server/bridges/MessageBridge'; @@ -10,6 +11,7 @@ import { deleteMessage } from '../../../lib/server/functions/deleteMessage'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; import notifications from '../../../notifications/server/lib/Notifications'; +import { executeSetReaction } from '../../../reactions/server/setReaction'; export class AppMessageBridge extends MessageBridge { constructor(private readonly orch: IAppServerOrchestrator) { @@ -118,4 +120,24 @@ export class AppMessageBridge extends MessageBridge { throw new Error('Unrecognized typing scope provided'); } } + + private isValidReaction(reaction: Reaction): boolean { + return reaction.startsWith(':') && reaction.endsWith(':'); + } + + protected async addReaction(messageId: string, userId: string, reaction: Reaction): Promise { + if (!this.isValidReaction(reaction)) { + throw new Error('Invalid reaction'); + } + + return executeSetReaction(userId, reaction, messageId, true); + } + + protected async removeReaction(messageId: string, userId: string, reaction: Reaction): Promise { + if (!this.isValidReaction(reaction)) { + throw new Error('Invalid reaction'); + } + + return executeSetReaction(userId, reaction, messageId, false); + } } diff --git a/apps/meteor/app/apps/server/converters/messages.js b/apps/meteor/app/apps/server/converters/messages.js index d7dae512e9a8..89ef2454d895 100644 --- a/apps/meteor/app/apps/server/converters/messages.js +++ b/apps/meteor/app/apps/server/converters/messages.js @@ -52,19 +52,26 @@ export class AppMessagesConverter { return transformMappedData(message, map); } - async convertMessage(msgObj) { + async convertMessage(msgObj, cacheObj = msgObj) { if (!msgObj) { return undefined; } const cache = - this.mem.get(msgObj) ?? + this.mem.get(cacheObj) ?? new Map([ ['room', cachedFunction(this.orch.getConverters().get('rooms').convertById.bind(this.orch.getConverters().get('rooms')))], - ['user', cachedFunction(this.orch.getConverters().get('users').convertById.bind(this.orch.getConverters().get('users')))], + [ + 'user.convertById', + cachedFunction(this.orch.getConverters().get('users').convertById.bind(this.orch.getConverters().get('users'))), + ], + [ + 'user.convertToApp', + cachedFunction(this.orch.getConverters().get('users').convertToApp.bind(this.orch.getConverters().get('users'))), + ], ]); - this.mem.set(msgObj, cache); + this.mem.set(cacheObj, cache); const map = { id: '_id', @@ -96,7 +103,7 @@ export class AppMessagesConverter { return undefined; } - return cache.get('user')(editedBy._id); + return cache.get('user.convertById')(editedBy._id); }, attachments: async (message) => { const result = await this._convertAttachmentsToApp(message.attachments); @@ -110,8 +117,8 @@ export class AppMessagesConverter { // When the message contains token, means the message is from the visitor(omnichannel) const user = await (isMessageFromVisitor(msgObj) - ? this.orch.getConverters().get('users').convertToApp(message.u) - : cache.get('user')(message.u._id)); + ? cache.get('user.convertToApp')(message.u) + : cache.get('user.convertById')(message.u._id)); delete message.u; @@ -120,7 +127,7 @@ export class AppMessagesConverter { * `sender` as undefined, so we need to add this fallback here. */ - return user || this.orch.getConverters().get('users').convertToApp(message.u); + return user || cache.get('user.convertToApp')(message.u); }, }; diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js index 670c1a248a0f..a98a6701b2c2 100644 --- a/apps/meteor/app/apps/server/converters/rooms.js +++ b/apps/meteor/app/apps/server/converters/rooms.js @@ -111,8 +111,8 @@ export class AppRoomsConverter { return Object.assign(newRoom, room._unmappedProperties_); } - async convertRoom(room) { - if (!room) { + async convertRoom(originalRoom) { + if (!originalRoom) { return undefined; } @@ -134,6 +134,7 @@ export class AppRoomsConverter { _USERNAMES: '_USERNAMES', description: 'description', source: 'source', + closer: 'closer', isDefault: (room) => { const result = !!room.default; delete room.default; @@ -210,6 +211,19 @@ export class AppRoomsConverter { return this.orch.getConverters().get('departments').convertById(departmentId); }, + closedBy: async (room) => { + const { closedBy } = room; + + if (!closedBy) { + return undefined; + } + + delete room.closedBy; + if (originalRoom.closer === 'user') { + return this.orch.getConverters().get('users').convertById(closedBy._id); + } + return this.orch.getConverters().get('visitors').convertById(closedBy._id); + }, servedBy: async (room) => { const { servedBy } = room; @@ -245,7 +259,7 @@ export class AppRoomsConverter { }, }; - return transformMappedData(room, map); + return transformMappedData(originalRoom, map); } _convertTypeToApp(typeChar) { diff --git a/apps/meteor/app/assets/server/assets.ts b/apps/meteor/app/assets/server/assets.ts index b9653a2f1b9a..5d627a4b1d29 100644 --- a/apps/meteor/app/assets/server/assets.ts +++ b/apps/meteor/app/assets/server/assets.ts @@ -2,8 +2,8 @@ import crypto from 'crypto'; import type { ServerResponse, IncomingMessage } from 'http'; import type { IRocketChatAssets, IRocketChatAsset, ISetting } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import type { NextHandleFunction } from 'connect'; import sizeOf from 'image-size'; import { Meteor } from 'meteor/meteor'; @@ -406,7 +406,7 @@ Meteor.startup(() => { }, 200); }); -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { refreshClients(): boolean; diff --git a/apps/meteor/app/authorization/server/constant/permissions.ts b/apps/meteor/app/authorization/server/constant/permissions.ts index 6efe99e14d0e..d9ae4133e49e 100644 --- a/apps/meteor/app/authorization/server/constant/permissions.ts +++ b/apps/meteor/app/authorization/server/constant/permissions.ts @@ -93,6 +93,10 @@ export const permissions = [ _id: 'view-l-room', roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'], }, + { + _id: 'create-livechat-contact', + roles: ['livechat-manager', 'livechat-monitor', 'livechat-agent', 'admin'], + }, { _id: 'view-livechat-manager', roles: ['livechat-manager', 'livechat-monitor', 'admin'] }, { _id: 'view-omnichannel-contact-center', diff --git a/apps/meteor/app/authorization/server/methods/addPermissionToRole.ts b/apps/meteor/app/authorization/server/methods/addPermissionToRole.ts index 13a114732bd2..9a336478ca3e 100644 --- a/apps/meteor/app/authorization/server/methods/addPermissionToRole.ts +++ b/apps/meteor/app/authorization/server/methods/addPermissionToRole.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Permissions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { notifyOnPermissionChangedById } from '../../../lib/server/lib/notifyListener'; import { CONSTANTS, AuthorizationUtils } from '../../lib'; import { hasPermissionAsync } from '../functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'authorization:addPermissionToRole'(permissionId: string, role: string): void; diff --git a/apps/meteor/app/authorization/server/methods/addUserToRole.ts b/apps/meteor/app/authorization/server/methods/addUserToRole.ts index 02ccc76373f1..81582dd7e9fb 100644 --- a/apps/meteor/app/authorization/server/methods/addUserToRole.ts +++ b/apps/meteor/app/authorization/server/methods/addUserToRole.ts @@ -1,14 +1,14 @@ import { api } from '@rocket.chat/core-services'; import type { IRole, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Roles, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { settings } from '../../../settings/server'; import { hasPermissionAsync } from '../functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'authorization:addUserToRole'(roleId: IRole['_id'], username: IUser['username'], scope: string | undefined): Promise; diff --git a/apps/meteor/app/authorization/server/methods/deleteRole.ts b/apps/meteor/app/authorization/server/methods/deleteRole.ts index 2ff09deadf68..140852e0f1ec 100644 --- a/apps/meteor/app/authorization/server/methods/deleteRole.ts +++ b/apps/meteor/app/authorization/server/methods/deleteRole.ts @@ -1,13 +1,13 @@ import type { IRole } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Roles } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import type { DeleteResult } from 'mongodb'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { hasPermissionAsync } from '../functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'authorization:deleteRole'(roleId: IRole['_id'] | IRole['name']): Promise; diff --git a/apps/meteor/app/authorization/server/methods/removeRoleFromPermission.ts b/apps/meteor/app/authorization/server/methods/removeRoleFromPermission.ts index 91a4df1eddf7..68ca11ef9fb5 100644 --- a/apps/meteor/app/authorization/server/methods/removeRoleFromPermission.ts +++ b/apps/meteor/app/authorization/server/methods/removeRoleFromPermission.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Permissions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { notifyOnPermissionChangedById } from '../../../lib/server/lib/notifyListener'; import { CONSTANTS } from '../../lib'; import { hasPermissionAsync } from '../functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'authorization:removeRoleFromPermission'(permissionId: string, role: string): void; diff --git a/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts b/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts index d5dba40a1e53..56f0c2e307ab 100644 --- a/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts +++ b/apps/meteor/app/authorization/server/methods/removeUserFromRole.ts @@ -1,14 +1,14 @@ import { api } from '@rocket.chat/core-services'; import type { IRole, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Roles, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { settings } from '../../../settings/server'; import { hasPermissionAsync } from '../functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'authorization:removeUserFromRole'(roleId: IRole['_id'], username: IUser['username'], scope?: string): Promise; diff --git a/apps/meteor/app/authorization/server/streamer/permissions/index.ts b/apps/meteor/app/authorization/server/streamer/permissions/index.ts index ff8fd3c93262..e74cf37869fd 100644 --- a/apps/meteor/app/authorization/server/streamer/permissions/index.ts +++ b/apps/meteor/app/authorization/server/streamer/permissions/index.ts @@ -1,11 +1,11 @@ import type { IPermission, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Permissions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { WithId } from 'mongodb'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'permissions/get'( diff --git a/apps/meteor/app/autotranslate/server/autotranslate.ts b/apps/meteor/app/autotranslate/server/autotranslate.ts index 7a9eb8780a2d..f3c6d9e55fdb 100644 --- a/apps/meteor/app/autotranslate/server/autotranslate.ts +++ b/apps/meteor/app/autotranslate/server/autotranslate.ts @@ -15,7 +15,7 @@ import _ from 'underscore'; import { callbacks } from '../../../lib/callbacks'; import { isTruthy } from '../../../lib/isTruthy'; -import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; +import { notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; import { Markdown } from '../../markdown/server'; import { settings } from '../../settings/server'; @@ -79,7 +79,7 @@ export class TranslationProviderRegistry { return null; } - return provider.translateMessage(message, room, targetLanguage); + return provider.translateMessage(message, { room, targetLanguage }); } static getProviders(): AutoTranslate[] { @@ -290,7 +290,7 @@ export abstract class AutoTranslate { * @param {object} targetLanguage * @returns {object} unmodified message object. */ - async translateMessage(message: IMessage, room: IRoom, targetLanguage?: string): Promise { + async translateMessage(message: IMessage, { room, targetLanguage }: { room: IRoom; targetLanguage?: string }): Promise { let targetLanguages: string[]; if (targetLanguage) { targetLanguages = [targetLanguage]; @@ -332,7 +332,7 @@ export abstract class AutoTranslate { } private notifyTranslatedMessage(messageId: string): void { - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: messageId, }); } diff --git a/apps/meteor/app/autotranslate/server/methods/getProviderUiMetadata.ts b/apps/meteor/app/autotranslate/server/methods/getProviderUiMetadata.ts index 30760e854ed1..1d443c21d210 100644 --- a/apps/meteor/app/autotranslate/server/methods/getProviderUiMetadata.ts +++ b/apps/meteor/app/autotranslate/server/methods/getProviderUiMetadata.ts @@ -1,9 +1,9 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { TranslationProviderRegistry } from '../autotranslate'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'autoTranslate.getProviderUiMetadata'(): Record; diff --git a/apps/meteor/app/autotranslate/server/methods/getSupportedLanguages.ts b/apps/meteor/app/autotranslate/server/methods/getSupportedLanguages.ts index e0118e8d95a4..7b9614da7739 100644 --- a/apps/meteor/app/autotranslate/server/methods/getSupportedLanguages.ts +++ b/apps/meteor/app/autotranslate/server/methods/getSupportedLanguages.ts @@ -1,5 +1,5 @@ import type { ISupportedLanguage } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; @@ -7,7 +7,7 @@ import { TranslationProviderRegistry } from '..'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'autoTranslate.getSupportedLanguages'(targetLanguage: string): ISupportedLanguage[] | undefined; diff --git a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts index e396d78887a9..3d4d15c0316e 100644 --- a/apps/meteor/app/autotranslate/server/methods/saveSettings.ts +++ b/apps/meteor/app/autotranslate/server/methods/saveSettings.ts @@ -1,11 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'autoTranslate.saveSettings'(rid: string, field: string, value: string, options: { defaultLanguage: string }): boolean; @@ -44,6 +45,8 @@ Meteor.methods({ }); } + let shouldNotifySubscriptionChanged = false; + switch (field) { case 'autoTranslate': const room = await Rooms.findE2ERoomById(rid, { projection: { _id: 1 } }); @@ -53,16 +56,34 @@ Meteor.methods({ }); } - await Subscriptions.updateAutoTranslateById(subscription._id, value === '1'); + const updateAutoTranslateResponse = await Subscriptions.updateAutoTranslateById(subscription._id, value === '1'); + if (updateAutoTranslateResponse.modifiedCount) { + shouldNotifySubscriptionChanged = true; + } + if (!subscription.autoTranslateLanguage && options.defaultLanguage) { - await Subscriptions.updateAutoTranslateLanguageById(subscription._id, options.defaultLanguage); + const updateAutoTranslateLanguageResponse = await Subscriptions.updateAutoTranslateLanguageById( + subscription._id, + options.defaultLanguage, + ); + if (updateAutoTranslateLanguageResponse.modifiedCount) { + shouldNotifySubscriptionChanged = true; + } } + break; case 'autoTranslateLanguage': - await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value); + const updateAutoTranslateLanguage = await Subscriptions.updateAutoTranslateLanguageById(subscription._id, value); + if (updateAutoTranslateLanguage.modifiedCount) { + shouldNotifySubscriptionChanged = true; + } break; } + if (shouldNotifySubscriptionChanged) { + void notifyOnSubscriptionChangedById(subscription._id); + } + return true; }, }); diff --git a/apps/meteor/app/autotranslate/server/methods/translateMessage.ts b/apps/meteor/app/autotranslate/server/methods/translateMessage.ts index d90cad90ce77..551eba57c005 100644 --- a/apps/meteor/app/autotranslate/server/methods/translateMessage.ts +++ b/apps/meteor/app/autotranslate/server/methods/translateMessage.ts @@ -1,11 +1,11 @@ import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { TranslationProviderRegistry } from '..'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'autoTranslate.translateMessage'(message: IMessage | undefined, targetLanguage: string): Promise; diff --git a/apps/meteor/app/bot-helpers/server/index.ts b/apps/meteor/app/bot-helpers/server/index.ts index 5e19e1652454..6c0984ae483d 100644 --- a/apps/meteor/app/bot-helpers/server/index.ts +++ b/apps/meteor/app/bot-helpers/server/index.ts @@ -1,7 +1,7 @@ import type { IUser } from '@rocket.chat/core-typings'; import { UserStatus } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import type { Filter, FindCursor } from 'mongodb'; @@ -194,7 +194,7 @@ settings.watch('BotHelpers_userFields', (value) => { botHelpers.setupCursors(value); }); -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { botRequest: (prop: keyof BotHelpers, ...params: unknown[]) => Promise; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts index 55d40cf3d7e6..ef70ff65c067 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomCustomFields.ts @@ -3,21 +3,28 @@ import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; + export const saveRoomCustomFields = async function (rid: string, roomCustomFields: Record): Promise { if (!Match.test(rid, String)) { throw new Meteor.Error('invalid-room', 'Invalid room', { function: 'RocketChat.saveRoomCustomFields', }); } + if (!Match.test(roomCustomFields, Object)) { throw new Meteor.Error('invalid-roomCustomFields-type', 'Invalid roomCustomFields type', { function: 'RocketChat.saveRoomCustomFields', }); } + const ret = await Rooms.setCustomFieldsById(rid, roomCustomFields); // Update customFields of any user's Subscription related with this rid - await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields); + const { modifiedCount } = await Subscriptions.updateCustomFieldsByRoomId(rid, roomCustomFields); + if (modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } return ret; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts index ed07540ba2b0..c1a441463a98 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomEncrypted.ts @@ -6,6 +6,8 @@ import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; + export const saveRoomEncrypted = async function (rid: string, encrypted: boolean, user: IUser, sendMessage = true): Promise { if (!Match.test(rid, String)) { throw new Meteor.Error('invalid-room', 'Invalid room', { @@ -27,7 +29,10 @@ export const saveRoomEncrypted = async function (rid: string, encrypted: boolean } if (encrypted) { - await Subscriptions.disableAutoTranslateByRoomId(rid); + const { modifiedCount } = await Subscriptions.disableAutoTranslateByRoomId(rid); + if (modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } } return update; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts index 0fc15f878bcf..f4a5afbb6380 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomName.ts @@ -1,4 +1,4 @@ -import { Message } from '@rocket.chat/core-services'; +import { Message, Room } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; import { Integrations, Rooms, Subscriptions } from '@rocket.chat/models'; @@ -8,11 +8,17 @@ import type { Document, UpdateResult } from 'mongodb'; import { callbacks } from '../../../../lib/callbacks'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability'; -import { notifyOnIntegrationChangedByChannels } from '../../../lib/server/lib/notifyListener'; +import { notifyOnIntegrationChangedByChannels, notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; const updateFName = async (rid: string, displayName: string): Promise<(UpdateResult | Document)[]> => { - return Promise.all([Rooms.setFnameById(rid, displayName), Subscriptions.updateFnameByRoomId(rid, displayName)]); + const responses = await Promise.all([Rooms.setFnameById(rid, displayName), Subscriptions.updateFnameByRoomId(rid, displayName)]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + + return responses; }; const updateRoomName = async (rid: string, displayName: string, slugifiedRoomName: string) => { @@ -24,10 +30,16 @@ const updateRoomName = async (rid: string, displayName: string, slugifiedRoomNam }); } - return Promise.all([ + const responses = await Promise.all([ Rooms.setNameById(rid, slugifiedRoomName, displayName), Subscriptions.updateNameAndAlertByRoomId(rid, slugifiedRoomName, displayName), ]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + + return responses; }; export async function saveRoomName( @@ -48,6 +60,9 @@ export async function saveRoomName( function: 'RocketChat.saveRoomdisplayName', }); } + + await Room.beforeNameChange(room); + if (displayName === room.name) { return; } diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts index 11b9b5b6e565..a59f2ba82fba 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomTopic.ts @@ -1,4 +1,4 @@ -import { Message } from '@rocket.chat/core-services'; +import { Message, Room } from '@rocket.chat/core-services'; import { Rooms } from '@rocket.chat/models'; import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -20,6 +20,10 @@ export const saveRoomTopic = async function ( }); } + const room = await Rooms.findOneById(rid); + + await Room.beforeTopicChange(room!); + const update = await Rooms.setTopicById(rid, roomTopic); if (update && sendMessage) { await Message.saveSystemMessage('room_changed_topic', rid, roomTopic || '', user); diff --git a/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts b/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts index e8a60d1ea0eb..4600d1d46a80 100644 --- a/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts +++ b/apps/meteor/app/channel-settings/server/functions/saveRoomType.ts @@ -8,6 +8,7 @@ import type { UpdateResult, Document } from 'mongodb'; import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig'; import { i18n } from '../../../../server/lib/i18n'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { notifyOnSubscriptionChangedByRoomId } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; export const saveRoomType = async function ( @@ -41,11 +42,16 @@ export const saveRoomType = async function ( }); } - const result = (await Rooms.setTypeById(rid, roomType)) && (await Subscriptions.updateTypeByRoomId(rid, roomType)); + const result = await Promise.all([Rooms.setTypeById(rid, roomType), Subscriptions.updateTypeByRoomId(rid, roomType)]); + if (!result) { return result; } + if (result[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + if (sendMessage) { let message; if (roomType === 'c') { @@ -59,5 +65,6 @@ export const saveRoomType = async function ( } await Message.saveSystemMessage('room_changed_privacy', rid, message, user); } + return result; }; diff --git a/apps/meteor/app/channel-settings/server/functions/saveStreamingOptions.ts b/apps/meteor/app/channel-settings/server/functions/saveStreamingOptions.ts deleted file mode 100644 index aee596402cc6..000000000000 --- a/apps/meteor/app/channel-settings/server/functions/saveStreamingOptions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Rooms } from '@rocket.chat/models'; -import { Match, check } from 'meteor/check'; -import { Meteor } from 'meteor/meteor'; - -export const saveStreamingOptions = async function (rid: string, options: Record): Promise { - if (!Match.test(rid, String)) { - throw new Meteor.Error('invalid-room', 'Invalid room', { - function: 'RocketChat.saveStreamingOptions', - }); - } - - check(options, { - id: Match.Optional(String), - type: Match.Optional(String), - url: Match.Optional(String), - thumbnail: Match.Optional(String), - isAudioOnly: Match.Optional(Boolean), - message: Match.Optional(String), - }); - - await Rooms.setStreamingOptionsById(rid, options); -}; diff --git a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts index e17faebea384..04e8fdbaf186 100644 --- a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts +++ b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts @@ -1,8 +1,8 @@ import { Team } from '@rocket.chat/core-services'; import type { IRoom, IRoomWithRetentionPolicy, IUser, MessageTypesValues } from '@rocket.chat/core-typings'; -import { TEAM_TYPE } from '@rocket.chat/core-typings'; +import { TEAM_TYPE, isValidSidepanel } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -21,7 +21,6 @@ import { saveRoomReadOnly } from '../functions/saveRoomReadOnly'; import { saveRoomSystemMessages } from '../functions/saveRoomSystemMessages'; import { saveRoomTopic } from '../functions/saveRoomTopic'; import { saveRoomType } from '../functions/saveRoomType'; -import { saveStreamingOptions } from '../functions/saveStreamingOptions'; type RoomSettings = { roomAvatar: string; @@ -37,7 +36,6 @@ type RoomSettings = { systemMessages: MessageTypesValues[]; default: boolean; joinCode: string; - streamingOptions: NonNullable; retentionEnabled: boolean; retentionMaxAge: number; retentionExcludePinned: boolean; @@ -49,6 +47,7 @@ type RoomSettings = { favorite: boolean; defaultValue: boolean; }; + sidepanel?: IRoom['sidepanel']; }; type RoomSettingsValidators = { @@ -80,6 +79,24 @@ const validators: RoomSettingsValidators = { }); } }, + async sidepanel({ room, userId, value }) { + if (!room.teamMain) { + throw new Meteor.Error('error-action-not-allowed', 'Invalid room', { + method: 'saveRoomSettings', + }); + } + + if (!(await hasPermissionAsync(userId, 'edit-team', room._id))) { + throw new Meteor.Error('error-action-not-allowed', 'You do not have permission to change sidepanel items', { + method: 'saveRoomSettings', + }); + } + + if (!isValidSidepanel(value)) { + throw new Meteor.Error('error-invalid-sidepanel'); + } + }, + async roomType({ userId, room, value }) { if (value === room.t) { return; @@ -213,6 +230,11 @@ const settingSavers: RoomSettingsSavers = { await saveRoomTopic(rid, value, user); } }, + async sidepanel({ value, rid, room }) { + if (JSON.stringify(value) !== JSON.stringify(room.sidepanel)) { + await Rooms.setSidepanelById(rid, value); + } + }, async roomAnnouncement({ value, room, rid, user }) { if (!value && !room.announcement) { return; @@ -248,9 +270,6 @@ const settingSavers: RoomSettingsSavers = { void Team.update(user._id, room.teamId, { type, updateRoom: false }); } }, - async streamingOptions({ value, rid }) { - await saveStreamingOptions(rid, value); - }, async readOnly({ value, room, rid, user }) { if (value !== room.ro) { await saveRoomReadOnly(rid, value, user); @@ -304,7 +323,7 @@ const settingSavers: RoomSettingsSavers = { }, }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { saveRoomSettings(rid: IRoom['_id'], settings: Partial): Promise<{ result: true; rid: IRoom['_id'] }>; @@ -330,7 +349,6 @@ const fields: (keyof RoomSettings)[] = [ 'systemMessages', 'default', 'joinCode', - 'streamingOptions', 'retentionEnabled', 'retentionMaxAge', 'retentionExcludePinned', @@ -339,6 +357,7 @@ const fields: (keyof RoomSettings)[] = [ 'retentionOverrideGlobal', 'encrypted', 'favorite', + 'sidepanel', ]; const validate = ( diff --git a/apps/meteor/app/cloud/server/methods.ts b/apps/meteor/app/cloud/server/methods.ts index 0e47a8ba8754..29daefe0d58c 100644 --- a/apps/meteor/app/cloud/server/methods.ts +++ b/apps/meteor/app/cloud/server/methods.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -13,7 +13,7 @@ import { startRegisterWorkspace } from './functions/startRegisterWorkspace'; import { syncWorkspace } from './functions/syncWorkspace'; import { userLogout } from './functions/userLogout'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'cloud:checkRegisterStatus': () => { diff --git a/apps/meteor/app/crowd/server/methods.ts b/apps/meteor/app/crowd/server/methods.ts index a621e3c8d027..48faa2fcbcab 100644 --- a/apps/meteor/app/crowd/server/methods.ts +++ b/apps/meteor/app/crowd/server/methods.ts @@ -1,4 +1,5 @@ -import type { ServerMethods, TranslationKey } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; @@ -6,7 +7,7 @@ import { settings } from '../../settings/server'; import { CROWD } from './crowd'; import { logger } from './logger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { crowd_test_connection(): { message: TranslationKey; params: string[] }; diff --git a/apps/meteor/app/custom-sounds/server/methods/deleteCustomSound.ts b/apps/meteor/app/custom-sounds/server/methods/deleteCustomSound.ts index 5ddf0cc66e52..1c63c7a67d9f 100644 --- a/apps/meteor/app/custom-sounds/server/methods/deleteCustomSound.ts +++ b/apps/meteor/app/custom-sounds/server/methods/deleteCustomSound.ts @@ -1,13 +1,13 @@ import { api } from '@rocket.chat/core-services'; import type { ICustomSound } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { CustomSounds } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteCustomSound(_id: ICustomSound['_id']): Promise; diff --git a/apps/meteor/app/custom-sounds/server/methods/insertOrUpdateSound.ts b/apps/meteor/app/custom-sounds/server/methods/insertOrUpdateSound.ts index 4b48a64dc14c..1b922c6b162e 100644 --- a/apps/meteor/app/custom-sounds/server/methods/insertOrUpdateSound.ts +++ b/apps/meteor/app/custom-sounds/server/methods/insertOrUpdateSound.ts @@ -1,6 +1,6 @@ import { api } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { CustomSounds } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -20,7 +20,7 @@ export type ICustomSoundData = { random?: number; }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { insertOrUpdateSound(soundData: ICustomSoundData): Promise; diff --git a/apps/meteor/app/custom-sounds/server/methods/listCustomSounds.ts b/apps/meteor/app/custom-sounds/server/methods/listCustomSounds.ts index 2c2130fc0d30..eda1325d7733 100644 --- a/apps/meteor/app/custom-sounds/server/methods/listCustomSounds.ts +++ b/apps/meteor/app/custom-sounds/server/methods/listCustomSounds.ts @@ -1,9 +1,9 @@ import type { ICustomSound } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { CustomSounds } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { listCustomSounds(): ICustomSound[]; diff --git a/apps/meteor/app/custom-sounds/server/methods/uploadCustomSound.ts b/apps/meteor/app/custom-sounds/server/methods/uploadCustomSound.ts index eee693634d7d..f955f373ed4d 100644 --- a/apps/meteor/app/custom-sounds/server/methods/uploadCustomSound.ts +++ b/apps/meteor/app/custom-sounds/server/methods/uploadCustomSound.ts @@ -1,6 +1,6 @@ import { api } from '@rocket.chat/core-services'; import type { RequiredField } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -8,7 +8,7 @@ import { RocketChatFile } from '../../../file/server'; import { RocketChatFileCustomSoundsInstance } from '../startup/custom-sounds'; import type { ICustomSoundData } from './insertOrUpdateSound'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { uploadCustomSound(binaryContent: string, contentType: string, soundData: RequiredField): void; diff --git a/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts b/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts index d8e3637575ab..1ff9ed1dc1ba 100644 --- a/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts +++ b/apps/meteor/app/discussion/server/hooks/propagateDiscussionMetadata.ts @@ -2,15 +2,15 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { Messages, Rooms, VideoConference } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; -import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; import { deleteRoom } from '../../../lib/server/functions/deleteRoom'; +import { notifyOnMessageChange } from '../../../lib/server/lib/notifyListener'; const updateAndNotifyParentRoomWithParentMessage = async (room: IRoom): Promise => { const { value: parentMessage } = await Messages.refreshDiscussionMetadata(room); if (!parentMessage) { return; } - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: parentMessage._id, data: parentMessage, }); @@ -22,7 +22,7 @@ const updateAndNotifyParentRoomWithParentMessage = async (room: IRoom): Promise< */ callbacks.add( 'afterSaveMessage', - async (message, { _id, prid }) => { + async (message, { room: { _id, prid } }) => { if (!prid) { return message; } diff --git a/apps/meteor/app/discussion/server/methods/createDiscussion.ts b/apps/meteor/app/discussion/server/methods/createDiscussion.ts index 18b42ba1a31f..96e0bd846390 100644 --- a/apps/meteor/app/discussion/server/methods/createDiscussion.ts +++ b/apps/meteor/app/discussion/server/methods/createDiscussion.ts @@ -1,11 +1,10 @@ import { Message } from '@rocket.chat/core-services'; import type { IMessage, IRoom, IUser, MessageAttachmentDefault } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Rooms, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import { callbacks } from '../../../../lib/callbacks'; import { i18n } from '../../../../server/lib/i18n'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { canSendMessageAsync } from '../../../authorization/server/functions/canSendMessage'; @@ -14,6 +13,7 @@ import { addUserToRoom } from '../../../lib/server/functions/addUserToRoom'; import { attachMessage } from '../../../lib/server/functions/attachMessage'; import { createRoom } from '../../../lib/server/functions/createRoom'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; +import { afterSaveMessageAsync } from '../../../lib/server/lib/afterSaveMessage'; import { settings } from '../../../settings/server'; const getParentRoom = async (rid: IRoom['_id']) => { @@ -27,13 +27,11 @@ async function createDiscussionMessage( drid: IRoom['_id'], msg: IMessage['msg'], messageEmbedded?: MessageAttachmentDefault, -): Promise { - const msgId = await Message.saveSystemMessage('discussion-created', rid, msg, user, { +): Promise { + return Message.saveSystemMessage('discussion-created', rid, msg, user, { drid, ...(messageEmbedded && { attachments: [messageEmbedded] }), }); - - return Messages.findOneById(msgId); } async function mentionMessage( @@ -191,12 +189,13 @@ const create = async ({ } if (discussionMsg) { - callbacks.runAsync('afterSaveMessage', discussionMsg, parentRoom); + afterSaveMessageAsync(discussionMsg, parentRoom); } + return discussion; }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { createDiscussion: typeof create; diff --git a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts index 860051c04d4d..22eccf03f407 100644 --- a/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts +++ b/apps/meteor/app/e2e/server/functions/handleSuggestedGroupKey.ts @@ -1,6 +1,8 @@ import { Rooms, Subscriptions } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; + export async function handleSuggestedGroupKey( handle: 'accept' | 'reject', rid: string, @@ -30,5 +32,8 @@ export async function handleSuggestedGroupKey( await Rooms.addUserIdToE2EEQueueByRoomIds([sub.rid], userId); } - await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); + const { modifiedCount } = await Subscriptions.unsetGroupE2ESuggestedKey(sub._id); + if (modifiedCount) { + void notifyOnSubscriptionChangedById(sub._id); + } } diff --git a/apps/meteor/app/e2e/server/methods/fetchMyKeys.ts b/apps/meteor/app/e2e/server/methods/fetchMyKeys.ts index 519317cb40fe..df1150cee846 100644 --- a/apps/meteor/app/e2e/server/methods/fetchMyKeys.ts +++ b/apps/meteor/app/e2e/server/methods/fetchMyKeys.ts @@ -1,8 +1,8 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.fetchMyKeys'(): { public_key?: string; private_key?: string }; diff --git a/apps/meteor/app/e2e/server/methods/getUsersOfRoomWithoutKey.ts b/apps/meteor/app/e2e/server/methods/getUsersOfRoomWithoutKey.ts index cc586676482f..1f1a21262de8 100644 --- a/apps/meteor/app/e2e/server/methods/getUsersOfRoomWithoutKey.ts +++ b/apps/meteor/app/e2e/server/methods/getUsersOfRoomWithoutKey.ts @@ -1,12 +1,12 @@ import type { IRoom, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.getUsersOfRoomWithoutKey'(rid: IRoom['_id']): { users: Pick[] }; diff --git a/apps/meteor/app/e2e/server/methods/requestSubscriptionKeys.ts b/apps/meteor/app/e2e/server/methods/requestSubscriptionKeys.ts index 8c5add77a0bf..cf899a5d64ad 100644 --- a/apps/meteor/app/e2e/server/methods/requestSubscriptionKeys.ts +++ b/apps/meteor/app/e2e/server/methods/requestSubscriptionKeys.ts @@ -1,9 +1,9 @@ import { api } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.requestSubscriptionKeys'(): boolean; diff --git a/apps/meteor/app/e2e/server/methods/resetOwnE2EKey.ts b/apps/meteor/app/e2e/server/methods/resetOwnE2EKey.ts index 365feb1ac95d..b1d40e48bb5e 100644 --- a/apps/meteor/app/e2e/server/methods/resetOwnE2EKey.ts +++ b/apps/meteor/app/e2e/server/methods/resetOwnE2EKey.ts @@ -1,10 +1,10 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { resetUserE2EEncriptionKey } from '../../../../server/lib/resetUserE2EKey'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.resetOwnE2EKey'(): Promise; diff --git a/apps/meteor/app/e2e/server/methods/setRoomKeyID.ts b/apps/meteor/app/e2e/server/methods/setRoomKeyID.ts index 6b0e685616b5..b52913e4f984 100644 --- a/apps/meteor/app/e2e/server/methods/setRoomKeyID.ts +++ b/apps/meteor/app/e2e/server/methods/setRoomKeyID.ts @@ -1,13 +1,13 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; import { notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.setRoomKeyID'(rid: IRoom['_id'], keyID: string): void; diff --git a/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts b/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts index 94d252601bc4..6ef35a063a28 100644 --- a/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts +++ b/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts @@ -1,8 +1,8 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.setUserPublicAndPrivateKeys'({ public_key, private_key }: { public_key: string; private_key: string; force?: boolean }): void; diff --git a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts index c856f8cf708a..87182f723e7d 100644 --- a/apps/meteor/app/e2e/server/methods/updateGroupKey.ts +++ b/apps/meteor/app/e2e/server/methods/updateGroupKey.ts @@ -1,10 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; +import { notifyOnSubscriptionChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'e2e.updateGroupKey'(rid: string, uid: string, key: string): Promise; @@ -25,12 +26,18 @@ Meteor.methods({ if (mySub) { // Setting the key to myself, can set directly to the final field if (userId === uid) { - await Subscriptions.setGroupE2EKey(mySub._id, key); + const setGroupE2EKeyResponse = await Subscriptions.setGroupE2EKey(mySub._id, key); + if (setGroupE2EKeyResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(mySub._id); + } return; } // uid also has subscription to this room - await Subscriptions.setGroupE2ESuggestedKey(uid, rid, key); + const { modifiedCount } = await Subscriptions.setGroupE2ESuggestedKey(uid, rid, key); + if (modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); + } } }, }); diff --git a/apps/meteor/app/emoji-custom/server/lib/insertOrUpdateEmoji.ts b/apps/meteor/app/emoji-custom/server/lib/insertOrUpdateEmoji.ts new file mode 100644 index 000000000000..7e838baee9b0 --- /dev/null +++ b/apps/meteor/app/emoji-custom/server/lib/insertOrUpdateEmoji.ts @@ -0,0 +1,148 @@ +import { api } from '@rocket.chat/core-services'; +import { EmojiCustom } from '@rocket.chat/models'; +import limax from 'limax'; +import { Meteor } from 'meteor/meteor'; +import _ from 'underscore'; + +import { trim } from '../../../../lib/utils/stringUtils'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; + +type EmojiData = { + _id?: string; + name: string; + aliases: string; + extension: string; + previousName?: string; + previousExtension?: string; + newFile?: boolean; +}; + +type EmojiDataWithParsedAliases = Omit & { _id: string; aliases: string[] }; + +export async function insertOrUpdateEmoji(userId: string | null, emojiData: EmojiData): Promise { + if (!userId || !(await hasPermissionAsync(userId, 'manage-emoji'))) { + throw new Meteor.Error('not_authorized'); + } + + if (!trim(emojiData.name)) { + throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { + method: 'insertOrUpdateEmoji', + field: 'Name', + }); + } + + emojiData.name = limax(emojiData.name, { replacement: '_' }); + emojiData.aliases = limax(emojiData.aliases, { replacement: '_' }); + + // allow all characters except colon, whitespace, comma, >, <, &, ", ', /, \, (, ) + // more practical than allowing specific sets of characters; also allows foreign languages + const nameValidation = /[\s,:><&"'\/\\\(\)]/; + const aliasValidation = /[:><&\|"'\/\\\(\)]/; + + // silently strip colon; this allows for uploading :emojiname: as emojiname + emojiData.name = emojiData.name.replace(/:/g, ''); + emojiData.aliases = emojiData.aliases.replace(/:/g, ''); + + if (nameValidation.test(emojiData.name)) { + throw new Meteor.Error('error-input-is-not-a-valid-field', `${emojiData.name} is not a valid name`, { + method: 'insertOrUpdateEmoji', + input: emojiData.name, + field: 'Name', + }); + } + + let aliases: string[] = []; + if (emojiData.aliases) { + if (aliasValidation.test(emojiData.aliases)) { + throw new Meteor.Error('error-input-is-not-a-valid-field', `${emojiData.aliases} is not a valid alias set`, { + method: 'insertOrUpdateEmoji', + input: emojiData.aliases, + field: 'Alias_Set', + }); + } + aliases = _.without(emojiData.aliases.split(/[\s,]/).filter(Boolean), emojiData.name); + } + + emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension; + + let matchingResults = []; + + if (emojiData._id) { + matchingResults = await EmojiCustom.findByNameOrAliasExceptID(emojiData.name, emojiData._id).toArray(); + for await (const alias of aliases) { + matchingResults = matchingResults.concat(await EmojiCustom.findByNameOrAliasExceptID(alias, emojiData._id).toArray()); + } + } else { + matchingResults = await EmojiCustom.findByNameOrAlias(emojiData.name).toArray(); + for await (const alias of aliases) { + matchingResults = matchingResults.concat(await EmojiCustom.findByNameOrAlias(alias).toArray()); + } + } + + if (matchingResults.length > 0) { + throw new Meteor.Error('Custom_Emoji_Error_Name_Or_Alias_Already_In_Use', 'The custom emoji or one of its aliases is already in use', { + method: 'insertOrUpdateEmoji', + }); + } + + if (typeof emojiData.extension === 'undefined') { + throw new Meteor.Error('error-the-field-is-required', 'The custom emoji file is required', { + method: 'insertOrUpdateEmoji', + }); + } + + if (!emojiData._id) { + // insert emoji + const createEmoji = { + name: emojiData.name, + aliases, + extension: emojiData.extension, + }; + + const _id = (await EmojiCustom.create(createEmoji)).insertedId; + + void api.broadcast('emoji.updateCustom', createEmoji); + + return { ...emojiData, ...createEmoji, _id }; + } + + // update emoji + if (emojiData.newFile) { + await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.extension}`)); + await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.previousExtension}`)); + await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.previousName}.${emojiData.extension}`)); + await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.previousName}.${emojiData.previousExtension}`)); + + await EmojiCustom.setExtension(emojiData._id, emojiData.extension); + } else if (emojiData.name !== emojiData.previousName) { + const rs = await RocketChatFileEmojiCustomInstance.getFileWithReadStream( + encodeURIComponent(`${emojiData.previousName}.${emojiData.previousExtension}`), + ); + if (rs !== null) { + await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.extension}`)); + const ws = RocketChatFileEmojiCustomInstance.createWriteStream( + encodeURIComponent(`${emojiData.name}.${emojiData.previousExtension}`), + rs.contentType, + ); + ws.on('end', () => + RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.previousName}.${emojiData.previousExtension}`)), + ); + rs.readStream.pipe(ws); + } + } + + if (emojiData.name !== emojiData.previousName) { + await EmojiCustom.setName(emojiData._id, emojiData.name); + } + + if (emojiData.aliases) { + await EmojiCustom.setAliases(emojiData._id, aliases); + } else { + await EmojiCustom.setAliases(emojiData._id, []); + } + + void api.broadcast('emoji.updateCustom', { ...emojiData, aliases }); + + return { ...emojiData, aliases } as EmojiDataWithParsedAliases; +} diff --git a/apps/meteor/app/emoji-custom/server/lib/uploadEmojiCustom.ts b/apps/meteor/app/emoji-custom/server/lib/uploadEmojiCustom.ts new file mode 100644 index 000000000000..07633eaa1a7d --- /dev/null +++ b/apps/meteor/app/emoji-custom/server/lib/uploadEmojiCustom.ts @@ -0,0 +1,77 @@ +import { api, Media } from '@rocket.chat/core-services'; +import limax from 'limax'; +import { Meteor } from 'meteor/meteor'; +import sharp from 'sharp'; + +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { RocketChatFile } from '../../../file/server'; +import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; + +const getFile = async (file: Buffer, extension: string) => { + if (extension !== 'svg+xml') { + return file; + } + + return sharp(file).png().toBuffer(); +}; + +type EmojiData = { + _id?: string; + name: string; + aliases?: string | string[]; + extension: string; + previousName?: string; + previousExtension?: string; + newFile?: boolean; +}; + +export async function uploadEmojiCustom(userId: string | null, binaryContent: string, contentType: string, emojiData: EmojiData) { + return uploadEmojiCustomWithBuffer(userId, Buffer.from(binaryContent, 'binary'), contentType, emojiData); +} + +export async function uploadEmojiCustomWithBuffer( + userId: string | null, + buffer: Buffer, + contentType: string, + emojiData: EmojiData, +): Promise { + // technically, since this method doesnt have any datatype validations, users can + // upload videos as emojis. The FE won't play them, but they will waste space for sure. + if (!userId || !(await hasPermissionAsync(userId, 'manage-emoji'))) { + throw new Meteor.Error('not_authorized'); + } + + emojiData.name = limax(emojiData.name, { replacement: '_' }); + // delete aliases for notification purposes. here, it is a string rather than an array + delete emojiData.aliases; + + const file = await getFile(buffer, emojiData.extension); + emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension; + + let fileBuffer; + // sharp doesn't support these formats without imagemagick or libvips installed + // so they will be stored as they are :( + if (['gif', 'x-icon', 'bmp', 'webm'].includes(emojiData.extension)) { + fileBuffer = file; + } else { + // This is to support the idea of having "sticker-like" emojis + const { data: resizedEmojiBuffer } = await Media.resizeFromBuffer(file, 512, 512, true, false, false, 'inside'); + fileBuffer = resizedEmojiBuffer; + } + + const rs = RocketChatFile.bufferToStream(fileBuffer); + await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.extension}`)); + + return new Promise((resolve) => { + const ws = RocketChatFileEmojiCustomInstance.createWriteStream( + encodeURIComponent(`${emojiData.name}.${emojiData.extension}`), + contentType, + ); + ws.on('end', () => { + setTimeout(() => api.broadcast('emoji.updateCustom', emojiData), 500); + resolve(); + }); + + rs.pipe(ws); + }); +} diff --git a/apps/meteor/app/emoji-custom/server/methods/deleteEmojiCustom.ts b/apps/meteor/app/emoji-custom/server/methods/deleteEmojiCustom.ts index 555d5544ab33..c16d3b82449b 100644 --- a/apps/meteor/app/emoji-custom/server/methods/deleteEmojiCustom.ts +++ b/apps/meteor/app/emoji-custom/server/methods/deleteEmojiCustom.ts @@ -1,13 +1,13 @@ import { api } from '@rocket.chat/core-services'; import type { ICustomEmojiDescriptor } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { EmojiCustom } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteEmojiCustom(emojiID: ICustomEmojiDescriptor['_id']): boolean; diff --git a/apps/meteor/app/emoji-custom/server/methods/insertOrUpdateEmoji.ts b/apps/meteor/app/emoji-custom/server/methods/insertOrUpdateEmoji.ts index 874180097cb9..1891d1b3ed95 100644 --- a/apps/meteor/app/emoji-custom/server/methods/insertOrUpdateEmoji.ts +++ b/apps/meteor/app/emoji-custom/server/methods/insertOrUpdateEmoji.ts @@ -1,15 +1,9 @@ -import { api } from '@rocket.chat/core-services'; -import { EmojiCustom } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; -import limax from 'limax'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -import _ from 'underscore'; -import { trim } from '../../../../lib/utils/stringUtils'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; +import { insertOrUpdateEmoji } from '../lib/insertOrUpdateEmoji'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { insertOrUpdateEmoji(emojiData: { @@ -26,130 +20,12 @@ declare module '@rocket.chat/ui-contexts' { Meteor.methods({ async insertOrUpdateEmoji(emojiData) { - if (!this.userId || !(await hasPermissionAsync(this.userId, 'manage-emoji'))) { - throw new Meteor.Error('not_authorized'); - } - - if (!trim(emojiData.name)) { - throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { - method: 'insertOrUpdateEmoji', - field: 'Name', - }); - } - - emojiData.name = limax(emojiData.name, { replacement: '_' }); - emojiData.aliases = limax(emojiData.aliases, { replacement: '_' }); - - // allow all characters except colon, whitespace, comma, >, <, &, ", ', /, \, (, ) - // more practical than allowing specific sets of characters; also allows foreign languages - const nameValidation = /[\s,:><&"'\/\\\(\)]/; - const aliasValidation = /[:><&\|"'\/\\\(\)]/; - - // silently strip colon; this allows for uploading :emojiname: as emojiname - emojiData.name = emojiData.name.replace(/:/g, ''); - emojiData.aliases = emojiData.aliases.replace(/:/g, ''); - - if (nameValidation.test(emojiData.name)) { - throw new Meteor.Error('error-input-is-not-a-valid-field', `${emojiData.name} is not a valid name`, { - method: 'insertOrUpdateEmoji', - input: emojiData.name, - field: 'Name', - }); - } - - let aliases: string[] = []; - if (emojiData.aliases) { - if (aliasValidation.test(emojiData.aliases)) { - throw new Meteor.Error('error-input-is-not-a-valid-field', `${emojiData.aliases} is not a valid alias set`, { - method: 'insertOrUpdateEmoji', - input: emojiData.aliases, - field: 'Alias_Set', - }); - } - aliases = _.without(emojiData.aliases.split(/[\s,]/).filter(Boolean), emojiData.name); - } - - emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension; - - let matchingResults = []; - - if (emojiData._id) { - matchingResults = await EmojiCustom.findByNameOrAliasExceptID(emojiData.name, emojiData._id).toArray(); - for await (const alias of aliases) { - matchingResults = matchingResults.concat(await EmojiCustom.findByNameOrAliasExceptID(alias, emojiData._id).toArray()); - } - } else { - matchingResults = await EmojiCustom.findByNameOrAlias(emojiData.name).toArray(); - for await (const alias of aliases) { - matchingResults = matchingResults.concat(await EmojiCustom.findByNameOrAlias(alias).toArray()); - } - } - - if (matchingResults.length > 0) { - throw new Meteor.Error( - 'Custom_Emoji_Error_Name_Or_Alias_Already_In_Use', - 'The custom emoji or one of its aliases is already in use', - { method: 'insertOrUpdateEmoji' }, - ); - } - - if (typeof emojiData.extension === 'undefined') { - throw new Meteor.Error('error-the-field-is-required', 'The custom emoji file is required', { - method: 'insertOrUpdateEmoji', - }); - } + const emoji = await insertOrUpdateEmoji(this.userId, emojiData); if (!emojiData._id) { - // insert emoji - const createEmoji = { - name: emojiData.name, - aliases, - extension: emojiData.extension, - }; - - const _id = (await EmojiCustom.create(createEmoji)).insertedId; - - void api.broadcast('emoji.updateCustom', createEmoji); - - return _id; + return emoji._id; } - // update emoji - if (emojiData.newFile) { - await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.extension}`)); - await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.previousExtension}`)); - await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.previousName}.${emojiData.extension}`)); - await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.previousName}.${emojiData.previousExtension}`)); - - await EmojiCustom.setExtension(emojiData._id, emojiData.extension); - } else if (emojiData.name !== emojiData.previousName) { - const rs = await RocketChatFileEmojiCustomInstance.getFileWithReadStream( - encodeURIComponent(`${emojiData.previousName}.${emojiData.previousExtension}`), - ); - if (rs !== null) { - await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.extension}`)); - const ws = RocketChatFileEmojiCustomInstance.createWriteStream( - encodeURIComponent(`${emojiData.name}.${emojiData.previousExtension}`), - rs.contentType, - ); - ws.on('end', () => - RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.previousName}.${emojiData.previousExtension}`)), - ); - rs.readStream.pipe(ws); - } - } - - if (emojiData.name !== emojiData.previousName) { - await EmojiCustom.setName(emojiData._id, emojiData.name); - } - - if (emojiData.aliases) { - await EmojiCustom.setAliases(emojiData._id, aliases); - } else { - await EmojiCustom.setAliases(emojiData._id, []); - } - - void api.broadcast('emoji.updateCustom', emojiData); - return true; + return !!emoji; }, }); diff --git a/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts b/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts index 485737b95f9b..a16d536d92d4 100644 --- a/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts +++ b/apps/meteor/app/emoji-custom/server/methods/listEmojiCustom.ts @@ -1,12 +1,12 @@ import type { IEmojiCustom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { EmojiCustom } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { listEmojiCustom(options?: { name?: string; aliases?: string[] }): IEmojiCustom[]; diff --git a/apps/meteor/app/emoji-custom/server/methods/uploadEmojiCustom.ts b/apps/meteor/app/emoji-custom/server/methods/uploadEmojiCustom.ts index ded98b7f5469..e387888b1311 100644 --- a/apps/meteor/app/emoji-custom/server/methods/uploadEmojiCustom.ts +++ b/apps/meteor/app/emoji-custom/server/methods/uploadEmojiCustom.ts @@ -1,22 +1,9 @@ -import { api, Media } from '@rocket.chat/core-services'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; -import limax from 'limax'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -import sharp from 'sharp'; -import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -import { RocketChatFile } from '../../../file/server'; -import { RocketChatFileEmojiCustomInstance } from '../startup/emoji-custom'; +import { uploadEmojiCustom } from '../lib/uploadEmojiCustom'; -const getFile = async (file: Buffer, extension: string) => { - if (extension !== 'svg+xml') { - return file; - } - - return sharp(file).png().toBuffer(); -}; - -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { uploadEmojiCustom( @@ -33,44 +20,6 @@ declare module '@rocket.chat/ui-contexts' { Meteor.methods({ async uploadEmojiCustom(binaryContent, contentType, emojiData) { - // technically, since this method doesnt have any datatype validations, users can - // upload videos as emojis. The FE won't play them, but they will waste space for sure. - if (!this.userId || !(await hasPermissionAsync(this.userId, 'manage-emoji'))) { - throw new Meteor.Error('not_authorized'); - } - - emojiData.name = limax(emojiData.name, { replacement: '_' }); - // delete aliases for notification purposes. here, it is a string rather than an array - delete emojiData.aliases; - - const file = await getFile(Buffer.from(binaryContent, 'binary'), emojiData.extension); - emojiData.extension = emojiData.extension === 'svg+xml' ? 'png' : emojiData.extension; - - let fileBuffer; - // sharp doesn't support these formats without imagemagick or libvips installed - // so they will be stored as they are :( - if (['gif', 'x-icon', 'bmp', 'webm'].includes(emojiData.extension)) { - fileBuffer = file; - } else { - // This is to support the idea of having "sticker-like" emojis - const { data: resizedEmojiBuffer } = await Media.resizeFromBuffer(file, 512, 512, true, false, false, 'inside'); - fileBuffer = resizedEmojiBuffer; - } - - const rs = RocketChatFile.bufferToStream(fileBuffer); - await RocketChatFileEmojiCustomInstance.deleteFile(encodeURIComponent(`${emojiData.name}.${emojiData.extension}`)); - - return new Promise((resolve) => { - const ws = RocketChatFileEmojiCustomInstance.createWriteStream( - encodeURIComponent(`${emojiData.name}.${emojiData.extension}`), - contentType, - ); - ws.on('end', () => { - setTimeout(() => api.broadcast('emoji.updateCustom', emojiData), 500); - resolve(); - }); - - rs.pipe(ws); - }); + await uploadEmojiCustom(this.userId, binaryContent, contentType, emojiData); }, }); diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index 4cab0b0c41e8..4f2a197b25ee 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -3,11 +3,16 @@ import { eventTypes } from '@rocket.chat/core-typings'; import { FederationServers, FederationRoomEvents, Rooms, Messages, Subscriptions, Users, ReadReceipts } from '@rocket.chat/models'; import EJSON from 'ejson'; -import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; import { API } from '../../../api/server'; import { FileUpload } from '../../../file-upload/server'; import { deleteRoom } from '../../../lib/server/functions/deleteRoom'; -import { notifyOnRoomChanged, notifyOnRoomChangedById } from '../../../lib/server/lib/notifyListener'; +import { + notifyOnMessageChange, + notifyOnRoomChanged, + notifyOnRoomChangedById, + notifyOnSubscriptionChanged, + notifyOnSubscriptionChangedById, +} from '../../../lib/server/lib/notifyListener'; import { notifyUsersOnMessage } from '../../../lib/server/lib/notifyUsersOnMessage'; import { sendAllNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage'; import { processThreads } from '../../../threads/server/hooks/aftersavemessage'; @@ -142,7 +147,10 @@ const eventHandlers = { const denormalizedSubscription = normalizers.denormalizeSubscription(subscription); // Create the subscription - await Subscriptions.insertOne(denormalizedSubscription); + const { insertedId } = await Subscriptions.insertOne(denormalizedSubscription); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId); + } federationAltered = true; } } catch (ex) { @@ -177,7 +185,10 @@ const eventHandlers = { } = event; // Remove the user's subscription - await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + if (deletedSubscription) { + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); + } // Refresh the servers list await FederationServers.refreshServers(); @@ -205,7 +216,10 @@ const eventHandlers = { } = event; // Remove the user's subscription - await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(roomId, user._id); + if (deletedSubscription) { + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); + } // Refresh the servers list await FederationServers.refreshServers(); @@ -294,8 +308,12 @@ const eventHandlers = { await processThreads(denormalizedMessage, room); - // Notify users - await notifyUsersOnMessage(denormalizedMessage, room); + const roomUpdater = Rooms.getUpdater(); + await notifyUsersOnMessage(denormalizedMessage, room, roomUpdater); + if (roomUpdater.hasChanges()) { + await Rooms.updateFromUpdater({ _id: room._id }, roomUpdater); + } + sendAllNotifications(denormalizedMessage, room); messageForNotification = denormalizedMessage; } catch (err) { @@ -303,7 +321,7 @@ const eventHandlers = { } } if (messageForNotification) { - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: messageForNotification._id, data: messageForNotification, }); @@ -334,7 +352,7 @@ const eventHandlers = { } else { // Update the message await Messages.updateOne({ _id: persistedMessage._id }, { $set: { msg: message.msg, federation: message.federation } }); - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: persistedMessage._id, data: { ...persistedMessage, @@ -404,7 +422,7 @@ const eventHandlers = { // Update the property await Messages.updateOne({ _id: messageId }, { $set: { [`reactions.${reaction}`]: reactionObj } }); - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: persistedMessage._id, data: { ...persistedMessage, @@ -462,7 +480,7 @@ const eventHandlers = { // Otherwise, update the property await Messages.updateOne({ _id: messageId }, { $set: { [`reactions.${reaction}`]: reactionObj } }); } - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: persistedMessage._id, data: { ...persistedMessage, diff --git a/apps/meteor/app/federation/server/hooks/afterSaveMessage.js b/apps/meteor/app/federation/server/hooks/afterSaveMessage.js index 7f67f4770686..20c64f87dda8 100644 --- a/apps/meteor/app/federation/server/hooks/afterSaveMessage.js +++ b/apps/meteor/app/federation/server/hooks/afterSaveMessage.js @@ -6,7 +6,7 @@ import { getFederationDomain } from '../lib/getFederationDomain'; import { clientLogger } from '../lib/logger'; import { normalizers } from '../normalizers'; -async function afterSaveMessage(message, room) { +async function afterSaveMessage(message, { room }) { // If there are not federated users on this room, ignore it if (!hasExternalDomain(room)) { return message; diff --git a/apps/meteor/app/federation/server/methods/dashboard.ts b/apps/meteor/app/federation/server/methods/dashboard.ts index 90a050556893..eacb42b24cbb 100644 --- a/apps/meteor/app/federation/server/methods/dashboard.ts +++ b/apps/meteor/app/federation/server/methods/dashboard.ts @@ -1,10 +1,10 @@ import type { IFederationServer } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { federationGetServers, federationGetOverviewData } from '../functions/dashboard'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'federation:getServers': () => { data: IFederationServer[] }; diff --git a/apps/meteor/app/federation/server/methods/loadContextEvents.ts b/apps/meteor/app/federation/server/methods/loadContextEvents.ts index 9e286bbf913a..e20acb7e37c9 100644 --- a/apps/meteor/app/federation/server/methods/loadContextEvents.ts +++ b/apps/meteor/app/federation/server/methods/loadContextEvents.ts @@ -1,11 +1,11 @@ import type { IFederationEvent } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { FederationRoomEvents } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'federation:loadContextEvents'(latestEventTimestamp: number): IFederationEvent[]; diff --git a/apps/meteor/app/federation/server/methods/testSetup.ts b/apps/meteor/app/federation/server/methods/testSetup.ts index 93e501535474..bc50ef158194 100644 --- a/apps/meteor/app/federation/server/methods/testSetup.ts +++ b/apps/meteor/app/federation/server/methods/testSetup.ts @@ -1,11 +1,11 @@ import { eventTypes } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { dispatchEvent } from '../handler'; import { getFederationDomain } from '../lib/getFederationDomain'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { FEDERATION_Test_Setup(): { message: string }; diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index 08e2ccb0a52b..8714c71f20d6 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -10,7 +10,7 @@ import URL from 'url'; import { hashLoginToken } from '@rocket.chat/account-utils'; import { Apps, AppEvents } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; -import type { IUpload } from '@rocket.chat/core-typings'; +import { isE2EEUpload, type IUpload } from '@rocket.chat/core-typings'; import { Users, Avatars, UserDataFiles, Uploads, Settings, Subscriptions, Messages, Rooms } from '@rocket.chat/models'; import type { NextFunction } from 'connect'; import filesize from 'filesize'; @@ -170,7 +170,13 @@ export const FileUpload = { throw new Meteor.Error('error-file-too-large', reason); } - if (!fileUploadIsValidContentType(file?.type)) { + if (!settings.get('E2E_Enable_Encrypt_Files') && isE2EEUpload(file)) { + const reason = i18n.t('Encrypted_file_not_allowed', { lng: language }); + throw new Meteor.Error('error-invalid-file-type', reason); + } + + // E2EE files are of type - application/octet-stream, application/octet-stream is whitelisted for E2EE files. + if (!fileUploadIsValidContentType(file?.type, isE2EEUpload(file) ? 'application/octet-stream' : undefined)) { const reason = i18n.t('File_type_is_not_accepted', { lng: language }); throw new Meteor.Error('error-invalid-file-type', reason); } diff --git a/apps/meteor/app/file-upload/server/methods/getS3FileUrl.ts b/apps/meteor/app/file-upload/server/methods/getS3FileUrl.ts index fdb8d131916d..3e9bcd192674 100644 --- a/apps/meteor/app/file-upload/server/methods/getS3FileUrl.ts +++ b/apps/meteor/app/file-upload/server/methods/getS3FileUrl.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms, Uploads } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -7,7 +7,7 @@ import { UploadFS } from '../../../../server/ufs'; import { canAccessRoomAsync } from '../../../authorization/server'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getS3FileUrl(fileId: string): string; diff --git a/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts b/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts index 485528a5e62f..73dfd0216a73 100644 --- a/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts +++ b/apps/meteor/app/file-upload/server/methods/sendFileMessage.ts @@ -7,8 +7,8 @@ import type { FilesAndAttachments, IMessage, } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms, Uploads, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -141,7 +141,7 @@ export const parseFileIntoMessageAttachments = async ( return { files, attachments }; }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { sendFileMessage: (roomId: string, _store: string, file: Partial, msgData?: Record) => boolean; diff --git a/apps/meteor/app/iframe-login/server/iframe_server.ts b/apps/meteor/app/iframe-login/server/iframe_server.ts index c7e67edf9deb..b3e661cf6ff3 100644 --- a/apps/meteor/app/iframe-login/server/iframe_server.ts +++ b/apps/meteor/app/iframe-login/server/iframe_server.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Accounts } from 'meteor/accounts-base'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -23,7 +23,7 @@ Accounts.registerLoginHandler('iframe', async (result) => { } }); -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'OAuth.retrieveCredential'(credentialToken: string, credentialSecret: string): unknown; diff --git a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts index 7b1e71eaa0f0..6de47e33b2b6 100644 --- a/apps/meteor/app/importer/server/classes/ImportDataConverter.ts +++ b/apps/meteor/app/importer/server/classes/ImportDataConverter.ts @@ -28,7 +28,7 @@ import { generateUsernameSuggestion } from '../../../lib/server/functions/getUse import { insertMessage } from '../../../lib/server/functions/insertMessage'; import { saveUserIdentity } from '../../../lib/server/functions/saveUserIdentity'; import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus'; -import { notifyOnUserChange } from '../../../lib/server/lib/notifyListener'; +import { notifyOnSubscriptionChangedByRoomId, notifyOnUserChange } from '../../../lib/server/lib/notifyListener'; import { createChannelMethod } from '../../../lib/server/methods/createChannel'; import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup'; import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; @@ -1161,8 +1161,11 @@ export class ImportDataConverter { } async archiveRoomById(rid: string) { - await Rooms.archiveById(rid); - await Subscriptions.archiveByRoomId(rid); + const responses = await Promise.all([Rooms.archiveById(rid), Subscriptions.archiveByRoomId(rid)]); + + if (responses[1]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } } async convertData(startedByUserId: string, callbacks: IConversionCallbacks = {}): Promise { diff --git a/apps/meteor/app/importer/server/methods/downloadPublicImportFile.ts b/apps/meteor/app/importer/server/methods/downloadPublicImportFile.ts index 81e06ec8eb0f..d2da85c7cfd4 100644 --- a/apps/meteor/app/importer/server/methods/downloadPublicImportFile.ts +++ b/apps/meteor/app/importer/server/methods/downloadPublicImportFile.ts @@ -4,7 +4,7 @@ import https from 'https'; import { Import } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { Importers } from '..'; @@ -75,7 +75,7 @@ export const executeDownloadPublicImportFile = async (userId: IUser['_id'], file } }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { downloadPublicImportFile(fileUrl: string, importerKey: string): void; diff --git a/apps/meteor/app/importer/server/methods/getImportFileData.ts b/apps/meteor/app/importer/server/methods/getImportFileData.ts index 03f9a53abe6c..1d36f7fc5a5e 100644 --- a/apps/meteor/app/importer/server/methods/getImportFileData.ts +++ b/apps/meteor/app/importer/server/methods/getImportFileData.ts @@ -2,8 +2,8 @@ import fs from 'fs'; import path from 'path'; import type { IImportProgress, IImporterSelection } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Imports } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { Importers } from '..'; @@ -61,7 +61,7 @@ export const executeGetImportFileData = async (): Promise => { return instance.getProgress(); }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getImportProgress(): IImportProgress; diff --git a/apps/meteor/app/importer/server/methods/getLatestImportOperations.ts b/apps/meteor/app/importer/server/methods/getLatestImportOperations.ts index edf021a19eac..0471cb57955d 100644 --- a/apps/meteor/app/importer/server/methods/getLatestImportOperations.ts +++ b/apps/meteor/app/importer/server/methods/getLatestImportOperations.ts @@ -1,6 +1,6 @@ import type { IImport } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Imports } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -17,7 +17,7 @@ export const executeGetLatestImportOperations = async () => { return data.toArray(); }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getLatestImportOperations(): IImport[]; diff --git a/apps/meteor/app/importer/server/methods/startImport.ts b/apps/meteor/app/importer/server/methods/startImport.ts index af91295ede29..bbb5ce76ad1c 100644 --- a/apps/meteor/app/importer/server/methods/startImport.ts +++ b/apps/meteor/app/importer/server/methods/startImport.ts @@ -1,7 +1,7 @@ import type { IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Imports } from '@rocket.chat/models'; import type { StartImportParamsPOST } from '@rocket.chat/rest-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { Importers, Selection, SelectionChannel, SelectionUser } from '..'; @@ -32,7 +32,7 @@ export const executeStartImport = async ({ input }: StartImportParamsPOST, start await instance.startImport(selection, startedByUserId); }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { startImport(params: StartImportParamsPOST): void; diff --git a/apps/meteor/app/importer/server/methods/uploadImportFile.ts b/apps/meteor/app/importer/server/methods/uploadImportFile.ts index d6ded455793b..df5d2af883fa 100644 --- a/apps/meteor/app/importer/server/methods/uploadImportFile.ts +++ b/apps/meteor/app/importer/server/methods/uploadImportFile.ts @@ -1,6 +1,6 @@ import { Import } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { Importers } from '..'; @@ -55,7 +55,7 @@ export const executeUploadImportFile = async ( await instance.updateProgress(ProgressStep.FILE_LOADED); }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { uploadImportFile(binaryContent: string, contentType: string, fileName: string, importerKey: string): void; diff --git a/apps/meteor/app/integrations/server/methods/clearIntegrationHistory.ts b/apps/meteor/app/integrations/server/methods/clearIntegrationHistory.ts index 5b8f13ef1a3a..a30bb9b5ee9a 100644 --- a/apps/meteor/app/integrations/server/methods/clearIntegrationHistory.ts +++ b/apps/meteor/app/integrations/server/methods/clearIntegrationHistory.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations, IntegrationHistory } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import notifications from '../../../notifications/server/lib/Notifications'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { clearIntegrationHistory(integrationId: string): Promise; diff --git a/apps/meteor/app/integrations/server/methods/incoming/addIncomingIntegration.ts b/apps/meteor/app/integrations/server/methods/incoming/addIncomingIntegration.ts index db058bec960b..8f1f1b5dd576 100644 --- a/apps/meteor/app/integrations/server/methods/incoming/addIncomingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/incoming/addIncomingIntegration.ts @@ -1,7 +1,7 @@ import type { INewIncomingIntegration, IIncomingIntegration } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations, Roles, Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Babel } from 'meteor/babel-compiler'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -13,7 +13,7 @@ import { validateScriptEngine, isScriptEngineFrozen } from '../../lib/validateSc const validChannelChars = ['@', '#']; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addIncomingIntegration(integration: INewIncomingIntegration): Promise; diff --git a/apps/meteor/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts b/apps/meteor/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts index e73a46bb27db..9a116f51bbaf 100644 --- a/apps/meteor/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/incoming/deleteIncomingIntegration.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { notifyOnIntegrationChangedById } from '../../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteIncomingIntegration(integrationId: string): Promise; diff --git a/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts b/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts index 0ea5028130da..c94b7b9fd7a8 100644 --- a/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/incoming/updateIncomingIntegration.ts @@ -1,7 +1,7 @@ import type { IIntegration, INewIncomingIntegration, IUpdateIncomingIntegration } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations, Roles, Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { wrapExceptions } from '@rocket.chat/tools'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Babel } from 'meteor/babel-compiler'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; @@ -12,7 +12,7 @@ import { isScriptEngineFrozen, validateScriptEngine } from '../../lib/validateSc const validChannelChars = ['@', '#']; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { updateIncomingIntegration( diff --git a/apps/meteor/app/integrations/server/methods/outgoing/addOutgoingIntegration.ts b/apps/meteor/app/integrations/server/methods/outgoing/addOutgoingIntegration.ts index c8dc31e08446..dc54c76b1897 100644 --- a/apps/meteor/app/integrations/server/methods/outgoing/addOutgoingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/outgoing/addOutgoingIntegration.ts @@ -1,6 +1,6 @@ import type { INewOutgoingIntegration, IOutgoingIntegration } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -9,7 +9,7 @@ import { notifyOnIntegrationChanged } from '../../../../lib/server/lib/notifyLis import { validateOutgoingIntegration } from '../../lib/validateOutgoingIntegration'; import { validateScriptEngine } from '../../lib/validateScriptEngine'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addOutgoingIntegration(integration: INewOutgoingIntegration): Promise; diff --git a/apps/meteor/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts b/apps/meteor/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts index c9f2211d835b..46d4c65159c0 100644 --- a/apps/meteor/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/outgoing/deleteOutgoingIntegration.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations, IntegrationHistory } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { notifyOnIntegrationChangedById } from '../../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteOutgoingIntegration(integrationId: string): Promise; diff --git a/apps/meteor/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts b/apps/meteor/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts index 417c308ca6cb..83c13f2d981b 100644 --- a/apps/meteor/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/outgoing/replayOutgoingIntegration.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations, IntegrationHistory } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { triggerHandler } from '../../lib/triggerHandler'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { replayOutgoingIntegration(params: { integrationId: string; historyId: string }): Promise; diff --git a/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts b/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts index 116dbd043039..c9b1f4ed7d64 100644 --- a/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts +++ b/apps/meteor/app/integrations/server/methods/outgoing/updateOutgoingIntegration.ts @@ -1,7 +1,7 @@ import type { IIntegration, INewOutgoingIntegration, IUpdateOutgoingIntegration } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Integrations, Users } from '@rocket.chat/models'; import { wrapExceptions } from '@rocket.chat/tools'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; @@ -9,7 +9,7 @@ import { notifyOnIntegrationChanged } from '../../../../lib/server/lib/notifyLis import { validateOutgoingIntegration } from '../../lib/validateOutgoingIntegration'; import { isScriptEngineFrozen, validateScriptEngine } from '../../lib/validateScriptEngine'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { updateOutgoingIntegration( diff --git a/apps/meteor/app/irc/server/methods/resetIrcConnection.ts b/apps/meteor/app/irc/server/methods/resetIrcConnection.ts index 24eef975d5d5..aaaeef1c06b8 100644 --- a/apps/meteor/app/irc/server/methods/resetIrcConnection.ts +++ b/apps/meteor/app/irc/server/methods/resetIrcConnection.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import Bridge from '../irc-bridge'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { resetIrcConnection(): { message: string; params: unknown[] }; diff --git a/apps/meteor/app/lib/client/methods/sendMessage.ts b/apps/meteor/app/lib/client/methods/sendMessage.ts index e824c9e491af..bdaca587493a 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.ts +++ b/apps/meteor/app/lib/client/methods/sendMessage.ts @@ -1,5 +1,5 @@ import type { IMessage, IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { onClientMessageReceived } from '../../../../client/lib/onClientMessageReceived'; diff --git a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts index b6d977dc36e2..3fb9c419aa5f 100644 --- a/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts +++ b/apps/meteor/app/lib/server/functions/addUserToDefaultChannels.ts @@ -5,6 +5,7 @@ import { Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; +import { notifyOnSubscriptionChangedById } from '../lib/notifyListener'; import { getDefaultChannels } from './getDefaultChannels'; export const addUserToDefaultChannels = async function (user: IUser, silenced?: boolean): Promise { @@ -14,8 +15,9 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?: for await (const room of defaultRooms) { if (!(await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, { projection: { _id: 1 } }))) { const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(user); + // Add a subscription to this user - await Subscriptions.createWithRoomAndUser(room, user, { + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, user, { ts: new Date(), open: true, alert: true, @@ -27,6 +29,10 @@ export const addUserToDefaultChannels = async function (user: IUser, silenced?: ...getDefaultSubscriptionPref(user), }); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } + // Insert user joined message if (!silenced) { await Message.saveSystemMessage('uj', room._id, user.username || '', user); diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts index b6ffc0ca4629..e6ca7b2a8b4d 100644 --- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts +++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts @@ -11,7 +11,7 @@ import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/li import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener'; export const addUserToRoom = async function ( rid: string, @@ -82,7 +82,7 @@ export const addUserToRoom = async function ( const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(userToBeAdded); - await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, { + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, userToBeAdded as IUser, { ts: now, open: true, alert: !skipAlertSound, @@ -93,6 +93,10 @@ export const addUserToRoom = async function ( ...getDefaultSubscriptionPref(userToBeAdded as IUser), }); + if (insertedId) { + void notifyOnSubscriptionChangedById(insertedId, 'inserted'); + } + void notifyOnRoomChangedById(rid); if (!userToBeAdded.username) { diff --git a/apps/meteor/app/lib/server/functions/archiveRoom.ts b/apps/meteor/app/lib/server/functions/archiveRoom.ts index 3378d69f99ff..46fd7a1ac35b 100644 --- a/apps/meteor/app/lib/server/functions/archiveRoom.ts +++ b/apps/meteor/app/lib/server/functions/archiveRoom.ts @@ -3,11 +3,16 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; -import { notifyOnRoomChanged } from '../lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; export const archiveRoom = async function (rid: string, user: IMessage['u']): Promise { await Rooms.archiveById(rid); - await Subscriptions.archiveByRoomId(rid); + + const archiveResponse = await Subscriptions.archiveByRoomId(rid); + if (archiveResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + await Message.saveSystemMessage('room-archived', rid, '', user); const room = await Rooms.findOneById(rid); diff --git a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts index 2bfb1086c635..765a03cad87b 100644 --- a/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts +++ b/apps/meteor/app/lib/server/functions/cleanRoomHistory.ts @@ -4,7 +4,7 @@ import { Messages, Rooms, Subscriptions, ReadReceipts, Users } from '@rocket.cha import { i18n } from '../../../../server/lib/i18n'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedById } from '../lib/notifyListener'; import { deleteRoom } from './deleteRoom'; export async function cleanRoomHistory({ @@ -75,6 +75,7 @@ export async function cleanRoomHistory({ if (!ignoreThreads) { const threads = new Set(); + await Messages.findThreadsByRoomIdPinnedTimestampAndUsers( { rid, pinned: excludePinned, ignoreDiscussion, ts, users: fromUsers }, { projection: { _id: 1 } }, @@ -83,7 +84,14 @@ export async function cleanRoomHistory({ }); if (threads.size > 0) { - await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); + const subscriptionIds: string[] = ( + await Subscriptions.findUnreadThreadsByRoomId(rid, [...threads], { projection: { _id: 1 } }).toArray() + ).map(({ _id }) => _id); + + const { modifiedCount } = await Subscriptions.removeUnreadThreadsByRoomId(rid, [...threads]); + if (modifiedCount) { + subscriptionIds.forEach((id) => notifyOnSubscriptionChangedById(id)); + } } } diff --git a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts index b716be044d57..263b137ae00c 100644 --- a/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts +++ b/apps/meteor/app/lib/server/functions/closeLivechatRoom.ts @@ -5,6 +5,7 @@ import { LivechatRooms, Subscriptions } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import type { CloseRoomParams } from '../../../livechat/server/lib/LivechatTyped'; import { Livechat } from '../../../livechat/server/lib/LivechatTyped'; +import { notifyOnSubscriptionChanged } from '../lib/notifyListener'; export const closeLivechatRoom = async ( user: IUser, @@ -34,9 +35,12 @@ export const closeLivechatRoom = async ( } if (!room.open) { - const subscriptionsLeft = await Subscriptions.countByRoomId(roomId); - if (subscriptionsLeft) { - await Subscriptions.removeByRoomId(roomId); + const { deletedCount } = await Subscriptions.removeByRoomId(roomId, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }); + if (deletedCount) { return; } throw new Error('error-room-already-closed'); diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index 67c6328f38f4..f77ee1f55901 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -11,7 +11,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { isTruthy } from '../../../../lib/isTruthy'; import { settings } from '../../../settings/server'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomIdAndUserId } from '../lib/notifyListener'; const generateSubscription = ( fname: string, @@ -135,7 +135,7 @@ export async function createDirectRoom( if (roomMembers.length === 1) { // dm to yourself - await Subscriptions.updateOne( + const { modifiedCount, upsertedCount } = await Subscriptions.updateOne( { rid, 'u._id': roomMembers[0]._id }, { $set: { open: true }, @@ -146,6 +146,9 @@ export async function createDirectRoom( }, { upsert: true }, ); + if (modifiedCount || upsertedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, roomMembers[0]._id, modifiedCount ? 'updated' : 'inserted'); + } } else { const memberIds = roomMembers.map((member) => member._id); const membersWithPreferences: IUser[] = await Users.find( @@ -155,7 +158,7 @@ export async function createDirectRoom( for await (const member of membersWithPreferences) { const otherMembers = sortedMembers.filter(({ _id }) => _id !== member._id); - await Subscriptions.updateOne( + const { modifiedCount, upsertedCount } = await Subscriptions.updateOne( { rid, 'u._id': member._id }, { ...(options?.creator === member._id && { $set: { open: true } }), @@ -166,6 +169,9 @@ export async function createDirectRoom( }, { upsert: true }, ); + if (modifiedCount || upsertedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, member._id, modifiedCount ? 'updated' : 'inserted'); + } } } diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 19e5fb2f9489..769155b66b60 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -1,7 +1,7 @@ /* eslint-disable complexity */ import { AppEvents, Apps } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; -import { Message, Team } from '@rocket.chat/core-services'; +import { Federation, FederationEE, License, Message, Team } from '@rocket.chat/core-services'; import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services'; import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; @@ -12,7 +12,7 @@ import { beforeCreateRoomCallback } from '../../../../lib/callbacks/beforeCreate import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref'; import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName'; -import { notifyOnRoomChanged } from '../lib/notifyListener'; +import { notifyOnRoomChanged, notifyOnSubscriptionChangedById } from '../lib/notifyListener'; import { createDirectRoom } from './createDirectRoom'; const isValidName = (name: unknown): name is string => { @@ -47,7 +47,11 @@ async function createUsersSubscriptions({ ...getDefaultSubscriptionPref(owner), }; - await Subscriptions.createWithRoomAndUser(room, owner, extra); + const { insertedId } = await Subscriptions.createWithRoomAndUser(room, owner, extra); + + if (insertedId) { + await notifyOnRoomChanged(room, 'inserted'); + } return; } @@ -98,7 +102,9 @@ async function createUsersSubscriptions({ await Users.addRoomByUserIds(memberIds, room._id); } - await Subscriptions.createWithRoomAndManyUsers(room, subs); + const { insertedIds } = await Subscriptions.createWithRoomAndManyUsers(room, subs); + + Object.values(insertedIds).forEach((subId) => notifyOnSubscriptionChangedById(subId, 'inserted')); await Rooms.incUsersCountById(room._id, subs.length); } @@ -112,6 +118,7 @@ export const createRoom = async ( readOnly?: boolean, roomExtraData?: Partial, options?: ICreateRoomParams['options'], + sidepanel?: ICreateRoomParams['sidepanel'], ): Promise< ICreatedRoom & { rid: string; @@ -187,6 +194,7 @@ export const createRoom = async ( }, ts: now, ro: readOnly === true, + sidepanel, }; if (teamId) { @@ -222,6 +230,13 @@ export const createRoom = async ( Object.assign(roomProps, eventResult); } + const shouldBeHandledByFederation = roomProps.federated === true || owner.username.includes(':'); + + if (shouldBeHandledByFederation) { + const federation = (await License.hasValidLicense()) ? FederationEE : Federation; + await federation.beforeCreateRoom(roomProps); + } + if (type === 'c') { await callbacks.run('beforeCreateChannel', owner, roomProps); } @@ -230,8 +245,6 @@ export const createRoom = async ( void notifyOnRoomChanged(room, 'inserted'); - const shouldBeHandledByFederation = room.federated === true || owner.username.includes(':'); - await createUsersSubscriptions({ room, members, now, owner, options, shouldBeHandledByFederation }); if (type === 'c') { diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index e977874b3454..a91e77858043 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -1,15 +1,14 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; -import { api } from '@rocket.chat/core-services'; +import { api, Message } from '@rocket.chat/core-services'; import type { AtLeast, IMessage, IUser } from '@rocket.chat/core-typings'; import { Messages, Rooms, Uploads, Users, ReadReceipts } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; -import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; import { canDeleteMessageAsync } from '../../../authorization/server/functions/canDeleteMessage'; import { FileUpload } from '../../../file-upload/server'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnMessageChange } from '../lib/notifyListener'; export const deleteMessageValidatingPermission = async (message: AtLeast, userId: IUser['_id']): Promise => { if (!message?._id) { @@ -36,10 +35,18 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise { await FileUpload.removeFilesByRoomId(rid); + await Messages.removeByRoomId(rid); + await callbacks.run('beforeDeleteRoom', rid); - await Subscriptions.removeByRoomId(rid); + + await Subscriptions.removeByRoomId(rid, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }); + await FileUpload.getStore('Avatars').deleteByRoomId(rid); + await callbacks.run('afterDeleteRoom', rid); - await Rooms.removeById(rid); - void notifyOnRoomChangedById(rid, 'removed'); + const { deletedCount } = await Rooms.removeById(rid); + if (deletedCount) { + void notifyOnRoomChangedById(rid, 'removed'); + } }; diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index d6457664671a..483085d40811 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -1,5 +1,5 @@ import { api } from '@rocket.chat/core-services'; -import type { IUser } from '@rocket.chat/core-typings'; +import { isUserFederated, type IUser } from '@rocket.chat/core-typings'; import { Integrations, FederationServers, @@ -12,6 +12,7 @@ import { ReadReceipts, LivechatUnitMonitors, ModerationReports, + MatrixBridgedUser, } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -46,6 +47,19 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele return; } + if (isUserFederated(user)) { + throw new Meteor.Error('error-not-allowed', 'Deleting federated, external user is not allowed', { + method: 'deleteUser', + }); + } + + const remoteUser = await MatrixBridgedUser.getExternalUserIdByLocalUserId(userId); + if (remoteUser) { + throw new Meteor.Error('error-not-allowed', 'User participated in federation, this user can only be deactivated permanently', { + method: 'deleteUser', + }); + } + const subscribedRooms = await getSubscribedRoomsForUserWithDetails(userId); if (shouldRemoveOrChangeOwner(subscribedRooms) && !confirmRelinquish) { @@ -98,7 +112,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele const rids = subscribedRooms.map((room) => room.rid); void notifyOnRoomChangedById(rids); - await Subscriptions.removeByUserId(userId); // Remove user subscriptions + await Subscriptions.removeByUserId(userId); // Remove user as livechat agent if (user.roles.includes('livechat-agent')) { diff --git a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts index 75b232462077..8f1981ca386d 100644 --- a/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts +++ b/apps/meteor/app/lib/server/functions/relinquishRoomOwnerships.ts @@ -1,6 +1,7 @@ import { Messages, Roles, Rooms, Subscriptions, ReadReceipts } from '@rocket.chat/models'; import { FileUpload } from '../../../file-upload/server'; +import { notifyOnSubscriptionChanged } from '../lib/notifyListener'; import type { SubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; const bulkRoomCleanUp = async (rids: string[]): Promise => { @@ -8,7 +9,11 @@ const bulkRoomCleanUp = async (rids: string[]): Promise => { await Promise.all(rids.map((rid) => FileUpload.removeFilesByRoomId(rid))); return Promise.all([ - Subscriptions.removeByRoomIds(rids), + Subscriptions.removeByRoomIds(rids, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }), Messages.removeByRoomIds(rids), ReadReceipts.removeByRoomIds(rids), Rooms.removeByIds(rids), diff --git a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts index c55ee382f10c..5800cb68af81 100644 --- a/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts +++ b/apps/meteor/app/lib/server/functions/removeUserFromRoom.ts @@ -1,6 +1,6 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; -import { Message, Team } from '@rocket.chat/core-services'; +import { Message, Team, Room } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; import { Subscriptions, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -8,7 +8,7 @@ import { Meteor } from 'meteor/meteor'; import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback'; import { beforeLeaveRoomCallback } from '../../../../lib/callbacks/beforeLeaveRoomCallback'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChanged } from '../lib/notifyListener'; export const removeUserFromRoom = async function (rid: string, user: IUser, options?: { byUser: IUser }): Promise { const room = await Rooms.findOneById(rid); @@ -27,6 +27,9 @@ export const removeUserFromRoom = async function (rid: string, user: IUser, opti throw error; } + await Room.beforeLeave(room); + + // TODO: move before callbacks to service await beforeLeaveRoomCallback.run(user, room); const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, user._id, { @@ -56,7 +59,10 @@ export const removeUserFromRoom = async function (rid: string, user: IUser, opti await Message.saveSystemMessage('command', rid, 'survey', user); } - await Subscriptions.removeByRoomIdAndUserId(rid, user._id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, user._id); + if (deletedSubscription) { + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); + } if (room.teamId && room.teamMain) { await Team.removeMember(room.teamId, user._id); diff --git a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts index 4a0ac005e55c..5383048f13bd 100644 --- a/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts +++ b/apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts @@ -5,6 +5,7 @@ import type { UpdateFilter } from 'mongodb'; import { trim } from '../../../../lib/utils/stringUtils'; import { settings } from '../../../settings/server'; +import { notifyOnSubscriptionChangedByUserIdAndRoomType } from '../lib/notifyListener'; export const saveCustomFieldsWithoutValidation = async function (userId: string, formData: Record): Promise { if (trim(settings.get('Accounts_CustomFields')) !== '') { @@ -22,7 +23,10 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string, await Users.setCustomFields(userId, customFields); // Update customFields of all Direct Messages' Rooms for userId - await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields); + const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields); + if (setCustomFieldsResponse.modifiedCount) { + void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd'); + } for await (const fieldName of Object.keys(customFields)) { if (!customFieldsMeta[fieldName].modifyRecordField) { diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts index 0b9ff21e53e3..1729a1ba8abd 100644 --- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts +++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts @@ -3,7 +3,11 @@ import { Messages, VideoConference, LivechatDepartmentAgents, Rooms, Subscriptio import { SystemLogger } from '../../../../server/lib/logger/system'; import { FileUpload } from '../../../file-upload/server'; -import { notifyOnRoomChangedByUsernamesOrUids } from '../lib/notifyListener'; +import { + notifyOnRoomChangedByUsernamesOrUids, + notifyOnSubscriptionChangedByUserId, + notifyOnSubscriptionChangedByNameAndRoomType, +} from '../lib/notifyListener'; import { _setRealName } from './setRealName'; import { _setUsername } from './setUsername'; import { updateGroupDMsName } from './updateGroupDMsName'; @@ -129,20 +133,38 @@ async function updateUsernameReferences({ await Messages.updateUsernameAndMessageOfMentionByIdAndOldUsername(msg._id, previousUsername, username, updatedMsg); } - await Rooms.replaceUsername(previousUsername, username); - await Rooms.replaceMutedUsername(previousUsername, username); - await Rooms.replaceUsernameOfUserByUserId(user._id, username); - await Subscriptions.setUserUsernameByUserId(user._id, username); + const responses = await Promise.all([ + Rooms.replaceUsername(previousUsername, username), + Rooms.replaceMutedUsername(previousUsername, username), + Rooms.replaceUsernameOfUserByUserId(user._id, username), + Subscriptions.setUserUsernameByUserId(user._id, username), + LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username), + ]); - await LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username); + if (responses[3]?.modifiedCount) { + void notifyOnSubscriptionChangedByUserId(user._id); + } - void notifyOnRoomChangedByUsernamesOrUids([user._id], [previousUsername, username]); + if (responses[0]?.modifiedCount || responses[1]?.modifiedCount || responses[2]?.modifiedCount) { + void notifyOnRoomChangedByUsernamesOrUids([user._id], [previousUsername, username]); + } } // update other references if either the name or username has changed if (usernameChanged || nameChanged) { // update name and fname of 1-on-1 direct messages - await Subscriptions.updateDirectNameAndFnameByName(previousUsername, rawUsername && username, rawName && name); + const updateDirectNameResponse = await Subscriptions.updateDirectNameAndFnameByName( + previousUsername, + rawUsername && username, + rawName && name, + ); + + if (updateDirectNameResponse?.modifiedCount) { + void notifyOnSubscriptionChangedByNameAndRoomType({ + t: 'd', + name: username, + }); + } // update name and fname of group direct messages await updateGroupDMsName(user); diff --git a/apps/meteor/app/lib/server/functions/sendMessage.ts b/apps/meteor/app/lib/server/functions/sendMessage.ts index d850f69a93a4..aba5ddb7264c 100644 --- a/apps/meteor/app/lib/server/functions/sendMessage.ts +++ b/apps/meteor/app/lib/server/functions/sendMessage.ts @@ -4,14 +4,13 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { Messages } from '@rocket.chat/models'; import { Match, check } from 'meteor/check'; -import { callbacks } from '../../../../lib/callbacks'; import { isRelativeURL } from '../../../../lib/utils/isRelativeURL'; import { isURL } from '../../../../lib/utils/isURL'; -import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { FileUpload } from '../../../file-upload/server'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { afterSaveMessage } from '../lib/afterSaveMessage'; +import { notifyOnRoomChangedById, notifyOnMessageChange } from '../lib/notifyListener'; import { validateCustomMessageFields } from '../lib/validateCustomMessageFields'; import { parseUrlsInMessage } from './parseUrlsInMessage'; @@ -290,11 +289,10 @@ export const sendMessage = async function (user: any, message: any, room: any, u void Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageSent', message); } - await callbacks.run('afterSaveMessage', message, room); + // TODO: is there an opportunity to send returned data to notifyOnMessageChange? + await afterSaveMessage(message, room); - void broadcastMessageFromData({ - id: message._id, - }); + void notifyOnMessageChange({ id: message._id }); void notifyOnRoomChangedById(message.rid); diff --git a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts index e3104db280dd..929c24210d2d 100644 --- a/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts +++ b/apps/meteor/app/lib/server/functions/setUserActiveStatus.ts @@ -1,6 +1,7 @@ +import { Federation, FederationEE, License } from '@rocket.chat/core-services'; import type { IUser, IUserEmail } from '@rocket.chat/core-typings'; import { isUserFederated, isDirectMessageRoom } from '@rocket.chat/core-typings'; -import { Rooms, Users, Subscriptions } from '@rocket.chat/models'; +import { Rooms, Users, Subscriptions, MatrixBridgedUser } from '@rocket.chat/models'; import { Accounts } from 'meteor/accounts-base'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -8,7 +9,12 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById, notifyOnRoomChangedByUserDM, notifyOnUserChange } from '../lib/notifyListener'; +import { + notifyOnRoomChangedById, + notifyOnRoomChangedByUserDM, + notifyOnSubscriptionChangedByNameAndRoomType, + notifyOnUserChange, +} from '../lib/notifyListener'; import { closeOmnichannelConversations } from './closeOmnichannelConversations'; import { shouldRemoveOrChangeOwner, getSubscribedRoomsForUserWithDetails } from './getRoomsWithSingleOwner'; import { getUserSingleOwnedRooms } from './getUserSingleOwnedRooms'; @@ -38,8 +44,10 @@ async function reactivateDirectConversations(userId: string) { return acc; }, []); - await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false); - void notifyOnRoomChangedById(roomsToReactivate); + const setDmReadOnlyResponse = await Rooms.setDmReadOnlyByUserId(userId, roomsToReactivate, false, false); + if (setDmReadOnlyResponse.modifiedCount) { + void notifyOnRoomChangedById(roomsToReactivate); + } } export async function setUserActiveStatus(userId: string, active: boolean, confirmRelinquish = false): Promise { @@ -58,6 +66,22 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi }); } + if (user.active !== active) { + const remoteUser = await MatrixBridgedUser.getExternalUserIdByLocalUserId(userId); + + if (remoteUser) { + if (active) { + throw new Meteor.Error('error-not-allowed', 'Deactivated federated users can not be re-activated', { + method: 'setUserActiveStatus', + }); + } + + const federation = (await License.hasValidLicense()) ? FederationEE : Federation; + + await federation.deactivateRemoteUser(remoteUser); + } + } + // Users without username can't do anything, so there is no need to check for owned rooms if (user.username != null && !active) { const userAdmin = await Users.findOneAdmin(userId || ''); @@ -101,7 +125,10 @@ export async function setUserActiveStatus(userId: string, active: boolean, confi } if (user.username) { - await Subscriptions.setArchivedByUsername(user.username, !active); + const { modifiedCount } = await Subscriptions.setArchivedByUsername(user.username, !active); + if (modifiedCount) { + void notifyOnSubscriptionChangedByNameAndRoomType({ t: 'd', name: user.username }); + } } if (active === false) { diff --git a/apps/meteor/app/lib/server/functions/setUsername.ts b/apps/meteor/app/lib/server/functions/setUsername.ts index e19ef874db0f..5b2b1923da75 100644 --- a/apps/meteor/app/lib/server/functions/setUsername.ts +++ b/apps/meteor/app/lib/server/functions/setUsername.ts @@ -17,6 +17,7 @@ import { getAvatarSuggestionForUser } from './getAvatarSuggestionForUser'; import { joinDefaultChannels } from './joinDefaultChannels'; import { saveUserIdentity } from './saveUserIdentity'; import { setUserAvatar } from './setUserAvatar'; +import { validateUsername } from './validateUsername'; export const setUsernameWithValidation = async (userId: string, username: string, joinDefaultChannelsSilenced?: boolean): Promise => { if (!username) { @@ -37,14 +38,7 @@ export const setUsernameWithValidation = async (userId: string, username: string return; } - let nameValidation; - try { - nameValidation = new RegExp(`^${settings.get('UTF8_User_Names_Validation')}$`); - } catch (error) { - nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$'); - } - - if (!nameValidation.test(username)) { + if (!validateUsername(username)) { throw new Meteor.Error( 'username-invalid', `${_.escape(username)} is not a valid username, use only letters, numbers, dots, hyphens and underscores`, @@ -74,18 +68,15 @@ export const setUsernameWithValidation = async (userId: string, username: string export const _setUsername = async function (userId: string, u: string, fullUser: IUser): Promise { const username = u.trim(); + if (!userId || !username) { return false; } - let nameValidation; - try { - nameValidation = new RegExp(`^${settings.get('UTF8_User_Names_Validation')}$`); - } catch (error) { - nameValidation = new RegExp('^[0-9a-zA-Z-_.]+$'); - } - if (!nameValidation.test(username)) { + + if (!validateUsername(username)) { return false; } + const user = fullUser || (await Users.findOneById(userId)); // User already has desired username, return if (user.username === username) { diff --git a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts index 7db86ed933a3..699f9c3701b1 100644 --- a/apps/meteor/app/lib/server/functions/unarchiveRoom.ts +++ b/apps/meteor/app/lib/server/functions/unarchiveRoom.ts @@ -2,11 +2,16 @@ import { Message } from '@rocket.chat/core-services'; import type { IMessage } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions } from '@rocket.chat/models'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; export const unarchiveRoom = async function (rid: string, user: IMessage['u']): Promise { await Rooms.unarchiveById(rid); - await Subscriptions.unarchiveByRoomId(rid); + + const unarchiveResponse = await Subscriptions.unarchiveByRoomId(rid); + if (unarchiveResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + await Message.saveSystemMessage('room-unarchived', rid, '', user); void notifyOnRoomChangedById(rid); diff --git a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts index a0ad2eedcf55..feb26ce6a1b0 100644 --- a/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts +++ b/apps/meteor/app/lib/server/functions/updateGroupDMsName.ts @@ -1,6 +1,8 @@ import type { IUser } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; +import { notifyOnSubscriptionChangedByRoomId } from '../lib/notifyListener'; + const getFname = (members: IUser[]): string => members.map(({ name, username }) => name || username).join(', '); const getName = (members: IUser[]): string => members.map(({ username }) => username).join(','); @@ -63,7 +65,10 @@ export const updateGroupDMsName = async (userThatChangedName: IUser): Promise _id !== sub.u._id); - await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers)); + const updateNameRespose = await Subscriptions.updateNameAndFnameById(sub._id, getName(otherMembers), getFname(otherMembers)); + if (updateNameRespose.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(room._id); + } } } }; diff --git a/apps/meteor/app/lib/server/functions/updateMessage.ts b/apps/meteor/app/lib/server/functions/updateMessage.ts index 2954517fb018..96683d40348f 100644 --- a/apps/meteor/app/lib/server/functions/updateMessage.ts +++ b/apps/meteor/app/lib/server/functions/updateMessage.ts @@ -4,15 +4,14 @@ import type { IMessage, IUser, AtLeast } from '@rocket.chat/core-typings'; import { Messages, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; -import { callbacks } from '../../../../lib/callbacks'; -import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; import { settings } from '../../../settings/server'; -import { notifyOnRoomChangedById } from '../lib/notifyListener'; +import { afterSaveMessage } from '../lib/afterSaveMessage'; +import { notifyOnRoomChangedById, notifyOnMessageChange } from '../lib/notifyListener'; import { validateCustomMessageFields } from '../lib/validateCustomMessageFields'; import { parseUrlsInMessage } from './parseUrlsInMessage'; export const updateMessage = async function ( - message: AtLeast, + message: AtLeast, user: IUser, originalMsg?: IMessage, previewUrls?: string[], @@ -100,11 +99,11 @@ export const updateMessage = async function ( // although this is an "afterSave" kind callback, we know they can extend message's properties // so we wait for it to run before broadcasting - const data = await callbacks.run('afterSaveMessage', msg, room, user._id); + const data = await afterSaveMessage(msg, room, user._id); - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: msg._id, - data: data as any, // TODO move "afterSaveMessage" type definition to specify a return value + data, }); if (room?.lastMessage?._id === msg._id) { diff --git a/apps/meteor/app/lib/server/functions/validateUsername.ts b/apps/meteor/app/lib/server/functions/validateUsername.ts new file mode 100644 index 000000000000..523667282d22 --- /dev/null +++ b/apps/meteor/app/lib/server/functions/validateUsername.ts @@ -0,0 +1,15 @@ +import { settings } from '../../../settings/server'; + +export const validateUsername = (username: string): boolean => { + const settingsRegExp = settings.get('UTF8_User_Names_Validation'); + const defaultPattern = /^[0-9a-zA-Z-_.]+$/; + + let usernameRegExp: RegExp; + try { + usernameRegExp = settingsRegExp ? new RegExp(`^${settingsRegExp}$`) : defaultPattern; + } catch (e) { + usernameRegExp = defaultPattern; + } + + return usernameRegExp.test(username); +}; diff --git a/apps/meteor/app/lib/server/index.ts b/apps/meteor/app/lib/server/index.ts index 80aaa2a64a9e..49fad2002c75 100644 --- a/apps/meteor/app/lib/server/index.ts +++ b/apps/meteor/app/lib/server/index.ts @@ -49,5 +49,6 @@ import './methods/unarchiveRoom'; import './methods/unblockUser'; import './methods/updateMessage'; import './methods/saveCustomFields'; +import './methods/checkFederationConfiguration'; export * from './lib'; diff --git a/apps/meteor/app/lib/server/lib/afterSaveMessage.ts b/apps/meteor/app/lib/server/lib/afterSaveMessage.ts new file mode 100644 index 000000000000..5b6e12b1e185 --- /dev/null +++ b/apps/meteor/app/lib/server/lib/afterSaveMessage.ts @@ -0,0 +1,35 @@ +import type { IMessage, IUser, IRoom } from '@rocket.chat/core-typings'; +import type { Updater } from '@rocket.chat/models'; +import { Rooms } from '@rocket.chat/models'; + +import { callbacks } from '../../../../lib/callbacks'; + +export async function afterSaveMessage( + message: IMessage, + room: IRoom, + uid?: IUser['_id'], + roomUpdater?: Updater, +): Promise { + const updater = roomUpdater ?? Rooms.getUpdater(); + const data = await callbacks.run('afterSaveMessage', message, { room, uid, roomUpdater: updater }); + + if (!roomUpdater && updater.hasChanges()) { + await Rooms.updateFromUpdater({ _id: room._id }, updater); + } + + // TODO: Fix type - callback configuration needs to be updated + return data as unknown as IMessage; +} + +export function afterSaveMessageAsync( + message: IMessage, + room: IRoom, + uid?: IUser['_id'], + roomUpdater: Updater = Rooms.getUpdater(), +): void { + callbacks.runAsync('afterSaveMessage', message, { room, uid, roomUpdater }); + + if (roomUpdater.hasChanges()) { + void Rooms.updateFromUpdater({ _id: room._id }, roomUpdater); + } +} diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 635c236fda27..778fe89dbbf4 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -15,8 +15,12 @@ import type { IEmailInbox, IIntegrationHistory, AtLeast, + ISubscription, ISettingColor, IUser, + IMessage, + SettingValue, + MessageTypesValues, } from '@rocket.chat/core-typings'; import { Rooms, @@ -27,10 +31,16 @@ import { Integrations, LoginServiceConfiguration, IntegrationHistory, + Subscriptions, LivechatInquiry, LivechatDepartmentAgents, Users, + Messages, } from '@rocket.chat/models'; +import mem from 'mem'; + +import { subscriptionFields } from '../../../../lib/publishFields'; +import { shouldHideSystemMessage } from '../../../../server/lib/systemMessage/hideSystemMessage'; type ClientAction = 'inserted' | 'updated' | 'removed'; @@ -401,3 +411,178 @@ export const notifyOnUserChangeById = withDbWatcherCheck( void notifyOnUserChange({ id, clientAction, data: user }); }, ); + +const getUserNameCached = mem( + async (userId: string): Promise => { + const user = await Users.findOne>(userId, { projection: { name: 1 } }); + return user?.name; + }, + { maxAge: 10000 }, +); + +const getSettingCached = mem(async (setting: string): Promise => Settings.getValueById(setting), { maxAge: 10000 }); + +export async function getMessageToBroadcast({ id, data }: { id: IMessage['_id']; data?: IMessage }): Promise { + const message = data ?? (await Messages.findOneById(id)); + if (!message) { + return; + } + + if (message.t) { + const hiddenSystemMessages = (await getSettingCached('Hide_System_Messages')) as MessageTypesValues[]; + const shouldHide = shouldHideSystemMessage(message.t, hiddenSystemMessages); + + if (shouldHide) { + return; + } + } + + if (message._hidden || message.imported != null) { + return; + } + + const useRealName = (await getSettingCached('UI_Use_Real_Name')) === true; + if (useRealName) { + if (message.u?._id) { + const name = await getUserNameCached(message.u._id); + if (name) { + message.u.name = name; + } + } + + if (message.mentions?.length) { + for await (const mention of message.mentions) { + const name = await getUserNameCached(mention._id); + if (name) { + mention.name = name; + } + } + } + } + + return message; +} + +export const notifyOnMessageChange = withDbWatcherCheck(async ({ id, data }: { id: IMessage['_id']; data?: IMessage }): Promise => { + const message = await getMessageToBroadcast({ id, data }); + if (!message) { + return; + } + void api.broadcast('watch.messages', { message }); +}); + +export const notifyOnSubscriptionChanged = withDbWatcherCheck( + async (subscription: ISubscription, clientAction: ClientAction = 'updated'): Promise => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }, +); + +export const notifyOnSubscriptionChangedByRoomIdAndUserId = withDbWatcherCheck( + async ( + rid: ISubscription['rid'], + uid: ISubscription['u']['_id'], + clientAction: Exclude = 'updated', + ): Promise => { + const cursor = Subscriptions.findByUserIdAndRoomIds(uid, [rid], { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedById = withDbWatcherCheck( + async (id: ISubscription['_id'], clientAction: Exclude = 'updated'): Promise => { + const subscription = await Subscriptions.findOneById(id); + if (!subscription) { + return; + } + + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }, +); + +export const notifyOnSubscriptionChangedByUserPreferences = withDbWatcherCheck( + async ( + uid: ISubscription['u']['_id'], + notificationOriginField: keyof ISubscription, + originFieldNotEqualValue: 'user' | 'subscription', + clientAction: Exclude = 'updated', + ): Promise => { + const cursor = Subscriptions.findByUserPreferences(uid, notificationOriginField, originFieldNotEqualValue, { + projection: subscriptionFields, + }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedByRoomId = withDbWatcherCheck( + async (rid: ISubscription['rid'], clientAction: Exclude = 'updated'): Promise => { + const cursor = Subscriptions.findByRoomId(rid, { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedByAutoTranslateAndUserId = withDbWatcherCheck( + async (uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated'): Promise => { + const cursor = Subscriptions.findByAutoTranslateAndUserId(uid, true, { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedByUserIdAndRoomType = withDbWatcherCheck( + async ( + uid: ISubscription['u']['_id'], + t: ISubscription['t'], + clientAction: Exclude = 'updated', + ): Promise => { + const cursor = Subscriptions.findByUserIdAndRoomType(uid, t, { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedByNameAndRoomType = withDbWatcherCheck( + async (filter: Partial>, clientAction: Exclude = 'updated'): Promise => { + const cursor = Subscriptions.findByNameAndRoomType(filter, { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedByUserId = withDbWatcherCheck( + async (uid: ISubscription['u']['_id'], clientAction: Exclude = 'updated'): Promise => { + const cursor = Subscriptions.findByUserId(uid, { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); + +export const notifyOnSubscriptionChangedByRoomIdAndUserIds = withDbWatcherCheck( + async ( + rid: ISubscription['rid'], + uids: ISubscription['u']['_id'][], + clientAction: Exclude = 'updated', + ): Promise => { + const cursor = Subscriptions.findByRoomIdAndUserIds(rid, uids, { projection: subscriptionFields }); + + void cursor.forEach((subscription) => { + void api.broadcast('watch.subscriptions', { clientAction, subscription }); + }); + }, +); diff --git a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts index 76a18eba3362..7551cabb6e63 100644 --- a/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/notifyUsersOnMessage.ts @@ -1,11 +1,17 @@ import type { IMessage, IRoom, IUser, RoomType } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; +import type { Updater } from '@rocket.chat/models'; import { Subscriptions, Rooms } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import moment from 'moment'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; +import { + notifyOnSubscriptionChanged, + notifyOnSubscriptionChangedByRoomIdAndUserId, + notifyOnSubscriptionChangedByRoomIdAndUserIds, +} from './notifyListener'; function messageContainsHighlight(message: IMessage, highlights: string[]): boolean { if (!highlights || highlights.length === 0) return false; @@ -50,26 +56,14 @@ export async function getMentions(message: IMessage): Promise<{ toAll: boolean; type UnreadCountType = 'all_messages' | 'user_mentions_only' | 'group_mentions_only' | 'user_and_group_mentions_only'; -const incGroupMentions = async ( - rid: IRoom['_id'], - roomType: RoomType, - excludeUserId: IUser['_id'], - unreadCount: Exclude, -): Promise => { +const getGroupMentions = (roomType: RoomType, unreadCount: Exclude): number => { const incUnreadByGroup = ['all_messages', 'group_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); - const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0; - await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(rid, excludeUserId, 1, incUnread); + return roomType === 'd' || roomType === 'l' || incUnreadByGroup ? 1 : 0; }; -const incUserMentions = async ( - rid: IRoom['_id'], - roomType: RoomType, - uids: IUser['_id'][], - unreadCount: Exclude, -): Promise => { - const incUnreadByUser = new Set(['all_messages', 'user_mentions_only', 'user_and_group_mentions_only']).has(unreadCount); - const incUnread = roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0; - await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(rid, uids, 1, incUnread); +const getUserMentions = (roomType: RoomType, unreadCount: Exclude): number => { + const incUnreadByUser = ['all_messages', 'user_mentions_only', 'user_and_group_mentions_only'].includes(unreadCount); + return roomType === 'd' || roomType === 'l' || incUnreadByUser ? 1 : 0; }; export const getUserIdsFromHighlights = async (rid: IRoom['_id'], message: IMessage): Promise => { @@ -100,53 +94,87 @@ const getUnreadSettingCount = (roomType: RoomType): UnreadCountType => { }; async function updateUsersSubscriptions(message: IMessage, room: IRoom): Promise { - // Don't increase unread counter on thread messages - if (room != null && !message.tmid) { - const { toAll, toHere, mentionIds } = await getMentions(message); - - const userIds = new Set(mentionIds); - - const unreadCount = getUnreadSettingCount(room.t); + if (!room || message.tmid) { + return; + } - (await getUserIdsFromHighlights(room._id, message)).forEach((uid) => userIds.add(uid)); + const [mentions, highlightIds] = await Promise.all([getMentions(message), getUserIdsFromHighlights(room._id, message)]); + + const { toAll, toHere, mentionIds } = mentions; + const userIds = [...new Set([...mentionIds, ...highlightIds])]; + const unreadCount = getUnreadSettingCount(room.t); + + const userMentionInc = getUserMentions(room.t, unreadCount as Exclude); + const groupMentionInc = getGroupMentions(room.t, unreadCount as Exclude); + + void Subscriptions.findByRoomIdAndNotAlertOrOpenExcludingUserIds({ + roomId: room._id, + uidsExclude: [message.u._id], + uidsInclude: userIds, + onlyRead: !toAll && !toHere, + }).forEach((sub) => { + const hasUserMention = userIds.includes(sub.u._id); + const shouldIncUnread = hasUserMention || toAll || toHere || unreadCount === 'all_messages'; + void notifyOnSubscriptionChanged( + { + ...sub, + alert: true, + open: true, + ...(shouldIncUnread && { unread: sub.unread + 1 }), + ...(hasUserMention && { userMentions: sub.userMentions + 1 }), + ...((toAll || toHere) && { groupMentions: sub.groupMentions + 1 }), + }, + 'updated', + ); + }); - // Give priority to user mentions over group mentions - if (userIds.size > 0) { - await incUserMentions(room._id, room.t, [...userIds], unreadCount as Exclude); - } else if (toAll || toHere) { - await incGroupMentions(room._id, room.t, message.u._id, unreadCount as Exclude); - } + // Give priority to user mentions over group mentions + if (userIds.length) { + await Subscriptions.incUserMentionsAndUnreadForRoomIdAndUserIds(room._id, userIds, 1, userMentionInc); + } else if (toAll || toHere) { + await Subscriptions.incGroupMentionsAndUnreadForRoomIdExcludingUserId(room._id, message.u._id, 1, groupMentionInc); + } - // this shouldn't run only if has group mentions because it will already exclude mentioned users from the query - if (!toAll && !toHere && unreadCount === 'all_messages') { - await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); - } + if (!toAll && !toHere && unreadCount === 'all_messages') { + await Subscriptions.incUnreadForRoomIdExcludingUserIds(room._id, [...userIds, message.u._id], 1); } - // Update all other subscriptions to alert their owners but without incrementing - // the unread counter, as it is only for mentions and direct messages - // We now set alert and open properties in two separate update commands. This proved to be more efficient on MongoDB - because it uses a more efficient index. + // update subscriptions of other members of the room await Promise.all([ Subscriptions.setAlertForRoomIdExcludingUserId(message.rid, message.u._id), Subscriptions.setOpenForRoomIdExcludingUserId(message.rid, message.u._id), ]); + + // update subscription of the message sender + await Subscriptions.setAsReadByRoomIdAndUserId(message.rid, message.u._id); + const setAsReadResponse = await Subscriptions.setAsReadByRoomIdAndUserId(message.rid, message.u._id); + if (setAsReadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(message.rid, message.u._id); + } } export async function updateThreadUsersSubscriptions(message: IMessage, replies: IUser['_id'][]): Promise { // Don't increase unread counter on thread messages - - await Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies); const repliesPlusSender = [...new Set([message.u._id, ...replies])]; - await Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender); - await Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date()); + + const responses = await Promise.all([ + Subscriptions.setAlertForRoomIdAndUserIds(message.rid, replies), + Subscriptions.setOpenForRoomIdAndUserIds(message.rid, repliesPlusSender), + Subscriptions.setLastReplyForRoomIdAndUserIds(message.rid, repliesPlusSender, new Date()), + ]); + + responses.some((response) => response?.modifiedCount) && + void notifyOnSubscriptionChangedByRoomIdAndUserIds(message.rid, repliesPlusSender); } -export async function notifyUsersOnMessage(message: IMessage, room: IRoom): Promise { +export async function notifyUsersOnMessage(message: IMessage, room: IRoom, roomUpdater: Updater): Promise { + console.log('notifyUsersOnMessage function'); + // Skips this callback if the message was edited and increments it if the edit was way in the past (aka imported) if (isEditedMessage(message)) { if (Math.abs(moment(message.editedAt).diff(Date.now())) > 60000) { // TODO: Review as I am not sure how else to get around this as the incrementing of the msgs count shouldn't be in this callback - await Rooms.incMsgCountById(message.rid, 1); + Rooms.getIncMsgCountUpdateQuery(1, roomUpdater); return message; } @@ -156,25 +184,39 @@ export async function notifyUsersOnMessage(message: IMessage, room: IRoom): Prom (!message.tmid || message.tshow) && (!room.lastMessage || room.lastMessage._id === message._id) ) { - await Rooms.setLastMessageById(message.rid, message); + Rooms.getLastMessageUpdateQuery(message, roomUpdater); } return message; } if (message.ts && Math.abs(moment(message.ts).diff(Date.now())) > 60000) { - await Rooms.incMsgCountById(message.rid, 1); + Rooms.getIncMsgCountUpdateQuery(1, roomUpdater); return message; } // If message sent ONLY on a thread, skips the rest as it is done on a callback specific to threads if (message.tmid && !message.tshow) { - await Rooms.incMsgCountById(message.rid, 1); + Rooms.getIncMsgCountUpdateQuery(1, roomUpdater); return message; } // Update all the room activity tracker fields - await Rooms.incMsgCountAndSetLastMessageById(message.rid, 1, message.ts, settings.get('Store_Last_Message') ? message : undefined); + Rooms.setIncMsgCountAndSetLastMessageUpdateQuery(1, message, !!settings.get('Store_Last_Message'), roomUpdater); + await updateUsersSubscriptions(message, room); + + return message; +} + +export async function notifyUsersOnSystemMessage(message: IMessage, room: IRoom): Promise { + const roomUpdater = Rooms.getUpdater(); + Rooms.setIncMsgCountAndSetLastMessageUpdateQuery(1, message, !!settings.get('Store_Last_Message'), roomUpdater); + + if (roomUpdater.hasChanges()) { + await Rooms.updateFromUpdater({ _id: room._id }, roomUpdater); + } + + // TODO: Rewrite to use just needed calls from the function await updateUsersSubscriptions(message, room); return message; @@ -182,7 +224,15 @@ export async function notifyUsersOnMessage(message: IMessage, room: IRoom): Prom callbacks.add( 'afterSaveMessage', - (message, room) => notifyUsersOnMessage(message, room), + async (message, { room, roomUpdater }) => { + if (!roomUpdater) { + return message; + } + + await notifyUsersOnMessage(message, room, roomUpdater); + + return message; + }, callbacks.priority.MEDIUM, 'notifyUsersOnMessage', ); diff --git a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts index 49fcc0ea4725..94c25f476222 100644 --- a/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts +++ b/apps/meteor/app/lib/server/lib/sendNotificationsOnMessage.ts @@ -406,7 +406,7 @@ settings.watch('Troubleshoot_Disable_Notifications', (value) => { callbacks.add( 'afterSaveMessage', - (message, room) => sendAllNotifications(message, room), + (message, { room }) => sendAllNotifications(message, room), callbacks.priority.LOW, 'sendNotificationsOnMessage', ); diff --git a/apps/meteor/app/lib/server/methods/addOAuthService.ts b/apps/meteor/app/lib/server/methods/addOAuthService.ts index 05b0e5a7e4e6..09287bb9bb74 100644 --- a/apps/meteor/app/lib/server/methods/addOAuthService.ts +++ b/apps/meteor/app/lib/server/methods/addOAuthService.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { addOAuthService } from '../../../../server/lib/oauth/addOAuthService'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addOAuthService(name: string): void; diff --git a/apps/meteor/app/lib/server/methods/addUserToRoom.ts b/apps/meteor/app/lib/server/methods/addUserToRoom.ts index 880b413d0618..8100cafc33b0 100644 --- a/apps/meteor/app/lib/server/methods/addUserToRoom.ts +++ b/apps/meteor/app/lib/server/methods/addUserToRoom.ts @@ -1,9 +1,9 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { addUsersToRoomMethod } from './addUsersToRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addUserToRoom(data: { rid: string; username: string }): void; diff --git a/apps/meteor/app/lib/server/methods/addUsersToRoom.ts b/apps/meteor/app/lib/server/methods/addUsersToRoom.ts index 48b7f9db58f5..73fbf6e51a04 100644 --- a/apps/meteor/app/lib/server/methods/addUsersToRoom.ts +++ b/apps/meteor/app/lib/server/methods/addUsersToRoom.ts @@ -1,8 +1,8 @@ import { api } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -12,7 +12,7 @@ import { Federation } from '../../../../server/services/federation/Federation'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { addUserToRoom } from '../functions/addUserToRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addUsersToRoom(data: { rid: string; users: string[] }): boolean; diff --git a/apps/meteor/app/lib/server/methods/archiveRoom.ts b/apps/meteor/app/lib/server/methods/archiveRoom.ts index c30014f59c11..dfa5a2c55412 100644 --- a/apps/meteor/app/lib/server/methods/archiveRoom.ts +++ b/apps/meteor/app/lib/server/methods/archiveRoom.ts @@ -1,6 +1,6 @@ import { isRegisterUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -9,7 +9,7 @@ import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { archiveRoom } from '../functions/archiveRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { archiveRoom(rid: string): Promise; diff --git a/apps/meteor/app/lib/server/methods/blockUser.ts b/apps/meteor/app/lib/server/methods/blockUser.ts index b65423edf25b..7fe6ec803dd1 100644 --- a/apps/meteor/app/lib/server/methods/blockUser.ts +++ b/apps/meteor/app/lib/server/methods/blockUser.ts @@ -1,12 +1,13 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; +import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { blockUser({ rid, blocked }: { rid: string; blocked: string }): boolean; @@ -33,14 +34,22 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); - const subscription2 = await Subscriptions.findOneByRoomIdAndUserId(rid, blocked); + const [blockedUser, blockerUser] = await Promise.all([ + Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }), + Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }), + ]); - if (!subscription || !subscription2) { + if (!blockedUser || !blockerUser) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } - await Subscriptions.setBlockedByRoomId(rid, blocked, userId); + const [blockedResponse, blockerResponse] = await Subscriptions.setBlockedByRoomId(rid, blocked, userId); + + const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])]; + + if (listenerUsers.length) { + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers); + } return true; }, diff --git a/apps/meteor/app/lib/server/methods/checkFederationConfiguration.ts b/apps/meteor/app/lib/server/methods/checkFederationConfiguration.ts new file mode 100644 index 000000000000..e32f2ab5d7af --- /dev/null +++ b/apps/meteor/app/lib/server/methods/checkFederationConfiguration.ts @@ -0,0 +1,80 @@ +import { Federation, FederationEE, Authorization } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; +import { License } from '@rocket.chat/license'; +import { Meteor } from 'meteor/meteor'; + +declare module '@rocket.chat/ddp-client' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + checkFederationConfiguration(): Promise<{ message: string }>; + } +} + +Meteor.methods({ + async checkFederationConfiguration() { + const uid = Meteor.userId(); + + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'checkFederationConfiguration', + }); + } + + if (!(await Authorization.hasPermission(uid, 'view-privileged-setting'))) { + throw new Meteor.Error('error-not-allowed', 'Action not allowed', { + method: 'checkFederationConfiguration', + }); + } + + const errors: string[] = []; + + const successes: string[] = []; + + const service = License.hasValidLicense() ? FederationEE : Federation; + + const status = await service.configurationStatus(); + + if (status.externalReachability.ok) { + successes.push('homeserver configuration looks good'); + } else { + let err = 'external reachability could not be verified'; + + const { error } = status.externalReachability; + if (error) { + err += `, error: ${error}`; + } + + errors.push(err); + } + + const { + roundTrip: { durationMs: duration }, + } = status.appservice; + + if (status.appservice.ok) { + successes.push(`appservice configuration looks good, total round trip time to homeserver ${duration}ms`); + } else { + errors.push(`failed to verify appservice configuration: ${status.appservice.error}`); + } + + if (errors.length) { + void service.markConfigurationInvalid(); + + if (successes.length) { + const message = ['Configuration could only be partially verified'].concat(successes).concat(errors).join(', '); + + throw new Meteor.Error('error-invalid-configuration', message, { method: 'checkFederationConfiguration' }); + } + + throw new Meteor.Error('error-invalid-configuration', ['Invalid configuration'].concat(errors).join(', '), { + method: 'checkFederationConfiguration', + }); + } + + void service.markConfigurationValid(); + + return { + message: ['All configuration looks good'].concat(successes).join(', '), + }; + }, +}); diff --git a/apps/meteor/app/lib/server/methods/checkRegistrationSecretURL.ts b/apps/meteor/app/lib/server/methods/checkRegistrationSecretURL.ts index 3f349a8b43e6..ecba6844da26 100644 --- a/apps/meteor/app/lib/server/methods/checkRegistrationSecretURL.ts +++ b/apps/meteor/app/lib/server/methods/checkRegistrationSecretURL.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; diff --git a/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts b/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts index 34ad02ddb2cf..4df2706bb479 100644 --- a/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts +++ b/apps/meteor/app/lib/server/methods/checkUsernameAvailability.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +6,7 @@ import { checkUsernameAvailabilityWithValidation } from '../functions/checkUsern import { RateLimiter } from '../lib'; import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { checkUsernameAvailability(username: string): boolean; diff --git a/apps/meteor/app/lib/server/methods/cleanRoomHistory.ts b/apps/meteor/app/lib/server/methods/cleanRoomHistory.ts index a724d1b38b2c..d6136eee9131 100644 --- a/apps/meteor/app/lib/server/methods/cleanRoomHistory.ts +++ b/apps/meteor/app/lib/server/methods/cleanRoomHistory.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { cleanRoomHistory } from '../functions/cleanRoomHistory'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { cleanRoomHistory(data: { diff --git a/apps/meteor/app/lib/server/methods/createChannel.ts b/apps/meteor/app/lib/server/methods/createChannel.ts index 98cea517bed4..a490fa1e3c42 100644 --- a/apps/meteor/app/lib/server/methods/createChannel.ts +++ b/apps/meteor/app/lib/server/methods/createChannel.ts @@ -1,13 +1,13 @@ import type { ICreatedRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { createRoom } from '../functions/createRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { createChannel( diff --git a/apps/meteor/app/lib/server/methods/createPrivateGroup.ts b/apps/meteor/app/lib/server/methods/createPrivateGroup.ts index 75097b5c89b8..3b92b2607829 100644 --- a/apps/meteor/app/lib/server/methods/createPrivateGroup.ts +++ b/apps/meteor/app/lib/server/methods/createPrivateGroup.ts @@ -1,13 +1,13 @@ import type { ICreatedRoom, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { createRoom } from '../functions/createRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { createPrivateGroup( diff --git a/apps/meteor/app/lib/server/methods/createToken.ts b/apps/meteor/app/lib/server/methods/createToken.ts index ed665944415a..de60a7e37fe8 100644 --- a/apps/meteor/app/lib/server/methods/createToken.ts +++ b/apps/meteor/app/lib/server/methods/createToken.ts @@ -1,12 +1,12 @@ import { User } from '@rocket.chat/core-services'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { createToken(userId: string): { userId: string; authToken: string }; diff --git a/apps/meteor/app/lib/server/methods/deleteMessage.ts b/apps/meteor/app/lib/server/methods/deleteMessage.ts index a1b1000c06b8..b0be64c245ef 100644 --- a/apps/meteor/app/lib/server/methods/deleteMessage.ts +++ b/apps/meteor/app/lib/server/methods/deleteMessage.ts @@ -1,11 +1,11 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { deleteMessageValidatingPermission } from '../functions/deleteMessage'; import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteMessage({ _id }: Pick): void; diff --git a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts index 2d651950da19..7b5663185bc5 100644 --- a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts +++ b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts @@ -1,7 +1,7 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; import { SHA256 } from '@rocket.chat/sha256'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Accounts } from 'meteor/accounts-base'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -10,7 +10,7 @@ import { trim } from '../../../../lib/utils/stringUtils'; import { settings } from '../../../settings/server'; import { deleteUser } from '../functions/deleteUser'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteUserOwnAccount(password: string, confirmRelinquish?: boolean): Promise; diff --git a/apps/meteor/app/lib/server/methods/executeSlashCommandPreview.ts b/apps/meteor/app/lib/server/methods/executeSlashCommandPreview.ts index 80b5428efbef..1c116863610d 100644 --- a/apps/meteor/app/lib/server/methods/executeSlashCommandPreview.ts +++ b/apps/meteor/app/lib/server/methods/executeSlashCommandPreview.ts @@ -1,10 +1,10 @@ import type { IMessage, RequiredField, SlashCommandPreviewItem } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { slashCommands } from '../../../utils/server/slashCommand'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { executeSlashCommandPreview( diff --git a/apps/meteor/app/lib/server/methods/getChannelHistory.ts b/apps/meteor/app/lib/server/methods/getChannelHistory.ts index 00ff01639593..8f1f4c586141 100644 --- a/apps/meteor/app/lib/server/methods/getChannelHistory.ts +++ b/apps/meteor/app/lib/server/methods/getChannelHistory.ts @@ -1,6 +1,6 @@ import type { IMessage, MessageTypesValues } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Subscriptions, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; @@ -11,7 +11,7 @@ import { settings } from '../../../settings/server/cached'; import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser'; import { getHiddenSystemMessages } from '../lib/getHiddenSystemMessages'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getChannelHistory(params: { diff --git a/apps/meteor/app/lib/server/methods/getMessages.ts b/apps/meteor/app/lib/server/methods/getMessages.ts index 909e3d0c656b..d8684f82453c 100644 --- a/apps/meteor/app/lib/server/methods/getMessages.ts +++ b/apps/meteor/app/lib/server/methods/getMessages.ts @@ -1,12 +1,12 @@ import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getMessages(messages: IMessage['_id'][]): Promise; diff --git a/apps/meteor/app/lib/server/methods/getRoomJoinCode.ts b/apps/meteor/app/lib/server/methods/getRoomJoinCode.ts index 75e4a4372f57..5ba4b3490722 100644 --- a/apps/meteor/app/lib/server/methods/getRoomJoinCode.ts +++ b/apps/meteor/app/lib/server/methods/getRoomJoinCode.ts @@ -1,12 +1,12 @@ import { isRoomWithJoinCode } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getRoomJoinCode(rid: string): string | false; diff --git a/apps/meteor/app/lib/server/methods/getRoomRoles.ts b/apps/meteor/app/lib/server/methods/getRoomRoles.ts index ac5fcc734d0f..b411ba8fedf5 100644 --- a/apps/meteor/app/lib/server/methods/getRoomRoles.ts +++ b/apps/meteor/app/lib/server/methods/getRoomRoles.ts @@ -1,6 +1,6 @@ import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -8,7 +8,7 @@ import { getRoomRoles } from '../../../../server/lib/roles/getRoomRoles'; import { canAccessRoomAsync } from '../../../authorization/server'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getRoomRoles(rid: IRoom['_id']): ISubscription[]; diff --git a/apps/meteor/app/lib/server/methods/getSingleMessage.ts b/apps/meteor/app/lib/server/methods/getSingleMessage.ts index 1aaf2a652257..c4b6f065296b 100644 --- a/apps/meteor/app/lib/server/methods/getSingleMessage.ts +++ b/apps/meteor/app/lib/server/methods/getSingleMessage.ts @@ -1,12 +1,12 @@ import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getSingleMessage(mid: IMessage['_id']): Promise; diff --git a/apps/meteor/app/lib/server/methods/getSlashCommandPreviews.ts b/apps/meteor/app/lib/server/methods/getSlashCommandPreviews.ts index dcb39c466063..965c91cc502f 100644 --- a/apps/meteor/app/lib/server/methods/getSlashCommandPreviews.ts +++ b/apps/meteor/app/lib/server/methods/getSlashCommandPreviews.ts @@ -1,10 +1,10 @@ import type { IMessage, RequiredField, SlashCommandPreviews } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { slashCommands } from '../../../utils/server/slashCommand'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getSlashCommandPreviews(command: { diff --git a/apps/meteor/app/lib/server/methods/getUserRoles.ts b/apps/meteor/app/lib/server/methods/getUserRoles.ts index cc7588276c33..cbd4712930a2 100644 --- a/apps/meteor/app/lib/server/methods/getUserRoles.ts +++ b/apps/meteor/app/lib/server/methods/getUserRoles.ts @@ -1,9 +1,9 @@ import { Authorization } from '@rocket.chat/core-services'; import type { IUser, IRocketChatRecord } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getUserRoles(): (IRocketChatRecord & Pick)[]; diff --git a/apps/meteor/app/lib/server/methods/getUsernameSuggestion.ts b/apps/meteor/app/lib/server/methods/getUsernameSuggestion.ts index 5724449a5790..b42799f24081 100644 --- a/apps/meteor/app/lib/server/methods/getUsernameSuggestion.ts +++ b/apps/meteor/app/lib/server/methods/getUsernameSuggestion.ts @@ -1,9 +1,9 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { generateUsernameSuggestion } from '../functions/getUsernameSuggestion'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getUsernameSuggestion(): Promise; diff --git a/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts b/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts index 5a8b1c8d1a58..122b11172d57 100644 --- a/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts +++ b/apps/meteor/app/lib/server/methods/insertOrUpdateUser.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +6,7 @@ import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; import { saveUser } from '../functions/saveUser'; import { methodDeprecationLogger } from '../lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { insertOrUpdateUser(userData: Record): Promise; diff --git a/apps/meteor/app/lib/server/methods/joinDefaultChannels.ts b/apps/meteor/app/lib/server/methods/joinDefaultChannels.ts index 309ac4e449be..df654bc4aef0 100644 --- a/apps/meteor/app/lib/server/methods/joinDefaultChannels.ts +++ b/apps/meteor/app/lib/server/methods/joinDefaultChannels.ts @@ -1,11 +1,11 @@ import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { addUserToDefaultChannels } from '../functions/addUserToDefaultChannels'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { joinDefaultChannels(silenced?: boolean): void; diff --git a/apps/meteor/app/lib/server/methods/joinRoom.ts b/apps/meteor/app/lib/server/methods/joinRoom.ts index 0fa3ac0b3c3b..ef3f069ee614 100644 --- a/apps/meteor/app/lib/server/methods/joinRoom.ts +++ b/apps/meteor/app/lib/server/methods/joinRoom.ts @@ -1,11 +1,11 @@ import { Room } from '@rocket.chat/core-services'; import type { IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { joinRoom(rid: IRoom['_id'], code?: string): boolean | undefined; diff --git a/apps/meteor/app/lib/server/methods/leaveRoom.ts b/apps/meteor/app/lib/server/methods/leaveRoom.ts index ec1bb1638a21..4fc85b35fd05 100644 --- a/apps/meteor/app/lib/server/methods/leaveRoom.ts +++ b/apps/meteor/app/lib/server/methods/leaveRoom.ts @@ -1,6 +1,6 @@ import type { IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Roles, Subscriptions, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -10,7 +10,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { removeUserFromRoom } from '../functions/removeUserFromRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { leaveRoom(rid: string): Promise; diff --git a/apps/meteor/app/lib/server/methods/refreshOAuthService.ts b/apps/meteor/app/lib/server/methods/refreshOAuthService.ts index e5b1c377a33e..1d547b770361 100644 --- a/apps/meteor/app/lib/server/methods/refreshOAuthService.ts +++ b/apps/meteor/app/lib/server/methods/refreshOAuthService.ts @@ -1,10 +1,10 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { refreshLoginServices } from '../../../../server/lib/refreshLoginServices'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { refreshOAuthService(): Promise; diff --git a/apps/meteor/app/lib/server/methods/removeOAuthService.ts b/apps/meteor/app/lib/server/methods/removeOAuthService.ts index 6e16dc8d2d5b..fee2caa911d2 100644 --- a/apps/meteor/app/lib/server/methods/removeOAuthService.ts +++ b/apps/meteor/app/lib/server/methods/removeOAuthService.ts @@ -1,13 +1,13 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; import { capitalize } from '@rocket.chat/string-helpers'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { notifyOnSettingChangedById } from '../lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { removeOAuthService(name: string): Promise; diff --git a/apps/meteor/app/lib/server/methods/restartServer.ts b/apps/meteor/app/lib/server/methods/restartServer.ts index 206824aebe7e..264ab4d1bee6 100644 --- a/apps/meteor/app/lib/server/methods/restartServer.ts +++ b/apps/meteor/app/lib/server/methods/restartServer.ts @@ -1,9 +1,9 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { restart_server(): { diff --git a/apps/meteor/app/lib/server/methods/saveCustomFields.ts b/apps/meteor/app/lib/server/methods/saveCustomFields.ts index a509c97bdb85..d683e59a905c 100644 --- a/apps/meteor/app/lib/server/methods/saveCustomFields.ts +++ b/apps/meteor/app/lib/server/methods/saveCustomFields.ts @@ -1,11 +1,11 @@ import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { saveCustomFields } from '../functions/saveCustomFields'; import { RateLimiter } from '../lib'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { saveCustomFields: (fields: IUser['customFields']) => Promise; diff --git a/apps/meteor/app/lib/server/methods/saveSetting.ts b/apps/meteor/app/lib/server/methods/saveSetting.ts index 7f900d1751d8..4f4d29b9fa24 100644 --- a/apps/meteor/app/lib/server/methods/saveSetting.ts +++ b/apps/meteor/app/lib/server/methods/saveSetting.ts @@ -1,6 +1,6 @@ import type { SettingValue } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -9,7 +9,7 @@ import { getSettingPermissionId } from '../../../authorization/lib'; import { hasPermissionAsync, hasAllPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { notifyOnSettingChanged } from '../lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { saveSetting(_id: string, value: SettingValue, editor?: string): Promise; diff --git a/apps/meteor/app/lib/server/methods/saveSettings.ts b/apps/meteor/app/lib/server/methods/saveSettings.ts index 8c4f92cfb88f..dffa986b7217 100644 --- a/apps/meteor/app/lib/server/methods/saveSettings.ts +++ b/apps/meteor/app/lib/server/methods/saveSettings.ts @@ -1,7 +1,7 @@ import type { ISetting } from '@rocket.chat/core-typings'; import { isSettingCode } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -11,7 +11,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { settings } from '../../../settings/server'; import { notifyOnSettingChangedById } from '../lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { saveSettings( diff --git a/apps/meteor/app/lib/server/methods/sendMessage.ts b/apps/meteor/app/lib/server/methods/sendMessage.ts index a61ab499ce87..56009f15fede 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.ts +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -1,7 +1,7 @@ import { api } from '@rocket.chat/core-services'; import type { AtLeast, IMessage, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import moment from 'moment'; @@ -111,7 +111,7 @@ export async function executeSendMessage(uid: IUser['_id'], message: AtLeast, previewUrls?: string[]): any; diff --git a/apps/meteor/app/lib/server/methods/sendSMTPTestEmail.ts b/apps/meteor/app/lib/server/methods/sendSMTPTestEmail.ts index 2ede129dd380..5a04032ad82e 100644 --- a/apps/meteor/app/lib/server/methods/sendSMTPTestEmail.ts +++ b/apps/meteor/app/lib/server/methods/sendSMTPTestEmail.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { sendSMTPTestEmail(): { diff --git a/apps/meteor/app/lib/server/methods/setAdminStatus.ts b/apps/meteor/app/lib/server/methods/setAdminStatus.ts index 42ffdc91f85e..300aa735b014 100644 --- a/apps/meteor/app/lib/server/methods/setAdminStatus.ts +++ b/apps/meteor/app/lib/server/methods/setAdminStatus.ts @@ -1,12 +1,12 @@ import { isUserFederated } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { setAdminStatus(userId: string, admin?: boolean): void; diff --git a/apps/meteor/app/lib/server/methods/setEmail.ts b/apps/meteor/app/lib/server/methods/setEmail.ts index a172eba72555..26540db26b4c 100644 --- a/apps/meteor/app/lib/server/methods/setEmail.ts +++ b/apps/meteor/app/lib/server/methods/setEmail.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +6,7 @@ import { settings } from '../../../settings/server'; import { setEmail } from '../functions/setEmail'; import { RateLimiter } from '../lib'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { setEmail(email: string): string; diff --git a/apps/meteor/app/lib/server/methods/setRealName.ts b/apps/meteor/app/lib/server/methods/setRealName.ts index 2a55bd102172..f347eef1580e 100644 --- a/apps/meteor/app/lib/server/methods/setRealName.ts +++ b/apps/meteor/app/lib/server/methods/setRealName.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +6,7 @@ import { settings } from '../../../settings/server'; import { setRealName } from '../functions/setRealName'; import { RateLimiter } from '../lib'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { setRealName(name: string): string; diff --git a/apps/meteor/app/lib/server/methods/setUsername.ts b/apps/meteor/app/lib/server/methods/setUsername.ts index 900848f37783..58fac75ed3bd 100644 --- a/apps/meteor/app/lib/server/methods/setUsername.ts +++ b/apps/meteor/app/lib/server/methods/setUsername.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { setUsernameWithValidation } from '../functions/setUsername'; import { RateLimiter } from '../lib'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { setUsername(username: string, param?: { joinDefaultChannelsSilenced?: boolean }): string; diff --git a/apps/meteor/app/lib/server/methods/unarchiveRoom.ts b/apps/meteor/app/lib/server/methods/unarchiveRoom.ts index 0f9349365f4f..74b74c11e54a 100644 --- a/apps/meteor/app/lib/server/methods/unarchiveRoom.ts +++ b/apps/meteor/app/lib/server/methods/unarchiveRoom.ts @@ -1,13 +1,13 @@ import { isRegisterUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { unarchiveRoom } from '../functions/unarchiveRoom'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { unarchiveRoom(rid: string): Promise; diff --git a/apps/meteor/app/lib/server/methods/unblockUser.ts b/apps/meteor/app/lib/server/methods/unblockUser.ts index 6c8a9d486bab..7b4bc5660010 100644 --- a/apps/meteor/app/lib/server/methods/unblockUser.ts +++ b/apps/meteor/app/lib/server/methods/unblockUser.ts @@ -1,9 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +import { notifyOnSubscriptionChangedByRoomIdAndUserIds } from '../lib/notifyListener'; + +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { unblockUser({ rid, blocked }: { rid: string; blocked: string }): boolean; @@ -20,14 +22,22 @@ Meteor.methods({ throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'blockUser' }); } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); - const subscription2 = await Subscriptions.findOneByRoomIdAndUserId(rid, blocked); + const [blockedUser, blockerUser] = await Promise.all([ + Subscriptions.findOneByRoomIdAndUserId(rid, blocked, { projection: { _id: 1 } }), + Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { _id: 1 } }), + ]); - if (!subscription || !subscription2) { + if (!blockedUser || !blockerUser) { throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'blockUser' }); } - await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId); + const [blockedResponse, blockerResponse] = await Subscriptions.unsetBlockedByRoomId(rid, blocked, userId); + + const listenerUsers = [...(blockedResponse?.modifiedCount ? [blocked] : []), ...(blockerResponse?.modifiedCount ? [userId] : [])]; + + if (listenerUsers.length) { + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, listenerUsers); + } return true; }, diff --git a/apps/meteor/app/lib/server/methods/updateMessage.ts b/apps/meteor/app/lib/server/methods/updateMessage.ts index fe15349e9158..c03208a438e9 100644 --- a/apps/meteor/app/lib/server/methods/updateMessage.ts +++ b/apps/meteor/app/lib/server/methods/updateMessage.ts @@ -1,6 +1,6 @@ import type { IEditedMessage, IMessage, IUser, AtLeast } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import moment from 'moment'; @@ -12,7 +12,11 @@ import { updateMessage } from '../functions/updateMessage'; const allowedEditedFields = ['tshow', 'alias', 'attachments', 'avatar', 'emoji', 'msg', 'customFields', 'content', 'e2eMentions']; -export async function executeUpdateMessage(uid: IUser['_id'], message: AtLeast, previewUrls?: string[]) { +export async function executeUpdateMessage( + uid: IUser['_id'], + message: AtLeast, + previewUrls?: string[], +) { const originalMessage = await Messages.findOneById(message._id); if (!originalMessage?._id) { return; @@ -26,8 +30,11 @@ export async function executeUpdateMessage(uid: IUser['_id'], message: AtLeast { + async (message, { room }) => { // TODO: check if I need to test this 60 second rule. // If the message was edited, or is older than 60 seconds (imported) // the notifications will be skipped, so we can also skip this validation diff --git a/apps/meteor/app/livechat-enterprise/client/startup.ts b/apps/meteor/app/livechat-enterprise/client/startup.ts index 0535f8926a7d..bdf6c2549816 100644 --- a/apps/meteor/app/livechat-enterprise/client/startup.ts +++ b/apps/meteor/app/livechat-enterprise/client/startup.ts @@ -15,7 +15,7 @@ const businessHours: Record = { Meteor.startup(() => { Tracker.autorun(async () => { const bhType = settings.get('Livechat_business_hour_type'); - if (await hasLicense('livechat-enterprise')) { + if (bhType && (await hasLicense('livechat-enterprise'))) { businessHourManager.registerBusinessHourBehavior(businessHours[bhType.toLowerCase()]); } }); diff --git a/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts b/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts index eb0742813c1f..54c6ba4d75f1 100644 --- a/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts +++ b/apps/meteor/app/livechat/client/externalFrame/generateNewKey.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { sdk } from '../../../utils/client/lib/SDKClient'; diff --git a/apps/meteor/app/livechat/client/lib/chartHandler.ts b/apps/meteor/app/livechat/client/lib/chartHandler.ts index 19c1a004ca22..da2d4be3735c 100644 --- a/apps/meteor/app/livechat/client/lib/chartHandler.ts +++ b/apps/meteor/app/livechat/client/lib/chartHandler.ts @@ -177,10 +177,9 @@ export const drawDoughnutChart = async ( chartContext: { destroy: () => void } | undefined, dataLabels: string[], dataPoints: number[], -): Promise | void> => { +): Promise => { if (!chart) { - console.error('No chart element'); - return; + throw new Error('No chart element'); } if (chartContext) { chartContext.destroy(); @@ -200,7 +199,7 @@ export const drawDoughnutChart = async ( ], }, options: doughnutChartConfiguration(title), - }); + }) as ChartType; }; /** @@ -209,12 +208,12 @@ export const drawDoughnutChart = async ( * @param {String} label [chart label] * @param {Array(Double)} data [updated data] */ -export const updateChart = async (c: ChartType, label: string, data: { [x: string]: number }): Promise => { +export const updateChart = async (c: ChartType, label: string, data: number[]): Promise => { const chart = await c; if (chart.data?.labels?.indexOf(label) === -1) { // insert data chart.data.labels.push(label); - chart.data.datasets.forEach((dataset: { data: any[] }, idx: string | number) => { + chart.data.datasets.forEach((dataset: { data: any[] }, idx: number) => { dataset.data.push(data[idx]); }); } else { @@ -224,7 +223,7 @@ export const updateChart = async (c: ChartType, label: string, data: { [x: strin return; } - chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: string | number) => { + chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: number) => { dataset.data[index] = data[idx]; }); } diff --git a/apps/meteor/app/livechat/server/api/lib/agents.ts b/apps/meteor/app/livechat/server/api/lib/agents.ts index 3bc5180c2f59..2dbcce8c7e2e 100644 --- a/apps/meteor/app/livechat/server/api/lib/agents.ts +++ b/apps/meteor/app/livechat/server/api/lib/agents.ts @@ -7,14 +7,8 @@ export async function findAgentDepartments({ }: { enabledDepartmentsOnly?: boolean; agentId: string; -}): Promise<{ departments: ILivechatDepartmentAgents[] }> { - if (enabledDepartmentsOnly) { - return { - departments: await LivechatDepartmentAgents.findActiveDepartmentsByAgentId(agentId).toArray(), - }; - } - +}): Promise<{ departments: (ILivechatDepartmentAgents & { departmentName: string })[] }> { return { - departments: await LivechatDepartmentAgents.find({ agentId }).toArray(), + departments: await LivechatDepartmentAgents.findDepartmentsOfAgent(agentId, enabledDepartmentsOnly).toArray(), }; } diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 617d255cb6cb..a922edd40899 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -61,6 +61,10 @@ export function findGuest(token: string): Promise { }); } +export function findGuestWithoutActivity(token: string): Promise { + return LivechatVisitors.getVisitorByToken(token, { projection: { name: 1, username: 1, token: 1, visitorEmails: 1, department: 1 } }); +} + export async function findRoom(token: string, rid?: string): Promise { const fields = { t: 1, diff --git a/apps/meteor/app/livechat/server/api/v1/agent.ts b/apps/meteor/app/livechat/server/api/v1/agent.ts index 4c3cad33c130..abc6163fe9c9 100644 --- a/apps/meteor/app/livechat/server/api/v1/agent.ts +++ b/apps/meteor/app/livechat/server/api/v1/agent.ts @@ -6,6 +6,7 @@ import { isGETAgentNextToken, isPOSTLivechatAgentStatusProps } from '@rocket.cha import { API } from '../../../../api/server'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { Livechat as LivechatTyped } from '../../lib/LivechatTyped'; +import { RoutingManager } from '../../lib/RoutingManager'; import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat'; API.v1.addRoute('livechat/agent.info/:rid/:token', { @@ -48,7 +49,7 @@ API.v1.addRoute( } } - const agentData = await LivechatTyped.getNextAgent(department); + const agentData = await RoutingManager.getNextAgent(department); if (!agentData) { throw new Error('agent-not-found'); } diff --git a/apps/meteor/app/livechat/server/api/v1/config.ts b/apps/meteor/app/livechat/server/api/v1/config.ts index 79a6132136c5..17a2945e75de 100644 --- a/apps/meteor/app/livechat/server/api/v1/config.ts +++ b/apps/meteor/app/livechat/server/api/v1/config.ts @@ -2,8 +2,9 @@ import { isGETLivechatConfigParams } from '@rocket.chat/rest-typings'; import mem from 'mem'; import { API } from '../../../../api/server'; +import { settings as serverSettings } from '../../../../settings/server'; import { Livechat } from '../../lib/LivechatTyped'; -import { settings, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat'; +import { settings, findOpenRoom, getExtraConfigInfo, findAgent, findGuestWithoutActivity } from '../lib/livechat'; const cachedSettings = mem(settings, { maxAge: process.env.TEST_MODE === 'true' ? 1 : 1000, cacheKey: JSON.stringify }); @@ -12,7 +13,7 @@ API.v1.addRoute( { validateParams: isGETLivechatConfigParams }, { async get() { - const enabled = Livechat.enabled(); + const enabled = serverSettings.get('Livechat_enabled'); if (!enabled) { return API.v1.success({ config: { enabled: false } }); @@ -23,7 +24,7 @@ API.v1.addRoute( const config = await cachedSettings({ businessUnit }); const status = await Livechat.online(department); - const guest = token ? await Livechat.findGuest(token) : null; + const guest = token ? await findGuestWithoutActivity(token) : null; const room = guest ? await findOpenRoom(guest.token) : undefined; const agent = guest && room && room.servedBy && (await findAgent(room.servedBy._id)); diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts index 57c1d117f1b0..91b18a6b21af 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.ts +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -1,14 +1,18 @@ import { LivechatCustomField, LivechatVisitors } from '@rocket.chat/models'; +import { isPOSTOmnichannelContactsProps } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { API } from '../../../../api/server'; -import { Contacts } from '../../lib/Contacts'; +import { Contacts, createContact } from '../../lib/Contacts'; API.v1.addRoute( 'omnichannel/contact', - { authRequired: true, permissionsRequired: ['view-l-room'] }, + { + authRequired: true, + permissionsRequired: ['view-l-room'], + }, { async post() { check(this.bodyParams, { @@ -82,3 +86,18 @@ API.v1.addRoute( }, }, ); + +API.v1.addRoute( + 'omnichannel/contacts', + { authRequired: true, permissionsRequired: ['create-livechat-contact'], validateParams: isPOSTOmnichannelContactsProps }, + { + async post() { + if (!process.env.TEST_MODE) { + throw new Meteor.Error('error-not-allowed', 'This endpoint is only allowed in test mode'); + } + const contactId = await createContact({ ...this.bodyParams, unknown: false }); + + return API.v1.success({ contactId }); + }, + }, +); diff --git a/apps/meteor/app/livechat/server/api/v1/pageVisited.ts b/apps/meteor/app/livechat/server/api/v1/pageVisited.ts index e89a3e17f0a1..2688ad673af0 100644 --- a/apps/meteor/app/livechat/server/api/v1/pageVisited.ts +++ b/apps/meteor/app/livechat/server/api/v1/pageVisited.ts @@ -1,5 +1,4 @@ import type { IOmnichannelSystemMessage } from '@rocket.chat/core-typings'; -import { Messages } from '@rocket.chat/models'; import { isPOSTLivechatPageVisitedParams } from '@rocket.chat/rest-typings'; import { API } from '../../../../api/server'; @@ -11,17 +10,13 @@ API.v1.addRoute( { async post() { const { token, rid, pageInfo } = this.bodyParams; - const msgId = await Livechat.savePageHistory(token, rid, pageInfo); - if (!msgId) { - return API.v1.success(); - } - const message = await Messages.findOneById(msgId); + const message = await Livechat.savePageHistory(token, rid, pageInfo); if (!message) { return API.v1.success(); } - const { msg, navigation } = message; + const { msg, navigation } = message as IOmnichannelSystemMessage; return API.v1.success({ page: { msg, navigation } }); }, }, diff --git a/apps/meteor/app/livechat/server/api/v1/transcript.ts b/apps/meteor/app/livechat/server/api/v1/transcript.ts index b36873a6ac27..e46e841628f1 100644 --- a/apps/meteor/app/livechat/server/api/v1/transcript.ts +++ b/apps/meteor/app/livechat/server/api/v1/transcript.ts @@ -6,6 +6,7 @@ import { isPOSTLivechatTranscriptParams, isPOSTLivechatTranscriptRequestParams } import { i18n } from '../../../../../server/lib/i18n'; import { API } from '../../../../api/server'; import { Livechat } from '../../lib/LivechatTyped'; +import { sendTranscript } from '../../lib/sendTranscript'; API.v1.addRoute( 'livechat/transcript', @@ -13,7 +14,7 @@ API.v1.addRoute( { async post() { const { token, rid, email } = this.bodyParams; - if (!(await Livechat.sendTranscript({ token, rid, email }))) { + if (!(await sendTranscript({ token, rid, email }))) { return API.v1.failure({ message: i18n.t('Error_sending_livechat_transcript') }); } diff --git a/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts b/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts index 11463bcf00d4..a8f145f47d39 100644 --- a/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts +++ b/apps/meteor/app/livechat/server/externalFrame/generateNewKey.ts @@ -1,7 +1,7 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { omnichannelExternalFrameGenerateKey(): unknown; diff --git a/apps/meteor/app/livechat/server/hooks/afterSaveOmnichannelMessage.ts b/apps/meteor/app/livechat/server/hooks/afterSaveOmnichannelMessage.ts new file mode 100644 index 000000000000..311343c4ad01 --- /dev/null +++ b/apps/meteor/app/livechat/server/hooks/afterSaveOmnichannelMessage.ts @@ -0,0 +1,24 @@ +import { isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { LivechatRooms } from '@rocket.chat/models'; + +import { callbacks } from '../../../../lib/callbacks'; + +callbacks.add( + 'afterSaveMessage', + async (message, { room }) => { + if (!isOmnichannelRoom(room)) { + return message; + } + + const updater = LivechatRooms.getUpdater(); + const result = await callbacks.run('afterOmnichannelSaveMessage', message, { room, roomUpdater: updater }); + + if (updater.hasChanges()) { + await LivechatRooms.updateFromUpdater({ _id: room._id }, updater); + } + + return result; + }, + callbacks.priority.MEDIUM, + 'after-omnichannel-save-message', +); diff --git a/apps/meteor/app/livechat/server/hooks/leadCapture.ts b/apps/meteor/app/livechat/server/hooks/leadCapture.ts index 4b987c00c02e..6a3826b8ba11 100644 --- a/apps/meteor/app/livechat/server/hooks/leadCapture.ts +++ b/apps/meteor/app/livechat/server/hooks/leadCapture.ts @@ -1,5 +1,5 @@ import type { IMessage, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { isEditedMessage } from '@rocket.chat/core-typings'; import { LivechatVisitors } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; @@ -31,12 +31,8 @@ function validateMessage(message: IMessage, room: IOmnichannelRoom) { } callbacks.add( - 'afterSaveMessage', - async (message, room) => { - if (!isOmnichannelRoom(room)) { - return message; - } - + 'afterOmnichannelSaveMessage', + async (message, { room }) => { if (!validateMessage(message, room)) { return message; } diff --git a/apps/meteor/app/livechat/server/hooks/markRoomNotResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomNotResponded.ts index f0bfb8574e6a..01d3014f1c27 100644 --- a/apps/meteor/app/livechat/server/hooks/markRoomNotResponded.ts +++ b/apps/meteor/app/livechat/server/hooks/markRoomNotResponded.ts @@ -1,15 +1,11 @@ -import { isOmnichannelRoom, isEditedMessage } from '@rocket.chat/core-typings'; +import { isEditedMessage } from '@rocket.chat/core-typings'; import { LivechatRooms } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; callbacks.add( - 'afterSaveMessage', - async (message, room) => { - if (!isOmnichannelRoom(room)) { - return message; - } - + 'afterOmnichannelSaveMessage', + (message, { room, roomUpdater }) => { // skips this callback if the message was edited if (!message || isEditedMessage(message)) { return message; @@ -21,11 +17,11 @@ callbacks.add( } // check if room is yet awaiting for response - if (typeof room.t !== 'undefined' && room.t === 'l' && room.waitingResponse) { + if (room.waitingResponse) { return message; } - await LivechatRooms.setNotResponseByRoomId(room._id); + LivechatRooms.getNotResponseByRoomIdUpdateQuery(roomUpdater); return message; }, diff --git a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts index 48ec985aa42c..6820bd4664bd 100644 --- a/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts +++ b/apps/meteor/app/livechat/server/hooks/markRoomResponded.ts @@ -1,78 +1,72 @@ -import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { isOmnichannelRoom, isEditedMessage } from '@rocket.chat/core-typings'; +import type { IOmnichannelRoom, IMessage } from '@rocket.chat/core-typings'; +import { isEditedMessage, isMessageFromVisitor, isSystemMessage } from '@rocket.chat/core-typings'; +import type { Updater } from '@rocket.chat/models'; import { LivechatRooms, LivechatVisitors, LivechatInquiry } from '@rocket.chat/models'; import moment from 'moment'; import { callbacks } from '../../../../lib/callbacks'; import { notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener'; -callbacks.add( - 'afterSaveMessage', - async (message, room) => { - if (!isOmnichannelRoom(room)) { - return message; - } - - // skips this callback if the message was edited - if (!message || isEditedMessage(message)) { - return message; - } +export async function markRoomResponded( + message: IMessage, + room: IOmnichannelRoom, + roomUpdater: Updater, +): Promise { + if (isSystemMessage(message) || isEditedMessage(message) || isMessageFromVisitor(message)) { + return; + } - // skips this callback if the message is a system message - if (message.t) { - return message; - } + const monthYear = moment().format('YYYY-MM'); + const isVisitorActive = await LivechatVisitors.isVisitorActiveOnPeriod(room.v._id, monthYear); - // if the message has a token, it was sent by the visitor, so ignore it - if (message.token) { - return message; - } + // Case: agent answers & visitor is not active, we mark visitor as active + if (!isVisitorActive) { + await LivechatVisitors.markVisitorActiveForPeriod(room.v._id, monthYear); + } - // Return YYYY-MM from moment - const monthYear = moment().format('YYYY-MM'); - const isVisitorActive = await LivechatVisitors.isVisitorActiveOnPeriod(room.v._id, monthYear); + if (!room.v?.activity?.includes(monthYear)) { + LivechatRooms.getVisitorActiveForPeriodUpdateQuery(monthYear, roomUpdater); + const livechatInquiry = await LivechatInquiry.markInquiryActiveForPeriod(room._id, monthYear); - // Case: agent answers & visitor is not active, we mark visitor as active - if (!isVisitorActive) { - await LivechatVisitors.markVisitorActiveForPeriod(room.v._id, monthYear); + if (livechatInquiry) { + void notifyOnLivechatInquiryChanged(livechatInquiry, 'updated', { v: livechatInquiry.v }); } + } - if (!room.v?.activity?.includes(monthYear)) { - const [, livechatInquiry] = await Promise.all([ - LivechatRooms.markVisitorActiveForPeriod(room._id, monthYear), - LivechatInquiry.markInquiryActiveForPeriod(room._id, monthYear), - ]); - if (livechatInquiry) { - void notifyOnLivechatInquiryChanged(livechatInquiry, 'updated', { v: livechatInquiry.v }); - } - } + if (room.responseBy) { + LivechatRooms.getAgentLastMessageTsUpdateQuery(roomUpdater); + } + if (!room.waitingResponse) { + // case where agent sends second message or any subsequent message in a room before visitor responds to the first message + // in this case, we just need to update the lastMessageTs of the responseBy object if (room.responseBy) { - await LivechatRooms.setAgentLastMessageTs(room._id); + LivechatRooms.getAgentLastMessageTsUpdateQuery(roomUpdater); } - // check if room is yet awaiting for response from visitor - if (!room.waitingResponse) { - // case where agent sends second message or any subsequent message in a room before visitor responds to the first message - // in this case, we just need to update the lastMessageTs of the responseBy object - if (room.responseBy) { - await LivechatRooms.setAgentLastMessageTs(room._id); - } - return message; - } + return room.responseBy; + } + + const responseBy: IOmnichannelRoom['responseBy'] = room.responseBy || { + _id: message.u._id, + username: message.u.username, + firstResponseTs: new Date(message.ts), + lastMessageTs: new Date(message.ts), + }; - // This is the first message from agent after visitor had last responded - const responseBy: IOmnichannelRoom['responseBy'] = room.responseBy || { - _id: message.u._id, - username: message.u.username, - firstResponseTs: new Date(message.ts), - lastMessageTs: new Date(message.ts), - }; + LivechatRooms.getResponseByRoomIdUpdateQuery(responseBy, roomUpdater); - // this unsets waitingResponse and sets responseBy object - await LivechatRooms.setResponseByRoomId(room._id, responseBy); + return responseBy; +} + +callbacks.add( + 'afterOmnichannelSaveMessage', + async (message, { room, roomUpdater }) => { + if (!message || isEditedMessage(message) || isMessageFromVisitor(message) || isSystemMessage(message)) { + return; + } - return message; + await markRoomResponded(message, room, roomUpdater); }, callbacks.priority.HIGH, 'markRoomResponded', diff --git a/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts b/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts index 8a5a4c280670..a6031bd42efa 100644 --- a/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts +++ b/apps/meteor/app/livechat/server/hooks/processRoomAbandonment.ts @@ -6,11 +6,12 @@ import moment from 'moment'; import { callbacks } from '../../../../lib/callbacks'; import { settings } from '../../../settings/server'; import { businessHourManager } from '../business-hour'; +import type { CloseRoomParams } from '../lib/localTypes'; -const getSecondsWhenOfficeHoursIsDisabled = (room: IOmnichannelRoom, agentLastMessage: IMessage) => +export const getSecondsWhenOfficeHoursIsDisabled = (room: IOmnichannelRoom, agentLastMessage: IMessage) => moment(new Date(room.closedAt || new Date())).diff(moment(new Date(agentLastMessage.ts)), 'seconds'); -const parseDays = ( +export const parseDays = ( acc: Record, day: IBusinessHourWorkHour, ) => { @@ -22,7 +23,7 @@ const parseDays = ( return acc; }; -const getSecondsSinceLastAgentResponse = async (room: IOmnichannelRoom, agentLastMessage: IMessage) => { +export const getSecondsSinceLastAgentResponse = async (room: IOmnichannelRoom, agentLastMessage: IMessage) => { if (!settings.get('Livechat_enable_business_hours')) { return getSecondsWhenOfficeHoursIsDisabled(room, agentLastMessage); } @@ -43,65 +44,91 @@ const getSecondsSinceLastAgentResponse = async (room: IOmnichannelRoom, agentLas officeDays = (await businessHourManager.getBusinessHour())?.workHours.reduce(parseDays, {}); } - if (!officeDays) { + // Empty object we assume invalid config + if (!officeDays || !Object.keys(officeDays).length) { return getSecondsWhenOfficeHoursIsDisabled(room, agentLastMessage); } let totalSeconds = 0; - const endOfConversation = moment(new Date(room.closedAt || new Date())); - const startOfInactivity = moment(new Date(agentLastMessage.ts)); + const endOfConversation = moment.utc(new Date(room.closedAt || new Date())); + const startOfInactivity = moment.utc(new Date(agentLastMessage.ts)); const daysOfInactivity = endOfConversation.clone().startOf('day').diff(startOfInactivity.clone().startOf('day'), 'days'); - const inactivityDay = moment(new Date(agentLastMessage.ts)); + const inactivityDay = moment.utc(new Date(agentLastMessage.ts)); + for (let index = 0; index <= daysOfInactivity; index++) { const today = inactivityDay.clone().format('dddd'); const officeDay = officeDays[today]; - const startTodaysOfficeHour = moment(`${officeDay.start.day}:${officeDay.start.time}`, 'dddd:HH:mm').add(index, 'days'); - const endTodaysOfficeHour = moment(`${officeDay.finish.day}:${officeDay.finish.time}`, 'dddd:HH:mm').add(index, 'days'); - if (officeDays[today].open) { - const firstDayOfInactivity = startOfInactivity.clone().format('D') === inactivityDay.clone().format('D'); - const lastDayOfInactivity = endOfConversation.clone().format('D') === inactivityDay.clone().format('D'); - - if (!firstDayOfInactivity && !lastDayOfInactivity) { - totalSeconds += endTodaysOfficeHour.clone().diff(startTodaysOfficeHour, 'seconds'); - } else { - const end = endOfConversation.isBefore(endTodaysOfficeHour) ? endOfConversation : endTodaysOfficeHour; - const start = firstDayOfInactivity ? inactivityDay : startTodaysOfficeHour; - totalSeconds += end.clone().diff(start, 'seconds'); - } + if (!officeDay) { + inactivityDay.add(1, 'days'); + continue; + } + if (!officeDay.open) { + inactivityDay.add(1, 'days'); + continue; + } + if (!officeDay?.start?.time || !officeDay?.finish?.time) { + inactivityDay.add(1, 'days'); + continue; } - inactivityDay.add(1, 'days'); - } - return totalSeconds; -}; -callbacks.add( - 'livechat.closeRoom', - async (params) => { - const { room } = params; + const [officeStartHour, officeStartMinute] = officeDay.start.time.split(':'); + const [officeCloseHour, officeCloseMinute] = officeDay.finish.time.split(':'); + // We should only take in consideration the time where the office is open and the conversation was inactive + const todayStartOfficeHours = inactivityDay + .clone() + .set({ hour: parseInt(officeStartHour, 10), minute: parseInt(officeStartMinute, 10) }); + const todayEndOfficeHours = inactivityDay.clone().set({ hour: parseInt(officeCloseHour, 10), minute: parseInt(officeCloseMinute, 10) }); - if (!isOmnichannelRoom(room)) { - return params; + // 1: Room was inactive the whole day, we add the whole time BH is inactive + if (startOfInactivity.isBefore(todayStartOfficeHours) && endOfConversation.isAfter(todayEndOfficeHours)) { + totalSeconds += todayEndOfficeHours.diff(todayStartOfficeHours, 'seconds'); } - const closedByAgent = room.closer !== 'visitor'; - const wasTheLastMessageSentByAgent = room.lastMessage && !room.lastMessage.token; - if (!closedByAgent || !wasTheLastMessageSentByAgent) { - return params; + // 2: Room was inactive before start but was closed before end of BH, we add the inactive time + if (startOfInactivity.isBefore(todayStartOfficeHours) && endOfConversation.isBefore(todayEndOfficeHours)) { + totalSeconds += endOfConversation.diff(todayStartOfficeHours, 'seconds'); } - if (!room.v?.lastMessageTs) { - return params; + // 3: Room was inactive after start and ended after end of BH, we add the inactive time + if (startOfInactivity.isAfter(todayStartOfficeHours) && endOfConversation.isAfter(todayEndOfficeHours)) { + totalSeconds += todayEndOfficeHours.diff(startOfInactivity, 'seconds'); } - const agentLastMessage = await Messages.findAgentLastMessageByVisitorLastMessageTs(room._id, room.v.lastMessageTs); - if (!agentLastMessage) { - return params; + // 4: Room was inactive after start and before end of BH, we add the inactive time + if (startOfInactivity.isAfter(todayStartOfficeHours) && endOfConversation.isBefore(todayEndOfficeHours)) { + totalSeconds += endOfConversation.diff(startOfInactivity, 'seconds'); } - const secondsSinceLastAgentResponse = await getSecondsSinceLastAgentResponse(room, agentLastMessage); - await LivechatRooms.setVisitorInactivityInSecondsById(room._id, secondsSinceLastAgentResponse); + inactivityDay.add(1, 'days'); + } + return totalSeconds; +}; + +export const onCloseRoom = async (params: { room: IOmnichannelRoom; options: CloseRoomParams['options'] }) => { + const { room } = params; + + if (!isOmnichannelRoom(room)) { return params; - }, - callbacks.priority.HIGH, - 'process-room-abandonment', -); + } + + const closedByAgent = room.closer !== 'visitor'; + const wasTheLastMessageSentByAgent = room.lastMessage && !room.lastMessage.token; + if (!closedByAgent || !wasTheLastMessageSentByAgent) { + return params; + } + + if (!room.v?.lastMessageTs) { + return params; + } + + const agentLastMessage = await Messages.findAgentLastMessageByVisitorLastMessageTs(room._id, room.v.lastMessageTs); + if (!agentLastMessage) { + return params; + } + const secondsSinceLastAgentResponse = await getSecondsSinceLastAgentResponse(room, agentLastMessage); + await LivechatRooms.setVisitorInactivityInSecondsById(room._id, secondsSinceLastAgentResponse); + + return params; +}; + +callbacks.add('livechat.closeRoom', onCloseRoom, callbacks.priority.HIGH, 'process-room-abandonment'); diff --git a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts index e92e6b4d940b..9553e9fe981b 100644 --- a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts +++ b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts @@ -1,84 +1,82 @@ -import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { isEditedMessage, isMessageFromVisitor, isSystemMessage } from '@rocket.chat/core-typings'; +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatRooms } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { normalizeMessageFileUpload } from '../../../utils/server/functions/normalizeMessageFileUpload'; -callbacks.add( - 'afterSaveMessage', - async (message, room) => { - // check if room is livechat - if (!isOmnichannelRoom(room)) { - return message; - } +const getMetricValue = (metric: T | undefined, defaultValue: T): T => metric ?? defaultValue; +const calculateTimeDifference = (startTime: T, now: Date): number => + (now.getTime() - new Date(startTime).getTime()) / 1000; +const calculateAvgResponseTime = (totalResponseTime: number, newResponseTime: number, responseCount: number) => + (totalResponseTime + newResponseTime) / (responseCount + 1); - // skips this callback if the message was edited - if (!message || isEditedMessage(message)) { - return message; - } +const getFirstResponseAnalytics = ( + visitorLastQuery: Date, + agentJoinTime: Date, + totalResponseTime: number, + responseCount: number, + now: Date, +) => { + const responseTime = calculateTimeDifference(visitorLastQuery, now); + const reactionTime = calculateTimeDifference(agentJoinTime, now); + const avgResponseTime = calculateAvgResponseTime(totalResponseTime, responseTime, responseCount); - // if the message has a token, it was sent by the visitor - if (message.token) { - // When visitor sends a mesage, most metrics wont be calculated/served. - // But, v.lq (last query) will be updated to the message time. This has to be done - // As not doing it will cause the metrics to be crazy and not have real values. - await LivechatRooms.saveAnalyticsDataByRoomId(room, message); - return message; - } + return { + firstResponseDate: now, + firstResponseTime: responseTime, + responseTime, + avgResponseTime, + firstReactionDate: now, + firstReactionTime: reactionTime, + reactionTime, + }; +}; - if (message.file) { - message = { ...(await normalizeMessageFileUpload(message)), ...{ _updatedAt: message._updatedAt } }; - } +const getSubsequentResponseAnalytics = (visitorLastQuery: Date, totalResponseTime: number, responseCount: number, now: Date) => { + const responseTime = calculateTimeDifference(visitorLastQuery, now); + const avgResponseTime = calculateAvgResponseTime(totalResponseTime, responseTime, responseCount); - const now = new Date(); - let analyticsData; + return { + responseTime, + avgResponseTime, + reactionTime: responseTime, + }; +}; - const visitorLastQuery = room.metrics?.v ? room.metrics.v.lq : room.ts; - const agentLastReply = room.metrics?.servedBy ? room.metrics.servedBy.lr : room.ts; - const agentJoinTime = room.servedBy?.ts ? room.servedBy.ts : room.ts; +const getAnalyticsData = (room: IOmnichannelRoom, now: Date): Record | undefined => { + const visitorLastQuery = getMetricValue(room.metrics?.v?.lq, room.ts); + const agentLastReply = getMetricValue(room.metrics?.servedBy?.lr, room.ts); + const agentJoinTime = getMetricValue(room.servedBy?.ts, room.ts); + const totalResponseTime = getMetricValue(room.metrics?.response?.tt, 0); + const responseCount = getMetricValue(room.metrics?.response?.total, 0); - const isResponseTt = room.metrics?.response?.tt; - const isResponseTotal = room.metrics?.response?.total; + if (agentLastReply === room.ts) { + return getFirstResponseAnalytics(visitorLastQuery, agentJoinTime, totalResponseTime, responseCount, now); + } + if (visitorLastQuery > agentLastReply) { + return getSubsequentResponseAnalytics(visitorLastQuery, totalResponseTime, responseCount, now); + } +}; - if (agentLastReply === room.ts) { - // first response - const firstResponseDate = now; - const firstResponseTime = (now.getTime() - new Date(visitorLastQuery).getTime()) / 1000; - const responseTime = (now.getTime() - new Date(visitorLastQuery).getTime()) / 1000; - const avgResponseTime = - ((isResponseTt ? room.metrics?.response?.tt : 0) || 0 + responseTime) / - ((isResponseTotal ? room.metrics?.response?.total : 0) || 0 + 1); - - const firstReactionDate = now; - const firstReactionTime = (now.getTime() - new Date(agentJoinTime).getTime()) / 1000; - const reactionTime = (now.getTime() - new Date(agentJoinTime).getTime()) / 1000; - - analyticsData = { - firstResponseDate, - firstResponseTime, - responseTime, - avgResponseTime, - firstReactionDate, - firstReactionTime, - reactionTime, - }; - } else if (visitorLastQuery > agentLastReply) { - // response, not first - const responseTime = (now.getTime() - new Date(visitorLastQuery).getTime()) / 1000; - const avgResponseTime = - ((isResponseTt ? room.metrics?.response?.tt : 0) || 0 + responseTime) / - ((isResponseTotal ? room.metrics?.response?.total : 0) || 0 + 1); +callbacks.add( + 'afterOmnichannelSaveMessage', + async (message, { room, roomUpdater }) => { + if (!message || isEditedMessage(message) || isSystemMessage(message)) { + return message; + } - const reactionTime = (now.getTime() - new Date(visitorLastQuery).getTime()) / 1000; + if (message.file) { + message = { ...(await normalizeMessageFileUpload(message)), ...{ _updatedAt: message._updatedAt } }; + } - analyticsData = { - responseTime, - avgResponseTime, - reactionTime, - }; - } // ignore, its continuing response + if (isMessageFromVisitor(message)) { + LivechatRooms.getAnalyticsUpdateQueryBySentByVisitor(room, message, roomUpdater); + } else { + const analyticsData = getAnalyticsData(room, new Date()); + LivechatRooms.getAnalyticsUpdateQueryBySentByAgent(room, message, analyticsData, roomUpdater); + } - await LivechatRooms.saveAnalyticsDataByRoomId(room, message, analyticsData); return message; }, callbacks.priority.LOW, diff --git a/apps/meteor/app/livechat/server/hooks/saveContactLastChat.ts b/apps/meteor/app/livechat/server/hooks/saveContactLastChat.ts index 6f42a910417d..9969f03bf8bb 100644 --- a/apps/meteor/app/livechat/server/hooks/saveContactLastChat.ts +++ b/apps/meteor/app/livechat/server/hooks/saveContactLastChat.ts @@ -1,7 +1,7 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { LivechatVisitors } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; -import { Livechat } from '../lib/LivechatTyped'; callbacks.add( 'livechat.newRoom', @@ -19,7 +19,7 @@ callbacks.add( _id, ts: new Date(), }; - await Livechat.updateLastChat(guestId, lastChat); + await LivechatVisitors.setLastChatById(guestId, lastChat); }, callbacks.priority.MEDIUM, 'livechat-save-last-chat', diff --git a/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts b/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts index e65f1d99b884..1925e135a562 100644 --- a/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts +++ b/apps/meteor/app/livechat/server/hooks/saveLastMessageToInquiry.ts @@ -1,4 +1,4 @@ -import { isOmnichannelRoom, isEditedMessage } from '@rocket.chat/core-typings'; +import { isEditedMessage } from '@rocket.chat/core-typings'; import { LivechatInquiry } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; @@ -7,9 +7,9 @@ import { settings } from '../../../settings/server'; import { RoutingManager } from '../lib/RoutingManager'; callbacks.add( - 'afterSaveMessage', - async (message, room) => { - if (!isOmnichannelRoom(room) || isEditedMessage(message) || message.t) { + 'afterOmnichannelSaveMessage', + async (message, { room }) => { + if (isEditedMessage(message) || message.t) { return message; } diff --git a/apps/meteor/app/livechat/server/hooks/saveLastVisitorMessageTs.ts b/apps/meteor/app/livechat/server/hooks/saveLastVisitorMessageTs.ts index 4bc28c3990ba..03dcfdbf81bd 100644 --- a/apps/meteor/app/livechat/server/hooks/saveLastVisitorMessageTs.ts +++ b/apps/meteor/app/livechat/server/hooks/saveLastVisitorMessageTs.ts @@ -1,22 +1,18 @@ -import { isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { isMessageFromVisitor } from '@rocket.chat/core-typings'; import { LivechatRooms } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; callbacks.add( - 'afterSaveMessage', - async (message, room) => { - if (!(isOmnichannelRoom(room) && room.v.token)) { - return message; - } - if (message.t) { - return message; - } - if (!message.token) { + 'afterOmnichannelSaveMessage', + async (message, { roomUpdater }) => { + if (message.t || !isMessageFromVisitor(message)) { return message; } - await LivechatRooms.setVisitorLastMessageTimestampByRoomId(room._id, message.ts); + await LivechatRooms.getVisitorLastMessageTsUpdateQueryByRoomId(message.ts, roomUpdater); + + return message; }, callbacks.priority.HIGH, 'save-last-visitor-message-timestamp', diff --git a/apps/meteor/app/livechat/server/hooks/sendEmailTranscriptOnClose.ts b/apps/meteor/app/livechat/server/hooks/sendEmailTranscriptOnClose.ts index fcc0e0228bc7..f6a35f4dd7f9 100644 --- a/apps/meteor/app/livechat/server/hooks/sendEmailTranscriptOnClose.ts +++ b/apps/meteor/app/livechat/server/hooks/sendEmailTranscriptOnClose.ts @@ -3,8 +3,8 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings'; import { LivechatRooms } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; -import { Livechat } from '../lib/LivechatTyped'; import type { CloseRoomParams } from '../lib/LivechatTyped'; +import { sendTranscript } from '../lib/sendTranscript'; type LivechatCloseCallbackParams = { room: IOmnichannelRoom; @@ -30,10 +30,7 @@ const sendEmailTranscriptOnClose = async (params: LivechatCloseCallbackParams): const { email, subject, requestedBy: user } = transcriptData; - await Promise.all([ - Livechat.sendTranscript({ token, rid, email, subject, user }), - LivechatRooms.unsetEmailTranscriptRequestedByRoomId(rid), - ]); + await Promise.all([sendTranscript({ token, rid, email, subject, user }), LivechatRooms.unsetEmailTranscriptRequestedByRoomId(rid)]); delete room.transcriptRequest; diff --git a/apps/meteor/app/livechat/server/hooks/sendToCRM.ts b/apps/meteor/app/livechat/server/hooks/sendToCRM.ts index 24e1d685a0e6..b3624bd3ecf6 100644 --- a/apps/meteor/app/livechat/server/hooks/sendToCRM.ts +++ b/apps/meteor/app/livechat/server/hooks/sendToCRM.ts @@ -261,13 +261,8 @@ callbacks.add( ); callbacks.add( - 'afterSaveMessage', - async (message, room) => { - // only call webhook if it is a livechat room - if (!isOmnichannelRoom(room) || !room?.v?.token) { - return message; - } - + 'afterOmnichannelSaveMessage', + async (message, { room }) => { // if the message has a token, it was sent from the visitor // if not, it was sent from the agent if (message.token && !settings.get('Livechat_webhook_on_visitor_message')) { diff --git a/apps/meteor/app/livechat/server/index.ts b/apps/meteor/app/livechat/server/index.ts index fc96f2a921a9..9a1f40238df5 100644 --- a/apps/meteor/app/livechat/server/index.ts +++ b/apps/meteor/app/livechat/server/index.ts @@ -16,6 +16,7 @@ import './hooks/saveContactLastChat'; import './hooks/saveLastMessageToInquiry'; import './hooks/afterUserActions'; import './hooks/afterAgentRemoved'; +import './hooks/afterSaveOmnichannelMessage'; import './methods/addAgent'; import './methods/addManager'; import './methods/changeLivechatStatus'; diff --git a/apps/meteor/app/livechat/server/lib/AnalyticsTyped.ts b/apps/meteor/app/livechat/server/lib/AnalyticsTyped.ts index 3b7c6a3051bf..c0be707ba212 100644 --- a/apps/meteor/app/livechat/server/lib/AnalyticsTyped.ts +++ b/apps/meteor/app/livechat/server/lib/AnalyticsTyped.ts @@ -1,7 +1,10 @@ import { OmnichannelAnalytics } from '@rocket.chat/core-services'; import mem from 'mem'; -export const getAgentOverviewDataCached = mem(OmnichannelAnalytics.getAgentOverviewData, { maxAge: 60000, cacheKey: JSON.stringify }); +export const getAgentOverviewDataCached = mem(OmnichannelAnalytics.getAgentOverviewData, { + maxAge: process.env.TEST_MODE === 'true' ? 1 : 60000, + cacheKey: JSON.stringify, +}); // Agent overview data on realtime is cached for 5 seconds // while the data on the overview page is cached for 1 minute export const getAnalyticsOverviewDataCached = mem(OmnichannelAnalytics.getAnalyticsOverviewData, { diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index 2e648b02f5dd..4f4a33ee61b2 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -1,12 +1,25 @@ -import type { ILivechatCustomField, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { LivechatVisitors, Users, LivechatRooms, LivechatCustomField, LivechatInquiry, Rooms, Subscriptions } from '@rocket.chat/models'; +import type { ILivechatContactChannel, ILivechatCustomField, ILivechatVisitor, IOmnichannelRoom, IUser } from '@rocket.chat/core-typings'; +import { + LivechatVisitors, + Users, + LivechatRooms, + LivechatCustomField, + LivechatInquiry, + Rooms, + Subscriptions, + LivechatContacts, +} from '@rocket.chat/models'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { MatchKeysAndValues, OnlyFieldsOfType } from 'mongodb'; import { callbacks } from '../../../../lib/callbacks'; import { trim } from '../../../../lib/utils/stringUtils'; -import { notifyOnRoomChangedById, notifyOnLivechatInquiryChangedByRoom } from '../../../lib/server/lib/notifyListener'; +import { + notifyOnRoomChangedById, + notifyOnSubscriptionChangedByRoomId, + notifyOnLivechatInquiryChangedByRoom, +} from '../../../lib/server/lib/notifyListener'; import { i18n } from '../../../utils/lib/i18n'; type RegisterContactProps = { @@ -22,6 +35,16 @@ type RegisterContactProps = { }; }; +type CreateContactParams = { + name: string; + emails: string[]; + phones: string[]; + unknown: boolean; + customFields?: Record; + contactManager?: string; + channels?: ILivechatContactChannel[]; +}; + export const Contacts = { async registerContact({ token, @@ -138,17 +161,88 @@ export const Contacts = { for await (const room of rooms) { const { _id: rid } = room; - await Promise.all([ + const responses = await Promise.all([ Rooms.setFnameById(rid, name), LivechatInquiry.setNameByRoomId(rid, name), Subscriptions.updateDisplayNameByRoomId(rid, name), ]); - void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name }); - void notifyOnRoomChangedById(rid); + if (responses[0]?.modifiedCount) { + void notifyOnRoomChangedById(rid); + } + + if (responses[1]?.modifiedCount) { + void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name }); + } + + if (responses[2]?.modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } } } return contactId; }, }; + +export async function createContact(params: CreateContactParams): Promise { + const { name, emails, phones, customFields = {}, contactManager, channels, unknown } = params; + + if (contactManager) { + const contactManagerUser = await Users.findOneAgentById>(contactManager, { projection: { roles: 1 } }); + if (!contactManagerUser) { + throw new Error('error-contact-manager-not-found'); + } + } + + const allowedCustomFields = await getAllowedCustomFields(); + validateCustomFields(allowedCustomFields, customFields); + + const { insertedId } = await LivechatContacts.insertOne({ + name, + emails, + phones, + contactManager, + channels, + customFields, + unknown, + }); + + return insertedId; +} + +async function getAllowedCustomFields(): Promise { + return LivechatCustomField.findByScope( + 'visitor', + { + projection: { _id: 1, label: 1, regexp: 1, required: 1 }, + }, + false, + ).toArray(); +} + +export function validateCustomFields(allowedCustomFields: ILivechatCustomField[], customFields: Record) { + for (const cf of allowedCustomFields) { + if (!customFields.hasOwnProperty(cf._id)) { + if (cf.required) { + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); + } + continue; + } + const cfValue: string = trim(customFields[cf._id]); + + if (!cfValue || typeof cfValue !== 'string') { + if (cf.required) { + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); + } + continue; + } + + if (cf.regexp) { + const regex = new RegExp(cf.regexp); + if (!regex.test(cfValue)) { + throw new Error(i18n.t('error-invalid-custom-field-value', { field: cf.label })); + } + } + } +} diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index c0e85a8c7c2b..17f21d8d7b04 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -36,10 +36,12 @@ import { validateEmail as validatorFunc } from '../../../../lib/emailValidator'; import { i18n } from '../../../../server/lib/i18n'; import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { sendNotification } from '../../../lib/server'; -import { sendMessage } from '../../../lib/server/functions/sendMessage'; import { notifyOnLivechatDepartmentAgentChanged, notifyOnLivechatDepartmentAgentChangedByAgentsAndDepartmentId, + notifyOnSubscriptionChangedById, + notifyOnSubscriptionChangedByRoomId, + notifyOnSubscriptionChanged, } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { Livechat as LivechatTyped } from './LivechatTyped'; @@ -141,10 +143,7 @@ export const createLivechatRoom = async < } await callbacks.run('livechat.newRoom', room); - - // TODO: replace with `Message.saveSystemMessage` - - await sendMessage(guest, { t: 'livechat-started', msg: '', groupable: false, token: guest.token }, room); + await Message.saveSystemMessageAndNotifyUser('livechat-started', rid, '', { _id, username }, { groupable: false, token: guest.token }); return result.value as IOmnichannelRoom; }; @@ -289,7 +288,13 @@ export const createLivechatSubscription = async ( ...(department && { department }), } as InsertionModel; - return Subscriptions.insertOne(subscriptionData); + const response = await Subscriptions.insertOne(subscriptionData); + + if (response?.insertedId) { + void notifyOnSubscriptionChangedById(response.insertedId, 'inserted'); + } + + return response; }; export const removeAgentFromSubscription = async (rid: string, { _id, username }: Pick) => { @@ -300,7 +305,11 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username } return; } - await Subscriptions.removeByRoomIdAndUserId(rid, _id); + const deletedSubscription = await Subscriptions.removeByRoomIdAndUserId(rid, _id); + if (deletedSubscription) { + void notifyOnSubscriptionChanged(deletedSubscription, 'removed'); + } + await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name }); setImmediate(() => { @@ -517,12 +526,16 @@ export const updateChatDepartment = async ({ newDepartmentId: string; oldDepartmentId?: string; }) => { - await Promise.all([ + const responses = await Promise.all([ LivechatRooms.changeDepartmentIdByRoomId(rid, newDepartmentId), LivechatInquiry.changeDepartmentIdByRoomId(rid, newDepartmentId), Subscriptions.changeDepartmentByRoomId(rid, newDepartmentId), ]); + if (responses[2].modifiedCount) { + void notifyOnSubscriptionChangedByRoomId(rid); + } + setImmediate(() => { void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { type: LivechatTransferEventType.DEPARTMENT, diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index ccca7a8eb68e..be79d565f6de 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -21,6 +21,7 @@ import type { ILivechatDepartmentAgents, LivechatDepartmentDTO, OmnichannelSourceType, + ILivechatInquiryRecord, } from '@rocket.chat/core-typings'; import { ILivechatAgentStatus, UserStatus, isOmnichannelRoom } from '@rocket.chat/core-typings'; import { Logger, type MainLogger } from '@rocket.chat/logger'; @@ -40,11 +41,12 @@ import { import { serverFetch as fetch } from '@rocket.chat/server-fetch'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import type { Filter, FindCursor } from 'mongodb'; +import type { Filter, FindCursor, ClientSession, MongoError } from 'mongodb'; import UAParser from 'ua-parser-js'; import { callbacks } from '../../../../lib/callbacks'; import { trim } from '../../../../lib/utils/stringUtils'; +import { client } from '../../../../server/database/utils'; import { i18n } from '../../../../server/lib/i18n'; import { addUserRolesAsync } from '../../../../server/lib/roles/addUserRoles'; import { removeUserFromRolesAsync } from '../../../../server/lib/roles/removeUserFromRoles'; @@ -60,8 +62,10 @@ import { notifyOnLivechatInquiryChangedByRoom, notifyOnRoomChangedById, notifyOnLivechatInquiryChangedByToken, - notifyOnLivechatDepartmentAgentChangedByDepartmentId, notifyOnUserChange, + notifyOnLivechatDepartmentAgentChangedByDepartmentId, + notifyOnSubscriptionChangedByRoomId, + notifyOnSubscriptionChanged, } from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; @@ -73,7 +77,6 @@ import { RoutingManager } from './RoutingManager'; import { isDepartmentCreationAvailable } from './isDepartmentCreationAvailable'; import type { CloseRoomParams, CloseRoomParamsByUser, CloseRoomParamsByVisitor } from './localTypes'; import { parseTranscriptRequest } from './parseTranscriptRequest'; -import { sendTranscript as sendTranscriptFunc } from './sendTranscript'; type RegisterGuestType = Partial> & { id?: string; @@ -140,6 +143,13 @@ type ICRMData = { crmData?: IOmnichannelRoom['crmData']; }; +type ChatCloser = { _id: string; username: string | undefined }; + +const isRoomClosedByUserParams = (params: CloseRoomParams): params is CloseRoomParamsByUser => + (params as CloseRoomParamsByUser).user !== undefined; +const isRoomClosedByVisitorParams = (params: CloseRoomParams): params is CloseRoomParamsByVisitor => + (params as CloseRoomParamsByVisitor).visitor !== undefined; + const dnsResolveMx = util.promisify(dns.resolveMx); class LivechatClass { @@ -152,22 +162,6 @@ class LivechatClass { this.webhookLogger = this.logger.section('Webhook'); } - findGuest(token: string) { - return LivechatVisitors.getVisitorByToken(token, { - projection: { - name: 1, - username: 1, - token: 1, - visitorEmails: 1, - department: 1, - }, - }); - } - - enabled() { - return Boolean(settings.get('Livechat_enabled')); - } - async online(department?: string, skipNoAgentSetting = false, skipFallbackCheck = false): Promise { Livechat.logger.debug(`Checking online agents ${department ? `for department ${department}` : ''}`); if (!skipNoAgentSetting && settings.get('Livechat_accept_chats_with_no_agents')) { @@ -192,10 +186,6 @@ class LivechatClass { return agentsOnline; } - getNextAgent(department?: string): Promise { - return RoutingManager.getNextAgent(department); - } - async getOnlineAgents(department?: string, agent?: SelectedAgent | null): Promise | undefined> { if (agent?.agentId) { return Users.findOnlineAgents(agent.agentId); @@ -217,14 +207,115 @@ class LivechatClass { return Users.findOnlineAgents(); } - async closeRoom(params: CloseRoomParams): Promise { + async closeRoom(params: CloseRoomParams, attempts = 2): Promise { + let newRoom: IOmnichannelRoom; + let chatCloser: ChatCloser; + let removedInquiryObj: ILivechatInquiryRecord | null; + + const session = client.startSession(); + try { + session.startTransaction(); + const { room, closedBy, removedInquiry } = await this.doCloseRoom(params, session); + await session.commitTransaction(); + + newRoom = room; + chatCloser = closedBy; + removedInquiryObj = removedInquiry; + } catch (e) { + this.logger.error({ err: e, msg: 'Failed to close room', afterAttempts: attempts }); + await session.abortTransaction(); + // Dont propagate transaction errors + if ( + (e as unknown as MongoError)?.errorLabels?.includes('UnknownTransactionCommitResult') || + (e as unknown as MongoError)?.errorLabels?.includes('TransientTransactionError') + ) { + if (attempts > 0) { + this.logger.debug(`Retrying close room because of transient error. Attempts left: ${attempts}`); + return this.closeRoom(params, attempts - 1); + } + + throw new Error('error-room-cannot-be-closed-try-again'); + } + throw e; + } finally { + await session.endSession(); + } + + // Note: when reaching this point, the room has been closed + // Transaction is commited and so these messages can be sent here. + return this.afterRoomClosed(newRoom, chatCloser, removedInquiryObj, params); + } + + async afterRoomClosed( + newRoom: IOmnichannelRoom, + chatCloser: ChatCloser, + inquiry: ILivechatInquiryRecord | null, + params: CloseRoomParams, + ): Promise { + if (!chatCloser) { + // this should never happen + return; + } + // Note: we are okay with these messages being sent outside of the transaction. The process of sending a message + // is huge and involves multiple db calls. Making it transactionable this way would be really hard. + // And passing just _some_ actions to the transaction creates some deadlocks since messages are updated in the afterSaveMessages callbacks. + const transcriptRequested = + !!params.room.transcriptRequest || (!settings.get('Livechat_enable_transcript') && settings.get('Livechat_transcript_send_always')); + this.logger.debug(`Sending closing message to room ${newRoom._id}`); + await Message.saveSystemMessageAndNotifyUser('livechat-close', newRoom._id, params.comment ?? '', chatCloser, { + groupable: false, + transcriptRequested, + ...(isRoomClosedByVisitorParams(params) && { token: params.visitor.token }), + }); + + if (settings.get('Livechat_enable_transcript') && !settings.get('Livechat_transcript_send_always')) { + await Message.saveSystemMessage('command', newRoom._id, 'promptTranscript', chatCloser); + } + + this.logger.debug(`Running callbacks for room ${newRoom._id}`); + + process.nextTick(() => { + /** + * @deprecated the `AppEvents.ILivechatRoomClosedHandler` event will be removed + * in the next major version of the Apps-Engine + */ + void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); + void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); + }); + + const visitor = isRoomClosedByVisitorParams(params) ? params.visitor : undefined; + const opts = await parseTranscriptRequest(params.room, params.options, visitor); + if (process.env.TEST_MODE) { + await callbacks.run('livechat.closeRoom', { + room: newRoom, + options: opts, + }); + } else { + callbacks.runAsync('livechat.closeRoom', { + room: newRoom, + options: opts, + }); + } + + void notifyOnRoomChangedById(newRoom._id); + if (inquiry) { + void notifyOnLivechatInquiryChanged(inquiry, 'removed'); + } + + this.logger.debug(`Room ${newRoom._id} was closed`); + } + + async doCloseRoom( + params: CloseRoomParams, + session: ClientSession, + ): Promise<{ room: IOmnichannelRoom; closedBy: ChatCloser; removedInquiry: ILivechatInquiryRecord | null }> { const { comment } = params; const { room } = params; this.logger.debug(`Attempting to close room ${room._id}`); if (!room || !isOmnichannelRoom(room) || !room.open) { this.logger.debug(`Room ${room._id} is not open`); - return; + throw new Error('error-room-closed'); } const commentRequired = settings.get('Livechat_request_comment_when_closing_conversation'); @@ -236,7 +327,7 @@ class LivechatClass { this.logger.debug(`Resolved chat tags for room ${room._id}`); const now = new Date(); - const { _id: rid, servedBy, transcriptRequest } = room; + const { _id: rid, servedBy } = room; const serviceTimeDuration = servedBy && (now.getTime() - new Date(servedBy.ts).getTime()) / 1000; const closeData: IOmnichannelRoomClosingInfo = { @@ -247,12 +338,6 @@ class LivechatClass { }; this.logger.debug(`Room ${room._id} was closed at ${closeData.closedAt} (duration ${closeData.chatDuration})`); - const isRoomClosedByUserParams = (params: CloseRoomParams): params is CloseRoomParamsByUser => - (params as CloseRoomParamsByUser).user !== undefined; - const isRoomClosedByVisitorParams = (params: CloseRoomParams): params is CloseRoomParamsByVisitor => - (params as CloseRoomParamsByVisitor).visitor !== undefined; - - let chatCloser: any; if (isRoomClosedByUserParams(params)) { const { user } = params; this.logger.debug(`Closing by user ${user?._id}`); @@ -261,7 +346,6 @@ class LivechatClass { _id: user?._id || '', username: user?.username, }; - chatCloser = user; } else if (isRoomClosedByVisitorParams(params)) { const { visitor } = params; this.logger.debug(`Closing by visitor ${params.visitor._id}`); @@ -270,87 +354,44 @@ class LivechatClass { _id: visitor._id, username: visitor.username, }; - chatCloser = visitor; } else { throw new Error('Error: Please provide details of the user or visitor who closed the room'); } this.logger.debug(`Updating DB for room ${room._id} with close data`); - const inquiry = await LivechatInquiry.findOneByRoomId(rid); - - const removedInquiry = await LivechatInquiry.removeByRoomId(rid); + const inquiry = await LivechatInquiry.findOneByRoomId(rid, { session }); + const removedInquiry = await LivechatInquiry.removeByRoomId(rid, { session }); if (removedInquiry && removedInquiry.deletedCount !== 1) { throw new Error('Error removing inquiry'); } - if (inquiry) { - void notifyOnLivechatInquiryChanged(inquiry, 'removed'); - } - const updatedRoom = await LivechatRooms.closeRoomById(rid, closeData); + const updatedRoom = await LivechatRooms.closeRoomById(rid, closeData, { session }); if (!updatedRoom || updatedRoom.modifiedCount !== 1) { throw new Error('Error closing room'); } - await Subscriptions.removeByRoomId(rid); + const subs = await Subscriptions.countByRoomId(rid, { session }); + const removedSubs = await Subscriptions.removeByRoomId(rid, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + session, + }); - this.logger.debug(`DB updated for room ${room._id}`); + if (removedSubs.deletedCount !== subs) { + throw new Error('Error removing subscriptions'); + } - const transcriptRequested = - !!transcriptRequest || (!settings.get('Livechat_enable_transcript') && settings.get('Livechat_transcript_send_always')); + this.logger.debug(`DB updated for room ${room._id}`); // Retrieve the closed room - const newRoom = await LivechatRooms.findOneById(rid); - + const newRoom = await LivechatRooms.findOneById(rid, { session }); if (!newRoom) { throw new Error('Error: Room not found'); } - this.logger.debug(`Sending closing message to room ${room._id}`); - await sendMessage( - chatCloser, - { - t: 'livechat-close', - msg: comment, - groupable: false, - transcriptRequested, - ...(isRoomClosedByVisitorParams(params) && { token: chatCloser.token }), - }, - newRoom, - ); - - if (settings.get('Livechat_enable_transcript') && !settings.get('Livechat_transcript_send_always')) { - await Message.saveSystemMessage('command', rid, 'promptTranscript', closeData.closedBy); - } - - this.logger.debug(`Running callbacks for room ${newRoom._id}`); - - process.nextTick(() => { - /** - * @deprecated the `AppEvents.ILivechatRoomClosedHandler` event will be removed - * in the next major version of the Apps-Engine - */ - void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); - void Apps.self?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); - }); - - const visitor = isRoomClosedByVisitorParams(params) ? params.visitor : undefined; - const opts = await parseTranscriptRequest(params.room, options, visitor); - if (process.env.TEST_MODE) { - await callbacks.run('livechat.closeRoom', { - room: newRoom, - options: opts, - }); - } else { - callbacks.runAsync('livechat.closeRoom', { - room: newRoom, - options: opts, - }); - } - - void notifyOnRoomChangedById(newRoom._id); - - this.logger.debug(`Room ${newRoom._id} was closed`); + return { room: newRoom, closedBy: closeData.closedBy, removedInquiry: inquiry }; } async getRequiredDepartment(onlineRequired = true) { @@ -389,7 +430,7 @@ class LivechatClass { agent?: SelectedAgent; extraData?: Record; }) { - if (!this.enabled()) { + if (!settings.get('Livechat_enabled')) { throw new Meteor.Error('error-omnichannel-is-disabled'); } @@ -440,7 +481,7 @@ class LivechatClass { agent?: SelectedAgent, extraData?: E, ) { - if (!this.enabled()) { + if (!settings.get('Livechat_enabled')) { throw new Meteor.Error('error-omnichannel-is-disabled'); } Livechat.logger.debug(`Attempting to find or create a room for visitor ${guest._id}`); @@ -526,12 +567,16 @@ class LivechatClass { const result = await Promise.allSettled([ Messages.removeByRoomId(rid), ReadReceipts.removeByRoomId(rid), - Subscriptions.removeByRoomId(rid), + Subscriptions.removeByRoomId(rid, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }), LivechatInquiry.removeByRoomId(rid), LivechatRooms.removeById(rid), ]); - if (inquiry) { + if (result[3]?.status === 'fulfilled' && result[3].value?.deletedCount && inquiry) { void notifyOnLivechatInquiryChanged(inquiry, 'removed'); } @@ -810,15 +855,6 @@ class LivechatClass { } } - async updateLastChat(contactId: string, lastChat: Required) { - const updateUser = { - $set: { - lastChat, - }, - }; - await LivechatVisitors.updateById(contactId, updateUser); - } - notifyRoomVisitorChange(roomId: string, visitor: ILivechatVisitor) { void api.broadcast('omnichannel.room', roomId, { type: 'visitorData', @@ -878,7 +914,7 @@ class LivechatClass { return Messages.findVisibleByRoomIdNotContainingTypes(rid, ignoredMessageTypes, { sort: { ts: 1 }, - }).toArray(); + }); } async archiveDepartment(_id: string) { @@ -1154,13 +1190,18 @@ class LivechatClass { const cursor = LivechatRooms.findByVisitorToken(token); for await (const room of cursor) { await Promise.all([ + Subscriptions.removeByRoomId(room._id, { + async onTrash(doc) { + void notifyOnSubscriptionChanged(doc, 'removed'); + }, + }), FileUpload.removeFilesByRoomId(room._id), Messages.removeByRoomId(room._id), ReadReceipts.removeByRoomId(room._id), ]); } - await Promise.all([Subscriptions.removeByVisitorToken(token), LivechatRooms.removeByVisitorToken(token)]); + await LivechatRooms.removeByVisitorToken(token); const livechatInquiries = await LivechatInquiry.findIdsByVisitorToken(token).toArray(); await LivechatInquiry.removeByIds(livechatInquiries.map(({ _id }) => _id)); @@ -1254,31 +1295,20 @@ class LivechatClass { const scopeData = scope || (nextDepartment ? 'department' : 'agent'); this.logger.info(`Storing new chat transfer of ${room._id} [Transfered by: ${_id} to ${scopeData}]`); - await sendMessage( - transferredBy, - { - t: 'livechat_transfer_history', - rid: room._id, + const transferMessage = { + ...(transferData.transferredBy.userType === 'visitor' && { token: room.v.token }), + transferData: { + transferredBy, ts: new Date(), - msg: '', - u: { - _id, - username, - }, - groupable: false, - ...(transferData.transferredBy.userType === 'visitor' && { token: room.v.token }), - transferData: { - transferredBy, - ts: new Date(), - scope: scopeData, - comment, - ...(previousDepartment && { previousDepartment }), - ...(nextDepartment && { nextDepartment }), - ...(transferredTo && { transferredTo }), - }, + scope: scopeData, + comment, + ...(previousDepartment && { previousDepartment }), + ...(nextDepartment && { nextDepartment }), + ...(transferredTo && { transferredTo }), }, - room, - ); + }; + + await Message.saveSystemMessageAndNotifyUser('livechat_transfer_history', room._id, '', { _id, username }, transferMessage); } async saveGuest(guestData: Pick & { email?: string; phone?: string }, userId: string) { @@ -1723,13 +1753,19 @@ class LivechatClass { const { _id: rid } = roomData; const { name } = guestData; - await Promise.all([ + const responses = await Promise.all([ Rooms.setFnameById(rid, name), LivechatInquiry.setNameByRoomId(rid, name), Subscriptions.updateDisplayNameByRoomId(rid, name), ]); - void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name }); + if (responses[1]?.modifiedCount) { + void notifyOnLivechatInquiryChangedByRoom(rid, 'updated', { name }); + } + + if (responses[2]?.modifiedCount) { + await notifyOnSubscriptionChangedByRoomId(rid); + } } void notifyOnRoomChangedById(roomData._id); @@ -1841,22 +1877,6 @@ class LivechatClass { return departmentDB; } - - async sendTranscript({ - token, - rid, - email, - subject, - user, - }: { - token: string; - rid: string; - email: string; - subject?: string; - user?: Pick | null; - }): Promise { - return sendTranscriptFunc({ token, rid, email, subject, user }); - } } export const Livechat = new LivechatClass(); diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 5ae03e0ee03b..e1ea79d84163 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -15,6 +15,7 @@ import { Random } from '@rocket.chat/random'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { dispatchInquiryPosition } from '../../../../ee/app/livechat-enterprise/server/lib/Helper'; import { callbacks } from '../../../../lib/callbacks'; import { sendNotification } from '../../../lib/server'; import { @@ -27,6 +28,7 @@ import { i18n } from '../../../utils/lib/i18n'; import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue } from './Helper'; import { Livechat } from './LivechatTyped'; import { RoutingManager } from './RoutingManager'; +import { getInquirySortMechanismSetting } from './settings'; const logger = new Logger('QueueManager'); @@ -221,7 +223,7 @@ export class QueueManager { const name = (roomInfo?.fname as string) || guest.name || guest.username; - const room = await createLivechatRoom(rid, name, guest, roomInfo, { + const room = await createLivechatRoom(rid, name, { ...guest, ...(department && { department }) }, roomInfo, { ...extraData, ...(Boolean(customFields) && { customFields }), }); @@ -259,6 +261,18 @@ export class QueueManager { throw new Error('room-not-found'); } + if (!newRoom.servedBy && settings.get('Omnichannel_calculate_dispatch_service_queue_statistics')) { + const [inq] = await LivechatInquiry.getCurrentSortedQueueAsync({ + inquiryId: inquiry._id, + department, + queueSortBy: getInquirySortMechanismSetting(), + }); + + if (inq) { + void dispatchInquiryPosition(inq); + } + } + return newRoom; } diff --git a/apps/meteor/app/livechat/server/methods/addAgent.ts b/apps/meteor/app/livechat/server/methods/addAgent.ts index 6160db590ece..0551e985e18e 100644 --- a/apps/meteor/app/livechat/server/methods/addAgent.ts +++ b/apps/meteor/app/livechat/server/methods/addAgent.ts @@ -1,12 +1,12 @@ import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:addAgent'(username: string): Promise; diff --git a/apps/meteor/app/livechat/server/methods/addManager.ts b/apps/meteor/app/livechat/server/methods/addManager.ts index 347c4f07d63f..a954d8111773 100644 --- a/apps/meteor/app/livechat/server/methods/addManager.ts +++ b/apps/meteor/app/livechat/server/methods/addManager.ts @@ -1,12 +1,12 @@ import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:addManager'(username: string): Promise; diff --git a/apps/meteor/app/livechat/server/methods/changeLivechatStatus.ts b/apps/meteor/app/livechat/server/methods/changeLivechatStatus.ts index 8ccfcd5ec468..f45588cad6d3 100644 --- a/apps/meteor/app/livechat/server/methods/changeLivechatStatus.ts +++ b/apps/meteor/app/livechat/server/methods/changeLivechatStatus.ts @@ -1,13 +1,13 @@ import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat as LivechatTS } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:changeLivechatStatus'(params?: { status?: ILivechatAgentStatus; agentId?: string }): unknown; diff --git a/apps/meteor/app/livechat/server/methods/closeRoom.ts b/apps/meteor/app/livechat/server/methods/closeRoom.ts index 5fdf9e7d504f..19c8b2709389 100644 --- a/apps/meteor/app/livechat/server/methods/closeRoom.ts +++ b/apps/meteor/app/livechat/server/methods/closeRoom.ts @@ -1,6 +1,6 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users, LivechatRooms, Subscriptions as SubscriptionRaw } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; @@ -35,7 +35,7 @@ type LivechatCloseRoomOptions = Omit }; }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:closeRoom'(roomId: string, comment?: string, options?: CloseRoomOptions): void; diff --git a/apps/meteor/app/livechat/server/methods/discardTranscript.ts b/apps/meteor/app/livechat/server/methods/discardTranscript.ts index d46c8ffea35f..3265aa9c54d2 100644 --- a/apps/meteor/app/livechat/server/methods/discardTranscript.ts +++ b/apps/meteor/app/livechat/server/methods/discardTranscript.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:discardTranscript'(rid: string): boolean; diff --git a/apps/meteor/app/livechat/server/methods/getAgentData.ts b/apps/meteor/app/livechat/server/methods/getAgentData.ts index d32d24d7f7c1..5fe58560806e 100644 --- a/apps/meteor/app/livechat/server/methods/getAgentData.ts +++ b/apps/meteor/app/livechat/server/methods/getAgentData.ts @@ -1,13 +1,13 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, LivechatRooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getAgentData'(params: { diff --git a/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts b/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts index b5ea8dafe92c..819bac03c5b4 100644 --- a/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts +++ b/apps/meteor/app/livechat/server/methods/getAgentOverviewData.ts @@ -1,13 +1,13 @@ import type { ConversationData } from '@rocket.chat/core-services'; import { OmnichannelAnalytics } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getAgentOverviewData'(options: { chartOptions: { name: string } }): ConversationData | void; diff --git a/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts b/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts index 85c8e033b8f5..2ff682c1c39c 100644 --- a/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts +++ b/apps/meteor/app/livechat/server/methods/getAnalyticsChartData.ts @@ -1,12 +1,12 @@ import type { ChartDataResult } from '@rocket.chat/core-services'; import { OmnichannelAnalytics } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getAnalyticsChartData'(options: { chartOptions: { name: string } }): ChartDataResult | void; diff --git a/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts b/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts index c9bb3d163457..9f56cfa57069 100644 --- a/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts +++ b/apps/meteor/app/livechat/server/methods/getAnalyticsOverviewData.ts @@ -1,14 +1,14 @@ import type { AnalyticsOverviewDataResult } from '@rocket.chat/core-services'; import { OmnichannelAnalytics } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getAnalyticsOverviewData'(options: { analyticsOptions: { name: string } }): AnalyticsOverviewDataResult[] | void; diff --git a/apps/meteor/app/livechat/server/methods/getCustomFields.ts b/apps/meteor/app/livechat/server/methods/getCustomFields.ts index ec0ac35d6c21..36dca08f0859 100644 --- a/apps/meteor/app/livechat/server/methods/getCustomFields.ts +++ b/apps/meteor/app/livechat/server/methods/getCustomFields.ts @@ -1,11 +1,11 @@ import type { ILivechatCustomField } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatCustomField } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getCustomFields'(): ILivechatCustomField[]; diff --git a/apps/meteor/app/livechat/server/methods/getDepartmentForwardRestrictions.ts b/apps/meteor/app/livechat/server/methods/getDepartmentForwardRestrictions.ts index b26d9706a8cd..2b891514d03e 100644 --- a/apps/meteor/app/livechat/server/methods/getDepartmentForwardRestrictions.ts +++ b/apps/meteor/app/livechat/server/methods/getDepartmentForwardRestrictions.ts @@ -1,10 +1,10 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getDepartmentForwardRestrictions'(departmentId: string): unknown; diff --git a/apps/meteor/app/livechat/server/methods/getFirstRoomMessage.ts b/apps/meteor/app/livechat/server/methods/getFirstRoomMessage.ts index 197898e8d2b2..d8accf7f84f5 100644 --- a/apps/meteor/app/livechat/server/methods/getFirstRoomMessage.ts +++ b/apps/meteor/app/livechat/server/methods/getFirstRoomMessage.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms, Messages } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getFirstRoomMessage'(params: { rid: string }): unknown; diff --git a/apps/meteor/app/livechat/server/methods/getNextAgent.ts b/apps/meteor/app/livechat/server/methods/getNextAgent.ts index 53b8441cad2e..f603aeef97f3 100644 --- a/apps/meteor/app/livechat/server/methods/getNextAgent.ts +++ b/apps/meteor/app/livechat/server/methods/getNextAgent.ts @@ -1,6 +1,6 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -8,8 +8,9 @@ import { callbacks } from '../../../../lib/callbacks'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { settings } from '../../../settings/server'; import { Livechat } from '../lib/LivechatTyped'; +import { RoutingManager } from '../lib/RoutingManager'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getNextAgent'(params: { @@ -38,7 +39,7 @@ Meteor.methods({ } } - const agent = await Livechat.getNextAgent(department); + const agent = await RoutingManager.getNextAgent(department); if (!agent) { return; } diff --git a/apps/meteor/app/livechat/server/methods/getRoutingConfig.ts b/apps/meteor/app/livechat/server/methods/getRoutingConfig.ts index 364853a74511..02b2840f41cc 100644 --- a/apps/meteor/app/livechat/server/methods/getRoutingConfig.ts +++ b/apps/meteor/app/livechat/server/methods/getRoutingConfig.ts @@ -1,10 +1,10 @@ import type { OmichannelRoutingConfig } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { RoutingManager } from '../lib/RoutingManager'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getRoutingConfig'(): OmichannelRoutingConfig | undefined; diff --git a/apps/meteor/app/livechat/server/methods/getTagsList.ts b/apps/meteor/app/livechat/server/methods/getTagsList.ts index e9e467d243c8..b3efe5d026a3 100644 --- a/apps/meteor/app/livechat/server/methods/getTagsList.ts +++ b/apps/meteor/app/livechat/server/methods/getTagsList.ts @@ -1,11 +1,11 @@ import type { ILivechatTag } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:getTagsList'(): ILivechatTag[]; diff --git a/apps/meteor/app/livechat/server/methods/loadHistory.ts b/apps/meteor/app/livechat/server/methods/loadHistory.ts index 373ede1a3610..1004f9dd604d 100644 --- a/apps/meteor/app/livechat/server/methods/loadHistory.ts +++ b/apps/meteor/app/livechat/server/methods/loadHistory.ts @@ -1,13 +1,13 @@ import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { loadMessageHistory } from '../../../lib/server/functions/loadMessageHistory'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:loadHistory'(params: { token: string; rid: string; end?: Date; limit?: number; ls: Date }): diff --git a/apps/meteor/app/livechat/server/methods/loginByToken.ts b/apps/meteor/app/livechat/server/methods/loginByToken.ts index cae23e6d16f7..3b82413e038a 100644 --- a/apps/meteor/app/livechat/server/methods/loginByToken.ts +++ b/apps/meteor/app/livechat/server/methods/loginByToken.ts @@ -1,10 +1,10 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:loginByToken'(token: string): { _id: string } | undefined; diff --git a/apps/meteor/app/livechat/server/methods/pageVisited.ts b/apps/meteor/app/livechat/server/methods/pageVisited.ts index c226a91de430..7c0864f27b74 100644 --- a/apps/meteor/app/livechat/server/methods/pageVisited.ts +++ b/apps/meteor/app/livechat/server/methods/pageVisited.ts @@ -1,10 +1,10 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:pageVisited'(token: string, room: string, pageInfo: { title: string; location: { href: string }; change: string }): void; diff --git a/apps/meteor/app/livechat/server/methods/registerGuest.ts b/apps/meteor/app/livechat/server/methods/registerGuest.ts index 4a531d0c89e5..c6a119d91591 100644 --- a/apps/meteor/app/livechat/server/methods/registerGuest.ts +++ b/apps/meteor/app/livechat/server/methods/registerGuest.ts @@ -1,13 +1,13 @@ import type { ILivechatVisitor, IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, Messages, LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat as LivechatTyped } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:registerGuest'({ diff --git a/apps/meteor/app/livechat/server/methods/removeAgent.ts b/apps/meteor/app/livechat/server/methods/removeAgent.ts index 643ae696ada0..ebb57383784f 100644 --- a/apps/meteor/app/livechat/server/methods/removeAgent.ts +++ b/apps/meteor/app/livechat/server/methods/removeAgent.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeAgent'(username: string): boolean; diff --git a/apps/meteor/app/livechat/server/methods/removeAllClosedRooms.ts b/apps/meteor/app/livechat/server/methods/removeAllClosedRooms.ts index 9be699ae05ed..ba3939bb8573 100644 --- a/apps/meteor/app/livechat/server/methods/removeAllClosedRooms.ts +++ b/apps/meteor/app/livechat/server/methods/removeAllClosedRooms.ts @@ -1,14 +1,14 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Logger } from '@rocket.chat/logger'; import { LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeAllClosedRooms'(departmentIds?: string[]): Promise; diff --git a/apps/meteor/app/livechat/server/methods/removeCustomField.ts b/apps/meteor/app/livechat/server/methods/removeCustomField.ts index 1d3e7c817738..ad02df582337 100644 --- a/apps/meteor/app/livechat/server/methods/removeCustomField.ts +++ b/apps/meteor/app/livechat/server/methods/removeCustomField.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatCustomField } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { DeleteResult } from 'mongodb'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeCustomField'(_id: string): DeleteResult; diff --git a/apps/meteor/app/livechat/server/methods/removeDepartment.ts b/apps/meteor/app/livechat/server/methods/removeDepartment.ts index 3c46f3a424d6..6e4b11c836f6 100644 --- a/apps/meteor/app/livechat/server/methods/removeDepartment.ts +++ b/apps/meteor/app/livechat/server/methods/removeDepartment.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { DeleteResult } from 'mongodb'; @@ -7,7 +7,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { DepartmentHelper } from '../lib/Departments'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeDepartment'(_id: string): DeleteResult; diff --git a/apps/meteor/app/livechat/server/methods/removeManager.ts b/apps/meteor/app/livechat/server/methods/removeManager.ts index 4c003ff922bc..85a5f3076c8c 100644 --- a/apps/meteor/app/livechat/server/methods/removeManager.ts +++ b/apps/meteor/app/livechat/server/methods/removeManager.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeManager'(username: string): boolean; diff --git a/apps/meteor/app/livechat/server/methods/removeRoom.ts b/apps/meteor/app/livechat/server/methods/removeRoom.ts index db3fcf2c849f..751d51d4f019 100644 --- a/apps/meteor/app/livechat/server/methods/removeRoom.ts +++ b/apps/meteor/app/livechat/server/methods/removeRoom.ts @@ -1,12 +1,12 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeRoom'(rid: IRoom['_id']): void; diff --git a/apps/meteor/app/livechat/server/methods/removeTrigger.ts b/apps/meteor/app/livechat/server/methods/removeTrigger.ts index 69f3a4a2d80c..41d79d725176 100644 --- a/apps/meteor/app/livechat/server/methods/removeTrigger.ts +++ b/apps/meteor/app/livechat/server/methods/removeTrigger.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatTrigger } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:removeTrigger'(triggerId: string): boolean; diff --git a/apps/meteor/app/livechat/server/methods/requestTranscript.ts b/apps/meteor/app/livechat/server/methods/requestTranscript.ts index 5abf6a3b72e3..80cb3a9625fe 100644 --- a/apps/meteor/app/livechat/server/methods/requestTranscript.ts +++ b/apps/meteor/app/livechat/server/methods/requestTranscript.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -7,7 +7,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:requestTranscript'(rid: string, email: string, subject: string): Promise; diff --git a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts index 38b58b9d2d42..bf76519a5afb 100644 --- a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts @@ -1,13 +1,13 @@ import { Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatDepartment, IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:returnAsInquiry'(rid: IRoom['_id'], departmentID?: ILivechatDepartment['_id']): boolean; diff --git a/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts b/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts index 90d9815d4cf1..fe542b67156e 100644 --- a/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts +++ b/apps/meteor/app/livechat/server/methods/saveAgentInfo.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { hasRoleAsync } from '../../../authorization/server/functions/hasRole'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveAgentInfo'(_id: string, agentData: Record, agentDepartments: string[]): unknown; diff --git a/apps/meteor/app/livechat/server/methods/saveAppearance.ts b/apps/meteor/app/livechat/server/methods/saveAppearance.ts index cac5d34264d2..619dae147708 100644 --- a/apps/meteor/app/livechat/server/methods/saveAppearance.ts +++ b/apps/meteor/app/livechat/server/methods/saveAppearance.ts @@ -1,12 +1,12 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveAppearance'(settings: { _id: string; value: any }[]): Promise; diff --git a/apps/meteor/app/livechat/server/methods/saveBusinessHour.ts b/apps/meteor/app/livechat/server/methods/saveBusinessHour.ts index 2e3e64365649..9bf32697fae9 100644 --- a/apps/meteor/app/livechat/server/methods/saveBusinessHour.ts +++ b/apps/meteor/app/livechat/server/methods/saveBusinessHour.ts @@ -1,10 +1,10 @@ import type { ILivechatBusinessHour } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { businessHourManager } from '../business-hour'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveBusinessHour'(businessHourData: ILivechatBusinessHour): void; diff --git a/apps/meteor/app/livechat/server/methods/saveCustomField.ts b/apps/meteor/app/livechat/server/methods/saveCustomField.ts index 8a0435560dd5..c83148ba8d29 100644 --- a/apps/meteor/app/livechat/server/methods/saveCustomField.ts +++ b/apps/meteor/app/livechat/server/methods/saveCustomField.ts @@ -1,12 +1,12 @@ import type { ILivechatCustomField } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatCustomField } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveCustomField'( diff --git a/apps/meteor/app/livechat/server/methods/saveDepartment.ts b/apps/meteor/app/livechat/server/methods/saveDepartment.ts index 971c2189f9a7..b4833523ab3f 100644 --- a/apps/meteor/app/livechat/server/methods/saveDepartment.ts +++ b/apps/meteor/app/livechat/server/methods/saveDepartment.ts @@ -1,11 +1,11 @@ import type { ILivechatDepartment } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveDepartment': ( diff --git a/apps/meteor/app/livechat/server/methods/saveDepartmentAgents.ts b/apps/meteor/app/livechat/server/methods/saveDepartmentAgents.ts index 73a471869926..42ee521713c0 100644 --- a/apps/meteor/app/livechat/server/methods/saveDepartmentAgents.ts +++ b/apps/meteor/app/livechat/server/methods/saveDepartmentAgents.ts @@ -1,12 +1,12 @@ import type { ILivechatDepartmentAgents } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveDepartmentAgents'( diff --git a/apps/meteor/app/livechat/server/methods/saveInfo.ts b/apps/meteor/app/livechat/server/methods/saveInfo.ts index a057706fdc23..bb22c127effa 100644 --- a/apps/meteor/app/livechat/server/methods/saveInfo.ts +++ b/apps/meteor/app/livechat/server/methods/saveInfo.ts @@ -1,6 +1,6 @@ import { isOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -9,7 +9,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat as LivechatTyped } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveInfo'( diff --git a/apps/meteor/app/livechat/server/methods/saveIntegration.ts b/apps/meteor/app/livechat/server/methods/saveIntegration.ts index de7461d08e10..0a4d82cfbcc1 100644 --- a/apps/meteor/app/livechat/server/methods/saveIntegration.ts +++ b/apps/meteor/app/livechat/server/methods/saveIntegration.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Settings } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { trim } from '../../../../lib/utils/stringUtils'; @@ -7,7 +7,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveIntegration'(values: Record): void; diff --git a/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts b/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts index 48dea8175ea8..36fc3f775b9f 100644 --- a/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts +++ b/apps/meteor/app/livechat/server/methods/saveSurveyFeedback.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms, LivechatVisitors } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult } from 'mongodb'; @@ -7,7 +7,7 @@ import _ from 'underscore'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveSurveyFeedback'( diff --git a/apps/meteor/app/livechat/server/methods/saveTrigger.ts b/apps/meteor/app/livechat/server/methods/saveTrigger.ts index 37f78d081203..850dead9a589 100644 --- a/apps/meteor/app/livechat/server/methods/saveTrigger.ts +++ b/apps/meteor/app/livechat/server/methods/saveTrigger.ts @@ -1,13 +1,13 @@ import type { ILivechatTrigger } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatTrigger } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:saveTrigger'(trigger: ILivechatTrigger): boolean; diff --git a/apps/meteor/app/livechat/server/methods/searchAgent.ts b/apps/meteor/app/livechat/server/methods/searchAgent.ts index 2a69679f2a07..932eb51e89d6 100644 --- a/apps/meteor/app/livechat/server/methods/searchAgent.ts +++ b/apps/meteor/app/livechat/server/methods/searchAgent.ts @@ -1,12 +1,12 @@ import type { IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:searchAgent'(username: string): { _id: string; username?: string } | undefined; diff --git a/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.ts b/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.ts index 15577abd76e3..0268207b2f6c 100644 --- a/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.ts +++ b/apps/meteor/app/livechat/server/methods/sendFileLivechatMessage.ts @@ -5,9 +5,9 @@ import type { VideoAttachmentProps, IUpload, } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, LivechatRooms } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -21,7 +21,7 @@ interface ISendFileLivechatMessage { msgData?: { avatar?: string; emoji?: string; alias?: string; groupable?: boolean; msg?: string }; } -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { sendFileLivechatMessage( diff --git a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts index ad16dea23e4f..6fac80397906 100644 --- a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts +++ b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts @@ -1,6 +1,6 @@ import { OmnichannelSourceType } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -18,7 +18,7 @@ interface ISendMessageLivechat { agent?: ILivechatMessageAgent; } -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { sendMessageLivechat(message: ILivechatMessage, agent: ILivechatMessageAgent): boolean; diff --git a/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts b/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts index c3b5537f31be..b620aa434100 100644 --- a/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts +++ b/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts @@ -1,4 +1,4 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; @@ -6,7 +6,7 @@ import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:sendOfflineMessage'(data: { name: string; email: string; message: string }): Promise; diff --git a/apps/meteor/app/livechat/server/methods/sendTranscript.ts b/apps/meteor/app/livechat/server/methods/sendTranscript.ts index 366a73c8bb0a..00287fa89327 100644 --- a/apps/meteor/app/livechat/server/methods/sendTranscript.ts +++ b/apps/meteor/app/livechat/server/methods/sendTranscript.ts @@ -1,14 +1,14 @@ import { Omnichannel } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatRooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { RateLimiter } from '../../../lib/server'; -import { Livechat } from '../lib/LivechatTyped'; +import { sendTranscript } from '../lib/sendTranscript'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:sendTranscript'(token: string, rid: string, email: string, subject: string): boolean; @@ -39,7 +39,7 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:sendTranscript' }); } - return Livechat.sendTranscript({ token, rid, email, subject, user }); + return sendTranscript({ token, rid, email, subject, user }); }, }); diff --git a/apps/meteor/app/livechat/server/methods/setCustomField.ts b/apps/meteor/app/livechat/server/methods/setCustomField.ts index fb07c2b29a4e..6d3a2384cdd7 100644 --- a/apps/meteor/app/livechat/server/methods/setCustomField.ts +++ b/apps/meteor/app/livechat/server/methods/setCustomField.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, LivechatCustomField, LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import type { UpdateResult, Document } from 'mongodb'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:setCustomField'(token: string, key: string, value: string, overwrite?: boolean): Promise; diff --git a/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts b/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts index a14933ed8d47..385f2d989015 100644 --- a/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts +++ b/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, Messages, LivechatRooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -7,7 +7,7 @@ import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarn import { normalizeTransferredByData } from '../lib/Helper'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:setDepartmentForVisitor'({ diff --git a/apps/meteor/app/livechat/server/methods/setUpConnection.ts b/apps/meteor/app/livechat/server/methods/setUpConnection.ts index 788b2384c4d3..21ce09acaa22 100644 --- a/apps/meteor/app/livechat/server/methods/setUpConnection.ts +++ b/apps/meteor/app/livechat/server/methods/setUpConnection.ts @@ -1,11 +1,11 @@ import { UserStatus } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:setUpConnection'(data: { token: string }): void; diff --git a/apps/meteor/app/livechat/server/methods/takeInquiry.ts b/apps/meteor/app/livechat/server/methods/takeInquiry.ts index 30a5dabb5717..733cbd995208 100644 --- a/apps/meteor/app/livechat/server/methods/takeInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/takeInquiry.ts @@ -1,13 +1,13 @@ import { Omnichannel } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; import { RoutingManager } from '../lib/RoutingManager'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:takeInquiry'( diff --git a/apps/meteor/app/livechat/server/methods/transfer.ts b/apps/meteor/app/livechat/server/methods/transfer.ts index 64a32c24638c..e0516fa27981 100644 --- a/apps/meteor/app/livechat/server/methods/transfer.ts +++ b/apps/meteor/app/livechat/server/methods/transfer.ts @@ -1,7 +1,7 @@ import { Omnichannel } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { LivechatVisitors, LivechatRooms, Subscriptions, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -10,7 +10,7 @@ import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarn import { normalizeTransferredByData } from '../lib/Helper'; import { Livechat } from '../lib/LivechatTyped'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'livechat:transfer'(transferData: { diff --git a/apps/meteor/app/livechat/server/methods/webhookTest.ts b/apps/meteor/app/livechat/server/methods/webhookTest.ts index 12a4711d75b4..68800e1a0616 100644 --- a/apps/meteor/app/livechat/server/methods/webhookTest.ts +++ b/apps/meteor/app/livechat/server/methods/webhookTest.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { SystemLogger } from '../../../../server/lib/logger/system'; @@ -14,7 +14,7 @@ const postCatchError = async function (url: string, options?: Record; diff --git a/apps/meteor/app/livechat/server/sendMessageBySMS.ts b/apps/meteor/app/livechat/server/sendMessageBySMS.ts index 2557fcdeb83d..c7f88646158b 100644 --- a/apps/meteor/app/livechat/server/sendMessageBySMS.ts +++ b/apps/meteor/app/livechat/server/sendMessageBySMS.ts @@ -1,5 +1,5 @@ import { OmnichannelIntegration } from '@rocket.chat/core-services'; -import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings'; +import { isEditedMessage } from '@rocket.chat/core-typings'; import { LivechatVisitors } from '@rocket.chat/models'; import { callbacks } from '../../../lib/callbacks'; @@ -8,8 +8,8 @@ import { normalizeMessageFileUpload } from '../../utils/server/functions/normali import { callbackLogger } from './lib/logger'; callbacks.add( - 'afterSaveMessage', - async (message, room) => { + 'afterOmnichannelSaveMessage', + async (message, { room }) => { // skips this callback if the message was edited if (isEditedMessage(message)) { return message; @@ -20,7 +20,7 @@ callbacks.add( } // only send the sms by SMS if it is a livechat room with SMS set to true - if (!(isOmnichannelRoom(room) && room.sms && room.v && room.v.token)) { + if (!(room.sms && room.v && room.v.token)) { return message; } diff --git a/apps/meteor/app/livechat/server/startup.ts b/apps/meteor/app/livechat/server/startup.ts index b61c85c4001e..32cf01d2e640 100644 --- a/apps/meteor/app/livechat/server/startup.ts +++ b/apps/meteor/app/livechat/server/startup.ts @@ -87,7 +87,7 @@ Meteor.startup(async () => { }); // Remove when accounts.onLogout is async - Accounts.onLogout(({ user }: { user: IUser }) => { + Accounts.onLogout(({ user }: { user?: IUser }) => { if (!user?.roles?.includes('livechat-agent') || user?.roles?.includes('bot')) { return; } diff --git a/apps/meteor/app/mail-messages/server/methods/sendMail.ts b/apps/meteor/app/mail-messages/server/methods/sendMail.ts index 158d271d5379..cc6469544df8 100644 --- a/apps/meteor/app/mail-messages/server/methods/sendMail.ts +++ b/apps/meteor/app/mail-messages/server/methods/sendMail.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Mailer } from '../lib/Mailer'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'Mailer.sendMail'(from: string, subject: string, body: string, dryrun?: boolean, query?: string): any; diff --git a/apps/meteor/app/mail-messages/server/methods/unsubscribe.ts b/apps/meteor/app/mail-messages/server/methods/unsubscribe.ts index 24243cc98c2d..e52677b5ee1a 100644 --- a/apps/meteor/app/mail-messages/server/methods/unsubscribe.ts +++ b/apps/meteor/app/mail-messages/server/methods/unsubscribe.ts @@ -1,11 +1,11 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { Mailer } from '../lib/Mailer'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'Mailer:unsubscribe'(_id: string, createdAt: string): Promise; diff --git a/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.ts b/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.ts index 948187d089ae..6d9a16539704 100644 --- a/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.ts +++ b/apps/meteor/app/mentions/server/methods/getUserMentionsByChannel.ts @@ -1,12 +1,12 @@ import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Users, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync } from '../../../authorization/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getUserMentionsByChannel(params: { roomId: string; options: { limit: number; sort: { ts: -1 | 1 } } }): IMessage[]; diff --git a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts index 2ba0224a0c70..f213ae4b7243 100644 --- a/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts +++ b/apps/meteor/app/message-mark-as-unread/server/unreadMessages.ts @@ -1,11 +1,12 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Subscriptions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedByRoomIdAndUserId } from '../../lib/server/lib/notifyListener'; import logger from './logger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { unreadMessages(firstUnreadMessage?: IMessage, room?: IRoom['_id']): void; @@ -36,7 +37,11 @@ Meteor.methods({ }); } - await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts); + const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts); + if (setAsUnreadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(lastMessage.rid, userId); + } + return; } @@ -72,7 +77,11 @@ Meteor.methods({ if (firstUnreadMessage.ts >= lastSeen) { return logger.debug('Provided message is already marked as unread'); } - logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`); - await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts); + + logger.debug(`Updating unread message of ${originalMessage.ts} as the first unread`); + const setAsUnreadResponse = await Subscriptions.setAsUnreadByRoomIdAndUserId(originalMessage.rid, userId, originalMessage.ts); + if (setAsUnreadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(originalMessage.rid, userId); + } }, }); diff --git a/apps/meteor/app/message-pin/server/pinMessage.ts b/apps/meteor/app/message-pin/server/pinMessage.ts index b0eab3f929d6..9f3dd44cc16d 100644 --- a/apps/meteor/app/message-pin/server/pinMessage.ts +++ b/apps/meteor/app/message-pin/server/pinMessage.ts @@ -2,17 +2,16 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import { Message } from '@rocket.chat/core-services'; import { isQuoteAttachment, isRegisterUser } from '@rocket.chat/core-typings'; import type { IMessage, MessageAttachment, MessageQuoteAttachment } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Rooms, Subscriptions, Users, ReadReceipts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../lib/isTruthy'; -import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; import { canAccessRoomAsync, roomAccessAttributes } from '../../authorization/server'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage'; -import { notifyOnRoomChangedById } from '../../lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; import { settings } from '../../settings/server'; import { getUserAvatarURL } from '../../utils/server/getUserAvatarURL'; @@ -36,7 +35,7 @@ const recursiveRemove = (msg: MessageAttachment, deep = 1) => { const shouldAdd = (attachments: MessageAttachment[], attachment: MessageQuoteAttachment) => !attachments.some((_attachment) => isQuoteAttachment(_attachment) && _attachment.message_link === attachment.message_link); -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { pinMessage(message: IMessage, pinnedAt?: Date): IMessage | null; @@ -135,7 +134,7 @@ Meteor.methods({ const pinMessageType = originalMessage.t === 'e2e' ? 'message_pinned_e2e' : 'message_pinned'; - const msgId = await Message.saveSystemMessage(pinMessageType, originalMessage.rid, '', me, { + return Message.saveSystemMessage(pinMessageType, originalMessage.rid, '', me, { attachments: [ { text: originalMessage.msg, @@ -146,8 +145,6 @@ Meteor.methods({ }, ], }); - - return Messages.findOneById(msgId); }, async unpinMessage(message) { check(message._id, String); @@ -227,7 +224,7 @@ Meteor.methods({ if (settings.get('Message_Read_Receipt_Store_Users')) { await ReadReceipts.setPinnedByMessageId(originalMessage._id, originalMessage.pinned); } - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: message._id, }); diff --git a/apps/meteor/app/message-star/server/starMessage.ts b/apps/meteor/app/message-star/server/starMessage.ts index 4529efb63f6f..36c67c1f4020 100644 --- a/apps/meteor/app/message-star/server/starMessage.ts +++ b/apps/meteor/app/message-star/server/starMessage.ts @@ -1,16 +1,15 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Subscriptions, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; import { canAccessRoomAsync, roomAccessAttributes } from '../../authorization/server'; import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage'; -import { notifyOnRoomChangedById } from '../../lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; import { settings } from '../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { starMessage(message: Omit & { starred: boolean }): boolean; @@ -63,7 +62,7 @@ Meteor.methods({ await Messages.updateUserStarById(message._id, uid, message.starred); - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: message._id, }); diff --git a/apps/meteor/app/meteor-accounts-saml/server/lib/settings.ts b/apps/meteor/app/meteor-accounts-saml/server/lib/settings.ts index 5c16716720b0..bb9567260337 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/lib/settings.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/lib/settings.ts @@ -230,12 +230,10 @@ export const addSettings = async function (name: string): Promise { await this.add(`SAML_Custom_${name}_button_label_color`, '#FFFFFF', { type: 'string', i18nLabel: 'Accounts_OAuth_Custom_Button_Label_Color', - alert: 'OAuth_button_colors_alert', }); await this.add(`SAML_Custom_${name}_button_color`, '#1d74f5', { type: 'string', i18nLabel: 'Accounts_OAuth_Custom_Button_Color', - alert: 'OAuth_button_colors_alert', }); }); diff --git a/apps/meteor/app/meteor-accounts-saml/server/methods/addSamlService.ts b/apps/meteor/app/meteor-accounts-saml/server/methods/addSamlService.ts index f952ce3b3fe3..0b6b5a6c8f6e 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/methods/addSamlService.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/methods/addSamlService.ts @@ -1,9 +1,9 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { addSamlService } from '../lib/settings'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addSamlService(name: string): void; diff --git a/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts b/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts index 956426082d40..a7f9e87a93de 100644 --- a/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts +++ b/apps/meteor/app/meteor-accounts-saml/server/methods/samlLogout.ts @@ -1,5 +1,5 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import type { IServiceProviderOptions } from '../definition/IServiceProviderOptions'; @@ -25,7 +25,7 @@ function getSamlServiceProviderOptions(provider: string): IServiceProviderOption return providers.filter(samlProvider)[0]; } -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { samlLogout(provider: string): string | undefined; diff --git a/apps/meteor/app/models/client/models/CachedChatRoom.ts b/apps/meteor/app/models/client/models/CachedChatRoom.ts index f66e5b447432..852bed5a6067 100644 --- a/apps/meteor/app/models/client/models/CachedChatRoom.ts +++ b/apps/meteor/app/models/client/models/CachedChatRoom.ts @@ -46,7 +46,6 @@ class CachedChatRoom extends CachedCollection { usernames: room.usernames, usersCount: room.usersCount, lastMessage: room.lastMessage, - streamingOptions: room.streamingOptions, teamId: room.teamId, teamMain: room.teamMain, v: (room as IOmnichannelRoom | undefined)?.v, diff --git a/apps/meteor/app/models/client/models/CachedChatSubscription.ts b/apps/meteor/app/models/client/models/CachedChatSubscription.ts index 0e325453539a..7c0e84800c77 100644 --- a/apps/meteor/app/models/client/models/CachedChatSubscription.ts +++ b/apps/meteor/app/models/client/models/CachedChatSubscription.ts @@ -35,7 +35,6 @@ class CachedChatSubscription extends CachedCollection { } } +Object.assign(Meteor.users, { + _connection: undefined, + findOneById: UsersCollection.prototype.findOneById, + isUserInRole: UsersCollection.prototype.isUserInRole, + findUsersInRoles: UsersCollection.prototype.findUsersInRoles, + remove: UsersCollection.prototype.remove, +}); + /** @deprecated */ -export const Users = new UsersCollection(); +export const Users = Meteor.users as UsersCollection; diff --git a/apps/meteor/app/notifications/server/lib/Notifications.ts b/apps/meteor/app/notifications/server/lib/Notifications.ts index ed4985777414..3ddb611d76bd 100644 --- a/apps/meteor/app/notifications/server/lib/Notifications.ts +++ b/apps/meteor/app/notifications/server/lib/Notifications.ts @@ -1,5 +1,5 @@ import { api } from '@rocket.chat/core-services'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { DDPCommon } from 'meteor/ddp-common'; import { Meteor } from 'meteor/meteor'; diff --git a/apps/meteor/app/notifications/server/lib/Presence.ts b/apps/meteor/app/notifications/server/lib/Presence.ts index 9955b687701f..17f35a9d39ff 100644 --- a/apps/meteor/app/notifications/server/lib/Presence.ts +++ b/apps/meteor/app/notifications/server/lib/Presence.ts @@ -1,6 +1,6 @@ import type { IUser } from '@rocket.chat/core-typings'; +import type { StreamerEvents } from '@rocket.chat/ddp-client'; import { Emitter } from '@rocket.chat/emitter'; -import type { StreamerEvents } from '@rocket.chat/ui-contexts'; import type { IPublication, IStreamerConstructor, Connection, IStreamer } from 'meteor/rocketchat:streamer'; type UserPresenceStreamProps = { diff --git a/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts b/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts index 4556a5be7d9f..3f41b1b01bea 100644 --- a/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts +++ b/apps/meteor/app/oauth2-server-config/server/admin/methods/addOAuthApp.ts @@ -1,12 +1,12 @@ import type { IOAuthApps } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import type { OauthAppsAddParams } from '@rocket.chat/rest-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../../lib/server/lib/deprecationWarningLogger'; import { addOAuthApp } from '../functions/addOAuthApp'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addOAuthApp(application: OauthAppsAddParams): IOAuthApps; diff --git a/apps/meteor/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.ts b/apps/meteor/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.ts index 0209f9d453b5..9c5cbf6fd9ae 100644 --- a/apps/meteor/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.ts +++ b/apps/meteor/app/oauth2-server-config/server/admin/methods/deleteOAuthApp.ts @@ -1,11 +1,11 @@ import type { IOAuthApps } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { OAuthAccessTokens, OAuthApps, OAuthAuthCodes } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteOAuthApp(applicationId: IOAuthApps['_id']): boolean; diff --git a/apps/meteor/app/oauth2-server-config/server/admin/methods/updateOAuthApp.ts b/apps/meteor/app/oauth2-server-config/server/admin/methods/updateOAuthApp.ts index 525d0fa2d124..f2daca1885c9 100644 --- a/apps/meteor/app/oauth2-server-config/server/admin/methods/updateOAuthApp.ts +++ b/apps/meteor/app/oauth2-server-config/server/admin/methods/updateOAuthApp.ts @@ -1,12 +1,12 @@ import type { IOAuthApps } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { OAuthApps, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../../authorization/server/functions/hasPermission'; import { parseUriList } from '../functions/parseUriList'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { updateOAuthApp( diff --git a/apps/meteor/app/otr/server/methods/deleteOldOTRMessages.ts b/apps/meteor/app/otr/server/methods/deleteOldOTRMessages.ts index a994b19dd1a4..1b57c65b4bb7 100644 --- a/apps/meteor/app/otr/server/methods/deleteOldOTRMessages.ts +++ b/apps/meteor/app/otr/server/methods/deleteOldOTRMessages.ts @@ -1,9 +1,9 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Subscriptions, ReadReceipts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteOldOTRMessages(roomId: IRoom['_id']): Promise; diff --git a/apps/meteor/app/otr/server/methods/sendSystemMessages.ts b/apps/meteor/app/otr/server/methods/sendSystemMessages.ts index a2abf0938b9e..13abbd7511af 100644 --- a/apps/meteor/app/otr/server/methods/sendSystemMessages.ts +++ b/apps/meteor/app/otr/server/methods/sendSystemMessages.ts @@ -1,7 +1,7 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { sendSystemMessages(rid: string, user: string | undefined, id: string): void; diff --git a/apps/meteor/app/otr/server/methods/updateOTRAck.ts b/apps/meteor/app/otr/server/methods/updateOTRAck.ts index a5a502ddf1a2..4fbd182e9d27 100644 --- a/apps/meteor/app/otr/server/methods/updateOTRAck.ts +++ b/apps/meteor/app/otr/server/methods/updateOTRAck.ts @@ -1,9 +1,9 @@ import { api } from '@rocket.chat/core-services'; import type { IOTRMessage } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { updateOTRAck({ message, ack }: { message: IOTRMessage; ack: string }): void; diff --git a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts index a4376c709275..ddac51b4eca9 100644 --- a/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts +++ b/apps/meteor/app/push-notifications/server/methods/saveNotificationSettings.ts @@ -1,15 +1,16 @@ import type { ISubscription } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Subscriptions } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSubscriptionChangedById } from '../../../lib/server/lib/notifyListener'; import { getUserNotificationPreference } from '../../../utils/server/getUserNotificationPreference'; const saveAudioNotificationValue = (subId: ISubscription['_id'], value: string) => value === 'default' ? Subscriptions.clearAudioNotificationValueById(subId) : Subscriptions.updateAudioNotificationValueById(subId, value); -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { saveNotificationSettings( @@ -132,7 +133,10 @@ Meteor.methods({ }); } - await notifications[field].updateMethod(subscription, value); + const updateResponse = await notifications[field].updateMethod(subscription, value); + if (updateResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } return true; }, @@ -144,13 +148,19 @@ Meteor.methods({ method: 'saveAudioNotificationValue', }); } + const subscription = await Subscriptions.findOneByRoomIdAndUserId(rid, userId); if (!subscription) { throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'saveAudioNotificationValue', }); } - await saveAudioNotificationValue(subscription._id, value); + + const saveAudioNotificationResponse = await saveAudioNotificationValue(subscription._id, value); + if (saveAudioNotificationResponse.modifiedCount) { + void notifyOnSubscriptionChangedById(subscription._id); + } + return true; }, }); diff --git a/apps/meteor/app/push/server/methods.ts b/apps/meteor/app/push/server/methods.ts index 364a16cbdb5e..1f1e261eccae 100644 --- a/apps/meteor/app/push/server/methods.ts +++ b/apps/meteor/app/push/server/methods.ts @@ -1,7 +1,7 @@ import type { IAppsTokens } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { AppsTokens } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Accounts } from 'meteor/accounts-base'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -9,7 +9,7 @@ import { Meteor } from 'meteor/meteor'; import { logger } from './logger'; import { _matchToken } from './push'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'raix:push-update'(options: { diff --git a/apps/meteor/app/reactions/client/methods/setReaction.ts b/apps/meteor/app/reactions/client/methods/setReaction.ts index a38e6c156790..ed15cda9ab8e 100644 --- a/apps/meteor/app/reactions/client/methods/setReaction.ts +++ b/apps/meteor/app/reactions/client/methods/setReaction.ts @@ -1,5 +1,5 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; diff --git a/apps/meteor/app/reactions/server/setReaction.ts b/apps/meteor/app/reactions/server/setReaction.ts index 896e5041bd61..d513c8dda6a5 100644 --- a/apps/meteor/app/reactions/server/setReaction.ts +++ b/apps/meteor/app/reactions/server/setReaction.ts @@ -1,19 +1,18 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; -import { api } from '@rocket.chat/core-services'; +import { api, Message } from '@rocket.chat/core-services'; import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, EmojiCustom, Rooms, Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; import { callbacks } from '../../../lib/callbacks'; import { i18n } from '../../../server/lib/i18n'; -import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; import { canAccessRoomAsync } from '../../authorization/server'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { emoji } from '../../emoji/server'; import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage'; -import { notifyOnRoomChangedById } from '../../lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnMessageChange } from '../../lib/server/lib/notifyListener'; const removeUserReaction = (message: IMessage, reaction: string, username: string) => { if (!message.reactions) { @@ -53,6 +52,8 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction // return; // } + await Message.beforeReacted(message, room); + const userAlreadyReacted = message.reactions && Boolean(message.reactions[reaction]) && @@ -111,7 +112,7 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction await Apps.self?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: message._id, }); } @@ -140,7 +141,7 @@ export async function executeSetReaction(userId: string, reaction: string, messa return setReaction(room, user, message, reaction, shouldReact); } -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { setReaction(reaction: string, messageId: IMessage['_id'], shouldReact?: boolean): boolean | undefined; diff --git a/apps/meteor/app/retention-policy/server/cronPruneMessages.ts b/apps/meteor/app/retention-policy/server/cronPruneMessages.ts index 337691bfbe57..640aa517a679 100644 --- a/apps/meteor/app/retention-policy/server/cronPruneMessages.ts +++ b/apps/meteor/app/retention-policy/server/cronPruneMessages.ts @@ -6,13 +6,20 @@ import { getCronAdvancedTimerFromPrecisionSetting } from '../../../lib/getCronAd import { cleanRoomHistory } from '../../lib/server/functions/cleanRoomHistory'; import { settings } from '../../settings/server'; -const maxTimes = { - c: 0, - p: 0, - d: 0, +type RetentionRoomTypes = 'c' | 'p' | 'd'; + +const getMaxAgeSettingIdByRoomType = (type: RetentionRoomTypes) => { + switch (type) { + case 'c': + return settings.get('RetentionPolicy_TTL_Channels'); + case 'p': + return settings.get('RetentionPolicy_TTL_Groups'); + case 'd': + return settings.get('RetentionPolicy_TTL_DMs'); + } }; -let types: (keyof typeof maxTimes)[] = []; +let types: RetentionRoomTypes[] = []; const oldest = new Date('0001-01-01T00:00:00Z'); @@ -29,7 +36,7 @@ async function job(): Promise { // get all rooms with default values for await (const type of types) { - const maxAge = maxTimes[type] || 0; + const maxAge = getMaxAgeSettingIdByRoomType(type) || 0; const latest = new Date(now.getTime() - maxAge); const rooms = await Rooms.find( @@ -95,9 +102,6 @@ settings.watchMultiple( 'RetentionPolicy_AppliesToChannels', 'RetentionPolicy_AppliesToGroups', 'RetentionPolicy_AppliesToDMs', - 'RetentionPolicy_TTL_Channels', - 'RetentionPolicy_TTL_Groups', - 'RetentionPolicy_TTL_DMs', 'RetentionPolicy_Advanced_Precision', 'RetentionPolicy_Advanced_Precision_Cron', 'RetentionPolicy_Precision', @@ -120,10 +124,6 @@ settings.watchMultiple( types.push('d'); } - maxTimes.c = settings.get('RetentionPolicy_TTL_Channels'); - maxTimes.p = settings.get('RetentionPolicy_TTL_Groups'); - maxTimes.d = settings.get('RetentionPolicy_TTL_DMs'); - const precision = (settings.get('RetentionPolicy_Advanced_Precision') && settings.get('RetentionPolicy_Advanced_Precision_Cron')) || getCronAdvancedTimerFromPrecisionSetting(settings.get('RetentionPolicy_Precision')); diff --git a/apps/meteor/app/search/server/methods.ts b/apps/meteor/app/search/server/methods.ts index 686b5b429e96..cdb3b4180f7a 100644 --- a/apps/meteor/app/search/server/methods.ts +++ b/apps/meteor/app/search/server/methods.ts @@ -1,12 +1,12 @@ import type { IMessageSearchProvider, IMessageSearchSuggestion, IRoom, IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { SearchLogger } from './logger/logger'; import type { IRawSearchResult, ISearchResult } from './model/ISearchResult'; import { searchProviderService, validationService } from './service'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'rocketchatSearch.getProvider'(): IMessageSearchProvider | undefined; diff --git a/apps/meteor/app/settings/server/SettingsRegistry.ts b/apps/meteor/app/settings/server/SettingsRegistry.ts index e86a391ad8fa..d7d2fa0a79f8 100644 --- a/apps/meteor/app/settings/server/SettingsRegistry.ts +++ b/apps/meteor/app/settings/server/SettingsRegistry.ts @@ -139,6 +139,7 @@ export class SettingsRegistry { const settingFromCodeOverwritten = overwriteSetting(settingFromCode); const settingStored = this.store.getSetting(_id); + const settingStoredOverwritten = settingStored && overwriteSetting(settingStored); try { @@ -166,7 +167,10 @@ export class SettingsRegistry { })(); await this.saveUpdatedSetting(_id, updatedProps, removedKeys); - this.store.set(settingFromCodeOverwritten); + if ('value' in updatedProps) { + this.store.set(updatedProps as ISetting); + } + return; } diff --git a/apps/meteor/app/slackbridge/client/slackbridge_import.client.js b/apps/meteor/app/slackbridge/client/slackbridge_import.client.js index 6aeffb7bef45..eebc07ddb72d 100644 --- a/apps/meteor/app/slackbridge/client/slackbridge_import.client.js +++ b/apps/meteor/app/slackbridge/client/slackbridge_import.client.js @@ -1,5 +1,5 @@ import { settings } from '../../settings/client'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; settings.onload('SlackBridge_Enabled', (key, value) => { if (value) { diff --git a/apps/meteor/app/slackbridge/server/removeChannelLinks.ts b/apps/meteor/app/slackbridge/server/removeChannelLinks.ts index 1a4e6f6a0096..2c89e71f9635 100644 --- a/apps/meteor/app/slackbridge/server/removeChannelLinks.ts +++ b/apps/meteor/app/slackbridge/server/removeChannelLinks.ts @@ -1,11 +1,11 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { settings } from '../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { removeSlackBridgeChannelLinks(): { message: string; params: unknown[] }; diff --git a/apps/meteor/app/slashcommand-asciiarts/client/gimme.ts b/apps/meteor/app/slashcommand-asciiarts/client/gimme.ts index 4c9d6a4e40c8..7cd5edb6bb87 100644 --- a/apps/meteor/app/slashcommand-asciiarts/client/gimme.ts +++ b/apps/meteor/app/slashcommand-asciiarts/client/gimme.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; /* * Gimme is a named function that will replace /gimme commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/client/lenny.ts b/apps/meteor/app/slashcommand-asciiarts/client/lenny.ts index 99eaa03b9e59..0e3cc55f6b86 100644 --- a/apps/meteor/app/slashcommand-asciiarts/client/lenny.ts +++ b/apps/meteor/app/slashcommand-asciiarts/client/lenny.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; /* * Lenny is a named function that will replace /lenny commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/client/shrug.ts b/apps/meteor/app/slashcommand-asciiarts/client/shrug.ts index bc0fb300789e..c4bdec8f1a8c 100644 --- a/apps/meteor/app/slashcommand-asciiarts/client/shrug.ts +++ b/apps/meteor/app/slashcommand-asciiarts/client/shrug.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; /* * Shrug is a named function that will replace /shrug commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts b/apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts index 0d709760fe84..8820b81f7c4a 100644 --- a/apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts +++ b/apps/meteor/app/slashcommand-asciiarts/client/tableflip.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; /* * Tableflip is a named function that will replace /Tableflip commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/client/unflip.ts b/apps/meteor/app/slashcommand-asciiarts/client/unflip.ts index a7dc0d257e78..6c02fa196052 100644 --- a/apps/meteor/app/slashcommand-asciiarts/client/unflip.ts +++ b/apps/meteor/app/slashcommand-asciiarts/client/unflip.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; /* * Unflip is a named function that will replace /unflip commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/server/gimme.ts b/apps/meteor/app/slashcommand-asciiarts/server/gimme.ts index f426d6cf85c0..f902d75f33d1 100644 --- a/apps/meteor/app/slashcommand-asciiarts/server/gimme.ts +++ b/apps/meteor/app/slashcommand-asciiarts/server/gimme.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Gimme is a named function that will replace /gimme commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/server/lenny.ts b/apps/meteor/app/slashcommand-asciiarts/server/lenny.ts index 878a10e356d4..e760b5a1169e 100644 --- a/apps/meteor/app/slashcommand-asciiarts/server/lenny.ts +++ b/apps/meteor/app/slashcommand-asciiarts/server/lenny.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Lenny is a named function that will replace /lenny commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/server/shrug.ts b/apps/meteor/app/slashcommand-asciiarts/server/shrug.ts index 1240027bb38f..c2e5d166bfd8 100644 --- a/apps/meteor/app/slashcommand-asciiarts/server/shrug.ts +++ b/apps/meteor/app/slashcommand-asciiarts/server/shrug.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Shrug is a named function that will replace /shrug commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/server/tableflip.ts b/apps/meteor/app/slashcommand-asciiarts/server/tableflip.ts index 34acef9805e2..ac3f599dff1d 100644 --- a/apps/meteor/app/slashcommand-asciiarts/server/tableflip.ts +++ b/apps/meteor/app/slashcommand-asciiarts/server/tableflip.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Tableflip is a named function that will replace /Tableflip commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommand-asciiarts/server/unflip.ts b/apps/meteor/app/slashcommand-asciiarts/server/unflip.ts index 689e7262eac0..b905ed567447 100644 --- a/apps/meteor/app/slashcommand-asciiarts/server/unflip.ts +++ b/apps/meteor/app/slashcommand-asciiarts/server/unflip.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Unflip is a named function that will replace /unflip commands * @param {Object} message - The message object diff --git a/apps/meteor/app/slashcommands-archiveroom/client/client.ts b/apps/meteor/app/slashcommands-archiveroom/client/client.ts index c24763106684..f5154fb32a5b 100644 --- a/apps/meteor/app/slashcommands-archiveroom/client/client.ts +++ b/apps/meteor/app/slashcommands-archiveroom/client/client.ts @@ -1,4 +1,4 @@ -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'archive', diff --git a/apps/meteor/app/slashcommands-archiveroom/server/server.ts b/apps/meteor/app/slashcommands-archiveroom/server/server.ts index 99bcec2cd7b3..f1b33c1022bb 100644 --- a/apps/meteor/app/slashcommands-archiveroom/server/server.ts +++ b/apps/meteor/app/slashcommands-archiveroom/server/server.ts @@ -10,7 +10,7 @@ import { roomCoordinator } from '../../../server/lib/rooms/roomCoordinator'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { archiveRoom } from '../../lib/server/functions/archiveRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'archive', diff --git a/apps/meteor/app/slashcommands-create/client/client.ts b/apps/meteor/app/slashcommands-create/client/client.ts index 299db606db9c..7e8ba831dbd8 100644 --- a/apps/meteor/app/slashcommands-create/client/client.ts +++ b/apps/meteor/app/slashcommands-create/client/client.ts @@ -1,4 +1,4 @@ -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'create', diff --git a/apps/meteor/app/slashcommands-create/server/server.ts b/apps/meteor/app/slashcommands-create/server/server.ts index 104d50c56926..6abee71c56fd 100644 --- a/apps/meteor/app/slashcommands-create/server/server.ts +++ b/apps/meteor/app/slashcommands-create/server/server.ts @@ -6,7 +6,7 @@ import { i18n } from '../../../server/lib/i18n'; import { createChannelMethod } from '../../lib/server/methods/createChannel'; import { createPrivateGroupMethod } from '../../lib/server/methods/createPrivateGroup'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'create', diff --git a/apps/meteor/app/slashcommands-help/server/server.ts b/apps/meteor/app/slashcommands-help/server/server.ts index c24bfb22c6fe..80efaffeb852 100644 --- a/apps/meteor/app/slashcommands-help/server/server.ts +++ b/apps/meteor/app/slashcommands-help/server/server.ts @@ -4,7 +4,7 @@ import { Users } from '@rocket.chat/models'; import { i18n } from '../../../server/lib/i18n'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Help is a named function that will replace /help commands diff --git a/apps/meteor/app/slashcommands-hide/client/hide.ts b/apps/meteor/app/slashcommands-hide/client/hide.ts index 99c1eaea7049..c6486053ecc2 100644 --- a/apps/meteor/app/slashcommands-hide/client/hide.ts +++ b/apps/meteor/app/slashcommands-hide/client/hide.ts @@ -1,4 +1,4 @@ -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'hide', diff --git a/apps/meteor/app/slashcommands-invite/client/client.ts b/apps/meteor/app/slashcommands-invite/client/client.ts index 729073b785d8..7c8af755d64d 100644 --- a/apps/meteor/app/slashcommands-invite/client/client.ts +++ b/apps/meteor/app/slashcommands-invite/client/client.ts @@ -1,4 +1,4 @@ -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'invite', diff --git a/apps/meteor/app/slashcommands-invite/server/server.ts b/apps/meteor/app/slashcommands-invite/server/server.ts index de525d8c6fc6..06a85301540c 100644 --- a/apps/meteor/app/slashcommands-invite/server/server.ts +++ b/apps/meteor/app/slashcommands-invite/server/server.ts @@ -6,7 +6,7 @@ import { Meteor } from 'meteor/meteor'; import { i18n } from '../../../server/lib/i18n'; import { addUsersToRoomMethod } from '../../lib/server/methods/addUsersToRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Invite is a named function that will replace /invite commands diff --git a/apps/meteor/app/slashcommands-inviteall/client/client.ts b/apps/meteor/app/slashcommands-inviteall/client/client.ts index f8ab40953d27..5083cd4a83ab 100644 --- a/apps/meteor/app/slashcommands-inviteall/client/client.ts +++ b/apps/meteor/app/slashcommands-inviteall/client/client.ts @@ -1,4 +1,4 @@ -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'invite-all-to', diff --git a/apps/meteor/app/slashcommands-inviteall/server/server.ts b/apps/meteor/app/slashcommands-inviteall/server/server.ts index bac4349ec72c..e74bb89899c2 100644 --- a/apps/meteor/app/slashcommands-inviteall/server/server.ts +++ b/apps/meteor/app/slashcommands-inviteall/server/server.ts @@ -15,7 +15,7 @@ import { addUsersToRoomMethod } from '../../lib/server/methods/addUsersToRoom'; import { createChannelMethod } from '../../lib/server/methods/createChannel'; import { createPrivateGroupMethod } from '../../lib/server/methods/createPrivateGroup'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; function inviteAll(type: T): SlashCommand['callback'] { return async function inviteAll({ command, params, message, userId }: SlashCommandCallbackParams): Promise { diff --git a/apps/meteor/app/slashcommands-join/client/client.ts b/apps/meteor/app/slashcommands-join/client/client.ts index 417fe1e5cd47..bc8d589f51ac 100644 --- a/apps/meteor/app/slashcommands-join/client/client.ts +++ b/apps/meteor/app/slashcommands-join/client/client.ts @@ -1,6 +1,6 @@ import type { Meteor } from 'meteor/meteor'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'join', diff --git a/apps/meteor/app/slashcommands-join/server/server.ts b/apps/meteor/app/slashcommands-join/server/server.ts index 33d0278f81a3..6497324ae9e0 100644 --- a/apps/meteor/app/slashcommands-join/server/server.ts +++ b/apps/meteor/app/slashcommands-join/server/server.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { i18n } from '../../../server/lib/i18n'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'join', diff --git a/apps/meteor/app/slashcommands-kick/client/client.ts b/apps/meteor/app/slashcommands-kick/client/client.ts index 475346216f1e..7fc167e17c88 100644 --- a/apps/meteor/app/slashcommands-kick/client/client.ts +++ b/apps/meteor/app/slashcommands-kick/client/client.ts @@ -1,6 +1,6 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'kick', diff --git a/apps/meteor/app/slashcommands-kick/server/server.ts b/apps/meteor/app/slashcommands-kick/server/server.ts index 5ca6b45ec835..fdde07b897bf 100644 --- a/apps/meteor/app/slashcommands-kick/server/server.ts +++ b/apps/meteor/app/slashcommands-kick/server/server.ts @@ -6,7 +6,7 @@ import { Users } from '@rocket.chat/models'; import { i18n } from '../../../server/lib/i18n'; import { removeUserFromRoomMethod } from '../../../server/methods/removeUserFromRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'kick', diff --git a/apps/meteor/app/slashcommands-leave/server/leave.ts b/apps/meteor/app/slashcommands-leave/server/leave.ts index 42dad0807246..fa108fe18c72 100644 --- a/apps/meteor/app/slashcommands-leave/server/leave.ts +++ b/apps/meteor/app/slashcommands-leave/server/leave.ts @@ -5,7 +5,7 @@ import { Users } from '@rocket.chat/models'; import { i18n } from '../../../server/lib/i18n'; import { leaveRoomMethod } from '../../lib/server/methods/leaveRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Leave is a named function that will replace /leave commands diff --git a/apps/meteor/app/slashcommands-me/server/me.ts b/apps/meteor/app/slashcommands-me/server/me.ts index ba6a9f8c82cc..b8b4a593cb73 100644 --- a/apps/meteor/app/slashcommands-me/server/me.ts +++ b/apps/meteor/app/slashcommands-me/server/me.ts @@ -1,7 +1,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Me is a named function that will replace /me commands diff --git a/apps/meteor/app/slashcommands-msg/server/server.ts b/apps/meteor/app/slashcommands-msg/server/server.ts index c6a244b80207..e757938106eb 100644 --- a/apps/meteor/app/slashcommands-msg/server/server.ts +++ b/apps/meteor/app/slashcommands-msg/server/server.ts @@ -7,7 +7,7 @@ import { i18n } from '../../../server/lib/i18n'; import { createDirectMessage } from '../../../server/methods/createDirectMessage'; import { executeSendMessage } from '../../lib/server/methods/sendMessage'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Msg is a named function that will replace /msg commands diff --git a/apps/meteor/app/slashcommands-mute/server/mute.ts b/apps/meteor/app/slashcommands-mute/server/mute.ts index 03ce960496da..da20ff4fed47 100644 --- a/apps/meteor/app/slashcommands-mute/server/mute.ts +++ b/apps/meteor/app/slashcommands-mute/server/mute.ts @@ -5,7 +5,7 @@ import { Users } from '@rocket.chat/models'; import { i18n } from '../../../server/lib/i18n'; import { muteUserInRoom } from '../../../server/methods/muteUserInRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Mute is a named function that will replace /mute commands diff --git a/apps/meteor/app/slashcommands-mute/server/unmute.ts b/apps/meteor/app/slashcommands-mute/server/unmute.ts index 25c0956d49e3..4dc683f4ca93 100644 --- a/apps/meteor/app/slashcommands-mute/server/unmute.ts +++ b/apps/meteor/app/slashcommands-mute/server/unmute.ts @@ -5,7 +5,7 @@ import { Users } from '@rocket.chat/models'; import { i18n } from '../../../server/lib/i18n'; import { unmuteUserInRoom } from '../../../server/methods/unmuteUserInRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; /* * Unmute is a named function that will replace /unmute commands diff --git a/apps/meteor/app/slashcommands-open/client/client.ts b/apps/meteor/app/slashcommands-open/client/client.ts index 987df9599761..99438a24eeb0 100644 --- a/apps/meteor/app/slashcommands-open/client/client.ts +++ b/apps/meteor/app/slashcommands-open/client/client.ts @@ -5,7 +5,7 @@ import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; import { router } from '../../../client/providers/RouterProvider'; import { Subscriptions, ChatSubscription } from '../../models/client'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'open', diff --git a/apps/meteor/app/slashcommands-status/client/status.ts b/apps/meteor/app/slashcommands-status/client/status.ts index 9136ef8f586f..3698b5fda4cb 100644 --- a/apps/meteor/app/slashcommands-status/client/status.ts +++ b/apps/meteor/app/slashcommands-status/client/status.ts @@ -2,7 +2,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'status', diff --git a/apps/meteor/app/slashcommands-status/server/status.ts b/apps/meteor/app/slashcommands-status/server/status.ts index 72d92afaf3f2..a2ff6483d398 100644 --- a/apps/meteor/app/slashcommands-status/server/status.ts +++ b/apps/meteor/app/slashcommands-status/server/status.ts @@ -5,7 +5,7 @@ import { Users } from '@rocket.chat/models'; import { i18n } from '../../../server/lib/i18n'; import { settings } from '../../settings/server'; import { setUserStatusMethod } from '../../user-status/server/methods/setUserStatus'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'status', diff --git a/apps/meteor/app/slashcommands-topic/client/topic.ts b/apps/meteor/app/slashcommands-topic/client/topic.ts index f5f5ed58bb0f..f7e47c334b5a 100644 --- a/apps/meteor/app/slashcommands-topic/client/topic.ts +++ b/apps/meteor/app/slashcommands-topic/client/topic.ts @@ -5,7 +5,7 @@ import { callbacks } from '../../../lib/callbacks'; import { hasPermission } from '../../authorization/client'; import { ChatRoom } from '../../models/client/models/ChatRoom'; import { sdk } from '../../utils/client/lib/SDKClient'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'topic', diff --git a/apps/meteor/app/slashcommands-topic/server/topic.ts b/apps/meteor/app/slashcommands-topic/server/topic.ts index 24fd51d5f509..c1fa6ea283b7 100644 --- a/apps/meteor/app/slashcommands-topic/server/topic.ts +++ b/apps/meteor/app/slashcommands-topic/server/topic.ts @@ -2,7 +2,7 @@ import type { SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { saveRoomSettings } from '../../channel-settings/server/methods/saveRoomSettings'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'topic', diff --git a/apps/meteor/app/slashcommands-unarchiveroom/client/client.ts b/apps/meteor/app/slashcommands-unarchiveroom/client/client.ts index 2fed1e1c7802..7b65fc067031 100644 --- a/apps/meteor/app/slashcommands-unarchiveroom/client/client.ts +++ b/apps/meteor/app/slashcommands-unarchiveroom/client/client.ts @@ -1,4 +1,4 @@ -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/client/slashCommand'; slashCommands.add({ command: 'unarchive', diff --git a/apps/meteor/app/slashcommands-unarchiveroom/server/server.ts b/apps/meteor/app/slashcommands-unarchiveroom/server/server.ts index d87981bd65a2..4c0c44269d2f 100644 --- a/apps/meteor/app/slashcommands-unarchiveroom/server/server.ts +++ b/apps/meteor/app/slashcommands-unarchiveroom/server/server.ts @@ -10,7 +10,7 @@ import { roomCoordinator } from '../../../server/lib/rooms/roomCoordinator'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { unarchiveRoom } from '../../lib/server/functions/unarchiveRoom'; import { settings } from '../../settings/server'; -import { slashCommands } from '../../utils/lib/slashCommand'; +import { slashCommands } from '../../utils/server/slashCommand'; slashCommands.add({ command: 'unarchive', diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index cff2aaefcc5a..e5001b2bff87 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -542,7 +542,6 @@ export const statistics = { statistics.totalOTRRooms = await Rooms.findByCreatedOTR().count(); statistics.totalOTR = settings.get('OTR_Count'); statistics.totalBroadcastRooms = await Rooms.findByBroadcast().count(); - statistics.totalRoomsWithActiveLivestream = await Rooms.findByActiveLivestream().count(); statistics.totalTriggeredEmails = settings.get('Triggered_Emails_Count'); statistics.totalRoomsWithStarred = await Messages.countRoomsWithStarredMessages({ readPreference }); statistics.totalRoomsWithPinned = await Messages.countRoomsWithPinnedMessages({ readPreference }); diff --git a/apps/meteor/app/statistics/server/methods/getStatistics.ts b/apps/meteor/app/statistics/server/methods/getStatistics.ts index c7eb79b5b985..a804ef83584e 100644 --- a/apps/meteor/app/statistics/server/methods/getStatistics.ts +++ b/apps/meteor/app/statistics/server/methods/getStatistics.ts @@ -1,10 +1,10 @@ import type { IStats } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { getLastStatistics } from '../functions/getLastStatistics'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getStatistics(refresh?: boolean): IStats; diff --git a/apps/meteor/app/theme/client/imports/general/base_old.css b/apps/meteor/app/theme/client/imports/general/base_old.css index 20b023cc61aa..3120d9c05ff0 100644 --- a/apps/meteor/app/theme/client/imports/general/base_old.css +++ b/apps/meteor/app/theme/client/imports/general/base_old.css @@ -776,21 +776,6 @@ padding: 21px 0 10px; } - & .start { - margin-top: 44px; - - text-align: center; - - & .start__purge-warning { - margin-top: -33px; - margin-bottom: 0.5rem; - padding: 1rem; - - border-width: 1px 0 0; - background: linear-gradient(to bottom, var(--rc-color-alert-message-warning-background) 0%, rgba(255, 255, 255, 0) 100%); - } - } - & .editing .body { border-radius: var(--border-radius); } diff --git a/apps/meteor/app/threads/server/functions.ts b/apps/meteor/app/threads/server/functions.ts index 30daef8b8b93..194e482c54ae 100644 --- a/apps/meteor/app/threads/server/functions.ts +++ b/apps/meteor/app/threads/server/functions.ts @@ -2,51 +2,57 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; import { Messages, Subscriptions, ReadReceipts, NotificationQueue } from '@rocket.chat/models'; +import { + notifyOnSubscriptionChangedByRoomIdAndUserIds, + notifyOnSubscriptionChangedByRoomIdAndUserId, +} from '../../lib/server/lib/notifyListener'; import { getMentions, getUserIdsFromHighlights } from '../../lib/server/lib/notifyUsersOnMessage'; export async function reply({ tmid }: { tmid?: string }, message: IMessage, parentMessage: IMessage, followers: string[]) { - const { rid, ts, u } = message; if (!tmid || isEditedMessage(message)) { return false; } - const { toAll, toHere, mentionIds } = await getMentions(message); + const { rid, ts, u } = message; + + const [highlightsUids, threadFollowers, { toAll, toHere, mentionIds }] = await Promise.all([ + getUserIdsFromHighlights(rid, message), + Messages.getThreadFollowsByThreadId(tmid), + getMentions(message), + ]); const addToReplies = [ - ...new Set([ - ...followers, - ...mentionIds, - ...(Array.isArray(parentMessage.replies) && parentMessage.replies.length ? [u._id] : [parentMessage.u._id, u._id]), - ]), + ...new Set([...followers, ...mentionIds, ...(parentMessage.replies?.length ? [u._id] : [parentMessage.u._id, u._id])]), ]; - const highlightedUserIds = new Set(); - (await getUserIdsFromHighlights(rid, message)).forEach((uid) => highlightedUserIds.add(uid)); - await Messages.updateRepliesByThreadId(tmid, addToReplies, ts); - await ReadReceipts.setAsThreadById(tmid); + const threadFollowersUids = threadFollowers?.filter((userId) => userId !== u._id && !mentionIds.includes(userId)) || []; - const replies = await Messages.getThreadFollowsByThreadId(tmid); + // Notify everyone involved in the thread + const notifyOptions = toAll || toHere ? { groupMention: true } : {}; - const repliesFiltered = (replies || []).filter((userId) => userId !== u._id).filter((userId) => !mentionIds.includes(userId)); + // Notify message mentioned users and highlights + const mentionedUsers = [...new Set([...mentionIds, ...highlightsUids])]; - if (toAll || toHere) { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, { - groupMention: true, - }); - } else { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, repliesFiltered, tmid, {}); - } + const promises = [ + Messages.updateRepliesByThreadId(tmid, addToReplies, ts), + ReadReceipts.setAsThreadById(tmid), + Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, threadFollowersUids, tmid, notifyOptions), + ]; - const mentionedUsers = new Set([...mentionIds, ...highlightedUserIds]); - for await (const userId of mentionedUsers) { - await Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, [userId], tmid, { userMention: true }); + if (mentionedUsers.length) { + promises.push(Subscriptions.addUnreadThreadByRoomIdAndUserIds(rid, mentionedUsers, tmid, { userMention: true })); } - const highlightIds = Array.from(highlightedUserIds); - if (highlightIds.length) { - await Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightIds); - await Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightIds); + if (highlightsUids.length) { + promises.push( + Subscriptions.setAlertForRoomIdAndUserIds(rid, highlightsUids), + Subscriptions.setOpenForRoomIdAndUserIds(rid, highlightsUids), + ); } + + await Promise.allSettled(promises); + + void notifyOnSubscriptionChangedByRoomIdAndUserIds(rid, [...threadFollowersUids, ...mentionedUsers, ...highlightsUids]); } export async function follow({ tmid, uid }: { tmid: string; uid: string }) { @@ -62,20 +68,27 @@ export async function unfollow({ tmid, rid, uid }: { tmid: string; rid: string; return false; } - await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, uid, tmid); + if (removeUnreadThreadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, uid); + } await Messages.removeThreadFollowerByThreadId(tmid, uid); } export const readThread = async ({ userId, rid, tmid }: { userId: string; rid: string; tmid: string }) => { - const projection = { tunread: 1 }; - const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection }); + const sub = await Subscriptions.findOneByRoomIdAndUserId(rid, userId, { projection: { tunread: 1 } }); if (!sub) { return; } + // if the thread being marked as read is the last one unread also clear the unread subscription flag const clearAlert = sub.tunread && sub.tunread?.length <= 1 && sub.tunread.includes(tmid); - await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); + const removeUnreadThreadResponse = await Subscriptions.removeUnreadThreadByRoomIdAndUserId(rid, userId, tmid, clearAlert); + if (removeUnreadThreadResponse.modifiedCount) { + void notifyOnSubscriptionChangedByRoomIdAndUserId(rid, userId); + } + await NotificationQueue.clearQueueByUserId(userId); }; diff --git a/apps/meteor/app/threads/server/hooks/aftersavemessage.ts b/apps/meteor/app/threads/server/hooks/aftersavemessage.ts index ab1fa182599b..a938dadddb27 100644 --- a/apps/meteor/app/threads/server/hooks/aftersavemessage.ts +++ b/apps/meteor/app/threads/server/hooks/aftersavemessage.ts @@ -4,7 +4,7 @@ import { Messages } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; -import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; +import { notifyOnMessageChange } from '../../../lib/server/lib/notifyListener'; import { updateThreadUsersSubscriptions, getMentions } from '../../../lib/server/lib/notifyUsersOnMessage'; import { sendMessageNotifications } from '../../../lib/server/lib/sendNotificationsOnMessage'; import { settings } from '../../../settings/server'; @@ -62,7 +62,7 @@ export async function processThreads(message: IMessage, room: IRoom) { await notifyUsersOnReply(message, replies); await metaData(message, parentMessage, replies); await notification(message, room, replies); - void broadcastMessageFromData({ + void notifyOnMessageChange({ id: message.tmid, }); @@ -77,7 +77,7 @@ Meteor.startup(() => { } callbacks.add( 'afterSaveMessage', - async (message, room) => { + async (message, { room }) => { return processThreads(message, room); }, callbacks.priority.LOW, diff --git a/apps/meteor/app/threads/server/methods/followMessage.ts b/apps/meteor/app/threads/server/methods/followMessage.ts index 05650d0ad2ef..8ed7093e00d4 100644 --- a/apps/meteor/app/threads/server/methods/followMessage.ts +++ b/apps/meteor/app/threads/server/methods/followMessage.ts @@ -1,16 +1,17 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; import { RateLimiter } from '../../../lib/server'; +import { notifyOnMessageChange } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { follow } from '../functions'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { followMessage(message: { mid: IMessage['_id'] }): false | undefined; @@ -41,7 +42,13 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'followMessage' }); } - const followResult = await follow({ tmid: message.tmid || message._id, uid }); + const id = message.tmid || message._id; + + const followResult = await follow({ tmid: id, uid }); + + void notifyOnMessageChange({ + id, + }); const isFollowed = true; await Apps.self?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); diff --git a/apps/meteor/app/threads/server/methods/getThreadMessages.ts b/apps/meteor/app/threads/server/methods/getThreadMessages.ts index d6c2d65ff4d4..8ae31130df1b 100644 --- a/apps/meteor/app/threads/server/methods/getThreadMessages.ts +++ b/apps/meteor/app/threads/server/methods/getThreadMessages.ts @@ -1,6 +1,6 @@ import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; @@ -8,7 +8,7 @@ import { canAccessRoomAsync } from '../../../authorization/server'; import { settings } from '../../../settings/server'; import { readThread } from '../functions'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getThreadMessages(params: { tmid: IMessage['_id']; limit?: number; skip?: number }): Promise; diff --git a/apps/meteor/app/threads/server/methods/getThreadsList.ts b/apps/meteor/app/threads/server/methods/getThreadsList.ts index 5a33dc16280b..c51449ff2d4d 100644 --- a/apps/meteor/app/threads/server/methods/getThreadsList.ts +++ b/apps/meteor/app/threads/server/methods/getThreadsList.ts @@ -1,6 +1,6 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages, Rooms } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync } from '../../../authorization/server'; @@ -8,7 +8,7 @@ import { settings } from '../../../settings/server'; const MAX_LIMIT = 100; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getThreadsList(params: { rid: IRoom['_id']; limit?: number; skip?: number }): IMessage[]; diff --git a/apps/meteor/app/threads/server/methods/unfollowMessage.ts b/apps/meteor/app/threads/server/methods/unfollowMessage.ts index afc9206b038f..de4f2683be41 100644 --- a/apps/meteor/app/threads/server/methods/unfollowMessage.ts +++ b/apps/meteor/app/threads/server/methods/unfollowMessage.ts @@ -1,16 +1,17 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Messages } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; import { RateLimiter } from '../../../lib/server'; +import { notifyOnMessageChange } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { unfollow } from '../functions'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { unfollowMessage(message: { mid: IMessage['_id'] }): false | undefined; @@ -41,7 +42,13 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'not-allowed', { method: 'unfollowMessage' }); } - const unfollowResult = await unfollow({ rid: message.rid, tmid: message.tmid || message._id, uid }); + const id = message.tmid || message._id; + + const unfollowResult = await unfollow({ rid: message.rid, tmid: id, uid }); + + void notifyOnMessageChange({ + id, + }); const isFollowed = false; await Apps.self?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts index 545e1e73342d..602a0eddb8ce 100644 --- a/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts +++ b/apps/meteor/app/ui-cached-collection/client/models/CachedCollection.ts @@ -1,5 +1,5 @@ +import type { StreamNames } from '@rocket.chat/ddp-client'; import { Emitter } from '@rocket.chat/emitter'; -import type { StreamNames } from '@rocket.chat/ui-contexts'; import localforage from 'localforage'; import { Accounts } from 'meteor/accounts-base'; import { Meteor } from 'meteor/meteor'; diff --git a/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts b/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts index 845731e2450a..b03115105841 100644 --- a/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts +++ b/apps/meteor/app/ui-cached-collection/client/models/CachedCollectionManager.ts @@ -41,12 +41,12 @@ class CachedCollectionManager extends Emitter<{ reconnect: void; login: string | } }); + Accounts.onLogin(() => { + this.emit('login', Meteor.userId()); + }); Tracker.autorun(() => { const uid = Meteor.userId(); this.logged = uid !== null; - if (this.logged) { - this.emit('login', uid); - } }); } diff --git a/apps/meteor/app/user-status/server/methods/deleteCustomUserStatus.ts b/apps/meteor/app/user-status/server/methods/deleteCustomUserStatus.ts index f58fa9551f4c..416bc6f678ed 100644 --- a/apps/meteor/app/user-status/server/methods/deleteCustomUserStatus.ts +++ b/apps/meteor/app/user-status/server/methods/deleteCustomUserStatus.ts @@ -1,11 +1,11 @@ import { api } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { CustomUserStatus } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { deleteCustomUserStatus(userStatusID: string): Promise; diff --git a/apps/meteor/app/user-status/server/methods/getUserStatusText.ts b/apps/meteor/app/user-status/server/methods/getUserStatusText.ts index 5aa80627d562..911a69854b95 100644 --- a/apps/meteor/app/user-status/server/methods/getUserStatusText.ts +++ b/apps/meteor/app/user-status/server/methods/getUserStatusText.ts @@ -1,9 +1,9 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; import { getStatusText } from '../../../lib/server/functions/getStatusText'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getUserStatusText(userId: string): Promise; diff --git a/apps/meteor/app/user-status/server/methods/insertOrUpdateUserStatus.ts b/apps/meteor/app/user-status/server/methods/insertOrUpdateUserStatus.ts index b6a98895bc2c..6e034f030679 100644 --- a/apps/meteor/app/user-status/server/methods/insertOrUpdateUserStatus.ts +++ b/apps/meteor/app/user-status/server/methods/insertOrUpdateUserStatus.ts @@ -1,14 +1,14 @@ import { api } from '@rocket.chat/core-services'; import type { ICustomUserStatus } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import type { InsertionModel } from '@rocket.chat/model-typings'; import { CustomUserStatus } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { trim } from '../../../../lib/utils/stringUtils'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { insertOrUpdateUserStatus(userStatusData: { diff --git a/apps/meteor/app/user-status/server/methods/listCustomUserStatus.ts b/apps/meteor/app/user-status/server/methods/listCustomUserStatus.ts index 3a962121d65c..d9a58f8b8c72 100644 --- a/apps/meteor/app/user-status/server/methods/listCustomUserStatus.ts +++ b/apps/meteor/app/user-status/server/methods/listCustomUserStatus.ts @@ -1,9 +1,9 @@ import type { ICustomUserStatus } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { CustomUserStatus } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { listCustomUserStatus(): ICustomUserStatus[]; diff --git a/apps/meteor/app/user-status/server/methods/setUserStatus.ts b/apps/meteor/app/user-status/server/methods/setUserStatus.ts index 32db93e722fb..0b40e7e37246 100644 --- a/apps/meteor/app/user-status/server/methods/setUserStatus.ts +++ b/apps/meteor/app/user-status/server/methods/setUserStatus.ts @@ -1,6 +1,6 @@ import { Presence } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -8,7 +8,7 @@ import { RateLimiter } from '../../../lib/server'; import { setStatusText } from '../../../lib/server/functions/setStatusText'; import { settings } from '../../../settings/server'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { setUserStatus(statusType: IUser['status'], statusText: IUser['statusText']): void; diff --git a/apps/meteor/app/utils/client/index.ts b/apps/meteor/app/utils/client/index.ts index fd03ffc3d720..561a1116141b 100644 --- a/apps/meteor/app/utils/client/index.ts +++ b/apps/meteor/app/utils/client/index.ts @@ -2,6 +2,6 @@ export { Info } from '../rocketchat.info'; export { getUserPreference } from './lib/getUserPreference'; export { fileUploadIsValidContentType } from './restrictions'; export { getUserAvatarURL } from './getUserAvatarURL'; -export { slashCommands } from '../lib/slashCommand'; +export { slashCommands } from './slashCommand'; export { getURL } from './getURL'; export { APIClient } from './lib/RestApiClient'; diff --git a/apps/meteor/app/utils/client/lib/SDKClient.ts b/apps/meteor/app/utils/client/lib/SDKClient.ts index c174f9125f49..3c7e43c85f7c 100644 --- a/apps/meteor/app/utils/client/lib/SDKClient.ts +++ b/apps/meteor/app/utils/client/lib/SDKClient.ts @@ -1,15 +1,12 @@ import type { RestClientInterface } from '@rocket.chat/api-client'; -import type { SDK } from '@rocket.chat/ddp-client/src/DDPSDK'; -import type { ClientStream } from '@rocket.chat/ddp-client/src/types/ClientStream'; -import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '@rocket.chat/ddp-client/src/types/streams'; +import type { SDK, ClientStream, StreamKeys, StreamNames, StreamerCallbackArgs, ServerMethods } from '@rocket.chat/ddp-client'; import { Emitter } from '@rocket.chat/emitter'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { DDPCommon } from 'meteor/ddp-common'; import { Meteor } from 'meteor/meteor'; import { APIClient } from './RestApiClient'; -declare module '@rocket.chat/ddp-client/src/DDPSDK' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface SDK { stream>( @@ -138,6 +135,12 @@ const createStreamManager = () => { const streams = new Map(); + Accounts.onLogout(() => { + streams.forEach((stream) => { + stream.unsubList.forEach((stop) => stop()); + }); + }); + Meteor.connection._stream.on('message', (rawMsg: string) => { const msg = DDPCommon.parseDDP(rawMsg); if (!isChangedCollectionPayload(msg)) { @@ -169,7 +172,6 @@ const createStreamManager = () => { const stop = (): void => { streamProxy.off(eventLiteral, proxyCallback); - // If someone is still listening, don't unsubscribe if (streamProxy.has(eventLiteral)) { return; @@ -182,11 +184,15 @@ const createStreamManager = () => { }; const stream = streams.get(eventLiteral) || createNewMeteorStream(name, key, args); + stream.unsubList.add(stop); if (!streams.has(eventLiteral)) { streams.set(eventLiteral, stream); } - stream.error(() => stop()); + + stream.error(() => { + stream.unsubList.forEach((stop) => stop()); + }); return { id: '', diff --git a/apps/meteor/app/utils/lib/slashCommand.ts b/apps/meteor/app/utils/client/slashCommand.ts similarity index 84% rename from apps/meteor/app/utils/lib/slashCommand.ts rename to apps/meteor/app/utils/client/slashCommand.ts index f4e2cc1b359a..66e793012fac 100644 --- a/apps/meteor/app/utils/lib/slashCommand.ts +++ b/apps/meteor/app/utils/client/slashCommand.ts @@ -6,7 +6,8 @@ import type { SlashCommandPreviewItem, SlashCommandPreviews, } from '@rocket.chat/core-typings'; -import { Meteor } from 'meteor/meteor'; + +import { InvalidCommandUsage, InvalidPreview } from '../../../client/lib/errors'; interface ISlashCommandAddParams { command: string; @@ -69,7 +70,7 @@ export const slashCommands = { } if (!message?.rid) { - throw new Meteor.Error('invalid-command-usage', 'Executing a command requires at least a message with a room id.'); + throw new InvalidCommandUsage(); } return cmd.callback({ command, params, message, triggerId, userId }); @@ -85,7 +86,7 @@ export const slashCommands = { } if (!message?.rid) { - throw new Meteor.Error('invalid-command-usage', 'Executing a command requires at least a message with a room id.'); + throw new InvalidCommandUsage(); } const previewInfo = await cmd.previewer(command, params, message); @@ -114,19 +115,19 @@ export const slashCommands = { } if (!message?.rid) { - throw new Meteor.Error('invalid-command-usage', 'Executing a command requires at least a message with a room id.'); + throw new InvalidCommandUsage(); } // { id, type, value } if (!preview.id || !preview.type || !preview.value) { - throw new Meteor.Error('error-invalid-preview', 'Preview Item must have an id, type, and value.'); + throw new InvalidPreview(); } return cmd.previewCallback(command, params, message, preview, triggerId); }, }; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { slashCommand(params: { cmd: string; params: string; msg: IMessage; triggerId: string }): unknown; diff --git a/apps/meteor/app/utils/lib/i18n.ts b/apps/meteor/app/utils/lib/i18n.ts index 9d3fbc59d245..b69fe6b30513 100644 --- a/apps/meteor/app/utils/lib/i18n.ts +++ b/apps/meteor/app/utils/lib/i18n.ts @@ -9,11 +9,11 @@ export const i18n = i18next.use(sprintf); export const addSprinfToI18n = function (t: (typeof i18n)['t']) { return function (key: string, ...replaces: any): string { if (replaces[0] === undefined) { - return t(key, ...replaces); + return t(key); } if (isObject(replaces[0]) && !Array.isArray(replaces[0])) { - return t(key, ...replaces); + return t(key, replaces[0]); } return t(key, { diff --git a/apps/meteor/app/utils/rocketchat.info b/apps/meteor/app/utils/rocketchat.info index ef7c8e5717c9..faf0e8f47de6 100644 --- a/apps/meteor/app/utils/rocketchat.info +++ b/apps/meteor/app/utils/rocketchat.info @@ -1,3 +1,3 @@ { - "version": "6.11.0-develop" + "version": "6.12.0-develop" } diff --git a/apps/meteor/app/utils/server/functions/getMongoInfo.ts b/apps/meteor/app/utils/server/functions/getMongoInfo.ts index 8460e9e4ced0..1caef4a22e32 100644 --- a/apps/meteor/app/utils/server/functions/getMongoInfo.ts +++ b/apps/meteor/app/utils/server/functions/getMongoInfo.ts @@ -7,7 +7,6 @@ function getOplogInfo(): { oplogEnabled: boolean; mongo: MongoConnection } { const oplogEnabled = isWatcherRunning(); - // @ts-expect-error - You're drunk ts return { oplogEnabled, mongo }; } diff --git a/apps/meteor/app/utils/server/slashCommand.ts b/apps/meteor/app/utils/server/slashCommand.ts index 74f2c5716cc4..27b3c81735f9 100644 --- a/apps/meteor/app/utils/server/slashCommand.ts +++ b/apps/meteor/app/utils/server/slashCommand.ts @@ -1,7 +1,139 @@ -import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import { MeteorError } from '@rocket.chat/core-services'; +import type { + IMessage, + SlashCommand, + SlashCommandOptions, + RequiredField, + SlashCommandPreviewItem, + SlashCommandPreviews, +} from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Meteor } from 'meteor/meteor'; -import { slashCommands } from '../lib/slashCommand'; +interface ISlashCommandAddParams { + command: string; + callback?: SlashCommand['callback']; + options?: SlashCommandOptions; + result?: SlashCommand['result']; + providesPreview?: boolean; + previewer?: SlashCommand['previewer']; + previewCallback?: SlashCommand['previewCallback']; + appId?: string; + description?: string; +} + +export const slashCommands = { + commands: {} as Record, + add({ + command, + callback, + options = {}, + result, + providesPreview = false, + previewer, + previewCallback, + appId, + description = '', + }: ISlashCommandAddParams): void { + if (this.commands[command]) { + return; + } + this.commands[command] = { + command, + callback, + params: options.params, + description: options.description || description, + permission: options.permission, + clientOnly: options.clientOnly || false, + result, + providesPreview: Boolean(providesPreview), + previewer, + previewCallback, + appId, + } as SlashCommand; + }, + async run({ + command, + message, + params, + triggerId, + userId, + }: { + command: string; + params: string; + message: RequiredField, 'rid' | '_id'>; + userId: string; + triggerId?: string | undefined; + }): Promise { + const cmd = this.commands[command]; + if (typeof cmd?.callback !== 'function') { + return; + } + + if (!message?.rid) { + throw new MeteorError('invalid-command-usage', 'Executing a command requires at least a message with a room id.'); + } + + return cmd.callback({ command, params, message, triggerId, userId }); + }, + async getPreviews( + command: string, + params: string, + message: RequiredField, 'rid'>, + ): Promise { + const cmd = this.commands[command]; + if (typeof cmd?.previewer !== 'function') { + return; + } + + if (!message?.rid) { + throw new MeteorError('invalid-command-usage', 'Executing a command requires at least a message with a room id.'); + } + + const previewInfo = await cmd.previewer(command, params, message); + + if (!previewInfo?.items?.length) { + return; + } + + // A limit of ten results, to save time and bandwidth + if (previewInfo.items.length >= 10) { + previewInfo.items = previewInfo.items.slice(0, 10); + } + + return previewInfo; + }, + async executePreview( + command: string, + params: string, + message: Pick & Partial>, + preview: SlashCommandPreviewItem, + triggerId?: string, + ) { + const cmd = this.commands[command]; + if (typeof cmd?.previewCallback !== 'function') { + return; + } + + if (!message?.rid) { + throw new MeteorError('invalid-command-usage', 'Executing a command requires at least a message with a room id.'); + } + + // { id, type, value } + if (!preview.id || !preview.type || !preview.value) { + throw new MeteorError('error-invalid-preview', 'Preview Item must have an id, type, and value.'); + } + + return cmd.previewCallback(command, params, message, preview, triggerId); + }, +}; + +declare module '@rocket.chat/ddp-client' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + slashCommand(params: { cmd: string; params: string; msg: IMessage; triggerId: string }): unknown; + } +} Meteor.methods({ async slashCommand(command) { @@ -27,5 +159,3 @@ Meteor.methods({ }); }, }); - -export { slashCommands }; diff --git a/apps/meteor/app/version-check/server/methods/banner_dismiss.ts b/apps/meteor/app/version-check/server/methods/banner_dismiss.ts index 5ffebcfbbd5a..a3bf337626f9 100644 --- a/apps/meteor/app/version-check/server/methods/banner_dismiss.ts +++ b/apps/meteor/app/version-check/server/methods/banner_dismiss.ts @@ -1,10 +1,10 @@ +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Users } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { notifyOnUserChange } from '../../../lib/server/lib/notifyListener'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { 'banner/dismiss'({ id }: { id: string }): void; diff --git a/apps/meteor/app/webdav/server/methods/addWebdavAccount.ts b/apps/meteor/app/webdav/server/methods/addWebdavAccount.ts index e2c9a9ddc324..2c3973649091 100644 --- a/apps/meteor/app/webdav/server/methods/addWebdavAccount.ts +++ b/apps/meteor/app/webdav/server/methods/addWebdavAccount.ts @@ -1,14 +1,14 @@ import { api } from '@rocket.chat/core-services'; import type { IWebdavAccountPayload } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { WebdavAccounts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings/server'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { addWebdavAccount(formData: IWebdavAccountPayload): boolean; diff --git a/apps/meteor/app/webdav/server/methods/getFileFromWebdav.ts b/apps/meteor/app/webdav/server/methods/getFileFromWebdav.ts index 346362935d87..38aeb0442c5c 100644 --- a/apps/meteor/app/webdav/server/methods/getFileFromWebdav.ts +++ b/apps/meteor/app/webdav/server/methods/getFileFromWebdav.ts @@ -1,15 +1,15 @@ import type { IWebdavAccount, IWebdavNode } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { WebdavAccounts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings/server'; import { getWebdavCredentials } from '../lib/getWebdavCredentials'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention - export interface ServerMethods { + interface ServerMethods { getFileFromWebdav(accountId: IWebdavAccount['_id'], file: IWebdavNode): Promise<{ success: boolean; data: Uint8Array }>; } } diff --git a/apps/meteor/app/webdav/server/methods/getWebdavFileList.ts b/apps/meteor/app/webdav/server/methods/getWebdavFileList.ts index f4d944181928..66e924b719a2 100644 --- a/apps/meteor/app/webdav/server/methods/getWebdavFileList.ts +++ b/apps/meteor/app/webdav/server/methods/getWebdavFileList.ts @@ -1,13 +1,13 @@ import type { IWebdavAccount, IWebdavNode } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { WebdavAccounts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings/server'; import { getWebdavCredentials } from '../lib/getWebdavCredentials'; import { WebdavClientAdapter } from '../lib/webdavClientAdapter'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getWebdavFileList(accountId: IWebdavAccount['_id'], path: string): { success: boolean; data: IWebdavNode[] }; diff --git a/apps/meteor/app/webdav/server/methods/getWebdavFilePreview.ts b/apps/meteor/app/webdav/server/methods/getWebdavFilePreview.ts index 1e01937a0e52..34077babf276 100644 --- a/apps/meteor/app/webdav/server/methods/getWebdavFilePreview.ts +++ b/apps/meteor/app/webdav/server/methods/getWebdavFilePreview.ts @@ -1,13 +1,13 @@ import type { IWebdavAccount } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { WebdavAccounts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { createClient } from 'webdav'; import { settings } from '../../../settings/server'; import { getWebdavCredentials } from '../lib/getWebdavCredentials'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { getWebdavFilePreview(accountId: IWebdavAccount['_id'], path: string): { success: true; data: ArrayBuffer } | undefined; diff --git a/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts b/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts index df6102297cc2..a68a14e29e40 100644 --- a/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts +++ b/apps/meteor/app/webdav/server/methods/removeWebdavAccount.ts @@ -1,14 +1,14 @@ import { api } from '@rocket.chat/core-services'; import type { IWebdavAccount } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { WebdavAccounts } from '@rocket.chat/models'; -import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { DeleteResult } from 'mongodb'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { removeWebdavAccount(accountId: IWebdavAccount['_id']): DeleteResult; diff --git a/apps/meteor/app/webdav/server/methods/uploadFileToWebdav.ts b/apps/meteor/app/webdav/server/methods/uploadFileToWebdav.ts index 8a2e1badd27d..97bcf4632216 100644 --- a/apps/meteor/app/webdav/server/methods/uploadFileToWebdav.ts +++ b/apps/meteor/app/webdav/server/methods/uploadFileToWebdav.ts @@ -1,7 +1,8 @@ import { MeteorError } from '@rocket.chat/core-services'; import type { IWebdavAccount } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ddp-client'; import { Logger } from '@rocket.chat/logger'; -import type { ServerMethods, TranslationKey } from '@rocket.chat/ui-contexts'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { settings } from '../../../settings/server'; @@ -9,7 +10,7 @@ import { uploadFileToWebdav } from '../lib/uploadFileToWebdav'; const logger = new Logger('WebDAV_Upload'); -declare module '@rocket.chat/ui-contexts' { +declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention interface ServerMethods { uploadFileToWebdav( diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.spec.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.spec.tsx index 11eddf934055..94fdfe25a92d 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.spec.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useAuditMenu.spec.tsx @@ -1,10 +1,11 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useAuditMenu } from './useAuditMenu'; it('should return an empty array of items if doesn`t have license', async () => { - const { result, waitFor } = renderHook(() => useAuditMenu(), { + const { result } = renderHook(() => useAuditMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ // @ts-expect-error: just for testing @@ -18,13 +19,12 @@ it('should return an empty array of items if doesn`t have license', async () => .build(), }); - await waitFor(() => result.all.length > 1); - - expect(result.current).toEqual([]); + await waitFor(() => expect(result.current).toEqual([])); }); it('should return an empty array of items if have license and not have permissions', async () => { - const { result, waitFor } = renderHook(() => useAuditMenu(), { + const { result } = renderHook(() => useAuditMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ license: { @@ -41,13 +41,12 @@ it('should return an empty array of items if have license and not have permissio .build(), }); - await waitFor(() => result.all.length > 1); - - expect(result.current).toEqual([]); + await waitFor(() => expect(result.current).toEqual([])); }); it('should return auditItems if have license and permissions', async () => { - const { result, waitFor } = renderHook(() => useAuditMenu(), { + const { result } = renderHook(() => useAuditMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ license: { @@ -65,12 +64,12 @@ it('should return auditItems if have license and permissions', async () => { .build(), }); - await waitFor(() => result.current.length > 0); - - expect(result.current[0].items[0]).toEqual( - expect.objectContaining({ - id: 'messages', - }), + await waitFor(() => + expect(result.current[0]?.items[0]).toEqual( + expect.objectContaining({ + id: 'messages', + }), + ), ); expect(result.current[0].items[1]).toEqual( @@ -81,7 +80,8 @@ it('should return auditItems if have license and permissions', async () => { }); it('should return auditMessages item if have license and can-audit permission', async () => { - const { result, waitFor } = renderHook(() => useAuditMenu(), { + const { result } = renderHook(() => useAuditMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ license: { @@ -98,17 +98,18 @@ it('should return auditMessages item if have license and can-audit permission', .build(), }); - await waitFor(() => result.current.length > 0); - - expect(result.current[0].items[0]).toEqual( - expect.objectContaining({ - id: 'messages', - }), + await waitFor(() => + expect(result.current[0]?.items[0]).toEqual( + expect.objectContaining({ + id: 'messages', + }), + ), ); }); it('should return audiLogs item if have license and can-audit-log permission', async () => { - const { result, waitFor } = renderHook(() => useAuditMenu(), { + const { result } = renderHook(() => useAuditMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ license: { @@ -125,11 +126,11 @@ it('should return audiLogs item if have license and can-audit-log permission', a .build(), }); - await waitFor(() => result.current.length > 0); - - expect(result.current[0].items[0]).toEqual( - expect.objectContaining({ - id: 'auditLog', - }), + await waitFor(() => + expect(result.current[0]?.items[0]).toEqual( + expect.objectContaining({ + id: 'auditLog', + }), + ), ); }); diff --git a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.spec.tsx b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.spec.tsx index 2a3d277e69fe..d2d1e36ca05e 100644 --- a/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.spec.tsx +++ b/apps/meteor/client/NavBarV2/NavBarPagesToolbar/hooks/useMarketPlaceMenu.spec.tsx @@ -1,11 +1,12 @@ import { UIActionButtonContext } from '@rocket.chat/apps-engine/definition/ui'; import { mockAppRoot } from '@rocket.chat/mock-providers'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useMarketPlaceMenu } from './useMarketPlaceMenu'; it('should return and empty array if the user does not have `manage-apps` and `access-marketplace` permission', () => { const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => []) .build(), @@ -16,6 +17,7 @@ it('should return and empty array if the user does not have `manage-apps` and `a it('should return `explore` and `installed` items if the user has `access-marketplace` permission', () => { const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => []) .withPermission('access-marketplace') @@ -37,6 +39,7 @@ it('should return `explore` and `installed` items if the user has `access-market it('should return `explore`, `installed` and `requested` items if the user has `manage-apps` permission', () => { const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => []) .withEndpoint('GET', '/apps/app-request/stats', () => ({ @@ -69,7 +72,8 @@ it('should return `explore`, `installed` and `requested` items if the user has ` }); it('should return one action from the server with no conditions', async () => { - const { result, waitForValueToChange } = renderHook(() => useMarketPlaceMenu(), { + const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => [ { @@ -101,18 +105,19 @@ it('should return one action from the server with no conditions', async () => { }), ); - await waitForValueToChange(() => result.current[0].items[3]); - - expect(result.current[0].items[3]).toEqual( - expect.objectContaining({ - id: 'APP_ID_ACTION_ID', - }), + await waitFor(() => + expect(result.current[0]?.items[3]).toEqual( + expect.objectContaining({ + id: 'APP_ID_ACTION_ID', + }), + ), ); }); describe('Marketplace menu with role conditions', () => { it('should return the action if the user has admin role', async () => { - const { result, waitForValueToChange } = renderHook(() => useMarketPlaceMenu(), { + const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => [ { @@ -149,17 +154,18 @@ describe('Marketplace menu with role conditions', () => { }), ); - await waitForValueToChange(() => result.current[0].items[3]); - - expect(result.current[0].items[3]).toEqual( - expect.objectContaining({ - id: 'APP_ID_ACTION_ID', - }), + await waitFor(() => + expect(result.current[0]?.items[3]).toEqual( + expect.objectContaining({ + id: 'APP_ID_ACTION_ID', + }), + ), ); }); it('should return filter the action if the user doesn`t have admin role', async () => { const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => [ { @@ -206,7 +212,8 @@ describe('Marketplace menu with role conditions', () => { describe('Marketplace menu with permission conditions', () => { it('should return the action if the user has manage-apps permission', async () => { - const { result, waitForValueToChange } = renderHook(() => useMarketPlaceMenu(), { + const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => [ { @@ -241,17 +248,18 @@ describe('Marketplace menu with permission conditions', () => { }), ); - await waitForValueToChange(() => result.current[0].items[3]); - - expect(result.current[0].items[3]).toEqual( - expect.objectContaining({ - id: 'APP_ID_ACTION_ID', - }), + await waitFor(() => + expect(result.current[0].items[3]).toEqual( + expect.objectContaining({ + id: 'APP_ID_ACTION_ID', + }), + ), ); }); it('should return filter the action if the user doesn`t have `any` permission', async () => { const { result } = renderHook(() => useMarketPlaceMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/apps/actionButtons', () => [ { diff --git a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.spec.tsx b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.spec.tsx index 1315d1053392..ba100fe79783 100644 --- a/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.spec.tsx +++ b/apps/meteor/client/NavBarV2/NavBarSettingsToolbar/hooks/useAdministrationMenu.spec.tsx @@ -1,10 +1,11 @@ import { mockAppRoot } from '@rocket.chat/mock-providers'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useAdministrationMenu } from './useAdministrationMenu'; it('should return omnichannel item if has `view-livechat-manager` permission ', async () => { - const { result, waitFor } = renderHook(() => useAdministrationMenu(), { + const { result } = renderHook(() => useAdministrationMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ // @ts-expect-error this is a mock @@ -19,17 +20,18 @@ it('should return omnichannel item if has `view-livechat-manager` permission ', .build(), }); - await waitFor(() => !!result.current.length); - - expect(result.current[0].items[0]).toEqual( - expect.objectContaining({ - id: 'omnichannel', - }), + await waitFor(() => + expect(result.current[0]?.items[0]).toEqual( + expect.objectContaining({ + id: 'omnichannel', + }), + ), ); }); it('should show administration item if has at least one admin permission', async () => { - const { result, waitFor } = renderHook(() => useAdministrationMenu(), { + const { result } = renderHook(() => useAdministrationMenu(), { + legacyRoot: true, wrapper: mockAppRoot() .withEndpoint('GET', '/v1/licenses.info', () => ({ // @ts-expect-error this is a mock @@ -44,11 +46,11 @@ it('should show administration item if has at least one admin permission', async .build(), }); - await waitFor(() => !!result.current.length); - - expect(result.current[0].items[0]).toEqual( - expect.objectContaining({ - id: 'workspace', - }), + await waitFor(() => + expect(result.current[0]?.items[0]).toEqual( + expect.objectContaining({ + id: 'workspace', + }), + ), ); }); diff --git a/apps/meteor/client/components/Contextualbar/ContextualbarHeader.tsx b/apps/meteor/client/components/Contextualbar/ContextualbarHeader.tsx index 795182df8465..ebd92f0095e3 100644 --- a/apps/meteor/client/components/Contextualbar/ContextualbarHeader.tsx +++ b/apps/meteor/client/components/Contextualbar/ContextualbarHeader.tsx @@ -8,10 +8,10 @@ type ContextualbarHeaderProps = { children: ReactNode; } & ComponentPropsWithoutRef; -const ContextualbarHeader = (props: ContextualbarHeaderProps) => ( +const ContextualbarHeader = ({ expanded, ...props }: ContextualbarHeaderProps) => ( - + diff --git a/apps/meteor/client/components/FilterByText.tsx b/apps/meteor/client/components/FilterByText.tsx index 1aeeb29a0a57..5c5a3d599e2f 100644 --- a/apps/meteor/client/components/FilterByText.tsx +++ b/apps/meteor/client/components/FilterByText.tsx @@ -1,26 +1,13 @@ -import { Box, Icon, TextInput, Button, Margins } from '@rocket.chat/fuselage'; +import { Box, Icon, TextInput, Margins } from '@rocket.chat/fuselage'; import { useAutoFocus, useMergedRefs } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactNode, ChangeEvent, FormEvent } from 'react'; -import React, { forwardRef, memo, useCallback, useEffect, useState } from 'react'; +import type { ChangeEvent, FormEvent, HTMLAttributes } from 'react'; +import React, { forwardRef, memo, useCallback, useState } from 'react'; -type FilterByTextCommonProps = { - children?: ReactNode | undefined; - placeholder?: string; - onChange: (filter: { text: string }) => void; +type FilterByTextProps = { + onChange: (filter: string) => void; shouldAutoFocus?: boolean; -}; - -type FilterByTextPropsWithButton = FilterByTextCommonProps & { - displayButton: true; - textButton: string; - onButtonClick: () => void; -}; - -type FilterByTextProps = FilterByTextCommonProps | FilterByTextPropsWithButton; - -const isFilterByTextPropsWithButton = (props: any): props is FilterByTextPropsWithButton => - 'displayButton' in props && props.displayButton === true; +} & Omit, 'is' | 'onChange'>; const FilterByText = forwardRef(function FilterByText( { placeholder, onChange: setFilter, shouldAutoFocus = false, children, ...props }, @@ -31,13 +18,10 @@ const FilterByText = forwardRef(function Fi const autoFocusRef = useAutoFocus(shouldAutoFocus); const mergedRefs = useMergedRefs(ref, autoFocusRef); - const handleInputChange = useCallback((event: ChangeEvent) => { + const handleInputChange = (event: ChangeEvent) => { setText(event.currentTarget.value); - }, []); - - useEffect(() => { - setFilter({ text }); - }, [setFilter, text]); + setFilter(event.currentTarget.value); + }; const handleFormSubmit = useCallback((event: FormEvent) => { event.preventDefault(); @@ -47,6 +31,7 @@ const FilterByText = forwardRef(function Fi } @@ -57,13 +42,7 @@ const FilterByText = forwardRef(function Fi aria-label={placeholder ?? t('Search')} /> - {isFilterByTextPropsWithButton(props) ? ( - - ) : ( - children && {children} - )} + {children && {children}} ); }); diff --git a/apps/meteor/client/components/GenericMenu/GenericMenu.spec.tsx b/apps/meteor/client/components/GenericMenu/GenericMenu.spec.tsx index 99e62bac1a60..530bd1404dc7 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenu.spec.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenu.spec.tsx @@ -1,4 +1,3 @@ -import '@testing-library/jest-dom'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; @@ -32,28 +31,28 @@ const sections = [regular, danger]; describe('Room Actions Menu', () => { it('should render kebab menu with the list content', async () => { - render(); + render(, { legacyRoot: true }); - userEvent.click(screen.getByRole('button')); + await userEvent.click(screen.getByRole('button')); expect(await screen.findByText('Edit')).toBeInTheDocument(); expect(await screen.findByText('Delete')).toBeInTheDocument(); }); it('should have two different sections, regular and danger', async () => { - render(); + render(, { legacyRoot: true }); - userEvent.click(screen.getByRole('button')); + await userEvent.click(screen.getByRole('button')); expect(screen.getAllByRole('presentation')).toHaveLength(2); expect(screen.getByRole('separator')).toBeInTheDocument(); }); it('should call the action when item clicked', async () => { - render(); + render(, { legacyRoot: true }); - userEvent.click(screen.getByRole('button')); - userEvent.click(screen.getAllByRole('menuitem')[0]); + await userEvent.click(screen.getByRole('button')); + await userEvent.click(screen.getAllByRole('menuitem')[0]); expect(mockedFunction).toHaveBeenCalled(); }); diff --git a/apps/meteor/client/components/GenericModal/GenericModal.spec.tsx b/apps/meteor/client/components/GenericModal/GenericModal.spec.tsx index 0ef7235729c4..b47b6abf7b00 100644 --- a/apps/meteor/client/components/GenericModal/GenericModal.spec.tsx +++ b/apps/meteor/client/components/GenericModal/GenericModal.spec.tsx @@ -1,6 +1,5 @@ import { useSetModal } from '@rocket.chat/ui-contexts'; -import { act, screen } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { act, screen, renderHook } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { ReactElement } from 'react'; import React, { Suspense } from 'react'; @@ -8,12 +7,11 @@ import React, { Suspense } from 'react'; import ModalProviderWithRegion from '../../providers/ModalProvider/ModalProviderWithRegion'; import GenericModal from './GenericModal'; -import '@testing-library/jest-dom'; - const renderModal = (modalElement: ReactElement) => { const { result: { current: setModal }, } = renderHook(() => useSetModal(), { + legacyRoot: true, wrapper: ({ children }) => ( {children} @@ -34,11 +32,11 @@ describe('callbacks', () => { renderModal(); - expect(await screen.findByRole('heading', { name: 'Modal', exact: true })).toBeInTheDocument(); + expect(await screen.findByRole('heading', { name: 'Modal' })).toBeInTheDocument(); - userEvent.keyboard('{Escape}'); + await userEvent.keyboard('{Escape}'); - expect(screen.queryByRole('heading', { name: 'Modal', exact: true })).not.toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Modal' })).not.toBeInTheDocument(); expect(handleClose).toHaveBeenCalled(); }); @@ -49,9 +47,9 @@ describe('callbacks', () => { const { setModal } = renderModal(); - expect(await screen.findByRole('heading', { name: 'Modal', exact: true })).toBeInTheDocument(); + expect(await screen.findByRole('heading', { name: 'Modal' })).toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Ok', exact: true })); + await userEvent.click(screen.getByRole('button', { name: 'Ok' })); expect(handleConfirm).toHaveBeenCalled(); @@ -59,7 +57,7 @@ describe('callbacks', () => { setModal(null); }); - expect(screen.queryByRole('heading', { name: 'Modal', exact: true })).not.toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Modal' })).not.toBeInTheDocument(); expect(handleClose).not.toHaveBeenCalled(); }); @@ -70,9 +68,9 @@ describe('callbacks', () => { const { setModal } = renderModal(); - expect(await screen.findByRole('heading', { name: 'Modal', exact: true })).toBeInTheDocument(); + expect(await screen.findByRole('heading', { name: 'Modal' })).toBeInTheDocument(); - userEvent.click(screen.getByRole('button', { name: 'Cancel', exact: true })); + await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); expect(handleCancel).toHaveBeenCalled(); @@ -80,7 +78,7 @@ describe('callbacks', () => { setModal(null); }); - expect(screen.queryByRole('heading', { name: 'Modal', exact: true })).not.toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Modal' })).not.toBeInTheDocument(); expect(handleClose).not.toHaveBeenCalled(); }); diff --git a/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx b/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx index 9a2e7eac4c45..8db42e8c649d 100644 --- a/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx +++ b/apps/meteor/client/components/InfoPanel/RetentionPolicyCallout.spec.tsx @@ -1,26 +1,30 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; -import '@testing-library/jest-dom/extend-expect'; import { createRenteionPolicySettingsMock as createMock } from '../../../tests/mocks/client/mockRetentionPolicySettings'; import { createFakeRoom } from '../../../tests/mocks/data'; -import { setDate } from '../../../tests/mocks/mockDate'; import RetentionPolicyCallout from './RetentionPolicyCallout'; jest.useFakeTimers(); +beforeEach(() => { + jest.setSystemTime(new Date(2024, 5, 1, 0, 0, 0)); +}); + describe('RetentionPolicyCallout', () => { it('Should render callout if settings are valid', () => { - setDate(); const fakeRoom = createFakeRoom({ t: 'c' }); - render(, { wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }) }); + render(, { + legacyRoot: true, + wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000 }), + }); expect(screen.getByRole('alert')).toHaveTextContent('a minute June 1, 2024, 12:30 AM'); }); it('Should not render callout if settings are invalid', () => { - setDate(); const fakeRoom = createFakeRoom({ t: 'c' }); render(, { + legacyRoot: true, wrapper: createMock({ appliesToChannels: true, TTLChannels: 60000, advancedPrecisionCron: '* * * 12 *', advancedPrecision: true }), }); expect(screen.queryByRole('alert')).not.toBeInTheDocument(); diff --git a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.spec.tsx b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.spec.tsx new file mode 100644 index 000000000000..87f7f70fbfbb --- /dev/null +++ b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.spec.tsx @@ -0,0 +1,48 @@ +import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import TranscriptModal from './TranscriptModal'; + +const room = { + open: true, + v: { token: '1234567890' }, + transcriptRequest: { + email: 'example@example.com', + subject: 'Transcript of livechat conversation', + }, +} as IOmnichannelRoom; + +const defaultProps = { + room, + email: 'test@example.com', + onRequest: () => null, + onSend: () => null, + onCancel: () => null, + onDiscard: () => null, +}; + +it('should show Undo request button when roomOpen is true and transcriptRequest exist', async () => { + const onDiscardMock = jest.fn(); + render(, { legacyRoot: true }); + + const undoRequestButton = await screen.findByText('Undo_request'); + await userEvent.click(undoRequestButton); + + expect(onDiscardMock).toHaveBeenCalled(); +}); + +it('should show Request button when roomOpen is true and transcriptRequest not exist', async () => { + render(, { legacyRoot: true }); + + const requestBtn = await screen.findByRole('button', { name: 'request-button' }); + expect(requestBtn).toBeInTheDocument(); +}); + +it('should show Send button when roomOpen is false', async () => { + render(, { legacyRoot: true }); + + const sendBtn = await screen.findByRole('button', { name: 'send-button' }); + expect(sendBtn).toBeInTheDocument(); +}); diff --git a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx index 95bda1e89107..c06b6a190465 100644 --- a/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/TranscriptModal.tsx @@ -21,8 +21,7 @@ const TranscriptModal = ({ email: emailDefault = '', room, onRequest, onSend, on handleSubmit, setValue, setFocus, - watch, - formState: { errors, isValid, isSubmitting }, + formState: { errors, isSubmitting }, } = useForm({ defaultValues: { email: emailDefault || '', subject: t('Transcript_of_your_livechat_conversation') }, }); @@ -56,7 +55,7 @@ const TranscriptModal = ({ email: emailDefault = '', room, onRequest, onSend, on } }, [setValue, transcriptRequest]); - const canSubmit = isValid && Boolean(watch('subject')); + // const canSubmit = isValid && Boolean(watch('subject')); return ( } {...props}> @@ -103,12 +102,12 @@ const TranscriptModal = ({ email: emailDefault = '', room, onRequest, onSend, on )} {roomOpen && !transcriptRequest && ( - )} {!roomOpen && ( - )} diff --git a/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx b/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx index 1f12f29ee13b..3f61f421035c 100644 --- a/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx +++ b/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx @@ -1,4 +1,4 @@ -import { AutoComplete, Option, Box, Chip, Options } from '@rocket.chat/fuselage'; +import { AutoComplete, Option, Box, Chip } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { UserAvatar } from '@rocket.chat/ui-avatar'; import { useEndpoint } from '@rocket.chat/ui-contexts'; @@ -46,7 +46,7 @@ const UserAutoComplete = ({ value, onChange, ...props }: UserAutoCompleteProps): )} renderItem={({ value, label, ...props }): ReactElement => ( -