From e6bfb4eafb77e49bda704303d039d9a2337e90dd Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 20 Jun 2024 16:44:57 -0300 Subject: [PATCH 1/7] fix: Emoji picker's object and symbols category icon swapped (#32612) --- .changeset/red-cheetahs-heal.md | 5 +++++ .../views/composer/EmojiPicker/EmojiPickerCategoryItem.tsx | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/red-cheetahs-heal.md diff --git a/.changeset/red-cheetahs-heal.md b/.changeset/red-cheetahs-heal.md new file mode 100644 index 000000000000..5b9934203da0 --- /dev/null +++ b/.changeset/red-cheetahs-heal.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes a cosmetic issue where emoji picker object and symbols category icon are swapped diff --git a/apps/meteor/client/views/composer/EmojiPicker/EmojiPickerCategoryItem.tsx b/apps/meteor/client/views/composer/EmojiPicker/EmojiPickerCategoryItem.tsx index 7aea040c8721..701a3eb3872e 100644 --- a/apps/meteor/client/views/composer/EmojiPicker/EmojiPickerCategoryItem.tsx +++ b/apps/meteor/client/views/composer/EmojiPicker/EmojiPickerCategoryItem.tsx @@ -30,10 +30,10 @@ const mapCategoryIcon = (category: string) => { return 'airplane'; case 'objects': - return 'percentage'; + return 'lamp-bulb'; case 'symbols': - return 'lamp-bulb'; + return 'percentage'; case 'flags': return 'flag'; From fade5a2c822785831cf009d1a32576cdd6b57a25 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 20 Jun 2024 16:49:10 -0300 Subject: [PATCH 2/7] test: make playwright reporter less verbose by showing only errors (#32637) --- apps/meteor/reporters/rocketchat.ts | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/meteor/reporters/rocketchat.ts b/apps/meteor/reporters/rocketchat.ts index d27424a1874b..82d8b43431ac 100644 --- a/apps/meteor/reporters/rocketchat.ts +++ b/apps/meteor/reporters/rocketchat.ts @@ -20,7 +20,7 @@ class RocketChatReporter implements Reporter { this.run = options.run; } - onTestEnd(test: TestCase, result: TestResult) { + async onTestEnd(test: TestCase, result: TestResult) { if (process.env.REPORTER_ROCKETCHAT_REPORT !== 'true') { console.log('REPORTER_ROCKETCHAT_REPORT is not true, skipping', { draft: this.draft, @@ -36,15 +36,23 @@ class RocketChatReporter implements Reporter { draft: this.draft, run: this.run, }; - console.log(`Sending test result to Rocket.Chat: ${JSON.stringify(payload)}`); - return fetch(this.url, { - method: 'POST', - body: JSON.stringify(payload), - headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': this.apiKey, - }, - }); + + try { + const res = await fetch(this.url, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': this.apiKey, + }, + }); + + if (!res.ok) { + console.error('Error sending test result to Rocket.Chat', JSON.stringify(payload), res); + } + } catch (error) { + console.error('Unknown error while sending test result to Rocket.Chat', JSON.stringify(payload), error); + } } } From 8ac631da62a3349690e55092b2f0e23c01eac9a1 Mon Sep 17 00:00:00 2001 From: Ricardo Garim Date: Thu, 20 Jun 2024 17:52:24 -0300 Subject: [PATCH 3/7] refactor: Settings out of DB Watcher (#32518) Co-authored-by: Diego Sampaio --- apps/meteor/app/api/server/v1/misc.ts | 59 +++++++++---- apps/meteor/app/api/server/v1/settings.ts | 28 ++++-- .../app/apps/server/bridges/settings.ts | 9 +- apps/meteor/app/assets/server/assets.ts | 20 ++++- .../authentication/server/startup/index.js | 4 +- .../functions/getOAuthAuthorizationUrl.ts | 3 + .../functions/getWorkspaceAccessToken.ts | 11 ++- .../removeWorkspaceRegistrationInfo.ts | 35 +++++--- .../server/functions/saveRegistrationData.ts | 71 ++++++++------- .../functions/startRegisterWorkspace.ts | 3 +- .../supportedVersionsToken.ts | 3 +- .../syncWorkspace/legacySyncWorkspace.ts | 7 +- .../server/lib/RocketChat.ErrorHandler.ts | 9 +- .../federation/server/functions/helpers.ts | 4 +- .../app/importer-csv/server/CsvImporter.ts | 6 +- .../server/HipChatEnterpriseImporter.js | 7 +- .../server/SlackUsersImporter.ts | 8 +- .../importer-slack/server/SlackImporter.ts | 6 +- .../app/importer/server/classes/Importer.ts | 19 +++- .../server/functions/sendInvitationEmail.ts | 7 +- .../meteor/app/irc/server/irc-bridge/index.js | 17 ++-- .../irc/server/methods/resetIrcConnection.ts | 31 ++----- .../app/lib/server/lib/notifyListener.ts | 34 +++++-- .../lib/server/methods/removeOAuthService.ts | 74 +++++++++------- .../app/lib/server/methods/saveSetting.ts | 6 +- .../app/lib/server/methods/saveSettings.ts | 9 +- .../imports/server/rest/appearance.ts | 13 +-- .../app/livechat/server/api/v1/integration.ts | 76 +++++++--------- .../app/livechat/server/api/v1/videoCall.ts | 14 ++- .../app/livechat/server/lib/QueueManager.ts | 7 +- .../livechat/server/methods/saveAppearance.ts | 14 +-- .../server/methods/saveIntegration.ts | 88 ++++++++++--------- apps/meteor/app/mailer/server/api.ts | 6 +- .../server/functions/updateStatsCounter.ts | 8 +- .../functions/buildVersionUpdateMessage.ts | 4 +- apps/meteor/ee/app/license/server/startup.ts | 38 +++++--- apps/meteor/ee/server/api/licenses.ts | 4 +- apps/meteor/ee/server/startup/upsell.ts | 7 +- apps/meteor/server/cron/federation.ts | 3 +- .../server/database/watchCollections.ts | 2 +- apps/meteor/server/lib/settingsRegenerator.ts | 1 + .../meteor/server/models/raw/LivechatRooms.ts | 14 +-- .../server/models/raw/LivechatVisitors.ts | 18 ++-- apps/meteor/server/models/raw/Settings.ts | 28 ++++-- .../rocket-chat/adapters/Settings.ts | 4 +- apps/meteor/server/settings/misc.ts | 8 +- .../server/startup/cloudRegistration.ts | 5 +- apps/meteor/server/startup/initialData.js | 11 ++- .../tests/e2e/fixtures/inject-initial-data.ts | 16 ---- apps/meteor/tests/e2e/saml.spec.ts | 58 ++++-------- .../src/models/ISettingsModel.ts | 14 ++- 51 files changed, 568 insertions(+), 383 deletions(-) diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index bdf6fa2dd1c6..dd4da47bff05 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -24,6 +24,7 @@ import { SystemLogger } from '../../../../server/lib/logger/system'; import { getLogs } from '../../../../server/stream/stdout'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { passwordPolicy } from '../../../lib/server'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields'; import { isSMTPConfigured } from '../../../utils/server/functions/isSMTPConfigured'; @@ -687,27 +688,49 @@ API.v1.addRoute( setDeploymentAs: String, }); + const settingsIds: string[] = []; + if (this.bodyParams.setDeploymentAs === 'new-workspace') { - await Promise.all([ - Settings.resetValueById('uniqueID', process.env.DEPLOYMENT_ID || uuidv4()), - // Settings.resetValueById('Cloud_Url'), - Settings.resetValueById('Cloud_Service_Agree_PrivacyTerms'), - Settings.resetValueById('Cloud_Workspace_Id'), - Settings.resetValueById('Cloud_Workspace_Name'), - Settings.resetValueById('Cloud_Workspace_Client_Id'), - Settings.resetValueById('Cloud_Workspace_Client_Secret'), - Settings.resetValueById('Cloud_Workspace_Client_Secret_Expires_At'), - Settings.resetValueById('Cloud_Workspace_Registration_Client_Uri'), - Settings.resetValueById('Cloud_Workspace_PublicKey'), - Settings.resetValueById('Cloud_Workspace_License'), - Settings.resetValueById('Cloud_Workspace_Had_Trial'), - Settings.resetValueById('Cloud_Workspace_Access_Token'), - Settings.resetValueById('Cloud_Workspace_Access_Token_Expires_At', new Date(0)), - Settings.resetValueById('Cloud_Workspace_Registration_State'), - ]); + settingsIds.push( + 'Cloud_Service_Agree_PrivacyTerms', + 'Cloud_Workspace_Id', + 'Cloud_Workspace_Name', + 'Cloud_Workspace_Client_Id', + 'Cloud_Workspace_Client_Secret', + 'Cloud_Workspace_Client_Secret_Expires_At', + 'Cloud_Workspace_Registration_Client_Uri', + 'Cloud_Workspace_PublicKey', + 'Cloud_Workspace_License', + 'Cloud_Workspace_Had_Trial', + 'Cloud_Workspace_Access_Token', + 'uniqueID', + 'Cloud_Workspace_Access_Token_Expires_At', + ); } - await Settings.updateValueById('Deployment_FingerPrint_Verified', true); + settingsIds.push('Deployment_FingerPrint_Verified'); + + const promises = settingsIds.map((settingId) => { + if (settingId === 'uniqueID') { + return Settings.resetValueById('uniqueID', process.env.DEPLOYMENT_ID || uuidv4()); + } + + if (settingId === 'Cloud_Workspace_Access_Token_Expires_At') { + return Settings.resetValueById('Cloud_Workspace_Access_Token_Expires_At', new Date(0)); + } + + if (settingId === 'Deployment_FingerPrint_Verified') { + return Settings.updateValueById('Deployment_FingerPrint_Verified', true); + } + + return Settings.resetValueById(settingId); + }); + + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settingsIds[index]); + } + }); return API.v1.success({}); }, diff --git a/apps/meteor/app/api/server/v1/settings.ts b/apps/meteor/app/api/server/v1/settings.ts index bccfc8d91fc7..574f4ee64194 100644 --- a/apps/meteor/app/api/server/v1/settings.ts +++ b/apps/meteor/app/api/server/v1/settings.ts @@ -13,6 +13,7 @@ import type { FindOptions } from 'mongodb'; import _ from 'underscore'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { notifyOnSettingChanged, notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { SettingsEvents, settings } from '../../../settings/server'; import { setValue } from '../../../settings/server/raw'; import { API } from '../api'; @@ -186,23 +187,34 @@ API.v1.addRoute( } if (isSettingColor(setting) && isSettingsUpdatePropsColor(this.bodyParams)) { - await Settings.updateOptionsById(this.urlParams._id, { - editor: this.bodyParams.editor, - }); - await Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value); + const updateOptionsPromise = Settings.updateOptionsById(this.urlParams._id, { editor: this.bodyParams.editor }); + const updateValuePromise = Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value); + + const [updateOptionsResult, updateValueResult] = await Promise.all([updateOptionsPromise, updateValuePromise]); + + if (updateOptionsResult.modifiedCount || updateValueResult.modifiedCount) { + await notifyOnSettingChangedById(this.urlParams._id); + } + return API.v1.success(); } - if ( - isSettingsUpdatePropDefault(this.bodyParams) && - (await Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value)) - ) { + if (isSettingsUpdatePropDefault(this.bodyParams)) { + const { matchedCount } = await Settings.updateValueNotHiddenById(this.urlParams._id, this.bodyParams.value); + if (!matchedCount) { + return API.v1.failure(); + } + const s = await Settings.findOneNotHiddenById(this.urlParams._id); if (!s) { return API.v1.failure(); } + settings.set(s); setValue(this.urlParams._id, this.bodyParams.value); + + await notifyOnSettingChanged(s); + return API.v1.success(); } diff --git a/apps/meteor/app/apps/server/bridges/settings.ts b/apps/meteor/app/apps/server/bridges/settings.ts index e90171813df8..37803d4f94f3 100644 --- a/apps/meteor/app/apps/server/bridges/settings.ts +++ b/apps/meteor/app/apps/server/bridges/settings.ts @@ -3,6 +3,8 @@ import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { ServerSettingBridge } from '@rocket.chat/apps-engine/server/bridges/ServerSettingBridge'; import { Settings } from '@rocket.chat/models'; +import { notifyOnSettingChanged, notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; + export class AppSettingBridge extends ServerSettingBridge { constructor(private readonly orch: IAppServerOrchestrator) { super(); @@ -54,7 +56,7 @@ export class AppSettingBridge extends ServerSettingBridge { throw new Error(`The setting "${setting.id}" is not readable.`); } - await Settings.updateValueById(setting.id, setting.value); + (await Settings.updateValueById(setting.id, setting.value)).modifiedCount && void notifyOnSettingChangedById(setting.id); } protected async incrementValue(id: string, value: number, appId: string): Promise { @@ -64,6 +66,9 @@ export class AppSettingBridge extends ServerSettingBridge { throw new Error(`The setting "${id}" is not readable.`); } - await Settings.incrementValueById(id, value); + const { value: setting } = await Settings.incrementValueById(id, value, { returnDocument: 'after' }); + if (setting) { + void notifyOnSettingChanged(setting); + } } } diff --git a/apps/meteor/app/assets/server/assets.ts b/apps/meteor/app/assets/server/assets.ts index 0acba139d5f4..b9653a2f1b9a 100644 --- a/apps/meteor/app/assets/server/assets.ts +++ b/apps/meteor/app/assets/server/assets.ts @@ -13,6 +13,7 @@ import sharp from 'sharp'; import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission'; import { RocketChatFile } from '../../file/server'; import { methodDeprecationLogger } from '../../lib/server/lib/deprecationWarningLogger'; +import { notifyOnSettingChangedById } from '../../lib/server/lib/notifyListener'; import { settings, settingsRegistry } from '../../settings/server'; import { getExtension } from '../../utils/lib/mimeTypes'; import { getURL } from '../../utils/server/getURL'; @@ -261,7 +262,13 @@ class RocketChatAssetsClass { defaultUrl: assetInstance.defaultUrl, }; - void Settings.updateValueById(key, value); + void (async () => { + const { modifiedCount } = await Settings.updateValueById(key, value); + if (modifiedCount) { + void notifyOnSettingChangedById(key); + } + })(); + return RocketChatAssets.processAsset(key, value); }, 200); }); @@ -282,7 +289,13 @@ class RocketChatAssetsClass { defaultUrl: getAssetByKey(asset).defaultUrl, }; - void Settings.updateValueById(key, value); + void (async () => { + const { modifiedCount } = await Settings.updateValueById(key, value); + if (modifiedCount) { + void notifyOnSettingChangedById(key); + } + })(); + await RocketChatAssets.processAsset(key, value); } @@ -371,7 +384,8 @@ export async function addAssetToSetting(asset: string, value: IRocketChatAsset, if (currentValue && typeof currentValue === 'object' && currentValue.defaultUrl !== getAssetByKey(asset).defaultUrl) { currentValue.defaultUrl = getAssetByKey(asset).defaultUrl; - await Settings.updateValueById(key, currentValue); + + (await Settings.updateValueById(key, currentValue)).modifiedCount && void notifyOnSettingChangedById(key); } } diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index cc5a04c275b7..bffbe1f9876d 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -19,6 +19,7 @@ import { getNewUserRoles } from '../../../../server/services/user/lib/getNewUser import { getAvatarSuggestionForUser } from '../../../lib/server/functions/getAvatarSuggestionForUser'; import { joinDefaultChannels } from '../../../lib/server/functions/joinDefaultChannels'; import { setAvatarFromServiceWithValidation } from '../../../lib/server/functions/setUserAvatar'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; import { safeGetMeteorUser } from '../../../utils/server/functions/safeGetMeteorUser'; @@ -323,7 +324,8 @@ const insertUserDocAsync = async function (options, user) { if (!roles.includes('admin') && !hasAdmin) { roles.push('admin'); if (settings.get('Show_Setup_Wizard') === 'pending') { - await Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); + (await Settings.updateValueById('Show_Setup_Wizard', 'in_progress')).modifiedCount && + void notifyOnSettingChangedById('Show_Setup_Wizard'); } } } diff --git a/apps/meteor/app/cloud/server/functions/getOAuthAuthorizationUrl.ts b/apps/meteor/app/cloud/server/functions/getOAuthAuthorizationUrl.ts index 14cb2f4a57ce..0550a3d7f238 100644 --- a/apps/meteor/app/cloud/server/functions/getOAuthAuthorizationUrl.ts +++ b/apps/meteor/app/cloud/server/functions/getOAuthAuthorizationUrl.ts @@ -1,6 +1,7 @@ import { Settings } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { userScopes } from '../oauthScopes'; import { getRedirectUri } from './getRedirectUri'; @@ -10,6 +11,8 @@ export async function getOAuthAuthorizationUrl() { await Settings.updateValueById('Cloud_Workspace_Registration_State', state); + void notifyOnSettingChangedById('Cloud_Workspace_Registration_State'); + const cloudUrl = settings.get('Cloud_Url'); const clientId = settings.get('Cloud_Workspace_Client_Id'); const redirectUri = getRedirectUri(); diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts index 7e970edfdfc2..1ea20812c062 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceAccessToken.ts @@ -1,5 +1,6 @@ import { Settings } from '@rocket.chat/models'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { getWorkspaceAccessTokenWithScope } from './getWorkspaceAccessTokenWithScope'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; @@ -32,11 +33,13 @@ export async function getWorkspaceAccessToken(forceNew = false, scope = '', save const accessToken = await getWorkspaceAccessTokenWithScope(scope, throwOnError); if (save) { - await Promise.all([ - Settings.updateValueById('Cloud_Workspace_Access_Token', accessToken.token), - Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', accessToken.expiresAt), - ]); + (await Settings.updateValueById('Cloud_Workspace_Access_Token', accessToken.token)).modifiedCount && + void notifyOnSettingChangedById('Cloud_Workspace_Access_Token'); + + (await Settings.updateValueById('Cloud_Workspace_Access_Token_Expires_At', accessToken.expiresAt)).modifiedCount && + void notifyOnSettingChangedById('Cloud_Workspace_Access_Token_Expires_At'); } + return accessToken.token; } diff --git a/apps/meteor/app/cloud/server/functions/removeWorkspaceRegistrationInfo.ts b/apps/meteor/app/cloud/server/functions/removeWorkspaceRegistrationInfo.ts index b4e5362f5ac7..45e1738e11e6 100644 --- a/apps/meteor/app/cloud/server/functions/removeWorkspaceRegistrationInfo.ts +++ b/apps/meteor/app/cloud/server/functions/removeWorkspaceRegistrationInfo.ts @@ -1,5 +1,6 @@ import { Settings } from '@rocket.chat/models'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; export async function removeWorkspaceRegistrationInfo() { @@ -8,16 +9,30 @@ export async function removeWorkspaceRegistrationInfo() { return true; } - await Promise.all([ - Settings.resetValueById('Cloud_Workspace_Id', null), - Settings.resetValueById('Cloud_Workspace_Name', null), - Settings.resetValueById('Cloud_Workspace_Client_Id', null), - Settings.resetValueById('Cloud_Workspace_Client_Secret', null), - Settings.resetValueById('Cloud_Workspace_Client_Secret_Expires_At', null), - Settings.resetValueById('Cloud_Workspace_PublicKey', null), - Settings.resetValueById('Cloud_Workspace_Registration_Client_Uri', null), - ]); + const settingsIds = [ + 'Cloud_Workspace_Id', + 'Cloud_Workspace_Name', + 'Cloud_Workspace_Client_Id', + 'Cloud_Workspace_Client_Secret', + 'Cloud_Workspace_Client_Secret_Expires_At', + 'Cloud_Workspace_PublicKey', + 'Cloud_Workspace_Registration_Client_Uri', + 'Show_Setup_Wizard', + ]; + + const promises = settingsIds.map((settingId) => { + if (settingId === 'Show_Setup_Wizard') { + return Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); + } + + return Settings.resetValueById(settingId, null); + }); + + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settingsIds[index]); + } + }); - await Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); return true; } diff --git a/apps/meteor/app/cloud/server/functions/saveRegistrationData.ts b/apps/meteor/app/cloud/server/functions/saveRegistrationData.ts index cb2d19cfb92e..d92273302442 100644 --- a/apps/meteor/app/cloud/server/functions/saveRegistrationData.ts +++ b/apps/meteor/app/cloud/server/functions/saveRegistrationData.ts @@ -1,6 +1,7 @@ import { applyLicense } from '@rocket.chat/license'; import { Settings } from '@rocket.chat/models'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { syncCloudData } from './syncWorkspace/syncCloudData'; @@ -33,7 +34,8 @@ export async function saveRegistrationData({ await syncCloudData(); } -function saveRegistrationDataBase({ + +async function saveRegistrationDataBase({ workspaceId, client_name, client_id, @@ -50,38 +52,47 @@ function saveRegistrationDataBase({ publicKey: string; registration_client_uri: string; }) { - return Promise.all([ - Settings.updateValueById('Register_Server', true), - Settings.updateValueById('Cloud_Workspace_Id', workspaceId), - Settings.updateValueById('Cloud_Workspace_Name', client_name), - Settings.updateValueById('Cloud_Workspace_Client_Id', client_id), - Settings.updateValueById('Cloud_Workspace_Client_Secret', client_secret), - Settings.updateValueById('Cloud_Workspace_Client_Secret_Expires_At', client_secret_expires_at), - Settings.updateValueById('Cloud_Workspace_PublicKey', publicKey), - Settings.updateValueById('Cloud_Workspace_Registration_Client_Uri', registration_client_uri), - ]).then(async (...results) => { - // wait until all the settings are updated before syncing the data - for await (const retry of Array.from({ length: 10 })) { - if ( - settings.get('Register_Server') === true && - settings.get('Cloud_Workspace_Id') === workspaceId && - settings.get('Cloud_Workspace_Name') === client_name && - settings.get('Cloud_Workspace_Client_Id') === client_id && - settings.get('Cloud_Workspace_Client_Secret') === client_secret && - settings.get('Cloud_Workspace_Client_Secret_Expires_At') === client_secret_expires_at && - settings.get('Cloud_Workspace_PublicKey') === publicKey && - settings.get('Cloud_Workspace_Registration_Client_Uri') === registration_client_uri - ) { - break; - } + const settingsData = [ + { _id: 'Register_Server', value: true }, + { _id: 'Cloud_Workspace_Id', value: workspaceId }, + { _id: 'Cloud_Workspace_Name', value: client_name }, + { _id: 'Cloud_Workspace_Client_Id', value: client_id }, + { _id: 'Cloud_Workspace_Client_Secret', value: client_secret }, + { _id: 'Cloud_Workspace_Client_Secret_Expires_At', value: client_secret_expires_at }, + { _id: 'Cloud_Workspace_PublicKey', value: publicKey }, + { _id: 'Cloud_Workspace_Registration_Client_Uri', value: registration_client_uri }, + ]; + + const promises = settingsData.map(({ _id, value }) => Settings.updateValueById(_id, value)); - if (retry === 9) { - throw new Error('Failed to save registration data'); - } - await new Promise((resolve) => setTimeout(resolve, 1000)); + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settingsData[index]._id); } - return results; }); + + // TODO: Why is this taking so long that needs a timeout? + for await (const retry of Array.from({ length: 10 })) { + const isSettingsUpdated = + settings.get('Register_Server') === true && + settings.get('Cloud_Workspace_Id') === workspaceId && + settings.get('Cloud_Workspace_Name') === client_name && + settings.get('Cloud_Workspace_Client_Id') === client_id && + settings.get('Cloud_Workspace_Client_Secret') === client_secret && + settings.get('Cloud_Workspace_Client_Secret_Expires_At') === client_secret_expires_at && + settings.get('Cloud_Workspace_PublicKey') === publicKey && + settings.get('Cloud_Workspace_Registration_Client_Uri') === registration_client_uri; + + if (isSettingsUpdated) { + return; + } + + if (retry === 9) { + throw new Error('Failed to save registration data'); + } + + await new Promise((resolve) => setTimeout(resolve, 1000)); + } } export async function saveRegistrationDataManual({ diff --git a/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts b/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts index 5f5df80d0d3d..1fb2dcc06449 100644 --- a/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts +++ b/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts @@ -2,6 +2,7 @@ import { Settings } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; import { SystemLogger } from '../../../../server/lib/logger/system'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { buildWorkspaceRegistrationData } from './buildRegistrationData'; import { retrieveRegistrationStatus } from './retrieveRegistrationStatus'; @@ -15,7 +16,7 @@ export async function startRegisterWorkspace(resend = false) { return true; } - await Settings.updateValueById('Register_Server', true); + (await Settings.updateValueById('Register_Server', true)).modifiedCount && void notifyOnSettingChangedById('Register_Server'); const regInfo = await buildWorkspaceRegistrationData(undefined); diff --git a/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts b/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts index a543c0681f38..f4334bd04d64 100644 --- a/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts +++ b/apps/meteor/app/cloud/server/functions/supportedVersionsToken/supportedVersionsToken.ts @@ -6,6 +6,7 @@ import type { Response } from '@rocket.chat/server-fetch'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; import { SystemLogger } from '../../../../../server/lib/logger/system'; +import { notifyOnSettingChangedById } from '../../../../lib/server/lib/notifyListener'; import { settings } from '../../../../settings/server'; import { supportedVersions as supportedVersionsFromBuild } from '../../../../utils/rocketchat-supported-versions.info'; import { buildVersionUpdateMessage } from '../../../../version-check/server/functions/buildVersionUpdateMessage'; @@ -65,7 +66,7 @@ const cacheValueInSettings = ( SystemLogger.debug(`Resetting cached value ${key} in settings`); const value = await fn(); - await Settings.updateValueById(key, value); + (await Settings.updateValueById(key, value)).modifiedCount && void notifyOnSettingChangedById(key); return value; }; diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts index 91202d973170..f2e66bfdf9f7 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/legacySyncWorkspace.ts @@ -5,6 +5,7 @@ import { v, compile } from 'suretype'; import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; +import { notifyOnSettingChangedById } from '../../../../lib/server/lib/notifyListener'; import { settings } from '../../../../settings/server'; import type { WorkspaceRegistrationData } from '../buildRegistrationData'; import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; @@ -126,11 +127,13 @@ const fetchWorkspaceClientPayload = async ({ /** @deprecated */ const consumeWorkspaceSyncPayload = async (result: Serialized) => { if (result.publicKey) { - await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey); + (await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey)).modifiedCount && + void notifyOnSettingChangedById('Cloud_Workspace_PublicKey'); } if (result.trial?.trialID) { - await Settings.updateValueById('Cloud_Workspace_Had_Trial', true); + (await Settings.updateValueById('Cloud_Workspace_Had_Trial', true)).modifiedCount && + void notifyOnSettingChangedById('Cloud_Workspace_Had_Trial'); } // add banners diff --git a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts index 99a7497f13c7..264443a1378b 100644 --- a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts +++ b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts @@ -3,10 +3,17 @@ import { Meteor } from 'meteor/meteor'; import { throttledCounter } from '../../../../lib/utils/throttledCounter'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; +import { notifyOnSettingChanged } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; const incException = throttledCounter((counter) => { - Settings.incrementValueById('Uncaught_Exceptions_Count', counter).catch(console.error); + Settings.incrementValueById('Uncaught_Exceptions_Count', counter, { returnDocument: 'after' }) + .then(({ value }) => { + if (value) { + void notifyOnSettingChanged(value); + } + }) + .catch(console.error); }, 10000); class ErrorHandler { diff --git a/apps/meteor/app/federation/server/functions/helpers.ts b/apps/meteor/app/federation/server/functions/helpers.ts index 3b9090311e01..c684b7b8f74a 100644 --- a/apps/meteor/app/federation/server/functions/helpers.ts +++ b/apps/meteor/app/federation/server/functions/helpers.ts @@ -2,6 +2,7 @@ import { isDirectMessageRoom } from '@rocket.chat/core-typings'; import type { ISubscription, IUser, IRoom } from '@rocket.chat/core-typings'; import { Settings, Users, Subscriptions } from '@rocket.chat/models'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { STATUS_ENABLED, STATUS_REGISTERING } from '../constants'; export const getNameAndDomain = (fullyQualifiedName: string): string[] => fullyQualifiedName.split('@'); @@ -14,11 +15,12 @@ export async function isRegisteringOrEnabled(): Promise { } export async function updateStatus(status: string): Promise { + // No need to call ws listener because current function is called on startup await Settings.updateValueById('FEDERATION_Status', status); } export async function updateEnabled(enabled: boolean): Promise { - await Settings.updateValueById('FEDERATION_Enabled', enabled); + (await Settings.updateValueById('FEDERATION_Enabled', enabled)).modifiedCount && void notifyOnSettingChangedById('FEDERATION_Enabled'); } export const checkRoomType = (room: IRoom): boolean => room.t === 'p' || room.t === 'd'; diff --git a/apps/meteor/app/importer-csv/server/CsvImporter.ts b/apps/meteor/app/importer-csv/server/CsvImporter.ts index 302aeb882ac5..60c07c3288ce 100644 --- a/apps/meteor/app/importer-csv/server/CsvImporter.ts +++ b/apps/meteor/app/importer-csv/server/CsvImporter.ts @@ -7,6 +7,7 @@ import { Importer, ProgressStep, ImporterWebsocket } from '../../importer/server import type { IConverterOptions } from '../../importer/server/classes/ImportDataConverter'; import type { ImporterProgress } from '../../importer/server/classes/ImporterProgress'; import type { ImporterInfo } from '../../importer/server/definitions/ImporterInfo'; +import { notifyOnSettingChanged } from '../../lib/server/lib/notifyListener'; export class CsvImporter extends Importer { private csvParser: (csv: string) => string[]; @@ -236,7 +237,10 @@ export class CsvImporter extends Importer { } if (usersCount) { - await Settings.incrementValueById('CSV_Importer_Count', usersCount); + const { value } = await Settings.incrementValueById('CSV_Importer_Count', usersCount, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } } // Check if any of the message usernames was not in the imported list of users diff --git a/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js b/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js index ac3d278d82ab..663300e44154 100644 --- a/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js +++ b/apps/meteor/app/importer-hipchat-enterprise/server/HipChatEnterpriseImporter.js @@ -6,6 +6,7 @@ import { Settings } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { Importer, ProgressStep } from '../../importer/server'; +import { notifyOnSettingChanged } from '../../lib/server/lib/notifyListener'; /** @deprecated HipChat was discontinued at 2019-02-15 */ export class HipChatEnterpriseImporter extends Importer { @@ -54,7 +55,11 @@ export class HipChatEnterpriseImporter extends Importer { this.converter.addUser(newUser); } - await Settings.incrementValueById('Hipchat_Enterprise_Importer_Count', count); + const { value } = await Settings.incrementValueById('Hipchat_Enterprise_Importer_Count', count, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } + await super.updateRecord({ 'count.users': count }); await super.addCountToTotal(count); } diff --git a/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts b/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts index 2c26531bd5c4..95461820bf2d 100644 --- a/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts +++ b/apps/meteor/app/importer-slack-users/server/SlackUsersImporter.ts @@ -9,6 +9,7 @@ import { Importer, ProgressStep } from '../../importer/server'; import type { IConverterOptions } from '../../importer/server/classes/ImportDataConverter'; import type { ImporterProgress } from '../../importer/server/classes/ImporterProgress'; import type { ImporterInfo } from '../../importer/server/definitions/ImporterInfo'; +import { notifyOnSettingChanged } from '../../lib/server/lib/notifyListener'; export class SlackUsersImporter extends Importer { private csvParser: (csv: string) => string[]; @@ -93,7 +94,12 @@ export class SlackUsersImporter extends Importer { await super.updateProgress(ProgressStep.USER_SELECTION); await super.addCountToTotal(userCount); - await Settings.incrementValueById('Slack_Users_Importer_Count', userCount); + + const { value } = await Settings.incrementValueById('Slack_Users_Importer_Count', userCount, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } + await super.updateRecord({ 'count.users': userCount }); return super.getProgress(); } diff --git a/apps/meteor/app/importer-slack/server/SlackImporter.ts b/apps/meteor/app/importer-slack/server/SlackImporter.ts index 0ef81c69a1e0..344db6656531 100644 --- a/apps/meteor/app/importer-slack/server/SlackImporter.ts +++ b/apps/meteor/app/importer-slack/server/SlackImporter.ts @@ -4,6 +4,7 @@ import type { IZipEntry } from 'adm-zip'; import { Importer, ProgressStep, ImporterWebsocket } from '../../importer/server'; import type { ImporterProgress } from '../../importer/server/classes/ImporterProgress'; +import { notifyOnSettingChanged } from '../../lib/server/lib/notifyListener'; import { MentionsParser } from '../../mentions/lib/MentionsParser'; import { settings } from '../../settings/server'; import { getUserAvatarURL } from '../../utils/server/getUserAvatarURL'; @@ -337,7 +338,10 @@ export class SlackImporter extends Importer { } if (userCount) { - await Settings.incrementValueById('Slack_Importer_Count', userCount); + const { value } = await Settings.incrementValueById('Slack_Importer_Count', userCount, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } } const missedTypes: Record = {}; diff --git a/apps/meteor/app/importer/server/classes/Importer.ts b/apps/meteor/app/importer/server/classes/Importer.ts index 68a12513a06c..846f9ef4b4f5 100644 --- a/apps/meteor/app/importer/server/classes/Importer.ts +++ b/apps/meteor/app/importer/server/classes/Importer.ts @@ -7,6 +7,7 @@ import type { MatchKeysAndValues, MongoServerError } from 'mongodb'; import { Selection, SelectionChannel, SelectionUser } from '..'; import { callbacks } from '../../../../lib/callbacks'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { t } from '../../../utils/lib/i18n'; import { ProgressStep, ImportPreparingStartedStates } from '../../lib/ImporterProgressStep'; import type { ImporterInfo } from '../definitions/ImporterInfo'; @@ -245,10 +246,20 @@ export class Importer { } async applySettingValues(settingValues: OldSettings) { - await Settings.updateValueById('Accounts_AllowUsernameChange', settingValues.allowUsernameChange ?? true); - await Settings.updateValueById('FileUpload_MaxFileSize', settingValues.maxFileSize ?? -1); - await Settings.updateValueById('FileUpload_MediaTypeWhiteList', settingValues.mediaTypeWhiteList ?? '*'); - await Settings.updateValueById('FileUpload_MediaTypeBlackList', settingValues.mediaTypeBlackList ?? ''); + const settingsIds = [ + { _id: 'Accounts_AllowUsernameChange', value: settingValues.allowUsernameChange ?? true }, + { _id: 'FileUpload_MaxFileSize', value: settingValues.maxFileSize ?? -1 }, + { _id: 'FileUpload_MediaTypeWhiteList', value: settingValues.mediaTypeWhiteList ?? '*' }, + { _id: 'FileUpload_MediaTypeBlackList', value: settingValues.mediaTypeBlackList ?? '' }, + ]; + + const promises = settingsIds.map((setting) => Settings.updateValueById(setting._id, setting.value)); + + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settingsIds[index]._id); + } + }); } getProgress(): ImporterProgress { diff --git a/apps/meteor/app/invites/server/functions/sendInvitationEmail.ts b/apps/meteor/app/invites/server/functions/sendInvitationEmail.ts index 1c00671ae41d..87b8133e3491 100644 --- a/apps/meteor/app/invites/server/functions/sendInvitationEmail.ts +++ b/apps/meteor/app/invites/server/functions/sendInvitationEmail.ts @@ -3,6 +3,7 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { notifyOnSettingChanged } from '../../../lib/server/lib/notifyListener'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; @@ -53,7 +54,11 @@ export const sendInvitationEmail = async (userId: string, emails: string[]) => { }, }); - await Settings.incrementValueById('Invitation_Email_Count'); + const { value } = await Settings.incrementValueById('Invitation_Email_Count', 1, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } + continue; } catch ({ message }: any) { throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${message}`, { diff --git a/apps/meteor/app/irc/server/irc-bridge/index.js b/apps/meteor/app/irc/server/irc-bridge/index.js index 9ab6c47987d0..09b7a3568362 100644 --- a/apps/meteor/app/irc/server/irc-bridge/index.js +++ b/apps/meteor/app/irc/server/irc-bridge/index.js @@ -7,6 +7,7 @@ import { callbacks } from '../../../../lib/callbacks'; import { afterLeaveRoomCallback } from '../../../../lib/callbacks/afterLeaveRoomCallback'; import { afterLogoutCleanUpCallback } from '../../../../lib/callbacks/afterLogoutCleanUpCallback'; import { withThrottling } from '../../../../lib/utils/highOrderFunctions'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import * as servers from '../servers'; import * as localCommandHandlers from './localHandlers'; import * as peerCommandHandlers from './peerHandlers'; @@ -19,15 +20,13 @@ const updateLastPing = withThrottling({ wait: 10_000 })(() => { if (removed) { return; } - void Settings.updateOne( - { _id: 'IRC_Bridge_Last_Ping' }, - { - $set: { - value: new Date(), - }, - }, - { upsert: true }, - ); + + void (async () => { + const updatedValue = await Settings.updateValueById('IRC_Bridge_Last_Ping', new Date(), { upsert: true }); + if (updatedValue.modifiedCount || updatedValue.upsertedCount) { + void notifyOnSettingChangedById('IRC_Bridge_Last_Ping'); + } + })(); }); class Bridge { diff --git a/apps/meteor/app/irc/server/methods/resetIrcConnection.ts b/apps/meteor/app/irc/server/methods/resetIrcConnection.ts index 1bbc5b6a3ef8..24eef975d5d5 100644 --- a/apps/meteor/app/irc/server/methods/resetIrcConnection.ts +++ b/apps/meteor/app/irc/server/methods/resetIrcConnection.ts @@ -2,6 +2,7 @@ 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'; @@ -16,29 +17,15 @@ Meteor.methods({ async resetIrcConnection() { const ircEnabled = Boolean(settings.get('IRC_Enabled')); - await Settings.updateOne( - { _id: 'IRC_Bridge_Last_Ping' }, - { - $set: { - value: new Date(0), - }, - }, - { - upsert: true, - }, - ); + const updatedLastPingValue = await Settings.updateValueById('IRC_Bridge_Last_Ping', new Date(0), { upsert: true }); + if (updatedLastPingValue.modifiedCount || updatedLastPingValue.upsertedCount) { + void notifyOnSettingChangedById('IRC_Bridge_Last_Ping'); + } - await Settings.updateOne( - { _id: 'IRC_Bridge_Reset_Time' }, - { - $set: { - value: new Date(), - }, - }, - { - upsert: true, - }, - ); + const updatedResetTimeValue = await Settings.updateValueById('IRC_Bridge_Reset_Time', new Date(), { upsert: true }); + if (updatedResetTimeValue.modifiedCount || updatedResetTimeValue.upsertedCount) { + void notifyOnSettingChangedById('IRC_Bridge_Last_Ping'); + } if (!ircEnabled) { return { diff --git a/apps/meteor/app/lib/server/lib/notifyListener.ts b/apps/meteor/app/lib/server/lib/notifyListener.ts index 8ba5f45c4ae7..5fae0b083980 100644 --- a/apps/meteor/app/lib/server/lib/notifyListener.ts +++ b/apps/meteor/app/lib/server/lib/notifyListener.ts @@ -14,6 +14,7 @@ import type { IEmailInbox, IIntegrationHistory, AtLeast, + ISettingColor, } from '@rocket.chat/core-typings'; import { Rooms, @@ -105,14 +106,6 @@ export async function notifyOnRoomChangedByUserDM( } } -export async function notifyOnSettingChanged(setting: ISetting, clientAction: ClientAction = 'updated'): Promise { - if (!dbWatchersDisabled) { - return; - } - - void api.broadcast('watch.settings', { clientAction, setting }); -} - export async function notifyOnPermissionChanged(permission: IPermission, clientAction: ClientAction = 'updated'): Promise { if (!dbWatchersDisabled) { return; @@ -353,3 +346,28 @@ export async function notifyOnLivechatDepartmentAgentChangedByAgentsAndDepartmen void api.broadcast('watch.livechatDepartmentAgents', { clientAction, id: item._id, data: item }); } } + +export async function notifyOnSettingChanged( + setting: ISetting & { editor?: ISettingColor['editor'] }, + clientAction: ClientAction = 'updated', +): Promise { + if (!dbWatchersDisabled) { + return; + } + + void api.broadcast('watch.settings', { clientAction, setting }); +} + +export async function notifyOnSettingChangedById(id: ISetting['_id'], clientAction: ClientAction = 'updated'): Promise { + if (!dbWatchersDisabled) { + return; + } + + const item = clientAction === 'removed' ? await Settings.trashFindOneById(id) : await Settings.findOneById(id); + + if (!item) { + return; + } + + void api.broadcast('watch.settings', { clientAction, setting: item }); +} diff --git a/apps/meteor/app/lib/server/methods/removeOAuthService.ts b/apps/meteor/app/lib/server/methods/removeOAuthService.ts index 6d1bb688979d..6e16dc8d2d5b 100644 --- a/apps/meteor/app/lib/server/methods/removeOAuthService.ts +++ b/apps/meteor/app/lib/server/methods/removeOAuthService.ts @@ -5,6 +5,7 @@ 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' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -31,37 +32,46 @@ Meteor.methods({ name = name.toLowerCase().replace(/[^a-z0-9_]/g, ''); name = capitalize(name); - await Promise.all([ - Settings.removeById(`Accounts_OAuth_Custom-${name}`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-url`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-token_path`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-identity_path`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-authorize_path`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-scope`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-access_token_param`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-token_sent_via`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-identity_token_sent_via`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-id`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-secret`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-button_label_text`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-button_label_color`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-button_color`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-login_style`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-key_field`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-username_field`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-email_field`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-name_field`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-avatar_field`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-roles_claim`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-merge_roles`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-roles_to_sync`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-merge_users`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-show_button`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-groups_claim`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-channels_admin`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-map_channels`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-groups_channel_map`), - Settings.removeById(`Accounts_OAuth_Custom-${name}-merge_users_distinct_services`), - ]); + + const settingsIds = [ + `Accounts_OAuth_Custom-${name}`, + `Accounts_OAuth_Custom-${name}-url`, + `Accounts_OAuth_Custom-${name}-token_path`, + `Accounts_OAuth_Custom-${name}-identity_path`, + `Accounts_OAuth_Custom-${name}-authorize_path`, + `Accounts_OAuth_Custom-${name}-scope`, + `Accounts_OAuth_Custom-${name}-access_token_param`, + `Accounts_OAuth_Custom-${name}-token_sent_via`, + `Accounts_OAuth_Custom-${name}-identity_token_sent_via`, + `Accounts_OAuth_Custom-${name}-id`, + `Accounts_OAuth_Custom-${name}-secret`, + `Accounts_OAuth_Custom-${name}-button_label_text`, + `Accounts_OAuth_Custom-${name}-button_label_color`, + `Accounts_OAuth_Custom-${name}-button_color`, + `Accounts_OAuth_Custom-${name}-login_style`, + `Accounts_OAuth_Custom-${name}-key_field`, + `Accounts_OAuth_Custom-${name}-username_field`, + `Accounts_OAuth_Custom-${name}-email_field`, + `Accounts_OAuth_Custom-${name}-name_field`, + `Accounts_OAuth_Custom-${name}-avatar_field`, + `Accounts_OAuth_Custom-${name}-roles_claim`, + `Accounts_OAuth_Custom-${name}-merge_roles`, + `Accounts_OAuth_Custom-${name}-roles_to_sync`, + `Accounts_OAuth_Custom-${name}-merge_users`, + `Accounts_OAuth_Custom-${name}-show_button`, + `Accounts_OAuth_Custom-${name}-groups_claim`, + `Accounts_OAuth_Custom-${name}-channels_admin`, + `Accounts_OAuth_Custom-${name}-map_channels`, + `Accounts_OAuth_Custom-${name}-groups_channel_map`, + `Accounts_OAuth_Custom-${name}-merge_users_distinct_services`, + ]; + + const promises = settingsIds.map((id) => Settings.removeById(id)); + + (await Promise.all(promises)).forEach((value, index) => { + if (value?.deletedCount) { + void notifyOnSettingChangedById(settingsIds[index], 'removed'); + } + }); }, }); diff --git a/apps/meteor/app/lib/server/methods/saveSetting.ts b/apps/meteor/app/lib/server/methods/saveSetting.ts index d6cd62dc7470..7f900d1751d8 100644 --- a/apps/meteor/app/lib/server/methods/saveSetting.ts +++ b/apps/meteor/app/lib/server/methods/saveSetting.ts @@ -7,6 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; import { getSettingPermissionId } from '../../../authorization/lib'; import { hasPermissionAsync, hasAllPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { notifyOnSettingChanged } from '../lib/notifyListener'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -56,7 +57,10 @@ Meteor.methods({ break; } - await Settings.updateValueAndEditorById(_id, value as SettingValue, editor); + (await Settings.updateValueAndEditorById(_id, value as SettingValue, editor)).modifiedCount && + setting && + void notifyOnSettingChanged({ ...setting, editor, value: value as SettingValue }); + return true; }), }); diff --git a/apps/meteor/app/lib/server/methods/saveSettings.ts b/apps/meteor/app/lib/server/methods/saveSettings.ts index 6d7c0927c3f8..93e884c9f77e 100644 --- a/apps/meteor/app/lib/server/methods/saveSettings.ts +++ b/apps/meteor/app/lib/server/methods/saveSettings.ts @@ -9,6 +9,7 @@ import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; import { getSettingPermissionId } from '../../../authorization/lib'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; +import { notifyOnSettingChangedById } from '../lib/notifyListener'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -107,7 +108,13 @@ Meteor.methods({ }); } - await Promise.all(params.map(({ _id, value }) => Settings.updateValueById(_id, value))); + const promises = params.map(({ _id, value }) => Settings.updateValueById(_id, value)); + + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(params[index]._id); + } + }); return true; }, diff --git a/apps/meteor/app/livechat/imports/server/rest/appearance.ts b/apps/meteor/app/livechat/imports/server/rest/appearance.ts index 02239bc0edc5..48863fc9e5d3 100644 --- a/apps/meteor/app/livechat/imports/server/rest/appearance.ts +++ b/apps/meteor/app/livechat/imports/server/rest/appearance.ts @@ -4,6 +4,7 @@ import { isPOSTLivechatAppearanceParams } from '@rocket.chat/rest-typings'; import { isTruthy } from '../../../../../lib/isTruthy'; import { API } from '../../../../api/server'; +import { notifyOnSettingChangedById } from '../../../../lib/server/lib/notifyListener'; import { findAppearance } from '../../../server/api/lib/appearance'; API.v1.addRoute( @@ -89,11 +90,13 @@ API.v1.addRoute( }) .toArray(); - await Promise.all( - dbSettings.filter(isTruthy).map((setting) => { - return Settings.updateValueById(setting._id, setting.value); - }), - ); + const eligibleSettings = dbSettings.filter(isTruthy); + const promises = eligibleSettings.map(({ _id, value }) => Settings.updateValueById(_id, value)); + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(eligibleSettings[index]._id); + } + }); return API.v1.success(); }, diff --git a/apps/meteor/app/livechat/server/api/v1/integration.ts b/apps/meteor/app/livechat/server/api/v1/integration.ts index 6cf8ffd52192..a1f9c59ffb87 100644 --- a/apps/meteor/app/livechat/server/api/v1/integration.ts +++ b/apps/meteor/app/livechat/server/api/v1/integration.ts @@ -3,6 +3,7 @@ import { isPOSTomnichannelIntegrations } from '@rocket.chat/rest-typings'; import { trim } from '../../../../../lib/utils/stringUtils'; import { API } from '../../../../api/server'; +import { notifyOnSettingChangedById } from '../../../../lib/server/lib/notifyListener'; API.v1.addRoute( 'omnichannel/integrations', @@ -23,53 +24,40 @@ API.v1.addRoute( LivechatWebhookOnAgentMessage, } = this.bodyParams; - const promises = []; + const settingsIds = [ + typeof LivechatWebhookUrl !== 'undefined' && { _id: 'Livechat_webhookUrl', value: trim(LivechatWebhookUrl) }, + typeof LivechatSecretToken !== 'undefined' && { _id: 'Livechat_secret_token', value: trim(LivechatSecretToken) }, + typeof LivechatHttpTimeout !== 'undefined' && { _id: 'Livechat_http_timeout', value: LivechatHttpTimeout }, + typeof LivechatWebhookOnStart !== 'undefined' && { _id: 'Livechat_webhook_on_start', value: !!LivechatWebhookOnStart }, + typeof LivechatWebhookOnClose !== 'undefined' && { _id: 'Livechat_webhook_on_close', value: !!LivechatWebhookOnClose }, + typeof LivechatWebhookOnChatTaken !== 'undefined' && { _id: 'Livechat_webhook_on_chat_taken', value: !!LivechatWebhookOnChatTaken }, + typeof LivechatWebhookOnChatQueued !== 'undefined' && { + _id: 'Livechat_webhook_on_chat_queued', + value: !!LivechatWebhookOnChatQueued, + }, + typeof LivechatWebhookOnForward !== 'undefined' && { _id: 'Livechat_webhook_on_forward', value: !!LivechatWebhookOnForward }, + typeof LivechatWebhookOnOfflineMsg !== 'undefined' && { + _id: 'Livechat_webhook_on_offline_msg', + value: !!LivechatWebhookOnOfflineMsg, + }, + typeof LivechatWebhookOnVisitorMessage !== 'undefined' && { + _id: 'Livechat_webhook_on_visitor_message', + value: !!LivechatWebhookOnVisitorMessage, + }, + typeof LivechatWebhookOnAgentMessage !== 'undefined' && { + _id: 'Livechat_webhook_on_agent_message', + value: !!LivechatWebhookOnAgentMessage, + }, + ].filter(Boolean) as unknown as { _id: string; value: any }[]; - if (typeof LivechatWebhookUrl !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhookUrl', trim(LivechatWebhookUrl))); - } + const promises = settingsIds.map((setting) => Settings.updateValueById(setting._id, setting.value)); - if (typeof LivechatSecretToken !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_secret_token', trim(LivechatSecretToken))); - } + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settingsIds[index]._id); + } + }); - if (typeof LivechatHttpTimeout !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_http_timeout', LivechatHttpTimeout)); - } - - if (typeof LivechatWebhookOnStart !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_start', !!LivechatWebhookOnStart)); - } - - if (typeof LivechatWebhookOnClose !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_close', !!LivechatWebhookOnClose)); - } - - if (typeof LivechatWebhookOnChatTaken !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_chat_taken', !!LivechatWebhookOnChatTaken)); - } - - if (typeof LivechatWebhookOnChatQueued !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_chat_queued', !!LivechatWebhookOnChatQueued)); - } - - if (typeof LivechatWebhookOnForward !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_forward', !!LivechatWebhookOnForward)); - } - - if (typeof LivechatWebhookOnOfflineMsg !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_offline_msg', !!LivechatWebhookOnOfflineMsg)); - } - - if (typeof LivechatWebhookOnVisitorMessage !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_visitor_message', !!LivechatWebhookOnVisitorMessage)); - } - - if (typeof LivechatWebhookOnAgentMessage !== 'undefined') { - promises.push(Settings.updateValueById('Livechat_webhook_on_agent_message', !!LivechatWebhookOnAgentMessage)); - } - - await Promise.all(promises); return API.v1.success(); }, }, diff --git a/apps/meteor/app/livechat/server/api/v1/videoCall.ts b/apps/meteor/app/livechat/server/api/v1/videoCall.ts index 8cbf9e100deb..dd9c701e6495 100644 --- a/apps/meteor/app/livechat/server/api/v1/videoCall.ts +++ b/apps/meteor/app/livechat/server/api/v1/videoCall.ts @@ -6,7 +6,7 @@ import { isGETWebRTCCall, isPUTWebRTCCallId } from '@rocket.chat/rest-typings'; import { i18n } from '../../../../../server/lib/i18n'; import { API } from '../../../../api/server'; import { canSendMessageAsync } from '../../../../authorization/server/functions/canSendMessage'; -import { notifyOnRoomChangedById } from '../../../../lib/server/lib/notifyListener'; +import { notifyOnRoomChangedById, notifyOnSettingChanged } from '../../../../lib/server/lib/notifyListener'; import { settings as rcSettings } from '../../../../settings/server'; import { Livechat } from '../../lib/LivechatTyped'; import { settings } from '../lib/livechat'; @@ -46,14 +46,20 @@ API.v1.addRoute( let { callStatus } = room; if (!callStatus || callStatus === 'ended' || callStatus === 'declined') { - await Settings.incrementValueById('WebRTC_Calls_Count'); + const { value } = await Settings.incrementValueById('WebRTC_Calls_Count', 1, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } + callStatus = 'ringing'; - await Rooms.setCallStatusAndCallStartTime(room._id, callStatus); - void notifyOnRoomChangedById(room._id); + + (await Rooms.setCallStatusAndCallStartTime(room._id, callStatus)).modifiedCount && void notifyOnRoomChangedById(room._id); + await Message.saveSystemMessage('livechat_webrtc_video_call', room._id, i18n.t('Join_my_room_to_start_the_video_call'), this.user, { actionLinks: config.theme.actionLinks.webrtc, }); } + const videoCall = { rid: room._id, provider: 'webrtc', diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 8be71aa4c991..e4f422e25788 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -7,6 +7,7 @@ import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; +import { notifyOnSettingChanged } from '../../../lib/server/lib/notifyListener'; import { checkServiceStatus, createLivechatRoom, createLivechatInquiry } from './Helper'; import { RoutingManager } from './RoutingManager'; @@ -106,7 +107,11 @@ export const QueueManager: queueManager = { } void Apps.self?.triggerEvent(AppEvents.IPostLivechatRoomStarted, room); - await LivechatRooms.updateRoomCount(); + + const livechatSetting = await LivechatRooms.updateRoomCount(); + if (livechatSetting) { + void notifyOnSettingChanged(livechatSetting); + } await queueInquiry(inquiry, agent); logger.debug(`Inquiry ${inquiry._id} queued`); diff --git a/apps/meteor/app/livechat/server/methods/saveAppearance.ts b/apps/meteor/app/livechat/server/methods/saveAppearance.ts index 35152d136afd..cac5d34264d2 100644 --- a/apps/meteor/app/livechat/server/methods/saveAppearance.ts +++ b/apps/meteor/app/livechat/server/methods/saveAppearance.ts @@ -4,6 +4,7 @@ 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' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -15,6 +16,7 @@ declare module '@rocket.chat/ui-contexts' { Meteor.methods({ async 'livechat:saveAppearance'(settings) { methodDeprecationLogger.method('livechat:saveAppearance', '7.0.0'); + const uid = Meteor.userId(); if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { @@ -50,10 +52,12 @@ Meteor.methods({ throw new Meteor.Error('invalid-setting'); } - await Promise.all( - settings.map((setting) => { - return Settings.updateValueById(setting._id, setting.value); - }), - ); + const promises = settings.map((setting) => Settings.updateValueById(setting._id, setting.value)); + + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settings[index]._id); + } + }); }, }); diff --git a/apps/meteor/app/livechat/server/methods/saveIntegration.ts b/apps/meteor/app/livechat/server/methods/saveIntegration.ts index 18bad34f0aea..de7461d08e10 100644 --- a/apps/meteor/app/livechat/server/methods/saveIntegration.ts +++ b/apps/meteor/app/livechat/server/methods/saveIntegration.ts @@ -5,6 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { trim } from '../../../../lib/utils/stringUtils'; 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' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -15,56 +16,59 @@ declare module '@rocket.chat/ui-contexts' { Meteor.methods({ async 'livechat:saveIntegration'(values) { + methodDeprecationLogger.method('livechat:saveIntegration', '7.0.0'); + const uid = Meteor.userId(); if (!uid || !(await hasPermissionAsync(uid, 'view-livechat-manager'))) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveIntegration', }); } - methodDeprecationLogger.method('livechat:saveIntegration', '7.0.0'); - - if (typeof values.Livechat_webhookUrl !== 'undefined') { - await Settings.updateValueById('Livechat_webhookUrl', trim(values.Livechat_webhookUrl)); - } - - if (typeof values.Livechat_secret_token !== 'undefined') { - await Settings.updateValueById('Livechat_secret_token', trim(values.Livechat_secret_token)); - } - - if (typeof values.Livechat_http_timeout === 'number') { - await Settings.updateValueById('Livechat_http_timeout', values.Livechat_http_timeout); - } - - if (typeof values.Livechat_webhook_on_start !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_start', !!values.Livechat_webhook_on_start); - } - if (typeof values.Livechat_webhook_on_close !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_close', !!values.Livechat_webhook_on_close); - } - - if (typeof values.Livechat_webhook_on_chat_taken !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_chat_taken', !!values.Livechat_webhook_on_chat_taken); - } + const settingsIds = [ + typeof values.Livechat_webhookUrl !== 'undefined' && { _id: 'Livechat_webhookUrl', value: trim(values.Livechat_webhookUrl) }, + typeof values.Livechat_secret_token !== 'undefined' && { _id: 'Livechat_secret_token', value: trim(values.Livechat_secret_token) }, + typeof values.Livechat_http_timeout !== 'undefined' && { _id: 'Livechat_http_timeout', value: values.Livechat_http_timeout }, + typeof values.Livechat_webhook_on_start !== 'undefined' && { + _id: 'Livechat_webhook_on_start', + value: !!values.Livechat_webhook_on_start, + }, + typeof values.Livechat_webhook_on_close !== 'undefined' && { + _id: 'Livechat_webhook_on_close', + value: !!values.Livechat_webhook_on_close, + }, + typeof values.Livechat_webhook_on_forward !== 'undefined' && { + _id: 'Livechat_webhook_on_forward', + value: !!values.Livechat_webhook_on_forward, + }, + typeof values.Livechat_webhook_on_chat_taken !== 'undefined' && { + _id: 'Livechat_webhook_on_chat_taken', + value: !!values.Livechat_webhook_on_chat_taken, + }, + typeof values.Livechat_webhook_on_chat_queued !== 'undefined' && { + _id: 'Livechat_webhook_on_chat_queued', + value: !!values.Livechat_webhook_on_chat_queued, + }, + typeof values.Livechat_webhook_on_offline_msg !== 'undefined' && { + _id: 'Livechat_webhook_on_offline_msg', + value: !!values.Livechat_webhook_on_offline_msg, + }, + typeof values.Livechat_webhook_on_visitor_message !== 'undefined' && { + _id: 'Livechat_webhook_on_visitor_message', + value: !!values.Livechat_webhook_on_visitor_message, + }, + typeof values.Livechat_webhook_on_agent_message !== 'undefined' && { + _id: 'Livechat_webhook_on_agent_message', + value: !!values.Livechat_webhook_on_agent_message, + }, + ].filter(Boolean) as unknown as { _id: string; value: any }[]; - if (typeof values.Livechat_webhook_on_chat_queued !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_chat_queued', !!values.Livechat_webhook_on_chat_queued); - } - - if (typeof values.Livechat_webhook_on_forward !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_forward', !!values.Livechat_webhook_on_forward); - } + const promises = settingsIds.map((setting) => Settings.updateValueById(setting._id, setting.value)); - if (typeof values.Livechat_webhook_on_offline_msg !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_offline_msg', !!values.Livechat_webhook_on_offline_msg); - } - - if (typeof values.Livechat_webhook_on_visitor_message !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_visitor_message', !!values.Livechat_webhook_on_visitor_message); - } - - if (typeof values.Livechat_webhook_on_agent_message !== 'undefined') { - await Settings.updateValueById('Livechat_webhook_on_agent_message', !!values.Livechat_webhook_on_agent_message); - } + (await Promise.all(promises)).forEach((value, index) => { + if (value?.modifiedCount) { + void notifyOnSettingChangedById(settingsIds[index]._id); + } + }); }, }); diff --git a/apps/meteor/app/mailer/server/api.ts b/apps/meteor/app/mailer/server/api.ts index e562fc8e7b39..7e0ad0380b11 100644 --- a/apps/meteor/app/mailer/server/api.ts +++ b/apps/meteor/app/mailer/server/api.ts @@ -11,6 +11,7 @@ import _ from 'underscore'; import { validateEmail } from '../../../lib/emailValidator'; import { strLeft, strRightBack } from '../../../lib/utils/stringUtils'; import { i18n } from '../../../server/lib/i18n'; +import { notifyOnSettingChanged } from '../../lib/server/lib/notifyListener'; import { settings } from '../../settings/server'; import { replaceVariables } from './replaceVariables'; @@ -166,7 +167,10 @@ export const sendNoWrap = async ({ html = undefined; } - await Settings.incrementValueById('Triggered_Emails_Count'); + const { value } = await Settings.incrementValueById('Triggered_Emails_Count', 1, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } const email = { to, from, replyTo, subject, html, text, headers }; diff --git a/apps/meteor/app/statistics/server/functions/updateStatsCounter.ts b/apps/meteor/app/statistics/server/functions/updateStatsCounter.ts index c9eb356841be..af88e3c548ad 100644 --- a/apps/meteor/app/statistics/server/functions/updateStatsCounter.ts +++ b/apps/meteor/app/statistics/server/functions/updateStatsCounter.ts @@ -1,11 +1,17 @@ import { Settings } from '@rocket.chat/models'; +import { notifyOnSettingChanged } from '../../../lib/server/lib/notifyListener'; import telemetryEvent from '../lib/telemetryEvents'; type updateCounterDataType = { settingsId: string }; export function updateCounter(data: updateCounterDataType): void { - void Settings.incrementValueById(data.settingsId); + void (async () => { + const { value } = await Settings.incrementValueById(data.settingsId, 1, { returnDocument: 'after' }); + if (value) { + void notifyOnSettingChanged(value); + } + })(); } telemetryEvent.register('updateCounter', updateCounter); diff --git a/apps/meteor/app/version-check/server/functions/buildVersionUpdateMessage.ts b/apps/meteor/app/version-check/server/functions/buildVersionUpdateMessage.ts index fb55d478b6f5..4cca28f1d5a9 100644 --- a/apps/meteor/app/version-check/server/functions/buildVersionUpdateMessage.ts +++ b/apps/meteor/app/version-check/server/functions/buildVersionUpdateMessage.ts @@ -3,6 +3,7 @@ import semver from 'semver'; import { i18n } from '../../../../server/lib/i18n'; import { sendMessagesToAdmins } from '../../../../server/lib/sendMessagesToAdmins'; +import { notifyOnSettingChangedById } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { Info } from '../../../utils/rocketchat.info'; @@ -37,7 +38,8 @@ export const buildVersionUpdateMessage = async ( continue; } - await Settings.updateValueById('Update_LatestAvailableVersion', version.version); + (await Settings.updateValueById('Update_LatestAvailableVersion', version.version)).modifiedCount && + void notifyOnSettingChangedById('Update_LatestAvailableVersion'); await sendMessagesToAdmins({ msgs: async ({ adminUser }) => [ diff --git a/apps/meteor/ee/app/license/server/startup.ts b/apps/meteor/ee/app/license/server/startup.ts index a14837df8beb..3c47c8738613 100644 --- a/apps/meteor/ee/app/license/server/startup.ts +++ b/apps/meteor/ee/app/license/server/startup.ts @@ -6,6 +6,7 @@ import { wrapExceptions } from '@rocket.chat/tools'; import moment from 'moment'; import { syncWorkspace } from '../../../../app/cloud/server/functions/syncWorkspace'; +import { notifyOnSettingChangedById } from '../../../../app/lib/server/lib/notifyListener'; import { settings } from '../../../../app/settings/server'; import { callbacks } from '../../../../lib/callbacks'; import { getAppCount } from './lib/getAppCount'; @@ -18,17 +19,24 @@ export const startLicense = async () => { }); License.onValidateLicense(async () => { - await Settings.updateValueById('Enterprise_License', License.encryptedLicense); - await Settings.updateValueById('Enterprise_License_Status', 'Valid'); + (await Settings.updateValueById('Enterprise_License', License.encryptedLicense)).modifiedCount && + void notifyOnSettingChangedById('Enterprise_License'); + + (await Settings.updateValueById('Enterprise_License_Status', 'Valid')).modifiedCount && + void notifyOnSettingChangedById('Enterprise_License_Status'); }); License.onInvalidateLicense(async () => { - await Settings.updateValueById('Enterprise_License_Status', 'Invalid'); + (await Settings.updateValueById('Enterprise_License_Status', 'Invalid')).modifiedCount && + void notifyOnSettingChangedById('Enterprise_License_Status'); }); License.onRemoveLicense(async () => { - await Settings.updateValueById('Enterprise_License', ''); - await Settings.updateValueById('Enterprise_License_Status', 'Invalid'); + (await Settings.updateValueById('Enterprise_License', '')).modifiedCount && + void notifyOnSettingChangedById('Enterprise_License_Status'); + + (await Settings.updateValueById('Enterprise_License_Status', 'Invalid')).modifiedCount && + void notifyOnSettingChangedById('Enterprise_License_Status'); }); /** @@ -78,15 +86,17 @@ export const startLicense = async () => { const obj = Object.fromEntries(contexts.map((context) => [context, period])); - await Settings.updateValueById( - 'Enterprise_License_Data', - JSON.stringify({ - ...(existingData.signed === signed && existingData), - ...existingData, - ...obj, - signed, - }), - ); + ( + await Settings.updateValueById( + 'Enterprise_License_Data', + JSON.stringify({ + ...(existingData.signed === signed && existingData), + ...existingData, + ...obj, + signed, + }), + ) + ).modifiedCount && void notifyOnSettingChangedById('Enterprise_License_Data'); try { await syncWorkspace(); diff --git a/apps/meteor/ee/server/api/licenses.ts b/apps/meteor/ee/server/api/licenses.ts index db3d73911ef0..28b0b2e080f3 100644 --- a/apps/meteor/ee/server/api/licenses.ts +++ b/apps/meteor/ee/server/api/licenses.ts @@ -5,6 +5,7 @@ import { check } from 'meteor/check'; import { API } from '../../../app/api/server/api'; import { hasPermissionAsync } from '../../../app/authorization/server/functions/hasPermission'; +import { notifyOnSettingChangedById } from '../../../app/lib/server/lib/notifyListener'; API.v1.addRoute( 'licenses.get', @@ -56,7 +57,8 @@ API.v1.addRoute( return API.v1.failure('Invalid license'); } - await Settings.updateValueById('Enterprise_License', license); + (await Settings.updateValueById('Enterprise_License', license)).modifiedCount && + void notifyOnSettingChangedById('Enterprise_License'); return API.v1.success(); }, diff --git a/apps/meteor/ee/server/startup/upsell.ts b/apps/meteor/ee/server/startup/upsell.ts index b31bf0635060..f2fdfc50ba29 100644 --- a/apps/meteor/ee/server/startup/upsell.ts +++ b/apps/meteor/ee/server/startup/upsell.ts @@ -2,9 +2,14 @@ import { License } from '@rocket.chat/license'; import { Settings } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; +import { notifyOnSettingChangedById } from '../../../app/lib/server/lib/notifyListener'; + const handleHadTrial = (): void => { if (License.getLicense()?.information.trial) { - void Settings.updateValueById('Cloud_Workspace_Had_Trial', true); + void (async () => { + (await Settings.updateValueById('Cloud_Workspace_Had_Trial', true)).modifiedCount && + void notifyOnSettingChangedById('Cloud_Workspace_Had_Trial'); + })(); } }; diff --git a/apps/meteor/server/cron/federation.ts b/apps/meteor/server/cron/federation.ts index 56b1134f9b20..2bcd4ba27e9b 100644 --- a/apps/meteor/server/cron/federation.ts +++ b/apps/meteor/server/cron/federation.ts @@ -6,6 +6,7 @@ import { Users, Settings } from '@rocket.chat/models'; import { resolveSRV, resolveTXT } from '../../app/federation/server/functions/resolveDNS'; import { dispatchEvent } from '../../app/federation/server/handler'; import { getFederationDomain } from '../../app/federation/server/lib/getFederationDomain'; +import { notifyOnSettingChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings, settingsRegistry } from '../../app/settings/server'; async function updateSetting(id: string, value: SettingValue | null): Promise { @@ -15,7 +16,7 @@ async function updateSetting(id: string, value: SettingValue | null): Promise _id), }); await Settings.deleteMany({ _id: { $in: invalidSettings.map(({ _id }) => _id) } }); + // No need to notify listener } else { logger.info('No invalid settings found on DB.'); } diff --git a/apps/meteor/server/models/raw/LivechatRooms.ts b/apps/meteor/server/models/raw/LivechatRooms.ts index c85b633dd677..5fbf856d5b38 100644 --- a/apps/meteor/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/server/models/raw/LivechatRooms.ts @@ -3,7 +3,6 @@ import type { RocketChatRecordDeleted, IOmnichannelRoomClosingInfo, DeepWritable, - ISetting, IMessage, ILivechatPriority, IOmnichannelServiceLevelAgreements, @@ -1853,18 +1852,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive } async updateRoomCount() { - const query: Filter = { - _id: 'Livechat_Room_Count', - }; - - const update: UpdateFilter = { - $inc: { - // @ts-expect-error - Caused by `OnlyFieldsOfType` on mongo which excludes `SettingValue` from $inc - value: 1, - }, - }; - - const livechatCount = await Settings.findOneAndUpdate(query, update, { returnDocument: 'after' }); + const livechatCount = await Settings.incrementValueById('Livechat_Room_Count', 1, { returnDocument: 'after' }); return livechatCount.value; } diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 7974867a289b..e5234a8d5712 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor, ISetting, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { FindPaginated, ILivechatVisitorsModel } from '@rocket.chat/model-typings'; import { Settings } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; @@ -16,6 +16,7 @@ import type { UpdateFilter, } from 'mongodb'; +import { notifyOnSettingChanged } from '../../../app/lib/server/lib/notifyListener'; import { BaseRaw } from './BaseRaw'; export class LivechatVisitorsRaw extends BaseRaw implements ILivechatVisitorsModel { @@ -115,24 +116,15 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL } async getNextVisitorUsername(): Promise { - const query = { - _id: 'Livechat_guest_count', - }; - - const update: UpdateFilter = { - $inc: { - // @ts-expect-error looks like the typings of ISetting.value conflict with this type of update - value: 1, - }, - }; - // TODO remove dependency from another model - this logic should be inside a service/function - const livechatCount = await Settings.findOneAndUpdate(query, update, { returnDocument: 'after' }); + const livechatCount = await Settings.incrementValueById('Livechat_guest_count', 1, { returnDocument: 'after' }); if (!livechatCount.value) { throw new Error("Can't find Livechat_guest_count setting"); } + void notifyOnSettingChanged(livechatCount.value); + return `guest-${livechatCount.value.value}`; } diff --git a/apps/meteor/server/models/raw/Settings.ts b/apps/meteor/server/models/raw/Settings.ts index 16ccdc788fdf..3bb735c28905 100644 --- a/apps/meteor/server/models/raw/Settings.ts +++ b/apps/meteor/server/models/raw/Settings.ts @@ -1,6 +1,18 @@ import type { ISetting, ISettingColor, ISettingSelectOption, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { ISettingsModel } from '@rocket.chat/model-typings'; -import type { Collection, FindCursor, Db, Filter, UpdateFilter, UpdateResult, Document, FindOptions } from 'mongodb'; +import type { + Collection, + FindCursor, + Db, + Filter, + UpdateFilter, + UpdateResult, + Document, + FindOptions, + FindOneAndUpdateOptions, + ModifyResult, + UpdateOptions, +} from 'mongodb'; import { BaseRaw } from './BaseRaw'; @@ -53,6 +65,7 @@ export class SettingsRaw extends BaseRaw implements ISettingsModel { updateValueById( _id: string, value: (ISetting['value'] extends undefined ? never : ISetting['value']) | null, + options?: UpdateOptions, ): Promise { const query = { blocked: { $ne: true }, @@ -66,7 +79,7 @@ export class SettingsRaw extends BaseRaw implements ISettingsModel { }, }; - return this.updateOne(query, update); + return this.updateOne(query, update, options); } async resetValueById( @@ -88,17 +101,22 @@ export class SettingsRaw extends BaseRaw implements ISettingsModel { return this.updateValueById(_id, value); } - async incrementValueById(_id: ISetting['_id'], value = 1): Promise { - return this.updateOne( + async incrementValueById( + _id: ISetting['_id'], + value?: ISetting['value'], + options?: FindOneAndUpdateOptions, + ): Promise> { + return this.findOneAndUpdate( { blocked: { $ne: true }, _id, }, { $inc: { - value, + value: value || 1, }, } as unknown as UpdateFilter, + options, ); } diff --git a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts index 219c9277a619..9d447e881e78 100644 --- a/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts +++ b/apps/meteor/server/services/federation/infrastructure/rocket-chat/adapters/Settings.ts @@ -4,6 +4,7 @@ import { Settings } from '@rocket.chat/models'; import yaml from 'js-yaml'; import { v4 as uuidv4 } from 'uuid'; +import { notifyOnSettingChangedById } from '../../../../../../app/lib/server/lib/notifyListener'; import { settings, settingsRegistry } from '../../../../../../app/settings/server'; import type { IFederationBridgeRegistrationFile } from '../../../domain/IFederationBridge'; @@ -55,7 +56,8 @@ export class RocketChatSettingsAdapter { } public async disableFederation(): Promise { - await Settings.updateValueById('Federation_Matrix_enabled', false); + (await Settings.updateValueById('Federation_Matrix_enabled', false)).modifiedCount && + void notifyOnSettingChangedById('Federation_Matrix_enabled'); } public isFederationEnabled(): boolean { diff --git a/apps/meteor/server/settings/misc.ts b/apps/meteor/server/settings/misc.ts index ae8371276533..af2725628ec3 100644 --- a/apps/meteor/server/settings/misc.ts +++ b/apps/meteor/server/settings/misc.ts @@ -17,9 +17,11 @@ const generateFingerprint = function () { }; const updateFingerprint = async function (fingerprint: string, verified: boolean) { - await Settings.updateValueById('Deployment_FingerPrint_Hash', fingerprint); - - await Settings.updateValueById('Deployment_FingerPrint_Verified', verified); + // No need to call ws listener because current function is called on startup + await Promise.all([ + Settings.updateValueById('Deployment_FingerPrint_Hash', fingerprint), + Settings.updateValueById('Deployment_FingerPrint_Verified', verified), + ]); }; const verifyFingerPrint = async function () { diff --git a/apps/meteor/server/startup/cloudRegistration.ts b/apps/meteor/server/startup/cloudRegistration.ts index e69d4446d6ec..65d870de4d89 100644 --- a/apps/meteor/server/startup/cloudRegistration.ts +++ b/apps/meteor/server/startup/cloudRegistration.ts @@ -1,5 +1,7 @@ import { Settings } from '@rocket.chat/models'; +import { notifyOnSettingChangedById } from '../../app/lib/server/lib/notifyListener'; + export async function ensureCloudWorkspaceRegistered(): Promise { const cloudWorkspaceClientId = await Settings.getValueById('Cloud_Workspace_Client_Id'); const cloudWorkspaceClientSecret = await Settings.getValueById('Cloud_Workspace_Client_Secret'); @@ -16,5 +18,6 @@ export async function ensureCloudWorkspaceRegistered(): Promise { } // otherwise, set the setup wizard to in_progress forcing admins to complete the registration - await Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); + (await Settings.updateValueById('Show_Setup_Wizard', 'in_progress')).modifiedCount && + void notifyOnSettingChangedById('Show_Setup_Wizard'); } diff --git a/apps/meteor/server/startup/initialData.js b/apps/meteor/server/startup/initialData.js index 8e008191ea44..cefed3eef922 100644 --- a/apps/meteor/server/startup/initialData.js +++ b/apps/meteor/server/startup/initialData.js @@ -8,6 +8,7 @@ import { FileUpload } from '../../app/file-upload/server'; import { RocketChatFile } from '../../app/file/server'; import { addUserToDefaultChannels } from '../../app/lib/server/functions/addUserToDefaultChannels'; import { checkUsernameAvailability } from '../../app/lib/server/functions/checkUsernameAvailability'; +import { notifyOnSettingChangedById } from '../../app/lib/server/lib/notifyListener'; import { settings } from '../../app/settings/server'; import { validateEmail } from '../../lib/emailValidator'; import { addUserRolesAsync } from '../lib/roles/addUserRoles'; @@ -126,7 +127,8 @@ Meteor.startup(async () => { }); } - Settings.updateValueById('Initial_Channel_Created', true); + (await Settings.updateValueById('Initial_Channel_Created', true)).modifiedCount && + void notifyOnSettingChangedById('Initial_Channel_Created'); } try { @@ -198,7 +200,9 @@ Meteor.startup(async () => { if ((await (await getUsersInRole('admin')).count()) !== 0) { if (settings.get('Show_Setup_Wizard') === 'pending') { console.log('Setting Setup Wizard to "in_progress" because, at least, one admin was found'); - Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); + + (await Settings.updateValueById('Show_Setup_Wizard', 'in_progress')).modifiedCount && + void notifyOnSettingChangedById('Show_Setup_Wizard'); } } @@ -244,7 +248,8 @@ Meteor.startup(async () => { await addUserRolesAsync(adminUser._id, ['admin']); if (settings.get('Show_Setup_Wizard') === 'pending') { - Settings.updateValueById('Show_Setup_Wizard', 'in_progress'); + (await Settings.updateValueById('Show_Setup_Wizard', 'in_progress')).modifiedCount && + void notifyOnSettingChangedById('Show_Setup_Wizard'); } return addUserToDefaultChannels(adminUser, true); diff --git a/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts b/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts index e7e68790cf3d..38835db4aaa6 100644 --- a/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts +++ b/apps/meteor/tests/e2e/fixtures/inject-initial-data.ts @@ -57,22 +57,6 @@ export default async function injectInitialData() { _id: 'API_Enable_Rate_Limiter_Dev', value: false, }, - { - _id: 'SAML_Custom_Default_provider', - value: 'test-sp', - }, - { - _id: 'SAML_Custom_Default_issuer', - value: 'http://localhost:3000/_saml/metadata/test-sp', - }, - { - _id: 'SAML_Custom_Default_entry_point', - value: 'http://localhost:8080/simplesaml/saml2/idp/SSOService.php', - }, - { - _id: 'SAML_Custom_Default_idp_slo_redirect_url', - value: 'http://localhost:8080/simplesaml/saml2/idp/SingleLogoutService.php', - }, { _id: 'Accounts_OAuth_Google', value: false, diff --git a/apps/meteor/tests/e2e/saml.spec.ts b/apps/meteor/tests/e2e/saml.spec.ts index b1c1687bfa6a..de4cca395be9 100644 --- a/apps/meteor/tests/e2e/saml.spec.ts +++ b/apps/meteor/tests/e2e/saml.spec.ts @@ -17,7 +17,7 @@ import { setSettingValueById } from './utils/setSettingValueById'; import type { BaseTest } from './utils/test'; import { test, expect } from './utils/test'; -const resetTestData = async (cleanupOnly = false) => { +const resetTestData = async ({ api, cleanupOnly = false }: { api?: any; cleanupOnly?: boolean } = {}) => { // Reset saml users' data on mongo in the beforeAll hook to allow re-running the tests within the same playwright session // This is needed because those tests will modify this data and running them a second time would trigger different code paths const connection = await MongoClient.connect(constants.URL_MONGODB); @@ -45,43 +45,21 @@ const resetTestData = async (cleanupOnly = false) => { ), ); - await Promise.all( - [ - { - _id: 'Accounts_AllowAnonymousRead', - value: false, - }, - { - _id: 'SAML_Custom_Default_logout_behaviour', - value: 'SAML', - }, - { - _id: 'SAML_Custom_Default_immutable_property', - value: 'EMail', - }, - { - _id: 'SAML_Custom_Default_mail_overwrite', - value: false, - }, - { - _id: 'SAML_Custom_Default', - value: false, - }, - { - _id: 'SAML_Custom_Default_role_attribute_sync', - value: true, - }, - { - _id: 'SAML_Custom_Default_role_attribute_name', - value: 'role', - }, - ].map((setting) => - connection - .db() - .collection('rocketchat_settings') - .updateOne({ _id: setting._id }, { $set: { value: setting.value } }), - ), - ); + const settings = [ + { _id: 'Accounts_AllowAnonymousRead', value: false }, + { _id: 'SAML_Custom_Default_logout_behaviour', value: 'SAML' }, + { _id: 'SAML_Custom_Default_immutable_property', value: 'EMail' }, + { _id: 'SAML_Custom_Default_mail_overwrite', value: false }, + { _id: 'SAML_Custom_Default', value: false }, + { _id: 'SAML_Custom_Default_role_attribute_sync', value: true }, + { _id: 'SAML_Custom_Default_role_attribute_name', value: 'role' }, + { _id: 'SAML_Custom_Default_provider', value: 'test-sp' }, + { _id: 'SAML_Custom_Default_issuer', value: 'http://localhost:3000/_saml/metadata/test-sp' }, + { _id: 'SAML_Custom_Default_entry_point', value: 'http://localhost:8080/simplesaml/saml2/idp/SSOService.php' }, + { _id: 'SAML_Custom_Default_idp_slo_redirect_url', value: 'http://localhost:8080/simplesaml/saml2/idp/SingleLogoutService.php' }, + ]; + + await Promise.all(settings.map(({ _id, value }) => setSettingValueById(api, _id, value))); }; const setupCustomRole = async (api: BaseTest['api']) => { @@ -102,7 +80,7 @@ test.describe('SAML', () => { const containerPath = path.join(__dirname, 'containers', 'saml'); test.beforeAll(async ({ api }) => { - await resetTestData(); + await resetTestData({ api }); // Only one setting updated through the API to avoid refreshing the service configurations several times await expect((await setSettingValueById(api, 'SAML_Custom_Default', true)).status()).toBe(200); @@ -149,7 +127,7 @@ test.describe('SAML', () => { } // Remove saml test users so they don't interfere with other tests - await resetTestData(true); + await resetTestData({ cleanupOnly: true }); // Remove created custom role if (constants.IS_EE) { diff --git a/packages/model-typings/src/models/ISettingsModel.ts b/packages/model-typings/src/models/ISettingsModel.ts index cb11d4b1b405..196b1a6bce1c 100644 --- a/packages/model-typings/src/models/ISettingsModel.ts +++ b/packages/model-typings/src/models/ISettingsModel.ts @@ -1,5 +1,14 @@ import type { ISetting, ISettingColor, ISettingSelectOption } from '@rocket.chat/core-typings'; -import type { FindCursor, UpdateFilter, UpdateResult, Document, FindOptions } from 'mongodb'; +import type { + FindCursor, + UpdateFilter, + UpdateResult, + Document, + FindOptions, + FindOneAndUpdateOptions, + ModifyResult, + UpdateOptions, +} from 'mongodb'; import type { IBaseModel } from './IBaseModel'; @@ -15,6 +24,7 @@ export interface ISettingsModel extends IBaseModel { updateValueById( _id: string, value: (ISetting['value'] extends undefined ? never : ISetting['value']) | null, + options?: UpdateOptions, ): Promise; resetValueById( @@ -22,7 +32,7 @@ export interface ISettingsModel extends IBaseModel { value?: (ISetting['value'] extends undefined ? never : ISetting['value']) | null, ): Promise; - incrementValueById(_id: ISetting['_id'], value?: number): Promise; + incrementValueById(_id: ISetting['_id'], value?: ISetting['value'], options?: FindOneAndUpdateOptions): Promise>; updateOptionsById( _id: ISetting['_id'], From 68b624e080da6aab61efb2641bbd5dffb822bdd9 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Thu, 20 Jun 2024 23:01:31 -0300 Subject: [PATCH 4/7] regression: Incorrect translation of room data on listeners of message events (#32641) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ce099ca48a7f..99edf76b94dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8509,8 +8509,8 @@ __metadata: linkType: soft "@rocket.chat/apps-engine@npm:alpha": - version: 1.43.0-alpha.763 - resolution: "@rocket.chat/apps-engine@npm:1.43.0-alpha.763" + version: 1.43.0-alpha.765 + resolution: "@rocket.chat/apps-engine@npm:1.43.0-alpha.765" dependencies: "@msgpack/msgpack": 3.0.0-beta2 adm-zip: ^0.5.9 @@ -8526,7 +8526,7 @@ __metadata: uuid: ~8.3.2 peerDependencies: "@rocket.chat/ui-kit": "*" - checksum: f4498febb2f11766c6cab98c3738ca8ba50e9cfa68d129577a781a6fbbe91cfc48c82d6e418f05f0db6b3561becd6aab09b5b19b26652deb5e5621d7fd7b4c4a + checksum: 97fb5773c8d99e63e8c1d6f6002956d35b031cbe60d640d637b28a65ebbc37b89366e91f5c8bda254f634987a66fef37b70716c20d9a75bae86171447d477bb6 languageName: node linkType: hard From 4a0480bdf67feb81ec1c90c8f9bf4a7791871ee1 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 20 Jun 2024 23:02:03 -0300 Subject: [PATCH 5/7] fix: Override retention policy missing ignore threads option (#32485) --- .changeset/rare-dancers-own.md | 5 ++++ .../Info/EditRoomInfo/EditRoomInfo.tsx | 15 ++++++++++ .../EditRoomInfo/useEditRoomInitialValues.ts | 1 + .../views/room/hooks/useRetentionPolicy.ts | 12 ++++++++ apps/meteor/server/models/raw/Rooms.ts | 5 ++-- .../fragments/home-flextab-room.ts | 12 ++++++-- .../meteor/tests/e2e/retention-policy.spec.ts | 30 ++++++++++++++++++- 7 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 .changeset/rare-dancers-own.md diff --git a/.changeset/rare-dancers-own.md b/.changeset/rare-dancers-own.md new file mode 100644 index 000000000000..358963661bef --- /dev/null +++ b/.changeset/rare-dancers-own.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Adds the missing `ignoreThreads` param fixing the issue not allowing ignoring threads when overriding retention policy diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx index a32df8a44580..2bdf5ff395f6 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditRoomInfo.tsx @@ -152,6 +152,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => retentionMaxAge, retentionExcludePinned, retentionFilesOnly, + retentionIgnoreThreads, ...formData }) => { const data = getDirtyFields(formData, dirtyFields); @@ -172,6 +173,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => retentionMaxAge, retentionExcludePinned, retentionFilesOnly, + retentionIgnoreThreads, }), }); @@ -218,6 +220,7 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => const retentionMaxAgeField = useUniqueId(); const retentionExcludePinnedField = useUniqueId(); const retentionFilesOnlyField = useUniqueId(); + const retentionIgnoreThreads = useUniqueId(); return ( <> @@ -538,6 +541,18 @@ const EditRoomInfo = ({ room, onClickClose, onClickBack }: EditRoomInfoProps) => /> + + + {t('RetentionPolicy_DoNotPruneThreads')} + ( + + )} + /> + + {canViewEncrypted && ( diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts index 1cb76cadd335..fa25dab96f0d 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/useEditRoomInitialValues.ts @@ -34,6 +34,7 @@ export const useEditRoomInitialValues = (room: IRoomWithRetentionPolicy) => { retentionMaxAge: retention?.maxAge ?? retentionPolicy.maxAge, retentionExcludePinned: retention?.excludePinned ?? retentionPolicy.excludePinned, retentionFilesOnly: retention?.filesOnly ?? retentionPolicy.filesOnly, + retentionIgnoreThreads: retention?.ignoreThreads ?? retentionPolicy.ignoreThreads, }), }), [ diff --git a/apps/meteor/client/views/room/hooks/useRetentionPolicy.ts b/apps/meteor/client/views/room/hooks/useRetentionPolicy.ts index ce16df8bc329..1b048630ce74 100644 --- a/apps/meteor/client/views/room/hooks/useRetentionPolicy.ts +++ b/apps/meteor/client/views/room/hooks/useRetentionPolicy.ts @@ -8,6 +8,7 @@ type RetentionPolicySettings = { enabled: boolean; filesOnly: boolean; doNotPrunePinned: boolean; + ignoreThreads: boolean; appliesToChannels: boolean; maxAgeChannels: number; appliesToGroups: boolean; @@ -55,6 +56,14 @@ const extractExcludePinned = (room: IRoom, { doNotPrunePinned }: RetentionPolicy return doNotPrunePinned; }; +const extractIgnoreThreads = (room: IRoom, { ignoreThreads }: RetentionPolicySettings): boolean => { + if (hasRetentionPolicy(room) && room.retention.overrideGlobal) { + return room.retention.ignoreThreads; + } + + return ignoreThreads; +}; + const getMaxAge = (room: IRoom, { maxAgeChannels, maxAgeGroups, maxAgeDMs }: RetentionPolicySettings): number => { if (hasRetentionPolicy(room) && room.retention.overrideGlobal) { return room.retention.maxAge; @@ -81,6 +90,7 @@ export const useRetentionPolicy = ( isActive: boolean; filesOnly: boolean; excludePinned: boolean; + ignoreThreads: boolean; maxAge: number; } | undefined => { @@ -88,6 +98,7 @@ export const useRetentionPolicy = ( enabled: useSetting('RetentionPolicy_Enabled') as boolean, filesOnly: useSetting('RetentionPolicy_FilesOnly') as boolean, doNotPrunePinned: useSetting('RetentionPolicy_DoNotPrunePinned') as boolean, + ignoreThreads: useSetting('RetentionPolicy_DoNotPruneThreads') as boolean, appliesToChannels: useSetting('RetentionPolicy_AppliesToChannels') as boolean, maxAgeChannels: useSetting('RetentionPolicy_MaxAge_Channels') as number, appliesToGroups: useSetting('RetentionPolicy_AppliesToGroups') as boolean, @@ -105,6 +116,7 @@ export const useRetentionPolicy = ( isActive: isActive(room, settings), filesOnly: extractFilesOnly(room, settings), excludePinned: extractExcludePinned(room, settings), + ignoreThreads: extractIgnoreThreads(room, settings), maxAge: getMaxAge(room, settings), }; }; diff --git a/apps/meteor/server/models/raw/Rooms.ts b/apps/meteor/server/models/raw/Rooms.ts index 64bde1c32924..78efa845ba34 100644 --- a/apps/meteor/server/models/raw/Rooms.ts +++ b/apps/meteor/server/models/raw/Rooms.ts @@ -1838,13 +1838,12 @@ export class RoomsRaw extends BaseRaw implements IRoomsModel { return this.updateOne(query, update); } - // 5 saveRetentionIgnoreThreadsById(_id: IRoom['_id'], value: boolean): Promise { const query: Filter = { _id }; const update: UpdateFilter = { - [value === true ? '$set' : '$unset']: { - 'retention.ignoreThreads': true, + $set: { + 'retention.ignoreThreads': value === true, }, }; diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts index 041b37da9919..038409eceec9 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-room.ts @@ -56,10 +56,18 @@ export class HomeFlextabRoom { } get checkboxPruneMessages(): Locator { - return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Automatically prune old messages' }) }); + return this.page + .getByRole('dialog') + .locator('label', { has: this.page.getByRole('checkbox', { name: 'Automatically prune old messages' }) }); } get checkboxOverrideGlobalRetention(): Locator { - return this.page.locator('label', { has: this.page.getByRole('checkbox', { name: 'Override global retention policy' }) }); + return this.page + .getByRole('dialog') + .locator('label', { has: this.page.getByRole('checkbox', { name: 'Override global retention policy' }) }); + } + + get checkboxIgnoreThreads(): Locator { + return this.page.getByRole('dialog').locator('label', { has: this.page.getByRole('checkbox', { name: 'Do not prune Threads' }) }); } } diff --git a/apps/meteor/tests/e2e/retention-policy.spec.ts b/apps/meteor/tests/e2e/retention-policy.spec.ts index d17276a160af..c5db60384d92 100644 --- a/apps/meteor/tests/e2e/retention-policy.spec.ts +++ b/apps/meteor/tests/e2e/retention-policy.spec.ts @@ -3,7 +3,7 @@ import { faker } from '@faker-js/faker'; import { createAuxContext } from './fixtures/createAuxContext'; import { Users } from './fixtures/userStates'; import { HomeChannel } from './page-objects'; -import { createTargetPrivateChannel, createTargetTeam, setSettingValueById } from './utils'; +import { createTargetTeam, createTargetPrivateChannel, getSettingValueById, setSettingValueById } from './utils'; import { test, expect } from './utils/test'; test.use({ storageState: Users.admin.state }); @@ -155,7 +155,10 @@ test.describe.serial('retention-policy', () => { }); test.describe('retention policy override', () => { + let ignoreThreadsSetting: boolean; + test.beforeAll(async ({ api }) => { + ignoreThreadsSetting = (await getSettingValueById(api, 'RetentionPolicy_DoNotPruneThreads')) as boolean; expect((await setSettingValueById(api, 'RetentionPolicy_MaxAge_Channels', 15)).status()).toBe(200); }); @@ -186,6 +189,31 @@ test.describe.serial('retention-policy', () => { await expect(poHomeChannel.tabs.room.getMaxAgeLabel('15')).toBeVisible(); await expect(poHomeChannel.tabs.room.inputRetentionMaxAge).toHaveValue('365'); }); + + test('should ignore threads be checked accordingly with the global default value', async () => { + await poHomeChannel.sidenav.openChat(targetChannel); + await poHomeChannel.tabs.btnRoomInfo.click(); + await poHomeChannel.tabs.room.btnEdit.click(); + await poHomeChannel.tabs.room.pruneAccordion.click(); + + await expect(poHomeChannel.tabs.room.checkboxIgnoreThreads).toBeChecked({ checked: ignoreThreadsSetting }); + }); + + test('should override ignore threads default value', async () => { + await poHomeChannel.sidenav.openChat(targetChannel); + await poHomeChannel.tabs.btnRoomInfo.click(); + await poHomeChannel.tabs.room.btnEdit.click(); + await poHomeChannel.tabs.room.pruneAccordion.click(); + await poHomeChannel.tabs.room.checkboxIgnoreThreads.click(); + await poHomeChannel.tabs.room.btnSave.click(); + await poHomeChannel.dismissToast(); + + await poHomeChannel.tabs.btnRoomInfo.click(); + await poHomeChannel.tabs.room.btnEdit.click(); + await poHomeChannel.tabs.room.pruneAccordion.click(); + + await expect(poHomeChannel.tabs.room.checkboxIgnoreThreads).toBeChecked({ checked: !ignoreThreadsSetting }); + }); }); }); }); From 832ae0f70c9c26473f2d26e42dbcc0d2d8aa54e2 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 20 Jun 2024 23:02:27 -0300 Subject: [PATCH 6/7] feat: Change Quote Position (#32605) --- .changeset/nasty-windows-reply.md | 5 +++++ .../message/content/attachments/QuoteAttachment.tsx | 2 +- .../message/variants/room/RoomMessageContent.tsx | 11 ++++++++--- .../message/variants/thread/ThreadMessageContent.tsx | 10 ++++++++-- .../views/admin/moderation/helpers/ContextMessage.tsx | 11 ++++++++--- .../MessageList/ContactHistoryMessage.tsx | 9 +++++++-- 6 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 .changeset/nasty-windows-reply.md diff --git a/.changeset/nasty-windows-reply.md b/.changeset/nasty-windows-reply.md new file mode 100644 index 000000000000..be62ea558762 --- /dev/null +++ b/.changeset/nasty-windows-reply.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": minor +--- + +Moves the quotes to be on top of the message for better readability diff --git a/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx b/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx index 7c2c2011cac9..394bf24995dc 100644 --- a/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx @@ -67,12 +67,12 @@ export const QuoteAttachment = ({ attachment }: QuoteAttachmentProps): ReactElem )} - {attachment.md ? : attachment.text.substring(attachment.text.indexOf('\n') + 1)} {attachment.attachments && ( )} + {attachment.md ? : attachment.text.substring(attachment.text.indexOf('\n') + 1)} diff --git a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx index 84ea4039ca93..247f51173530 100644 --- a/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx +++ b/apps/meteor/client/components/message/variants/room/RoomMessageContent.tsx @@ -1,5 +1,5 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import { isDiscussionMessage, isThreadMainMessage, isE2EEMessage } from '@rocket.chat/core-typings'; +import { isDiscussionMessage, isThreadMainMessage, isE2EEMessage, isQuoteAttachment } from '@rocket.chat/core-typings'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useSetting, useTranslation, useUserId } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; @@ -44,8 +44,13 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM const normalizedMessage = useNormalizedMessage(message); + const quotes = normalizedMessage?.attachments?.filter(isQuoteAttachment) || []; + + const attachments = normalizedMessage?.attachments?.filter((attachment) => !isQuoteAttachment(attachment)) || []; + return ( <> + {!!quotes?.length && } {!normalizedMessage.blocks?.length && !!normalizedMessage.md?.length && ( <> {(!encrypted || normalizedMessage.e2e === 'done') && ( @@ -61,12 +66,12 @@ const RoomMessageContent = ({ message, unread, all, mention, searchText }: RoomM )} + {!!attachments && } + {normalizedMessage.blocks && ( )} - {!!normalizedMessage?.attachments?.length && } - {oembedEnabled && !!normalizedMessage.urls?.length && } {normalizedMessage.actionLinks?.length && ( diff --git a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx index 7098a2709d5b..bec9b8a15c90 100644 --- a/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx +++ b/apps/meteor/client/components/message/variants/thread/ThreadMessageContent.tsx @@ -1,5 +1,5 @@ import type { IThreadMainMessage, IThreadMessage } from '@rocket.chat/core-typings'; -import { isE2EEMessage } from '@rocket.chat/core-typings'; +import { isE2EEMessage, isQuoteAttachment } from '@rocket.chat/core-typings'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useSetting, useUserId, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; @@ -37,8 +37,14 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem const normalizedMessage = useNormalizedMessage(message); + const quotes = normalizedMessage?.attachments?.filter(isQuoteAttachment) || []; + + const attachments = normalizedMessage?.attachments?.filter((attachment) => !isQuoteAttachment(attachment)) || []; + return ( <> + {!!quotes?.length && } + {!normalizedMessage.blocks?.length && !!normalizedMessage.md?.length && ( <> {(!encrypted || normalizedMessage.e2e === 'done') && ( @@ -52,7 +58,7 @@ const ThreadMessageContent = ({ message }: ThreadMessageContentProps): ReactElem )} - {normalizedMessage.attachments && } + {!!attachments && } {oembedEnabled && !!normalizedMessage.urls?.length && } diff --git a/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx b/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx index 51a2be087276..afca74f7528c 100644 --- a/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx +++ b/apps/meteor/client/views/admin/moderation/helpers/ContextMessage.tsx @@ -1,5 +1,5 @@ -import type { IMessage, MessageReport } from '@rocket.chat/core-typings'; -import { isE2EEMessage } from '@rocket.chat/core-typings'; +import type { IMessage, MessageReport, MessageAttachment } from '@rocket.chat/core-typings'; +import { isE2EEMessage, isQuoteAttachment } from '@rocket.chat/core-typings'; import { Message, MessageName, MessageToolbarItem, MessageToolbarWrapper, MessageUsername } from '@rocket.chat/fuselage'; import { UserAvatar } from '@rocket.chat/ui-avatar'; import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; @@ -47,6 +47,10 @@ const ContextMessage = ({ const displayName = useUserDisplayName({ name, username }); + const quotes = message?.attachments?.filter(isQuoteAttachment) || []; + + const attachments = message?.attachments?.filter((attachment: MessageAttachment) => !isQuoteAttachment(attachment)) || []; + return ( <> {formatDate(message._updatedAt)} @@ -65,6 +69,7 @@ const ContextMessage = ({ {room.name || room.fname || 'DM'} + {!!quotes?.length && } {!message.blocks?.length && !!message.md?.length ? ( <> {(!isEncryptedMessage || message.e2e === 'done') && ( @@ -76,8 +81,8 @@ const ContextMessage = ({ message.msg )} + {!!attachments && } {message.blocks && } - {message.attachments?.length > 0 && } diff --git a/apps/meteor/client/views/omnichannel/contactHistory/MessageList/ContactHistoryMessage.tsx b/apps/meteor/client/views/omnichannel/contactHistory/MessageList/ContactHistoryMessage.tsx index 9c779d1d5ea3..e6e3ef534be1 100644 --- a/apps/meteor/client/views/omnichannel/contactHistory/MessageList/ContactHistoryMessage.tsx +++ b/apps/meteor/client/views/omnichannel/contactHistory/MessageList/ContactHistoryMessage.tsx @@ -1,4 +1,4 @@ -import type { IMessage } from '@rocket.chat/core-typings'; +import { isQuoteAttachment, type IMessage, type MessageAttachment } from '@rocket.chat/core-typings'; import { Message as MessageTemplate, MessageLeftContainer, @@ -43,6 +43,10 @@ const ContactHistoryMessage: FC<{ const format = useFormatDate(); const formatTime = useFormatTime(); + const quotes = message?.attachments?.filter(isQuoteAttachment) || []; + + const attachments = message?.attachments?.filter((attachment: MessageAttachment) => !isQuoteAttachment(attachment)) || []; + if (message.t === 'livechat-close') { return ( @@ -104,13 +108,14 @@ const ContactHistoryMessage: FC<{ )} + {!!quotes?.length && } {!message.blocks && message.md && ( )} {message.blocks && } - {message.attachments && } + {!!attachments && } From c0828b4d22e1070bf7cd5e404b22ba0342705879 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 20 Jun 2024 23:03:57 -0300 Subject: [PATCH 7/7] feat: Upgrade `fuselage-toastbar` adding RTL support (#32604) --- .changeset/spotty-seals-whisper.md | 6 ++++++ apps/meteor/package.json | 2 +- packages/uikit-playground/package.json | 2 +- yarn.lock | 12 ++++++------ 4 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 .changeset/spotty-seals-whisper.md diff --git a/.changeset/spotty-seals-whisper.md b/.changeset/spotty-seals-whisper.md new file mode 100644 index 000000000000..242b5f6dde63 --- /dev/null +++ b/.changeset/spotty-seals-whisper.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/uikit-playground': minor +'@rocket.chat/meteor': minor +--- + +Upgrades fuselage-toastbar version in order to add RTL support to the component diff --git a/apps/meteor/package.json b/apps/meteor/package.json index b060c09a53b4..ff44a6053a68 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -245,7 +245,7 @@ "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", - "@rocket.chat/fuselage-toastbar": "^0.31.26", + "@rocket.chat/fuselage-toastbar": "^0.32.0", "@rocket.chat/fuselage-tokens": "^0.33.1", "@rocket.chat/fuselage-ui-kit": "workspace:^", "@rocket.chat/gazzodown": "workspace:^", diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index 732b74c0d3d3..d73bf87bf968 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -18,7 +18,7 @@ "@rocket.chat/fuselage": "^0.54.3", "@rocket.chat/fuselage-hooks": "^0.33.1", "@rocket.chat/fuselage-polyfills": "~0.31.25", - "@rocket.chat/fuselage-toastbar": "^0.31.26", + "@rocket.chat/fuselage-toastbar": "^0.32.0", "@rocket.chat/fuselage-tokens": "^0.33.1", "@rocket.chat/fuselage-ui-kit": "workspace:~", "@rocket.chat/icons": "^0.36.0", diff --git a/yarn.lock b/yarn.lock index 99edf76b94dd..367068d93f56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8883,9 +8883,9 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-toastbar@npm:^0.31.26": - version: 0.31.26 - resolution: "@rocket.chat/fuselage-toastbar@npm:0.31.26" +"@rocket.chat/fuselage-toastbar@npm:^0.32.0": + version: 0.32.0 + resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0" peerDependencies: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" @@ -8893,7 +8893,7 @@ __metadata: "@rocket.chat/styled": "*" react: ^17.0.2 react-dom: ^17.0.2 - checksum: 92b80ee312b03cfd36f3fbbceafaf81f57ce1c7bbe3232cf93ea1aeb26f73ec034f4eded9b57e1fc85e3141609af02af3ffcaa15f82c57163e398eef22fc1467 + checksum: 5e78516aee6446da4c76dac10ff83adb4deeb86d6111c42419f0629c35ec64b19ae6ab563f20b5efa2600c9c723b9096edc3c166e960fd25cfda1f07c4df3f3f languageName: node linkType: hard @@ -9371,7 +9371,7 @@ __metadata: "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 - "@rocket.chat/fuselage-toastbar": ^0.31.26 + "@rocket.chat/fuselage-toastbar": ^0.32.0 "@rocket.chat/fuselage-tokens": ^0.33.1 "@rocket.chat/fuselage-ui-kit": "workspace:^" "@rocket.chat/gazzodown": "workspace:^" @@ -10526,7 +10526,7 @@ __metadata: "@rocket.chat/fuselage": ^0.54.3 "@rocket.chat/fuselage-hooks": ^0.33.1 "@rocket.chat/fuselage-polyfills": ~0.31.25 - "@rocket.chat/fuselage-toastbar": ^0.31.26 + "@rocket.chat/fuselage-toastbar": ^0.32.0 "@rocket.chat/fuselage-tokens": ^0.33.1 "@rocket.chat/fuselage-ui-kit": "workspace:~" "@rocket.chat/icons": ^0.36.0