Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/meteor/app/autotranslate/client/lib/autotranslate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,12 @@ export const AutoTranslate = {

const loadProviders = async () => {
try {
[this.providersMetadata, this.supportedLanguages] = await Promise.all([
const [providersMetadata, supportedLanguagesResponse] = await Promise.all([
sdk.call('autoTranslate.getProviderUiMetadata'),
sdk.call('autoTranslate.getSupportedLanguages', 'en'),
sdk.rest.get('/v1/autotranslate.getSupportedLanguages', { targetLanguage: 'en' }),
]);
this.providersMetadata = providersMetadata;
this.supportedLanguages = supportedLanguagesResponse.languages;
} catch (e: unknown) {
// Avoid unwanted error message on UI when autotranslate is disabled while fetching data
console.error((e as Error).message);
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/slashcommands-open/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ slashCommands.add({
roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters());
}

if (type && type.indexOf('d') === -1) {
if (type?.indexOf('d') === -1) {
return;
}
try {
await sdk.call('createDirectMessage', room);
await sdk.rest.post('/v1/im.create', { username: room });
const subscription = Subscriptions.state.find(predicate);
if (!subscription) {
return;
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/slashcommands-status/client/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ slashCommands.add({
}

try {
await sdk.call('setUserStatus', undefined, params);
await sdk.rest.post('/v1/users.setStatus', { message: params });
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/slashcommands-topic/client/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ slashCommands.add({
callback: async function Topic({ params, message }: SlashCommandCallbackParams<'topic'>): Promise<void> {
if (hasPermission('edit-room', message.rid)) {
try {
await sdk.call('saveRoomSettings', message.rid, 'roomTopic', params);
await sdk.rest.post('/v1/rooms.saveRoomSettings', { rid: message.rid, roomTopic: params });
await clientCallbacks.run('roomTopicChanged', Rooms.state.get(message.rid));
} catch (error: unknown) {
dispatchToastMessage({ type: 'error', message: error });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';

import type { IGame } from './GameCenter';
import { sdk } from '../../../app/utils/client/lib/SDKClient';
import UserAutoCompleteMultiple from '../../components/UserAutoCompleteMultiple';
import { useOpenedRoom } from '../../lib/RoomManager';
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
Expand All @@ -30,14 +31,14 @@ const GameCenterInvitePlayersModal = ({ game, onClose }: IGameCenterInvitePlayer
const privateGroupName = `${name.replace(/\s/g, '-')}-${Random.id(10)}`;

try {
const result = await callWithErrorHandling('createPrivateGroup' as any, privateGroupName, users);
const { group } = await sdk.rest.post('/v1/groups.create', { name: privateGroupName, members: users });

roomCoordinator.openRouteLink(result.t, result);
roomCoordinator.openRouteLink(group.t, { rid: group._id, name: group.name });

if (openedRoom === result.rid) {
callWithErrorHandling('sendMessage', {
if (openedRoom === group._id) {
await callWithErrorHandling('sendMessage', {
_id: Random.id(),
rid: result.rid,
rid: group._id,
msg: t('Apps_Game_Center_Play_Game_Together', { name }),
});
}
Comment on lines +38 to 44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unreachable code: invite message will never be sent.

The condition openedRoom === group._id can never be true. openedRoom is captured from useOpenedRoom() at render time (the room open before the modal), while group._id is the ID of a group that was just created in the preceding line. A newly created group cannot match a previously opened room.

This appears to be a regression—the invite message is now unreachable.

Proposed fix: send the message unconditionally to the new group
 			roomCoordinator.openRouteLink(group.t, { rid: group._id, name: group.name });
 
-			if (openedRoom === group._id) {
-				void sdk.call('sendMessage', {
-					_id: Random.id(),
-					rid: group._id,
-					msg: t('Apps_Game_Center_Play_Game_Together', { name }),
-				});
-			}
+			void sdk.call('sendMessage', {
+				_id: Random.id(),
+				rid: group._id,
+				msg: t('Apps_Game_Center_Play_Game_Together', { name }),
+			});
 			onClose();

If the original intent was conditional sending, please clarify the expected behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/meteor/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx` around
lines 37 - 43, The check using openedRoom (from useOpenedRoom()) prevents
sending the invite to the newly created group because openedRoom cannot equal
the new group._id; remove the conditional and call sdk.call('sendMessage', {
_id: Random.id(), rid: group._id, msg: t('Apps_Game_Center_Play_Game_Together',
{ name }) }) unconditionally after the group is created (or, if conditional
behavior was intended, update the condition to reflect the correct runtime value
instead of openedRoom). Ensure the sendMessage invocation uses Random.id(),
group._id, and the same i18n key t('Apps_Game_Center_Play_Game_Together', { name
}).

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IMessage, ISubscription, IRoom } from '@rocket.chat/core-typings';
import { useMethod, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useEndpoint, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';

import { AutoTranslate } from '../../../../app/autotranslate/client';
Expand All @@ -15,7 +15,7 @@ export const useTranslateAction = (
const user = useUser();
const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false);
const canAutoTranslate = usePermission('auto-translate');
const translateMessage = useMethod('autoTranslate.translateMessage');
const translateMessage = useEndpoint('POST', '/v1/autotranslate.translateMessage');

const language = useMemo(
() => subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid),
Expand Down Expand Up @@ -54,7 +54,7 @@ export const useTranslateAction = (
(record) => record._id === message._id,
(record) => ({ ...record, autoTranslateFetching: true }),
);
void translateMessage(message, language);
void translateMessage({ messageId: message._id, targetLanguage: language });
}

updateMessages(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings';
import { useMethod, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useEndpoint, usePermission, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useMemo } from 'react';

import { AutoTranslate } from '../../../../app/autotranslate/client';
Expand All @@ -15,7 +15,7 @@ export const useViewOriginalTranslationAction = (
const user = useUser();
const autoTranslateEnabled = useSetting('AutoTranslate_Enabled', false);
const canAutoTranslate = usePermission('auto-translate');
const translateMessage = useMethod('autoTranslate.translateMessage');
const translateMessage = useEndpoint('POST', '/v1/autotranslate.translateMessage');

const language = useMemo(
() => subscription?.autoTranslateLanguage || AutoTranslate.getLanguage(message.rid),
Expand Down Expand Up @@ -54,7 +54,7 @@ export const useViewOriginalTranslationAction = (
(record) => record._id === message._id,
(record) => ({ ...record, autoTranslateFetching: true }),
);
void translateMessage(message, language);
void translateMessage({ messageId: message._id, targetLanguage: language });
}

updateMessages(
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/client/hooks/notification/useNotification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const useNotification = () => {
} as any);

const n = new Notification(notification.title, {
icon: notification.icon || getUserAvatarURL(notification.payload.sender?.username as string),
icon: notification.icon || getUserAvatarURL(notification.payload.sender?.username),
body: stripTags(message?.msg),
tag: msgId,
canReply: true,
Expand Down
12 changes: 7 additions & 5 deletions apps/meteor/client/lib/chats/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { sdk } from '../../../app/utils/client/lib/SDKClient';
import { Messages, Rooms, Subscriptions } from '../../stores';
import { settings } from '../settings';
import { getUserId } from '../user';
import { mapMessageFromApi } from '../utils/mapMessageFromApi';
import { prependReplies } from '../utils/prependReplies';
import { upsertThreadMessageInCache } from '../utils/threadMessageUtils';

Expand All @@ -34,7 +35,8 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage
};

const findMessageByID = async (mid: IMessage['_id']): Promise<IMessage | null> =>
Messages.state.find((record) => record._id === mid && record._hidden !== true) ?? sdk.call('getSingleMessage', mid);
Messages.state.find((record) => record._id === mid && record._hidden !== true) ??
sdk.rest.get('/v1/chat.getMessage', { msgId: mid }).then((response) => mapMessageFromApi(response.message));

const getMessageByID = async (mid: IMessage['_id']): Promise<IMessage> => {
const message = await findMessageByID(mid);
Expand Down Expand Up @@ -68,14 +70,14 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage
}

const canEditMessage = hasAtLeastOnePermission('edit-message', message.rid);
const editAllowed = (settings.peek('Message_AllowEditing') as boolean | undefined) ?? false;
const editAllowed = settings.peek('Message_AllowEditing') ?? false;
const editOwn = message?.u && message.u._id === getUserId();

if (!canEditMessage && (!editAllowed || !editOwn)) {
return false;
}

const blockEditInMinutes = settings.peek('Message_AllowEditing_BlockEditInMinutes') as number | undefined;
const blockEditInMinutes = settings.peek('Message_AllowEditing_BlockEditInMinutes');
const bypassBlockTimeLimit = hasPermission('bypass-time-limit-edit-and-delete', message.rid);

const elapsedMinutes = differenceInMinutes(new Date(), message.ts);
Expand Down Expand Up @@ -206,7 +208,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage
return true;
}

const deletionEnabled = settings.peek('Message_AllowDeleting') as boolean | undefined;
const deletionEnabled = settings.peek('Message_AllowDeleting');
if (!deletionEnabled) {
return false;
}
Expand All @@ -219,7 +221,7 @@ export const createDataAPI = ({ rid, tmid }: { rid: IRoom['_id']; tmid: IMessage
return false;
}

const blockDeleteInMinutes = settings.peek('Message_AllowDeleting_BlockDeleteInMinutes') as number | undefined;
const blockDeleteInMinutes = settings.peek('Message_AllowDeleting_BlockDeleteInMinutes');
const bypassBlockTimeLimit = hasPermission('bypass-time-limit-edit-and-delete', message.rid);
const elapsedMinutes = differenceInMinutes(new Date(), message.ts);
const onTimeForDelete = bypassBlockTimeLimit || !blockDeleteInMinutes || !elapsedMinutes || elapsedMinutes <= blockDeleteInMinutes;
Expand Down
12 changes: 7 additions & 5 deletions apps/meteor/client/lib/e2ee/rocketchat.e2e.room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ export class E2ERoom extends Emitter {

// Import session key for use.
try {
const key = await Aes.importKey(JSON.parse(this.sessionKeyExportedString!));
const key = await Aes.importKey(JSON.parse(this.sessionKeyExportedString));
// Key has been obtained. E2E is now in session.
this.groupSessionKey = key;
span.info('Group key imported');
Expand All @@ -423,7 +423,7 @@ export class E2ERoom extends Emitter {
async createGroupKey() {
await this.createNewGroupKey();

await sdk.call('e2e.setRoomKeyID', this.roomId, this.keyID);
await sdk.rest.post('/v1/e2e.setRoomKeyID', { rid: this.roomId, keyID: this.keyID });
const myKey = await this.encryptGroupKeyForParticipant(e2e.publicKey!);
if (myKey) {
await sdk.rest.post('/v1/e2e.updateGroupKey', {
Expand Down Expand Up @@ -482,7 +482,9 @@ export class E2ERoom extends Emitter {
try {
const mySub = Subscriptions.state.find((record) => record.rid === this.roomId);
const decryptedOldGroupKeys = await this.exportOldRoomKeys(mySub?.oldRoomKeys);
const users = (await sdk.call('e2e.getUsersOfRoomWithoutKey', this.roomId)).users.filter((user) => user?.e2e?.public_key);
const users = (await sdk.rest.get('/v1/e2e.getUsersOfRoomWithoutKey', { rid: this.roomId })).users.filter(
(user) => user?.e2e?.public_key,
);

if (!users.length) {
span.info('No users to encrypt the key for');
Expand All @@ -498,13 +500,13 @@ export class E2ERoom extends Emitter {
}[]
> = { [this.roomId]: [] };
for await (const user of users) {
const encryptedGroupKey = await this.encryptGroupKeyForParticipant(user.e2e!.public_key!);
const encryptedGroupKey = await this.encryptGroupKeyForParticipant(user.e2e!.public_key);
if (!encryptedGroupKey) {
span.warn(`Could not encrypt group key for user ${user._id}`);
return;
}
if (decryptedOldGroupKeys) {
const oldKeys = await this.encryptOldKeysForParticipant(user.e2e!.public_key!, decryptedOldGroupKeys);
const oldKeys = await this.encryptOldKeysForParticipant(user.e2e!.public_key, decryptedOldGroupKeys);
if (oldKeys) {
usersSuggestedGroupKeys[this.roomId].push({ _id: user._id, key: encryptedGroupKey, oldKeys });
continue;
Expand Down
6 changes: 6 additions & 0 deletions apps/meteor/client/lib/utils/mapCustomUserStatusFromApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ICustomUserStatus, Serialized } from '@rocket.chat/core-typings';

export const mapCustomUserStatusFromApi = ({ _updatedAt, ...status }: Serialized<ICustomUserStatus>): ICustomUserStatus => ({
...status,
_updatedAt: new Date(_updatedAt),
});
7 changes: 7 additions & 0 deletions apps/meteor/client/lib/utils/mapReadReceiptFromApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { IReadReceiptWithUser, Serialized } from '@rocket.chat/core-typings';

export const mapReadReceiptFromApi = ({ ts, _updatedAt, ...receipt }: Serialized<IReadReceiptWithUser>): IReadReceiptWithUser => ({
...receipt,
ts: new Date(ts),
_updatedAt: new Date(_updatedAt),
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { ICustomUserStatus } from '@rocket.chat/core-typings';
import { Box } from '@rocket.chat/fuselage';
import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
import { clientCallbacks } from '@rocket.chat/ui-client';
import { useEndpoint, useMethod, useSetting, useStream } from '@rocket.chat/ui-contexts';
import { useEndpoint, useSetting, useStream } from '@rocket.chat/ui-contexts';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';

import { useCustomStatusModalHandler } from './useCustomStatusModalHandler';
Expand All @@ -12,6 +13,7 @@ import { UserStatus } from '../../../../components/UserStatus';
import { useFireGlobalEvent } from '../../../../hooks/useFireGlobalEvent';
import { userStatuses } from '../../../../lib/userStatuses';
import type { UserStatusDescriptor } from '../../../../lib/userStatuses';
import { mapCustomUserStatusFromApi } from '../../../../lib/utils/mapCustomUserStatusFromApi';
import { useStatusDisabledModal } from '../../../../views/admin/customUserStatus/hooks/useStatusDisabledModal';

export const useStatusItems = (): GenericMenuItemProps[] => {
Expand All @@ -21,7 +23,20 @@ export const useStatusItems = (): GenericMenuItemProps[] => {

const queryClient = useQueryClient();
const stream = useStream('notify-logged');
const listCustomUserStatus = useMethod('listCustomUserStatus');
const listCustomUserStatusEndpoint = useEndpoint('GET', '/v1/custom-user-status.list');
const listCustomUserStatus = useCallback(async (): Promise<ICustomUserStatus[]> => {
const all: ICustomUserStatus[] = [];
const count = 100;
let offset = 0;
// REST endpoint is paginated; loop until total reached.
while (true) {
const { statuses, total } = await listCustomUserStatusEndpoint({ count, offset });
all.push(...statuses.map(mapCustomUserStatusFromApi));
if (all.length >= total || statuses.length === 0) break;
offset += statuses.length;
}
return all;
}, [listCustomUserStatusEndpoint]);

useEffect(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
GenericTableHeaderCell,
usePagination,
} from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useEndpoint } from '@rocket.chat/ui-contexts';
import { useSetModal, useToastMessageDispatch, useUserId, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import type { ReactElement, RefObject } from 'react';
import { useMemo, useCallback } from 'react';
Expand All @@ -26,8 +26,8 @@ const AccountTokensTable = (): ReactElement => {
const setModal = useSetModal();
const userId = useUserId();

const regenerateToken = useMethod('personalAccessTokens:regenerateToken');
const removeToken = useMethod('personalAccessTokens:removeToken');
const regenerateToken = useEndpoint('POST', '/v1/users.regeneratePersonalAccessToken');
const removeToken = useEndpoint('POST', '/v1/users.removePersonalAccessToken');

const getPersonalAccessTokens = useEndpoint('GET', '/v1/users.getPersonalAccessTokens');
const { isPending, isSuccess, data, isError, error } = useQuery({
Expand Down Expand Up @@ -69,7 +69,7 @@ const AccountTokensTable = (): ReactElement => {
const onConfirm: () => Promise<void> = async () => {
try {
setModal(null);
const token = await regenerateToken({ tokenName: name });
const { token } = await regenerateToken({ tokenName: name });

setModal(
<GenericModal title={t('API_Personal_Access_Token_Generated')} onConfirm={closeModal}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SelectOption } from '@rocket.chat/fuselage';
import { Box, TextInput, Button, Margins, Select, FieldError, FieldGroup, Field, FieldRow } from '@rocket.chat/fuselage';
import { GenericModal } from '@rocket.chat/ui-client';
import { useSetModal, useToastMessageDispatch, useUserId, useMethod } from '@rocket.chat/ui-contexts';
import { useSetModal, useToastMessageDispatch, useUserId, useEndpoint } from '@rocket.chat/ui-contexts';
import { useCallback, useId, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
Expand All @@ -19,7 +19,7 @@ const AddToken = ({ reload }: AddTokenProps) => {
const { t } = useTranslation();
const userId = useUserId();
const setModal = useSetModal();
const createTokenFn = useMethod('personalAccessTokens:generateToken');
const createTokenFn = useEndpoint('POST', '/v1/users.generatePersonalAccessToken');
const dispatchToastMessage = useToastMessageDispatch();

const initialValues = useMemo(() => ({ name: '', bypassTwoFactor: 'require' }), []);
Expand All @@ -42,7 +42,7 @@ const AddToken = ({ reload }: AddTokenProps) => {
const handleAddToken = useCallback(
async ({ name: tokenName, bypassTwoFactor }: AddTokenFormData) => {
try {
const token = await createTokenFn({ tokenName, bypassTwoFactor: bypassTwoFactor === 'bypass' });
const { token } = await createTokenFn({ tokenName, bypassTwoFactor: bypassTwoFactor === 'bypass' });

const handleDismissModal = () => {
setModal(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ModalTitle,
} from '@rocket.chat/fuselage';
import { useSafely } from '@rocket.chat/fuselage-hooks';
import { useMethod, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useEndpoint, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -29,22 +29,23 @@ const RegisteredWorkspaceModal = ({ onClose, onStatusChange, ...props }: Registe
const bulletFeatures = useFeatureBullets();
const [isSyncing, setSyncing] = useSafely(useState(false));

const syncWorkspace = useMethod('cloud:syncWorkspace');
const syncWorkspace = useEndpoint('POST', '/v1/cloud.syncWorkspace');

const handleSyncAction = async () => {
setSyncing(true);

try {
const isSynced = await syncWorkspace();
const { success } = await syncWorkspace();

if (!isSynced) {
if (!success) {
throw Error(t('RegisterWorkspace_Syncing_Error'));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

dispatchToastMessage({ type: 'success', message: t('RegisterWorkspace_Syncing_Complete') });
setModal(null);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
const message = error instanceof Error ? error.message : t('RegisterWorkspace_Syncing_Error');
dispatchToastMessage({ type: 'error', message });
} finally {
onStatusChange?.();
setSyncing(false);
Expand Down
Loading
Loading