UNPKG

@veramo/did-comm

Version:

Veramo messaging plugin implementing DIDComm v2.

252 lines 10.8 kB
import { AbstractMessageHandler } from '@veramo/message-handler'; import Debug from 'debug'; import { v4 } from 'uuid'; import { QUEUE_MESSAGE_TYPE } from './routing-message-handler.js'; import { DIDCommMessageMediaType } from '../types/message-types.js'; const debug = Debug('veramo:did-comm:messagepickup-message-handler'); export const STATUS_REQUEST_MESSAGE_TYPE = 'https://didcomm.org/messagepickup/3.0/status-request'; export const STATUS_MESSAGE_TYPE = 'https://didcomm.org/messagepickup/3.0/status'; export const DELIVERY_REQUEST_MESSAGE_TYPE = 'https://didcomm.org/messagepickup/3.0/delivery-request'; export const DELIVERY_MESSAGE_TYPE = 'https://didcomm.org/messagepickup/3.0/delivery'; export const MESSAGES_RECEIVED_MESSAGE_TYPE = 'https://didcomm.org/messagepickup/3.0/messages-received'; function generateGetMessagesWhereQuery(from, recipientKey) { return [ { column: 'type', value: [QUEUE_MESSAGE_TYPE], op: 'In', }, recipientKey ? { column: 'to', value: [recipientKey], op: 'In', } : { column: 'to', value: [`${from}%`], op: 'Like', }, ]; } /** * A plugin for the {@link @veramo/message-handler#MessageHandler} that handles Pickup messages for the mediator role. * @beta This API may change without a BREAKING CHANGE notice. */ export class PickupMediatorMessageHandler extends AbstractMessageHandler { constructor() { super(); } /** * Handles messages for Pickup protocol and mediator role * https://didcomm.org/pickup/3.0/ */ async handle(message, context) { if (message.type === STATUS_REQUEST_MESSAGE_TYPE) { debug('Status Request Message Received'); try { await this.replyWithStatusMessage(message, context); } catch (ex) { debug(ex); } return message; } else if (message.type === DELIVERY_REQUEST_MESSAGE_TYPE) { debug('Delivery Request Message Received'); try { const { returnRoute, data, from, to } = message; if (!to) { throw new Error('invalid_argument: DeliveryRequest received without `to` set'); } if (!from) { throw new Error('invalid_argument: DeliveryRequest received without `from` set'); } if (!data.limit || Number.isNaN(data.limit)) { throw new Error('invalid_argument: DeliveryRequest received without `body.limit` set'); } if (returnRoute === 'all') { const queuedMessages = await context.agent.dataStoreORMGetMessages({ where: generateGetMessagesWhereQuery(from, data.recipient_key), take: data.limit, }); if (queuedMessages.length == 0) { await this.replyWithStatusMessage(message, context); return message; } const attachments = queuedMessages.map((message) => { return { id: message.id, media_type: DIDCommMessageMediaType.ENCRYPTED, data: { json: JSON.parse(message.raw), }, }; }); const replyRecipientKey = data.recipient_key ? { recipient_key: data.recipient_key } : {}; const replyMessage = { type: DELIVERY_MESSAGE_TYPE, from: to, to: [from], id: v4(), thid: message.threadId ?? message.id, created_time: new Date().toISOString(), body: { ...replyRecipientKey, }, attachments, }; const packedResponse = await context.agent.packDIDCommMessage({ message: replyMessage, packing: 'authcrypt', }); const returnResponse = { id: replyMessage.id, message: packedResponse.message, contentType: DIDCommMessageMediaType.ENCRYPTED, }; message.addMetaData({ type: 'ReturnRouteResponse', value: JSON.stringify(returnResponse) }); } else { throw new Error('No return_route found for DeliveryRequest'); } } catch (ex) { debug(ex); } return message; } else if (message.type === MESSAGES_RECEIVED_MESSAGE_TYPE) { debug('MessagesReceived Message Received'); try { const { data, from } = message; if (!from) { throw new Error('invalid_argument: MessagesReceived received without `from` set'); } if (!data.message_id_list || !Array.isArray(data.message_id_list)) { throw new Error('invalid_argument: MessagesReceived received without `body.message_id_list` set'); } await Promise.all(data.message_id_list.map(async (messageId) => { const message = await context.agent.dataStoreGetMessage({ id: messageId }); // Delete message if meant for recipient if (message.to?.startsWith(`${from}#`)) { await context.agent.dataStoreDeleteMessage({ id: messageId }); context.agent.emit('DIDCommV2Message-forwardMessageDequeued', messageId); } })); await this.replyWithStatusMessage(message, context); } catch (ex) { debug(ex); } } return super.handle(message, context); } async replyWithStatusMessage(message, context) { const { returnRoute, data, from, to } = message; if (!to) { throw new Error('invalid_argument: StatusRequest received without `to` set'); } if (!from) { throw new Error('invalid_argument: StatusRequest received without `from` set'); } if (returnRoute === 'all') { const queuedMessageCount = await context.agent.dataStoreORMGetMessagesCount({ where: generateGetMessagesWhereQuery(from, data.recipient_key), }); const replyRecipientKey = data.recipient_key ? { recipient_key: data.recipient_key } : {}; const replyMessage = { type: STATUS_MESSAGE_TYPE, from: to, to: [from], id: v4(), thid: message.threadId ?? message.id, created_time: new Date().toISOString(), body: { message_count: queuedMessageCount, live_delivery: false, ...replyRecipientKey, }, }; const packedResponse = await context.agent.packDIDCommMessage({ message: replyMessage, packing: 'authcrypt', }); const returnResponse = { id: replyMessage.id, message: packedResponse.message, contentType: DIDCommMessageMediaType.ENCRYPTED, }; message.addMetaData({ type: 'ReturnRouteResponse', value: JSON.stringify(returnResponse) }); } else { throw new Error('No return_route found for StatusRequest'); } } } /** * A plugin for the {@link @veramo/message-handler#MessageHandler} that handles Pickup messages for the mediator role. * @beta This API may change without a BREAKING CHANGE notice. */ export class PickupRecipientMessageHandler extends AbstractMessageHandler { constructor() { super(); } /** * Handles messages for Pickup protocol and recipient role * https://didcomm.org/pickup/3.0/ */ async handle(message, context) { if (message.type === DELIVERY_MESSAGE_TYPE) { debug('Message Delivery batch Received'); try { const { attachments, to, from } = message; if (!to) { throw new Error('invalid_argument: StatusRequest received without `to` set'); } if (!from) { throw new Error('invalid_argument: StatusRequest received without `from` set'); } if (!attachments) { throw new Error('invalid_argument: MessagesDelivery received without `attachments` set'); } // 1. Handle batch of messages const messageIds = await Promise.all(attachments.map(async (attachment) => { await context.agent.handleMessage({ raw: JSON.stringify(attachment.data.json), metaData: [{ type: 'didCommMsgFromMediator', value: attachment.id }], }); return attachment.id; })); // 2. Reply with messages-received const replyMessage = { type: MESSAGES_RECEIVED_MESSAGE_TYPE, from: to, to: [from], id: v4(), thid: message.threadId ?? message.id, created_time: new Date().toISOString(), return_route: 'all', body: { message_id_list: messageIds, }, }; const packedResponse = await context.agent.packDIDCommMessage({ message: replyMessage, packing: 'authcrypt', }); await context.agent.sendDIDCommMessage({ packedMessage: packedResponse, messageId: replyMessage.id, recipientDidUrl: from, }); } catch (ex) { debug(ex); } return message; } return super.handle(message, context); } } //# sourceMappingURL=messagepickup-message-handler.js.map