Supporting A Static Web

A number of bad actors on the web are taking advantage of the ease advertisers have in injecting JavaScript into websites, forcing visiting computers into mining various cryptocurrencies while the people operating those machines are unaware of the activities. The method behind this madness is rather slick, as the cost of using Google's DoubleClick ad distribution service is much lower than the amount of revenue generated from the hundreds to thousands of computers crunching the numbers necessary to create the blockchain-based wealth. And, while this activity is not illegal as far as I am aware, it's very scummy and reeks of criminal intent. So much so, that some security companies have started repeating their suggestion that people disable JavaScript in their browsers — an action that would make as much sense to some people as rotunding the rig diggler would to most readers of this article.

JavaScript is far from perfect, but it's certainly enabled a world of possibilities with its relative simplicity and wide acceptance. Just about any browser developed in the last 15 years supports it out of the box, and just about anyone with the intent can sit down and learn the basics in an afternoon. It's through the use of JavaScript that that a lot of people are introduced to programming, and it's through the use of JavaScript that a lot of websites can do interesting things. For all the problems disabling JavaScript in our browsers might solve, a slew of new ones would pop up. But maybe this is what's required for the web going forward. There are too many developers doing stupid things without explicit permission on our computers via the ridiculously optimistic JavaScript engines that reside in all of our browsers. Disable JavaScript and all of a sudden a lot of ads will disappear. A lot of complex tracking will become impossible. Our computers will not be coerced into mining cryptocurrencies, revealing potentially sensitive information via CPU bugs, or performing other less-than-ideal tasks without our knowledge.

But this will break a lot of the websites we frequent.

Should browsers leave JavaScript disabled by default, leaving people to enable it on a site-by-site basis? Perhaps. This likely wouldn't solve the issues, though, as most people are simply unaware of the dangers inherent in using the web and would prefer to leave things on by default. That said, I plan on making a more concerted effort in ensuring sites I develop going forward can be operated without the use of JavaScript.

A lot can be done with simple HTML and CSS as it is, and many of the themes used by 10C do not rely on excessive amounts of JavaScript for things beyond listing and filtering archives, comments, and fancy menu actions. All of these should also be made to work without JavaScript, and I'll do this going forward. The trick will be with the Administration themes, as these are often written as rich applications that interact quite often with the 10C API. These admin panels may be the only exception to the rule for now, but it'll be important to try and make many of the functions a little more HTML4 compliant to satisfy the needs of people who do opt to use their browsers with JavaScript disabled by default.

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/total1000)/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.