diff --git a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.js b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts similarity index 57% rename from apps/meteor/app/channel-settings/server/methods/saveRoomSettings.js rename to apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts index 18b9dc86d90..27c07ecaeac 100644 --- a/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.js +++ b/apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts @@ -1,12 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { Match } from 'meteor/check'; +import type { IRoom, IRoomWithRetentionPolicy, IUser } from '@rocket.chat/core-typings'; import { TEAM_TYPE } from '@rocket.chat/core-typings'; import { Team } from '@rocket.chat/core-services'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { setRoomAvatar } from '../../../lib/server/functions/setRoomAvatar'; import { hasPermission } from '../../../authorization/server'; import { Rooms } from '../../../models/server'; -import { callbacks } from '../../../../lib/callbacks'; import { saveRoomName } from '../functions/saveRoomName'; import { saveRoomTopic } from '../functions/saveRoomTopic'; import { saveRoomAnnouncement } from '../functions/saveRoomAnnouncement'; @@ -21,32 +22,47 @@ import { saveStreamingOptions } from '../functions/saveStreamingOptions'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; import { RoomSettingsEnum } from '../../../../definition/IRoomTypeConfig'; -const fields = [ - 'roomAvatar', - 'featured', - 'roomName', - 'roomTopic', - 'roomAnnouncement', - 'roomCustomFields', - 'roomDescription', - 'roomType', - 'readOnly', - 'reactWhenReadOnly', - 'systemMessages', - 'default', - 'joinCode', - 'streamingOptions', - 'retentionEnabled', - 'retentionMaxAge', - 'retentionExcludePinned', - 'retentionFilesOnly', - 'retentionIgnoreThreads', - 'retentionOverrideGlobal', - 'encrypted', - 'favorite', -]; +type RoomSettings = { + roomAvatar: string; + featured: unknown; + roomName: string | undefined; + roomTopic: unknown; + roomAnnouncement: unknown; + roomCustomFields: unknown; + roomDescription: unknown; + roomType: unknown; + readOnly: unknown; + reactWhenReadOnly: unknown; + systemMessages: unknown; + default: unknown; + joinCode: unknown; + streamingOptions: unknown; + retentionEnabled: unknown; + retentionMaxAge: unknown; + retentionExcludePinned: unknown; + retentionFilesOnly: unknown; + retentionIgnoreThreads: unknown; + retentionOverrideGlobal: unknown; + encrypted: boolean; + favorite: { + favorite: unknown; + defaultValue: unknown; + }; +}; + +type RoomSettingsValidators = { + [TRoomSetting in keyof RoomSettings]?: (params: { + userId: IUser['_id']; + value: RoomSettings[TRoomSetting]; + room: IRoom; + rid: IRoom['_id']; + }) => void; +}; -const validators = { +const hasRetentionPolicy = (room: IRoom & { retention?: any }): room is IRoomWithRetentionPolicy => + 'retention' in room && room.retention !== undefined; + +const validators: RoomSettingsValidators = { default({ userId }) { if (!hasPermission(userId, 'view-room-administration')) { throw new Meteor.Error('error-action-not-allowed', 'Viewing room administration is not allowed', { @@ -100,6 +116,13 @@ const validators = { } }, retentionEnabled({ userId, value, room, rid }) { + if (!hasRetentionPolicy(room)) { + throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', { + method: 'saveRoomSettings', + action: 'Editing_room', + }); + } + if (!hasPermission(userId, 'edit-room-retention-policy', rid) && value !== room.retention.enabled) { throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', { method: 'saveRoomSettings', @@ -108,6 +131,13 @@ const validators = { } }, retentionMaxAge({ userId, value, room, rid }) { + if (!hasRetentionPolicy(room)) { + throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', { + method: 'saveRoomSettings', + action: 'Editing_room', + }); + } + if (!hasPermission(userId, 'edit-room-retention-policy', rid) && value !== room.retention.maxAge) { throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', { method: 'saveRoomSettings', @@ -116,6 +146,13 @@ const validators = { } }, retentionExcludePinned({ userId, value, room, rid }) { + if (!hasRetentionPolicy(room)) { + throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', { + method: 'saveRoomSettings', + action: 'Editing_room', + }); + } + if (!hasPermission(userId, 'edit-room-retention-policy', rid) && value !== room.retention.excludePinned) { throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', { method: 'saveRoomSettings', @@ -124,6 +161,13 @@ const validators = { } }, retentionFilesOnly({ userId, value, room, rid }) { + if (!hasRetentionPolicy(room)) { + throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', { + method: 'saveRoomSettings', + action: 'Editing_room', + }); + } + if (!hasPermission(userId, 'edit-room-retention-policy', rid) && value !== room.retention.filesOnly) { throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', { method: 'saveRoomSettings', @@ -132,6 +176,13 @@ const validators = { } }, retentionIgnoreThreads({ userId, value, room, rid }) { + if (!hasRetentionPolicy(room)) { + throw new Meteor.Error('error-action-not-allowed', 'Room does not have retention policy', { + method: 'saveRoomSettings', + action: 'Editing_room', + }); + } + if (!hasPermission(userId, 'edit-room-retention-policy', rid) && value !== room.retention.ignoreThreads) { throw new Meteor.Error('error-action-not-allowed', 'Editing room retention policy is not allowed', { method: 'saveRoomSettings', @@ -149,14 +200,28 @@ const validators = { }, }; -const settingSavers = { +type RoomSettingsSavers = { + [TRoomSetting in keyof RoomSettings]?: (params: { + userId: IUser['_id']; + user: IUser; + value: RoomSettings[TRoomSetting]; + room: IRoom; + rid: IRoom['_id']; + }) => void | Promise; +}; + +const settingSavers: RoomSettingsSavers = { roomName({ value, rid, user, room }) { if (!Promise.await(saveRoomName(rid, value, user))) { return; } if (room.teamId && room.teamMain) { - Team.update(user._id, room.teamId, { name: value, updateRoom: false }); + Team.update(user._id, room.teamId, { + type: room.t === 'c' ? TEAM_TYPE.PUBLIC : TEAM_TYPE.PRIVATE, + name: value, + updateRoom: false, + }); } }, roomTopic({ value, room, rid, user }) { @@ -176,7 +241,7 @@ const settingSavers = { } }, roomCustomFields({ value, room, rid }) { - if (value !== room.customFields) { + if (value !== (room as { customFields?: unknown }).customFields) { saveRoomCustomFields(rid, value); } }, @@ -215,9 +280,9 @@ const settingSavers = { saveReactWhenReadOnly(rid, value, user); } }, - systemMessages({ value, room, rid, user }) { + systemMessages({ value, room, rid }) { if (JSON.stringify(value) !== JSON.stringify(room.sysMes)) { - saveRoomSystemMessages(rid, value, user); + saveRoomSystemMessages(rid, value); } }, joinCode({ value, rid }) { @@ -258,105 +323,172 @@ const settingSavers = { }, }; -Meteor.methods({ - async saveRoomSettings(rid, settings, value) { - const userId = Meteor.userId(); +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + saveRoomSettings(rid: IRoom['_id'], settings: Partial): Promise<{ result: true; rid: IRoom['_id'] }>; + saveRoomSettings( + rid: IRoom['_id'], + setting: RoomSettingName, + value: RoomSettings[RoomSettingName], + ): Promise<{ result: true; rid: IRoom['_id'] }>; + } +} - if (!userId) { - throw new Meteor.Error('error-invalid-user', 'Invalid user', { - function: 'RocketChat.saveRoomName', - }); - } - if (!Match.test(rid, String)) { - throw new Meteor.Error('error-invalid-room', 'Invalid room', { - method: 'saveRoomSettings', - }); - } +const fields: (keyof RoomSettings)[] = [ + 'roomAvatar', + 'featured', + 'roomName', + 'roomTopic', + 'roomAnnouncement', + 'roomCustomFields', + 'roomDescription', + 'roomType', + 'readOnly', + 'reactWhenReadOnly', + 'systemMessages', + 'default', + 'joinCode', + 'streamingOptions', + 'retentionEnabled', + 'retentionMaxAge', + 'retentionExcludePinned', + 'retentionFilesOnly', + 'retentionIgnoreThreads', + 'retentionOverrideGlobal', + 'encrypted', + 'favorite', +]; - if (typeof settings !== 'object') { - settings = { - [settings]: value, - }; - } +const validate = ( + setting: TRoomSetting, + params: { + userId: IUser['_id']; + value: RoomSettings[TRoomSetting]; + room: IRoom; + rid: IRoom['_id']; + }, +) => { + const validator = validators[setting]; + validator?.(params); +}; - if (!Object.keys(settings).every((key) => fields.includes(key))) { - throw new Meteor.Error('error-invalid-settings', 'Invalid settings provided', { - method: 'saveRoomSettings', - }); - } +async function save( + setting: TRoomSetting, + params: { + userId: IUser['_id']; + user: IUser; + value: RoomSettings[TRoomSetting]; + room: IRoom; + rid: IRoom['_id']; + }, +) { + const saver = settingSavers[setting]; + await saver?.(params); +} - const room = Rooms.findOneById(rid); +async function saveRoomSettings(rid: IRoom['_id'], settings: Partial): Promise<{ result: true; rid: IRoom['_id'] }>; +async function saveRoomSettings( + rid: IRoom['_id'], + setting: RoomSettingName, + value: RoomSettings[RoomSettingName], +): Promise<{ result: true; rid: IRoom['_id'] }>; +async function saveRoomSettings( + rid: IRoom['_id'], + settings: Partial | keyof RoomSettings, + value?: RoomSettings[keyof RoomSettings], +): Promise<{ result: true; rid: IRoom['_id'] }> { + const uid = Meteor.userId(); - if (!room) { - throw new Meteor.Error('error-invalid-room', 'Invalid room', { - method: 'saveRoomSettings', - }); - } + if (!uid) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + function: 'RocketChat.saveRoomName', + }); + } + if (!Match.test(rid, String)) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { + method: 'saveRoomSettings', + }); + } - if (!hasPermission(userId, 'edit-room', rid)) { - if (!(Object.keys(settings).includes('encrypted') && room.t === 'd')) { - throw new Meteor.Error('error-action-not-allowed', 'Editing room is not allowed', { - method: 'saveRoomSettings', - action: 'Editing_room', - }); - } - settings = { encrypted: settings.encrypted }; - } + if (typeof settings !== 'object') { + settings = { + [settings]: value, + }; + } + + if (!Object.keys(settings).every((key) => fields.includes(key as keyof typeof settings))) { + throw new Meteor.Error('error-invalid-settings', 'Invalid settings provided', { + method: 'saveRoomSettings', + }); + } - if (room.broadcast && (settings.readOnly || settings.reactWhenReadOnly)) { - throw new Meteor.Error('error-action-not-allowed', 'Editing readOnly/reactWhenReadOnly are not allowed for broadcast rooms', { + const room = Rooms.findOneById(rid) as IRoom | undefined; + + if (!room) { + throw new Meteor.Error('error-invalid-room', 'Invalid room', { + method: 'saveRoomSettings', + }); + } + + if (!hasPermission(uid, 'edit-room', rid)) { + if (!(Object.keys(settings).includes('encrypted') && room.t === 'd')) { + throw new Meteor.Error('error-action-not-allowed', 'Editing room is not allowed', { method: 'saveRoomSettings', action: 'Editing_room', }); } + settings = { encrypted: settings.encrypted }; + } - const user = Meteor.user(); - - // validations - Object.keys(settings).forEach((setting) => { - const value = settings[setting]; + if (room.broadcast && (settings.readOnly || settings.reactWhenReadOnly)) { + throw new Meteor.Error('error-action-not-allowed', 'Editing readOnly/reactWhenReadOnly are not allowed for broadcast rooms', { + method: 'saveRoomSettings', + action: 'Editing_room', + }); + } - const validator = validators[setting]; - if (validator) { - validator({ - userId, - value, - room, - rid, - }); - } + const user = Meteor.user() as IUser | null; + if (!user) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { + method: 'saveRoomSettings', + }); + } - if (setting === 'retentionOverrideGlobal') { - delete settings.retentionMaxAge; - delete settings.retentionExcludePinned; - delete settings.retentionFilesOnly; - delete settings.retentionIgnoreThreads; - } + // validations + for (const setting of Object.keys(settings) as (keyof RoomSettings)[]) { + validate(setting, { + userId: uid, + value: settings[setting], + room, + rid, }); - // saving data - for await (const setting of Object.keys(settings)) { - const value = settings[setting]; - - const saver = await settingSavers[setting]; - if (saver) { - saver({ - value, - room, - rid, - user, - }); - } + if (setting === 'retentionOverrideGlobal') { + delete settings.retentionMaxAge; + delete settings.retentionExcludePinned; + delete settings.retentionFilesOnly; + delete settings.retentionIgnoreThreads; } + } - Meteor.defer(function () { - const room = Rooms.findOneById(rid); - callbacks.run('afterSaveRoomSettings', room); + // saving data + for await (const setting of Object.keys(settings) as (keyof RoomSettings)[]) { + await save(setting, { + userId: uid, + user, + value: settings[setting], + room, + rid, }); + } - return { - result: true, - rid: room._id, - }; - }, + return { + result: true, + rid: room._id, + }; +} + +Meteor.methods({ + saveRoomSettings, }); diff --git a/apps/meteor/app/lib/client/methods/sendMessage.js b/apps/meteor/app/lib/client/methods/sendMessage.ts similarity index 61% rename from apps/meteor/app/lib/client/methods/sendMessage.js rename to apps/meteor/app/lib/client/methods/sendMessage.ts index 246c229f77b..2c0fb7ea5f2 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.js +++ b/apps/meteor/app/lib/client/methods/sendMessage.ts @@ -1,44 +1,48 @@ import { Meteor } from 'meteor/meteor'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { IMessage, IUser } from '@rocket.chat/core-typings'; import { ChatMessage, Rooms } from '../../../models/client'; -import { settings } from '../../../settings'; +import { settings } from '../../../settings/client'; import { callbacks } from '../../../../lib/callbacks'; import { t } from '../../../utils/client'; import { dispatchToastMessage } from '../../../../client/lib/toast'; import { onClientMessageReceived } from '../../../../client/lib/onClientMessageReceived'; import { trim } from '../../../../lib/utils/stringUtils'; -Meteor.methods({ +Meteor.methods({ sendMessage(message) { - if (!Meteor.userId() || trim(message.msg) === '') { + const uid = Meteor.userId(); + if (!uid || trim(message.msg) === '') { return false; } const messageAlreadyExists = message._id && ChatMessage.findOne({ _id: message._id }); if (messageAlreadyExists) { return dispatchToastMessage({ type: 'error', message: t('Message_Already_Sent') }); } - const user = Meteor.user(); + const user = Meteor.user() as IUser | null; + if (!user?.username) { + throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'sendMessage' }); + } message.ts = new Date(); message.u = { - _id: Meteor.userId(), + _id: uid, username: user.username, + ...(settings.get('UI_Use_Real_Name') && user.name && { name: user.name }), }; - if (settings.get('UI_Use_Real_Name')) { - message.u.name = user.name; - } message.temp = true; if (settings.get('Message_Read_Receipt_Enabled')) { message.unread = true; } // If the room is federated, send the message to matrix only - const { federated } = Rooms.findOne({ _id: message.rid }, { fields: { federated: 1 } }); + const federated = Rooms.findOne({ _id: message.rid }, { fields: { federated: 1 } })?.federated; if (federated) { return; } message = callbacks.run('beforeSaveMessage', message); - onClientMessageReceived(message).then(function (message) { + onClientMessageReceived(message as IMessage).then(function (message) { ChatMessage.insert(message); return callbacks.run('afterSaveMessage', message); }); diff --git a/apps/meteor/app/lib/server/methods/saveSettings.js b/apps/meteor/app/lib/server/methods/saveSettings.ts similarity index 70% rename from apps/meteor/app/lib/server/methods/saveSettings.js rename to apps/meteor/app/lib/server/methods/saveSettings.ts index 5bbfd4d2cfe..f471c5a6bfb 100644 --- a/apps/meteor/app/lib/server/methods/saveSettings.js +++ b/apps/meteor/app/lib/server/methods/saveSettings.ts @@ -1,15 +1,34 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import { Settings } from '@rocket.chat/models'; +import type { ISetting } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { hasPermission } from '../../../authorization/server'; import { getSettingPermissionId } from '../../../authorization/lib'; import { twoFactorRequired } from '../../../2fa/server/twoFactorRequired'; -Meteor.methods({ - saveSettings: twoFactorRequired(async function (params = []) { +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + saveSettings( + changes: { + _id: ISetting['_id']; + value: ISetting['value']; + }[], + ): Promise; + } +} + +Meteor.methods({ + saveSettings: twoFactorRequired(async function ( + params: { + _id: ISetting['_id']; + value: ISetting['value']; + }[] = [], + ) { const uid = Meteor.userId(); - const settingsNotAllowed = []; + const settingsNotAllowed: ISetting['_id'][] = []; if (uid === null) { throw new Meteor.Error('error-action-not-allowed', 'Editing settings is not allowed', { method: 'saveSetting', @@ -55,7 +74,7 @@ Meteor.methods({ }); } - await Promise.all(params.map(({ _id, value, editor }) => Settings.updateValueById(_id, value, editor))); + await Promise.all(params.map(({ _id, value }) => Settings.updateValueById(_id, value))); return true; }, {}), diff --git a/apps/meteor/app/lib/server/methods/sendMessage.js b/apps/meteor/app/lib/server/methods/sendMessage.ts similarity index 81% rename from apps/meteor/app/lib/server/methods/sendMessage.js rename to apps/meteor/app/lib/server/methods/sendMessage.ts index 9b7739e68e3..f66c31c036c 100644 --- a/apps/meteor/app/lib/server/methods/sendMessage.js +++ b/apps/meteor/app/lib/server/methods/sendMessage.ts @@ -3,6 +3,8 @@ import { check } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import moment from 'moment'; import { api } from '@rocket.chat/core-services'; +import type { AtLeast, IMessage, IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { hasPermission, canSendMessage } from '../../../authorization/server'; import { metrics } from '../../../metrics/server'; @@ -12,7 +14,7 @@ import { sendMessage } from '../functions'; import { RateLimiter } from '../lib'; import { SystemLogger } from '../../../../server/lib/logger/system'; -export function executeSendMessage(uid, message) { +export function executeSendMessage(uid: IUser['_id'], message: AtLeast) { if (message.tshow && !message.tmid) { throw new Meteor.Error('invalid-params', 'tshow provided but missing tmid', { method: 'sendMessage', @@ -26,7 +28,7 @@ export function executeSendMessage(uid, message) { } if (message.ts) { - const tsDiff = Math.abs(moment(message.ts).diff()); + const tsDiff = Math.abs(moment(message.ts).diff(Date.now())); if (tsDiff > 60000) { throw new Meteor.Error('error-message-ts-out-of-sync', 'Message timestamp is out of sync', { method: 'sendMessage', @@ -41,7 +43,7 @@ export function executeSendMessage(uid, message) { } if (message.msg) { - if (message.msg.length > settings.get('Message_MaxAllowedSize')) { + if (message.msg.length > (settings.get('Message_MaxAllowedSize') ?? 0)) { throw new Meteor.Error('error-message-size-exceeded', 'Message size exceeds Message_MaxAllowedSize', { method: 'sendMessage', }); @@ -73,7 +75,7 @@ export function executeSendMessage(uid, message) { metrics.messagesSent.inc(); // TODO This line needs to be moved to it's proper place. See the comments on: https://github.com/RocketChat/Rocket.Chat/pull/5736 return sendMessage(user, message, room, false); - } catch (err) { + } catch (err: any) { SystemLogger.error({ msg: 'Error sending message:', err }); const errorMessage = typeof err === 'string' ? err : err.error || err.message; @@ -89,7 +91,14 @@ export function executeSendMessage(uid, message) { } } -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + sendMessage(message: AtLeast): any; + } +} + +Meteor.methods({ sendMessage(message) { check(message, Object); @@ -102,7 +111,7 @@ Meteor.methods({ try { return executeSendMessage(uid, message); - } catch (error) { + } catch (error: any) { if ((error.error || error.message) === 'error-not-allowed') { throw new Meteor.Error(error.error || error.message, error.reason, { method: 'sendMessage', @@ -113,7 +122,7 @@ Meteor.methods({ }); // Limit a user, who does not have the "bot" role, to sending 5 msgs/second RateLimiter.limitMethod('sendMessage', 5, 1000, { - userId(userId) { + userId(userId: IUser['_id']) { return !hasPermission(userId, 'send-many-messages'); }, }); diff --git a/apps/meteor/app/mail-messages/server/methods/sendMail.ts b/apps/meteor/app/mail-messages/server/methods/sendMail.ts index 48253c07ab8..4ed5e9dce7e 100644 --- a/apps/meteor/app/mail-messages/server/methods/sendMail.ts +++ b/apps/meteor/app/mail-messages/server/methods/sendMail.ts @@ -1,10 +1,18 @@ import { Meteor } from 'meteor/meteor'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Mailer } from '../lib/Mailer'; import { hasPermission } from '../../../authorization/server'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + 'Mailer.sendMail'(from: string, subject: string, body: string, dryrun?: boolean, query?: string): any; + } +} + +Meteor.methods({ 'Mailer.sendMail'(from, subject, body, dryrun, query) { methodDeprecationLogger.warn('Mailer.sendMail will be deprecated in future versions of Rocket.Chat'); diff --git a/apps/meteor/app/utils/server/lib/normalizeMessagesForUser.ts b/apps/meteor/app/utils/server/lib/normalizeMessagesForUser.ts index 65dd7a9630f..1d1aa7e6b6b 100644 --- a/apps/meteor/app/utils/server/lib/normalizeMessagesForUser.ts +++ b/apps/meteor/app/utils/server/lib/normalizeMessagesForUser.ts @@ -28,7 +28,7 @@ export const normalizeMessagesForUser = (messages: IMessage[], uid: string): IMe messages.forEach((message) => { message = filterStarred(message, uid); - if (!message.u || !message.u.username) { + if (!message.u?.username) { return; } usernames.add(message.u.username); diff --git a/apps/meteor/server/methods/loadSurroundingMessages.js b/apps/meteor/server/methods/loadSurroundingMessages.ts similarity index 67% rename from apps/meteor/server/methods/loadSurroundingMessages.js rename to apps/meteor/server/methods/loadSurroundingMessages.ts index f0ea978735d..e89e375b141 100644 --- a/apps/meteor/server/methods/loadSurroundingMessages.js +++ b/apps/meteor/server/methods/loadSurroundingMessages.ts @@ -1,11 +1,29 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { canAccessRoomId } from '../../app/authorization/server'; import { Messages } from '../../app/models/server'; import { normalizeMessagesForUser } from '../../app/utils/server/lib/normalizeMessagesForUser'; -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + loadSurroundingMessages( + message: Pick & { ts?: Date }, + limit?: number, + ): + | { + messages: IMessage[]; + moreBefore: boolean; + moreAfter: boolean; + } + | false; + } +} + +Meteor.methods({ loadSurroundingMessages(message, limit = 50) { check(message, Object); check(limit, Number); @@ -16,7 +34,7 @@ Meteor.methods({ }); } - const fromId = Meteor.userId(); + const fromId = Meteor.userId() ?? undefined; if (!message._id) { return false; @@ -24,7 +42,7 @@ Meteor.methods({ message = Messages.findOneById(message._id); - if (!message || !message.rid) { + if (!message?.rid) { return false; } @@ -60,7 +78,7 @@ Meteor.methods({ messages.push(...afterMessages); return { - messages: normalizeMessagesForUser(messages, fromId), + messages: fromId ? normalizeMessagesForUser(messages, fromId) : messages, moreBefore, moreAfter, }; diff --git a/apps/meteor/server/methods/logoutCleanUp.js b/apps/meteor/server/methods/logoutCleanUp.js deleted file mode 100644 index 48e733de80b..00000000000 --- a/apps/meteor/server/methods/logoutCleanUp.js +++ /dev/null @@ -1,18 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; - -import { callbacks } from '../../lib/callbacks'; -import { AppEvents, Apps } from '../../ee/server/apps/orchestrator'; - -Meteor.methods({ - logoutCleanUp(user) { - check(user, Object); - - Meteor.defer(function () { - callbacks.run('afterLogoutCleanUp', user); - }); - - // App IPostUserLogout event hook - Promise.await(Apps.triggerEvent(AppEvents.IPostUserLoggedOut, user)); - }, -}); diff --git a/apps/meteor/server/methods/logoutCleanUp.ts b/apps/meteor/server/methods/logoutCleanUp.ts new file mode 100644 index 00000000000..856165a72ee --- /dev/null +++ b/apps/meteor/server/methods/logoutCleanUp.ts @@ -0,0 +1,27 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import type { IUser } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; + +import { callbacks } from '../../lib/callbacks'; +import { AppEvents, Apps } from '../../ee/server/apps/orchestrator'; + +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + logoutCleanUp(user: IUser): Promise; + } +} + +Meteor.methods({ + async logoutCleanUp(user) { + check(user, Object); + + Meteor.defer(() => { + callbacks.run('afterLogoutCleanUp', user); + }); + + // App IPostUserLogout event hook + await Apps.triggerEvent(AppEvents.IPostUserLoggedOut, user); + }, +}); diff --git a/apps/meteor/server/methods/openRoom.js b/apps/meteor/server/methods/openRoom.ts similarity index 51% rename from apps/meteor/server/methods/openRoom.js rename to apps/meteor/server/methods/openRoom.ts index f3ae621096c..58b73ffeb35 100644 --- a/apps/meteor/server/methods/openRoom.js +++ b/apps/meteor/server/methods/openRoom.ts @@ -1,9 +1,18 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Subscriptions } from '../../app/models/server'; -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + openRoom(rid: IRoom['_id']): ISubscription; + } +} + +Meteor.methods({ openRoom(rid) { check(rid, String); diff --git a/apps/meteor/server/methods/readThreads.js b/apps/meteor/server/methods/readThreads.ts similarity index 58% rename from apps/meteor/server/methods/readThreads.js rename to apps/meteor/server/methods/readThreads.ts index afab0fe355e..bb21100571b 100644 --- a/apps/meteor/server/methods/readThreads.js +++ b/apps/meteor/server/methods/readThreads.ts @@ -1,5 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { settings } from '../../app/settings/server'; import { Messages, Rooms } from '../../app/models/server'; @@ -7,7 +9,14 @@ import { canAccessRoom } from '../../app/authorization/server'; import { readThread } from '../../app/threads/server/functions'; import { callbacks } from '../../lib/callbacks'; -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + readThreads(tmid: IMessage['_id']): void; + } +} + +Meteor.methods({ readThreads(tmid) { check(tmid, String); @@ -22,7 +31,7 @@ Meteor.methods({ return; } - const user = Meteor.user(); + const user = Meteor.user() ?? undefined; const room = Rooms.findOneById(thread.rid); @@ -30,8 +39,10 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getThreadMessages' }); } - callbacks.run('beforeReadMessages', thread.rid, user._id); - readThread({ userId: user._id, rid: thread.rid, tmid }); - callbacks.runAsync('afterReadMessages', room._id, { uid: user._id, tmid }); + callbacks.run('beforeReadMessages', thread.rid, user?._id); + readThread({ userId: user?._id, rid: thread.rid, tmid }); + if (user?._id) { + callbacks.runAsync('afterReadMessages', room._id, { uid: user._id, tmid }); + } }, }); diff --git a/apps/meteor/server/methods/reportMessage.js b/apps/meteor/server/methods/reportMessage.ts similarity index 74% rename from apps/meteor/server/methods/reportMessage.js rename to apps/meteor/server/methods/reportMessage.ts index 61b4ae70ee1..9971bb6bb96 100644 --- a/apps/meteor/server/methods/reportMessage.js +++ b/apps/meteor/server/methods/reportMessage.ts @@ -1,17 +1,28 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { Reports, Rooms } from '@rocket.chat/models'; +import type { IMessage } from '@rocket.chat/core-typings'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Messages } from '../../app/models/server'; import { canAccessRoomAsync } from '../../app/authorization/server/functions/canAccessRoom'; import { AppEvents, Apps } from '../../ee/server/apps'; -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + reportMessage(messageId: IMessage['_id'], description: string): Promise; + } +} + +Meteor.methods({ async reportMessage(messageId, description) { check(messageId, String); check(description, String); - if (!Meteor.userId()) { + const uid = Meteor.userId(); + + if (!uid) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'reportMessage', }); @@ -30,7 +41,6 @@ Meteor.methods({ }); } - const uid = Meteor.userId(); const { rid } = message; // If the user can't access the room where the message is, report that the message id is invalid const room = await Rooms.findOneById(rid); @@ -42,7 +52,7 @@ Meteor.methods({ await Reports.createWithMessageDescriptionAndUserId(message, description, uid); - Promise.await(Apps.triggerEvent(AppEvents.IPostMessageReported, message, Meteor.user(), description)); + await Apps.triggerEvent(AppEvents.IPostMessageReported, message, Meteor.user(), description); return true; }, diff --git a/apps/meteor/server/methods/roomNameExists.ts b/apps/meteor/server/methods/roomNameExists.ts index 01885be7760..20d97771a36 100644 --- a/apps/meteor/server/methods/roomNameExists.ts +++ b/apps/meteor/server/methods/roomNameExists.ts @@ -1,10 +1,18 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Rooms } from '../../app/models/server'; import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; -Meteor.methods({ +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + roomNameExists(roomName: string): boolean; + } +} + +Meteor.methods({ roomNameExists(roomName) { check(roomName, String); diff --git a/apps/meteor/server/methods/saveUserPreferences.js b/apps/meteor/server/methods/saveUserPreferences.ts similarity index 74% rename from apps/meteor/server/methods/saveUserPreferences.js rename to apps/meteor/server/methods/saveUserPreferences.ts index 194f58139ed..ffe16762ca5 100644 --- a/apps/meteor/server/methods/saveUserPreferences.js +++ b/apps/meteor/server/methods/saveUserPreferences.ts @@ -1,9 +1,53 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; +import type { ServerMethods } from '@rocket.chat/ui-contexts'; +import type { IUser } from '@rocket.chat/core-typings'; import { Users, Subscriptions } from '../../app/models/server'; -Meteor.methods({ +type UserPreferences = { + language: string; + newRoomNotification: string; + newMessageNotification: string; + clockMode: number; + useEmojis: boolean; + convertAsciiEmoji: boolean; + saveMobileBandwidth: boolean; + collapseMediaByDefault: boolean; + autoImageLoad: boolean; + emailNotificationMode: string; + unreadAlert: boolean; + notificationsSoundVolume: number; + desktopNotifications: string; + pushNotifications: string; + enableAutoAway: boolean; + highlights: string[]; + hideUsernames: boolean; + hideRoles: boolean; + displayAvatars: boolean; + hideFlexTab: boolean; + sendOnEnter: string; + idleTimeLimit: number; + sidebarShowFavorites: boolean; + sidebarShowUnread: boolean; + sidebarSortby: string; + sidebarViewMode: string; + sidebarDisplayAvatar: boolean; + sidebarGroupByType: boolean; + muteFocusedConversations: boolean; + dontAskAgainList: { action: string; label: string }[]; + themeAppearence: 'auto' | 'light' | 'dark'; + receiveLoginDetectionEmail: boolean; +}; + +declare module '@rocket.chat/ui-contexts' { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface ServerMethods { + saveUserPreferences(preferences: Partial): boolean; + } +} + +Meteor.methods({ saveUserPreferences(settings) { const keys = { language: Match.Optional(String), @@ -39,7 +83,7 @@ Meteor.methods({ omnichannelTranscriptPDF: Match.Optional(Boolean), }; check(settings, Match.ObjectIncluding(keys)); - const user = Meteor.user(); + const user = Meteor.user() as IUser | null; if (!user) { return false; @@ -49,7 +93,7 @@ Meteor.methods({ desktopNotifications: oldDesktopNotifications, pushNotifications: oldMobileNotifications, emailNotificationMode: oldEmailNotifications, - } = (user.settings && user.settings.preferences) || {}; + } = user.settings?.preferences || {}; if (user.settings == null) { Users.clearSettings(user._id); diff --git a/packages/ui-contexts/src/ServerContext/methods.ts b/packages/ui-contexts/src/ServerContext/methods.ts index 9e63708742d..9a6cdca13ca 100644 --- a/packages/ui-contexts/src/ServerContext/methods.ts +++ b/packages/ui-contexts/src/ServerContext/methods.ts @@ -1,5 +1,4 @@ import type { - AtLeast, IMessage, IPermission, IRoom, @@ -13,11 +12,6 @@ import type { import type { TranslationKey } from '../TranslationContext'; import type { GetReadReceiptsMethod } from './methods/getReadReceipts'; import type { UnsubscribeMethod as MailerUnsubscribeMethod } from './methods/mailer/unsubscribe'; -import type { RoomNameExistsMethod } from './methods/roomNameExists'; -import type { SaveRoomSettingsMethod } from './methods/saveRoomSettings'; -import type { SaveSettingsMethod } from './methods/saveSettings'; -import type { SaveUserPreferencesMethod } from './methods/saveUserPreferences'; -import type { ReportMessageMethod } from './methods/message/reportMessage'; // TODO: frontend chapter day - define methods @@ -42,42 +36,22 @@ export interface ServerMethods { 'ignoreUser': (...args: any[]) => any; 'insertOrUpdateUserStatus': (...args: any[]) => any; 'leaveRoom': (...args: any[]) => any; - 'loadSurroundingMessages': ( - message: Pick & { ts?: Date }, - limit?: number, - ) => - | { - messages: IMessage[]; - moreBefore: boolean; - moreAfter: boolean; - } - | false; - 'logoutCleanUp': (user: IUser) => void; - 'Mailer.sendMail': (from: string, subject: string, body: string, dryrun: boolean, query: string) => any; 'muteUserInRoom': (...args: any[]) => any; - 'openRoom': (rid: IRoom['_id']) => ISubscription; 'personalAccessTokens:generateToken': (...args: any[]) => any; 'personalAccessTokens:regenerateToken': (...args: any[]) => any; 'personalAccessTokens:removeToken': (...args: any[]) => any; 'e2e.requestSubscriptionKeys': (...args: any[]) => any; 'readMessages': (...args: any[]) => any; - 'readThreads': (tmid: IMessage['_id']) => void; 'refreshOAuthService': (...args: any[]) => any; 'registerUser': (...args: any[]) => any; 'removeOAuthService': (...args: any[]) => any; 'removeCannedResponse': (...args: any[]) => any; 'replayOutgoingIntegration': (...args: any[]) => any; - 'reportMessage': ReportMessageMethod; 'requestDataDownload': (...args: any[]) => any; 'resetPassword': (...args: any[]) => any; - 'roomNameExists': RoomNameExistsMethod; 'saveCannedResponse': (...args: any[]) => any; - 'saveRoomSettings': SaveRoomSettingsMethod; - 'saveSettings': SaveSettingsMethod; - 'saveUserPreferences': SaveUserPreferencesMethod; 'saveUserProfile': (...args: any[]) => any; 'sendConfirmationEmail': (...args: any[]) => any; - 'sendMessage': (message: AtLeast) => any; 'setAdminStatus': (...args: any[]) => any; 'setAvatarFromService': (...args: any[]) => any; 'setReaction': (reaction: string, mid: IMessage['_id']) => void; diff --git a/packages/ui-contexts/src/ServerContext/methods/message/reportMessage.ts b/packages/ui-contexts/src/ServerContext/methods/message/reportMessage.ts deleted file mode 100644 index d08d5e088b0..00000000000 --- a/packages/ui-contexts/src/ServerContext/methods/message/reportMessage.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { IMessage } from '@rocket.chat/core-typings'; - -export type ReportMessageMethod = (messageId: IMessage['_id'], description: string) => true; diff --git a/packages/ui-contexts/src/ServerContext/methods/roomNameExists.ts b/packages/ui-contexts/src/ServerContext/methods/roomNameExists.ts deleted file mode 100644 index 3a753dd24b5..00000000000 --- a/packages/ui-contexts/src/ServerContext/methods/roomNameExists.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; - -export type RoomNameExistsMethod = (name: IRoom['name']) => boolean; diff --git a/packages/ui-contexts/src/ServerContext/methods/saveRoomSettings.ts b/packages/ui-contexts/src/ServerContext/methods/saveRoomSettings.ts deleted file mode 100644 index 33191cb463a..00000000000 --- a/packages/ui-contexts/src/ServerContext/methods/saveRoomSettings.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { IRoom } from '@rocket.chat/core-typings'; - -type RoomSettings = { - roomAvatar: unknown; - featured: unknown; - roomName: unknown; - roomTopic: unknown; - roomAnnouncement: unknown; - roomCustomFields: unknown; - roomDescription: unknown; - roomType: unknown; - readOnly: unknown; - reactWhenReadOnly: unknown; - systemMessages: unknown; - default: unknown; - joinCode: unknown; - streamingOptions: unknown; - retentionEnabled: unknown; - retentionMaxAge: unknown; - retentionExcludePinned: unknown; - retentionFilesOnly: unknown; - retentionIgnoreThreads: unknown; - retentionOverrideGlobal: unknown; - encrypted: boolean; - favorite: unknown; -}; - -export type SaveRoomSettingsMethod = { - (rid: IRoom['_id'], settings: Partial): { result: true; rid: IRoom['_id'] }; - (rid: IRoom['_id'], setting: RoomSettingName, value: RoomSettings[RoomSettingName]): { - result: true; - rid: IRoom['_id']; - }; -}; diff --git a/packages/ui-contexts/src/ServerContext/methods/saveSettings.ts b/packages/ui-contexts/src/ServerContext/methods/saveSettings.ts deleted file mode 100644 index 1a783d8719c..00000000000 --- a/packages/ui-contexts/src/ServerContext/methods/saveSettings.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { ISetting } from '@rocket.chat/core-typings'; - -type SettingChange = { - _id: ISetting['_id']; - value: unknown; - editor?: unknown; -}; - -export type SaveSettingsMethod = (changes: SettingChange[]) => true; diff --git a/packages/ui-contexts/src/ServerContext/methods/saveUserPreferences.ts b/packages/ui-contexts/src/ServerContext/methods/saveUserPreferences.ts deleted file mode 100644 index ae1df52b409..00000000000 --- a/packages/ui-contexts/src/ServerContext/methods/saveUserPreferences.ts +++ /dev/null @@ -1,36 +0,0 @@ -type UserPreferences = { - language: string; - newRoomNotification: string; - newMessageNotification: string; - clockMode: number; - useEmojis: boolean; - convertAsciiEmoji: boolean; - saveMobileBandwidth: boolean; - collapseMediaByDefault: boolean; - autoImageLoad: boolean; - emailNotificationMode: string; - unreadAlert: boolean; - notificationsSoundVolume: number; - desktopNotifications: string; - pushNotifications: string; - enableAutoAway: boolean; - highlights: string[]; - hideUsernames: boolean; - hideRoles: boolean; - displayAvatars: boolean; - hideFlexTab: boolean; - sendOnEnter: string; - idleTimeLimit: number; - sidebarShowFavorites: boolean; - sidebarShowUnread: boolean; - sidebarSortby: string; - sidebarViewMode: string; - sidebarDisplayAvatar: boolean; - sidebarGroupByType: boolean; - muteFocusedConversations: boolean; - dontAskAgainList: { action: string; label: string }[]; - themeAppearence: 'auto' | 'light' | 'dark'; - receiveLoginDetectionEmail: boolean; -}; - -export type SaveUserPreferencesMethod = (preferences: Partial) => boolean;