A Full Javascript Architecture, Part Two – Chrome Extension
In the first part of this series we saw how to create a simple Node server which tracks and broadcast tweets about the What’s Next event in real time.
In this one we’ll see how to develop a Google Chrome extension that will display the tweets thanks to the HTML5 notification API. The extension will also use the Local Storage API and the Socket.IO client side library.
Google Chrome Extensions
Google Chrome extensions are very easy to develop and have a good documentation to rely on.
With extensions you can interact with web pages, tabs, bookmarks and servers. To develop one, you simply need to use a set of HTML, CSS and JavaScript files. There is no other language needed, UI side included.
Just like a Chrome tab, extensions run in different processes to avoid taking down other extensions or the browser itself when an extension crashes.
Extensions Architecture
A Chrome extension is based on a manifest file that describe it’s architecture with Json encoded data. The manifest.json will be filled differently depending on what kind of functionalities an extension will use. Here is the basic one, only the name and version fields are required :
{ "name": "My First Extension", "version": "1.0", "description": "The first extension that I made." "background_page": "background.html", }
In the manifest.json you declare the name, version and description for your extension. What you also need to declare is the background page. The background page is the one that will hold the extension logic and will be executed when you lunch the browser or add the extension.
The What’s Next Extension
Prerequisites
First thing we need to do is to create the folder that will hold our extension. Create a folder named wsn and put in it the folders pics and style which contain the elements we will use for the UI. You can grab all the resources from the github repository dedicated to this post.
- wsn |-- pics |-- wsn.png |-- zenika-full.gif |-- zenika-small.png |-- style |-- style.css
The Manifest
Let’s create the manifest.json file for our extension.
- wsn |-- pics |-- style |-- manifest.json
{ "name": "What's Next Paris - Live !", "version": "1.0", "description": "Follow The What's Next Paris Tweets", "background_page": "background.html", "permissions": [ "notifications" ], "browser_action": { "default_icon": "pics/zenika-small.png", "popup": "popup.html" } }
As you can see, our extension name is “What’s Next Paris – Live !” and we set the version to be 1.0. We also set the background page name and add the “permissions” and “browser_action” sections:
- permissions
The permission field is used to configure the privileges that our extension requires. In our case we only need the permission to display notifications so we set “notification” in the array to gain access to this api. You can find more about permissions needed for other API here
- browser_action
The browser_action field allow us to add a button to the chrome user interface. The added icon will take the appearance of the one provided by the “default_icon” parameter. It needs to be present otherwise your extension will not start.
The “popup” parameter define the html page that will be opened when we click on the browser action icon. We will use this popup to display the last five tweets received and to allow the users to disable tweet notifications.
The Background Page
The background page is the center of the extension. It runs in the extension process and exists for the lifetime of our extension. Only one instance of it at a time is active. This is where we are going to establish the connection between the extension and the Node server.
The logic it contains is quite simple and is divided as :
- 1 – Include the Socket.IO library.
The library is included with the common <script src=”…”></script> tag, you don’t need to make the script available yourself since using the Socket.IO library on the server side does it for you. To make sure you can access it try browsing http://yourserver:port/socket.io/socket.io.js
- 2 – Initialize the socket and connect to the Node server.
To initialize a new connection we create a new Socket.IO object with the host and port of the Node server as parameters. The initialization part also include the tweet news counter and the tweets array where we they will be stored.
- 3 – Wait for incoming messages
Once the connection established, we listen for the “message” event that will be fired each time a new tweet is received by the socket. This is like when we were waiting for the “tweet” event back on the server side.
- 4 – Store and display the tweet.
When a new tweet comes in, we store it in an array
before displaying it to the user.
To create a notification we just need to call the createHTMLNotification() method (with the popup template in parameter) and call the show() method to render it. To close the popup we use a five second timeout and use the cancel() method. Users will be able to disable the notifications, in that case they will will be notified for a new tweet by a small counter of unread tweets visible on the extension’s icon. This behavior is implemented by the badge functionality with a call to chrome.browserAction.setBadgeText() method. When a user will click on the icon to see the news, the badge counter will be reinitialized.
Create the background.html page and add the following content:
- wsn |-- pics |-- style |-- manifest.json |-- background.html
<script src="http://hostname:8124/socket.io/socket.io.js"></script> <script> // Server configuration var SERVER_HOST = 'hostname'; var SERVER_PORT = 8124; // Nofication display time in ms. var NOTIFICATION_DISPLAY_TIME = 5000; // Regular expression to catch links in tweets. var EXP = new RegExp('(http://[a-zA-Z0-9./-]+)','gi'); var LINK = '<a href="$1" target="_blank">$1</a>'; // Store the last weet received. var tweet; // Store the five last received. var tweets = []; // Counter to store and display the number of new tweets when notifications are disabled. var news = 0; // Socket.IO client creation and connection with Node server. var socket = new io.Socket(SERVER_HOST, { port : SERVER_PORT }); socket.connect(); // Listening to the 'message' event, it will be fired // each time our Node server send us a new tweet. socket.on('message', function(data){ // Parsing the data to make a json object tweet = JSON.parse(data); // Converting links in links... tweet.text = tweet.text.replace(EXP, LINK) // Storing only the five last tweet to display them in the popup if(tweets.length == 5) tweets.pop(); tweets.unshift(tweet); // Counter incrementation news++; // Display the notification if the user allow it or update // the counter to alert the user. if(window.localStorage.noticationEnable == 'true'){ var notification = webkitNotifications.createHTMLNotification("notification.html"); notification.show(); // Hide the notification after the configured duration. setTimeout(function(){ notification.cancel(); tweet = null; }, NOTIFICATION_DISPLAY_TIME); }else{ // Set the text layered on the icon chrome.browserAction.setBadgeText({ text : news + ""}); } }); // Reset the badge counter when a user click on the browser action icon. function resetBadgeText(){ news = 0; chrome.browserAction.setBadgeText({ text : ""}); } </script>
The Notification
Chrome offers two type of notifications in its API, simple text and html. We’ll use html because it allows the use of links and it remains the most elegant solution.
Here is what our notification will look like :
Create the notification.html file and add the following content:
- wsn |-- pics |-- style |-- manifest.json |-- background.html |-- notification.html
<html> <head> <link href="style/style.css" rel="stylesheet" type="text/css"> <script> // We retrieve the new tweet from the background page. var tweet = chrome.extension.getBackgroundPage().tweet; </script> </head> <body> <div id="notification"> <div id="picture"> <script>document.write('<img src="' + tweet.picture + '" />');</script> </div> <div id="content"> <div id="user"> <script> document.write('<a href="http://twitter.com/' + tweet.user + '" target="_blank">@' + tweet.user + '</a>'); </script> </div> <div id="tweet"> <script>document.write(tweet.text);</script> </div> </div> <div style="clear:left;"></div> </div> </body> </html>
The most important thing in this is the use of the chrome.extension.getBackgroundPage() method provided by the API to access an object located in the background page.
The Popup
By clicking on the browser action icon, users can view the last five tweets in a popup page previously defined in the manifest.json. Just like we did in the notification.html file, we are going to use the chrome.extension.getBackgroundPage() to retrieve the array of tweets and iterate over it to display them.
The popup is also the place where users will be able to switch off notifications, for that we are going to use the localStorage API. Thanks to the API, user’s preference is sa
ved even when the browser is closed and reopened. You can learn more on the LocalStorage api and offline mode here.
Create the popup.html file at the root of you project and add the following content:
- wsn |-- pics |-- style |-- manifest.json |-- background.html |-- notification.html |-- popup.html
<html> <head> <link href="style/style.css" rel="stylesheet" type="text/css"> </head> <body> <div id="head"> <script> document.write('<img src="' + chrome.extension.getURL('pics/wsn.png') + '" alt="www.whatsnextparis.com"/>'); </script> <div>The first conference of its kind in France, the future of IT technologies 26-27 May 2011.</div> <a href="http://www.whatsnextparis.com" target="_blank" title="What's Next Paris - 26 27 Mai 2011">www.whatsnextparis.com</a> </div> <hr/> <div id="config"> <input id="chkbox" type="checkbox" onchange="switchNotification(this);"/> <label>Disable notifications</label> <div></div> </div> <hr/> <div id="wall"> <script> // We retrieve the tweets array from the background page. var tweets = chrome.extension.getBackgroundPage().tweets; for ( var i = 0 ; i < tweets.length ; i++ ){ document.write('<div class="tweet">'); document.write('<div class="picture">'); document.write('<img src="' + tweets[i].picture + '" alt="picture"/>'); document.write('</div>'); document.write('<div class="content">'); document.write('<div class="user">'); document.write('<a href="http://twitter.com/' + tweets[i].user + '" target="_blank">@' + tweets[i].user + '</a>'); document.write('</div>'); document.write('<div class="text">'); document.write(tweets[i].text); document.write('</div>'); document.write('</div>'); document.write('<div style="clear:left;"></div></div>'); } </script> </div> <div id="footer"> <script> document.write('<img src="' + chrome.extension.getURL('pics/zenika-full.gif') + '" alt="Zenika - Architecture Informatique"/>'); </script> <a href="http://www.zenika.com" target="_blank" title="Zenika - Architecture Informatique">www.zenika.com</a> </div> <script> // Each time the popup is opened we clear the tweets badge counter chrome.extension.getBackgroundPage().resetBadgeText(); // Checking the box according to the localStorage. if(window.localStorage){ if(window.localStorage.noticationEnable == 'true'){ window.document.getElementById('chkbox').checked = false;; }else{ window.document.getElementById('chkbox').checked = true; } } // Change notification display preference. // User preference is stored in localStorage. function switchNotification(checkbox){ if(window.localStorage){ if(checkbox.checked){ window.localStorage.noticationEnable = 'false'; }else{ window.localStorage.noticationEnable = 'true'; chrome.extension.getBackgroundPage().resetBadgeText(); } } } </script> </body> </html>
After a few tweets, this is what the popup will look like :
Installation
To install your extension open your browser, click on the Chrome configuration icon and go to the Tools -> Extensions manager. You should see a Developer mode link on the top right.
When the developer mode is enabled, you can add your extension by using the Load unpacked extension… button.
Select the root folder of your extension and confirm. Your extension is now installed and you should see a brand new icon next to the Chrome configuration one. To refresh your project after a file modification you only need to hit Reload.
Download
The packaged version of the What’s Next extension is available here for a quick installation.
Conclusion
We have seen how easy it is to develop a chrome extension that communicate with our Node server in real time. This ease comes from the facts that today a large portion of developers know core technologies behind website and the choice made by Google to keep using this technologies to enhance their browser. Developing an extension is done without using any tools or plug-in, productivity is excellent because there is no need to restart the browser after an extension update. The Chrome API documentation is very complete and most features are illustrated which makes it even easier task for a developer to understand the concepts.
In the next post we will return to the server side and link our Node server with a MongoDB database to store the tweets.