From 8b8bad024ea3fd657b37f236411dc4f5baadef7f Mon Sep 17 00:00:00 2001 From: "Eswar Prasad Clinton. A" <64120992+eswarclynn@users.noreply.github.com> Date: Fri, 17 May 2024 12:04:15 +0530 Subject: [PATCH] fix: use delay instead of count to wait for initial whiteboard records (#2903) * fix: add delay to reopen whiteboard * test: add logs and qa creds * fix: set permission state on receiving instance * fix: use delay instead of count to wait for initial records * fix: clean code --- .../hms-whiteboard/src/hooks/StoreClient.ts | 32 ++----- .../src/hooks/useCollaboration.ts | 92 ++++++++++--------- .../src/hooks/useSetEditorPermissions.tsx | 2 + packages/hms-whiteboard/src/utils.ts | 2 +- 4 files changed, 60 insertions(+), 68 deletions(-) diff --git a/packages/hms-whiteboard/src/hooks/StoreClient.ts b/packages/hms-whiteboard/src/hooks/StoreClient.ts index 2ced592e6f..dd4f4d8a90 100644 --- a/packages/hms-whiteboard/src/hooks/StoreClient.ts +++ b/packages/hms-whiteboard/src/hooks/StoreClient.ts @@ -1,6 +1,7 @@ import { GrpcWebFetchTransport } from '@protobuf-ts/grpcweb-transport'; import { Value_Type } from '../grpc/sessionstore'; import { StoreClient } from '../grpc/sessionstore.client'; +import { OPEN_WAIT_TIMEOUT } from '../utils'; interface OpenCallbacks { handleOpen: (values: T[]) => void; @@ -31,28 +32,23 @@ export class SessionStore { }, { abort: this.abortController.signal }, ); - /** - * on open, get key count to call handleOpen with the pre-existing values from the store - * retry if getKeysCount is called before open call is completed - */ - const keyCount = await this.retryForOpen(this.getKeysCount.bind(this)); const initialValues: T[] = []; + let initialised = false; - if (!keyCount) { - handleOpen([]); - } + // on open, wait to call handleOpen with the pre-existing values from the store + setTimeout(() => { + handleOpen(initialValues); + initialised = true; + }, OPEN_WAIT_TIMEOUT); call.responses.onMessage(message => { if (message.value) { if (message.value?.data.oneofKind === 'str') { const record = JSON.parse(message.value.data.str) as T; - if (initialValues.length === keyCount) { + if (initialised) { handleChange(message.key, record); } else { initialValues.push(record); - if (initialValues.length === keyCount) { - handleOpen(initialValues); - } } } } else { @@ -103,16 +99,4 @@ export class SessionStore { delete(key: string) { return this.storeClient.delete({ key }); } - - private async retryForOpen(fn: () => Promise, retries = 3): Promise { - try { - return await fn(); - } catch (error) { - const shouldRetry = (error as Error).message.includes('peer not found') && retries > 0; - if (!shouldRetry) { - return Promise.reject(error); - } - return await this.retryForOpen(fn, retries - 1); - } - } } diff --git a/packages/hms-whiteboard/src/hooks/useCollaboration.ts b/packages/hms-whiteboard/src/hooks/useCollaboration.ts index afc58c0fec..0f19442285 100644 --- a/packages/hms-whiteboard/src/hooks/useCollaboration.ts +++ b/packages/hms-whiteboard/src/hooks/useCollaboration.ts @@ -17,7 +17,7 @@ import { import { DEFAULT_STORE } from './default_store'; import { useSessionStore } from './useSessionStore'; import { useSetEditorPermissions } from './useSetEditorPermissions'; -import { CURRENT_PAGE_KEY, OPEN_DELAY, PAGES_DEBOUNCE_TIME, SHAPES_THROTTLE_TIME } from '../utils'; +import { CURRENT_PAGE_KEY, PAGES_DEBOUNCE_TIME, SHAPES_THROTTLE_TIME } from '../utils'; // mandatory record types required for initialisation of the whiteboard and for a full remote sync const FULL_SYNC_REQUIRED_RECORD_TYPES: TLRecord['typeName'][] = [ @@ -63,37 +63,14 @@ export function useCollaboration({ }, []); const sessionStore = useSessionStore({ token, endpoint, handleError }); + const permissions = useSetEditorPermissions({ token, editor, zoomToContent, handleError }); - useSetEditorPermissions({ token, editor, zoomToContent, handleError }); - - useEffect(() => { - if (!sessionStore) return; - - setStoreWithStatus({ status: 'loading' }); - - const unsubs: (() => void)[] = []; - - // 1. - // Connect store to yjs store and vis versa, for both the document and awareness - - /* -------------------- Document -------------------- */ - - const handleChange = (key: string, value?: TLRecord) => { - // put / remove the records in the store - store.mergeRemoteChanges(() => { - if (!value) { - return store.remove([key as TLRecord['id']]); - } - if (key === CURRENT_PAGE_KEY) { - setCurrentPage(value as TLPage); - } else { - store.put([value]); - } - }); - }; + const handleOpen = useCallback( + (initialRecords: TLRecord[]) => { + if (!sessionStore) { + return; + } - const handleOpen = (initialRecords: TLRecord[]) => { - // 2. // Initialize the tldraw store with the session store server records—or, if the session store // is empty, initialize the session store server with the default tldraw store records. const shouldUseServerRecords = FULL_SYNC_REQUIRED_RECORD_TYPES.every( @@ -118,20 +95,49 @@ export function useCollaboration({ status: 'synced-remote', connectionStatus: 'online', }); - }; + }, + [store, sessionStore], + ); + + const handleChange = useCallback( + (key: string, value?: TLRecord) => { + // put / remove the records in the store + store.mergeRemoteChanges(() => { + if (!value) { + return store.remove([key as TLRecord['id']]); + } + if (key === CURRENT_PAGE_KEY) { + setCurrentPage(value as TLPage); + } else { + transact(() => { + store.put([value]); + if (key === TLINSTANCE_ID) { + store.put([ + { ...value, canMoveCamera: !!zoomToContent, isReadonly: !permissions.includes('write') } as TLInstance, + ]); + } + }); + } + }); + }, + [store, permissions, zoomToContent], + ); + + useEffect(() => { + if (!sessionStore) return; + + setStoreWithStatus({ status: 'loading' }); + + const unsubs: (() => void)[] = []; // Open session and sync the session store changes to the store - // On opening, closing and reopening whiteboard with no delay(in case of role change), the session store server needs time to cleanup on the close before opening again - // so there is a delay here before opening the connection - setTimeout(() => { - sessionStore - .open({ - handleOpen, - handleChange, - handleError, - }) - .then(unsub => unsubs.push(unsub)); - }, OPEN_DELAY); + sessionStore + .open({ + handleOpen, + handleChange, + handleError, + }) + .then(unsub => unsubs.push(unsub)); // Sync store changes to the yjs doc unsubs.push( @@ -187,7 +193,7 @@ export function useCollaboration({ unsubs.forEach(fn => fn()); unsubs.length = 0; }; - }, [store, sessionStore, handleError]); + }, [store, sessionStore, handleChange, handleOpen, handleError]); useEffect(() => { if (!editor || !sessionStore) return; diff --git a/packages/hms-whiteboard/src/hooks/useSetEditorPermissions.tsx b/packages/hms-whiteboard/src/hooks/useSetEditorPermissions.tsx index 1c9626d5da..48a0eac2cc 100644 --- a/packages/hms-whiteboard/src/hooks/useSetEditorPermissions.tsx +++ b/packages/hms-whiteboard/src/hooks/useSetEditorPermissions.tsx @@ -33,4 +33,6 @@ export const useSetEditorPermissions = ({ const isReadonly = !permissions.includes('write'); editor?.updateInstanceState({ isReadonly }); }, [permissions, zoomToContent, editor]); + + return permissions; }; diff --git a/packages/hms-whiteboard/src/utils.ts b/packages/hms-whiteboard/src/utils.ts index 2a15dc5879..1a4fc22de2 100644 --- a/packages/hms-whiteboard/src/utils.ts +++ b/packages/hms-whiteboard/src/utils.ts @@ -19,4 +19,4 @@ export default function decodeJWT(token?: string) { export const CURRENT_PAGE_KEY = 'currentPage'; export const SHAPES_THROTTLE_TIME = 11; export const PAGES_DEBOUNCE_TIME = 200; -export const OPEN_DELAY = 100; +export const OPEN_WAIT_TIMEOUT = 1000;