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

Backfill hand raised events once client has synced. #2769

Draft
wants to merge 4 commits into
base: livekit
Choose a base branch
from
Draft
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
16 changes: 8 additions & 8 deletions src/useReactions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Please see LICENSE in the repository root for full details.
*/

import { render } from "@testing-library/react";
import { findByRole, getByRole, render } from "@testing-library/react";

Check failure on line 8 in src/useReactions.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

'findByRole' is defined but never used

Check failure on line 8 in src/useReactions.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

'getByRole' is defined but never used
import { act, FC } from "react";
import { describe, expect, test } from "vitest";
import { RoomEvent } from "matrix-js-sdk/src/matrix";
Expand Down Expand Up @@ -43,7 +43,7 @@
<div>
<ul>
{Object.entries(raisedHands).map(([userId, date]) => (
<li key={userId}>
<li role="listitem" key={userId}>

Check failure on line 46 in src/useReactions.test.tsx

View workflow job for this annotation

GitHub Actions / Lint, format & type check

The element li has an implicit role of listitem. Defining this explicitly is redundant and should be avoided
<span>{userId}</span>
<time>{date.getTime()}</time>
</li>
Expand Down Expand Up @@ -106,12 +106,12 @@
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
const { findByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
expect(findByRole("listitem")).toBeTruthy();
});
// If the membership event changes for a user, we want to remove
// the raised hand event.
Expand All @@ -120,12 +120,12 @@
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
const { findByRole, queryByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
expect(findByRole("listitem")).toBeTruthy();
act(() => rtcSession.testRemoveMember(memberUserIdAlice));
expect(queryByRole("list")?.children).to.have.lengthOf(0);
});
Expand All @@ -134,12 +134,12 @@
createHandRaisedReaction(memberEventAlice, membership),
]);
const rtcSession = new MockRTCSession(room, membership);
const { queryByRole } = render(
const { queryByRole, findByRole } = render(
<TestReactionsWrapper rtcSession={rtcSession}>
<TestComponent />
</TestReactionsWrapper>,
);
expect(queryByRole("list")?.children).to.have.lengthOf(1);
expect(findByRole("listitem")).toBeTruthy();
// Simulate leaving and rejoining
act(() => {
rtcSession.testRemoveMember(memberUserIdAlice);
Expand Down
50 changes: 35 additions & 15 deletions src/useReactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ export const ReactionsProvider = ({
);

const addRaisedHand = useCallback((userId: string, info: RaisedHandInfo) => {
logger.info(`Adding raised hand for ${userId}`);
setRaisedHands((prevRaisedHands) => ({
...prevRaisedHands,
[userId]: info,
}));
}, []);

const removeRaisedHand = useCallback((userId: string) => {
logger.info(`Removing raised hand for ${userId}`);
setRaisedHands(
({ [userId]: _removed, ...remainingRaisedHands }) => remainingRaisedHands,
);
Expand All @@ -121,16 +123,26 @@ export const ReactionsProvider = ({
// This effect will check the state whenever the membership of the session changes.
useEffect(() => {
// Fetches the first reaction for a given event.
const getLastReactionEvent = (
const getLastReactionEvent = async (
eventId: string,
expectedSender: string,
): MatrixEvent | undefined => {
): Promise<MatrixEvent | undefined> => {
const relations = room.relations.getChildEventsForEvent(
eventId,
RelationType.Annotation,
EventType.Reaction,
);
const allEvents = relations?.getRelations() ?? [];
let allEvents = relations?.getRelations() ?? [];
// If we found no relations for this event, fetch via fetchRelations.
if (allEvents.length === 0) {
const res = await room.client.fetchRelations(
room.roomId,
eventId,
RelationType.Annotation,
EventType.Reaction,
);
allEvents = res.chunk.map((e) => new MatrixEvent(e));
}
return allEvents.find(
(reaction) =>
reaction.event.sender === expectedSender &&
Expand Down Expand Up @@ -160,18 +172,26 @@ export const ReactionsProvider = ({
// was raised, reset.
removeRaisedHand(m.sender);
}
const reaction = getLastReactionEvent(m.eventId, m.sender);
if (reaction) {
const eventId = reaction?.getId();
if (!eventId) {
continue;
}
addRaisedHand(m.sender, {
membershipEventId: m.eventId,
reactionEventId: eventId,
time: new Date(reaction.localTimestamp),
getLastReactionEvent(m.eventId, m.sender)
.then((reaction) => {
if (reaction) {
const eventId = reaction.getId();
if (!eventId) {
return;
}
addRaisedHand(m.sender!, {
membershipEventId: m.eventId!,
reactionEventId: eventId,
time: new Date(reaction.localTimestamp),
});
}
})
.catch((ex) => {
logger.warn(
`Failed to fetch reaction for member ${m.sender} (${m.eventId})`,
ex,
);
});
}
}
// Ignoring raisedHands here because we don't want to trigger each time the raised
// hands set is updated.
Expand Down Expand Up @@ -307,8 +327,8 @@ export const ReactionsProvider = ({
return (): void => {
room.off(MatrixRoomEvent.Timeline, handleReactionEvent);
room.off(MatrixRoomEvent.Redaction, handleReactionEvent);
room.client.off(MatrixEventEvent.Decrypted, handleReactionEvent);
room.off(MatrixRoomEvent.LocalEchoUpdated, handleReactionEvent);
room.client.off(MatrixEventEvent.Decrypted, handleReactionEvent);
reactionTimeouts.forEach((t) => clearTimeout(t));
// If we're clearing timeouts, we also clear all reactions.
setReactions({});
Expand Down
1 change: 1 addition & 0 deletions src/utils/testReactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class MockRoom extends EventEmitter {
return Promise.resolve({ event_id: randomUUID() });
},
decryptEventIfNeeded: async () => {},
fetchRelations: async () => Promise.resolve({ chunk: [] }),
on() {
return this;
},
Expand Down
Loading