refactor: Types of Meteor methods (3/N) (#28431)

pull/26662/head^2
Tasso Evangelista 3 years ago committed by GitHub
parent ddf677cde3
commit d48a70f43f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 356
      apps/meteor/app/channel-settings/server/methods/saveRoomSettings.ts
  2. 24
      apps/meteor/app/lib/client/methods/sendMessage.ts
  3. 27
      apps/meteor/app/lib/server/methods/saveSettings.ts
  4. 23
      apps/meteor/app/lib/server/methods/sendMessage.ts
  5. 10
      apps/meteor/app/mail-messages/server/methods/sendMail.ts
  6. 2
      apps/meteor/app/utils/server/lib/normalizeMessagesForUser.ts
  7. 26
      apps/meteor/server/methods/loadSurroundingMessages.ts
  8. 18
      apps/meteor/server/methods/logoutCleanUp.js
  9. 27
      apps/meteor/server/methods/logoutCleanUp.ts
  10. 11
      apps/meteor/server/methods/openRoom.ts
  11. 21
      apps/meteor/server/methods/readThreads.ts
  12. 18
      apps/meteor/server/methods/reportMessage.ts
  13. 10
      apps/meteor/server/methods/roomNameExists.ts
  14. 50
      apps/meteor/server/methods/saveUserPreferences.ts
  15. 26
      packages/ui-contexts/src/ServerContext/methods.ts
  16. 3
      packages/ui-contexts/src/ServerContext/methods/message/reportMessage.ts
  17. 3
      packages/ui-contexts/src/ServerContext/methods/roomNameExists.ts
  18. 34
      packages/ui-contexts/src/ServerContext/methods/saveRoomSettings.ts
  19. 9
      packages/ui-contexts/src/ServerContext/methods/saveSettings.ts
  20. 36
      packages/ui-contexts/src/ServerContext/methods/saveUserPreferences.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<void>;
};
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<RoomSettings>): Promise<{ result: true; rid: IRoom['_id'] }>;
saveRoomSettings<RoomSettingName extends keyof RoomSettings>(
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 = <TRoomSetting extends keyof RoomSettings>(
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<TRoomSetting extends keyof RoomSettings>(
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<RoomSettings>): Promise<{ result: true; rid: IRoom['_id'] }>;
async function saveRoomSettings<RoomSettingName extends keyof RoomSettings>(
rid: IRoom['_id'],
setting: RoomSettingName,
value: RoomSettings[RoomSettingName],
): Promise<{ result: true; rid: IRoom['_id'] }>;
async function saveRoomSettings(
rid: IRoom['_id'],
settings: Partial<RoomSettings> | 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<ServerMethods>({
saveRoomSettings,
});

@ -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<ServerMethods>({
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);
});

@ -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<boolean>;
}
}
Meteor.methods<ServerMethods>({
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;
}, {}),

@ -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<IMessage, 'rid'>) {
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<IMessage, '_id' | 'rid' | 'msg'>): any;
}
}
Meteor.methods<ServerMethods>({
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');
},
});

@ -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<ServerMethods>({
'Mailer.sendMail'(from, subject, body, dryrun, query) {
methodDeprecationLogger.warn('Mailer.sendMail will be deprecated in future versions of Rocket.Chat');

@ -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);

@ -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<IMessage, '_id' | 'rid'> & { ts?: Date },
limit?: number,
):
| {
messages: IMessage[];
moreBefore: boolean;
moreAfter: boolean;
}
| false;
}
}
Meteor.methods<ServerMethods>({
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,
};

@ -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));
},
});

@ -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<void>;
}
}
Meteor.methods<ServerMethods>({
async logoutCleanUp(user) {
check(user, Object);
Meteor.defer(() => {
callbacks.run('afterLogoutCleanUp', user);
});
// App IPostUserLogout event hook
await Apps.triggerEvent(AppEvents.IPostUserLoggedOut, user);
},
});

@ -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<ServerMethods>({
openRoom(rid) {
check(rid, String);

@ -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<ServerMethods>({
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 });
}
},
});

@ -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<boolean>;
}
}
Meteor.methods<ServerMethods>({
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;
},

@ -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<ServerMethods>({
roomNameExists(roomName) {
check(roomName, String);

@ -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<UserPreferences>): boolean;
}
}
Meteor.methods<ServerMethods>({
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);

@ -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<IMessage, '_id' | 'rid'> & { 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<IMessage, '_id' | 'rid' | 'msg'>) => any;
'setAdminStatus': (...args: any[]) => any;
'setAvatarFromService': (...args: any[]) => any;
'setReaction': (reaction: string, mid: IMessage['_id']) => void;

@ -1,3 +0,0 @@
import type { IMessage } from '@rocket.chat/core-typings';
export type ReportMessageMethod = (messageId: IMessage['_id'], description: string) => true;

@ -1,3 +0,0 @@
import type { IRoom } from '@rocket.chat/core-typings';
export type RoomNameExistsMethod = (name: IRoom['name']) => boolean;

@ -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<RoomSettings>): { result: true; rid: IRoom['_id'] };
<RoomSettingName extends keyof RoomSettings>(rid: IRoom['_id'], setting: RoomSettingName, value: RoomSettings[RoomSettingName]): {
result: true;
rid: IRoom['_id'];
};
};

@ -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;

@ -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<UserPreferences>) => boolean;
Loading…
Cancel
Save