Skip to content

Commit

Permalink
MessagesGet (#313)
Browse files Browse the repository at this point in the history
* implement `MessagesGet`
  • Loading branch information
mistermoe authored Apr 20, 2023
1 parent 149c713 commit 07bcddb
Show file tree
Hide file tree
Showing 16 changed files with 705 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK

Code Coverage
![Statements](https://img.shields.io/badge/statements-93.69%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.17%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.36%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-93.69%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-93.86%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.36%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.54%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-93.86%25-brightgreen.svg?style=flat)

## Introduction

Expand Down
4 changes: 3 additions & 1 deletion build/compile-validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import EventsGet from '../json-schemas/events/events-get.json' assert { type: 'j
import GeneralJwk from '../json-schemas/jwk/general-jwk.json' assert { type: 'json' };
import GeneralJws from '../json-schemas/general-jws.json' assert { type: 'json' };
import HooksWrite from '../json-schemas/hooks/hooks-write.json' assert { type: 'json' };
import JwkVerificationMethod from '../json-schemas/jwk-verification-method.json' assert {type: 'json'};
import JwkVerificationMethod from '../json-schemas/jwk-verification-method.json' assert { type: 'json' };
import MessagesGet from '../json-schemas/messages/messages-get.json' assert { type: 'json' };
import PermissionsDefinitions from '../json-schemas/permissions/definitions.json' assert { type: 'json' };
import PermissionsGrant from '../json-schemas/permissions/permissions-grant.json' assert { type: 'json' };
import PermissionsRequest from '../json-schemas/permissions/permissions-request.json' assert { type: 'json' };
Expand All @@ -46,6 +47,7 @@ const schemas = {
GeneralJws,
HooksWrite,
JwkVerificationMethod,
MessagesGet,
PermissionsDefinitions,
PermissionsGrant,
PermissionsRequest,
Expand Down
44 changes: 44 additions & 0 deletions json-schemas/messages/messages-get.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://identity.foundation/dwn/json-schemas/messages-get.json",
"type": "object",
"additionalProperties": false,
"required": [
"authorization",
"descriptor"
],
"properties": {
"authorization": {
"$ref": "https://identity.foundation/dwn/json-schemas/general-jws.json"
},
"descriptor": {
"type": "object",
"additionalProperties": false,
"required": [
"interface",
"method"
],
"properties": {
"interface": {
"enum": [
"Messages"
],
"type": "string"
},
"method": {
"enum": [
"Get"
],
"type": "string"
},
"messageCids": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1
}
}
}
}
}
18 changes: 15 additions & 3 deletions karma.conf.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
const playwright = require('playwright');
const esbuildBrowserConfig = require('./build/esbuild-browser-config.cjs');

// set playwright as run-target for webkit tests
// use playwright chrome exec path as run target for chromium tests
process.env.CHROME_BIN = playwright.chromium.executablePath();

// use playwright webkit exec path as run target for safari tests
process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath();

module.exports = function(config) {
// use playwright firefox exec path as run target for firefox tests
process.env.FIREFOX_BIN = playwright.firefox.executablePath();

/** @typedef {import('karma').Config} KarmaConfig */

/**
*
* @param {KarmaConfig} config
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
module.exports = function configure(config) {
config.set({
plugins: [
require('karma-chrome-launcher'),
Expand All @@ -23,7 +36,6 @@ module.exports = function(config) {
// available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter
frameworks: ['mocha'],


// list of files / patterns to load in the browser
files: [
{ pattern: 'tests/**/*.spec.ts', watched: false }
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,16 @@
"multiformats": "11.0.2",
"randombytes": "2.1.0",
"readable-stream": "4.3.0",
"ulid": "2.3.0",
"secp256k1": "5.0.0",
"ulid": "2.3.0",
"uuid": "8.3.2",
"varint": "6.0.0"
},
"devDependencies": {
"@types/chai": "4.3.0",
"@types/chai-as-promised": "7.1.5",
"@types/flat": "^5.0.2",
"@types/karma": "^6.3.3",
"@types/lodash": "4.14.179",
"@types/mocha": "9.1.0",
"@types/randombytes": "2.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/core/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { validateJsonSchema } from '../schema-validator.js';
export enum DwnInterfaceName {
Events = 'Events',
Hooks = 'Hooks',
Messages = 'Messages',
Permissions = 'Permissions',
Protocols = 'Protocols',
Records = 'Records'
Expand Down
11 changes: 11 additions & 0 deletions src/dwn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { MessageStore } from './store/message-store.js';
import type { MethodHandler } from './interfaces/types.js';
import type { Readable } from 'readable-stream';
import type { TenantGate } from './core/tenant-gate.js';
import type { MessagesGetMessage, MessagesGetReply } from './interfaces/messages/types.js';
import type { RecordsReadMessage, RecordsReadReply } from './interfaces/records/types.js';

import { AllowAllTenantGate } from './core/tenant-gate.js';
Expand All @@ -13,6 +14,7 @@ import { DidResolver } from './did/did-resolver.js';
import { EventLogLevel } from './event-log/event-log-level.js';
import { EventsGetHandler } from './interfaces/events/handlers/events-get.js';
import { MessageReply } from './core/message-reply.js';
import { MessagesGetHandler } from './interfaces/messages/handlers/messages-get.js';
import { MessageStoreLevel } from './store/message-store-level.js';
import { PermissionsRequestHandler } from './interfaces/permissions/handlers/permissions-request.js';
import { ProtocolsConfigureHandler } from './interfaces/protocols/handlers/protocols-configure.js';
Expand Down Expand Up @@ -40,6 +42,7 @@ export class Dwn {

this.methodHandlers = {
[DwnInterfaceName.Events + DwnMethodName.Get] : new EventsGetHandler(this.didResolver, this.eventLog),
[DwnInterfaceName.Messages + DwnMethodName.Get] : new MessagesGetHandler(this.didResolver, this.messageStore, this.dataStore),
[DwnInterfaceName.Permissions + DwnMethodName.Request] : new PermissionsRequestHandler(this.didResolver, this.messageStore, this.dataStore),
[DwnInterfaceName.Protocols + DwnMethodName.Configure] : new ProtocolsConfigureHandler(
this.didResolver, this.messageStore, this.dataStore, this.eventLog),
Expand Down Expand Up @@ -126,6 +129,14 @@ export class Dwn {
return reply as RecordsReadReply;
}

/**
* Handles a `MessagesGet` message.
*/
public async handleMessagesGet(tenant: string, message: MessagesGetMessage): Promise<MessagesGetReply> {
const reply = await this.processMessage(tenant, message);
return reply as MessagesGetReply;
}

public async dump(): Promise<void> {
console.group('didResolver');
await this.didResolver['dump']?.();
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type { DwnServiceEndpoint, ServiceEndpoint, DidDocument, DidResolutionRes
export type { EventLog, Event } from './event-log/event-log.js';
export type { EventsGetMessage, EventsGetReply } from './interfaces/events/types.js';
export type { HooksWriteMessage } from './interfaces/hooks/types.js';
export type { MessagesGetMessage, MessagesGetReply } from './interfaces/messages/types.js';
export type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage, ProtocolsQueryMessage } from './interfaces/protocols/types.js';
export type { RecordsDeleteMessage, RecordsQueryMessage, RecordsWriteMessage } from './interfaces/records/types.js';
export { AllowAllTenantGate, TenantGate } from './core/tenant-gate.js';
Expand All @@ -31,6 +32,7 @@ export { EncryptionInput, KeyEncryptionInput, RecordsWrite, RecordsWriteOptions,
export { HooksWrite, HooksWriteOptions } from './interfaces/hooks/messages/hooks-write.js';
export { Jws } from './utils/jws.js';
export { KeyMaterial, PrivateJwk, PublicJwk } from './jose/types.js';
export { MessagesGet, MessagesGetOptions } from './interfaces/messages/messages/messages-get.js';
export { MessageReply } from './core/message-reply.js';
export { MessageStore } from './store/message-store.js';
export { MessageStoreLevel } from './store/message-store-level.js';
Expand Down
88 changes: 88 additions & 0 deletions src/interfaces/messages/handlers/messages-get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { DataStore } from '../../../store/data-store.js';
import type { DidResolver } from '../../../did/did-resolver.js';
import type { MessageStore } from '../../../store/message-store.js';
import type { MethodHandler } from '../../types.js';
import type { MessagesGetMessage, MessagesGetReply, MessagesGetReplyEntry } from '../types.js';

import { DataStream } from '../../../utils/data-stream.js';
import { DwnConstant } from '../../../core/dwn-constant.js';
import { Encoder } from '../../../utils/encoder.js';
import { MessageReply } from '../../../core/message-reply.js';
import { MessagesGet } from '../messages/messages-get.js';
import { authenticate, authorize } from '../../../core/auth.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../../../core/message.js';

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

export class MessagesGetHandler implements MethodHandler {
constructor(private didResolver: DidResolver, private messageStore: MessageStore, private dataStore: DataStore) {}

public async handle({ tenant, message }: HandleArgs): Promise<MessagesGetReply> {
let messagesGet: MessagesGet;

try {
messagesGet = await MessagesGet.parse(message);
} catch (e) {
return MessageReply.fromError(e, 400);
}

try {
await authenticate(message.authorization, this.didResolver);
await authorize(tenant, messagesGet);
} catch (e) {
return MessageReply.fromError(e, 401);
}

const promises: Promise<MessagesGetReplyEntry>[] = [];
const messageCids = new Set(message.descriptor.messageCids);

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);
}

const messages = await Promise.all(promises);

// for every message, include associated data as `encodedData` IF:
// * its a RecordsWrite
// * the data size is equal or smaller than the size threshold
//! NOTE: this is somewhat duplicate code that also exists in `StorageController.query`.
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;
}

const dataCid = message.descriptor.dataCid;
const dataSize = message.descriptor.dataSize;

if (dataCid !== undefined && dataSize! <= DwnConstant.maxDataSizeAllowedToBeEncoded) {
const messageCid = await Message.getCid(message);
const result = await this.dataStore.get(tenant, messageCid, dataCid);

if (result) {
const dataBytes = await DataStream.toBytes(result.dataStream);
entry.encodedData = Encoder.bytesToBase64Url(dataBytes);
}
}
}

return {
status: { code: 200, detail: 'OK' },
messages
};
}
}
53 changes: 53 additions & 0 deletions src/interfaces/messages/messages/messages-get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { SignatureInput } from '../../../jose/jws/general/types.js';
import type { MessagesGetDescriptor, MessagesGetMessage } from '../types.js';

import { parseCid } from '../../../utils/cid.js';
import { validateAuthorizationIntegrity } from '../../../core/auth.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../../../core/message.js';

export type MessagesGetOptions = {
messageCids: string[];
authorizationSignatureInput: SignatureInput;
};

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

await validateAuthorizationIntegrity(message);

return new MessagesGet(message);
}

public static async create(options: MessagesGetOptions): Promise<MessagesGet> {
const descriptor: MessagesGetDescriptor = {
interface : DwnInterfaceName.Messages,
method : DwnMethodName.Get,
messageCids : options.messageCids
};

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSignatureInput);
const message = { descriptor, authorization };

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

return new MessagesGet(message);
}

/**
* validates the provided cids
* @param messageCids - the cids in question
* @throws {Error} if an invalid cid is found.
*/
private static validateMessageCids(messageCids: string[]): void {
for (const cid of messageCids) {
try {
parseCid(cid);
} catch (_) {
throw new Error(`${cid} is not a valid CID`);
}
}
}
}
Loading

0 comments on commit 07bcddb

Please sign in to comment.