Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record.store(), Record.import(), and roles API support #385

Merged
merged 35 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
91319bd
Adding changes to src
csuwildcat Jan 22, 2024
22e7fbd
Updates from branch
csuwildcat Jan 22, 2024
c277199
Removing logging
csuwildcat Jan 22, 2024
8676306
Add docs
csuwildcat Jan 22, 2024
fd78372
Update based on PR feedback
csuwildcat Jan 24, 2024
74348f2
update readme
csuwildcat Jan 24, 2024
a94d9d8
Add role-based record tests, fix for cloned obj issue
csuwildcat Jan 29, 2024
bd67e0c
Updated protocol config, added elaborate role tests
csuwildcat Jan 30, 2024
2f046b1
Pass option to initial write processing
csuwildcat Jan 30, 2024
9cdc8af
Add new test for updating with existing role in play
csuwildcat Jan 30, 2024
ff8ddc5
Store/Import Typing Updates (#388)
LiranCohen Jan 30, 2024
418adc7
fix lint
LiranCohen Jan 30, 2024
9f86f94
remove errant only() in tests
csuwildcat Jan 30, 2024
3271fd5
Add test for changing the protocol role that a record is being update…
csuwildcat Jan 30, 2024
a2742c2
Remove only() from test
csuwildcat Jan 30, 2024
443c615
method dyping more consistant
LiranCohen Jan 30, 2024
fb66a7b
Added comments and removed TODO
csuwildcat Jan 30, 2024
54a12ca
Add comment about send()'s new features
csuwildcat Jan 30, 2024
380085c
clean up types, use web5/common removeUndefinedProperties
LiranCohen Jan 30, 2024
9f52b83
Delete unused test
csuwildcat Jan 30, 2024
8ce30ac
Add comments to store/import and other functions added to Record class
csuwildcat Jan 30, 2024
8b28ac3
add code comments to make Frank smile
csuwildcat Jan 30, 2024
5122571
Some more clean up for import/store (#391)
LiranCohen Jan 31, 2024
9e4af87
update test cases and coverage
LiranCohen Jan 31, 2024
8bd889e
linting errors
LiranCohen Jan 31, 2024
ff8816d
move SendCache class into it's own file, not exported in index.ts
LiranCohen Jan 31, 2024
5df7574
js doc updates
LiranCohen Jan 31, 2024
420efb1
add test coverage to dwn manager
LiranCohen Jan 31, 2024
f5b3d55
test coverage for store/import scenarios
LiranCohen Jan 31, 2024
1790b94
fix test case in dwn manager test
LiranCohen Feb 1, 2024
f01bee1
remove unused import
LiranCohen Feb 1, 2024
7453b68
update test covreage for bug fix failures
LiranCohen Feb 1, 2024
3113697
remove unreachable code path
LiranCohen Feb 1, 2024
e30a466
send cache full coverage
LiranCohen Feb 1, 2024
f51f896
updated test to have a better description and comments
LiranCohen Feb 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ Each `Record` instance has the following instance methods:
- **`text`** - _`function`_: returns the data as a string.
- **`send`** - _`function`_: sends the record the instance represents to the DWeb Node endpoints of a provided DID.
- **`update`** - _`function`_: takes in a new request object matching the expected method signature of a `write` and overwrites the record. This is a convenience method that allows you to easily overwrite records with less verbosity.
- **`store`** - _`function`_: stores the record in the local DWN instance, offering the following options:
- `import`: imports the record as with an owner-signed override (still subject to Protocol rules, when a record is Protocol-based)
- **`import`** - _`function`_: signs a record with an owner override to import the record into the local DWN instance:
- `store` - _`boolean`_: when false is passed, the record will only be signed with an owner override, not stored in the local DWN instance. Defaults to `true`.

### **`web5.dwn.records.query(request)`**

Expand Down
29 changes: 18 additions & 11 deletions packages/agent/src/dwn-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type DwnMessage = {
data?: Blob;
}

const dwnMessageCreators = {
const dwnMessageConstructors = {
[DwnInterfaceName.Events + DwnMethodName.Get] : EventsGet,
[DwnInterfaceName.Messages + DwnMethodName.Get] : MessagesGet,
[DwnInterfaceName.Records + DwnMethodName.Read] : RecordsRead,
Expand Down Expand Up @@ -245,14 +245,14 @@ export class DwnManager {
request: ProcessDwnRequest
}) {
const { request } = options;

const rawMessage = request.rawMessage as any;
let readableStream: Readable | undefined;

// TODO: Consider refactoring to move data transformations imposed by fetch() limitations to the HTTP transport-related methods.
if (request.messageType === 'RecordsWrite') {
const messageOptions = request.messageOptions as RecordsWriteOptions;

if (request.dataStream && !messageOptions.data) {
if (request.dataStream && !messageOptions?.data) {
const { dataStream } = request;
let isomorphicNodeReadable: Readable;

Expand All @@ -266,21 +266,28 @@ export class DwnManager {
readableStream = webReadableToIsomorphicNodeReadable(forProcessMessage);
}

// @ts-ignore
messageOptions.dataCid = await Cid.computeDagPbCidFromStream(isomorphicNodeReadable);
// @ts-ignore
messageOptions.dataSize ??= isomorphicNodeReadable['bytesRead'];
if (!rawMessage) {
// @ts-ignore
messageOptions.dataCid = await Cid.computeDagPbCidFromStream(isomorphicNodeReadable);
// @ts-ignore
messageOptions.dataSize ??= isomorphicNodeReadable['bytesRead'];
}
}
}

const dwnSigner = await this.constructDwnSigner(request.author);

const messageCreator = dwnMessageCreators[request.messageType];
const dwnMessage = await messageCreator.create({
const dwnMessageConstructor = dwnMessageConstructors[request.messageType];
const dwnMessage = rawMessage ? await dwnMessageConstructor.parse(rawMessage) : await dwnMessageConstructor.create({
...<any>request.messageOptions,
signer: dwnSigner
});

if (dwnMessageConstructor === RecordsWrite){
if (request.signAsOwner) {
await (dwnMessage as RecordsWrite).signAsOwner(dwnSigner);
}
}

return { message: dwnMessage.message, dataStream: readableStream };
}

Expand Down Expand Up @@ -411,7 +418,7 @@ export class DwnManager {

const dwnSigner = await this.constructDwnSigner(author);

const messageCreator = dwnMessageCreators[messageType];
const messageCreator = dwnMessageConstructors[messageType];

const dwnMessage = await messageCreator.create({
...<any>messageOptions,
Expand Down
4 changes: 3 additions & 1 deletion packages/agent/src/types/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ export type DwnRequest = {
*/
export type ProcessDwnRequest = DwnRequest & {
dataStream?: Blob | ReadableStream | Readable;
messageOptions: unknown;
rawMessage?: unknown;
messageOptions?: unknown;
store?: boolean;
signAsOwner?: boolean;
};

export type SendDwnRequest = DwnRequest & (ProcessDwnRequest | { messageCid: string })
Expand Down
147 changes: 119 additions & 28 deletions packages/agent/tests/dwn-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,26 +95,33 @@ describe('DwnManager', () => {
});

describe('processRequest()', () => {
let identity: ManagedIdentity;
let alice: ManagedIdentity;
let bob: ManagedIdentity;

beforeEach(async () => {
await testAgent.clearStorage();
await testAgent.createAgentDid();
// Creates a new Identity to author the DWN messages.
identity = await testAgent.agent.identityManager.create({
alice = await testAgent.agent.identityManager.create({
name : 'Alice',
didMethod : 'key',
kms : 'local'
});

bob = await testAgent.agent.identityManager.create({
name : 'Bob',
didMethod : 'key',
kms : 'local'
});
});

it('handles EventsGet', async () => {
const testCursor = 'foo';

// Attempt to process the EventsGet.
let eventsGetResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'EventsGet',
messageOptions : {
cursor: testCursor,
Expand All @@ -140,8 +147,8 @@ describe('DwnManager', () => {

// Write a record to use for the MessagesGet test.
let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsWrite',
messageOptions : {
dataFormat : 'text/plain',
Expand All @@ -157,8 +164,8 @@ describe('DwnManager', () => {

// Attempt to process the MessagesGet.
let messagesGetResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'MessagesGet',
messageOptions : {
messageCids: [messageCid]
Expand Down Expand Up @@ -187,8 +194,8 @@ describe('DwnManager', () => {

it('handles ProtocolsConfigure', async () => {
let protocolsConfigureResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'ProtocolsConfigure',
messageOptions : {
definition: emailProtocolDefinition
Expand All @@ -211,8 +218,8 @@ describe('DwnManager', () => {
it('handles ProtocolsQuery', async () => {
// Configure a protocol to use for the ProtocolsQuery test.
let protocolsConfigureResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'ProtocolsConfigure',
messageOptions : {
definition: emailProtocolDefinition
Expand All @@ -222,8 +229,8 @@ describe('DwnManager', () => {

// Attempt to query for the protocol that was just configured.
let protocolsQueryResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'ProtocolsQuery',
messageOptions : {
filter: { protocol: emailProtocolDefinition.protocol },
Expand Down Expand Up @@ -252,8 +259,8 @@ describe('DwnManager', () => {

// Write a record that can be deleted.
let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsWrite',
messageOptions : {
dataFormat : 'text/plain',
Expand All @@ -266,8 +273,8 @@ describe('DwnManager', () => {

// Attempt to process the RecordsRead.
const deleteResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsDelete',
messageOptions : {
recordId: writeMessage.recordId
Expand All @@ -294,8 +301,8 @@ describe('DwnManager', () => {

// Write a record that can be queried for.
let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsWrite',
messageOptions : {
dataFormat : 'text/plain',
Expand All @@ -308,8 +315,8 @@ describe('DwnManager', () => {

// Attempt to process the RecordsQuery.
const queryResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsQuery',
messageOptions : {
filter: {
Expand Down Expand Up @@ -343,8 +350,8 @@ describe('DwnManager', () => {

// Write a record that can be read.
let { message, reply: { status: writeStatus } } = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsWrite',
messageOptions : {
dataFormat : 'text/plain',
Expand All @@ -357,8 +364,8 @@ describe('DwnManager', () => {

// Attempt to process the RecordsRead.
const readResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsRead',
messageOptions : {
filter: {
Expand Down Expand Up @@ -391,8 +398,8 @@ describe('DwnManager', () => {

// Attempt to process the RecordsWrite
let writeResponse = await testAgent.agent.dwnManager.processRequest({
author : identity.did,
target : identity.did,
author : alice.did,
target : alice.did,
messageType : 'RecordsWrite',
messageOptions : {
dataFormat: 'text/plain'
Expand All @@ -414,6 +421,90 @@ describe('DwnManager', () => {
expect(writeReply).to.have.property('status');
expect(writeReply.status.code).to.equal(202);
});

it('handles RecordsWrite messages to sign as owner', async () => {
// bob authors a public record to his dwn
const dataStream = new Blob([ Convert.string('Hello, world!').toUint8Array() ]);

const bobWrite = await testAgent.agent.dwnManager.processRequest({
author : bob.did,
target : bob.did,
messageType : 'RecordsWrite',
messageOptions : {
published : true,
schema : 'foo/bar',
dataFormat : 'text/plain'
},
dataStream,
});
expect(bobWrite.reply.status.code).to.equal(202);
const message = bobWrite.message as RecordsWriteMessage;

// alice queries bob's DWN for the record
const queryBobResponse = await testAgent.agent.dwnManager.processRequest({
messageType : 'RecordsQuery',
author : alice.did,
target : bob.did,
messageOptions : {
filter: {
recordId: message.recordId
}
}
});
let reply = queryBobResponse.reply as RecordsQueryReply;
expect(reply.status.code).to.equal(200);
expect(reply.entries!.length).to.equal(1);
expect(reply.entries![0].recordId).to.equal(message.recordId);

// alice attempts to process the rawMessage as is without signing it, should fail
let aliceWrite = await testAgent.agent.dwnManager.processRequest({
messageType : 'RecordsWrite',
author : alice.did,
target : alice.did,
rawMessage : message,
dataStream,
});
expect(aliceWrite.reply.status.code).to.equal(401);

// alice queries to make sure the record is not saved on her dwn
let queryAliceResponse = await testAgent.agent.dwnManager.processRequest({
messageType : 'RecordsQuery',
author : alice.did,
target : alice.did,
messageOptions : {
filter: {
recordId: message.recordId
}
}
});
expect(queryAliceResponse.reply.status.code).to.equal(200);
expect(queryAliceResponse.reply.entries!.length).to.equal(0);

// alice attempts to process the rawMessage again this time marking it to be signed as owner
aliceWrite = await testAgent.agent.dwnManager.processRequest({
messageType : 'RecordsWrite',
author : alice.did,
target : alice.did,
rawMessage : message,
signAsOwner : true,
dataStream,
});
expect(aliceWrite.reply.status.code).to.equal(202);

// alice now queries for the record, it should be there
queryAliceResponse = await testAgent.agent.dwnManager.processRequest({
messageType : 'RecordsQuery',
author : alice.did,
target : alice.did,
messageOptions : {
filter: {
recordId: message.recordId
}
}
});
expect(queryAliceResponse.reply.status.code).to.equal(200);
expect(queryAliceResponse.reply.entries!.length).to.equal(1);
});
});

describe('sendDwnRequest()', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/dwn-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ export class DwnApi {
const { entries, status, cursor } = reply;

const records = entries.map((entry: RecordsQueryReplyEntry) => {

const recordOptions = {
/**
* Extract the `author` DID from the record entry since records may be signed by the
Expand Down
Loading
Loading