Building a Chrome Extension with Yeoman

Added on by Dario Villanueva.

Last night I was wondering what to have for dinner. After coding all day, I usually get home pretty tired, so the last thing I want to do is cook - It's way easier to open my browser and order some hot steamy junk from one of the major fast-food aggregators like just-eat or hungry house.

Lately, I've been ordering online food way too often, to the point that I'm starting to feel it on my waistline (and my wallet). This had to stop. I decided to redirect all these sites to a page that would insult me, in hopes that I would not get chinese/pizza/curry/fried chicken. I told my friend Ash about it, and, judging by his reaction, it seemed like a very good idea:

The conversation that started it all

The conversation that started it all

Before I decided to build a browser extension, My Idea involved editing my hosts file to point all these sites away into somewhere stupid, but since my friend wanted to be able to have the same functionality, a chrome extension sounded like the right thing to build.

My flatmate has an awesome collection of cookery books, so I picked up Laxmi Khurana's "An Indian Housewife's Recipe Book" and started looking for something nice to make. Rice and dhal is very easy to make, and very good for you. No wonder it is a staple of Indian diet.

While the dhal was cooking, I started looking at chrome extension's developer site. As is the case with most of google's products, the documentation is extensive but a bit hard to get into - it induced a bit of blank-page syndrome on me. I began to expect that this project would take me way longer than an evening to complete. How wrong I was!  

Yeoman to the rescue

It dawned upon me that I had once glanced over a npm package named `generator-chrome-extension` while browsing through yeoman generators. I never gave it too much thought that time, but I wondered if this could be of any use. I did a `git init nofattie` on my workspace and then ran this:

$ npm install -g generator-chrome-extension

It installed successfully! My previous experience with yeoman generators meant that I was a bit skeptical about what the generator would create. However, I saw who was behind it:

sindresorhus, ragingwind, addyosmani, paulirish, passy, kevva

These guys mean business, so I knew this would probably work pretty well.  So I invoked the generator:

$ yo chrome-extension

I was prompted by quite an elegant series of questions about the extension's name, requirements and permissions. I left them all as default. Once I was done with the questions, I let npm and bower run their install tasks while I checked up on the dhal, which was simmering nicely. I got the rice going and went back to the computer. 

I had a look at the directory and saw it had created a tree of files, including all the required stuff to have a chrome extension: a manifest file, a background.js file, some icons and all of the other ancillary stuff, Gruntfile with nice tasks, a gitignore... I had all I needed.

Background.js

This is the main bit of the application. On the boilerplate built by yeoman, It sits under app/scripts/background.js, and it looks like this:

'use strict';

chrome.runtime.onInstalled.addListener(function (details) {
console.log('previousVersion', details.previousVersion);
});

okay, it looks like all it is doing is printing the details of the previous version to the console. I needed it to catch all web requests, and if the url matched a pattern, I would redirect the user to a local page that insulted them. So I googled a bit and found that chrome.webRequest API has an event called `onBeforeRequest`, which fires every time a web request is about to occur.

This event has a method `addListener` that will take three arguments:

  1. A callback function to execute when the event fires (mandatory)
  2. A RequestFilter object to control the conditions under which the callback will be fired.
  3. An array that may contain the string 'blocking' if you want your callback to be handled synchronously.

It looked like I had all I needed to build my extension. I wrote a callback that returns a BlockingResponse with the `redirectUrl` property pointing to a html insult page. Then, I created a filter with a list of url match patterns to catch all takeaway website urls that I use and/or know of. My `background.js` file now looked like this:

'use strict';

chrome.webRequest.onBeforeRequest.addListener(
function(details) {
return {redirectUrl: chrome.extension.getURL('html/index.html')};
},
{
urls: [
"*://*.pizzahut.co.uk/*",
"*://*.dominos.co.uk/*",
"*://*.papajohns.co.uk/*",
"*://*.just-eat.co.uk/*",
"*://*.hungryhouse.co.uk/*",
"*://*.grubhub.com/*",
"*://*.meal2go.com/*",
"*://*.curriesonline.co.uk/*",
"*://*.eatstudent.co.uk/*"
],
types: ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"]
},
["blocking"]
);

Notice i'm using chrome.extension.getURL() to create the url I'm redirecting to. This is because the chrome-extension urls follow the following form:

chrome-extension://__extensionid__/your/file.extension

The extension id is an ugly string that chrome generates for you, and it changes once you publish it to the chrome web store, so hard coding won't work here. This took me a while to figure out, but once i had it down, my rice and dhal was ready to eat. I served myself a nice portion and went on to the more creative side of things.

Building the redirect page - manifest destiny.

This bit was going to be a piece of cake: One heading to bully you, an image to reinforce the message and a subheading to better help get the point across. I think I'd be a great UX designer If I knew where I put my post-it notes and my sharpies... A couple of spoonfuls of rice later I had a nice page setup:

<!DOCTYPE html>
<html lang="en">
<head>
<title>You're Fat</title>
<link rel="stylesheet" href="/styles/main.css">
</head>
<body>
<h1>NO, FATTIE</h1>
<img src="/images/fattie.jpg">
<h2>DON'T GET THAT TAKEAWAY</h2>
</body>
</html>

I created a main.css some basic styles (centre all elements on the page, make the font pretty big, etc) and then put it under app/styles/main.css. I googled for an image of a forbidden pizza and found this:

 

Perfect, I thought, as I savoured my vegan indian low-carb, low-GI meal. It looked like everything was ready, so I decided to take my extension for a spin.

You can add unpacked extensions to chrome if you activate the developer checkbox on the extensions page. Then you can tell chrome to load your extension's working directory:

Screenshot 2013-10-30 15.13.49.png

I pointed chrome's load dialog to my `app` folder and presto, my extension was loaded. I tried a couple of test runs. I typed 'p' and right away chrome suggested i go to Pizza Hut's website. I pressed enter and instead of being greeted with the picture of a hot, steamy, halloween family box deal with chicken wings, I ended on a page that called me a fattie. Success! However, the styling was all wrong, and the forbidden pizza graphic wasn't showing.

I wasn't going to give up after I had gotten this far, especially now that I had all the functionality down. I did some googling and I found out that in order to load resources from a local file, these have to be listed in the manifest.json file under a property named `web_accessible_resources`. I modified my manifest to point to the image and style files:

...
"web_accessible_resources": [
"images/fattie.jpg",
"style/main.css"
],
...

I reloaded the extension, tried to order a curry and hey presto! I got nicely insulted: 

Screenshot 2013-10-30 15.26.39.png

Sweet! I was full, happy and excited.

I proceeded to run `grunt build` on my project folder and grunt took care of the rest. It created a `dist` folder with all the files, and then packaged them up into a zip file. I went on to create all the files that google requires you to provide in order to publish the app to the chrome app store.

No more takeaways for me, at least for a while. 

 

You can install the nofattie chrome extension here. (it would mean a lot to me if you took it for a spin and gave it a nice review)

You can check out the github repo here.