Skip to content

Commit

Permalink
Fixes #84 Unable to use record.data.blob() for files > 10KB
Browse files Browse the repository at this point in the history
Signed-off-by: Frank Hinek <[email protected]>
  • Loading branch information
frankhinek committed May 22, 2023
1 parent f1bbb94 commit 1d1a69e
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('Web5UserAgent', () => {
expect(response.reply.status).to.exist;
expect(response.reply.entries).to.exist;
expect(response.reply.status.code).to.equal(200);
});
}).timeout(10_000);

it('handles RecordsDelete Messages', async () => {
const { did: aliceDid } = await testAgent.createProfile({
Expand Down
25 changes: 4 additions & 21 deletions packages/web5/src/dwn-api.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Web5Agent } from '@tbd54566975/web5-agent';
import type {
MessageReply,
ProtocolDefinition,
ProtocolsConfigureDescriptor,
ProtocolsConfigureOptions,
ProtocolsQueryOptions,
RecordsDeleteOptions,
RecordsQueryOptions,
RecordsQueryReplyEntry,
RecordsReadOptions,
RecordsWriteDescriptor,
RecordsWriteMessage,
RecordsWriteOptions,
ProtocolsConfigureMessage
Expand All @@ -19,15 +19,6 @@ import { Record } from './record.js';
import { Protocol } from './protocol.js';
import { dataToBlob, isEmptyObject } from './utils.js';

// TODO: Export type ProtocolsConfigureDescriptor from dwn-sdk-js.
export type ProtocolsConfigureDescriptor = {
dateCreated: string;
definition: ProtocolDefinition;
interface : DwnInterfaceName.Protocols;
method: DwnMethodName.Configure;
protocol: string;
};

export type ProtocolsConfigureRequest = {
message: Omit<ProtocolsConfigureOptions, 'authorizationSignatureInput'>;
}
Expand Down Expand Up @@ -71,16 +62,6 @@ export type RecordsDeleteResponse = {
status: MessageReply['status'];
};

// TODO: Export type RecordsQueryReplyEntry and EncryptionProperty from dwn-sdk-js.
export type RecordsQueryReplyEntry = {
recordId: string,
contextId?: string;
descriptor: RecordsWriteDescriptor;
encryption?: RecordsWriteMessage['encryption'];
encodedData?: string;
};


export type RecordsQueryRequest = {
/** The from property indicates the DID to query from and return results. */
from?: string;
Expand Down Expand Up @@ -248,6 +229,7 @@ export class DwnApi {
if (agentResponse.reply) {
({ reply: { status } } = agentResponse);
} else {
console.log('HIT THIS');
({ status } = agentResponse);
}

Expand Down Expand Up @@ -315,6 +297,7 @@ export class DwnApi {
if (agentResponse.reply) {
({ reply: { record: responseRecord, status } } = agentResponse);
} else {
console.log('HIT THIS');
({ status } = agentResponse);
}

Expand Down
25 changes: 13 additions & 12 deletions packages/web5/src/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ export class Record implements RecordModel {
if (this.isDeleted) throw new Error('Operation failed: Attempted to access `data` of a record that has already been deleted.');

if (!this.#encodedData && !this.#readableStream) {
// `encodedData` will be set if `dataSize` <= DwnConstant.maxDataSizeAllowedToBeEncoded. (10KB as of April 2023)
// `readableStream` will be set if Record was instantiated from a RecordsRead reply.
// `encodedData` will be set if the Record was instantiated by dwn.records.create()/write().
// `readableStream` will be set if Record was instantiated by dwn.records.read().
// If neither of the above are true, then the record must be fetched from the DWN.
this.#readableStream = this.#web5Agent.processDwnRequest({
author : this.author,
Expand All @@ -119,34 +119,35 @@ export class Record implements RecordModel {

if (typeof this.#encodedData === 'string') {
// If `encodedData` is set, then it is expected that:
// `dataSize` <= DwnConstant.maxDataSizeAllowedToBeEncoded (10KB as of April 2023)
// type is Uint8Array bytes if the Record object was instantiated from a RecordsWrite response
// type is Base64 URL encoded string if the Record object was instantiated from a RecordsQuery response
// If it is a string, we need to Base64 URL decode to bytes
// type is Blob if the Record object was instantiated by dwn.records.create()/write().
// type is Base64 URL encoded string if the Record object was instantiated by dwn.records.query().
// If it is a string, we need to Base64 URL decode to bytes and instantiate a Blob.
const dataBytes = Encoder.base64UrlToBytes(this.#encodedData);
this.#encodedData = new Blob([dataBytes], { type: this.dataFormat });
}

// Explicitly cast #encodedData as a Blob since if non-null, it has been converted from string to Blob.
const dataBlob = this.#encodedData as Blob;

// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this; // Capture the context of the `Record` instance.
const dataBlob = this.#encodedData as Blob;
const dataObj = {
async blob(): Promise<Blob> {
if (self.#encodedData) return self.#encodedData as Blob;
if (self.#readableStream) return new Blob([this.stream().then(DataStream.toBytes)], { type: self.dataFormat });
if (dataBlob) return dataBlob;
if (self.#readableStream) return new Blob([await this.stream().then(DataStream.toBytes)], { type: self.dataFormat });
},
async json() {
if (self.#encodedData) return this.text().then(JSON.parse);
if (dataBlob) return this.text().then(JSON.parse);
if (self.#readableStream) return this.text().then(JSON.parse);
return null;
},
async text() {
if (self.#encodedData) return dataBlob.text();
if (dataBlob) return dataBlob.text();
if (self.#readableStream) return this.stream().then(DataStream.toBytes).then(Encoder.bytesToString);
return null;
},
async stream() {
if (self.#encodedData) return new ReadableWebToNodeStream(dataBlob.stream());
if (dataBlob) return new ReadableWebToNodeStream(dataBlob.stream());
if (self.#readableStream) return self.#readableStream;
return null;
},
Expand Down
Loading

0 comments on commit 1d1a69e

Please sign in to comment.