Skip to content

Support asynchronous read-file function (aka read-from-stream) - #321

Merged
drzraf merged 2 commits into
flowjs:v3from
drzraf:asyncReadFileFn2
Dec 31, 2020
Merged

drzraf merged 2 commits into
flowjs:v3from
drzraf:asyncReadFileFn2

Conversation

@drzraf

@drzraf drzraf commented Oct 15, 2020

Copy link
Copy Markdown
Collaborator

Reroll of #304 (see initial discussion there) based on v3 branch.
See #295, #296 and #300

@drzraf
drzraf changed the base branch from master to v3 October 15, 2020 22:15
Raphaël Droz added 2 commits October 15, 2020 19:19
Since implementing a good concurrency-handler may be painful we
 provide a promise-based read-locking one so that asyncReadFileFn
 is safe to use out-of-the-box.

Given that streams-read are often unable to correctly/exactly compute their
 final amount of bytes to upload, the number of chunks may also be off.
 We handle this in a safe way via FlowChunk.readBytes:
- if read using an async function
- and the last chunk read less bytes than usual (including 0)
- and the current chunk read no byte
- assume that this chunk is in fact superfluous and simulate its completion.

Previous discussion on:
* flowjs#42
* flowjs#318
(In the long-term Flow.js may have to support the concept of "approximate chunk number")
Comment thread src/FlowChunk.js
}

if (asyncRead) {
this.readState = 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we still using states? I suggest to remove this old code and rely only on promises. Some breaking changes are welcome.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

  1. The the regular, non-async, readFileFn is untouched and still depends on state
  2. isComplete depends on chunk.readState === 1 so we must still keep updating this value.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove regular readFileFn anyway, it's too complicated and same thing can be achieved via promises. Isn't it?

@drzraf
drzraf merged commit f7e6d77 into flowjs:v3 Dec 31, 2020
@bertrandg

Copy link
Copy Markdown

Hi, 👋

Thanks for your great work,
I've just discovered this v3 branch/issue #304 and i'm following it with lot of interest (will need to do file E2EE).

What's the current state of this v3 branch, is it currently "unfinished/break" or "working/need more tests" ?
What could I do to help you on it?
Do you plan to publish an npm "v3.0.0-beta" version soon?

@drzraf

drzraf commented Jan 6, 2021

Copy link
Copy Markdown
Collaborator Author

It's definitely usable (and developing a E2EE project already relying on it, also not -production ready).
As you know it's not only about async but about streams. What's already in, works, but other details must still be polished.

ES6 + class-split gave us the base. But subsequent code cleaning and improvements are needed:

@bertrandg

Copy link
Copy Markdown

Awesome, I will experiment with it and will tell you if I have some feedbacks 👍

@bertrandg

Copy link
Copy Markdown

Just to let you know, I've coded an e2ee POC with v3 and it works well 👍

Here is how i did with latest v3 commit:

function readFileChunk(file, startByte, endByte) {
    return new Promise(resolve => {
        const reader = new FileReader();
        reader.onload = () => {
            const bytes = new Uint8Array(reader.result);
            resolve(bytes);
        };
        const blob = file.slice(startByte, endByte);
        reader.readAsArrayBuffer(blob);
    });
}
  
async function encryptFileChunk(plaintextbytes, iv, key) {
    let cypherchunkbytes = await window.crypto.subtle.encrypt({name: 'AES-GCM', iv}, key, plaintextbytes);
  
    if(cypherchunkbytes) {
        cypherchunkbytes = new Uint8Array(cypherchunkbytes);
        return cypherchunkbytes;
    }
}
  
function init() {
    const flow = new Flow({
        testChunks: false,
        target: '/upload',
        chunkSize: window.chunkSize,
        allowDuplicateUploads: true,
        forceChunkSize: false,
        simultaneousUploads: 4,
        uploadMethod: 'POST',
        fileParameterName: 'file',
        asyncReadFileFn: async function(flowObj, startByte, endByte, fileType, chunk) {
            const plaintextbytes = await readFileChunk(flowObj.file, startByte, endByte);
            const cypherbytes = await encryptFileChunk(plaintextbytes, window.ivbytes, window.key);
    
            // Add 16 bytes from initialization vector
            chunk.chunkSize = chunk.chunkSize + 16; 
    
            const blob = new Blob([cypherbytes], {type: 'application/octet-stream'});
            return blob;
        }
    });
  
    flow.on('fileAdded', function(file) {
        // Add 16 bytes from each chunk initialization vector to total encrypted file size
        file.size += file.chunks.length * 16;
    });
}

@drzraf

drzraf commented Feb 2, 2021

Copy link
Copy Markdown
Collaborator Author

Very good. In my case I had the need for an async initFileFn because of openpgp encryption key initialization and full stream based encryption.
For that purpose: #329 you may want to test it too in case you want to avoid keeping the whole regular file in memory ;)

@AidasK

AidasK commented Feb 3, 2021

Copy link
Copy Markdown
Member

@bertrandg Can you add this to our samples folder? It would be great to have it for others to see. It's enough just to make one samples/encryption.md file with some explanations

@bertrandg

Copy link
Copy Markdown

@drzraf I don't think I got the whole regular file in memory there..
I read/load only file chunk (const blob = file.slice(startByte, endByte); reader.readAsArrayBuffer(blob);) in asyncReadFileFn on the fly called before each chunk POST request.
Is the lib loading full file in memory?

@AidasK Yes sure, happy to help! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants