Skip to content

Commit

Permalink
feat(core): linking of ipex flows to contacts (#422)
Browse files Browse the repository at this point in the history
* feat: rework some API in cred server

* feat: add apply schema API

* refactor: function and var name and dependency link

* feat: add full ipex flow for idw and cred server

* refactor: testing and some missing logic

* feature: save linked IPEX messages for connections

* refactor: change the file contruction

* feat: linking of IPEX flows to contacts

* fix: update unit tests

* update: add isUpdate to linkedIpexMessage record

* update: add credentialType to linkedIpexMessageRecord

* update: get linkedIpexRecord from exchange message

* update: get linkedIpexRecord id from exchange message SAID

* feat: resolve schema if we fail to get the schema

* feat: handle for apply and agree routes

---------

Co-authored-by: Bao Hoang <bao.hoanga@sotatek.com>
Co-authored-by: Martin Nguyen <tung.nguyen2a@sotatek.com>
  • Loading branch information
3 people committed Jul 31, 2024
1 parent 788167c commit eb1932f
Show file tree
Hide file tree
Showing 14 changed files with 818 additions and 66 deletions.
11 changes: 10 additions & 1 deletion src/core/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
PeerConnectionStorage,
NotificationRecord,
NotificationStorage,
IpexMessageStorage,
IpexMessageRecord,
} from "./records";
import { KeyStoreKeys, SecureStorage } from "../storage";
import { MultiSigService } from "./services/multiSigService";
Expand Down Expand Up @@ -75,6 +77,7 @@ class Agent {
private connectionNoteStorage!: ConnectionNoteStorage;
private notificationStorage!: NotificationStorage;
private peerConnectionStorage!: PeerConnectionStorage;
private ipexMessageStorage!: IpexMessageStorage;
private operationPendingStorage!: OperationPendingStorage;

private signifyClient!: SignifyClient;
Expand Down Expand Up @@ -120,6 +123,7 @@ class Agent {
this.identifierStorage,
this.credentialStorage,
this.notificationStorage,
this.ipexMessageStorage,
this.operationPendingStorage
);
}
Expand All @@ -133,6 +137,7 @@ class Agent {
this.connectionStorage,
this.connectionNoteStorage,
this.credentialStorage,
this.ipexMessageStorage,
this.operationPendingStorage
);
}
Expand Down Expand Up @@ -165,7 +170,8 @@ class Agent {
this.notificationStorage,
this.identifierStorage,
this.operationPendingStorage,
this.connectionStorage
this.connectionStorage,
this.ipexMessageStorage
);
}
return this.signifyNotificationService;
Expand Down Expand Up @@ -339,6 +345,9 @@ class Agent {
this.operationPendingStorage = new OperationPendingStorage(
this.getStorageService<OperationPendingRecord>(this.storageSession)
);
this.ipexMessageStorage = new IpexMessageStorage(
this.getStorageService<IpexMessageRecord>(this.storageSession)
);
this.agentServicesProps = {
signifyClient: this.signifyClient,
eventService: new EventService(),
Expand Down
40 changes: 40 additions & 0 deletions src/core/agent/agent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,41 @@ type ConnectionNoteDetails = {
message: string;
};

interface JSONObject {
[x: string]: JSONValue;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface JSONArray extends Array<JSONValue> {}

type JSONValue = string | number | boolean | JSONObject | JSONArray;

type IpexMessage = {
exn: {
v: string;
t: string;
d: string;
i: string;
p: string;
dt: string;
r: string;
q: JSONValue;
a: any;
e: any;
};
pathed: {
acdc: string;
iss: string;
anc: string;
};
};

type IpexMessageDetails = {
id: string;
content: IpexMessage;
createdAt: Date;
};

type ConnectionNoteProps = Pick<ConnectionNoteDetails, "title" | "message">;

interface ConnectionDetails extends ConnectionShortDetails {
Expand Down Expand Up @@ -185,6 +220,9 @@ enum NotificationRoute {

enum ExchangeRoute {
IpexAdmit = "/ipex/admit",
IpexGrant = "/ipex/grant",
IpexApply = "/ipex/apply",
IpexAgree = "/ipex/agree",
}

interface BranAndMnemonic {
Expand Down Expand Up @@ -221,6 +259,8 @@ export type {
KeriaStatusChangedEvent,
AgentUrls,
BranAndMnemonic,
IpexMessage,
IpexMessageDetails,
NotificationRpy,
AuthorizationRequestExn,
};
10 changes: 0 additions & 10 deletions src/core/agent/records/credentialStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,6 @@ class CredentialStorage {
return record;
}

async getCredentialMetadataByConnectionId(connectionId: string) {
const record = await this.storageService.findAllByQuery(
{
connectionId,
},
CredentialMetadataRecord
);
return record;
}

async saveCredentialMetadataRecord(data: CredentialMetadataRecordProps) {
const record = new CredentialMetadataRecord(data);
return this.storageService.save(record);
Expand Down
2 changes: 2 additions & 0 deletions src/core/agent/records/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ export * from "./connectionStorage";
export * from "./connectionNoteStorage";
export * from "./notificationStorage";
export * from "./operationPendingStorage";
export * from "./ipexMessageRecord";
export * from "./ipexMessageStorage";
45 changes: 45 additions & 0 deletions src/core/agent/records/ipexMessageRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { BaseRecord } from "../../storage/storage.types";
import { IpexMessage } from "../agent.types";
import { ConnectionHistoryType } from "../services/connection.types";

interface IpexMessageProps {
id: string;
credentialType: string;
content: IpexMessage;
historyType: ConnectionHistoryType;
createdAt?: Date;
connectionId: string;
}

class IpexMessageRecord extends BaseRecord {
credentialType!: string;
content!: IpexMessage;
connectionId!: string;
historyType!: ConnectionHistoryType;

static readonly type = "IpexMessageRecord";
readonly type = IpexMessageRecord.type;

constructor(props: IpexMessageProps) {
super();

if (props) {
this.id = props.id;
this.credentialType = props.credentialType;
this.content = props.content;
this.connectionId = props.connectionId;
this.historyType = props.historyType;
this.createdAt = props.createdAt ?? new Date();
}
}

getTags() {
return {
...this._tags,
connectionId: this.connectionId,
};
}
}

export { IpexMessageRecord };
export type { IpexMessageProps };
97 changes: 97 additions & 0 deletions src/core/agent/records/ipexMessageStorage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { StorageService } from "../../storage/storage.types";
import { ConnectionHistoryType } from "../services/connection.types";
import { IpexMessageRecord, IpexMessageProps } from "./ipexMessageRecord";
import { IpexMessageStorage } from "./ipexMessageStorage";

const storageService = jest.mocked<StorageService<IpexMessageRecord>>({
save: jest.fn(),
delete: jest.fn(),
deleteById: jest.fn(),
update: jest.fn(),
findById: jest.fn(),
findAllByQuery: jest.fn(),
getAll: jest.fn(),
});

const ipexMessageStorage = new IpexMessageStorage(storageService);

const id1 = "id1";
const id2 = "id2";

const now = new Date();
const ipexMessage = {
exn: {
v: "string",
d: "string",
t: "string",
i: "string",
p: "string",
dt: "string",
r: "string",
q: {},
a: {},
e: {},
},
pathed: {
acdc: "string",
iss: "string",
anc: "string",
},
};
const ipexMessageRecordProps: IpexMessageProps = {
id: id1,
credentialType: "IIW 2024 Demo Day Attendee",
createdAt: now,
connectionId: "connectionId",
content: ipexMessage,
historyType: ConnectionHistoryType.CREDENTIAL_ISSUANCE,
};

const ipexMessageRecordA = new IpexMessageRecord(ipexMessageRecordProps);

const ipexMessageRecordB = new IpexMessageRecord({
...ipexMessageRecordProps,
id: id2,
historyType: ConnectionHistoryType.CREDENTIAL_UPDATE,
});

describe("ipexMessage Storage", () => {
beforeEach(() => {
jest.resetAllMocks();
});

test("Should save ipexMessage record", async () => {
storageService.save.mockResolvedValue(ipexMessageRecordA);
await ipexMessageStorage.createIpexMessageRecord(ipexMessageRecordProps);
expect(storageService.save).toBeCalledWith(ipexMessageRecordA);
});

test("Should find ipexMessage record by id", async () => {
storageService.findById.mockResolvedValue(ipexMessageRecordB);
const result = await ipexMessageStorage.getIpexMessageMetadata(
ipexMessageRecordB.connectionId
);
expect(result).toEqual(ipexMessageRecordB);
});

test("Should throw error if there is no matching record", async () => {
storageService.findById.mockResolvedValue(null);
await expect(
ipexMessageStorage.getIpexMessageMetadata("not-found-id")
).rejects.toThrowError(
IpexMessageStorage.IPEX_MESSAGE_METADATA_RECORD_MISSING
);
});

test("Should find ipexMessage record by connectionId", async () => {
storageService.findAllByQuery.mockResolvedValue([
ipexMessageRecordA,
ipexMessageRecordB,
]);
const result =
await ipexMessageStorage.getIpexMessageMetadataByConnectionId(
ipexMessageRecordA.connectionId
);
expect(result).toEqual([ipexMessageRecordA, ipexMessageRecordB]);
});
});
39 changes: 39 additions & 0 deletions src/core/agent/records/ipexMessageStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { StorageService } from "../../storage/storage.types";
import { IpexMessageProps, IpexMessageRecord } from "./ipexMessageRecord";

class IpexMessageStorage {
static readonly IPEX_MESSAGE_METADATA_RECORD_MISSING =
"Ipex message metadata record does not exist";
private storageService: StorageService<IpexMessageRecord>;

constructor(storageService: StorageService<IpexMessageRecord>) {
this.storageService = storageService;
}

async createIpexMessageRecord(data: IpexMessageProps): Promise<void> {
const record = new IpexMessageRecord(data);
await this.storageService.save(record);
}

async getIpexMessageMetadata(id: string): Promise<IpexMessageRecord> {
const metadata = await this.storageService.findById(id, IpexMessageRecord);
if (!metadata) {
throw new Error(IpexMessageStorage.IPEX_MESSAGE_METADATA_RECORD_MISSING);
}
return metadata;
}

async getIpexMessageMetadataByConnectionId(
connectionId: string
): Promise<IpexMessageRecord[]> {
const records = await this.storageService.findAllByQuery(
{
connectionId,
},
IpexMessageRecord
);
return records;
}
}

export { IpexMessageStorage };
5 changes: 4 additions & 1 deletion src/core/agent/services/connection.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ interface KeriaContact {
}

enum ConnectionHistoryType {
CREDENTIAL_ACCEPTED,
CREDENTIAL_ISSUANCE,
CREDENTIAL_REQUEST_PRESENT,
CREDENTIAL_REQUEST_AGREE,
CREDENTIAL_UPDATE,
}

export { ConnectionHistoryType };
Expand Down
Loading

0 comments on commit eb1932f

Please sign in to comment.