Skip to content

Commit

Permalink
MessagesGet gets a single message along with the its data
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed May 30, 2024
1 parent 40567c9 commit b225850
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 209 deletions.
8 changes: 2 additions & 6 deletions json-schemas/interface-methods/messages-get.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@
"messageTimestamp": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/date-time"
},
"messageCids": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
"messageCid": {
"type": "string"
}
}
}
Expand Down
57 changes: 23 additions & 34 deletions src/handlers/messages-get.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type { DataEncodedRecordsWriteMessage } from '../types/records-types.js';
import type { DataStore } from '../types/data-store.js';
import type { DidResolver } from '@web5/dids';
import type { MessageStore } from '../types/message-store.js';
import type { MethodHandler } from '../types/method-handler.js';
import type { RecordsQueryReplyEntry } from '../types/records-types.js';
import type { MessagesGetMessage, MessagesGetReply, MessagesGetReplyEntry } from '../types/messages-types.js';

import { DataStream } from '../utils/data-stream.js';
import { Encoder } from '../utils/encoder.js';
import { messageReplyFromError } from '../core/message-reply.js';
import { MessagesGet } from '../interfaces/messages-get.js';
import { Records } from '../utils/records.js';
import { authenticate, authorizeOwner } from '../core/auth.js';
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';

type HandleArgs = { tenant: string, message: MessagesGetMessage };

Expand All @@ -31,50 +33,37 @@ export class MessagesGetHandler implements MethodHandler {
return messageReplyFromError(e, 401);
}

const promises: Promise<MessagesGetReplyEntry>[] = [];
const messageCids = new Set(message.descriptor.messageCids);
const messageResult = await this.messageStore.get(tenant, message.descriptor.messageCid);

for (const messageCid of messageCids) {
const promise = this.messageStore.get(tenant, messageCid)
.then(message => {
return { messageCid, message };
})
.catch(_ => {
return { messageCid, message: undefined, error: `Failed to get message ${messageCid}` };
});

promises.push(promise);
if (messageResult === undefined) {
return { status: { code: 404, detail: 'Not Found' } };
}

const messages = await Promise.all(promises);

// for every message, include associated data as `encodedData` IF:
// Include associated data as `encodedData` IF:
// * its a RecordsWrite
// * the data size is equal or smaller than the size threshold
for (const entry of messages) {
const { message } = entry;

if (!message) {
continue;
}

const { interface: messageInterface, method } = message.descriptor;
if (messageInterface !== DwnInterfaceName.Records || method !== DwnMethodName.Write) {
continue;
}

// * `encodedData` exists which means the data size is equal or smaller than the size threshold
const entry: MessagesGetReplyEntry = { message: messageResult, messageCid: message.descriptor.messageCid };
if (entry.message && Records.isRecordsWrite(messageResult)) {
const recordsWrite = entry.message as DataEncodedRecordsWriteMessage;
// RecordsWrite specific handling, if MessageStore has embedded `encodedData` return it with the entry.
// we store `encodedData` along with the message if the data is below a certain threshold.
const recordsWrite = message as RecordsQueryReplyEntry;
if (recordsWrite.encodedData !== undefined) {
entry.encodedData = recordsWrite.encodedData;
const dataBytes = Encoder.base64UrlToBytes(recordsWrite.encodedData);
entry.message.data = DataStream.fromBytes(dataBytes);
delete recordsWrite.encodedData;
} else {
const result = await this.dataStore.get(tenant, recordsWrite.recordId, recordsWrite.descriptor.dataCid);
if (result?.dataStream !== undefined) {
entry.message.data = result.dataStream;
} else {
delete entry.message.data; // if there is no data, return with the data property undefined
}
}
}

return {
status : { code: 200, detail: 'OK' },
entries : messages
status: { code: 200, detail: 'OK' },
entry
};
}
}
24 changes: 11 additions & 13 deletions src/interfaces/messages-get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';

export type MessagesGetOptions = {
messageCids: string[];
messageCid: string;
signer: Signer;
messageTimestamp?: string;
};

export class MessagesGet extends AbstractMessage<MessagesGetMessage> {
public static async parse(message: MessagesGetMessage): Promise<MessagesGet> {
Message.validateJsonSchema(message);
this.validateMessageCids(message.descriptor.messageCids);
this.validateMessageCid(message.descriptor.messageCid);

await Message.validateSignatureStructure(message.authorization.signature, message.descriptor);
Time.validateTimestamp(message.descriptor.messageTimestamp);
Expand All @@ -29,31 +29,29 @@ export class MessagesGet extends AbstractMessage<MessagesGetMessage> {
const descriptor: MessagesGetDescriptor = {
interface : DwnInterfaceName.Messages,
method : DwnMethodName.Get,
messageCids : options.messageCids,
messageCid : options.messageCid,
messageTimestamp : options?.messageTimestamp ?? Time.getCurrentTimestamp(),
};

const authorization = await Message.createAuthorization({ descriptor, signer: options.signer });
const message = { descriptor, authorization };

Message.validateJsonSchema(message);
MessagesGet.validateMessageCids(options.messageCids);
MessagesGet.validateMessageCid(options.messageCid);

return new MessagesGet(message);
}

/**
* validates the provided cids
* @param messageCids - the cids in question
* validates the provided cid
* @param messageCid - the cid in question
* @throws {DwnError} if an invalid cid is found.
*/
private static validateMessageCids(messageCids: string[]): void {
for (const cid of messageCids) {
try {
Cid.parseCid(cid);
} catch (_) {
throw new DwnError(DwnErrorCode.MessageGetInvalidCid, `${cid} is not a valid CID`);
}
private static validateMessageCid(messageCid: string): void {
try {
Cid.parseCid(messageCid);
} catch (_) {
throw new DwnError(DwnErrorCode.MessageGetInvalidCid, `${messageCid} is not a valid CID`);
}
}
}
8 changes: 4 additions & 4 deletions src/types/messages-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Readable } from 'readable-stream';
import type { AuthorizationModel, GenericMessage, GenericMessageReply } from './message-types.js';
import type { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';

export type MessagesGetDescriptor = {
interface : DwnInterfaceName.Messages;
method: DwnMethodName.Get;
messageCids: string[];
messageCid: string;
messageTimestamp: string;
};

Expand All @@ -15,11 +16,10 @@ export type MessagesGetMessage = GenericMessage & {

export type MessagesGetReplyEntry = {
messageCid: string;
message?: GenericMessage;
encodedData?: string;
message?: (GenericMessage & { data?: Readable });
error?: string;
};

export type MessagesGetReply = GenericMessageReply & {
entries?: MessagesGetReplyEntry[];
entry?: MessagesGetReplyEntry;
};
Loading

0 comments on commit b225850

Please sign in to comment.