Custom System Via Client Side Scripting

Published Jan 10, 2018Last updated Jan 17, 2018
Custom System Via Client Side Scripting

In my digital travels, I've come across tons of sites. On a vast majority of these sites, I've thought to myself, "I know there's a better way to do XYZ."

I see text that is absurdly small, thumbnails that are either too small or far too large, or single file uploads where you really need batch file uploading, etc. I don't like being stuck with a format that doesn't make sense.

In this regard, I've come to love client-side scripting as a means of enhancing a website's functionality when I don't have access to the server code — i.e. I don't own the server, I just use their software for my needs.

I've done cosmetic overrides on sites before to enhance text, shrink/magnify thumbnails, etc. But, one site in particular, that I use at my workplace, has a particularly frustrating single file upload system that was just plain inefficient.

It only received one file at a time and required the user to always specify what columns information was in in a CSV, with no means of recording this data for later use.

I had an idea one night to inject my own multiple, automatic file uploader onto their site. I decided to make a Chrome extension that would detect when I was on their site and on that particular page.

I hadn't yet injected my own system into a system where I couldn't modify the server code. I guess I hadn't really thought about it before then. So, I formulated a game plan.

I used Fiddler by Telerik to analyze the request/responses the website sent to the server when I used the file uploader. I realized that the request sent to the server was just a bunch of form URL encoded data. As long as I provided it with my current session ID, I could send as many requests as I wished for one time.

So, in the Chrome extension, I created an input element of type "file," with the attribute of multiple to be able to select multiple files. When I'm adding in elements that don't exist, I tend to go the extra mile to make sure they have unique names that won't conflict with any elements already on the page.

<input type="file" id="multifiles-3342789a" multiple>

I made a change event listener that listened for the file input to select new files. Once files are selected, I created a loop that runs through the list of files, making the appropriate FormData appends and an XMLHttpRequest for each file.

By wrapping the loadstart and load listeners for each XMLHttpRequest in a closure, I could then give an active status report on when a file is uploading and when it is done doing so. I did this by also giving each status report its own uniquely generated ID.

document.querySelector('#multifiles-3342789a').addEventListener('change', function (e) { 
    let files = e.target.files;
    let filename = "";
    //hold XHR requests
    let xhr = [];
    //hold the FormData for each request
    let data = [];
    if (files.length > 0) {
        //for each file in the files, different xhr requests with different form data
        for (let i = 0; i < files.length; i++) {
            let newid = Math.floor(Math.random() * 30000);
            while (newids.indexOf(newid) >= 0) {
                newid = Math.floor(Math.random() * 30000);
            }
            newids[i] = newid;
            data[i] = new FormData();
            //All the formdata here...
            data[i].append('name-of-input', 'value-of-input');
            //...
            
            //Create closure for beginning of transfer
            (function (i, filename, id) {
                xhr[i].upload.addEventListener('loadstart', function () {
                    let self = this;
                    this.id = id;
                    this.filename = filename;
                    document.querySelector('#suppminialerts-53261').innerHTML += `<div id="upload-${self.id}"><span style="color:${uploadingColor};">${self.filename} is uploading...</span><br></div>`;
                });
            })(i, filename, newids[i]);

            //Closure for loaded xhr request
            (function(i, filename, id) {
                xhr[i].addEventListener('load', function (e) {
                    let self = this;
                    let status, text, readyState;
                    this.filename = filename;
                    this.id = id;
                    document.querySelector(`#upload-${self.id}`).innerHTML = `<span style="color:${uploadSuccessColor};">${self.filename} uploaded!</span><br>`;
                    window.setTimeout(function () {
                        document.getElementById(`upload-${self.id}`).remove();
                    }, 5000);
                });
            })(i, filename, newids[i]);
        }
    }
});

The original single file uploader on their site made you click an OK button after doing everything else when the file had finished uploading.

Again, I didn't like that. This new system I had created only required that I select the files I needed, and they would be automatically uploaded.

When you have to upload 10 or more files, it takes the wait time down to just a second to upload, compared to around 15-20 minutes using their single file uploader.

The entirety of that script was written in plain JavaScript, no libraries. The script was written specifically for Chrome so I could use the enhanced capabilities of ECMAScript 6 to get the work done easier and faster without having to worry about cross site compatibility.

I still use this script at my workplace and have added more implementations, such as cosmetic overrides and autoclickers. All of this and more is possible with client-side scripting!

You, too, can create your own custom system on a site that you don't directly control with some JavaScript knowledge and a will to succeed.

Check out my Chrome Extension for Instagram called "InstagramNightMode" that turns Instagram's site into a dark theme.

Discover and read more posts from Marcus Parsons
get started