A Shiny New Me

  • Archive
  • RSS
AngularJS is the XAML of the web
Me. Just now.
  • 5 months ago
  • Comments
  • Permalink
  • Share
    Tweet

Your First Chrome Packaged App - Minecraft Style

As a follow-up to the previous blog post I did a webinar with Paul Kinlan of Google on building Chrome Packaged Applications. He covered all the awesome stuff you can do, and I did the bit about building the apps using Kendo UI. I specifically go over the CSP and how to use the Pkg.js plugin to work inside of a sandbox.

Check out the video I cut before the webinar here. Also for your viewing pleasure, I did the whole thing with a Minecraft head on.

  • 5 months ago
  • Comments
  • Permalink
  • Share
    Tweet

Chrome Packaged Apps And The CSP Dilemma

Chrome Packaged Apps were announced at I/O in July of this year and then quietly landed in Chrome proper just a few weeks back. If you missed it or you aren’t sure what Chrome Packaged Apps are, here is the rundown from the packaged apps site.

Packaged apps deliver an experience as capable as a native app, but as safe as a web page. Just like web apps, packaged apps are written in HTML5, JavaScript, and CSS. But packaged apps look and behave like native apps, and they have native-like capabilities that are much more powerful than those available to web apps.

This is a really exciting idea because you can create native desktop applications using HTML5 and all the user needs to have installed is Chrome. The apps run outside of the browser and look just like a normal application. Write it once, and you can instantly deploy it on Ubuntu, Windows, OS X and Chrome OS.

That’s some serious reach baby.

However there is a enormous caviat contained in the description above. Did you catch it?

Packaged apps deliver an experience as capable as a native app, but as safe as a web page.

Do you know why the web is generally “safe” for applications? It’s because they can’t execute code on your machine! The web is like a giant sandbox. Chrome Packaged Apps are running locally and they provide API’s that do have access to your local machine. So how are they safe?

Packaged App Developer - Meet CSP

Content Security Policy is what it’s called and it’s been around since extensions and web apps in Chrome. It’s not a new theory. The idea is to reduce attack surfaces by restricting what applications can do when you install them. This includes things like unsafe JavaScript execution (eval or new Function), reading from a dirty canvas, inline JavaScript and script source restrictions.

The difference is that in extensions and web applications you could relax the CSP. In Packaged Apps, you can’t

This presents you with an interesting problem if you are going to start building packaged apps. There are a lot of things that you cannot do. For instance, say you wanted to write a simple application that just displays “Hello World” in an alert.

<script>

  alert("Hello World!");

</script>

Simple enough, but doomed from the get go. If you run this in a packaged app, nothing will happen. If you inspect the app, you will see this error.

Per the CSP, you cannot use inline scripts. The resolution is of course to move this script into a file and then reference it in the page as a script include. If you do that, now when you run it you will still get no alert and your error message tells you why.

MAN! I REALLY want to display my “Hello World” in an alert. UX experts be damned.

It’s no problem though. I can just use a JavaScript UI framework to create a modal window. I’m going to need one eventually anyway. I’m going to use Kendo UI. I’ll just go ahead and drop it’s styles and js references into the page and use a Kendo UI Window. I’ll add a div to the page with my message.

<div id="hello"><h3>Hello World!</h3></div>

Then I’ll just show it as the Kendo UI window widget in my external JS file where I WAS doing the alert.

$("#hello").kendoWindow()
.data("kendoWindow")
.center();

Run that and still nothing. Inspect it and find a rather nasty error.

Invalid Template? I didn’t even DECLARE a template? What has happened is what will happen with virtually any JavaScript UI framework. Kendo UI uses new Function to create templates for it’s widgets. It does it internally. The template isn’t invalid, but that’s the best information Kendo can give you based on the fact that a core peice of the JavaScript language has been cut off.

This seems daunting. The restrictions are keeping us from doing things that are extremely simple on the web. It’s frustrating. There is so much win here, but the CSP is keeping us from getting at it.

The Sandbox

Google realizes this going to be an issue, so they have given you a restricted area where you can run your code with a significantly relaxed CSP. To do this, just make sure your page is marked as sandbox in the manifest.

"sandbox": {
  "pages": [ "index.html" ]
}

And with that one change, everything magically just works again! My Hello World app in all it’s shining glory. Don’t act like you aren’t impressed.

You may think this problem is solved and the CSP is just a bad memory, but unfortunately, this isn’t the case. You see, you can display a simple “Hello World” message in your favorite UI framework, but you cannot access any of the chrome extenion API’s. You are completely “sandboxed” from the extension. This means that none of the chrome. API’s are relative to you now, which sort of negates much of the benefit of building a packaged application.

The CSP just won’t die! Will you never be free? Free to build your application using your web dev skills and a pirated copy of Fireworks?

A New World Order

We are going to work around this, but first we need to set up a new architecture. It’s going to introduce a layer of complexity that will seem redundant, but is necessary to have a secure application while still being able to actually build the application.

What we are going to do is to separate the extension logic from the app logic. To do that, we are going to create a main.html file which will load another html file in an iFrame. Once that is done we can communicate between the two using Post Messages. Since we can pass messages between the two pages, we can trigger events in the extension and then down in the application by responding to these messages. We will be listening and responding to the message using pubsub or the observer pattern.

Fortunately for you, I have wrapped all of this up into a jQuery plugin called $.pkg. It’s based very heavily on the bloody jQuery pubsub library by Peter Higgins and is really just a twist in that it adds post messaging. It’s simple and it is your “rainbow bridge” between the extension and the sandbox. Let’s have a look at how this works.

Let’s setup a simple main.html which will be loaded by the extenion as the main page. This page will have the super tight CSP so we won’t do anything there except make chrome API calls. Don’t forget to set your sandboxed page as such in the manifest

<!DOCTYPE HTML>
<html lang="en-US">
<head>
  <meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" href="css/main.css">
</head>
<body>

  <iframe id="iframe" src="index.html" width="100%" height="100%" frameborder="none"></iframe> 

  <script src="js/jquery.min.js" ></script>
  <script src="js/pkg.js"></script>
  <script src="js/main.js"></script>

</body>
</html>

We include a main.js file since we can’t execute any inline JavaScript. That’s where we will put all our extension JavaScript code.

The index.html page is the sandboxed page where we can include any UI libraries, do inline JavaScript and generally just go nuts.

<!DOCTYPE HTML>
<html lang="en-US">
<head>
  <meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>

  <div id="hello">
    <h3>Hello World!</h3>
  </div>

  <script src="js/jquery.min.js" ></script>
  <script src="js/pkg.js"></script>
  <script src="js/kendo.all.min.js"></script>

  <script>

    $("#hello").kendoWindow()
               .data("kendoWindow")
               .center()
               .open();

  </script>

</body>
</html>

Now we have all the right structure and files included. Of note is that jQuery is included in both pages and so is the $.pkg plugin. It’s important that the plugin is included on both pages as they are completely agnostic of each other.

To use the $.pkg plugin to communicate between the two pages, we need to initialize it both inside and outside of the sandbox. It’s a singleton, or rather a static object so it has an init method which will want to know where to send messages.

In the main.js file we initialize it like this..

// get a reference to the iframe that holds the app
iframe = document.getElementById("iframe");

// initialize the pkg plugin. it needs to know
// who the recipient is which is the iframe where the app lives.
$.pkg.init(iframe.contentWindow);

In the index.html page we can initialize it directly inline in the page since it’s the sandbox. Passing in window.top here will get us a reference to the main.html page.

  // initialize the pkg plugin
  $.pkg.init(window.top);

The $.pkg plugin as 3 methods asside from the init.

  • send(string message, optional array arguments) - Sends a message to the receiver with the arguments specified in the arguments array
  • listen(string message, function(param1, param2, …)) - Responds to a message. Any parameters passed on the send method array will be mapped into the listen function in the corresponding order.
  • ignore(string message) - In the event that you don’t want to respond to a message anymore you need to specifically ignore it.

Now we are completely wired up and ready to start communicating. Let’s look at a simple example. Suppose you wanted to be able to pick a file off of the file system and display it in a modal window. First, implement the appropriate permissions in the manifest.json file to read from the File System.

“permissions”: [ “fileSystem” ]

The actual file picker dialog is created by calling the chrome.fileSystem.chooseFile method. This method wasn’t there before, but adding the permission to the manifest makes it available. Then we can specify a type of “openFile” which gives us a choose file dialog. The is going to be initiated by the sandbox so we will tell the $.pkg plugin to listen for a “/select/file” message (which we haven’t defined yet). When it hears that message, it will open the dialog picker. When the user actually selects a file, we’ll read it in with a file reader and then send the picked image into the sandbox along with the message “/file/loaded”

// subscribe to the /select/image message and return an image using
// the native api file picker in chrome packaged apps
$.pkg.listen("/select/file", function() {

    // open the file picker dialog. restrict to image types
    chrome.fileSystem.chooseFile({ type: "openFile", 
      accepts: [ { extensions: [ "gif", "jpeg", "jpg", "bmp", "png" ] } ] }, function(entry) {

      // this gives us a file entry. We just need to read it.
      entry.file(function(file) {

        // create a new file reader
        var reader = new FileReader();

        // create an event for when the file is done reading
        reader.onloadend = function(e) {
          // send the image into the sandbox
          $.pkg.send("/file/loaded", [ this.result ])
        }

        // read the file as a data URL
        reader.readAsDataURL(file);

      });

    });   
});

In the sandbox we need to send this “/select/file” message to the extension, as well as listen for the “/file/loaded” event that comes back.

<button id="selectFile" class="k-button">Pick File</button>

<script>

  (function ($) {

    // initialize the pkg plugin
    $.pkg.init(window.top);

    // create a new kendo ui window, but don't open it. just store
    // a reference in the "win" variable
    var win = $("<div></div>").kendoWindow({
      modal: true,
      visible: false
    }).data("kendoWindow");

    // button click event
    $("#selectFile").on("click", function() {

      // send an message to the extension to pick a file
      $.pkg.send("/select/file");

    });

    // subscribe to the file loaded event
    $.pkg.listen("/file/loaded", function(src) {
      // create a new image
      var img = new Image();
      img.src = src;
      // when the image has loaded
      img.onload = function()  {
        // assign this image as the content of the kendo ui window and open it
        win.content(img).center().open();
      }
    });

  }(jQuery));

</script>

And now we can pick our cat fight gif off the file system and display it in a modal window. Million dollar app? Maybe! You can never say what the demand for angry cat GIF’s is.

I’ve put together a bootstrap for working with Chrome Packaged Apps using the methodology described above and including the $.pkg plugin. It even contains Twitter Bootstrap. It’s bootstrap in a bootstrap.

It has a bunch of samples including how to pipe video down into the sandbox with the $.pkg plugin. You can fork it or download it to get started building Chrome Packaged Apps.

CSP Aint A Thing

CSP is no reason to deter you from building packaged apps. It ensures the that the user won’t be compromised by some malicous JSON, but doesn’t really stop you from building native apps the same way that you build web apps today, and doesn’t make you compromise on functionality.

  • 5 months ago
  • Comments
  • Permalink
  • Share
    Tweet
← Newer • Older →
Page 1 of 17

Portrait/Logo

I am Burke Holland. I am an Evangelist For Kendo UI. I built Instasharp. I live in Nashville. Click here to contact me.

I'm All Over The Interwebs

  • @burkeholland on Twitter
  • Facebook Profile
  • burkeholland on Youtube
  • burkeholland on Grooveshark
  • burkeholland on Foursquare
  • Google
  • My Skype Info
  • burkeholland on github
Follow @burkeholland

Twitter

loading tweets…

​
  • RSS
  • Random
  • Archive
  • Mobile

Effector Theme by Carlo Franco.

Powered by Tumblr