import { useCallback , useEffect , useMemo , useState } from "react" ; import { Entry , EntryFacade , EntryID , Group , Vault , VaultSourceID , createEntryFacade , GroupID , EntryType } from "buttercup" ; import { useBiometricsEnabledForSource } from "./biometrics" ; import { getVault , getVaultManager , getVaultSource , sourceHasOfflineCopy } from "../services/buttercup" ; import { getEmitter as getStatisticsEmitter , getSourceItemsCount } from "../services/statistics" ; import { VaultContentsItem , VaultDetails } from "../types" ; interface VaultStatistics { authMethod : "password" | "biometrics" ; numEntries : number ; numGroups : number ; offlineAvailable : boolean ; } function extractItems ( vault , targetGroupID = null ) { let groups = [ ] , entries = [ ] ; if ( targetGroupID ) { const group = vault . findGroupByID ( targetGroupID ) ; if ( ! group ) return [ ] ; groups = group . getGroups ( ) ; entries = group . getEntries ( ) ; } else { groups = vault . getGroups ( ) ; } return [ ... groups . map ( ( group ) : VaultContentsItem => ( { id : group . id , title : group . getTitle ( ) , type : "group" as const , isTrash : group . isTrash ( ) } ) ) , ... entries . map ( ( entry ) : VaultContentsItem => ( { id : entry . id , title : entry . getProperty ( "title" ) as string , type : "entry" as const , entryType : entry . getType ( ) , entryProperties : entry . getProperties ( ) , isTrash : false } ) ) ] ; } function sortItems ( items ) { return items . sort ( ( a , b ) => { if ( a . isTrash ) return 1 ; if ( b . isTrash ) return - 1 ; if ( a . type === "group" && b . type === "entry" ) return - 1 ; if ( b . type === "group" && a . type === "entry" ) return 1 ; if ( a . title > b . title ) return 1 ; if ( b . title > a . title ) return - 1 ; return 0 ; } ) ; } export function useEntries ( sourceID ) { const source = useMemo ( ( ) => getVaultSource ( sourceID ) , [ sourceID ] ) ; const [ entries , setEntries ] = useState < Array < Entry > > ( [ ] ) ; const updateContents = useCallback ( ( ) => { setEntries ( source . vault . getAllEntries ( ) ) ; } , [ source ] ) ; useEffect ( ( ) => { if ( ! source ) return ; updateContents ( ) ; source . on ( "updated" , updateContents ) ; return ( ) => { source . off ( "updated" , updateContents ) ; } ; } , [ source , updateContents ] ) ; return entries ; } export function useEntryFacade ( sourceID , entryID ) { const source = useMemo ( ( ) => getVaultSource ( sourceID ) , [ sourceID ] ) ; const [ entryFacade , setEntryFacade ] = useState < EntryFacade > ( null ) ; const updateFacade = useCallback ( ( vault ) => { const entry = vault . findEntryByID ( entryID ) ; if ( entry ) { setEntryFacade ( createEntryFacade ( entry ) ) ; } } , [ entryID ] ) ; useEffect ( ( ) => { const cb = ( ) => updateFacade ( source . vault ) ; if ( source . vault ) cb ( ) ; source . on ( "updated" , cb ) ; return ( ) => { source . off ( "updated" , cb ) ; } ; } , [ updateFacade , source ] ) ; return entryFacade ; } export function useGroupTitle ( sourceID , groupID ) { const vault = useMemo ( ( ) => sourceID ? getVault ( sourceID ) : null , [ sourceID ] ) ; const group = useMemo ( ( ) => vault ?. findGroupByID ( groupID ) ?? null , [ vault ] ) ; return group ?. getTitle ( ) ?? null ; } export function useVaultContents ( sourceID , targetGroupID = null ) { const source = useMemo ( ( ) => getVaultSource ( sourceID ) , [ sourceID ] ) ; const [ contents , setContents ] = useState < Array < VaultContentsItem > > ( [ ] ) ; const updateContents = useCallback ( ( ) => { if ( ! source ?. vault ) return ; setContents ( sortItems ( extractItems ( source . vault , targetGroupID ) ) ) ; } , [ source , targetGroupID ] ) ; useEffect ( ( ) => { if ( ! source ) return ; updateContents ( ) ; source . on ( "updated" , updateContents ) ; return ( ) => { source . off ( "updated" , updateContents ) ; } ; } , [ source , updateContents ] ) ; return contents ; } export function useVaultStatistics ( sourceID ) { const sourceUsesBiometrics = useBiometricsEnabledForSource ( sourceID ) ; const [ offlineAvailable , setOfflineAvailable ] = useState < boolean > ( false ) ; const [ numEntries , setNumEntries ] = useState < number > ( 0 ) ; const [ numGroups , setNumGroups ] = useState < number > ( 0 ) ; const statisticsEmitter = useMemo ( getStatisticsEmitter , [ ] ) ; const updateCallback = useCallback ( async ( ) => { const [ hasOffline , itemsCount ] = await Promise . all ( [ sourceHasOfflineCopy ( sourceID ) , getSourceItemsCount ( sourceID ) ] ) ; setOfflineAvailable ( hasOffline ) ; setNumEntries ( itemsCount . entries ) ; setNumGroups ( itemsCount . groups ) ; } , [ sourceID ] ) ; useEffect ( ( ) => { const vaultManager = getVaultManager ( ) ; vaultManager . on ( "sourcesUpdated" , updateCallback ) ; statisticsEmitter . on ( `updated:${ sourceID }` , updateCallback ) ; updateCallback ( ) ; return ( ) => { vaultManager . off ( "sourcesUpdated" , updateCallback ) ; statisticsEmitter . off ( `updated:${ sourceID }` , updateCallback ) ; } ; } , [ sourceID , statisticsEmitter , updateCallback ] ) ; return { authMethod : sourceUsesBiometrics ? "biometrics" : "password" , numEntries , numGroups , offlineAvailable } ; } export function useVaultWalletEntries ( sourceID ) { const entries = useEntries ( sourceID ) ; const walletEntries = useMemo ( ( ) => entries . filter ( entry => [ EntryType . CreditCard ] . includes ( entry . getType ( ) ) ) , [ entries ] ) ; return walletEntries ; } export function useVaults ( vaultsOveride ? ) { const [ vaults , setVaults ] = useState < Array < VaultDetails > > ( [ ] ) ; const updateCallback = useCallback ( ( ) => { if ( Array . isArray ( vaultsOveride ) ) return ; const vaultManager = getVaultManager ( ) ; setVaults ( vaultManager . sources . map ( source => ( { id : source . id , name : source . name , state : source . status , order : source . order , type : source . type } ) ) ) ; } , [ vaultsOveride ] ) ; useEffect ( ( ) => { if ( Array . isArray ( vaultsOveride ) ) { setVaults ( vaultsOveride ) ; return ; } const vaultManager = getVaultManager ( ) ; vaultManager . on ( "sourcesUpdated" , updateCallback ) ; updateCallback ( ) ; return ( ) => { vaultManager . off ( "sourcesUpdated" , updateCallback ) ; } ; } , [ vaultsOveride ] ) ; return vaults ; }