Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Disable OTR messages selection when exporting messages #34220

Merged
merged 11 commits into from
Dec 20, 2024
5 changes: 5 additions & 0 deletions .changeset/pretty-islands-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': minor
---

Disables OTR messages selection when exporting messages
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ const RoomMessage = ({
const { openUserCard, triggerProps } = useUserCard();

const selecting = useIsSelecting();
const isOTRMessage = message.t === 'otr' || message.t === 'otr-ack';

const toggleSelected = useToggleSelect(message._id);
const selected = useIsSelectedMessage(message._id);
const selected = useIsSelectedMessage(message._id, isOTRMessage);

useCountSelected();

Expand All @@ -70,7 +72,7 @@ const RoomMessage = ({
aria-roledescription={t('message')}
tabIndex={0}
aria-labelledby={`${message._id}-displayName ${message._id}-time ${message._id}-content ${message._id}-read-status`}
onClick={selecting ? toggleSelected : undefined}
onClick={selecting && !isOTRMessage ? toggleSelected : undefined}
isSelected={selected}
isEditing={editing}
isPending={message.temp}
Expand Down Expand Up @@ -99,7 +101,7 @@ const RoomMessage = ({
{...triggerProps}
/>
)}
{selecting && <CheckBox checked={selected} onChange={toggleSelected} />}
{selecting && <CheckBox disabled={isOTRMessage} checked={selected} onChange={toggleSelected} />}
{sequential && <StatusIndicators message={message} />}
</MessageLeftContainer>
<MessageContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:
const { t } = useTranslation();

const isSelecting = useIsSelecting();
const isOTRMessage = message.t === 'otr' || message.t === 'otr-ack';

const toggleSelected = useToggleSelect(message._id);
const isSelected = useIsSelectedMessage(message._id);
const isSelected = useIsSelectedMessage(message._id, isOTRMessage);
useCountSelected();

const messageType = parentMessage.isSuccess ? MessageTypes.getType(parentMessage.data) : null;
Expand All @@ -65,6 +67,10 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:
return goToThread({ rid: message.rid, tmid: message.tmid, msg: message._id });
}

if (isOTRMessage) {
return;
}

return toggleSelected();
};

Expand Down Expand Up @@ -117,7 +123,7 @@ const ThreadMessagePreview = ({ message, showUserAvatar, sequential, ...props }:
size='x18'
/>
)}
{isSelecting && <CheckBox checked={isSelected} onChange={toggleSelected} />}
{isSelecting && <CheckBox disabled={isOTRMessage} checked={isSelected} onChange={toggleSelected} />}
</ThreadMessageLeftContainer>
<ThreadMessageContainer>
<ThreadMessageBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const SelectedMessageContext = createContext({
selectedMessageStore,
} as SelectMessageContextValue);

export const useIsSelectedMessage = (mid: string): boolean => {
export const useIsSelectedMessage = (mid: string, omit?: boolean): boolean => {
const { selectedMessageStore } = useContext(SelectedMessageContext);

const subscribe = useCallback(
Expand All @@ -24,14 +24,14 @@ export const useIsSelectedMessage = (mid: string): boolean => {
const isSelected = useSyncExternalStore(subscribe, getSnapshot);

useEffect(() => {
if (isSelected) {
if (isSelected || omit) {
return;
}

selectedMessageStore.addAvailableMessage(mid);

return () => selectedMessageStore.removeAvailableMessage(mid);
}, [mid, selectedMessageStore, isSelected]);
}, [mid, selectedMessageStore, isSelected, omit]);

return isSelected;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ const ExportMessages = () => {
<ContextualbarScrollableContent>
<form ref={formFocus} tabIndex={-1} aria-labelledby={`${formId}-title`} id={formId} onSubmit={handleSubmit(handleExport)}>
<FieldGroup>
{room.createdOTR && (
<Field>
<Callout role='alert' type='warning'>
{t('OTR_messages_cannot_be_exported')}
</Callout>
</Field>
)}
<Field>
<FieldLabel htmlFor={methodField}>{t('Method')}</FieldLabel>
<FieldRow>
Expand Down
46 changes: 46 additions & 0 deletions apps/meteor/tests/e2e/otr.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Users } from './fixtures/userStates';
import { HomeChannel } from './page-objects';
import { createDirectMessage } from './utils';
import { test, expect } from './utils/test';

test.use({ storageState: Users.admin.state });

test.describe.serial('OTR', () => {
let poHomeChannel: HomeChannel;

test.beforeEach(async ({ page, api }) => {
await createDirectMessage(api);
poHomeChannel = new HomeChannel(page);

await page.goto('/home');
});

test('should not allow export OTR messages', async ({ browser }) => {
const user1Page = await browser.newPage({ storageState: Users.user1.state });
const user1Channel = new HomeChannel(user1Page);

await test.step('log in user1', async () => {
await user1Page.goto(`/direct/${Users.admin.data.username}`);
await user1Channel.content.waitForChannel();
});

await test.step('invite OTR with user1', async () => {
await poHomeChannel.sidenav.openChat(Users.user1.data.username);
await poHomeChannel.tabs.kebab.click({ force: true });
await poHomeChannel.tabs.btnEnableOTR.click({ force: true });
await poHomeChannel.tabs.otr.btnStartOTR.click();
});

await test.step('accept handshake with user1', async () => {
await user1Channel.tabs.otr.btnAcceptOTR.click();
});

await poHomeChannel.content.sendMessage('hello OTR');
await poHomeChannel.tabs.kebab.click({ force: true });
await poHomeChannel.tabs.btnExportMessages.click();
await poHomeChannel.content.getMessageByText('hello OTR').click();
await expect(poHomeChannel.content.btnClearSelection).toBeDisabled();

await user1Page.close();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -417,4 +417,8 @@ export class HomeContent {
await this.page.getByRole('dialog').getByRole('textbox', { name: 'Message' }).fill(text);
await this.page.getByRole('dialog').getByRole('button', { name: 'Send', exact: true }).click();
}

get btnClearSelection() {
return this.page.getByRole('button', { name: 'Clear selection' });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Locator, Page } from '@playwright/test';

export class HomeFlextabOtr {
private readonly page: Page;

constructor(page: Page) {
this.page = page;
}

get otrDialog(): Locator {
return this.page.getByRole('dialog', { name: 'OTR' });
}

get btnStartOTR(): Locator {
return this.otrDialog.getByRole('button', { name: 'Start OTR' });
}

get btnAcceptOTR(): Locator {
return this.page.getByRole('dialog').getByRole('button', { name: 'Yes' });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { HomeFlextabChannels } from './home-flextab-channels';
import { HomeFlextabExportMessages } from './home-flextab-exportMessages';
import { HomeFlextabMembers } from './home-flextab-members';
import { HomeFlextabNotificationPreferences } from './home-flextab-notificationPreferences';
import { HomeFlextabOtr } from './home-flextab-otr';
import { HomeFlextabRoom } from './home-flextab-room';

export class HomeFlextab {
Expand All @@ -17,6 +18,8 @@ export class HomeFlextab {

readonly notificationPreferences: HomeFlextabNotificationPreferences;

readonly otr: HomeFlextabOtr;

readonly exportMessages: HomeFlextabExportMessages;

constructor(page: Page) {
Expand All @@ -25,6 +28,7 @@ export class HomeFlextab {
this.room = new HomeFlextabRoom(page);
this.channels = new HomeFlextabChannels(page);
this.notificationPreferences = new HomeFlextabNotificationPreferences(page);
this.otr = new HomeFlextabOtr(page);
this.exportMessages = new HomeFlextabExportMessages(page);
}

Expand Down
1 change: 1 addition & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -4231,6 +4231,7 @@
"others": "others",
"Others": "Others",
"OTR": "OTR",
"OTR_messages_cannot_be_exported": "OTR messages cannot be exported",
"OTR_unavailable_for_federation": "OTR is unavailable for federated rooms",
"OTR_Description": "Off-the-record chats are secure, private and disappear once ended.",
"OTR_Chat_Declined_Title": "OTR Chat invite Declined",
Expand Down
Loading