layout: post author:
name: Fil Maj
url: https://twitter.com/filmaj
title: "Transition off of cordova-plugin-file-transfer" categories: blog
Early on in Cordova's existence, the file-transfer plugin
was created to solve the problem of downloading binary files.
At the time, there weren't great options for solving this using standards-compliant
web APIs. The web took a twisty path to get to a solution (see
Firefox's sendAsBinary
and the now-defunct FileSystem API's
BlobBuilder,
among others), but today you can use our good friend XMLHttpRequest's
newest features, combined with some newer JavaScript types and objects,
to solve this problem.
This is an exciting moment for Cordova as the dream for this project was always
to eventually reduce the surface area of APIs the project maintains, and instead
see regular web APIs be able to handle these use cases.
As a result, Cordova is sunsetting the file-transfer plugin. What does "sunsetting" mean? In summary:
All of us at Apache Cordova don't want to leave y'all hanging, though, so we thought it'd be a good idea to show you how to use these newer XHR features to do what file-transfer lets you do, but in a way that will work in any modern web browser to boot!
Based on how deeply you interact with the underlying device filesystem, and on
which platforms, you may still need to rely on the
Cordova File plugin. If you
still have references to requestFileSystem
or root.fs
in your application's
JavaScript, you will definitely need the File plugin because these are not
standards-compliant APIs. Take note and care!
Binary types in JavaScript, as well as the extended XHR features, are available on the following Cordova-supported platforms without requiring any additional plugins:
As always, check caniuse.com for detailed support for the
required bits, like Blob
,
Typed Arrays, and
extended XHR features.
Standards are great and all, but what do you actually have to copy-paste to replace the previous FileTransfer examples? We have you covered:
Here's a replacement for FileTransfer's "Download a Binary File" example:
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
console.log('file system open: ' + fs.name);
fs.root.getFile('bot.png', { create: true, exclusive: false }, function (fileEntry) {
console.log('fileEntry is file? ' + fileEntry.isFile.toString());
var oReq = new XMLHttpRequest();
// Make sure you add the domain name to the Content-Security-Policy <meta> element.
oReq.open("GET", "http://cordova.apache.org/static/img/cordova_bot.png", true);
// Define how you want the XHR data to come back
oReq.responseType = "blob";
oReq.onload = function (oEvent) {
var blob = oReq.response; // Note: not oReq.responseText
if (blob) {
// Create a URL based on the blob, and set an <img> tag's src to it.
var url = window.URL.createObjectURL(blob);
document.getElementById('bot-img').src = url;
// Or read the data with a FileReader
var reader = new FileReader();
reader.addEventListener("loadend", function() {
// reader.result contains the contents of blob as text
});
reader.readAsText(blob);
} else console.error('we didnt get an XHR response!');
};
oReq.send(null);
}, function (err) { console.error('error getting file! ' + err); });
}, function (err) { console.error('error getting persistent fs! ' + err); });
Here's a similar replacement for FileTransfer's "Upload a File" example:
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
console.log('file system open: ' + fs.name);
fs.root.getFile('bot.png', { create: true, exclusive: false }, function (fileEntry) {
fileEntry.file(function (file) {
var reader = new FileReader();
reader.onloadend = function() {
// Create a blob based on the FileReader "result", which we asked to be retrieved as an ArrayBuffer
var blob = new Blob([new Uint8Array(this.result)], { type: "image/png" });
var oReq = new XMLHttpRequest();
oReq.open("POST", "http://mysweeturl.com/upload_handler", true);
oReq.onload = function (oEvent) {
// all done!
};
// Pass the blob in to XHR's send method
oReq.send(blob);
};
// Read the file as an ArrayBuffer
reader.readAsArrayBuffer(file);
}, function (err) { console.error('error getting fileentry file!' + err); });
}, function (err) { console.error('error getting file! ' + err); });
}, function (err) { console.error('error getting persistent fs! ' + err); });
Note that both the above examples rely on the File plugin, so if you remove the FileTransfer plugin from your app, make sure to add the File plugin!
If you want to understand some of the nuts and bolts enabling binary data transferring, you'll need to grasp two (possibly three) concepts. MDN has an absolutely fantastic article on the topic that is worth a quick read, but I'll provide a summary here, too.
For the longest time, there was no way to directly represent binary data and
access the underlying bytes in memory within JavaScript. We could encode this data
in different formats (base64,
anyone?), and that was cool, but just let me play with the bytes already. For
our purposes, we are interested in two objects in particular:
ArrayBuffer
and Blob.
Why do we care about these two? Because we can have XHRs return downloaded data
as these types, or pass these types directly to XHRs' send
method.
There are two newer XHR features, originally as part of what was referred to as "XHR2" during its development, that we need to leverage to tie this all together.
For downloading binary data, we need to set the
responseType
property to either arraybuffer
or blob
- this tells XHR what type we want
the data we are retrieving back in. With responseType
set, we can then access
the read-only response
property to get either the ArrayBuffer
or Blob
object representing the data retrieved by XHR.
For uploading binary data, it is simpler: pass a Blob
or ArrayBuffer
directly
to XHR's send
method. That's it.
Binary types and extended XHR features are well supported in modern desktop
browsers, and on recent-ish mobile browsers (and WebViews). For existing Cordova
users, as long as your app targets the platform and OS version combinations listed
above under Platform Support, you should be good to go! Remember that if you rely
on certain File plugin APIs like requestFileSystem
, root
, or getFile
,
you'll need to ensure the File plugin is added to your app.
Happy standards-compliant coding!