@dwn-protocol/id-sdk
Version:
SDK for accessing the features and capabilities
296 lines (243 loc) • 10.2 kB
text/typescript
//@ts-nocheck
import type { RecordsWriteMessage } from '@dwn-protocol/id';
import { Convert } from '../common/index.js';
import type { IDManagedAgent } from './types/agent.js';
import type { ManagedDid } from './did-manager.js';
export interface ManagedDidStore {
deleteDid(options: { did: string, agent?: IDManagedAgent, context?: string }): Promise<boolean>
getDid(options: { did: string, agent?: IDManagedAgent, context?: string }): Promise<ManagedDid | undefined>
findDid(options: { did: string, agent?: IDManagedAgent, context?: string }): Promise<ManagedDid | undefined>
findDid(options: { alias: string, agent?: IDManagedAgent, context?: string }): Promise<ManagedDid | undefined>
importDid(options: { did: ManagedDid, agent?: IDManagedAgent, context?: string }): Promise<void>
listDids(options?: { agent?: IDManagedAgent, context?: string }): Promise<ManagedDid[]>
}
/**
*
*/
export class DidStoreDwn implements ManagedDidStore {
private _didRecordProperties = {
dataFormat : 'application/json',
schema : 'https://identity.foundation/schemas/dwn/managed-did'
};
async deleteDid(options: {
agent: IDManagedAgent,
context?: string,
did: string
}): Promise<boolean> {
const { agent, context, did } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did });
// Query the DWN for all stored DID objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : {
filter: { ...this._didRecordProperties }
}
});
// Loop through all of the entries and try to find a match.
let matchingRecordId: string | undefined;
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid;
if (storedDid && storedDid.did === did) {
matchingRecordId = (record as RecordsWriteMessage).recordId ;
break;
}
}
}
// Return undefined if the specified DID was not found in the store.
if (!matchingRecordId) return false;
// If a record for the specified DID was found, attempt to delete it.
const { reply: { status } } = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsDelete',
messageOptions : {
recordId: matchingRecordId
}
});
// If the DID was successfully deleted, return true;
if (status.code === 202) return true;
// If the DID could not be deleted, return false;
return false;
}
async findDid(options: { agent: IDManagedAgent, context?: string, did: string }): Promise<ManagedDid | undefined>;
async findDid(options: { agent: IDManagedAgent, context?: string, alias: string }): Promise<ManagedDid | undefined>;
async findDid(options: { agent: IDManagedAgent, alias: string, context?: string, did: string }): Promise<ManagedDid | undefined> {
const { agent, alias, context, did } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did });
// Query the DWN for all stored DID objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : {
filter: { ...this._didRecordProperties }
}
});
// Loop through all of the entries and return a match, if found.
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid;
if (storedDid && storedDid.did === did) return storedDid;
if (storedDid && storedDid.alias === alias) return storedDid;
}
}
// Return undefined if no matches were found.
return undefined;
}
async getDid(options: {
agent: IDManagedAgent,
context?: string,
did: string
}): Promise<ManagedDid | undefined> {
const { agent, context, did } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did });
// Query the DWN for all stored DID objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : { filter: { ...this._didRecordProperties } }
});
// Loop through all of the entries and return a match, if found.
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid;
if (storedDid && storedDid.did === did) return storedDid;
}
}
// Return undefined if no matches were found.
return undefined;
}
async importDid(options: {
agent: IDManagedAgent,
context?: string,
did: ManagedDid
}) {
const { agent, context, did: importDid } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context, did: importDid.did });
// Check if the DID being imported is already present in the store.
const duplicateFound = await this.getDid({ agent, context, did: importDid.did });
if (duplicateFound) {
throw new Error(`DidStoreDwn: DID with ID already exists: '${importDid.did}'`);
}
// Encode the ManagedDid as bytes.
const importDidU8A = Convert.object(importDid).toUint8Array();
const { reply: { status } } = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsWrite',
messageOptions : { ...this._didRecordProperties },
dataStream : new Blob([importDidU8A])
});
// If the write fails, throw an error.
if (status.code !== 202) {
throw new Error('DidStoreDwn: Failed to write imported DID to store.');
}
}
async listDids(options: {
agent: IDManagedAgent,
context?: string
}): Promise<ManagedDid[]> {
const { agent, context } = options;
// Determine which DID to use to author DWN messages.
const authorDid = await this.getAuthor({ agent, context });
// Query the DWN for all stored DID objects.
const { reply: queryReply} = await agent.dwnManager.processRequest({
author : authorDid,
target : authorDid,
messageType : 'RecordsQuery',
messageOptions : {
filter: { ...this._didRecordProperties }
}
});
// Loop through all of the entries and accumulate the DID objects.
let storedDids: ManagedDid[] = [];
for (const record of queryReply.entries ?? []) {
if (record.encodedData) {
const storedDid = Convert.base64Url(record.encodedData).toObject() as ManagedDid;
storedDids.push(storedDid);
}
}
return storedDids;
}
private async getAuthor(options: {
context?: string,
did?: string,
agent: IDManagedAgent
}): Promise<string> {
const { context, did, agent } = options;
// If `context` is specified, DWN messages will be signed by this DID.
if (context) return context;
// If Agent has an agentDid, use it to sign DWN messages.
if (agent.agentDid) return agent.agentDid;
// If `context`, `agent.agentDid`, and `did` are undefined, throw error.
if (!did) {
throw new Error(`DidStoreDwn: Agent property 'agentDid' is undefined.`);
}
/** Lacking a context and agentDid DID, check whether KeyManager has
* a key pair for the given `did` value.*/
const signingKeyId = await agent.didManager.getDefaultSigningKey({ did });
const keyPair = (signingKeyId)
? await agent.keyManager.getKey({ keyRef: signingKeyId })
: undefined;
// If a key pair is found, use the `did` to sign messages.
if (keyPair) return did;
// If all else fails, throw an error.
throw new Error(`DidStoreDwn: Agent property 'agentDid' is undefined and no keys were found for: '${did}'`);
}
}
/**
*
*/
export class DidStoreMemory implements ManagedDidStore {
/**
* A private field that contains the Map used as the in-memory key-value store.
*/
private store: Map<string, ManagedDid> = new Map();
async deleteDid({ did }: { did: string; }): Promise<boolean> {
if (this.store.has(did)) {
// DID with given identifier exists so proceed with delete.
this.store.delete(did);
return true;
}
// DID with given identifier not present so delete operation not possible.
return false;
}
async getDid({ did }: { did: string; }): Promise<ManagedDid | undefined> {
return this.store.get(did);
}
async findDid(options: { did: string }): Promise<ManagedDid | undefined>;
async findDid(options: { alias: string }): Promise<ManagedDid | undefined>;
async findDid(options: { alias?: string, did?: string}): Promise<ManagedDid | undefined> {
let { alias, did } = options;
// Get DID by identifier.
if (did) return this.store.get(did);
if (alias) {
// Search through the store to find a matching entry
for (const did of this.store.values()) {
if (did.alias === alias) return did;
}
}
return undefined;
}
async importDid(options: { did: ManagedDid }) {
const { did: importDid } = options;
if (this.store.has(importDid.did)) {
// DID with given identifier already exists so import operation cannot proceed.
throw new Error(`DidStoreMemory: DID with ID already exists: '${importDid.did}'`);
}
// Make a deep copy of the DID so that the object stored does not share the same references as the input.
const clonedDid = structuredClone(importDid);
this.store.set(importDid.did, clonedDid);
}
async listDids(): Promise<ManagedDid[]> {
return Array.from(this.store.values());
}
}