From f08e9f7f2e068b64c0543d7c96d0daf29f42c835 Mon Sep 17 00:00:00 2001 From: Akiff Manji Date: Wed, 10 Aug 2022 17:31:13 -0700 Subject: [PATCH] Combine records with format data and extend hooks Signed-off-by: Akiff Manji --- .../react-hooks/src/CredentialProvider.tsx | 62 +++++++++++++------ packages/react-hooks/src/recordUtils.ts | 55 +++++++++++++++- 2 files changed, 96 insertions(+), 21 deletions(-) diff --git a/packages/react-hooks/src/CredentialProvider.tsx b/packages/react-hooks/src/CredentialProvider.tsx index 1fadef98..698ae19f 100644 --- a/packages/react-hooks/src/CredentialProvider.tsx +++ b/packages/react-hooks/src/CredentialProvider.tsx @@ -1,5 +1,5 @@ -import type { RecordsState } from './recordUtils' -import type { Agent, CredentialState } from '@aries-framework/core' +import type { CombinedRecordsState, RecordsState } from './recordUtils' +import type { Agent, CredentialState, GetFormatDataReturn, IndyCredentialFormat } from '@aries-framework/core' import type { PropsWithChildren } from 'react' import { CredentialExchangeRecord } from '@aries-framework/core' @@ -7,17 +7,17 @@ import { useState, createContext, useContext, useEffect, useMemo } from 'react' import * as React from 'react' import { + addCombinedRecord, + removeCombinedRecord, + updateCombinedRecord, recordsRemovedByType, recordsUpdatedByType, recordsAddedByType, - removeRecord, - updateRecord, - addRecord, } from './recordUtils' -const CredentialContext = createContext | undefined>(undefined) +const CredentialContext = createContext | undefined>(undefined) -export const useCredentials = () => { +export const useCombinedCredentials = () => { const credentialContext = useContext(CredentialContext) if (!credentialContext) { throw new Error('useCredentials must be used within a CredentialContextProvider') @@ -25,6 +25,11 @@ export const useCredentials = () => { return credentialContext } +export const useCredentials = (): RecordsState => { + const { records: combinedRecords, loading } = useCombinedCredentials() + return { records: combinedRecords.map(({ record }) => record), loading } +} + export const useCredentialById = (id: string): CredentialExchangeRecord | undefined => { const { records: credentials } = useCredentials() return credentials.find((c: CredentialExchangeRecord) => c.id === id) @@ -39,20 +44,34 @@ export const useCredentialByState = (state: CredentialState): CredentialExchange return filteredCredentials } +export const useFormatDataForCredentialById = (id: string): GetFormatDataReturn<[IndyCredentialFormat]> | undefined => { + const { records: combinedRecords } = useCombinedCredentials() + return combinedRecords.find(({ record: c }) => c.id === id)?.formatData +} + interface Props { agent: Agent | undefined } const CredentialProvider: React.FC> = ({ agent, children }) => { - const [state, setState] = useState>({ + const [state, setState] = useState>({ records: [], loading: true, }) + const combineRecord = async (record: CredentialExchangeRecord) => { + let formatData: GetFormatDataReturn<[IndyCredentialFormat]> = {} + if (agent) { + formatData = await agent.credentials.getFormatData(record.id) + } + return { record, formatData } + } + const setInitialState = async () => { if (agent) { const records = await agent.credentials.getAll() - setState({ records, loading: false }) + const combinedRecords = await Promise.all(records.map(combineRecord)) + setState({ records: combinedRecords, loading: false }) } } @@ -62,17 +81,20 @@ const CredentialProvider: React.FC> = ({ agent, childre useEffect(() => { if (!state.loading) { - const credentialAdded$ = recordsAddedByType(agent, CredentialExchangeRecord).subscribe((record) => - setState(addRecord(record, state)) - ) - - const credentialUpdated$ = recordsUpdatedByType(agent, CredentialExchangeRecord).subscribe((record) => - setState(updateRecord(record, state)) - ) - - const credentialRemoved$ = recordsRemovedByType(agent, CredentialExchangeRecord).subscribe((record) => - setState(removeRecord(record, state)) - ) + const credentialAdded$ = recordsAddedByType(agent, CredentialExchangeRecord).subscribe(async (record) => { + const combinedRecord = await combineRecord(record) + return setState(addCombinedRecord(combinedRecord, state)) + }) + + const credentialUpdated$ = recordsUpdatedByType(agent, CredentialExchangeRecord).subscribe(async (record) => { + const combinedRecord = await combineRecord(record) + setState(updateCombinedRecord(combinedRecord, state)) + }) + + const credentialRemoved$ = recordsRemovedByType(agent, CredentialExchangeRecord).subscribe(async (record) => { + const combinedRecord = await combineRecord(record) + setState(removeCombinedRecord(combinedRecord, state)) + }) return () => { credentialAdded$?.unsubscribe() diff --git a/packages/react-hooks/src/recordUtils.ts b/packages/react-hooks/src/recordUtils.ts index 1c511112..7c1a5f42 100644 --- a/packages/react-hooks/src/recordUtils.ts +++ b/packages/react-hooks/src/recordUtils.ts @@ -5,6 +5,8 @@ import type { RecordUpdatedEvent, Agent, BaseEvent, + GetFormatDataReturn, + IndyCredentialFormat, } from '@aries-framework/core' import type { Constructor } from '@aries-framework/core/build/utils/mixins' @@ -14,9 +16,20 @@ import { map, filter, pipe } from 'rxjs' // eslint-disable-next-line @typescript-eslint/no-explicit-any type BaseRecordAny = BaseRecord type RecordClass = Constructor & { type: string } + +interface CombinedRecord { + record: R + formatData: GetFormatDataReturn<[IndyCredentialFormat]> +} + export interface RecordsState { loading: boolean - records: R[] + records: Array +} + +export interface CombinedRecordsState { + loading: boolean + records: Array> } export const addRecord = (record: R, state: RecordsState): RecordsState => { @@ -48,6 +61,46 @@ export const removeRecord = (record: R, state: RecordsS } } +export const addCombinedRecord = ( + combinedRecord: CombinedRecord, + state: CombinedRecordsState +): CombinedRecordsState => { + const newRecordsState = [...state.records] + newRecordsState.unshift(combinedRecord) + return { + loading: state.loading, + records: newRecordsState, + } +} + +export const updateCombinedRecord = ( + combinedRecord: CombinedRecord, + state: CombinedRecordsState +): CombinedRecordsState => { + const { record } = combinedRecord + const newRecordsState = [...state.records] + const index = newRecordsState.findIndex(({ record: r }) => r.id === record.id) + if (index > -1) { + newRecordsState[index] = combinedRecord + } + return { + loading: state.loading, + records: newRecordsState, + } +} + +export const removeCombinedRecord = ( + combinedRecord: CombinedRecord, + state: CombinedRecordsState +): CombinedRecordsState => { + const { record } = combinedRecord + const newRecordsState = state.records.filter(({ record: r }) => r.id !== record.id) + return { + loading: state.loading, + records: newRecordsState, + } +} + const filterByType = (recordClass: RecordClass) => { return pipe( map((event: BaseEvent) => (event.payload as Record).record),