From e2901d95356c4fe3f37a5ad959db04e7bfeba2bc Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Wed, 20 Sep 2023 17:20:30 +0530 Subject: [PATCH] chore: only invalidate vault user cache when required (#2519) --- .../Domain/VaultInvite/VaultInviteService.ts | 3 +- .../src/Domain/VaultUser/VaultUserService.ts | 38 +++++++++--------- .../Domain/VaultUser/VaultUserServiceEvent.ts | 2 + .../VaultUser/VaultUserServiceInterface.ts | 1 + packages/snjs/lib/Application/Application.ts | 1 + .../Dependencies/DependencyEvents.ts | 1 - .../FileView/FileViewWithoutProtection.tsx | 39 ++++++++++++++++++- .../Components/NoteView/NoteView.tsx | 17 ++++++++ 8 files changed, 80 insertions(+), 22 deletions(-) diff --git a/packages/services/src/Domain/VaultInvite/VaultInviteService.ts b/packages/services/src/Domain/VaultInvite/VaultInviteService.ts index b35c14310..3c9fa5df3 100644 --- a/packages/services/src/Domain/VaultInvite/VaultInviteService.ts +++ b/packages/services/src/Domain/VaultInvite/VaultInviteService.ts @@ -179,7 +179,8 @@ export class VaultInviteService this.removePendingInvite(pendingInvite.invite.uuid) - void this.sync.sync() + this.sync.sync().catch(console.error) + this.vaultUsers.invalidateVaultUsersCache(pendingInvite.invite.shared_vault_uuid).catch(console.error) await this._decryptErroredPayloads.execute() diff --git a/packages/services/src/Domain/VaultUser/VaultUserService.ts b/packages/services/src/Domain/VaultUser/VaultUserService.ts index 4e9333bd9..137ca575b 100644 --- a/packages/services/src/Domain/VaultUser/VaultUserService.ts +++ b/packages/services/src/Domain/VaultUser/VaultUserService.ts @@ -12,16 +12,10 @@ import { AbstractService } from './../Service/AbstractService' import { VaultUserServiceEvent } from './VaultUserServiceEvent' import { Result } from '@standardnotes/domain-core' import { IsVaultOwner } from './UseCase/IsVaultOwner' -import { InternalEventInterface } from '../Internal/InternalEventInterface' -import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface' -import { ApplicationEvent } from '../Event/ApplicationEvent' import { IsReadonlyVaultMember } from './UseCase/IsReadonlyVaultMember' import { IsVaultAdmin } from './UseCase/IsVaultAdmin' -export class VaultUserService - extends AbstractService - implements VaultUserServiceInterface, InternalEventHandlerInterface -{ +export class VaultUserService extends AbstractService implements VaultUserServiceInterface { constructor( private vaults: VaultServiceInterface, private vaultLocks: VaultLockServiceInterface, @@ -47,20 +41,28 @@ export class VaultUserService ;(this._leaveVault as unknown) = undefined } - async handleEvent(event: InternalEventInterface): Promise { - if (event.type === ApplicationEvent.CompletedFullSync) { - this.vaults.getVaults().forEach((vault) => { + async invalidateVaultUsersCache(sharedVaultUuid?: string) { + if (sharedVaultUuid) { + await this._getVaultUsers.execute({ + sharedVaultUuid: sharedVaultUuid, + readFromCache: false, + }) + void this.notifyEvent(VaultUserServiceEvent.InvalidatedUserCacheForVault, sharedVaultUuid) + return + } + await Promise.all( + this.vaults.getVaults().map(async (vault) => { if (!vault.isSharedVaultListing()) { return } - this._getVaultUsers - .execute({ - sharedVaultUuid: vault.sharing.sharedVaultUuid, - readFromCache: false, - }) - .catch(console.error) - }) - } + await this._getVaultUsers.execute({ + sharedVaultUuid: vault.sharing.sharedVaultUuid, + readFromCache: false, + }) + void this.notifyEvent(VaultUserServiceEvent.InvalidatedUserCacheForVault, vault.sharing.sharedVaultUuid) + }), + ) + void this.notifyEvent(VaultUserServiceEvent.InvalidatedAllUserCache) } public async getSharedVaultUsersFromServer( diff --git a/packages/services/src/Domain/VaultUser/VaultUserServiceEvent.ts b/packages/services/src/Domain/VaultUser/VaultUserServiceEvent.ts index c9fb7e930..f19bb29b3 100644 --- a/packages/services/src/Domain/VaultUser/VaultUserServiceEvent.ts +++ b/packages/services/src/Domain/VaultUser/VaultUserServiceEvent.ts @@ -1,3 +1,5 @@ export enum VaultUserServiceEvent { UsersChanged = 'VaultUserServiceEvent.UsersChanged', + InvalidatedAllUserCache = 'VaultUserServiceEvent.InvalidatedUserCache', + InvalidatedUserCacheForVault = 'VaultUserServiceEvent.InvalidatedUserCacheForVault', } diff --git a/packages/services/src/Domain/VaultUser/VaultUserServiceInterface.ts b/packages/services/src/Domain/VaultUser/VaultUserServiceInterface.ts index e41f2ba65..a46b3c23e 100644 --- a/packages/services/src/Domain/VaultUser/VaultUserServiceInterface.ts +++ b/packages/services/src/Domain/VaultUser/VaultUserServiceInterface.ts @@ -15,4 +15,5 @@ export interface VaultUserServiceInterface extends ApplicationServiceInterface isVaultUserOwner(user: SharedVaultUserServerHash): boolean getFormattedMemberPermission(permission: string): string + invalidateVaultUsersCache(sharedVaultUuid?: string): Promise } diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 8abfe211c..c69d5343b 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -469,6 +469,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli source: SyncSource.External, sourceDescription: 'Application Launch', }) + this.vaultUsers.invalidateVaultUsersCache().catch(console.error) }) .catch((error) => { void this.notifyEvent(ApplicationEvent.LocalDatabaseReadError, error) diff --git a/packages/snjs/lib/Application/Dependencies/DependencyEvents.ts b/packages/snjs/lib/Application/Dependencies/DependencyEvents.ts index c194c7711..6d7781f80 100644 --- a/packages/snjs/lib/Application/Dependencies/DependencyEvents.ts +++ b/packages/snjs/lib/Application/Dependencies/DependencyEvents.ts @@ -43,7 +43,6 @@ export function RegisterApplicationServicesEvents(container: Dependencies, event events.addEventHandler(container.get(TYPES.VaultInviteService), ApplicationEvent.Launched) events.addEventHandler(container.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites) events.addEventHandler(container.get(TYPES.VaultInviteService), WebSocketsServiceEvent.UserInvitedToSharedVault) - events.addEventHandler(container.get(TYPES.VaultUserService), ApplicationEvent.CompletedFullSync) if (container.get(TYPES.FilesBackupService)) { events.addEventHandler(container.get(TYPES.FilesBackupService), ApplicationEvent.ApplicationStageChanged) diff --git a/packages/web/src/javascripts/Components/FileView/FileViewWithoutProtection.tsx b/packages/web/src/javascripts/Components/FileView/FileViewWithoutProtection.tsx index f09877af1..ae77848cc 100644 --- a/packages/web/src/javascripts/Components/FileView/FileViewWithoutProtection.tsx +++ b/packages/web/src/javascripts/Components/FileView/FileViewWithoutProtection.tsx @@ -11,11 +11,35 @@ import Popover from '../Popover/Popover' import FilePreviewInfoPanel from '../FilePreview/FilePreviewInfoPanel' import { useFileDragNDrop } from '../FileDragNDropProvider' import RoundIconButton from '../Button/RoundIconButton' +import { useItemVaultInfo } from '@/Hooks/useItemVaultInfo' +import Icon from '../Icon/Icon' +import { VaultUserServiceEvent } from '@standardnotes/snjs' const SyncTimeoutNoDebounceMs = 100 const SyncTimeoutDebounceMs = 350 const FileViewWithoutProtection = ({ application, file }: FileViewProps) => { + const { vault } = useItemVaultInfo(file) + + const [isReadonly, setIsReadonly] = useState(false) + useEffect(() => { + if (!vault) { + return + } + + setIsReadonly(application.vaultUsers.isCurrentUserReadonlyVaultMember(vault)) + }, [application.vaultUsers, vault]) + useEffect(() => { + return application.vaultUsers.addEventObserver((event, data) => { + if (event === VaultUserServiceEvent.InvalidatedUserCacheForVault) { + if ((data as string) !== vault?.sharing?.sharedVaultUuid) { + return + } + setIsReadonly(vault ? application.vaultUsers.isCurrentUserReadonlyVaultMember(vault) : false) + } + }) + }, [application.vaultUsers, vault]) + const syncTimeoutRef = useRef() const fileInfoButtonRef = useRef(null) @@ -67,6 +91,12 @@ const FileViewWithoutProtection = ({ application, file }: FileViewProps) => { return (
+ {isReadonly && ( +
+ + This file is readonly +
+ )}
{ spellCheck={false} defaultValue={file.name} autoComplete="off" + disabled={isReadonly} />
- + {!isReadonly && } {
- +
diff --git a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx index 0980c4f22..40d369117 100644 --- a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx +++ b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx @@ -26,6 +26,7 @@ import { PrefKey, ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction, SNNote, + VaultUserServiceEvent, } from '@standardnotes/snjs' import { confirmDialog, DELETE_NOTE_KEYBOARD_COMMAND, KeyboardKey } from '@standardnotes/ui-services' import { ChangeEventHandler, createRef, CSSProperties, KeyboardEventHandler, RefObject } from 'react' @@ -97,6 +98,7 @@ class NoteView extends AbstractComponent { private removeNoteStreamObserver?: () => void private removeComponentManagerObserver?: () => void private removeInnerNoteObserver?: () => void + private removeVaultUsersEventHandler?: () => void private protectionTimeoutId: ReturnType | null = null private noteViewElementRef: RefObject @@ -158,6 +160,9 @@ class NoteView extends AbstractComponent { this.removeTrashKeyObserver?.() this.removeTrashKeyObserver = undefined + this.removeVaultUsersEventHandler?.() + this.removeVaultUsersEventHandler = undefined + this.clearNoteProtectionInactivityTimer() ;(this.ensureNoteIsInsertedBeforeUIAction as unknown) = undefined @@ -211,6 +216,18 @@ class NoteView extends AbstractComponent { override componentDidMount(): void { super.componentDidMount() + this.removeVaultUsersEventHandler = this.application.vaultUsers.addEventObserver((event, data) => { + if (event === VaultUserServiceEvent.InvalidatedUserCacheForVault) { + const vault = this.application.vaults.getItemVault(this.note) + if ((data as string) !== vault?.sharing?.sharedVaultUuid) { + return + } + this.setState({ + readonly: vault ? this.application.vaultUsers.isCurrentUserReadonlyVaultMember(vault) : false, + }) + } + }) + this.registerKeyboardShortcuts() this.removeInnerNoteObserver = this.controller.addNoteInnerValueChangeObserver((note, source) => {