From c8a68d4082818bc1fdb812ea8c6da3f616629591 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Fri, 13 Dec 2024 15:36:03 +0700 Subject: [PATCH 1/8] feat: basic record to mark restore as complete --- src/core/agent/agent.ts | 18 ++++ src/core/agent/agent.types.ts | 1 + src/core/agent/event.types.ts | 10 ++ .../agent/services/connectionService.test.ts | 3 + src/core/agent/services/connectionService.ts | 10 ++ .../agent/services/credentialService.test.ts | 5 +- src/core/agent/services/credentialService.ts | 12 ++- .../agent/services/identifierService.test.ts | 102 ++++++++++++++---- src/core/agent/services/identifierService.ts | 77 +++++++++---- src/ui/components/AppWrapper/AppWrapper.tsx | 15 +++ 10 files changed, 212 insertions(+), 41 deletions(-) diff --git a/src/core/agent/agent.ts b/src/core/agent/agent.ts index efee713ce..9f578f700 100644 --- a/src/core/agent/agent.ts +++ b/src/core/agent/agent.ts @@ -296,12 +296,30 @@ class Agent { this.agentServicesProps.signifyClient = this.signifyClient; await this.connectSignifyClient(); + await this.basicStorage.save({ + id: MiscRecordId.PROCESS_RECOVERING, + content: { value: true }, + }); + await SecureStorage.set(KeyStoreKeys.SIGNIFY_BRAN, bran); await this.saveAgentUrls({ url: connectUrl, bootUrl: "", }); this.markAgentStatus(true); + + await this.syncWithKeria(); + } + + private async syncWithKeria() { + await this.connections.syncKeriaContacts(); + await this.identifiers.syncKeriaIdentifiers(); + await this.credentials.syncACDCs(); + + // await this.basicStorage.save({ + // id: MiscRecordId.PROCESS_RECOVERING, + // content: { value: true }, + // }); } private async connectSignifyClient(): Promise { diff --git a/src/core/agent/agent.types.ts b/src/core/agent/agent.types.ts index ae871bdfb..f0c100e76 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", + PROCESS_RECOVERING = "process-recovering", } interface ConnectionShortDetails { diff --git a/src/core/agent/event.types.ts b/src/core/agent/event.types.ts index ea87d113c..7859f5719 100644 --- a/src/core/agent/event.types.ts +++ b/src/core/agent/event.types.ts @@ -5,6 +5,7 @@ import { CredentialShortDetails, CredentialStatus, } from "./services/credentialService.types"; +import { IdentifierShortDetails } from "./services/identifier.types"; interface BaseEventEmitter { type: string; @@ -21,6 +22,7 @@ enum EventTypes { KeriaStatusChanged = "KeriaStatusChanged", NotificationRemoved = "NotificationRemoved", IdentifierRemoved = "IdentifierRemoved", + IdentifierStateChanged = "IdentifierStateChanged", } interface NotificationAddedEvent extends BaseEventEmitter { @@ -92,6 +94,13 @@ interface IdentifierRemovedEvent extends BaseEventEmitter { }; } +interface IdentifierStateChangedEvent extends BaseEventEmitter { + type: typeof EventTypes.IdentifierStateChanged; + payload: { + identifier: IdentifierShortDetails; + }; +} + export type { NotificationAddedEvent, OperationCompleteEvent, @@ -103,5 +112,6 @@ export type { NotificationRemovedEvent, ConnectionRemovedEvent, IdentifierRemovedEvent, + IdentifierStateChangedEvent, }; export { EventTypes }; diff --git a/src/core/agent/services/connectionService.test.ts b/src/core/agent/services/connectionService.test.ts index 9e7dbdc79..a80b0e804 100644 --- a/src/core/agent/services/connectionService.test.ts +++ b/src/core/agent/services/connectionService.test.ts @@ -539,9 +539,12 @@ describe("Connection service of agent", () => { wellKnowns: [], }, ]); + + eventEmitter.emit = jest.fn(); connectionStorage.getAll = jest.fn().mockReturnValue([]); await connectionService.syncKeriaContacts(); expect(connectionStorage.save).toBeCalledTimes(2); + expect(eventEmitter.emit).toBeCalledTimes(2); }); test("Can get multisig linked contacts", async () => { diff --git a/src/core/agent/services/connectionService.ts b/src/core/agent/services/connectionService.ts index b8213721e..497fb8891 100644 --- a/src/core/agent/services/connectionService.ts +++ b/src/core/agent/services/connectionService.ts @@ -444,6 +444,16 @@ class ConnectionService extends AgentService { groupId: contact.groupCreationId, createdAtUTC: contact.createdAt, }); + + this.props.eventEmitter.emit({ + type: EventTypes.ConnectionStateChanged, + payload: { + connectionId: contact.id, + status: ConnectionStatus.CONFIRMED, + url: contact.oobi, + label: contact.alias, + }, + }); } } } diff --git a/src/core/agent/services/credentialService.test.ts b/src/core/agent/services/credentialService.test.ts index f88786ecc..57e58df32 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,8 +423,10 @@ describe("Credential service of agent", () => { ...memberIdentifierRecord, id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", }); + eventEmitter.emit = jest.fn(); await credentialService.syncACDCs(); expect(credentialStorage.saveCredentialMetadataRecord).toBeCalledTimes(2); + expect(eventEmitter.emit).toBeCalledTimes(2); }); test("Must throw 'Credential with given SAID not found on KERIA' when there's no KERI credential", async () => { diff --git a/src/core/agent/services/credentialService.ts b/src/core/agent/services/credentialService.ts index 939b299ca..a9e9e761c 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(), @@ -192,6 +192,16 @@ class CredentialService extends AgentService { identifierType: identifier.multisigManageAid ? IdentifierType.Group : IdentifierType.Individual, + }; + + await this.createMetadata(metadata); + + this.props.eventEmitter.emit({ + type: EventTypes.AcdcStateChanged, + payload: { + status: CredentialStatus.CONFIRMED, + credential: metadata, + }, }); } catch (error) { /* eslint-disable no-console */ diff --git a/src/core/agent/services/identifierService.test.ts b/src/core/agent/services/identifierService.test.ts index 11961119b..ae09c83eb 100644 --- a/src/core/agent/services/identifierService.test.ts +++ b/src/core/agent/services/identifierService.test.ts @@ -229,19 +229,21 @@ const WITNESSES = [ "http://witnesess:5644/oobi/BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX/controller?role=witness", "http://witnesess:5645/oobi/BM35JN8XeJSEfpxopjn5jr7tAHCE5749f0OobhMLCorE/controller?role=witness", "http://witnesess:5646/oobi/BIj15u5V11bkbtAxMA7gcNJZcax-7TgaBMLsQnMHpYHP/controller?role=witness", - "http://witnesess:5647/oobi/BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM/controller?role=witness" + "http://witnesess:5647/oobi/BF2rZTW79z4IXocYRQnjjsOuvFUQv-ptCf8Yltd7PfsM/controller?role=witness", ]; -const witnessEids = WITNESSES.map((oobi: string) => oobi.split("/oobi/")[1].split("/")[0]); +const witnessEids = WITNESSES.map( + (oobi: string) => oobi.split("/oobi/")[1].split("/")[0] +); describe("Single sig service of agent", () => { beforeEach(() => { jest.resetAllMocks(); getAgentConfigMock.mockResolvedValue({ - iurls: WITNESSES + iurls: WITNESSES, }); }); - + test("can get all identifiers", async () => { identifierStorage.getAllIdentifierMetadata = jest .fn() @@ -369,7 +371,7 @@ describe("Single sig service of agent", () => { }); expect(createIdentifierMock).toBeCalledWith(`0:${displayName}`, { toad: WITNESSES.length, - wits: witnessEids + wits: witnessEids, }); expect(identifierStorage.createIdentifierMetadataRecord).toBeCalledTimes(1); }); @@ -414,7 +416,7 @@ describe("Single sig service of agent", () => { }); expect(createIdentifierMock).toBeCalledWith(`0:${displayName}`, { toad: WITNESSES.length, - wits: witnessEids + wits: witnessEids, }); expect(identifierStorage.createIdentifierMetadataRecord).toBeCalledTimes(1); expect(eventEmitter.emit).toHaveBeenCalledWith({ @@ -464,7 +466,9 @@ describe("Single sig service of agent", () => { test("cannot create identifier is there are no discoverable witnesses", async () => { Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValueOnce(true); getAgentConfigMock.mockResolvedValueOnce({ - iurls: ["http://witnesess:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller"] + iurls: [ + "http://witnesess:5642/oobi/BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha/controller", + ], }); await expect( @@ -564,7 +568,7 @@ describe("Single sig service of agent", () => { } ); expect(updateIdentifierMock).toBeCalledWith(identifierMetadataRecord.id, { - name: `XX-0ADQpus-mQmmO4mgWcT3ekDz:${identifierMetadataRecord.displayName}` + name: `XX-0ADQpus-mQmmO4mgWcT3ekDz:${identifierMetadataRecord.displayName}`, }); }); @@ -615,7 +619,7 @@ describe("Single sig service of agent", () => { } ); expect(updateIdentifierMock).toBeCalledWith(identifierMetadataRecord.id, { - name: `XX-0ADQpus-mQmmO4mgWcT3ekDz:${identifierMetadataRecord.displayName}` + name: `XX-0ADQpus-mQmmO4mgWcT3ekDz:${identifierMetadataRecord.displayName}`, }); expect(PeerConnection.peerConnection.disconnectDApp).toBeCalledWith( "dApp-address", @@ -626,7 +630,7 @@ describe("Single sig service of agent", () => { test("Should correctly sync KERI identifiers, handling both group and non-group cases", async () => { // Mock the online status of the agent Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValue(true); - + // Mock the list of identifiers returned by signifyClient listIdentifiersMock.mockReturnValue({ aids: [ @@ -650,12 +654,14 @@ describe("Single sig service of agent", () => { }, ], }); - + // Mock the identifier storage - identifierStorage.getKeriIdentifiersMetadata = jest.fn().mockReturnValue([]); + identifierStorage.getKeriIdentifiersMetadata = jest + .fn() + .mockReturnValue([]); identifierStorage.createIdentifierMetadataRecord = jest.fn(); identifierStorage.updateIdentifierMetadata = jest.fn(); - + // Mock the signifyClient operations call const mockOperation = { done: true, @@ -664,12 +670,14 @@ describe("Single sig service of agent", () => { jest .spyOn(signifyClient.operations(), "get") .mockResolvedValue(mockOperation); - + eventEmitter.emit = jest.fn(); // Call the function to test await identifierService.syncKeriaIdentifiers(); - + // sync data of non-group record - expect(identifierStorage.createIdentifierMetadataRecord).toHaveBeenCalledWith({ + expect( + identifierStorage.createIdentifierMetadataRecord + ).toHaveBeenCalledWith({ id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", displayName: "1-group1", theme: 0, @@ -680,14 +688,47 @@ describe("Single sig service of agent", () => { }, isPending: false, }); - - expect(identifierStorage.createIdentifierMetadataRecord).toHaveBeenCalledWith({ + + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: EventTypes.IdentifierStateChanged, + payload: { + identifier: { + id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", + displayName: "1-group1", + groupMetadata: { + groupId: "1-group1", + groupCreated: false, + groupInitiator: true, + }, + theme: 0, + isPending: false, + createdAtUTC: expect.any(String), + }, + }, + }); + + expect( + identifierStorage.createIdentifierMetadataRecord + ).toHaveBeenCalledWith({ id: "EJ9oenRW3_SNc0JkETnOegspNGaDCypBfTU1kJiL2AMs", - displayName: "EJ9oenRW3_SNc0JkETnOegspNGaDCypBfTU1kJiL2AMs", + displayName: "test2", theme: 33, isPending: false, }); + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: EventTypes.IdentifierStateChanged, + payload: { + identifier: { + id: "EJ9oenRW3_SNc0JkETnOegspNGaDCypBfTU1kJiL2AMs", + displayName: "test2", + theme: 33, + isPending: false, + createdAtUTC: expect.any(String), + }, + }, + }); + // sync data of group record expect(identifierStorage.updateIdentifierMetadata).toHaveBeenCalledWith( "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", @@ -700,15 +741,34 @@ describe("Single sig service of agent", () => { } ); - expect(identifierStorage.createIdentifierMetadataRecord).toHaveBeenCalledWith({ + expect( + identifierStorage.createIdentifierMetadataRecord + ).toHaveBeenCalledWith({ id: "EPMFON5GHY3o4mLr7XsHvXBCED4gkr1ILUX9NSRkOPM", displayName: "1-group1", theme: 15, multisigManageAid: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", isPending: false, }); + + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: EventTypes.IdentifierStateChanged, + payload: { + identifier: { + id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", + displayName: "1-group1", + theme: 0, + groupMetadata: { + groupId: "1-group1", + groupCreated: false, + groupInitiator: true, + }, + isPending: false, + createdAtUTC: expect.any(String), + }, + }, + }); }); - test("should call signify.rotateIdentifier with correct params", async () => { Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValueOnce(true); diff --git a/src/core/agent/services/identifierService.ts b/src/core/agent/services/identifierService.ts index 14eb993dd..e24058ddc 100644 --- a/src/core/agent/services/identifierService.ts +++ b/src/core/agent/services/identifierService.ts @@ -20,6 +20,7 @@ import { ConnectionService } from "./connectionService"; import { EventTypes, IdentifierRemovedEvent, + IdentifierStateChangedEvent, OperationAddedEvent, } from "../event.types"; @@ -41,8 +42,10 @@ class IdentifierService extends AgentService { "Identifier name has already been used on KERIA"; static readonly IDENTIFIER_IS_PENDING = "Cannot fetch identifier details as the identifier is still pending"; - static readonly NO_WITNESSES_AVAILABLE = "No discoverable witnesses available on connected KERIA instance"; - static readonly MISCONFIGURED_AGENT_CONFIGURATION = "Misconfigured KERIA agent for this wallet type"; + static readonly NO_WITNESSES_AVAILABLE = + "No discoverable witnesses available on connected KERIA instance"; + static readonly MISCONFIGURED_AGENT_CONFIGURATION = + "Misconfigured KERIA agent for this wallet type"; protected readonly identifierStorage: IdentifierStorage; protected readonly operationPendingStorage: OperationPendingStorage; @@ -69,6 +72,12 @@ class IdentifierService extends AgentService { ); } + onIdentifierStateChanged( + callback: (event: IdentifierStateChangedEvent) => void + ) { + this.props.eventEmitter.on(EventTypes.IdentifierStateChanged, callback); + } + async getIdentifiers(): Promise { const identifiers: IdentifierShortDetails[] = []; const listMetadata: IdentifierMetadataRecord[] = @@ -345,7 +354,7 @@ class IdentifierService extends AgentService { (identifier: IdentifierResult) => !storageIdentifiers.find((item) => identifier.prefix === item.id) ); - + const [unSyncedDataWithGroup, unSyncedDataWithoutGroup] = [ unSyncedData.filter((item: HabState) => item.group !== undefined), unSyncedData.filter((item: HabState) => item.group === undefined), @@ -355,7 +364,7 @@ class IdentifierService extends AgentService { if (identifier.name.startsWith("XX")) { continue; } - + const op: Operation = await this.props.signifyClient .operations() .get(`witness.${identifier.prefix}`) @@ -370,7 +379,7 @@ class IdentifierService extends AgentService { }); const isPending = !op.done; - if(isPending){ + if (isPending) { const pendingOperation = await this.operationPendingStorage.save({ id: op.name, recordType: OperationPendingRecordType.Witness, @@ -385,7 +394,7 @@ class IdentifierService extends AgentService { const theme = parseInt(name[0], 10); const isMultiSig = name.length === 3; - if(isMultiSig){ + if (isMultiSig) { const groupId = identifier.name.split(":")[1]; const groupInitiator = groupId.split("-")[0] === "1"; @@ -396,20 +405,50 @@ class IdentifierService extends AgentService { groupMetadata: { groupId, groupCreated: false, - groupInitiator + groupInitiator, }, - isPending + isPending, }); + this.props.eventEmitter.emit({ + type: EventTypes.IdentifierStateChanged, + payload: { + identifier: { + id: identifier.prefix, + displayName: groupId, + theme, + groupMetadata: { + groupId, + groupCreated: false, + groupInitiator, + }, + isPending, + createdAtUTC: new Date().toISOString(), + }, + }, + }); continue; } await this.identifierStorage.createIdentifierMetadataRecord({ id: identifier.prefix, - displayName: identifier.prefix, + displayName: identifier.name.split(":")[1], theme, isPending, }); + + this.props.eventEmitter.emit({ + type: EventTypes.IdentifierStateChanged, + payload: { + identifier: { + id: identifier.prefix, + displayName: identifier.name.split(":")[1], + theme, + isPending, + createdAtUTC: new Date().toISOString(), + }, + }, + }); } for (const identifier of unSyncedDataWithGroup) { @@ -421,10 +460,12 @@ class IdentifierService extends AgentService { const groupId = identifier.group.mhab.name.split(":")[1]; const theme = parseInt(identifier.name.split(":")[0], 10); const groupInitiator = groupId.split("-")[0] === "1"; - const op = await this.props.signifyClient.operations().get(`group.${identifier.prefix}`) + const op = await this.props.signifyClient + .operations() + .get(`group.${identifier.prefix}`); const isPending = !op.done; - if(isPending){ + if (isPending) { const pendingOperation = await this.operationPendingStorage.save({ id: op.name, recordType: OperationPendingRecordType.Group, @@ -439,17 +480,17 @@ class IdentifierService extends AgentService { groupMetadata: { groupId, groupCreated: true, - groupInitiator - } + groupInitiator, + }, }); - + await this.identifierStorage.createIdentifierMetadataRecord({ id: identifier.prefix, displayName: groupId, theme, multisigManageAid, - isPending - }) + isPending, + }); } } @@ -472,12 +513,12 @@ class IdentifierService extends AgentService { if (!config.iurls) { throw new Error(IdentifierService.MISCONFIGURED_AGENT_CONFIGURATION); } - + const witnesses = []; for (const oobi of config.iurls) { const role = new URL(oobi).searchParams.get("role"); if (role === "witness") { - witnesses.push(oobi.split("/oobi/")[1].split("/")[0]); // EID - endpoint identifier + witnesses.push(oobi.split("/oobi/")[1].split("/")[0]); // EID - endpoint identifier } } diff --git a/src/ui/components/AppWrapper/AppWrapper.tsx b/src/ui/components/AppWrapper/AppWrapper.tsx index e9c33b9f7..fb89a8727 100644 --- a/src/ui/components/AppWrapper/AppWrapper.tsx +++ b/src/ui/components/AppWrapper/AppWrapper.tsx @@ -34,6 +34,7 @@ import { setFavouritesIdentifiersCache, setIdentifiersCache, setIdentifiersFilters, + updateOrAddIdentifiersCache, } from "../../../store/reducers/identifiersCache"; import { FavouriteIdentifier } from "../../../store/reducers/identifiersCache/identifiersCache.types"; import { @@ -75,6 +76,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"; @@ -121,6 +123,15 @@ const acdcChangeHandler = async ( } }; +const identifierChangeHandler = async ( + event: IdentifierStateChangedEvent, + dispatch: ReturnType +) => { + const identifierRecord = event.payload.identifier; + + dispatch(updateOrAddIdentifiersCache(identifierRecord)); +}; + const peerConnectRequestSignChangeHandler = async ( event: PeerConnectSigningEvent, dispatch: ReturnType @@ -489,6 +500,10 @@ const AppWrapper = (props: { children: ReactNode }) => { Agent.agent.keriaNotifications.onRemoveNotification((event) => { notificatiStateChanged(event, dispatch); }); + + Agent.agent.identifiers.onIdentifierStateChanged((event) => { + identifierChangeHandler(event, dispatch); + }); }; const initApp = async () => { From b7d433763909df2e835db0c5f3b88c569240c607 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Fri, 13 Dec 2024 15:44:00 +0700 Subject: [PATCH 2/8] feat: mark done when finished recovering in the DB --- src/core/agent/agent.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/agent/agent.ts b/src/core/agent/agent.ts index 9f578f700..49519f1d2 100644 --- a/src/core/agent/agent.ts +++ b/src/core/agent/agent.ts @@ -307,7 +307,7 @@ class Agent { bootUrl: "", }); this.markAgentStatus(true); - + await this.syncWithKeria(); } @@ -316,10 +316,12 @@ class Agent { await this.identifiers.syncKeriaIdentifiers(); await this.credentials.syncACDCs(); - // await this.basicStorage.save({ - // id: MiscRecordId.PROCESS_RECOVERING, - // content: { value: true }, - // }); + await this.basicStorage.update( + new BasicRecord({ + id: MiscRecordId.PROCESS_RECOVERING, + content: { value: false }, + }) + ); } private async connectSignifyClient(): Promise { From 9bca87cd06a4768f93582d4a7025fa64b9b62ed3 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Fri, 13 Dec 2024 16:49:35 +0700 Subject: [PATCH 3/8] feat: update unittest --- src/core/agent/agent.test.ts | 21 ++++++++++++++++++- src/core/agent/agent.ts | 2 +- src/ui/App.test.tsx | 1 + .../components/AppWrapper/AppWrapper.test.tsx | 4 ++-- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/core/agent/agent.test.ts b/src/core/agent/agent.test.ts index f0a58b46f..548ce76a8 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,14 +28,20 @@ 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 = { removeIdentifiersPendingDeletion: jest.fn(), + syncKeriaIdentifiers: jest.fn(), +}; +const mockCredentialService = { + syncACDCs: jest.fn(), }; const mockEntropy = "00000000000000000000000000000000"; @@ -56,6 +62,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", @@ -277,12 +284,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: { value: false }, + createdAt: now, + id: MiscRecordId.PROCESS_RECOVERING, + 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 49519f1d2..7e209bb52 100644 --- a/src/core/agent/agent.ts +++ b/src/core/agent/agent.ts @@ -311,7 +311,7 @@ class Agent { await this.syncWithKeria(); } - private async syncWithKeria() { + async syncWithKeria() { await this.connections.syncKeriaContacts(); await this.identifiers.syncKeriaIdentifiers(); await this.credentials.syncACDCs(); diff --git a/src/ui/App.test.tsx b/src/ui/App.test.tsx index ddb106bac..05faf0500 100644 --- a/src/ui/App.test.tsx +++ b/src/ui/App.test.tsx @@ -26,6 +26,7 @@ jest.mock("../core/agent/agent", () => ({ identifiers: { getIdentifiers: jest.fn().mockResolvedValue([]), syncKeriaIdentifiers: jest.fn(), + onIdentifierStateChanged: jest.fn(), }, connections: { getConnections: jest.fn().mockResolvedValue([]), diff --git a/src/ui/components/AppWrapper/AppWrapper.test.tsx b/src/ui/components/AppWrapper/AppWrapper.test.tsx index ed7c526ab..8d9d4bc16 100644 --- a/src/ui/components/AppWrapper/AppWrapper.test.tsx +++ b/src/ui/components/AppWrapper/AppWrapper.test.tsx @@ -26,7 +26,6 @@ import { store } from "../../../store"; import { updateOrAddConnectionCache } from "../../../store/reducers/connectionsCache"; import { updateOrAddCredsCache } from "../../../store/reducers/credsCache"; import { updateIsPending } from "../../../store/reducers/identifiersCache"; -import { setNotificationsCache } from "../../../store/reducers/notificationsCache"; import { setQueueIncomingRequest, setToastMsg, @@ -65,6 +64,7 @@ jest.mock("../../../core/agent/agent", () => ({ identifiers: { getIdentifiers: jest.fn().mockResolvedValue([]), syncKeriaIdentifiers: jest.fn(), + onIdentifierStateChanged: jest.fn(), }, multiSigs: { getMultisigIcpDetails: jest.fn().mockResolvedValue({}), @@ -105,7 +105,7 @@ jest.mock("../../../core/agent/agent", () => ({ onNewNotification: jest.fn(), onLongOperationComplete: jest.fn(), onRemoveNotification: jest.fn(), - stopNotification: jest.fn() + stopNotification: jest.fn(), }, getKeriaOnlineStatus: jest.fn(), onKeriaStatusStateChanged: jest.fn(), From b88048a468542be74c6163922a95cf58a2c33738 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Mon, 16 Dec 2024 15:18:38 +0700 Subject: [PATCH 4/8] update: resolve comment --- src/core/agent/agent.test.ts | 2 +- src/core/agent/agent.ts | 14 ++--- src/core/agent/agent.types.ts | 2 +- .../agent/services/connectionService.test.ts | 1 - src/core/agent/services/connectionService.ts | 21 ------- src/core/agent/services/credentialService.ts | 8 --- .../agent/services/identifierService.test.ts | 49 --------------- src/core/agent/services/identifierService.ts | 63 ++++++------------- src/ui/App.test.tsx | 3 +- .../components/AppWrapper/AppWrapper.test.tsx | 1 - src/ui/components/AppWrapper/AppWrapper.tsx | 22 +++---- 11 files changed, 39 insertions(+), 147 deletions(-) diff --git a/src/core/agent/agent.test.ts b/src/core/agent/agent.test.ts index 548ce76a8..02f1ef643 100644 --- a/src/core/agent/agent.test.ts +++ b/src/core/agent/agent.test.ts @@ -298,7 +298,7 @@ describe("test cases of recoverKeriaAgent function", () => { _tags: {}, content: { value: false }, createdAt: now, - id: MiscRecordId.PROCESS_RECOVERING, + id: MiscRecordId.CLOUD_RECOVERY_STATUS, type: "BasicRecord", updatedAt: undefined, }); diff --git a/src/core/agent/agent.ts b/src/core/agent/agent.ts index 7e209bb52..64e3d6e16 100644 --- a/src/core/agent/agent.ts +++ b/src/core/agent/agent.ts @@ -296,11 +296,6 @@ class Agent { this.agentServicesProps.signifyClient = this.signifyClient; await this.connectSignifyClient(); - await this.basicStorage.save({ - id: MiscRecordId.PROCESS_RECOVERING, - content: { value: true }, - }); - await SecureStorage.set(KeyStoreKeys.SIGNIFY_BRAN, bran); await this.saveAgentUrls({ url: connectUrl, @@ -312,14 +307,19 @@ class Agent { } async syncWithKeria() { + await this.basicStorage.save({ + id: MiscRecordId.CLOUD_RECOVERY_STATUS, + content: { syncing: true }, + }); + await this.connections.syncKeriaContacts(); await this.identifiers.syncKeriaIdentifiers(); await this.credentials.syncACDCs(); await this.basicStorage.update( new BasicRecord({ - id: MiscRecordId.PROCESS_RECOVERING, - content: { value: false }, + id: MiscRecordId.CLOUD_RECOVERY_STATUS, + content: { syncing: false }, }) ); } diff --git a/src/core/agent/agent.types.ts b/src/core/agent/agent.types.ts index daef46827..1c8ef351b 100644 --- a/src/core/agent/agent.types.ts +++ b/src/core/agent/agent.types.ts @@ -39,7 +39,7 @@ enum MiscRecordId { LOGIN_METADATA = "login-metadata", CAMERA_DIRECTION = "camera-direction", FAILED_NOTIFICATIONS = "failed-notifications", - PROCESS_RECOVERING = "process-recovering", + CLOUD_RECOVERY_STATUS = "cloud-recovery-status", } interface ConnectionShortDetails { diff --git a/src/core/agent/services/connectionService.test.ts b/src/core/agent/services/connectionService.test.ts index a80b0e804..b43b9828b 100644 --- a/src/core/agent/services/connectionService.test.ts +++ b/src/core/agent/services/connectionService.test.ts @@ -544,7 +544,6 @@ describe("Connection service of agent", () => { connectionStorage.getAll = jest.fn().mockReturnValue([]); await connectionService.syncKeriaContacts(); expect(connectionStorage.save).toBeCalledTimes(2); - expect(eventEmitter.emit).toBeCalledTimes(2); }); test("Can get multisig linked contacts", async () => { diff --git a/src/core/agent/services/connectionService.ts b/src/core/agent/services/connectionService.ts index 497fb8891..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 }; @@ -444,16 +433,6 @@ class ConnectionService extends AgentService { groupId: contact.groupCreationId, createdAtUTC: contact.createdAt, }); - - this.props.eventEmitter.emit({ - type: EventTypes.ConnectionStateChanged, - payload: { - connectionId: contact.id, - status: ConnectionStatus.CONFIRMED, - url: contact.oobi, - label: contact.alias, - }, - }); } } } diff --git a/src/core/agent/services/credentialService.ts b/src/core/agent/services/credentialService.ts index 1785fbde7..85d1222ce 100644 --- a/src/core/agent/services/credentialService.ts +++ b/src/core/agent/services/credentialService.ts @@ -196,14 +196,6 @@ class CredentialService extends AgentService { }; await this.createMetadata(metadata); - - this.props.eventEmitter.emit({ - type: EventTypes.AcdcStateChanged, - payload: { - status: CredentialStatus.CONFIRMED, - credential: 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 69ab4404d..d74d87b95 100644 --- a/src/core/agent/services/identifierService.test.ts +++ b/src/core/agent/services/identifierService.test.ts @@ -708,24 +708,6 @@ describe("Single sig service of agent", () => { createdAt: new Date("2024-12-10T07:28:18.217384+00:00") }); - expect(eventEmitter.emit).toHaveBeenCalledWith({ - type: EventTypes.IdentifierStateChanged, - payload: { - identifier: { - id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", - displayName: "1-group1", - groupMetadata: { - groupId: "1-group1", - groupCreated: false, - groupInitiator: true, - }, - theme: 0, - isPending: false, - createdAtUTC: expect.any(String), - }, - }, - }); - expect( identifierStorage.createIdentifierMetadataRecord ).toHaveBeenCalledWith({ @@ -736,19 +718,6 @@ describe("Single sig service of agent", () => { createdAt: new Date("2024-12-10T07:28:18.217384+00:00") }); - expect(eventEmitter.emit).toHaveBeenCalledWith({ - type: EventTypes.IdentifierStateChanged, - payload: { - identifier: { - id: "EJ9oenRW3_SNc0JkETnOegspNGaDCypBfTU1kJiL2AMs", - displayName: "test2", - theme: 33, - isPending: false, - createdAtUTC: expect.any(String), - }, - }, - }); - // sync data of group record expect(identifierStorage.updateIdentifierMetadata).toHaveBeenCalledWith( "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", @@ -771,24 +740,6 @@ describe("Single sig service of agent", () => { isPending: false, createdAt: new Date("2024-12-10T07:28:18.217384+00:00") }); - - expect(eventEmitter.emit).toHaveBeenCalledWith({ - type: EventTypes.IdentifierStateChanged, - payload: { - identifier: { - id: "EL-EboMhx-DaBLiAS_Vm3qtJOubb2rkcS3zLU_r7UXtl", - displayName: "1-group1", - theme: 0, - groupMetadata: { - groupId: "1-group1", - groupCreated: false, - groupInitiator: true, - }, - isPending: false, - createdAtUTC: expect.any(String), - }, - }, - }); }); test("should call signify.rotateIdentifier with correct params", async () => { diff --git a/src/core/agent/services/identifierService.ts b/src/core/agent/services/identifierService.ts index c509fcda8..37f5d40d4 100644 --- a/src/core/agent/services/identifierService.ts +++ b/src/core/agent/services/identifierService.ts @@ -193,7 +193,9 @@ class IdentifierService extends AgentService { const identifier = operation.serder.ked.i; // @TODO - foconnor: Need update HabState interface on signify. - const identifierDetail = await this.props.signifyClient.identifiers().get(identifier) as HabState & { icp_dt: string }; + const identifierDetail = (await this.props.signifyClient + .identifiers() + .get(identifier)) as HabState & { icp_dt: string }; const addRoleOperation = await this.props.signifyClient .identifiers() @@ -222,9 +224,13 @@ class IdentifierService extends AgentService { id: identifier, ...metadata, isPending: !op.done, - createdAt: new Date(identifierDetail.icp_dt) + createdAt: new Date(identifierDetail.icp_dt), }); - return { identifier, isPending: !op.done, createdAt: identifierDetail.icp_dt }; + return { + identifier, + isPending: !op.done, + createdAt: identifierDetail.icp_dt, + }; } async deleteIdentifier(identifier: string): Promise { @@ -405,15 +411,16 @@ class IdentifierService extends AgentService { const name = identifier.name.split(":"); const theme = parseInt(name[0], 10); const isMultiSig = name.length === 3; - const identifierDetail = await this.props.signifyClient.identifiers().get(identifier) as HabState & { icp_dt: string }; - + const identifierDetail = (await this.props.signifyClient + .identifiers() + .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, @@ -421,25 +428,7 @@ class IdentifierService extends AgentService { groupInitiator, }, isPending, - createdAt: new Date(identifierDetail.icp_dt) - }); - - this.props.eventEmitter.emit({ - type: EventTypes.IdentifierStateChanged, - payload: { - identifier: { - id: identifier.prefix, - displayName: groupId, - theme, - groupMetadata: { - groupId, - groupCreated: false, - groupInitiator, - }, - isPending, - createdAtUTC: new Date().toISOString(), - }, - }, + createdAt: new Date(identifierDetail.icp_dt), }); continue; } @@ -449,20 +438,7 @@ class IdentifierService extends AgentService { displayName: identifier.name.split(":")[1], theme, isPending, - createdAt: new Date(identifierDetail.icp_dt) - }); - - this.props.eventEmitter.emit({ - type: EventTypes.IdentifierStateChanged, - payload: { - identifier: { - id: identifier.prefix, - displayName: identifier.name.split(":")[1], - theme, - isPending, - createdAtUTC: new Date().toISOString(), - }, - }, + createdAt: new Date(identifierDetail.icp_dt), }); } @@ -479,7 +455,9 @@ class IdentifierService extends AgentService { .operations() .get(`group.${identifier.prefix}`); const isPending = !op.done; - const identifierDetail = await this.props.signifyClient.identifiers().get(identifier) as HabState & { icp_dt: string }; + const identifierDetail = (await this.props.signifyClient + .identifiers() + .get(identifier)) as HabState & { icp_dt: string }; if (isPending) { const pendingOperation = await this.operationPendingStorage.save({ @@ -506,9 +484,8 @@ class IdentifierService extends AgentService { theme, multisigManageAid, isPending, - createdAt: new Date(identifierDetail.icp_dt - ) - }) + createdAt: new Date(identifierDetail.icp_dt), + }); } } diff --git a/src/ui/App.test.tsx b/src/ui/App.test.tsx index 05faf0500..7fb41c69c 100644 --- a/src/ui/App.test.tsx +++ b/src/ui/App.test.tsx @@ -26,7 +26,6 @@ jest.mock("../core/agent/agent", () => ({ identifiers: { getIdentifiers: jest.fn().mockResolvedValue([]), syncKeriaIdentifiers: jest.fn(), - onIdentifierStateChanged: jest.fn(), }, connections: { getConnections: jest.fn().mockResolvedValue([]), @@ -134,7 +133,7 @@ const addKeyboardEventMock = jest.fn(); jest.mock("@capacitor/keyboard", () => ({ Keyboard: { addListener: (...params: any[]) => addKeyboardEventMock(...params), - hide: jest.fn() + hide: jest.fn(), }, })); diff --git a/src/ui/components/AppWrapper/AppWrapper.test.tsx b/src/ui/components/AppWrapper/AppWrapper.test.tsx index 8d9d4bc16..591fe2dc0 100644 --- a/src/ui/components/AppWrapper/AppWrapper.test.tsx +++ b/src/ui/components/AppWrapper/AppWrapper.test.tsx @@ -64,7 +64,6 @@ jest.mock("../../../core/agent/agent", () => ({ identifiers: { getIdentifiers: jest.fn().mockResolvedValue([]), syncKeriaIdentifiers: jest.fn(), - onIdentifierStateChanged: jest.fn(), }, multiSigs: { getMultisigIcpDetails: jest.fn().mockResolvedValue({}), diff --git a/src/ui/components/AppWrapper/AppWrapper.tsx b/src/ui/components/AppWrapper/AppWrapper.tsx index fb89a8727..6629097ca 100644 --- a/src/ui/components/AppWrapper/AppWrapper.tsx +++ b/src/ui/components/AppWrapper/AppWrapper.tsx @@ -123,15 +123,6 @@ const acdcChangeHandler = async ( } }; -const identifierChangeHandler = async ( - event: IdentifierStateChangedEvent, - dispatch: ReturnType -) => { - const identifierRecord = event.payload.identifier; - - dispatch(updateOrAddIdentifiersCache(identifierRecord)); -}; - const peerConnectRequestSignChangeHandler = async ( event: PeerConnectSigningEvent, dispatch: ReturnType @@ -500,10 +491,6 @@ const AppWrapper = (props: { children: ReactNode }) => { Agent.agent.keriaNotifications.onRemoveNotification((event) => { notificatiStateChanged(event, dispatch); }); - - Agent.agent.identifiers.onIdentifierStateChanged((event) => { - identifierChangeHandler(event, dispatch); - }); }; const initApp = async () => { @@ -534,6 +521,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) { + Agent.agent.syncWithKeria(); + } + } + await Agent.agent.start(keriaConnectUrlRecord.content.url as string); } catch (e) { const errorMessage = (e as Error).message; From d6262a9e06582e0323dc63332aabdfdce0b0b68e Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Mon, 16 Dec 2024 16:44:13 +0700 Subject: [PATCH 5/8] update: update unit test --- src/core/agent/agent.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/agent/agent.test.ts b/src/core/agent/agent.test.ts index 02f1ef643..d923e4001 100644 --- a/src/core/agent/agent.test.ts +++ b/src/core/agent/agent.test.ts @@ -296,7 +296,7 @@ describe("test cases of recoverKeriaAgent function", () => { expect(mockSignifyClient.connect).toHaveBeenCalled(); expect(mockBasicStorageService.update).toHaveBeenCalledWith({ _tags: {}, - content: { value: false }, + content: { syncing: false }, createdAt: now, id: MiscRecordId.CLOUD_RECOVERY_STATUS, type: "BasicRecord", From db6fb03f47c8843e6604046ff201a781c7a8a314 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Wed, 18 Dec 2024 09:33:16 +0700 Subject: [PATCH 6/8] feat: resolve comment --- src/core/agent/agent.ts | 12 ++++++------ src/ui/components/AppWrapper/AppWrapper.tsx | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core/agent/agent.ts b/src/core/agent/agent.ts index 64e3d6e16..879b86b54 100644 --- a/src/core/agent/agent.ts +++ b/src/core/agent/agent.ts @@ -296,22 +296,22 @@ 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: "", }); - this.markAgentStatus(true); await this.syncWithKeria(); } async syncWithKeria() { - await this.basicStorage.save({ - id: MiscRecordId.CLOUD_RECOVERY_STATUS, - content: { syncing: true }, - }); - + this.markAgentStatus(true); await this.connections.syncKeriaContacts(); await this.identifiers.syncKeriaIdentifiers(); await this.credentials.syncACDCs(); diff --git a/src/ui/components/AppWrapper/AppWrapper.tsx b/src/ui/components/AppWrapper/AppWrapper.tsx index 6629097ca..6d1b092c4 100644 --- a/src/ui/components/AppWrapper/AppWrapper.tsx +++ b/src/ui/components/AppWrapper/AppWrapper.tsx @@ -526,7 +526,8 @@ const AppWrapper = (props: { children: ReactNode }) => { MiscRecordId.CLOUD_RECOVERY_STATUS ); if (recoveryStatus?.content?.syncing) { - Agent.agent.syncWithKeria(); + await Agent.agent.syncWithKeria(); + await loadDatabase() } } From ec0787408e902ad1cbe69e2c97b7d90608c54ea3 Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Mon, 23 Dec 2024 15:05:56 +0700 Subject: [PATCH 7/8] feat: long running operation recovery --- src/core/agent/records/notificationRecord.ts | 8 +- .../agent/records/notificationRecord.types.ts | 4 +- .../records/operationPendingRecord.type.ts | 1 + src/core/agent/services/identifierService.ts | 8 +- .../services/ipexCommunicationService.test.ts | 148 +++++++++--------- .../services/ipexCommunicationService.ts | 75 +++++---- .../ipexCommunicationService.types.ts | 4 +- .../services/keriaNotificationService.test.ts | 61 +++++--- .../services/keriaNotificationService.ts | 32 +++- .../CredentialRequest.test.tsx | 2 +- .../CredentialRequest/CredentialRequest.tsx | 4 +- .../CredentialRequestInformation.test.tsx | 8 +- .../CredentialRequestInformation.tsx | 10 +- .../ReceiveCredential.test.tsx | 8 +- .../ReceiveCredential/ReceiveCredential.tsx | 12 +- 15 files changed, 222 insertions(+), 163 deletions(-) diff --git a/src/core/agent/records/notificationRecord.ts b/src/core/agent/records/notificationRecord.ts index 6c0e6bd55..45d4a526b 100644 --- a/src/core/agent/records/notificationRecord.ts +++ b/src/core/agent/records/notificationRecord.ts @@ -1,6 +1,6 @@ import { BaseRecord, Tags } from "../../storage/storage.types"; import { NotificationRoute } from "../agent.types"; -import { LinkedGroupRequest } from "./notificationRecord.types"; +import { LinkedRequest } from "./notificationRecord.types"; import { randomSalt } from "../services/utils"; interface NotificationRecordStorageProps { @@ -13,7 +13,7 @@ interface NotificationRecordStorageProps { multisigId?: string; connectionId: string; credentialId?: string; - linkedGroupRequest?: LinkedGroupRequest; + linkedRequest?: LinkedRequest; } class NotificationRecord extends BaseRecord { @@ -22,7 +22,7 @@ class NotificationRecord extends BaseRecord { read!: boolean; multisigId?: string; connectionId!: string; - linkedGroupRequest!: LinkedGroupRequest; + linkedRequest!: LinkedRequest; credentialId?: string; static readonly type = "NotificationRecord"; @@ -39,7 +39,7 @@ class NotificationRecord extends BaseRecord { this.multisigId = props.multisigId; this.connectionId = props.connectionId; this._tags = props.tags ?? {}; - this.linkedGroupRequest = props.linkedGroupRequest ?? { accepted: false }; + this.linkedRequest = props.linkedRequest ?? { accepted: false }; this.credentialId = props.credentialId; } } diff --git a/src/core/agent/records/notificationRecord.types.ts b/src/core/agent/records/notificationRecord.types.ts index 5b3f11d10..169a6eed9 100644 --- a/src/core/agent/records/notificationRecord.types.ts +++ b/src/core/agent/records/notificationRecord.types.ts @@ -1,6 +1,6 @@ import { Notification } from "../services/credentialService.types"; -interface LinkedGroupRequest { +interface LinkedRequest { accepted: boolean; current?: string; previous?: string; @@ -12,4 +12,4 @@ interface NotificationAttempts { notification: Notification; } -export type { LinkedGroupRequest, NotificationAttempts }; +export type { LinkedRequest, NotificationAttempts }; diff --git a/src/core/agent/records/operationPendingRecord.type.ts b/src/core/agent/records/operationPendingRecord.type.ts index 09f278cac..58a195c56 100644 --- a/src/core/agent/records/operationPendingRecord.type.ts +++ b/src/core/agent/records/operationPendingRecord.type.ts @@ -1,6 +1,7 @@ export enum OperationPendingRecordType { Witness = "witness", Group = "group", + Done = "done", Oobi = "oobi", ExchangeReceiveCredential = "exchange.receivecredential", ExchangeOfferCredential = "exchange.offercredential", diff --git a/src/core/agent/services/identifierService.ts b/src/core/agent/services/identifierService.ts index 2b7724cf0..4c134390d 100644 --- a/src/core/agent/services/identifierService.ts +++ b/src/core/agent/services/identifierService.ts @@ -498,24 +498,30 @@ class IdentifierService extends AgentService { continue; } + let recordType = OperationPendingRecordType.Witness; + const op: Operation = await this.props.signifyClient .operations() .get(`witness.${identifier.prefix}`) .catch(async (error) => { const status = error.message.split(" - ")[1]; + if (/404/gi.test(status)) { + recordType = OperationPendingRecordType.Done return await this.props.signifyClient .operations() .get(`done.${identifier.prefix}`); } + throw error; }); + const isPending = !op.done; if (isPending) { const pendingOperation = await this.operationPendingStorage.save({ id: op.name, - recordType: OperationPendingRecordType.Witness, + recordType, }); this.props.eventEmitter.emit({ type: EventTypes.OperationAdded, diff --git a/src/core/agent/services/ipexCommunicationService.test.ts b/src/core/agent/services/ipexCommunicationService.test.ts index 98decdde6..dbb1ab7a4 100644 --- a/src/core/agent/services/ipexCommunicationService.test.ts +++ b/src/core/agent/services/ipexCommunicationService.test.ts @@ -1,4 +1,5 @@ import { Saider, Serder } from "signify-ts"; +import { current } from "@reduxjs/toolkit"; import { CoreEventEmitter } from "../event"; import { IpexCommunicationService } from "./ipexCommunicationService"; import { Agent } from "../agent"; @@ -314,7 +315,7 @@ describe("Receive individual ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -432,7 +433,9 @@ describe("Receive individual ACDC actions", () => { a: { d: "saidForUuid", }, - linkedGroupRequest: { accepted: false }, + linkedRequest: { + current: "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", + accepted: false }, }); identifierStorage.getIdentifierMetadata = jest .fn() @@ -442,10 +445,13 @@ describe("Receive individual ACDC actions", () => { IpexCommunicationService.ISSUEE_NOT_FOUND_LOCALLY ); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", + recordType: OperationPendingRecordType.ExchangeReceiveCredential, + }) expect(ipexAdmitMock).not.toBeCalled(); expect(ipexSubmitAdmitMock).not.toBeCalled(); expect(notificationStorage.save).not.toBeCalled(); - expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); }); @@ -469,7 +475,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", @@ -533,7 +539,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { "EDm8iNyZ9I3P93jb0lFtL6DJD-4Mtd2zw1ADFOoEQAqw": false, }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", @@ -587,7 +593,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { "accepted": true, "current": "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", }, @@ -633,7 +639,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "EC1cyV3zLnGs4B9AYgoGNjXESyQZrBWygz3jLlRD30bR" }, @@ -643,9 +649,13 @@ describe("Receive group ACDC actions", () => { await expect(ipexCommunicationService.admitAcdcFromGrant(id)).rejects.toThrowError(IpexCommunicationService.IPEX_ALREADY_REPLIED); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "EC1cyV3zLnGs4B9AYgoGNjXESyQZrBWygz3jLlRD30bR", + recordType: OperationPendingRecordType.ExchangeReceiveCredential + }); + expect(ipexAdmitMock).not.toBeCalled(); expect(ipexSubmitAdmitMock).not.toBeCalled(); - expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -662,7 +672,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", }, @@ -765,7 +775,7 @@ describe("Receive group ACDC actions", () => { expect(notificationStorage.update).toBeCalledWith(expect.objectContaining({ id: "id", route: NotificationRoute.ExnIpexGrant, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", }, @@ -825,7 +835,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-admit-said" }, @@ -857,7 +867,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", @@ -888,7 +898,7 @@ describe("Receive group ACDC actions", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", }, @@ -916,7 +926,7 @@ describe("Receive group ACDC progress", () => { await new ConfigurationService().start(); }); - test("Cannot get linkedGroupRequest from ipex/grant if the notification is missing in the DB", async () => { + test("Cannot get linkedRequest from ipex/grant if the notification is missing in the DB", async () => { const id = "uuid"; const date = DATETIME.toISOString(); const notification = { @@ -939,7 +949,7 @@ describe("Receive group ACDC progress", () => { test("Should return the current progress of an admit linked to a grant", async () => { const grantNoteRecord = { - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "currentsaid" }, @@ -984,7 +994,7 @@ describe("Receive group ACDC progress", () => { members: ["memberA", "memberB", "memberC"], threshold: "2", othersJoined: ["memberB", "memberC"], - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "currentsaid" } @@ -993,7 +1003,7 @@ describe("Receive group ACDC progress", () => { test("Should return the defaults when there is no admit linked to a grant", async () => { const grantNoteRecord = { - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, a: { d: "d" }, }; @@ -1030,7 +1040,7 @@ describe("Receive group ACDC progress", () => { members: ["memberA", "memberB", "memberC"], threshold: "2", othersJoined: [], - linkedGroupRequest: { + linkedRequest: { accepted: false, } }); @@ -1056,7 +1066,7 @@ describe("Offer ACDC individual actions", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { current: "EC1cyV3zLnGs4B9AYgoGNjXESyQZrBWygz3jLlRD30bR", accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -1081,6 +1091,11 @@ describe("Offer ACDC individual actions", () => { await ipexCommunicationService.offerAcdcFromApply(id, grantForIssuanceExnMessage.exn.e.acdc); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "EC1cyV3zLnGs4B9AYgoGNjXESyQZrBWygz3jLlRD30bR", + recordType: OperationPendingRecordType.ExchangeOfferCredential, + }); + expect(ipexOfferMock).toBeCalledWith({ senderName: "abc123", recipient: "i", @@ -1099,15 +1114,7 @@ describe("Offer ACDC individual actions", () => { id: "opName", recordType: OperationPendingRecordType.ExchangeOfferCredential, }); - expect(eventEmitter.emit).toHaveBeenCalledWith({ - type: EventTypes.OperationAdded, - payload: { - operation: { - id: "opName", - recordType: OperationPendingRecordType.ExchangeOfferCredential, - }, - }, - }); + expect(eventEmitter.emit).toBeCalledTimes(1); expect(notificationStorage.deleteById).toBeCalledWith(id); }); @@ -1151,7 +1158,7 @@ describe("Offer ACDC group actions", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -1197,7 +1204,7 @@ describe("Offer ACDC group actions", () => { expect(notificationStorage.update).toBeCalledWith(expect.objectContaining({ id, route: NotificationRoute.ExnIpexApply, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "EARi8kQ1PkSSRyFEIPOFPdnsnv7P2QZYEQqnmr1Eo2N8", }, @@ -1206,15 +1213,7 @@ describe("Offer ACDC group actions", () => { id: "opName", recordType: OperationPendingRecordType.ExchangeOfferCredential, }); - expect(eventEmitter.emit).toHaveBeenCalledWith({ - type: EventTypes.OperationAdded, - payload: { - operation: { - id: "opName", - recordType: OperationPendingRecordType.ExchangeOfferCredential, - }, - }, - }); + expect(eventEmitter.emit).toBeCalledTimes(1); expect(markNotificationMock).not.toBeCalled(); expect(notificationStorage.deleteById).not.toBeCalled(); }); @@ -1223,7 +1222,7 @@ describe("Offer ACDC group actions", () => { Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValueOnce(true); eventEmitter.emit = jest.fn(); const applyNoteRecord = { - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "currentsaid" }, @@ -1232,10 +1231,13 @@ describe("Offer ACDC group actions", () => { await expect(ipexCommunicationService.offerAcdcFromApply("id", grantForIssuanceExnMessage.exn.e.acdc)).rejects.toThrowError(IpexCommunicationService.IPEX_ALREADY_REPLIED); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "currentsaid", + recordType: OperationPendingRecordType.ExchangeOfferCredential, + }); expect(ipexOfferMock).not.toBeCalled(); expect(ipexSubmitOfferMock).not.toBeCalled(); expect(notificationStorage.save).not.toBeCalled(); - expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -1250,7 +1252,7 @@ describe("Offer ACDC group actions", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "current-offer-said" }, @@ -1320,7 +1322,7 @@ describe("Offer ACDC group actions", () => { expect(notificationStorage.update).toBeCalledWith(expect.objectContaining({ id: "id", route: NotificationRoute.ExnIpexApply, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-offer-said", }, @@ -1350,7 +1352,7 @@ describe("Offer ACDC group actions", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-offer-said" }, @@ -1378,7 +1380,7 @@ describe("Offer ACDC group actions", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", @@ -1422,7 +1424,7 @@ describe("Offer ACDC group progress", () => { test("Should return the current progress of a group offer", async () => { const applyNoteRecord = { - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-offer-said" }, @@ -1462,7 +1464,7 @@ describe("Offer ACDC group progress", () => { expect(result).toEqual({ members: ["memberA", "memberB", "memberC"], threshold: "2", - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-offer-said", }, @@ -1472,7 +1474,7 @@ describe("Offer ACDC group progress", () => { test("Should return the defaults when there is no offer linked to the apply", async () => { const applyNoteRecord = { - linkedGroupRequest: { + linkedRequest: { accepted: false, }, a: { d: "d" }, @@ -1504,7 +1506,7 @@ describe("Offer ACDC group progress", () => { expect(result).toEqual({ members: ["memberA", "memberB"], threshold: "2", - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, othersJoined: [], }); }); @@ -1528,7 +1530,7 @@ describe("Grant ACDC individual actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false, current: "current-grant-said" }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -1545,6 +1547,11 @@ describe("Grant ACDC individual actions", () => { await ipexCommunicationService.grantAcdcFromAgree("agree-note-id"); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "current-grant-said", + recordType: OperationPendingRecordType.ExchangePresentCredential, + }); + expect(ipexGrantMock).toBeCalledWith({ acdc: new Serder(credentialProps.sad), acdcAttachment: credentialProps.atc, @@ -1561,15 +1568,8 @@ describe("Grant ACDC individual actions", () => { id: "opName", recordType: OperationPendingRecordType.ExchangePresentCredential, }); - expect(eventEmitter.emit).toHaveBeenCalledWith({ - type: EventTypes.OperationAdded, - payload: { - operation: { - id: "opName", - recordType: OperationPendingRecordType.ExchangePresentCredential, - }, - }, - }); + + expect(eventEmitter.emit).toBeCalledTimes(1); expect(notificationStorage.deleteById).toBeCalledWith("agree-note-id"); }); @@ -1592,7 +1592,7 @@ describe("Grant ACDC individual actions", () => { test("Cannot present non existing ACDC", async () => { Agent.agent.getKeriaOnlineStatus = jest.fn().mockReturnValue(true); eventEmitter.emit = jest.fn(); - notificationStorage.findById = jest.fn().mockResolvedValue({ + notificationStorage.findById = jest.fn().mockResolvedValueOnce({ type: "NotificationRecord", id: "note-id", createdAt: DATETIME, @@ -1602,7 +1602,7 @@ describe("Grant ACDC individual actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false, current: "current-grant-said" }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -1618,10 +1618,13 @@ describe("Grant ACDC individual actions", () => { IpexCommunicationService.CREDENTIAL_NOT_FOUND ); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "current-grant-said", + recordType: OperationPendingRecordType.ExchangePresentCredential, + }); expect(ipexGrantMock).not.toBeCalled(); expect(ipexSubmitGrantMock).not.toBeCalled(); expect(notificationStorage.save).not.toBeCalled(); - expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -1638,7 +1641,7 @@ describe("Grant ACDC individual actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -1669,7 +1672,7 @@ describe("Grant ACDC group actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); @@ -1727,7 +1730,7 @@ describe("Grant ACDC group actions", () => { expect(notificationStorage.update).toBeCalledWith(expect.objectContaining({ id: "note-id", route: NotificationRoute.ExnIpexAgree, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "EEpfEHR6EedLnEzleK7mM3AKJSoPWuSQeREC8xjyq3pa", }, @@ -1760,16 +1763,19 @@ describe("Grant ACDC group actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: true, current: "current-grant-said" }, + linkedRequest: { accepted: true, current: "current-grant-said" }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }); await expect(ipexCommunicationService.admitAcdcFromGrant("id")).rejects.toThrowError(IpexCommunicationService.IPEX_ALREADY_REPLIED); + expect(operationPendingStorage.save).toBeCalledWith({ + id: "current-grant-said", + recordType: OperationPendingRecordType.ExchangeReceiveCredential, + }); expect(ipexGrantMock).not.toBeCalled(); expect(ipexSubmitGrantMock).not.toBeCalled(); - expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -1813,7 +1819,7 @@ describe("Grant ACDC group actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "current-grant-said" }, @@ -1850,7 +1856,7 @@ describe("Grant ACDC group actions", () => { expect(notificationStorage.update).toBeCalledWith(expect.objectContaining({ id: "note-id", route: NotificationRoute.ExnIpexAgree, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-grant-said", }, @@ -1868,7 +1874,7 @@ describe("Grant ACDC group actions", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "current-grant-said" }, @@ -1892,7 +1898,7 @@ describe("Grant ACDC group actions", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { + linkedRequest: { accepted: false, }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", diff --git a/src/core/agent/services/ipexCommunicationService.ts b/src/core/agent/services/ipexCommunicationService.ts index 13620230b..c54ee3cf7 100644 --- a/src/core/agent/services/ipexCommunicationService.ts +++ b/src/core/agent/services/ipexCommunicationService.ts @@ -93,8 +93,15 @@ class IpexCommunicationService extends AgentService { ); } + if (grantNoteRecord.linkedRequest) { + await this.operationPendingStorage.save({ + id: grantNoteRecord.linkedRequest.current, + recordType: OperationPendingRecordType.ExchangeReceiveCredential, + }); + } + // For groups only - if (grantNoteRecord.linkedGroupRequest.accepted) { + if (grantNoteRecord.linkedRequest.accepted) { throw new Error(`${IpexCommunicationService.IPEX_ALREADY_REPLIED} ${notificationId}`); } @@ -149,8 +156,8 @@ class IpexCommunicationService extends AgentService { allSchemaSaids ); op = opMultisigAdmit; - grantNoteRecord.linkedGroupRequest = { - ...grantNoteRecord.linkedGroupRequest, + grantNoteRecord.linkedRequest = { + ...grantNoteRecord.linkedRequest, accepted: true, current: exnSaid, }; @@ -198,8 +205,15 @@ class IpexCommunicationService extends AgentService { ); } + if (applyNoteRecord.linkedRequest) { + await this.operationPendingStorage.save({ + id: applyNoteRecord.linkedRequest.current, + recordType: OperationPendingRecordType.ExchangeOfferCredential, + }); + } + // For groups only - if (applyNoteRecord.linkedGroupRequest.accepted) { + if (applyNoteRecord.linkedRequest.accepted) { throw new Error(`${IpexCommunicationService.IPEX_ALREADY_REPLIED} ${notificationId}`); } @@ -221,8 +235,8 @@ class IpexCommunicationService extends AgentService { applyExn.exn.i ); op = opMultisigOffer; - applyNoteRecord.linkedGroupRequest = { - ...applyNoteRecord.linkedGroupRequest, + applyNoteRecord.linkedRequest = { + ...applyNoteRecord.linkedRequest, accepted: true, current: exnSaid, }; @@ -268,8 +282,15 @@ class IpexCommunicationService extends AgentService { ); } + if (agreeNoteRecord.linkedRequest) { + await this.operationPendingStorage.save({ + id: agreeNoteRecord.linkedRequest.current, + recordType: OperationPendingRecordType.ExchangePresentCredential, + }); + } + // For groups only - if (agreeNoteRecord.linkedGroupRequest.accepted) { + if (agreeNoteRecord.linkedRequest.accepted) { throw new Error(`${IpexCommunicationService.IPEX_ALREADY_REPLIED} ${notificationId}`); } @@ -313,8 +334,8 @@ class IpexCommunicationService extends AgentService { ); op = opMultisigGrant; - agreeNoteRecord.linkedGroupRequest = { - ...agreeNoteRecord.linkedGroupRequest, + agreeNoteRecord.linkedRequest = { + ...agreeNoteRecord.linkedRequest, accepted: true, current: exnSaid, }; @@ -543,11 +564,11 @@ class IpexCommunicationService extends AgentService { ); } - if (grantNoteRecord.linkedGroupRequest.accepted) { + if (grantNoteRecord.linkedRequest.accepted) { throw new Error(IpexCommunicationService.IPEX_ALREADY_REPLIED); } - const multiSigExnSaid = grantNoteRecord.linkedGroupRequest.current; + const multiSigExnSaid = grantNoteRecord.linkedRequest.current; if (!multiSigExnSaid) { throw new Error(IpexCommunicationService.NO_CURRENT_IPEX_MSG_TO_JOIN); } @@ -613,8 +634,8 @@ class IpexCommunicationService extends AgentService { payload: { operation: pendingOperation }, }); - grantNoteRecord.linkedGroupRequest = { - ...grantNoteRecord.linkedGroupRequest, + grantNoteRecord.linkedRequest = { + ...grantNoteRecord.linkedRequest, accepted: true, }; await this.notificationStorage.update(grantNoteRecord); @@ -628,11 +649,11 @@ class IpexCommunicationService extends AgentService { ); } - if (applyNoteRecord.linkedGroupRequest.accepted) { + if (applyNoteRecord.linkedRequest.accepted) { throw new Error(IpexCommunicationService.IPEX_ALREADY_REPLIED); } - const multiSigExnSaid = applyNoteRecord.linkedGroupRequest.current; + const multiSigExnSaid = applyNoteRecord.linkedRequest.current; if (!multiSigExnSaid) { throw new Error(IpexCommunicationService.NO_CURRENT_IPEX_MSG_TO_JOIN); } @@ -658,19 +679,19 @@ class IpexCommunicationService extends AgentService { payload: { operation: pendingOperation }, }); - applyNoteRecord.linkedGroupRequest = { - ...applyNoteRecord.linkedGroupRequest, + applyNoteRecord.linkedRequest = { + ...applyNoteRecord.linkedRequest, accepted: true, }; await this.notificationStorage.update(applyNoteRecord); } async joinMultisigGrant(multiSigExn: ExnMessage, agreeNoteRecord: NotificationRecord): Promise { - if (agreeNoteRecord.linkedGroupRequest.accepted) { + if (agreeNoteRecord.linkedRequest.accepted) { throw new Error(IpexCommunicationService.IPEX_ALREADY_REPLIED); } - if (!agreeNoteRecord.linkedGroupRequest.current) { + if (!agreeNoteRecord.linkedRequest.current) { throw new Error(IpexCommunicationService.NO_CURRENT_IPEX_MSG_TO_JOIN); } @@ -696,8 +717,8 @@ class IpexCommunicationService extends AgentService { payload: { operation: pendingOperation }, }); - agreeNoteRecord.linkedGroupRequest = { - ...agreeNoteRecord.linkedGroupRequest, + agreeNoteRecord.linkedRequest = { + ...agreeNoteRecord.linkedRequest, accepted: true, }; await this.notificationStorage.update(agreeNoteRecord); @@ -1078,8 +1099,8 @@ class IpexCommunicationService extends AgentService { const memberAids = members.signing.map((member: any) => member.aid); const othersJoined: string[] = []; - if (grantNoteRecord.linkedGroupRequest.current) { - for (const signal of (await this.props.signifyClient.groups().getRequest(grantNoteRecord.linkedGroupRequest.current))) { + if (grantNoteRecord.linkedRequest.current) { + for (const signal of (await this.props.signifyClient.groups().getRequest(grantNoteRecord.linkedRequest.current))) { othersJoined.push(signal.exn.i); } } @@ -1088,7 +1109,7 @@ class IpexCommunicationService extends AgentService { threshold: multisigAid.state.kt, members: memberAids, othersJoined: othersJoined, - linkedGroupRequest: grantNoteRecord.linkedGroupRequest, + linkedRequest: grantNoteRecord.linkedRequest, } } @@ -1113,8 +1134,8 @@ class IpexCommunicationService extends AgentService { const memberAids = members.signing.map((member: any) => member.aid); const othersJoined: string[] = []; - if (applyNoteRecord.linkedGroupRequest.current) { - for (const signal of (await this.props.signifyClient.groups().getRequest(applyNoteRecord.linkedGroupRequest.current))) { + if (applyNoteRecord.linkedRequest.current) { + for (const signal of (await this.props.signifyClient.groups().getRequest(applyNoteRecord.linkedRequest.current))) { othersJoined.push(signal.exn.i); } } @@ -1123,7 +1144,7 @@ class IpexCommunicationService extends AgentService { threshold: multisigAid.state.kt, members: memberAids, othersJoined: othersJoined, - linkedGroupRequest: applyNoteRecord.linkedGroupRequest, + linkedRequest: applyNoteRecord.linkedRequest, } } diff --git a/src/core/agent/services/ipexCommunicationService.types.ts b/src/core/agent/services/ipexCommunicationService.types.ts index fa1088c29..1cc131081 100644 --- a/src/core/agent/services/ipexCommunicationService.types.ts +++ b/src/core/agent/services/ipexCommunicationService.types.ts @@ -1,4 +1,4 @@ -import { LinkedGroupRequest } from "../records/notificationRecord.types"; +import { LinkedRequest } from "../records/notificationRecord.types"; interface CredentialsMatchingApply { schema: { @@ -17,7 +17,7 @@ interface LinkedGroupInfo { threshold: string | string[]; members: string[]; othersJoined: string[]; - linkedGroupRequest: LinkedGroupRequest; + linkedRequest: LinkedRequest; } export type { CredentialsMatchingApply, LinkedGroupInfo }; diff --git a/src/core/agent/services/keriaNotificationService.test.ts b/src/core/agent/services/keriaNotificationService.test.ts index b8355ab11..373fe2b32 100644 --- a/src/core/agent/services/keriaNotificationService.test.ts +++ b/src/core/agent/services/keriaNotificationService.test.ts @@ -1,4 +1,5 @@ import { create } from "domain"; +import { current } from "@reduxjs/toolkit"; import { Agent } from "../agent"; import { ConnectionStatus, @@ -300,7 +301,7 @@ describe("Signify notification service of agent", () => { }, connectionId: "ED_3K5-VPI8N3iRrV7o75fIMOnJfoSmEJy679HTkWsFQ", read: false, - linkedGroupRequest: { accepted: false } + linkedRequest: { accepted: false } }); groupGetRequestMock.mockResolvedValue([{ exn: { a: { gid: "id" } } }]); @@ -338,6 +339,8 @@ describe("Signify notification service of agent", () => { et: "rev", }); notificationStorage.findAllByQuery = jest.fn().mockResolvedValue([]); + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); + const notes = [notificationIpexGrantProp]; credentialStorage.getCredentialMetadata.mockResolvedValue( credentialMetadataMock @@ -628,7 +631,8 @@ describe("Signify notification service of agent", () => { ); notificationStorage.save = jest .fn() - .mockReturnValue({ id: "id", createdAt: new Date(), linkedGroupRequest: { accepted: false } }); + .mockReturnValue({ id: "id", createdAt: new Date(), linkedRequest: { accepted: false } }); + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); await keriaNotificationService.processNotification( notificationIpexApplyProp @@ -668,6 +672,7 @@ describe("Signify notification service of agent", () => { done: true, }); notificationStorage.findAllByQuery = jest.fn().mockResolvedValue([]); + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); await keriaNotificationService.processNotification( notificationIpexGrantProp @@ -686,7 +691,7 @@ describe("Signify notification service of agent", () => { }); notificationStorage.save = jest .fn() - .mockReturnValue({ id: "id", createdAt: new Date(), linkedGroupRequest: { accepted: false } }); + .mockReturnValue({ id: "id", createdAt: new Date(), linkedRequest: { accepted: false } }); credentialStorage.getCredentialMetadata.mockResolvedValue( credentialMetadataMock ); @@ -715,11 +720,12 @@ describe("Signify notification service of agent", () => { }, route: NotificationRoute.ExnIpexGrant, read: false, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }; notificationStorage.findAllByQuery = jest.fn().mockResolvedValue([notification]); + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); await keriaNotificationService.processNotification( notificationIpexGrantProp @@ -764,7 +770,7 @@ describe("Signify notification service of agent", () => { }); notificationStorage.save = jest .fn() - .mockReturnValue({ id: "id", createdAt: new Date(), linkedGroupRequest: { accepted: false } }); + .mockReturnValue({ id: "id", createdAt: new Date(), linkedRequest: { accepted: false } }); credentialStorage.getCredentialMetadata.mockResolvedValue(undefined); const notification = { type: "NotificationRecord", @@ -776,7 +782,7 @@ describe("Signify notification service of agent", () => { }, route: NotificationRoute.ExnIpexGrant, read: false, - linkedGroupRequest: { accepted: false }, + linkedGlinkedRequestroupRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }; @@ -792,6 +798,7 @@ describe("Signify notification service of agent", () => { .mockResolvedValue({ id: "id", }); + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); await keriaNotificationService.processNotification( notificationIpexGrantProp @@ -830,7 +837,8 @@ describe("Signify notification service of agent", () => { .mockResolvedValue({ id: "id", }); - + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); + await expect(keriaNotificationService.processNotification( notificationIpexGrantProp )).rejects.toThrowError(KeriaNotificationService.DUPLICATE_ISSUANCE); @@ -921,7 +929,7 @@ describe("Signify notification service of agent", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }, @@ -936,7 +944,7 @@ describe("Signify notification service of agent", () => { id: "id", read: false, route: NotificationRoute.ExnIpexGrant, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "ELW97_QXT2MWtsmWLCSR8RBzH-dcyF2gTJvt72I0wEFO", }, @@ -1029,7 +1037,7 @@ describe("Signify notification service of agent", () => { multisigId: "multisig1", read: false, connectionId: "ED_3K5-VPI8N3iRrV7o75fIMOnJfoSmEJy679HTkWsFQ", - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, }, { id: "0AC0W34tnnd2WyUCOy-790AY", @@ -1038,7 +1046,7 @@ describe("Signify notification service of agent", () => { multisigId: "multisig2", read: false, connectionId: "ED_5C2-UOA8N3iRrV7o75fIMOnJfoSmYAe829YCiSaVB", - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, }, ]; @@ -1084,7 +1092,7 @@ describe("Signify notification service of agent", () => { multisigId: "multisig1", read: false, connectionId: "ED_3K5-VPI8N3iRrV7o75fIMOnJfoSmEJy679HTkWsFQ", - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, }, { id: "0AC0W34tnnd2WyUCOy-790AY", @@ -1093,7 +1101,7 @@ describe("Signify notification service of agent", () => { multisigId: "multisig2", read: false, connectionId: "ED_5C2-UOA8N3iRrV7o75fIMOnJfoSmYAe829YCiSaVB", - linkedGroupRequest: { accepted: false, current: "current-admit-said" }, + linkedRequest: { accepted: false, current: "current-admit-said" }, }, ]; @@ -1276,7 +1284,8 @@ describe("Signify notification service of agent", () => { .mockRejectedValueOnce( new Error(IdentifierStorage.IDENTIFIER_METADATA_RECORD_MISSING) ); - + notificationStorage.findById = jest.fn().mockResolvedValueOnce({linkedRequest: {current: "current_id"}}); + jest.useRealTimers(); await keriaNotificationService.processNotification( notificationIpexGrantProp @@ -1328,7 +1337,7 @@ describe("Signify notification service of agent", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }, @@ -1359,7 +1368,7 @@ describe("Signify notification service of agent", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }, @@ -1433,7 +1442,7 @@ describe("Signify notification service of agent", () => { }); notificationStorage.save = jest .fn() - .mockReturnValue({ id: "id", createdAt: new Date(), linkedGroupRequest: { accepted: false } }); + .mockReturnValue({ id: "id", createdAt: new Date(), linkedRequest: { accepted: false } }); const notes = [notificationIpexAgreeProp]; for (const notif of notes) { @@ -1507,7 +1516,7 @@ describe("Group IPEX presentation", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: DATETIME, }, @@ -1520,7 +1529,7 @@ describe("Group IPEX presentation", () => { expect(notificationStorage.update).toBeCalledWith(expect.objectContaining({ id: "id", route: NotificationRoute.ExnIpexApply, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "ELW97_QXT2MWtsmWLCSR8RBzH-dcyF2gTJvt72I0wEFO", }, @@ -1597,7 +1606,7 @@ describe("Group IPEX presentation", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date("2024-04-29T11:01:04.903Z"), }, @@ -1610,7 +1619,7 @@ describe("Group IPEX presentation", () => { const updatedAgree = { id: "id", route: NotificationRoute.ExnIpexAgree, - linkedGroupRequest: { + linkedRequest: { accepted: false, current: "ELW97_QXT2MWtsmWLCSR8RBzH-dcyF2gTJvt72I0wEFO", }, @@ -1650,7 +1659,7 @@ describe("Group IPEX presentation", () => { .mockResolvedValue(groupIdentifierMetadataRecord); notificationStorage.save = jest .fn() - .mockReturnValue({ id: "id", createdAt: new Date(), linkedGroupRequest: { accepted: false } }); + .mockReturnValue({ id: "id", createdAt: new Date(), linkedRequest: { accepted: false } }); identifiersMemberMock.mockResolvedValue({ signing: [ { @@ -1694,7 +1703,7 @@ describe("Group IPEX presentation", () => { .mockResolvedValue(groupIdentifierMetadataRecord); notificationStorage.save = jest .fn() - .mockReturnValue({ id: "id", createdAt: new Date(), linkedGroupRequest: { accepted: false } }); + .mockReturnValue({ id: "id", createdAt: new Date(), linkedRequest: { accepted: false } }); identifiersMemberMock.mockResolvedValue({ signing: [ { @@ -2473,7 +2482,7 @@ describe("Long running operation tracker", () => { }, route: NotificationRoute.ExnIpexGrant, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }, @@ -2544,7 +2553,7 @@ describe("Long running operation tracker", () => { }, route: NotificationRoute.ExnIpexApply, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }, @@ -2633,7 +2642,7 @@ describe("Long running operation tracker", () => { }, route: NotificationRoute.ExnIpexAgree, read: true, - linkedGroupRequest: { accepted: false }, + linkedRequest: { accepted: false }, connectionId: "EEFjBBDcUM2IWpNF7OclCme_bE76yKE3hzULLzTOFE8E", updatedAt: new Date(), }, diff --git a/src/core/agent/services/keriaNotificationService.ts b/src/core/agent/services/keriaNotificationService.ts index 621f2416e..61a455674 100644 --- a/src/core/agent/services/keriaNotificationService.ts +++ b/src/core/agent/services/keriaNotificationService.ts @@ -286,8 +286,24 @@ class KeriaNotificationService extends AgentService { let shouldCreateRecord = true; if (notif.a.r === NotificationRoute.ExnIpexApply) { + const applyNoteRecord = await this.notificationStorage.findById(notif.a.d); + + if (applyNoteRecord?.linkedRequest) { + await this.operationPendingStorage.save({ + id: applyNoteRecord?.linkedRequest.current, + recordType: OperationPendingRecordType.ExchangeOfferCredential, + }); + } shouldCreateRecord = await this.processExnIpexApplyNotification(exn); } else if (notif.a.r === NotificationRoute.ExnIpexGrant) { + const grantNoteRecord = await this.notificationStorage.findById(notif.a.d); + + if (grantNoteRecord?.linkedRequest) { + await this.operationPendingStorage.save({ + id: grantNoteRecord?.linkedRequest.current, + recordType: OperationPendingRecordType.ExchangePresentCredential, + }); + } shouldCreateRecord = await this.processExnIpexGrantNotification( notif, exn @@ -651,8 +667,8 @@ class KeriaNotificationService extends AgentService { // Refresh the date and read status for UI, and link const notificationRecord = grantNotificationRecords[0]; - notificationRecord.linkedGroupRequest = { - ...notificationRecord.linkedGroupRequest, + notificationRecord.linkedRequest = { + ...notificationRecord.linkedRequest, current: exchange.exn.d, }; notificationRecord.createdAt = new Date(); @@ -701,8 +717,8 @@ class KeriaNotificationService extends AgentService { // Refresh the date and read status for UI, and link const notificationRecord = applyNotificationRecords[0]; - notificationRecord.linkedGroupRequest = { - ...notificationRecord.linkedGroupRequest, + notificationRecord.linkedRequest = { + ...notificationRecord.linkedRequest, current: exchange.exn.d, }; notificationRecord.createdAt = new Date(); @@ -751,8 +767,8 @@ class KeriaNotificationService extends AgentService { // @TODO - foconnor: Could be optimised to only update record once but deviates from the other IPEX messages - OK for now. const notificationRecord = agreeNotificationRecords[0]; - notificationRecord.linkedGroupRequest = { - ...notificationRecord.linkedGroupRequest, + notificationRecord.linkedRequest = { + ...notificationRecord.linkedRequest, current: exchange.exn.d, }; @@ -810,7 +826,7 @@ class KeriaNotificationService extends AgentService { multisigId: result.multisigId, connectionId: result.connectionId, read: result.read, - groupReplied: result.linkedGroupRequest.current !== undefined, + groupReplied: result.linkedRequest.current !== undefined, }; } @@ -850,7 +866,7 @@ class KeriaNotificationService extends AgentService { multisigId: notification.multisigId, connectionId: notification.connectionId, read: notification.read, - groupReplied: notification.linkedGroupRequest.current !== undefined + groupReplied: notification.linkedRequest.current !== undefined }; }); } diff --git a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.test.tsx b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.test.tsx index e54d1bb65..178a35e8f 100644 --- a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.test.tsx +++ b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.test.tsx @@ -217,7 +217,7 @@ describe("Credential request: Multisig", () => { beforeEach(() => { getLinkedGroupFromIpexApplyMock.mockImplementation(() => Promise.resolve({ - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "", previous: undefined, diff --git a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.tsx b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.tsx index 4e7984b2b..a66b9e518 100644 --- a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.tsx +++ b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequest.tsx @@ -37,7 +37,7 @@ const CredentialRequest = ({ const reachThreshold = linkedGroup && - linkedGroup.othersJoined.length + (linkedGroup.linkedGroupRequest.accepted ? 1 : 0) >= Number(linkedGroup.threshold); + linkedGroup.othersJoined.length + (linkedGroup.linkedRequest.accepted ? 1 : 0) >= Number(linkedGroup.threshold); const userAID = useMemo(() => { if(!credentialRequest) return null; @@ -59,7 +59,7 @@ const CredentialRequest = ({ return { aid: member, name: userName, - joined: linkedGroup.linkedGroupRequest.accepted, + joined: linkedGroup.linkedRequest.accepted, } } diff --git a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.test.tsx b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.test.tsx index ba1249f3d..adde6db47 100644 --- a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.test.tsx +++ b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.test.tsx @@ -111,7 +111,7 @@ describe("Credential request information", () => { describe("Credential request information: multisig", () => { const linkedGroup = { - linkedGroupRequest:{ + linkedRequest:{ accepted: false, current: "", previous: undefined, @@ -184,7 +184,7 @@ describe("Credential request information: multisig", () => { test("Initiator chosen cred", async () => { const linkedGroup = { - linkedGroupRequest:{ + linkedRequest:{ accepted: true, current: "cred-id", previous: undefined, @@ -257,7 +257,7 @@ describe("Credential request information: multisig", () => { test("Member open cred", async () => { const linkedGroup = { - linkedGroupRequest:{ + linkedRequest:{ accepted: false, current: "cred-id", previous: undefined, @@ -344,7 +344,7 @@ describe("Credential request information: multisig", () => { test("Member open cred after initiator chosen cred", async () => { const linkedGroup = { - linkedGroupRequest:{ + linkedRequest:{ accepted: true, current: "cred-id", previous: undefined, diff --git a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.tsx b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.tsx index 09627c6f8..14d014c61 100644 --- a/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.tsx +++ b/src/ui/pages/NotificationDetails/components/CredentialRequest/CredentialRequestInformation/CredentialRequestInformation.tsx @@ -60,15 +60,15 @@ const CredentialRequestInformation = ({ const isGroupInitiatorJoined = !!linkedGroup?.memberInfos.at(0)?.joined; const getCred = useCallback(async () => { - if(!isGroupInitiatorJoined || !linkedGroup?.linkedGroupRequest.current) return; + if(!isGroupInitiatorJoined || !linkedGroup?.linkedRequest.current) return; try { - const id = await Agent.agent.ipexCommunications.getOfferedCredentialSaid(linkedGroup.linkedGroupRequest.current); + const id = await Agent.agent.ipexCommunications.getOfferedCredentialSaid(linkedGroup.linkedRequest.current); setChooseCredId(id); } catch (error) { showError("Unable to get choosen cred", error, dispatch); } - }, [dispatch, isGroupInitiatorJoined, linkedGroup?.linkedGroupRequest]); + }, [dispatch, isGroupInitiatorJoined, linkedGroup?.linkedRequest]); useOnlineStatusEffect(getCred); @@ -114,7 +114,7 @@ const CredentialRequestInformation = ({ const reachThreshold = linkedGroup && - linkedGroup.othersJoined.length + (linkedGroup.linkedGroupRequest.accepted ? 1 : 0) >= Number(linkedGroup.threshold); + linkedGroup.othersJoined.length + (linkedGroup.linkedRequest.accepted ? 1 : 0) >= Number(linkedGroup.threshold); const showProvidedCred = () => { setViewCredId(chooseCredId); @@ -251,7 +251,7 @@ const CredentialRequestInformation = ({ } { - linkedGroup?.linkedGroupRequest.current && { threshold: "2", members: ["member-1", "member-2"], othersJoined: [], - linkedGroupRequest: { + linkedRequest: { accepted: false, } }); @@ -514,7 +514,7 @@ describe("Credential request: Multisig", () => { threshold: "2", members: ["member-1", "member-2"], othersJoined: ["member-1"], - linkedGroupRequest: { + linkedRequest: { accepted: false, } }); @@ -562,7 +562,7 @@ describe("Credential request: Multisig", () => { threshold: "2", members: ["member-1", "member-2", "member-3"], othersJoined: ["member-1", "member-2"], - linkedGroupRequest: { + linkedRequest: { accepted: false, } }); @@ -604,7 +604,7 @@ describe("Credential request: Multisig", () => { threshold: "2", members: ["member-1", "member-2"], othersJoined: ["member-1"], - linkedGroupRequest: { + linkedRequest: { accepted: true, current: "currentadmitsaid" } diff --git a/src/ui/pages/NotificationDetails/components/ReceiveCredential/ReceiveCredential.tsx b/src/ui/pages/NotificationDetails/components/ReceiveCredential/ReceiveCredential.tsx index 5470fdff0..6c9c64e6f 100644 --- a/src/ui/pages/NotificationDetails/components/ReceiveCredential/ReceiveCredential.tsx +++ b/src/ui/pages/NotificationDetails/components/ReceiveCredential/ReceiveCredential.tsx @@ -79,7 +79,7 @@ const ReceiveCredential = ({ threshold: "0", members: [], othersJoined: [], - linkedGroupRequest: { + linkedRequest: { accepted: false, } }); @@ -93,10 +93,10 @@ const ReceiveCredential = ({ const connection = connectionsCache?.[notificationDetails.connectionId]?.label; - const userAccepted = multisigMemberStatus.linkedGroupRequest.accepted; + const userAccepted = multisigMemberStatus.linkedRequest.accepted; const maxThreshold = isMultisig && - (multisigMemberStatus.othersJoined.length + (multisigMemberStatus.linkedGroupRequest.accepted ? 1 : 0)) >= + (multisigMemberStatus.othersJoined.length + (multisigMemberStatus.linkedRequest.accepted ? 1 : 0)) >= Number(multisigMemberStatus.threshold); const identifier = useMemo(() => { @@ -195,7 +195,7 @@ const ReceiveCredential = ({ if(!isMultisig || (isMultisig && isGroupInitiator)) { await Agent.agent.ipexCommunications.admitAcdcFromGrant(notificationDetails.id); - } else if(multisigMemberStatus.linkedGroupRequest.current) { + } else if(multisigMemberStatus.linkedRequest.current) { await Agent.agent.ipexCommunications.joinMultisigAdmit(notificationDetails.id); } @@ -242,13 +242,13 @@ const ReceiveCredential = ({ return MemberAcceptStatus.Accepted; } - if (multisigMemberStatus.linkedGroupRequest.accepted && identifier?.multisigManageAid === member) { + if (multisigMemberStatus.linkedRequest.accepted && identifier?.multisigManageAid === member) { return MemberAcceptStatus.Accepted; } return MemberAcceptStatus.Waiting; }, - [multisigMemberStatus.othersJoined, multisigMemberStatus.linkedGroupRequest, identifier] + [multisigMemberStatus.othersJoined, multisigMemberStatus.linkedRequest, identifier] ); const members = useMemo(() => { From 261bb135e49e6dd474f474824d302fee5f9de9be Mon Sep 17 00:00:00 2001 From: Martin Nguyen Date: Tue, 24 Dec 2024 10:42:43 +0700 Subject: [PATCH 8/8] remove duplicate logic --- .../services/ipexCommunicationService.test.ts | 67 ++++++++++--------- .../services/ipexCommunicationService.ts | 21 ------ 2 files changed, 34 insertions(+), 54 deletions(-) diff --git a/src/core/agent/services/ipexCommunicationService.test.ts b/src/core/agent/services/ipexCommunicationService.test.ts index dbb1ab7a4..cbe32cc05 100644 --- a/src/core/agent/services/ipexCommunicationService.test.ts +++ b/src/core/agent/services/ipexCommunicationService.test.ts @@ -445,13 +445,10 @@ describe("Receive individual ACDC actions", () => { IpexCommunicationService.ISSUEE_NOT_FOUND_LOCALLY ); - expect(operationPendingStorage.save).toBeCalledWith({ - id: "EL3A2jk9gvmVe4ROISB2iWmM8yPSNwQlmar6-SFVWSPW", - recordType: OperationPendingRecordType.ExchangeReceiveCredential, - }) expect(ipexAdmitMock).not.toBeCalled(); expect(ipexSubmitAdmitMock).not.toBeCalled(); expect(notificationStorage.save).not.toBeCalled(); + expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); }); @@ -649,13 +646,9 @@ describe("Receive group ACDC actions", () => { await expect(ipexCommunicationService.admitAcdcFromGrant(id)).rejects.toThrowError(IpexCommunicationService.IPEX_ALREADY_REPLIED); - expect(operationPendingStorage.save).toBeCalledWith({ - id: "EC1cyV3zLnGs4B9AYgoGNjXESyQZrBWygz3jLlRD30bR", - recordType: OperationPendingRecordType.ExchangeReceiveCredential - }); - expect(ipexAdmitMock).not.toBeCalled(); expect(ipexSubmitAdmitMock).not.toBeCalled(); + expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -1092,7 +1085,7 @@ describe("Offer ACDC individual actions", () => { await ipexCommunicationService.offerAcdcFromApply(id, grantForIssuanceExnMessage.exn.e.acdc); expect(operationPendingStorage.save).toBeCalledWith({ - id: "EC1cyV3zLnGs4B9AYgoGNjXESyQZrBWygz3jLlRD30bR", + id: "opName", recordType: OperationPendingRecordType.ExchangeOfferCredential, }); @@ -1114,8 +1107,15 @@ describe("Offer ACDC individual actions", () => { id: "opName", recordType: OperationPendingRecordType.ExchangeOfferCredential, }); - expect(eventEmitter.emit).toBeCalledTimes(1); - expect(notificationStorage.deleteById).toBeCalledWith(id); + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: EventTypes.OperationAdded, + payload: { + operation: { + id: "opName", + recordType: OperationPendingRecordType.ExchangeOfferCredential, + }, + }, + }); expect(notificationStorage.deleteById).toBeCalledWith(id); }); test("Cannot offer ACDC if the apply notification is missing in the DB", async () => { @@ -1213,8 +1213,15 @@ describe("Offer ACDC group actions", () => { id: "opName", recordType: OperationPendingRecordType.ExchangeOfferCredential, }); - expect(eventEmitter.emit).toBeCalledTimes(1); - expect(markNotificationMock).not.toBeCalled(); + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: EventTypes.OperationAdded, + payload: { + operation: { + id: "opName", + recordType: OperationPendingRecordType.ExchangeOfferCredential, + }, + }, + }); expect(markNotificationMock).not.toBeCalled(); expect(notificationStorage.deleteById).not.toBeCalled(); }); @@ -1231,13 +1238,10 @@ describe("Offer ACDC group actions", () => { await expect(ipexCommunicationService.offerAcdcFromApply("id", grantForIssuanceExnMessage.exn.e.acdc)).rejects.toThrowError(IpexCommunicationService.IPEX_ALREADY_REPLIED); - expect(operationPendingStorage.save).toBeCalledWith({ - id: "currentsaid", - recordType: OperationPendingRecordType.ExchangeOfferCredential, - }); expect(ipexOfferMock).not.toBeCalled(); expect(ipexSubmitOfferMock).not.toBeCalled(); expect(notificationStorage.save).not.toBeCalled(); + expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -1547,11 +1551,6 @@ describe("Grant ACDC individual actions", () => { await ipexCommunicationService.grantAcdcFromAgree("agree-note-id"); - expect(operationPendingStorage.save).toBeCalledWith({ - id: "current-grant-said", - recordType: OperationPendingRecordType.ExchangePresentCredential, - }); - expect(ipexGrantMock).toBeCalledWith({ acdc: new Serder(credentialProps.sad), acdcAttachment: credentialProps.atc, @@ -1568,7 +1567,15 @@ describe("Grant ACDC individual actions", () => { id: "opName", recordType: OperationPendingRecordType.ExchangePresentCredential, }); - + expect(eventEmitter.emit).toHaveBeenCalledWith({ + type: EventTypes.OperationAdded, + payload: { + operation: { + id: "opName", + recordType: OperationPendingRecordType.ExchangePresentCredential, + }, + }, + }); expect(eventEmitter.emit).toBeCalledTimes(1); expect(notificationStorage.deleteById).toBeCalledWith("agree-note-id"); }); @@ -1617,14 +1624,11 @@ describe("Grant ACDC individual actions", () => { await expect(ipexCommunicationService.grantAcdcFromAgree("id")).rejects.toThrowError( IpexCommunicationService.CREDENTIAL_NOT_FOUND ); - - expect(operationPendingStorage.save).toBeCalledWith({ - id: "current-grant-said", - recordType: OperationPendingRecordType.ExchangePresentCredential, - }); + expect(ipexGrantMock).not.toBeCalled(); expect(ipexSubmitGrantMock).not.toBeCalled(); expect(notificationStorage.save).not.toBeCalled(); + expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); @@ -1770,12 +1774,9 @@ describe("Grant ACDC group actions", () => { await expect(ipexCommunicationService.admitAcdcFromGrant("id")).rejects.toThrowError(IpexCommunicationService.IPEX_ALREADY_REPLIED); - expect(operationPendingStorage.save).toBeCalledWith({ - id: "current-grant-said", - recordType: OperationPendingRecordType.ExchangeReceiveCredential, - }); expect(ipexGrantMock).not.toBeCalled(); expect(ipexSubmitGrantMock).not.toBeCalled(); + expect(operationPendingStorage.save).not.toBeCalled(); expect(eventEmitter.emit).not.toBeCalled(); }); diff --git a/src/core/agent/services/ipexCommunicationService.ts b/src/core/agent/services/ipexCommunicationService.ts index c54ee3cf7..dbbbd9111 100644 --- a/src/core/agent/services/ipexCommunicationService.ts +++ b/src/core/agent/services/ipexCommunicationService.ts @@ -93,13 +93,6 @@ class IpexCommunicationService extends AgentService { ); } - if (grantNoteRecord.linkedRequest) { - await this.operationPendingStorage.save({ - id: grantNoteRecord.linkedRequest.current, - recordType: OperationPendingRecordType.ExchangeReceiveCredential, - }); - } - // For groups only if (grantNoteRecord.linkedRequest.accepted) { throw new Error(`${IpexCommunicationService.IPEX_ALREADY_REPLIED} ${notificationId}`); @@ -204,13 +197,6 @@ class IpexCommunicationService extends AgentService { `${IpexCommunicationService.NOTIFICATION_NOT_FOUND} ${notificationId}` ); } - - if (applyNoteRecord.linkedRequest) { - await this.operationPendingStorage.save({ - id: applyNoteRecord.linkedRequest.current, - recordType: OperationPendingRecordType.ExchangeOfferCredential, - }); - } // For groups only if (applyNoteRecord.linkedRequest.accepted) { @@ -282,13 +268,6 @@ class IpexCommunicationService extends AgentService { ); } - if (agreeNoteRecord.linkedRequest) { - await this.operationPendingStorage.save({ - id: agreeNoteRecord.linkedRequest.current, - recordType: OperationPendingRecordType.ExchangePresentCredential, - }); - } - // For groups only if (agreeNoteRecord.linkedRequest.accepted) { throw new Error(`${IpexCommunicationService.IPEX_ALREADY_REPLIED} ${notificationId}`);