Skip to content

Commit

Permalink
fix(matrix-client/reactions/edits): add conditional decrypt condition…
Browse files Browse the repository at this point in the history
… to fetched events and apply reactions (#2411)
  • Loading branch information
domw30 authored Nov 7, 2024
1 parent bef4178 commit 484319b
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 118 deletions.
2 changes: 1 addition & 1 deletion src/components/message/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-size: 12px;
line-height: 14px;
}
}
7 changes: 6 additions & 1 deletion src/lib/chat/matrix-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ export class MatrixClient implements IChatClient {
hasMore = await this.matrix.paginateEventTimeline(liveTimeline, { backwards: true, limit: 50 });
}

const isEncrypted = room?.hasEncryptionStateEvent();
if (isEncrypted) {
await room.decryptAllEvents();
}

const effectiveEvents = events.map((event) => event.getEffectiveEvent());
const messages = await this.getAllChatMessagesFromRoom(effectiveEvents);

Expand Down Expand Up @@ -551,7 +556,7 @@ export class MatrixClient implements IChatClient {
const events = room.getLiveTimeline().getEvents();

const result = events
.filter((event) => event.getType() === MatrixConstants.REACTION)
.filter((event) => event.getType() === MatrixConstants.REACTION && !event?.event?.unsigned?.redacted_because)
.map((event) => {
const content = event.getContent();
const relatesTo = content[MatrixConstants.RELATES_TO];
Expand Down
10 changes: 9 additions & 1 deletion src/store/messages/saga.fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ describe(fetch, () => {
const messageResponse = { hasMore: false, messages: [] };

const { storeState } = await subject(fetch, { payload: { channelId: channel.id } })
.provide([[matchers.call.fn(chatClient.getMessagesByChannelId), messageResponse]])
.provide([
[matchers.call.fn(chatClient.getMessagesByChannelId), messageResponse],
[matchers.call.fn(mapMessagesAndPreview), messageResponse.messages],
])
.withReducer(rootReducer, initialChannelState(channel))
.run();

Expand All @@ -62,8 +65,12 @@ describe(fetch, () => {

it('sets hasLoadedMessages on channel', async () => {
const channel = { id: 'channel-id', hasLoadedMessages: false };
const messageResponse = { hasMore: false, messages: [] };

const { storeState } = await subject(fetch, { payload: { channelId: channel.id } })
.provide([
[matchers.call.fn(mapMessagesAndPreview), messageResponse.messages],
])
.withReducer(rootReducer, initialChannelState(channel))
.run();

Expand All @@ -86,6 +93,7 @@ describe(fetch, () => {
.withReducer(rootReducer, initialState as any)
.provide([
[call([chatClient, chatClient.getMessagesByChannelId], channel.id, referenceTimestamp), messageResponse],
[matchers.call.fn(mapMessagesAndPreview), messageResponse.messages],
])
.run();

Expand Down
50 changes: 50 additions & 0 deletions src/store/messages/saga.receiveNewMessage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { denormalize as denormalizeChannel } from '../channels';
import { expectSaga, stubResponse } from '../../test/saga';
import { markConversationAsRead } from '../channels/saga';
import { StoreBuilder } from '../test/store';
import { getMessageEmojiReactions } from '../../lib/chat';

describe(receiveNewMessage, () => {
function subject(...args: Parameters<typeof expectSaga>) {
Expand All @@ -29,7 +30,11 @@ describe(receiveNewMessage, () => {
const initialState = new StoreBuilder().withConversationList({ id: channelId, messages: existingMessages });

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())

.run();

const channel = denormalizeChannel(channelId, storeState);
Expand All @@ -45,6 +50,9 @@ describe(receiveNewMessage, () => {
.withUsers({ userId: 'user-1', matrixId: 'matrix-id', firstName: 'the real user' });

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand All @@ -60,6 +68,7 @@ describe(receiveNewMessage, () => {

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
stubResponse(call(getPreview, 'www.google.com'), stubPreview),
])
.withReducer(rootReducer, initialState.build())
Expand All @@ -69,6 +78,25 @@ describe(receiveNewMessage, () => {
expect(channel.messages[0].preview).toEqual(stubPreview);
});

it('adds the reactions to the message', async () => {
const channelId = 'channel-id';
const message = { id: 'message-id', message: 'www.google.com' };
const stubPreview = { id: 'simulated-preview' };
const stubReactions = [{ eventId: 'message-id', key: '😂' }];
const initialState = new StoreBuilder().withConversationList({ id: channelId });

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), stubReactions),
stubResponse(call(getPreview, 'www.google.com'), stubPreview),
])
.withReducer(rootReducer, initialState.build())
.run();

const channel = denormalizeChannel(channelId, storeState);
expect(channel.messages[0].reactions).toEqual({ '😂': 1 });
});

it('does nothing if the channel does not exist', async () => {
const channelId = 'non-existing-channel-id';
const initialState = new StoreBuilder().withConversationList({ id: 'other-channel' });
Expand All @@ -89,6 +117,9 @@ describe(receiveNewMessage, () => {
const initialState = new StoreBuilder().withConversationList({ id: channelId, messages: existingMessages });

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand All @@ -102,6 +133,9 @@ describe(receiveNewMessage, () => {
const conversationState = new StoreBuilder().withConversationList({ id: 'channel-id' });

await subject(receiveNewMessage, { payload: { channelId: 'channel-id', message } })
.provide([
stubResponse(call(getMessageEmojiReactions, 'channel-id'), [{}]),
])
.withReducer(rootReducer, conversationState.build())
.not.call(markConversationAsRead, 'channel-id')
.run();
Expand All @@ -125,6 +159,9 @@ describe(receiveNewMessage, () => {
});

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand All @@ -149,6 +186,9 @@ describe(receiveNewMessage, () => {
});

const { storeState } = await subject(receiveNewMessage, { payload: { channelId, message } })
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand Down Expand Up @@ -177,6 +217,9 @@ describe(receiveNewMessage, () => {
const initialState = new StoreBuilder().withConversationList({ id: channelId, messages: existingMessages });

const { storeState } = await subject(batchedReceiveNewMessage, eventPayloads)
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand All @@ -202,6 +245,10 @@ describe(receiveNewMessage, () => {
);

const { storeState } = await subject(batchedReceiveNewMessage, eventPayloads)
.provide([
stubResponse(call(getMessageEmojiReactions, channelId1), [{}]),
stubResponse(call(getMessageEmojiReactions, channelId2), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand All @@ -226,6 +273,9 @@ describe(receiveNewMessage, () => {
const initialState = new StoreBuilder().withConversationList({ id: channelId, messages: existingMessages });

const { storeState } = await subject(batchedReceiveNewMessage, eventPayloads)
.provide([
stubResponse(call(getMessageEmojiReactions, channelId), [{}]),
])
.withReducer(rootReducer, initialState.build())
.run();

Expand Down
119 changes: 30 additions & 89 deletions src/store/messages/saga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
sendBrowserNotification,
receiveUpdateMessage,
replaceOptimisticMessage,
applyEmojiReactions,
onMessageEmojiReactionChange,
updateMessageEmojiReaction,
sendEmojiReaction,
Expand Down Expand Up @@ -289,7 +288,7 @@ describe(receiveUpdateMessage, () => {
const { storeState } = await expectSaga(receiveUpdateMessage, {
payload: { channelId: 'channel-1', message: editedMessage },
})
.provide([...successResponses()])
.provide([...successResponses(), [call(getMessageEmojiReactions, 'channel-1'), [{}]]])
.withReducer(rootReducer, initialState.build())
.run();

Expand All @@ -300,20 +299,47 @@ describe(receiveUpdateMessage, () => {
const message = { id: 8667728016, message: 'original message' };
const editedMessage = { id: 8667728016, message: 'edited message: www.example.com' };
const preview = { id: 'fdf2ce2b-062e-4a83-9c27-03f36c81c0c0', type: 'link' };
const initialState = new StoreBuilder().withConversationList({ id: 'channel-1', messages: [message] as any });
const { storeState } = await expectSaga(receiveUpdateMessage, {
payload: { channelId: 'channel-1', message: editedMessage },
})
.provide([
[call(getMessageEmojiReactions, 'channel-1'), [{}]],
[call(getPreview, editedMessage.message), preview],

...successResponses(),
])
.withReducer(rootReducer, initialState.build())
.run();
expect(storeState.normalized.messages[message.id]).toEqual({ ...editedMessage, preview });
});

it('adds the reactions if they exist', async () => {
const message = { id: 8667728016, message: 'original message' };
const editedMessage = { id: 8667728016, message: 'edited message with reaction' };
const reactions = [
{ eventId: 8667728016, key: '😂' },
{ eventId: 8667728016, key: '👍' },
];

const expectedReactions = {
'😂': 1,
'👍': 1,
};

const initialState = new StoreBuilder().withConversationList({ id: 'channel-1', messages: [message] as any });

const { storeState } = await expectSaga(receiveUpdateMessage, {
payload: { channelId: 'channel-1', message: editedMessage },
})
.provide([
[call(getPreview, editedMessage.message), preview],
[call(getMessageEmojiReactions, 'channel-1'), reactions],
...successResponses(),
])
.withReducer(rootReducer, initialState.build())
.run();

expect(storeState.normalized.messages[message.id]).toEqual({ ...editedMessage, preview });
expect(storeState.normalized.messages[message.id]).toEqual({ ...editedMessage, reactions: expectedReactions });
});

function successResponses() {
Expand Down Expand Up @@ -392,91 +418,6 @@ describe(replaceOptimisticMessage, () => {
});
});

describe('applyEmojiReactions', () => {
it('applies emoji reactions to messages correctly', async () => {
const roomId = 'room-id';
const messages = [
{ id: 'message-1', reactions: {} },
{ id: 'message-2', reactions: {} },
] as any;

const reactions = [
{ eventId: 'message-1', key: '😲' },
{ eventId: 'message-1', key: '❤️' },
{ eventId: 'message-2', key: '😂' },
{ eventId: 'message-2', key: '❤️' },
];

await expectSaga(applyEmojiReactions, roomId, messages)
.provide([[call(getMessageEmojiReactions, roomId), reactions]])
.run();

expect(messages).toEqual([
{ id: 'message-1', reactions: { '😲': 1, '❤️': 1 } },
{ id: 'message-2', reactions: { '😂': 1, '❤️': 1 } },
]);
});

it('does not modify messages without reactions', async () => {
const roomId = 'room-id';
const messages = [
{ id: 'message-1', reactions: {} },
{ id: 'message-2', reactions: {} },
] as any;

const reactions = [];

await expectSaga(applyEmojiReactions, roomId, messages)
.provide([[call(getMessageEmojiReactions, roomId), reactions]])
.run();

expect(messages).toEqual([
{ id: 'message-1', reactions: {} },
{ id: 'message-2', reactions: {} },
]);
});

it('accumulates reactions for the same key', async () => {
const roomId = 'room-id';
const messages = [
{ id: 'message-1', reactions: {} },
] as any;

const reactions = [
{ eventId: 'message-1', key: '❤️' },
{ eventId: 'message-1', key: '❤️' },
{ eventId: 'message-1', key: '😂' },
];

await expectSaga(applyEmojiReactions, roomId, messages)
.provide([[call(getMessageEmojiReactions, roomId), reactions]])
.run();

expect(messages).toEqual([
{ id: 'message-1', reactions: { '❤️': 2, '😂': 1 } },
]);
});

it('handles reactions when there are no matching messages', async () => {
const roomId = 'room-id';
const messages = [
{ id: 'message-1', reactions: {} },
] as any;

const reactions = [
{ eventId: 'message-2', key: '❤️' },
];

await expectSaga(applyEmojiReactions, roomId, messages)
.provide([[call(getMessageEmojiReactions, roomId), reactions]])
.run();

expect(messages).toEqual([
{ id: 'message-1', reactions: {} },
]);
});
});

describe('onMessageEmojiReactionChange', () => {
it('calls updateMessageEmojiReaction with the correct arguments', async () => {
const roomId = 'room-id';
Expand Down
Loading

0 comments on commit 484319b

Please sign in to comment.