From 68fc06835cd92c7c2e757f5a9aecdc8f563aab3e Mon Sep 17 00:00:00 2001 From: Milton Moura Date: Wed, 13 Nov 2024 20:18:47 -0100 Subject: [PATCH] Make persist opts optional, refactor and changeset Signed-off-by: Milton Moura --- .../FallbackSnapshotProvider.test.tsx | 2 +- .../FallbackSnapshotProvider.tsx | 136 +++++++++++------- packages/react-sdk/src/state/types.ts | 6 +- .../src/state/whiteboardInstanceImpl.ts | 4 +- 4 files changed, 93 insertions(+), 55 deletions(-) diff --git a/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.test.tsx b/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.test.tsx index 22ecca5ca..8ed1ad3f4 100644 --- a/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.test.tsx +++ b/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.test.tsx @@ -208,7 +208,7 @@ describe('', () => { ['unencrypted', undefined], ['encrypted', { event: mockRoomEncryption() }], ])( - 'should persist snapshot immediatly if the last event is an invite and inviter is the current user', + 'should persist snapshot immediatly if the last event is an invite and inviter is the current user (%s room)', (_, encryptionState) => { (useGetRoomMembersQuery as Mock).mockReturnValue({ data: mockRoomMembersOwnUser, diff --git a/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.tsx b/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.tsx index 7f7541b84..0eb929c64 100644 --- a/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.tsx +++ b/packages/react-sdk/src/components/FallbackSnapshotProvider/FallbackSnapshotProvider.tsx @@ -20,6 +20,7 @@ import { } from '@matrix-widget-toolkit/api'; import { useWidgetApi } from '@matrix-widget-toolkit/react'; import { PropsWithChildren, useEffect } from 'react'; +import { RoomEncryptionEvent, RoomHistoryVisibilityEvent } from '../../model'; import { useActiveWhiteboardInstance } from '../../state'; import { useGetRoomEncryptionQuery, @@ -35,37 +36,23 @@ export function FallbackSnapshotProvider({ children }: PropsWithChildren<{}>) { const ownUserId = useWidgetApi().widgetParameters.userId; useEffect(() => { - const isRoomEncrypted = - (roomEncryption && roomEncryption.event?.content?.algorithm) !== - undefined; - const roomHistoryVisibilityValue = - roomHistoryVisibility && - roomHistoryVisibility?.event?.content?.history_visibility; - const isRoomHistoryVisibilityShared = - roomHistoryVisibilityValue && roomHistoryVisibilityValue === 'shared'; - const sendFallbackSnapshot = - isRoomEncrypted || - (!isRoomHistoryVisibilityShared && - roomHistoryVisibilityValue !== undefined); + const isRoomEncrypted = checkIfRoomEncrypted(roomEncryption); + const roomHistoryVisibilityValue = getRoomHistoryVisibilityValue( + roomHistoryVisibility, + ); + const isRoomHistoryVisibilityShared = checkIfRoomHistoryVisibilityShared( + roomHistoryVisibilityValue, + ); + const sendFallbackSnapshot = shouldSendFallbackSnapshot( + isRoomEncrypted, + isRoomHistoryVisibilityShared, + roomHistoryVisibilityValue, + ); if (sendFallbackSnapshot) { - let membershipFilter = ( - member: StateEvent, - ) => { - return ( - member.content.membership == 'invite' || - member.content.membership == 'join' - ); - }; - if (roomHistoryVisibilityValue == 'joined') { - membershipFilter = ( - member: StateEvent, - ) => { - return member.content.membership == 'join'; - }; - } - + const membershipFilter = getMembershipFilter(roomHistoryVisibilityValue); const members = roomMembers?.entities; + if (members) { const invitesOrJoins = Object.values(members).filter(membershipFilter); const sortedInvitesOrJoins = invitesOrJoins.sort((a, b) => { @@ -74,33 +61,18 @@ export function FallbackSnapshotProvider({ children }: PropsWithChildren<{}>) { const lastInviteOrJoin = sortedInvitesOrJoins[0]; if ( - lastInviteOrJoin.content.membership == 'join' && - roomHistoryVisibilityValue == 'invited' + shouldUseInviteTimestamp(lastInviteOrJoin, roomHistoryVisibilityValue) ) { - // get the invite instead - const joinEventObject = Object(lastInviteOrJoin); - const prevEventWasInvite = - joinEventObject['unsigned'].prev_content?.membership == 'invite'; - if (prevEventWasInvite) { - const _inviteEventId = joinEventObject['unsigned']?.replaces_state; - // TODO: we now need to get the timestamp of the invite event and use that instead - // but the Widget API doesn't have a way to get an event by event ID - // and the `readRoomEvents` is filtering out events with state_key set - } + // TODO: Implement logic to get the timestamp of the invite event. + // There is currently no way to get an event by id or + // fetch an older state event from the room timeline using the Widget API } - let immediate = false; - if ( - lastInviteOrJoin.sender == ownUserId && - lastInviteOrJoin.content.membership == 'invite' - ) { - // if the sender is the current user, try to persist immediatly - immediate = true; - } + const immediate = shouldPersistImmediately(lastInviteOrJoin, ownUserId); whiteboardInstance.persist({ timestamp: lastInviteOrJoin.origin_server_ts, - immediate: immediate, + immediate, }); } } @@ -114,3 +86,69 @@ export function FallbackSnapshotProvider({ children }: PropsWithChildren<{}>) { return <>{children}; } + +function checkIfRoomEncrypted(roomEncryption?: { + event: StateEvent | undefined; +}): boolean { + return ( + (roomEncryption && roomEncryption.event?.content?.algorithm) !== undefined + ); +} + +function getRoomHistoryVisibilityValue(roomHistoryVisibility?: { + event: StateEvent | undefined; +}): string | undefined { + return roomHistoryVisibility?.event?.content?.history_visibility; +} + +function checkIfRoomHistoryVisibilityShared( + roomHistoryVisibilityValue: string | undefined, +): boolean { + return roomHistoryVisibilityValue === 'shared'; +} + +function shouldSendFallbackSnapshot( + isRoomEncrypted: boolean, + isRoomHistoryVisibilityShared: boolean, + roomHistoryVisibilityValue: string | undefined, +): boolean { + return ( + isRoomEncrypted || + (!isRoomHistoryVisibilityShared && roomHistoryVisibilityValue !== undefined) + ); +} + +function getMembershipFilter( + roomHistoryVisibilityValue: string | undefined, +): (member: StateEvent) => boolean { + if (roomHistoryVisibilityValue === 'joined') { + return (member) => member.content.membership === 'join'; + } + return (member) => + member.content.membership === 'invite' || + member.content.membership === 'join'; +} + +function shouldUseInviteTimestamp( + lastInviteOrJoin: StateEvent, + roomHistoryVisibilityValue: string | undefined, +) { + if ( + lastInviteOrJoin.content.membership === 'join' && + roomHistoryVisibilityValue === 'invited' + ) { + const joinEventObject = Object(lastInviteOrJoin); + return joinEventObject['unsigned'].prev_content?.membership === 'invite'; + } + return false; +} + +function shouldPersistImmediately( + lastInviteOrJoin: StateEvent, + ownUserId: string | undefined, +) { + return ( + lastInviteOrJoin.sender === ownUserId && + lastInviteOrJoin.content.membership === 'invite' + ); +} diff --git a/packages/react-sdk/src/state/types.ts b/packages/react-sdk/src/state/types.ts index aed7179b3..8668d0b5c 100644 --- a/packages/react-sdk/src/state/types.ts +++ b/packages/react-sdk/src/state/types.ts @@ -56,8 +56,8 @@ export type WhiteboardStatistics = { }; export type PersistOptions = { - timestamp?: number; - immediate?: boolean; + timestamp: number; + immediate: boolean; }; /** An instance of a whiteboard that can be used to read and manipulate it. */ @@ -153,7 +153,7 @@ export type WhiteboardInstance = { destroy(): void; /** Persist the whiteboard state. */ - persist(options: PersistOptions): Promise; + persist(options?: PersistOptions): Promise; }; export type ElementUpdate = { diff --git a/packages/react-sdk/src/state/whiteboardInstanceImpl.ts b/packages/react-sdk/src/state/whiteboardInstanceImpl.ts index 6b3a15430..34019cc6a 100644 --- a/packages/react-sdk/src/state/whiteboardInstanceImpl.ts +++ b/packages/react-sdk/src/state/whiteboardInstanceImpl.ts @@ -453,8 +453,8 @@ export class WhiteboardInstanceImpl implements WhiteboardInstance { this.communicationChannel.destroy(); } - async persist(options: PersistOptions): Promise { - if (options.timestamp !== undefined && options.immediate !== undefined) { + async persist(options?: PersistOptions): Promise { + if (options !== undefined) { const snapshot = this.synchronizedDocument.getLatestDocumentSnapshot(); if (snapshot && snapshot.origin_server_ts < options.timestamp) { if (options.immediate) {