diff --git a/.changeset/gold-flowers-shake.md b/.changeset/gold-flowers-shake.md new file mode 100644 index 00000000000..26182d785c2 --- /dev/null +++ b/.changeset/gold-flowers-shake.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Added E2EE room setup header, with just limited functionality and room actions. diff --git a/apps/meteor/client/ui.ts b/apps/meteor/client/ui.ts index 98d3233134a..6c7971a8cca 100644 --- a/apps/meteor/client/ui.ts +++ b/apps/meteor/client/ui.ts @@ -78,3 +78,5 @@ export const quickActionHooks = [ useCloseChatQuickAction, useOnHoldChatQuickAction, ] satisfies (() => QuickActionsActionConfig | undefined)[]; + +export const roomActionHooksForE2EESetup = [useChannelSettingsRoomAction, useMembersListRoomAction, useE2EERoomAction]; diff --git a/apps/meteor/client/views/room/Header/Header.tsx b/apps/meteor/client/views/room/Header/Header.tsx index bd5537e0098..c350544e815 100644 --- a/apps/meteor/client/views/room/Header/Header.tsx +++ b/apps/meteor/client/views/room/Header/Header.tsx @@ -1,15 +1,16 @@ import type { IRoom } from '@rocket.chat/core-typings'; -import { isVoipRoom } from '@rocket.chat/core-typings'; +import { isDirectMessageRoom, isVoipRoom } from '@rocket.chat/core-typings'; import { HeaderToolbar } from '@rocket.chat/ui-client'; -import { useLayout } from '@rocket.chat/ui-contexts'; +import { useLayout, useSetting } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { lazy, memo, useMemo } from 'react'; import SidebarToggler from '../../../components/SidebarToggler'; -const DirectRoomHeader = lazy(() => import('./DirectRoomHeader')); const OmnichannelRoomHeader = lazy(() => import('./Omnichannel/OmnichannelRoomHeader')); const VoipRoomHeader = lazy(() => import('./Omnichannel/VoipRoomHeader')); +const RoomHeaderE2EESetup = lazy(() => import('./RoomHeaderE2EESetup')); +const DirectRoomHeader = lazy(() => import('./DirectRoomHeader')); const RoomHeader = lazy(() => import('./RoomHeader')); type HeaderProps = { @@ -18,6 +19,9 @@ type HeaderProps = { const Header = ({ room }: HeaderProps): ReactElement | null => { const { isMobile, isEmbedded, showTopNavbarEmbeddedLayout } = useLayout(); + const encrypted = Boolean(room.encrypted); + const unencryptedMessagesAllowed = useSetting('E2E_Allow_Unencrypted_Messages'); + const shouldDisplayE2EESetup = encrypted && !unencryptedMessagesAllowed; const slots = useMemo( () => ({ @@ -34,10 +38,6 @@ const Header = ({ room }: HeaderProps): ReactElement | null => { return null; } - if (room.t === 'd' && (room.uids?.length ?? 0) < 3) { - return ; - } - if (room.t === 'l') { return ; } @@ -46,7 +46,15 @@ const Header = ({ room }: HeaderProps): ReactElement | null => { return ; } - return ; + if (shouldDisplayE2EESetup) { + return ; + } + + if (isDirectMessageRoom(room) && (room.uids?.length ?? 0) < 3) { + return ; + } + + return ; }; export default memo(Header); diff --git a/apps/meteor/client/views/room/Header/RoomHeader.tsx b/apps/meteor/client/views/room/Header/RoomHeader.tsx index 05f80a98498..fee9be6a55a 100644 --- a/apps/meteor/client/views/room/Header/RoomHeader.tsx +++ b/apps/meteor/client/views/room/Header/RoomHeader.tsx @@ -30,9 +30,10 @@ export type RoomHeaderProps = { pos?: unknown; }; }; + roomToolbox?: JSX.Element; }; -const RoomHeader = ({ room, topic = '', slots = {} }: RoomHeaderProps) => { +const RoomHeader = ({ room, topic = '', slots = {}, roomToolbox }: RoomHeaderProps) => { const t = useTranslation(); return ( @@ -65,7 +66,7 @@ const RoomHeader = ({ room, topic = '', slots = {} }: RoomHeaderProps) => { {slots?.toolbox?.pre} - {slots?.toolbox?.content || } + {slots?.toolbox?.content || roomToolbox || } {slots?.toolbox?.pos} diff --git a/apps/meteor/client/views/room/Header/RoomHeaderE2EESetup.tsx b/apps/meteor/client/views/room/Header/RoomHeaderE2EESetup.tsx new file mode 100644 index 00000000000..2b868c28882 --- /dev/null +++ b/apps/meteor/client/views/room/Header/RoomHeaderE2EESetup.tsx @@ -0,0 +1,29 @@ +import { isDirectMessageRoom } from '@rocket.chat/core-typings'; +import React, { lazy } from 'react'; + +import { E2EEState } from '../../../../app/e2e/client/E2EEState'; +import { E2ERoomState } from '../../../../app/e2e/client/E2ERoomState'; +import { useE2EERoomState } from '../hooks/useE2EERoomState'; +import { useE2EEState } from '../hooks/useE2EEState'; +import DirectRoomHeader from './DirectRoomHeader'; +import RoomHeader from './RoomHeader'; +import type { RoomHeaderProps } from './RoomHeader'; + +const RoomToolboxE2EESetup = lazy(() => import('./RoomToolbox/RoomToolboxE2EESetup')); + +const RoomHeaderE2EESetup = ({ room, topic = '', slots = {} }: RoomHeaderProps) => { + const e2eeState = useE2EEState(); + const e2eRoomState = useE2EERoomState(room._id); + + if (e2eeState === E2EEState.SAVE_PASSWORD || e2eeState === E2EEState.ENTER_PASSWORD || e2eRoomState === E2ERoomState.WAITING_KEYS) { + return } />; + } + + if (isDirectMessageRoom(room) && (room.uids?.length ?? 0) < 3) { + return ; + } + + return ; +}; + +export default RoomHeaderE2EESetup; diff --git a/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolboxE2EESetup.tsx b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolboxE2EESetup.tsx new file mode 100644 index 00000000000..9b79dff5a6b --- /dev/null +++ b/apps/meteor/client/views/room/Header/RoomToolbox/RoomToolboxE2EESetup.tsx @@ -0,0 +1,41 @@ +import { useStableArray } from '@rocket.chat/fuselage-hooks'; +import { HeaderToolbarAction } from '@rocket.chat/ui-client'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { roomActionHooksForE2EESetup } from '../../../../ui'; +import type { RoomToolboxActionConfig } from '../../contexts/RoomToolboxContext'; +import { useRoomToolbox } from '../../contexts/RoomToolboxContext'; + +const RoomToolboxE2EESetup = () => { + const t = useTranslation(); + const toolbox = useRoomToolbox(); + + const { tab } = toolbox; + + const actions = useStableArray( + roomActionHooksForE2EESetup + .map((roomActionHook) => roomActionHook()) + .filter((roomAction): roomAction is RoomToolboxActionConfig => !!roomAction), + ); + + return ( + <> + {actions.map(({ id, icon, title, action, disabled, tooltip }, index) => ( + toolbox.openTab(id))} + disabled={disabled} + tooltip={tooltip} + /> + ))} + + ); +}; + +export default RoomToolboxE2EESetup; diff --git a/apps/meteor/tests/e2e/e2e-encryption.spec.ts b/apps/meteor/tests/e2e/e2e-encryption.spec.ts index 69b77d1ceff..8b0753c952d 100644 --- a/apps/meteor/tests/e2e/e2e-encryption.spec.ts +++ b/apps/meteor/tests/e2e/e2e-encryption.spec.ts @@ -429,11 +429,17 @@ test.describe.serial('e2ee room setup', () => { await poHomeChannel.dismissToast(); - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + await poHomeChannel.content.encryptedRoomHeaderIcon.first().waitFor(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon.first()).toBeVisible(); await page.locator('role=button[name="Save E2EE password"]').waitFor(); await expect(page.locator('role=button[name="Save E2EE password"]')).toBeVisible(); + await poHomeChannel.tabs.btnE2EERoomSetupDisableE2E.waitFor(); + await expect(poHomeChannel.tabs.btnE2EERoomSetupDisableE2E).toBeVisible(); + await expect(poHomeChannel.tabs.btnTabMembers).toBeVisible(); + await expect(poHomeChannel.tabs.btnRoomInfo).toBeVisible(); + await expect(poHomeChannel.content.inputMessage).not.toBeVisible(); await page.locator('role=button[name="Save E2EE password"]').click(); @@ -477,11 +483,17 @@ test.describe.serial('e2ee room setup', () => { await poHomeChannel.dismissToast(); - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon.first()).toBeVisible(); await page.locator('role=button[name="Enter your E2E password"]').waitFor(); await expect(page.locator('role=banner >> text="Enter your E2E password"')).toBeVisible(); + + await poHomeChannel.tabs.btnE2EERoomSetupDisableE2E.waitFor(); + await expect(poHomeChannel.tabs.btnE2EERoomSetupDisableE2E).toBeVisible(); + await expect(poHomeChannel.tabs.btnTabMembers).toBeVisible(); + await expect(poHomeChannel.tabs.btnRoomInfo).toBeVisible(); + await expect(poHomeChannel.content.inputMessage).not.toBeVisible(); await page.locator('role=button[name="Enter your E2E password"]').click(); @@ -518,7 +530,7 @@ test.describe.serial('e2ee room setup', () => { await poHomeChannel.dismissToast(); - await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible(); + await expect(poHomeChannel.content.encryptedRoomHeaderIcon.first()).toBeVisible(); await poHomeChannel.content.sendMessage('hello world'); @@ -551,5 +563,10 @@ test.describe.serial('e2ee room setup', () => { await expect(poHomeChannel.content.inputMessage).not.toBeVisible(); await expect(page.locator('.rcx-states__title')).toContainText('Check back later'); + + await poHomeChannel.tabs.btnE2EERoomSetupDisableE2E.waitFor(); + await expect(poHomeChannel.tabs.btnE2EERoomSetupDisableE2E).toBeVisible(); + await expect(poHomeChannel.tabs.btnTabMembers).toBeVisible(); + await expect(poHomeChannel.tabs.btnRoomInfo).toBeVisible(); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts index b8bd673c9cf..a6d91bc6fc3 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab.ts @@ -44,6 +44,10 @@ export class HomeFlextab { return this.page.locator('role=menuitem[name="Notifications Preferences"]'); } + get btnE2EERoomSetupDisableE2E(): Locator { + return this.page.locator('[data-qa-id=ToolBoxAction-key]'); + } + get btnDisableE2E(): Locator { return this.page.locator('role=menuitem[name="Disable E2E"]'); }