Posting Images to App.Net via Client-Side JavaScript

This past weekend I added a much needed feature to Nice.Social that allows people to upload images to their ADN Storage and share images without jumping through hoops. File Storage is something I struggled with in the past, eventually putting the functionality on the back burner while focusing on other parts of the application, but I share an awful lot of images on App.net. As a result, any client that I use will need to have this one function down pat. So, throwing myself at the problem, I dove into the documentation to make this crucial feature possible.

As with any web project, it’s important to think about which browsers the site might be used on, and this meant that using formData as a way to collect and append information was not really an option. formData is supported by almost every current browser, but versions of Internet Explorer prior to 10 do not use this feature. As a result, I needed something a bit simpler. In addition to this, I wanted to have a way to report upload progress back to the screen so that people could see how much of their file had been sent to the server. This meant making use of XMLHttpRequest(), something that is used quite extensively in the Nice.Social code.

Unfortunately, getting an XMLHttpRequest() to play nicely with App.net is not quite as cut and dry as one might hope. Despite what the documentation might state, it does not seem to be possible to upload a file object in one API call. Instead, the process needs to work like this:

  • send a JSON package outlining what the document is
  • receive a file_id and file_token back from the server
  • upload the file using the above-mentioned values

With this in mind, let’s step through the process of uploading a file to App.net’s File Storage. First step, send a JSON package outlining just what the document is.

Disclaimer I’m making this code available with no warranty whatsoever.

document.getElementById(‘file’).addEventListener(‘change’, function(e) {
    var file = this.files[0];
    var xhr = new XMLHttpRequest();
    var url = ‘https://api.app.net/files’;

var fileObj = { 'kind': "image", 'type': "com.example.photo", 'mime_type': file.type, 'name': file.name, 'public': true }; xhr.onreadystatechange = function(e) { if ( 4 == this.readyState ) { parseFileUpload( e.target.response, file ); } }; xhr.open('post', url, true); xhr.setRequestHeader("Authorization", "Bearer " + *[ACCOUNT_ACCESS_TOKEN]*); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send(JSON.stringify(fileObj)); }, false);

This code will create and upload the JSON object fileObj, which stipulates the mime type, file name, and whether the object should be publicly accessible or not. There is also a type, which allows you to specify a little more clearly what something is. In Nice, I set this to 'social.nice.image', though you may want to set one for your application if it’s going to be a unique type of upload. Be sure to pass the proper account access token to the API through the xhr.setRequestHeader() as well. Of course, the file itself has not been uploaded yet, just the precursors required to allow the upload to take place. So let’s upload some files!

Once the JSON data has been successfully uploaded, the App.net API will return a 200 status code along with a JSON package that contains all of the key information required to upload the actual file. Here is an example of what the return JSON will look like:

{
    "data": {
                "complete": false,
                "created_at": "2015-05-26T00:00:00Z",
                "file_token": "{a very, very long string}",
                "id": "999999",
                "kind": "other",
                "mime_type": "image/png",
                "name": "filename.png",
                "sha1": "ef0ccae4d36d4083b53e121a6cf9cc9d7ac1234a",
                "size": 1234567,
                "source": {
                        "name": "Nice.Social",
                        "link": "https://nice.social",
                        "client_id": "abcdefghijklmnopqrstuvwxyz0123456789"
                },
                "total_size": 1234567,
                "type": "com.example.test",
                "url": "https://cdn.app.net/your-unique-file-name",
                "url_expires": "2015-05-26T03:00:00Z",
                "user": {user object}
    },
    "meta": {
                "code": 200
    }
}

Armed with this information, we can now upload a file to the App.net File Storage. In the above JavaScript snippet, you can see a parseFileUpload() function. This takes the JSON response from ADN and performs the appropriate action based on the meta.code response.

function parseFileUpload( response, file ) {
    var rslt = jQuery.parseJSON( response );
    var meta = rslt.meta;
    var showMsg = false;
    switch ( meta.code ) {
        case 400:
        case 507:
            alert(‘App.Net Returned a ’ + meta.code + ’ Error:
’ + meta.error_message); break; case 200: var data = rslt.data; var xhr = new XMLHttpRequest(); var url = 'https://api.app.net/files/' + data.id + '/content'; if ( xhr.upload ) { xhr.upload.onprogress = function(e) { var done = e.loaded, total = e.total; var progress = (Math.floor(done/total*1000)/10); if ( progress > 0 && progress <= 100 ) { console.log('Uploading ... ' + progress + '% Complete'); } else { console.log('Upload Complete'); } }; } xhr.onreadystatechange = function(e) { if ( 4 == this.readyState ) { switch ( e.target.status ) { case 204: /* The Upload is Good. Return the File Object Array */ return data; break; case 507: /* A 507 Means "Insufficient Storage" */ alert('App.Net Returned a ' + meta.code + ' Error:<br>' + meta.error_message); break; default: /* Do Nothing */ } } }; xhr.open('put', url, true); xhr.setRequestHeader("Authorization", "Bearer " + *[ACCOUNT_ACCESS_TOKEN]*); xhr.setRequestHeader("Content-Type", file.type); xhr.send(file); break; default: /* Do Nothing */ } }

Notice that we are using a PUT rather than a POST with the file upload, and that the Content-Type value is of the file’s mime type rather than as a multipart/form-data. This code does work, and you can see it in operation on Nice.Social. After the post is uploaded to the server you can embed the object into a post using an oembed JSON object that references this file when creating a post. Be sure to use the file_token and id values that were returned when the initial JSON package was uploaded to the API.

Wrap Up

This was not an easy problem for me to solve, as I ran into a lot of unforeseen complications that were not discussed in the official documentation. That said, after everything was operational, the system has worked without any problems whatsoever. I’ve not seen any hiccups when uploading files, though the public URL will occasionally alternate between photos.app.net and files.app.net. Truth be told, I think it has something to do with the data type we’re reporting, but I haven’t validated it 100% just yet.

While App.net may not be a popular platform to develop against, it’s still a pretty decent tool that can offer a lot of flexibility so long as you know how to make use of the API and its quirks. Hopefully there will be a few more exciting tools created in the future that allows people to explore new ways of using the service.

Page generated in roughly: 0.29106 Seconds, 0 API Calls, 6 SQL Queries, 4 Cache Objects