feat: Disable some message menu items for encrypted messages. (#32559)

pull/32644/head
Yash Rajpal 2 years ago committed by GitHub
parent cd97aac0f6
commit 45dc3d5f72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/plenty-buses-kneel.md
  2. 3
      apps/meteor/app/ui-utils/client/lib/MessageAction.ts
  3. 11
      apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts
  4. 2
      apps/meteor/client/components/GenericMenu/GenericMenu.tsx
  5. 5
      apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx
  6. 38
      apps/meteor/client/components/message/toolbar/MessageActionMenu.tsx
  7. 11
      apps/meteor/client/components/message/toolbar/MessageToolbar.tsx
  8. 2
      apps/meteor/package.json
  9. 25
      apps/meteor/tests/e2e/e2e-encryption.spec.ts
  10. 2
      ee/packages/ui-theming/package.json
  11. 2
      packages/fuselage-ui-kit/package.json
  12. 2
      packages/gazzodown/package.json
  13. 1
      packages/i18n/src/locales/en.i18n.json
  14. 2
      packages/ui-avatar/package.json
  15. 2
      packages/ui-client/package.json
  16. 2
      packages/ui-composer/package.json
  17. 2
      packages/ui-video-conf/package.json
  18. 2
      packages/uikit-playground/package.json
  19. 26
      yarn.lock

@ -0,0 +1,6 @@
---
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---
Disable "Reply in direct message", "Copy link" and "Forward message" message menu items for encrypted messages as they don't apply to encrypted messages and also disable apps menu items and show a warning.

@ -25,7 +25,7 @@ export type MessageActionContext =
type MessageActionType = 'communication' | 'interaction' | 'duplication' | 'apps' | 'management';
type MessageActionConditionProps = {
export type MessageActionConditionProps = {
message: IMessage;
user: IUser | undefined;
room: IRoom;
@ -65,6 +65,7 @@ export type MessageActionConfig = {
) => any;
condition?: (props: MessageActionConditionProps) => Promise<boolean> | boolean;
type?: MessageActionType;
disabled?: (props: MessageActionConditionProps) => boolean;
};
class MessageAction {

@ -1,5 +1,5 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { isE2EEMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';
@ -63,6 +63,9 @@ Meteor.startup(async () => {
},
order: 0,
group: 'menu',
disabled({ message }) {
return isE2EEMessage(message);
},
});
MessageAction.addButton({
@ -87,6 +90,9 @@ Meteor.startup(async () => {
},
order: 0,
group: 'message',
disabled({ message }) {
return isE2EEMessage(message);
},
});
MessageAction.addButton({
@ -139,6 +145,9 @@ Meteor.startup(async () => {
},
order: 5,
group: 'menu',
disabled({ message }) {
return isE2EEMessage(message);
},
});
MessageAction.addButton({

@ -41,7 +41,7 @@ const GenericMenu = ({ title, icon = 'menu', disabled, onAction, ...props }: Gen
const hasIcon = itemsList.some(({ icon }) => icon);
const handleItems = (items: GenericMenuItemProps[]) =>
hasIcon ? items.map((item) => ({ ...item, gap: !item.icon && !item.status })) : items;
hasIcon ? items.map((item) => ({ ...item, gap: item.gap ?? (!item.icon && !item.status) })) : items;
const isMenuEmpty = !(sections && sections.length > 0) && !(items && items.length > 0);

@ -12,14 +12,15 @@ export type GenericMenuItemProps = {
disabled?: boolean;
description?: ReactNode;
gap?: boolean;
tooltip?: string;
};
const GenericMenuItem = ({ icon, content, addon, status, gap }: GenericMenuItemProps) => (
const GenericMenuItem = ({ icon, content, addon, status, gap, tooltip }: GenericMenuItemProps) => (
<>
{gap && <MenuItemColumn />}
{icon && <MenuItemIcon name={icon} />}
{status && <MenuItemColumn>{status}</MenuItemColumn>}
{content && <MenuItemContent>{content}</MenuItemContent>}
{content && <MenuItemContent title={tooltip}>{content}</MenuItemContent>}
{addon && <MenuItemInput>{addon}</MenuItemInput>}
</>
);

@ -1,8 +1,9 @@
import { useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { MouseEvent, ReactElement } from 'react';
import React from 'react';
import type { MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction';
import type { MessageActionConditionProps, MessageActionConfig } from '../../../../app/ui-utils/client/lib/MessageAction';
import GenericMenu from '../../GenericMenu/GenericMenu';
import type { GenericMenuItemProps } from '../../GenericMenu/GenericMenuItem';
@ -19,11 +20,13 @@ type MessageActionSection = {
type MessageActionMenuProps = {
onChangeMenuVisibility: (visible: boolean) => void;
options: MessageActionConfigOption[];
context: MessageActionConditionProps;
isMessageEncrypted: boolean;
};
const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMenuProps): ReactElement => {
const MessageActionMenu = ({ options, onChangeMenuVisibility, context, isMessageEncrypted }: MessageActionMenuProps): ReactElement => {
const t = useTranslation();
const id = useUniqueId();
const groupOptions = options
.map((option) => ({
variant: option.color === 'alert' ? 'danger' : '',
@ -32,6 +35,9 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMen
content: t(option.label),
onClick: option.action,
type: option.type,
...(option.disabled && { disabled: option?.disabled?.(context) }),
...(option.disabled &&
option?.disabled?.(context) && { tooltip: t('Action_not_available_encrypted_content', { action: t(option.label) }) }),
}))
.reduce((acc, option) => {
const group = option.type ? option.type : '';
@ -44,7 +50,31 @@ const MessageActionMenu = ({ options, onChangeMenuVisibility }: MessageActionMen
acc.push(newSection);
return acc;
}, [] as unknown as MessageActionSection[]);
}, [] as unknown as MessageActionSection[])
.map((section) => {
if (section.id !== 'apps') {
return section;
}
if (!isMessageEncrypted) {
return section;
}
return {
id: 'apps',
title: t('Apps'),
items: [
{
content: t('Unavailable'),
type: 'apps',
id,
disabled: true,
gap: false,
tooltip: t('Action_not_available_encrypted_content', { action: t('Apps') }),
},
],
};
});
return (
<GenericMenu

@ -1,6 +1,6 @@
import { useToolbar } from '@react-aria/toolbar';
import type { IMessage, IRoom, ISubscription, ITranslatedMessage } from '@rocket.chat/core-typings';
import { isThreadMessage, isRoomFederated, isVideoConfMessage } from '@rocket.chat/core-typings';
import { isThreadMessage, isRoomFederated, isVideoConfMessage, isE2EEMessage } from '@rocket.chat/core-typings';
import { MessageToolbar as FuselageMessageToolbar, MessageToolbarItem } from '@rocket.chat/fuselage';
import { useFeaturePreview } from '@rocket.chat/ui-client';
import { useUser, useSettings, useTranslation, useMethod, useLayoutHiddenActions } from '@rocket.chat/ui-contexts';
@ -125,9 +125,14 @@ const MessageToolbar = ({
onClick={(e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })}
key={action.id}
icon={action.icon}
title={t(action.label)}
title={
action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context })
? t('Action_not_available_encrypted_content', { action: t(action.label) })
: t(action.label)
}
data-qa-id={action.label}
data-qa-type='message-action-menu'
disabled={action?.disabled?.({ message, room, user, subscription, settings: mapSettings, chat, context })}
/>
))}
{actionsQueryResult.isSuccess && actionsQueryResult.data.menu.length > 0 && (
@ -138,6 +143,8 @@ const MessageToolbar = ({
}))}
onChangeMenuVisibility={onChangeMenuVisibility}
data-qa-type='message-action-menu-options'
context={{ message, room, user, subscription, settings: mapSettings, chat, context }}
isMessageEncrypted={isE2EEMessage(message)}
/>
)}
</FuselageMessageToolbar>

@ -242,7 +242,7 @@
"@rocket.chat/favicon": "workspace:^",
"@rocket.chat/forked-matrix-appservice-bridge": "^4.0.2",
"@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.3",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/fuselage-toastbar": "^0.31.26",

@ -213,6 +213,31 @@ test.describe.serial('e2e-encryption', () => {
await expect(poHomeChannel.content.mainThreadMessageText.locator('.rcx-icon--name-key')).toBeVisible();
});
test('expect create a private encrypted channel and check disabled message menu actions on an encrypted message', async ({ page }) => {
const channelName = faker.string.uuid();
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('This is an encrypted message.');
await expect(poHomeChannel.content.lastUserMessageBody).toHaveText('This is an encrypted message.');
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();
await page.locator('[data-qa-type="message"]').last().hover();
await expect(page.locator('role=button[name="Forward message"]')).toBeDisabled();
await poHomeChannel.content.openLastMessageMenu();
await expect(page.locator('role=menuitem[name="Reply in direct message"]')).toHaveClass(/disabled/);
await expect(page.locator('role=menuitem[name="Copy link"]')).toHaveClass(/disabled/);
});
test('expect create a private channel, encrypt it and send an encrypted message', async ({ page }) => {
const channelName = faker.string.uuid();

@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "^0.36.0",
"@rocket.chat/ui-contexts": "workspace:~",

@ -66,7 +66,7 @@
"@rocket.chat/apps-engine": "alpha",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/icons": "^0.36.0",

@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-tokens": "^0.33.1",
"@rocket.chat/message-parser": "workspace:^",
"@rocket.chat/styled": "~0.31.25",

@ -304,6 +304,7 @@
"Action_required": "Action required",
"Action_Available_After_Custom_Content_Added": "This action will become available after the custom content has been added",
"Action_Available_After_Custom_Content_Added_And_Visible": "This action will become available after the custom content has been added and made visible to everyone",
"Action_not_available_encrypted_content": "{{action}} not available on encrypted content",
"Activate": "Activate",
"Active": "Active",
"Active_users": "Active users",

@ -4,7 +4,7 @@
"private": true,
"devDependencies": {
"@babel/core": "~7.22.20",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/ui-contexts": "workspace:^",
"@types/babel__core": "~7.20.3",
"@types/react": "~17.0.69",

@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "^0.36.0",
"@rocket.chat/mock-providers": "workspace:^",

@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@react-aria/toolbar": "^3.0.0-beta.1",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/icons": "^0.36.0",
"@storybook/addon-actions": "~6.5.16",
"@storybook/addon-docs": "~6.5.16",

@ -6,7 +6,7 @@
"@babel/core": "~7.22.20",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/eslint-config": "workspace:^",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/icons": "^0.36.0",
"@rocket.chat/styled": "~0.31.25",

@ -15,7 +15,7 @@
"@codemirror/tooltip": "^0.19.16",
"@lezer/highlight": "^1.1.6",
"@rocket.chat/css-in-js": "~0.31.25",
"@rocket.chat/fuselage": "^0.54.2",
"@rocket.chat/fuselage": "^0.54.3",
"@rocket.chat/fuselage-hooks": "^0.33.1",
"@rocket.chat/fuselage-polyfills": "~0.31.25",
"@rocket.chat/fuselage-toastbar": "^0.31.26",

@ -8915,7 +8915,7 @@ __metadata:
"@rocket.chat/apps-engine": alpha
"@rocket.chat/core-typings": "workspace:^"
"@rocket.chat/eslint-config": "workspace:^"
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-hooks": ^0.33.1
"@rocket.chat/fuselage-polyfills": ~0.31.25
"@rocket.chat/gazzodown": "workspace:^"
@ -8977,9 +8977,9 @@ __metadata:
languageName: unknown
linkType: soft
"@rocket.chat/fuselage@npm:^0.54.2":
version: 0.54.2
resolution: "@rocket.chat/fuselage@npm:0.54.2"
"@rocket.chat/fuselage@npm:^0.54.3":
version: 0.54.3
resolution: "@rocket.chat/fuselage@npm:0.54.3"
dependencies:
"@rocket.chat/css-in-js": ^0.31.25
"@rocket.chat/css-supports": ^0.31.25
@ -8997,7 +8997,7 @@ __metadata:
react: ^17.0.2
react-dom: ^17.0.2
react-virtuoso: 1.2.4
checksum: 1e49b3324f50525a002dd450a28dc8c6fca559e51c8e93449e167b50ca88767cc3dd11ac3c89ea794597c6c06a4a681aea1043b8f8bb42c49df0362bb5791019
checksum: bec4d0b92e919103cda927520040f46004266ec5e1b3964c5bec6c6be59f8f051f2940689785f4e78984a9a18230a175b9f5f8e548f2b8f951387d567570735c
languageName: node
linkType: hard
@ -9008,7 +9008,7 @@ __metadata:
"@babel/core": ~7.22.20
"@rocket.chat/core-typings": "workspace:^"
"@rocket.chat/css-in-js": ~0.31.25
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-tokens": ^0.33.1
"@rocket.chat/message-parser": "workspace:^"
"@rocket.chat/styled": ~0.31.25
@ -9368,7 +9368,7 @@ __metadata:
"@rocket.chat/favicon": "workspace:^"
"@rocket.chat/forked-matrix-appservice-bridge": ^4.0.2
"@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.3
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-hooks": ^0.33.1
"@rocket.chat/fuselage-polyfills": ~0.31.25
"@rocket.chat/fuselage-toastbar": ^0.31.26
@ -10264,7 +10264,7 @@ __metadata:
resolution: "@rocket.chat/ui-avatar@workspace:packages/ui-avatar"
dependencies:
"@babel/core": ~7.22.20
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/ui-contexts": "workspace:^"
"@types/babel__core": ~7.20.3
"@types/react": ~17.0.69
@ -10290,7 +10290,7 @@ __metadata:
"@babel/core": ~7.22.20
"@react-aria/toolbar": ^3.0.0-beta.1
"@rocket.chat/css-in-js": ~0.31.25
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-hooks": ^0.33.1
"@rocket.chat/icons": ^0.36.0
"@rocket.chat/mock-providers": "workspace:^"
@ -10343,7 +10343,7 @@ __metadata:
"@babel/core": ~7.22.20
"@react-aria/toolbar": ^3.0.0-beta.1
"@rocket.chat/eslint-config": "workspace:^"
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/icons": ^0.36.0
"@storybook/addon-actions": ~6.5.16
"@storybook/addon-docs": ~6.5.16
@ -10435,7 +10435,7 @@ __metadata:
resolution: "@rocket.chat/ui-theming@workspace:ee/packages/ui-theming"
dependencies:
"@rocket.chat/css-in-js": ~0.31.25
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-hooks": ^0.33.1
"@rocket.chat/icons": ^0.36.0
"@rocket.chat/ui-contexts": "workspace:~"
@ -10478,7 +10478,7 @@ __metadata:
"@rocket.chat/css-in-js": ~0.31.25
"@rocket.chat/emitter": ~0.31.25
"@rocket.chat/eslint-config": "workspace:^"
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-hooks": ^0.33.1
"@rocket.chat/icons": ^0.36.0
"@rocket.chat/styled": ~0.31.25
@ -10523,7 +10523,7 @@ __metadata:
"@codemirror/tooltip": ^0.19.16
"@lezer/highlight": ^1.1.6
"@rocket.chat/css-in-js": ~0.31.25
"@rocket.chat/fuselage": ^0.54.2
"@rocket.chat/fuselage": ^0.54.3
"@rocket.chat/fuselage-hooks": ^0.33.1
"@rocket.chat/fuselage-polyfills": ~0.31.25
"@rocket.chat/fuselage-toastbar": ^0.31.26

Loading…
Cancel
Save