How <form.io> uses Dropbox as a file backend for JavaScript apps

Randall Knutson is the Lead Architect at <form.io>, a combined form and API data management platform designed specifically for application developers, addressing complex enterprise workflow applications that require highly-customized roles and permissions, offline mode, integration into multiple 3rd-party and legacy systems, and data analysis and reporting. <form.io> can be deployed as a cloud‐based or on‐site solution, allowing developers to maintain control of their data and back‐end logic.

<form.io> makes it easy to create complex form-based AngularJS and React applications. Many of these apps require file upload and download capability embedded directly within the application. We knew right away that Dropbox was the solution we wanted to to integrate into the <form.io> platform.

Dropbox provides SDKs to integrate with their new API v2 in a variety of languages. However, since the JavaScript SDK is still under development, we wrote our own integration using the HTTP endpoints directly.

Uploading a file to Dropbox

First, we added code to fetch an OAuth 2 access token for using in the Dropbox API calls. You can see the Dropbox OAuth guide for more info about using OAuth with Dropbox.

Note: client-side apps should be careful to only expose the access token to the owner of the token, not others.

Next, we created a file selector field and an onchange event that will upload the file.

<form>
  <input type="file" name="file" accept="image/*" onchange="uploadFile">
</form>

Initially we attempted to use both an Angular file upload service and $http directly. However, we found that these send as multipart/form-data which is not what the Dropbox API expects. Instead, the Dropbox API expects the data to be send directly with a type of application/octet-stream.

To make this happen we used XMLHttpRequest directly:

/**
 * Two variables should already be set.
 * dropboxToken = OAuth access token, specific to the user.
 * file = file object selected in the file widget.
 */
 
var xhr = new XMLHttpRequest();

xhr.upload.onprogress = function(evt) {
  var percentComplete = parseInt(100.0 * evt.loaded / evt.total);
  // Upload in progress. Do something here with the percent complete.
};

xhr.onload = function() {
  if (xhr.status === 200) {
    var fileInfo = JSON.parse(xhr.response);
    // Upload succeeded. Do something here with the file info.
  }
  else {
    var errorMessage = xhr.response || 'Unable to upload file';
    // Upload failed. Do something here with the error.
  }
};

xhr.open('POST', 'https://content.dropboxapi.com/2/files/upload');
xhr.setRequestHeader('Authorization', 'Bearer ' + dropboxToken);
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify({
  path: '/' +  file.name,
  mode: 'add',
  autorename: true,
  mute: false
}));

xhr.send(file);

Now when a user selects a file with the file field it will directly uploaded to Dropbox and a reference to the file can be saved on the server.

Accessing the file within a web app

The file is now stored in Dropbox and a link to the file can be displayed to the users to access the file. However, files stored in Dropbox are not directly accessible like normal web files. Instead, we need to intercept click events on the file link and handle the Dropbox authentication to the file.

Since we are getting the contents of the file directly from Dropbox we needed to intercept the click event, download the contents of the file using the Dropbox API /download endpoint, and save the contents to the user’s computer. We can do this using the new HTML5 Blob constructor and FileSaver, which are now supported on modern browsers.

Here is our click event in Angular:

downloadFile: function(evt, file) {
  evt.preventDefault();
  var xhr = new XMLHttpRequest();
  xhr.responseType = 'arraybuffer';

  xhr.onload = function() {
    if (xhr.status === 200) {
      var blob = new Blob([xhr.response], {type: ’application/octet-stream’});
      FileSaver.saveAs(blob, file.name, true);
    }
    else {
      var errorMessage = xhr.response || 'Unable to download file';
      // Upload failed. Do something here with the error.
    }
  };

  xhr.open('POST', 'https://content.dropboxapi.com/2/files/download');
  xhr.setRequestHeader('Authorization', 'Bearer ' + dropboxToken);
  xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify({
    path: file.path_lower
  }));
  xhr.send();
}

With this function we are able to intercept the file click event, authenticate the user with Dropbox, download the contents of the file, and initiate a file save event in the browser.

You can see our full implementation in our open source library on GitHub.

Using the Dropbox API, <form.io> was able to create a file uploader and a file viewer by accessing the HTTP endpoints directly. Now anyone building a web app on our platform can easily integrate Dropbox as their backend without having to do the hard work themselves.

Feel free to reach out to us at <form.io> if you have any questions or create an account and check out how we implemented the Dropbox API.