Sending a File through a port

I’m working on a work project where a user can select a PDF using elm/file and then send it to our server. Given other dependencies, we’re still using elm/http 1.0, which doesn’t support file uploads.

My solution was to convert to a dataUri and send across a port to upload, but this isn’t ideal. On large files this can be problematic on some browsers, either locking up or simply failing.

I have another project coming up where I’ll have to send files across a port to be store as blobs in an indexedDB, so I’m looking for guidance:

Is there a better way?

If not, what’s the path forward? Add file encoding to elm/json?

Hmm, one weird hack I can think of would be to send the file using Http.fileBody, and then intercept it with a service worker :smile: Probably not the best though.

Doh nvm, you said you’re using http 1.0. I somehow thought you wanted to get the file into JS.

edit2: or actually I guess I read it correctly after all…

I considered that for the upcoming project, but have to support some iOS devices that predate service workers (which are only about a year old on iOS).

I haven’t tested this, but I was looking at how file inputs work and it basically keeps the File object from JS around.

One way you could work with that is using createObjectURL to get a blob: URL that you can use to reference the file later on. You could send the blob URL to Elm, and when it’s time to send it to the server send info out a port (including the blob: url), and have it do the upload from JS by reading the blob. We do something similar for this where we send the real File object and the Blob URL in from a custom element for file upload, and we use that blob URL as a src for image previews.

Can you list the dependencies? Maybe it would be easier to just upgrade everything.

We’re going the other way through the port, and this is essentially what we’re doing. But as I said in the initial post, it’s unreliable with large files.

It’s lukewestby/elm-http-builder, but the team took a vote and voted against refactoring to remove (I had already started working on a replacement). Regardless, we have a project coming up where we have to send through a port to send a blob to an api that isn’t wrapped by a core library or exploration yet (indexedDB), and I’m sure there’s other instances where it will come up.

Using createObjectURL just gives you a string, it doesn’t base64 it or anything, it doesn’t have any overhead on the Elm side.

For these very custom cases I guess the best way to handle it is to delegate everything to JS.

Get the fileRef in Elm, send it to JS as a Value and then read the contents and send them where they need to get it in JS.

Can we encode an opaque type as a value? How do we get the fileRef on the elm side?

I considered writing a clone of elm/file using ports to do the file selection, but it required attaching an invisible node to the DOM which is something I’m loathe to do.

1 Like

I built a file sharing app to tryout Elm for real some months ago, which was in the pre-elm/file and elm/html@1 days. What I ended up doing is sending custom Request records that mirrored the elm/html structure over a port. I left the javascript File object I got from the event unencoded (using Json.Decode.value) and sent the raw Value inside of the Request over a port, which worked. The Javascript code was basically a copy of the relevant Kernel code inside of elm/html, with the bonus feature of handling file uploads.

Here is my event decoder: https://gitlab.com/arkandos/shareit/blob/master/src/FileHandle.elm#L84
The Port and Request record, Elm side: https://gitlab.com/arkandos/shareit/blob/master/src/Ports.elm
Javascript Callback: https://gitlab.com/arkandos/shareit/blob/master/public/initialize.js#L23

Please don’t blast the code too much, this was the first time I tried to do something “serious” in Elm. Hope it helps :slight_smile:

1 Like

You use a custom event handler on an input. It would look like this. You can then use the FileRef to send the file where it needs to get using your preferred JS method. .

2 Likes

Are there any benefits in using https://github.com/phylor/elm-image-upload versus this?

No benefit comes to my mind but maybe there is something I’m missing.

There is a tradeoff involved. The approach you linked has a simpler decoder but it pays for this by making you deal with ids. The approach I linked solves the issue without ids at the expense of a more complex decoder.

Oh, and welcome to the Elm Discourse. May you find all the answers you seek. :smiley:

Thank you for welcoming :slight_smile:
Yeah, I’m quite a newbie. The thing I still don’t understand - is it possible to avoid base64 overhead when uploading arbitrary file/data to a server?

Some time ago I wrote file uploading (to a server) app based on the approach I linked which uses base64 and I guess it is a requirement there. Consequently, file contents are encapsulated in json. However, your example has no base64 by default and it’s not clear for me if it’s really suitable for uploading arbitrary files to a server.

The example above was for the constrain of http 1.0. If you have no such constrain, just use http 2.0 and the new File module (it should be the most efficient approach).

Also, a key point of the ports approach was that you handle the file upload in JS, this means that you can read the file as binary and upload it as binary. You don’t have to go through the conversion.

Ah, my fault was that I wasn’t aware of the elm/file package and its purpose. It’s a relatively new package (appeared half a year ago). It looks like major progress over several custom solutions that were flying around the internet.

And in contrast elm/file package uses I guess some implicit native code approach without exposing JS internals to the programmer, right?
Maybe a bit offtopic, but I couldn’t find an explanation on how project sources like Elm/Kernel/File.js get involved in the build? Is it the case when the package is trusted (couldn’t see any notion on this) and allowed to use native code?

With 0.19, use of Kernel(JS) modules has been restricted to two organizations: elm and elm-explorations.

This kind of source code is not documented is because it is not really intended to be modified by contributors outside of the core team.

Thank you! I got it.

Hey Dan, you may want to check out:

for inspiration. We use this in production.

Works like a charm (we use it to upload PDFs to our server, then we upload from there to S3) , but it’s native so 0.18 only. (One of the reason we haven’t move to 0.19…)

Best,

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.