diff --git a/src/core/__fixtures__/agent/ipexCommunicationFixtures.ts b/src/core/__fixtures__/agent/ipexCommunicationFixtures.ts index e479683ca..6f3eb59a1 100644 --- a/src/core/__fixtures__/agent/ipexCommunicationFixtures.ts +++ b/src/core/__fixtures__/agent/ipexCommunicationFixtures.ts @@ -104,6 +104,25 @@ const offerForPresentingExnMessage = { }, }; +const admitForIssuanceExnMessage = { + "exn": { + "v": "KERI10JSON000178_", + "t": "exn", + "d": "EEqJBi-JmK5IG-rU4wnn5cplcnmAk6exhAwE2GkAG895", + "i": "EJHe0vp6WOPgBNjJEakNTW2xVPwKNRJfZ1q4DhoXem3D", + "rp": "EI6lgpgvnVbl6hdfJNWCxlWEz9il1S1mu89XBBjvUBwK", + "p": "EFYtFiqA6l2xlCtTwKksHpWtTSvIilwQGamB_qFvPuER", + "dt": "2024-12-23T07:42:34.448000+00:00", + "r": ExchangeRoute.IpexAdmit, + "q": {}, + "a": { + "i": "EI6lgpgvnVbl6hdfJNWCxlWEz9il1S1mu89XBBjvUBwK", + "m": "" + }, + "e": {} + }, + "pathed": {} +} // @TODO - foconnor: Agree must have valid p, and no embeds - but causing tests to fail right now. const agreeForPresentingExnMessage = { exn: { @@ -720,6 +739,7 @@ export { applyForPresentingExnMessage, offerForPresentingExnMessage, agreeForPresentingExnMessage, + admitForIssuanceExnMessage, groupIdentifierMetadataRecord, multisigExnOfferForPresenting, multisigExnAdmitForIssuance, diff --git a/src/core/agent/services/connectionService.types.ts b/src/core/agent/services/connectionService.types.ts index 8162b5f87..0d12521e7 100644 --- a/src/core/agent/services/connectionService.types.ts +++ b/src/core/agent/services/connectionService.types.ts @@ -19,6 +19,7 @@ enum ConnectionHistoryType { CREDENTIAL_REQUEST_PRESENT, CREDENTIAL_REVOKED, CREDENTIAL_PRESENTED, + CREDENTIAL_ADMITTED, } export { ConnectionHistoryType, KeriaContactKeyPrefix }; diff --git a/src/core/agent/services/ipexCommunicationService.test.ts b/src/core/agent/services/ipexCommunicationService.test.ts index 98decdde6..91b2f082f 100644 --- a/src/core/agent/services/ipexCommunicationService.test.ts +++ b/src/core/agent/services/ipexCommunicationService.test.ts @@ -34,6 +34,7 @@ import { ipexSubmitAdmitEnd, credentialStateIssued, credentialStateRevoked, + admitForIssuanceExnMessage, } from "../../__fixtures__/agent/ipexCommunicationFixtures"; import { NotificationRoute } from "../agent.types"; import { @@ -2124,6 +2125,31 @@ describe("IPEX communication service of agent", () => { expect(connections.resolveOobi).toBeCalledTimes(1); }); + test("Can create linked ipex message record with history type is credential admitted", async () => { + schemaGetMock.mockResolvedValueOnce(QVISchema); + getExchangeMock.mockResolvedValueOnce(admitForIssuanceExnMessage); + await ipexCommunicationService.createLinkedIpexMessageRecord( + admitForIssuanceExnMessage, + ConnectionHistoryType.CREDENTIAL_ADMITTED + ); + + expect(updateContactMock).toBeCalledWith( + admitForIssuanceExnMessage.exn.rp, + { + [`${KeriaContactKeyPrefix.HISTORY_IPEX}${admitForIssuanceExnMessage.exn.d}`]: + JSON.stringify({ + id: admitForIssuanceExnMessage.exn.d, + dt: admitForIssuanceExnMessage.exn.dt, + credentialType: QVISchema.title, + connectionId: admitForIssuanceExnMessage.exn.rp, + historyType: ConnectionHistoryType.CREDENTIAL_ADMITTED, + }), + } + ); + expect(schemaGetMock).toBeCalledTimes(1); + expect(connections.resolveOobi).toBeCalledTimes(1); + }); + test("Should throw error if history type invalid", async () => { schemaGetMock.mockResolvedValueOnce(QVISchema); getExchangeMock.mockResolvedValueOnce(agreeForPresentingExnMessage); diff --git a/src/core/agent/services/ipexCommunicationService.ts b/src/core/agent/services/ipexCommunicationService.ts index 13620230b..a4b6e559d 100644 --- a/src/core/agent/services/ipexCommunicationService.ts +++ b/src/core/agent/services/ipexCommunicationService.ts @@ -486,14 +486,18 @@ class IpexCommunicationService extends AgentService { ): Promise { let schemaSaid; const connectionId = - historyType === ConnectionHistoryType.CREDENTIAL_PRESENTED + historyType === ConnectionHistoryType.CREDENTIAL_PRESENTED || + historyType === ConnectionHistoryType.CREDENTIAL_ADMITTED ? message.exn.rp : message.exn.i; if (message.exn.r === ExchangeRoute.IpexGrant) { schemaSaid = message.exn.e.acdc.s; } else if (message.exn.r === ExchangeRoute.IpexApply) { schemaSaid = message.exn.a.s; - } else if (message.exn.r === ExchangeRoute.IpexAgree) { + } else if ( + message.exn.r === ExchangeRoute.IpexAgree || + message.exn.r === ExchangeRoute.IpexAdmit + ) { const previousExchange = await this.props.signifyClient .exchanges() .get(message.exn.p); @@ -515,6 +519,7 @@ class IpexCommunicationService extends AgentService { case ConnectionHistoryType.CREDENTIAL_ISSUANCE: case ConnectionHistoryType.CREDENTIAL_REQUEST_PRESENT: case ConnectionHistoryType.CREDENTIAL_PRESENTED: + case ConnectionHistoryType.CREDENTIAL_ADMITTED: prefix = KeriaContactKeyPrefix.HISTORY_IPEX; key = message.exn.d; break; diff --git a/src/core/agent/services/keriaNotificationService.test.ts b/src/core/agent/services/keriaNotificationService.test.ts index 667e8d285..462eb1ff8 100644 --- a/src/core/agent/services/keriaNotificationService.test.ts +++ b/src/core/agent/services/keriaNotificationService.test.ts @@ -2341,6 +2341,15 @@ describe("Long running operation tracker", () => { credentialIdMock, CredentialStatus.CONFIRMED ); + expect(ipexCommunications.createLinkedIpexMessageRecord).toBeCalledWith( + { + exn: { + r: ExchangeRoute.IpexAdmit, + p: "p", + }, + }, + ConnectionHistoryType.CREDENTIAL_ADMITTED + ); expect(notificationStorage.save).not.toBeCalled(); expect(operationPendingStorage.deleteById).toBeCalledTimes(1); }); diff --git a/src/core/agent/services/keriaNotificationService.ts b/src/core/agent/services/keriaNotificationService.ts index ad52f4b07..bb5a3c48a 100644 --- a/src/core/agent/services/keriaNotificationService.ts +++ b/src/core/agent/services/keriaNotificationService.ts @@ -1072,6 +1072,12 @@ class KeriaNotificationService extends AgentService { }); } } + + await this.ipexCommunications.createLinkedIpexMessageRecord( + admitExchange, + ConnectionHistoryType.CREDENTIAL_ADMITTED + ); + await this.credentialService.markAcdc( credentialId, CredentialStatus.CONFIRMED diff --git a/src/locales/en/en.json b/src/locales/en/en.json index c1ee3c7b5..724429807 100644 --- a/src/locales/en/en.json +++ b/src/locales/en/en.json @@ -1458,6 +1458,7 @@ "history": "Connection History", "connectedwith": "Connected with “{{ issuer }}“", "issuance": "Received a “{{ credential }}“", + "admitted": "Admitted “{{ credential }}“ credential", "requestpresent": "“{{ issuer }}“ has requested a credential presentation", "update": "“{{ credential }}“ has been revoked", "presented": "Presented “{{ credentialType }}“ credential", diff --git a/src/ui/pages/ConnectionDetails/components/ConnectionHistoryEvent.tsx b/src/ui/pages/ConnectionDetails/components/ConnectionHistoryEvent.tsx index ff2a0cba2..5538e0212 100644 --- a/src/ui/pages/ConnectionDetails/components/ConnectionHistoryEvent.tsx +++ b/src/ui/pages/ConnectionDetails/components/ConnectionHistoryEvent.tsx @@ -54,6 +54,14 @@ const ConnectionHistoryEvent = ({ .replace(/(\d)/g, "$1") .replace(/ {2,}/g, " "), })} + {historyItem.type === ConnectionHistoryType.CREDENTIAL_ADMITTED && + i18next.t("connections.details.admitted", { + credential: historyItem.credentialType + ?.replace(/([A-Z][a-z])/g, " $1") + .replace(/^ /, "") + .replace(/(\d)/g, "$1") + .replace(/ {2,}/g, " "), + })} {historyItem.type === ConnectionHistoryType.CREDENTIAL_REQUEST_PRESENT && i18next.t("connections.details.requestpresent", {