From b3cadb8e1c423af15acb6541d3168baf045546c4 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Thu, 19 Dec 2024 18:53:28 +0700 Subject: [PATCH] feat(core): basic record to mark restore as complete (#868) * feat: basic record to mark restore as complete * feat: mark done when finished recovering in the DB * feat: update unittest * update: resolve comment * update: update unit test * feat: resolve comment * feat: resolve comment * feat: resolve comment * feat: call loadDatabase() after syncWithKeria and before startup app * feat: remove unused import --- src/core/agent/agent.test.ts | 21 ++++++++++++++++++- src/core/agent/agent.ts | 20 ++++++++++++++++++ src/core/agent/agent.types.ts | 1 + src/core/agent/event.types.ts | 10 +++++++++ .../agent/services/connectionService.test.ts | 2 ++ src/core/agent/services/connectionService.ts | 11 ---------- .../agent/services/credentialService.test.ts | 4 +++- src/core/agent/services/credentialService.ts | 6 ++++-- .../agent/services/identifierService.test.ts | 3 +-- src/core/agent/services/identifierService.ts | 15 ++++++++----- src/ui/components/AppWrapper/AppWrapper.tsx | 11 +++++++++- 11 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/core/agent/agent.test.ts b/src/core/agent/agent.test.ts index 092176365..86eda673c 100644 --- a/src/core/agent/agent.test.ts +++ b/src/core/agent/agent.test.ts @@ -1,6 +1,6 @@ import { SignifyClient, ready as signifyReady, Tier } from "signify-ts"; import { mnemonicToEntropy } from "bip39"; -import { AgentUrls } from "./agent.types"; +import { AgentUrls, MiscRecordId } from "./agent.types"; import { Agent } from "./agent"; import { KeyStoreKeys, SecureStorage } from "../storage"; import { CoreEventEmitter } from "./event"; @@ -28,15 +28,21 @@ const getKeyStoreSpy = jest .mockResolvedValue(mockGetBranValue); const mockBasicStorageService = { save: jest.fn(), + update: jest.fn(), }; const mockConnectionService = { removeConnectionsPendingDeletion: jest.fn(), resolvePendingConnections: jest.fn(), + syncKeriaContacts: jest.fn(), }; const mockIdentifierService = { resolvePendingIdentifier: jest.fn(), removeIdentifiersPendingDeletion: jest.fn(), + syncKeriaIdentifiers: jest.fn(), +}; +const mockCredentialService = { + syncACDCs: jest.fn(), }; const mockEntropy = "00000000000000000000000000000000"; @@ -57,6 +63,7 @@ describe("test cases of bootAndConnect function", () => { (agent as any).agentServicesProps = mockAgentServicesProps; (agent as any).connectionService = mockConnectionService; (agent as any).identifierService = mockIdentifierService; + (agent as any).credentialService = mockCredentialService; mockAgentUrls = { url: "http://127.0.0.1:3901", @@ -285,12 +292,24 @@ describe("test cases of recoverKeriaAgent function", () => { await agent.recoverKeriaAgent(mockSeedPhrase, mockConnectUrl); + const now = new Date(); expect(SignifyClient).toHaveBeenCalledWith( mockConnectUrl, expectedBran, Tier.low ); + expect(mockConnectionService.syncKeriaContacts).toHaveBeenCalled(); + expect(mockIdentifierService.syncKeriaIdentifiers).toHaveBeenCalled(); + expect(mockCredentialService.syncACDCs).toHaveBeenCalled(); expect(mockSignifyClient.connect).toHaveBeenCalled(); + expect(mockBasicStorageService.update).toHaveBeenCalledWith({ + _tags: {}, + content: { syncing: false }, + createdAt: now, + id: MiscRecordId.CLOUD_RECOVERY_STATUS, + type: "BasicRecord", + updatedAt: undefined, + }); expect(SecureStorage.set).toHaveBeenCalledWith( KeyStoreKeys.SIGNIFY_BRAN, expectedBran diff --git a/src/core/agent/agent.ts b/src/core/agent/agent.ts index 7823ca6eb..b872ec888 100644 --- a/src/core/agent/agent.ts +++ b/src/core/agent/agent.ts @@ -297,12 +297,32 @@ class Agent { this.agentServicesProps.signifyClient = this.signifyClient; await this.connectSignifyClient(); + await this.basicStorage.save({ + id: MiscRecordId.CLOUD_RECOVERY_STATUS, + content: { syncing: true }, + }); + await SecureStorage.set(KeyStoreKeys.SIGNIFY_BRAN, bran); await this.saveAgentUrls({ url: connectUrl, bootUrl: "", }); + + await this.syncWithKeria(); + } + + async syncWithKeria() { this.markAgentStatus(true); + await this.connections.syncKeriaContacts(); + await this.identifiers.syncKeriaIdentifiers(); + await this.credentials.syncACDCs(); + + await this.basicStorage.update( + new BasicRecord({ + id: MiscRecordId.CLOUD_RECOVERY_STATUS, + content: { syncing: false }, + }) + ); } private async connectSignifyClient(): Promise { diff --git a/src/core/agent/agent.types.ts b/src/core/agent/agent.types.ts index 7d0299e6f..09a1dc760 100644 --- a/src/core/agent/agent.types.ts +++ b/src/core/agent/agent.types.ts @@ -39,6 +39,7 @@ enum MiscRecordId { LOGIN_METADATA = "login-metadata", CAMERA_DIRECTION = "camera-direction", FAILED_NOTIFICATIONS = "failed-notifications", + CLOUD_RECOVERY_STATUS = "cloud-recovery-status", IDENTIFIERS_PENDING_CREATION = "identifiers-pending-creation", } diff --git a/src/core/agent/event.types.ts b/src/core/agent/event.types.ts index 9c533203c..ff2aabe12 100644 --- a/src/core/agent/event.types.ts +++ b/src/core/agent/event.types.ts @@ -6,6 +6,7 @@ import { CredentialShortDetails, CredentialStatus, } from "./services/credentialService.types"; +import { IdentifierShortDetails } from "./services/identifier.types"; interface BaseEventEmitter { type: string; @@ -22,6 +23,7 @@ enum EventTypes { KeriaStatusChanged = "KeriaStatusChanged", NotificationRemoved = "NotificationRemoved", IdentifierRemoved = "IdentifierRemoved", + IdentifierStateChanged = "IdentifierStateChanged", IdentifierAdded = "IdentifierAdded", } @@ -94,6 +96,13 @@ interface IdentifierRemovedEvent extends BaseEventEmitter { }; } +interface IdentifierStateChangedEvent extends BaseEventEmitter { + type: typeof EventTypes.IdentifierStateChanged; + payload: { + identifier: IdentifierShortDetails; + }; +} + interface IdentifierAddedEvent extends BaseEventEmitter { type: typeof EventTypes.IdentifierAdded; payload: { @@ -112,6 +121,7 @@ export type { NotificationRemovedEvent, ConnectionRemovedEvent, IdentifierRemovedEvent, + IdentifierStateChangedEvent, IdentifierAddedEvent, }; export { EventTypes }; diff --git a/src/core/agent/services/connectionService.test.ts b/src/core/agent/services/connectionService.test.ts index 9e7dbdc79..b43b9828b 100644 --- a/src/core/agent/services/connectionService.test.ts +++ b/src/core/agent/services/connectionService.test.ts @@ -539,6 +539,8 @@ describe("Connection service of agent", () => { wellKnowns: [], }, ]); + + eventEmitter.emit = jest.fn(); connectionStorage.getAll = jest.fn().mockReturnValue([]); await connectionService.syncKeriaContacts(); expect(connectionStorage.save).toBeCalledTimes(2); diff --git a/src/core/agent/services/connectionService.ts b/src/core/agent/services/connectionService.ts index b8213721e..a79661e0a 100644 --- a/src/core/agent/services/connectionService.ts +++ b/src/core/agent/services/connectionService.ts @@ -168,17 +168,6 @@ class ConnectionService extends AgentService { } await this.createConnectionMetadata(connectionId, connectionMetadata); } else { - this.props.eventEmitter.emit({ - type: EventTypes.ConnectionStateChanged, - payload: { - isMultiSigInvite: false, - connectionId, - status: ConnectionStatus.PENDING, - url, - label: alias, - }, - }); - await this.createConnectionMetadata(connectionId, connectionMetadata); } return { type: KeriConnectionType.NORMAL, connection }; diff --git a/src/core/agent/services/credentialService.test.ts b/src/core/agent/services/credentialService.test.ts index fb942c3df..41b99cc66 100644 --- a/src/core/agent/services/credentialService.test.ts +++ b/src/core/agent/services/credentialService.test.ts @@ -100,9 +100,10 @@ const credentialStorage = jest.mocked({ updateCredentialMetadata: jest.fn(), }); +const eventEmitter = new CoreEventEmitter(); const agentServicesProps = { signifyClient: signifyClient as any, - eventEmitter: new CoreEventEmitter(), + eventEmitter, }; const notificationStorage = jest.mocked({ @@ -422,6 +423,7 @@ describe("Credential service of agent", () => { ...memberIdentifierRecord, id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", }); + eventEmitter.emit = jest.fn(); await credentialService.syncACDCs(); expect(credentialStorage.saveCredentialMetadataRecord).toBeCalledTimes(2); diff --git a/src/core/agent/services/credentialService.ts b/src/core/agent/services/credentialService.ts index 1a7df5e2b..85d1222ce 100644 --- a/src/core/agent/services/credentialService.ts +++ b/src/core/agent/services/credentialService.ts @@ -180,7 +180,7 @@ class CredentialService extends AgentService { const identifier = await this.identifierStorage.getIdentifierMetadata( credential.sad.a.i ); - await this.createMetadata({ + const metadata = { id: credential.sad.d, isArchived: false, issuanceDate: new Date(credential.sad.a.dt).toISOString(), @@ -193,7 +193,9 @@ class CredentialService extends AgentService { ? IdentifierType.Group : IdentifierType.Individual, createdAt: new Date(credential.sad.a.dt), - }); + }; + + await this.createMetadata(metadata); } catch (error) { /* eslint-disable no-console */ console.error(error); diff --git a/src/core/agent/services/identifierService.test.ts b/src/core/agent/services/identifierService.test.ts index 2e1f40dca..912777acb 100644 --- a/src/core/agent/services/identifierService.test.ts +++ b/src/core/agent/services/identifierService.test.ts @@ -875,7 +875,6 @@ describe("Single sig service of agent", () => { jest .spyOn(signifyClient.operations(), "get") .mockResolvedValue(mockOperation); - getIdentifiersMock.mockResolvedValue({ icp_dt: "2024-12-10T07:28:18.217384+00:00", }); @@ -903,7 +902,7 @@ describe("Single sig service of agent", () => { identifierStorage.createIdentifierMetadataRecord ).toHaveBeenCalledWith({ id: "EJ9oenRW3_SNc0JkETnOegspNGaDCypBfTU1kJiL2AMs", - displayName: "EJ9oenRW3_SNc0JkETnOegspNGaDCypBfTU1kJiL2AMs", + displayName: "test2", theme: 33, isPending: false, createdAt: new Date("2024-12-10T07:28:18.217384+00:00"), diff --git a/src/core/agent/services/identifierService.ts b/src/core/agent/services/identifierService.ts index 491e5287f..2b7724cf0 100644 --- a/src/core/agent/services/identifierService.ts +++ b/src/core/agent/services/identifierService.ts @@ -25,6 +25,7 @@ import { EventTypes, IdentifierAddedEvent, IdentifierRemovedEvent, + IdentifierStateChangedEvent, OperationAddedEvent, } from "../event.types"; @@ -81,6 +82,12 @@ class IdentifierService extends AgentService { ); } + onIdentifierStateChanged( + callback: (event: IdentifierStateChangedEvent) => void + ) { + this.props.eventEmitter.on(EventTypes.IdentifierStateChanged, callback); + } + onIdentifierAdded(callback: (event: IdentifierAddedEvent) => void) { this.props.eventEmitter.on(EventTypes.IdentifierAdded, callback); } @@ -521,15 +528,14 @@ class IdentifierService extends AgentService { const isMultiSig = name.length === 3; const identifierDetail = (await this.props.signifyClient .identifiers() - .get(identifier)) as HabState & { icp_dt: string }; - + .get(identifier.prefix)) as HabState & { icp_dt: string }; if (isMultiSig) { const groupId = identifier.name.split(":")[1]; const groupInitiator = groupId.split("-")[0] === "1"; await this.identifierStorage.createIdentifierMetadataRecord({ id: identifier.prefix, - displayName: groupId, + displayName: identifier.name.split(":")[1], theme, groupMetadata: { groupId, @@ -539,13 +545,12 @@ class IdentifierService extends AgentService { isPending, createdAt: new Date(identifierDetail.icp_dt), }); - continue; } await this.identifierStorage.createIdentifierMetadataRecord({ id: identifier.prefix, - displayName: identifier.prefix, + displayName: identifier.name.split(":")[1], theme, isPending, createdAt: new Date(identifierDetail.icp_dt), diff --git a/src/ui/components/AppWrapper/AppWrapper.tsx b/src/ui/components/AppWrapper/AppWrapper.tsx index cd02d58e9..4323cc87d 100644 --- a/src/ui/components/AppWrapper/AppWrapper.tsx +++ b/src/ui/components/AppWrapper/AppWrapper.tsx @@ -77,6 +77,7 @@ import { import { AcdcStateChangedEvent, ConnectionStateChangedEvent, + IdentifierStateChangedEvent, } from "../../../core/agent/event.types"; import { IdentifiersFilters } from "../../pages/Identifiers/Identifiers.types"; import { CredentialsFilters } from "../../pages/Credentials/Credentials.types"; @@ -543,7 +544,6 @@ const AppWrapper = (props: { children: ReactNode }) => { await Agent.agent.devPreload(); } - await loadDatabase(); const { keriaConnectUrlRecord } = await loadCacheBasicStorage(); // Ensure online/offline callback setup before connecting to KERIA @@ -551,6 +551,15 @@ const AppWrapper = (props: { children: ReactNode }) => { if (keriaConnectUrlRecord) { try { + if (keriaConnectUrlRecord?.content?.url) { + const recoveryStatus = await Agent.agent.basicStorage.findById( + MiscRecordId.CLOUD_RECOVERY_STATUS + ); + if (recoveryStatus?.content?.syncing) { + await Agent.agent.syncWithKeria(); + } + } + await loadDatabase(); await Agent.agent.start(keriaConnectUrlRecord.content.url as string); } catch (e) { const errorMessage = (e as Error).message;