JavaScript
Article

How To Build a Trello Chrome Extension – API Authentication

By Bruno Skvorc

How to Build a Trello Chrome Extension

At SitePoint, we use Trello extensively. Sure, it has its quirks and could use an improvement or two in various areas, but for the most part, it’s revolutionized the collaborative experience of not only staff, but also authors and their editors.

I recently found myself needing to export card titles from a specific list for a non member. By default, Trello only supports full board exports to JSON and that’s something that crashes my tab on a board of over 100 members with hundreds of cards. There’s a small army of Trello extensions in the store, and yet curiously, none that export lists in any manner.

Let’s make a Chrome extension that can do this for us! If you’re in a rush and just want to see the end result, see the Github repo for the final version of this tutorial’s code.

Building a Chrome Extension

I figured the best approach would be an extension because a separate application doing only these exports might be too much. Besides, Trello has a nifty API which we can use to get everything we need. I also figured it would be a nice transition back into extension development, something I hadn’t done for a while.

Bootstrapping

I’ll be reusing my trusty ChromeSkel repo – a skeleton extension I built a long time ago to make getting started with Chrome Extension development easier. For reference, if you’d like to see some other Chrome Extension tutorials I’ve written in the past, see here and here.

We begin by cloning the repo to any folder.

git clone https://github.com/Swader/ChromeSkel_a.git

To see if it works, load it into Chrome. Go into the extensions tab and click “Load Unpacked Extension”. If the option isn’t there, make sure you check the “Developer Mode” checkbox in the top right corner.

Once loaded, it should appear in your extension list.

01

Authentication

While we could simply harvest the data on the screen with a Chrome extension and parse that, Trello has proven to be unreliable at times and tends to crash on highly populated boards. This is why we’ll only use the Chrome extension’s integration with the trello.com domain to create new context menu options on lists (an “Export Cards” option), and we’ll perform the whole logic in the background page, fetching the data through the API.

Key and Secret

To generate an application key for Trello, please visit https://trello.com/1/appKey/generate while logged in. This will provide you with a key and secret you can use with your account. In the remainder of this tutorial, please consider {{KEY}} to be this key, and replace the content accordingly.

Once you have your key, in the scripts subfolder, create a file key.js:

// key.js
var APP_KEY = '{{KEY}}';

While you’re at it, you can delete the fragments and fancy-settings folders. We won’t be needing them.

Workflow and Manifest

The idea of the extension’s workflow is as follows:

  • given a user opens a Trello board
  • check if said user authorized the extension to use his Trello account
  • if yes, proceed
  • if no, open a settings page with an authorization button which lets them complete the procedure
  • once authorized, close the settings page automatically and keep the extension authorized

For an extension to automatically open a new tab with some content, we need to mark that content as a “web accessible resource”.

For this purpose, create the folder settings, which will contain our settings page, and update the manifest of the extension to look like this:

{
    "name": "Trello Helper",
    "version": "0.1",
    "manifest_version" : 2,
    "description": "Trello Helper adds some much needed functionality to Trello. The current version focuses on exporting card information from lists.",
    "background" : {
        "page" : "background.html",
        "persistent": false
    },
    "page_action" :
    {
        "default_icon": {
            "19": "icons/19.png",
            "38": "icons/38.png"
        }
    },
    "content_scripts": [
        {
            "matches": ["https://trello.com/b/*"],
            "js": [
                "lib/jquery-2.1.1.min.js",
                "scripts/main.js"
            ],
            "run_at": "document_idle"
        }
    ],
    "permissions": [
        "tabs"
    ],
    "icons": {
        "16": "icons/16.png",
        "48": "icons/48.png",
        "128": "icons/128.png"
    },
    "web_accessible_resources": [
        "settings/index.html"
    ],
    "options_page": "settings/index.html"
}

Most of this should be familiar. We set the version, give some metadata, define icons and declare an event page, load some prerequisite content scripts (we need jQuery for the Trello JS client library) and finally define the “web_accessible_resources” so we can use the settings page we’ll be building. We also limit the effects of the extension to https://trello.com/b/*, meaning only board URLs.

Settings and authorization

To build our settings page, we write a simple HTML page. For demonstration purposes, I’ll keep it extremely simple in this case. We’ll use Foundation to style it (right now, only for the buttons, but we’ll work on making a better settings page in future posts), so download a Foundation essentials bundle, and unarchive its contents into /settings, so that index.html falls into that folder. You should have a folder structure such as this one:

02

If you have some extra folders that aren’t visible in my screenshot, feel free to delete them. Create the file settings/js/settings.js, leave it empty for now.

Ok, let’s get down to business. So the assumption is that the user ends up on this page by either going to “Options” in the Extensions tab, or by trying to use the extension when being unauthenticated. Let’s build a fairly basic HTML page with two divs – one for when the user is authenticated with a “Log out” button, and one for when he’s still unauthenticated and needs to click an Authorize button. Replace the contents of index.html with this:

<!doctype html>
<html class="no-js" lang="en">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Trello Helper Settings</title>
    <link rel="stylesheet" href="css/foundation.css"/>
    <script src="js/vendor/modernizr.js"></script>
</head>
<body>

<div class="row">
    <div class="large-12 columns">
        <h1>Trello Helper Settings</h1>
    </div>
</div>

<div class="row">
    <div class="large-12 columns">
        <div class="panel" id="trello_helper_loggedout" style="display: none">
            <p>To get going, you'll need to authorize the extension to use your Trello account.</p>

            <p>Do it by clicking the big Authorize button below.</p>

            <a href="#" class="medium success button" id="trello_helper_login">Authorize</a><br/>
        </div>
        <div class="panel" id="trello_helper_loggedin" style="display: none">
            <p>You are already authorized. If something doesn't work, try logging out using the button below, and logging back in.</p>

            <a href="#" class="medium success button" id="trello_helper_logout">Log out</a><br/>
        </div>

    </div>
</div>

<script src="../scripts/key.js"></script>
<script src="js/vendor/jquery.js"></script>
<script src="js/settings.js"></script>
</body>
</html>

We have our two divs, the log in and log out button, and we pull in the JS we’ll need. In this case, we’re using Foundation’s default included jQuery, but it really doesn’t matter if you choose to use your own downloaded one elsewhere in the project, in case you fetched a more up to date one like I did (more on that later).

Now let’s add in some logic. Open settings.js and give it this content:

function init() {

    // Message and button containers
    var lout = $("#trello_helper_loggedout");
    var lin = $("#trello_helper_loggedin");

    // Log in button
    $("#trello_helper_login").click(function () {
        
    });

    // Log out button
    $("#trello_helper_logout").click(function () {
        
    });

    if (!localStorage.trello_token) {
        $(lout).show();
        $(lin).hide();
    } else {
        $(lout).hide();
        $(lin).show();
    }
}
$(document).ready(init);

By reading the docs, we can find out that a trello_token will exist in localStorage when a client app is authenticated. This means we can use it as an indicator on when to show each of our divs. In the init function, we grab the divs, add click handlers to the buttons (no logic there yet) and finally, we hide the appropriate div, according to the trello_token.

When a user is authenticated, for example, they get a screen like this one:

03

Let’s fetch the Trello JS client now.
The Trello documentation isn’t really written with Chrome extensions in mind, and recommends attaching the key to the URL while retrieving the JS client library from their domain, like so:

<script src="https://api.trello.com/1/client.js?key=substitutewithyourapplicationkey"></script>

We can’t really do that, seeing as we’re dealing with the closed environment of an extension, and seeing as it would make much more sense to have it available locally for performance reasons. Therefore, we visit the above URL but without the key param:

https://api.trello.com/1/client.js

Save the contents of this file into lib/trello_client.js, then make sure our settings page loads it, by adding it to the scripts section near the ending <body> tag, like so:

<script src="js/vendor/jquery.js"></script>
<script src="../lib/trello_client.js"></script>
<script src="js/settings.js"></script>

This will make sure we have the Trello object available in our JavaScript, letting us use its methods. Let’s handle logging out first. Change the click handler of the logout button to be this:

$("#trello_helper_logout").click(function () {
        Trello.deauthorize();
        location.reload();
    });

That’s all there is to deauthorizing with Trello. We call the method and reload the page we’re on (the settings screen, that is).

Now, let’s handle logging in, which is a tad more complex.

// Log in button
    $("#trello_helper_login").click(function () {
        Trello.setKey(APP_KEY);
        Trello.authorize(
            {
                name: "Trello Helper Extension",
                type: "redirect",
                expiration: "never",
                interactive: true,
                scope: {read: true, write: false},
                success: function () {
                    // Can't do nothing, we've left the page
                },
                error: function () {
                    alert("Failed to authorize with Trello.")
                }
            });
    });

Following the logic of the online docs, we see that the Trello object has a setKey method, which we aptly use to set it. Then, we call into action the authorize method. According to the documentation, using a type popup instead of redirect will require the user to manually paste a secret key into the app when it’s received, which is not something we’re looking for. If we use redirect, however, the page will redirect to authentication and back once done. The interactive field, when set to false, negates both the popup and the redirect and instead only checks if the localStorage.trello_token value exists. As per the docs, we need to first call the regular authorize with a redirect, and then, once returned to the settings page, we need to call it again but with interactive set to false, which will make it grab the token the previous redirecting provided. It’s all a bit convoluted, but it works.

There’s another problem, though. If we call the non-interactive authorize immediately after the settings page opens, then we’ll cause an error on the page because the redirect after authorization didn’t happen yet. An alternative is adding another button “Confirm” or something like that to our settings page which appears after the redirect from Trello back to our settings page, letting the user initiate the non-interactive authorization manually. This seems a bit like a UX nightmare, though. In the end, I opted for a third solution.

Save the following code into lib/hashSearch.js.

/*
As found on: http://stackoverflow.com/questions/3729150/retrieve-specific-hash-tags-value-from-url
 */

var HashSearch = new function () {
    var params;

    this.set = function (key, value) {
        params[key] = value;
        this.push();
    };

    this.remove = function (key, value) {
        delete params[key];
        this.push();
    };


    this.get = function (key, value) {
        return params[key];
    };

    this.keyExists = function (key) {
        return params.hasOwnProperty(key);
    };

    this.push= function () {
        var hashBuilder = [], key, value;

        for(key in params) if (params.hasOwnProperty(key)) {
            key = escape(key), value = escape(params[key]); // escape(undefined) == "undefined"
            hashBuilder.push(key + ( (value !== "undefined") ? '=' + value : "" ));
        }

        window.location.hash = hashBuilder.join("&");
    };

    (this.load = function () {
        params = {}
        var hashStr = window.location.hash, hashArray, keyVal
        hashStr = hashStr.substring(1, hashStr.length);
        hashArray = hashStr.split('&');

        for(var i = 0; i < hashArray.length; i++) {
            keyVal = hashArray[i].split('=');
            params[unescape(keyVal[0])] = (typeof keyVal[1] != "undefined") ? unescape(keyVal[1]) : keyVal[1];
        }
    })();
}

As grabbed from a StackOverflow answer, this small utility helps us grab the value of a specific hash from the URL.

When you authorize with Trello through redirect mode, it will redirect back to the page it came from but with a token in the URL. This token will be the auth token the Trello JS client needs. It therefore stands to reason that if we can detect the presence of this token in the URL, we can conclude that we’re dealing with a redirect from Trello and can thus automatically and safely trigger the non-interactive authorize protocol.

After you add hashSearch to the settings page like so…

<script src="../scripts/key.js"></script>
<script src="js/vendor/jquery.js"></script>
<script src="../lib/trello_client.js"></script>
<script src="../lib/hashSearch.js"></script>
<script src="js/settings.js"></script>

… the settings.js file should, in the end, look like this:

function init() {

    // Check if page load is a redirect back from the auth procedure
    if (HashSearch.keyExists('token')) {
        Trello.authorize(
            {
                name: "Trello Helper Extension",
                expiration: "never",
                interactive: false,
                scope: {read: true, write: false},
                success: function () {},
                error: function () {
                    alert("Failed to authorize with Trello.")
                }
            });
    }

    // Message and button containers
    var lout = $("#trello_helper_loggedout");
    var lin = $("#trello_helper_loggedin");

    // Log in button
    $("#trello_helper_login").click(function () {
        Trello.setKey(APP_KEY);
        Trello.authorize(
            {
                name: "Trello Helper Extension",
                type: "redirect",
                expiration: "never",
                interactive: true,
                scope: {read: true, write: false},
                success: function () {
                    // Can't do nothing, we've left the page
                },
                error: function () {
                    alert("Failed to authorize with Trello.")
                }
            });
    });

    // Log out button
    $("#trello_helper_logout").click(function () {
        Trello.deauthorize();
        location.reload();
    });

    if (!localStorage.trello_token) {
        $(lout).show();
        $(lin).hide();
    } else {
        $(lout).hide();
        $(lin).show();
    }
}
$(document).ready(init);

You can now try the extension out. Load the extensions tab, click the Options link and test Authentication and logging out. Everything should work fine.

Conclusion

In this part, we built the basics of our extension, implementing authentication through a custom Foundation-powered settings screen and using Trello’s JavaScript client library.

In the next part, we’ll build the whole logic behind the extension and wrap things up.

Comments
jkasavage

Nice write up. Chrome is pretty straight forward and solid way to create Web Apps with trusted resources. I can't wait to see what they come up with next. Also, only problem I'm seeing is how is this article part 2 of 1? Haha.

swader

Common WP bug. Will be fixed soon : )

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in JavaScript, once a week, for free.