Charles Engelke's Blog

January 8, 2012

Chrome Web App Bookshelf – Part 7 of 7

Filed under: Uncategorized — Charles Engelke @ 1:36 pm

Note: this is part 7 (the final part) of the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, part 3 finally called an Amazon web service, part 4 parsed the web service result, part 5 was useful enough to publish, and part 6 covered publishing it at my own website. This post finishes the series by publishing in the Chrome Web Store instead.

I’m almost done with getting my app out into the world now. I just have to put in the Chrome Web Store. Once that’s done I intend to update it with new features from time to time, but probably won’t post about that in any detail. Instead, I’ve put this project on Github. If you’re interested, you can follow its development there.

Following my practice to date I haven’t bothered to read any of the documentation about the store. Instead I just looked for information on how to develop for it, starting with the Settings icon in the upper right corner of the page:

Settings icon in Chrome Web Store

When I clicked on the gear icon the drop-down menu showed a choice for Developer Dashboard, so I chose that. The resulting page looks like it’s going to guide me through the process pretty easily. There’s a link to “Start uploading your apps now!“. Seems promising…

Developer dashboard section to Upload your app

It sure looks easy. I’m not supposed to upload the CRX file, just a ZIP of the directory. I just posted such a file for my previous entry. I’m a bit worried because I put in an auto-update URL that doesn’t make sense for the app store, so I’m going to remove both the homepage and update URLs from the manifest before uploading to see what happens. I’m also going to increment the version number to reduce my own confusion.

When I uploaded the resulting ZIP file I got a page showing how the web store sees it, starting with the icon:

App summary with placeholder icon

Where’s my icon? A bit further down the page I’m offered the chance to upload another icon, saying it should be 96 pixels square. But instead I just uploaded my 128 by 128 icon again, and it took it and it looks good:

Chrome store summary showing my icon

Going down the page, I’m asked for a detailed description, so I filled it in as follows:

Want to keep track of books that haven’t been published yet, so you can decide whether to buy them when they’re ready? This app allows you to add books by ISBN or Amazon’s ASIN (including Kindle books) and keep a list showing their scheduled release date and shipping status. When you’re ready to buy, just follow the link from the app.

This app uses Amazon’s Product Advertising API, so you will need an Amazon Web Services account to use it. Accounts are free to register, and this particular API incurs no charges.

Next, I’m asked for a screen shot and at least one “promotional image”. Huh. Okay, I’ll make them up. My promotional image is just a large version of the icon on a dark background. After I filled in the rest of the form as best I could, I saved the draft, returning to the dashboard:

Dashboard showing current status with publish link

Okay, let’s try it. I pressed Publish. And got a confirmation:

Publish confirmation image

Okay, let’s try it. And… well, I was kind of expecting this:

Pay $5 now

I need to pay this once to register. That’s not much, so I went ahead and paid it through Google. I then had to click Publish again, and the listing now shows an option for Unpublish. I guess I’m done. When I click the link showing for the item, it looks like I’ve got it in the store!

My app in the Chrome Web Store

And when I click to install it, it first shows me the permissions it requires:

Install confirmation

And it installed it just fine. I’ve got two nearly identical apps showing now, the previously packaged one and the new one from the store. Chrome thinks they are different because they have different unique IDs. They don’t share storage either. I’ll leave the old app there for a while, but don’t intend to update it.

Will this make the app sync to each of my browsers? It hasn’t as of this writing, but I’ll give it some time. Enhancements to this app will be posted on my Github repository, and published in the Chrome Web Store for anyone that wants to keep following it.

[Update a few hours later: the extension did finally sync to my other computers! The data did not, which I expected. I can either do that with some other facility (perhaps AWS SimpleDB) or wait until Chrome adds an API for that, which I think is in the works.]

Advertisement

January 7, 2012

Chrome Web App Bookshelf – Part 6

Filed under: Uncategorized — Charles Engelke @ 3:52 pm

Note: this is part 6 of the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, part 3 finally called an Amazon web service, part 4 parsed the web service result, and part 5 actually was somewhat useful. The series is almost done now. This post will cover packaging and privately publishing the app, and the next and final post will cover putting it in the Chrome Web Store.

There’s more functionality I want to add to the app eventually (updating the data on saved books, deleting books from the list, and even synchronizing the list of books between PCs) but the purpose of this series is to show how to create and publish Chrome web apps. My app is just barely functional enough now to publish, so I’m going to go ahead and do that.

Since my app contains software and images from others I’m going to have to add some acknowledgment of that fact. I want to be sure I’m complying with the license conditions when I distribute those pieces, and I should also specify whatever the license conditions are of my app. So I added a file called LICENSE to my project directory, spelling out my license terms. You can see the current version of that file here. As you can see, I chose the MIT license for my app because I feel that’s one that least encumbers the users.

One licensing issue I encountered was that the Stanford JavaScript Crypto Library includes patented code, and the conditions of its use apparently require either purchasing a license to the patent or using only the GPL, at least in the United States. I’m not a lawyer so I might not be understanding this clearly, but I don’t want to violate the terms or intent of that patent holder. Other than that issue the library can be licensed under the BSD license, which seems to be compatible with the MIT license I chose. That library includes tools to build subsets of the whole thing, so that’s what I did. The patented code is used in cipher algorithms, so I built a library without ciphers (in fact, it has the minimum functionality that I need) and am including only that. I believe that means I’m fine distributing it as part of my MIT licensed app.

I also added copyright notices to the files I created: main.html, main.js, aws.js, and main.css. I don’t think that the manifest file is actually a creative work, so didn’t put any copyright notice in it. I don’t even know where it could have gone had I wanted to add one.

I know a lot of people think that explicitly specifying copyright and license conditions aren’t really necessary unless you want to restrict use of your work, but as someone who builds commercial software for a living I can tell you that they’re very important even if you don’t want to restrict that use. Without clear indication of the conditions, nobody would risk reusing what you created in any professional or commercial endeavor.

Okay, the app has been slightly polished up, is (just barely) functional enough to actually use, and has clear claims and acknowledgment of ownership and licensing. I’m ready to publish it. But how?

I started by recognizing that once I publish it I will want to update it at times. Every update should have a higher version number. So far I’ve left that number as “1” in the manifest, but for publishing I’ll take advantage of the fact that Chrome allows that version number to be up to four integers, separated by periods, and start over at version “0.0.0.1”. With that done, I can package the app using any running copy of the Chrome web browser.

First, open the Extensions panel by choosing Tools/Extensions from the drop-down menu you get when you click the wrench icon in the upper right hand corner of the browser. When I do that, I see the unpacked version of the app I have been working on:

Extensions panel showing the unpacked app

When I click the Pack extension… button I get the following dialog box:

Pack Extension... dialog box

I enter the directory I have been working in, leave the Private key file field empty, and click Pack Extension. Chrome tells me what it has done now:

Message shown after packing

It created a Chrome extension file (ending in .crx) and a new key file for me to use (ending in .pem), and told me where they are. Next I opened the file in Chrome by dragging and dropping on to the browser, and was asked whether to install it. After I said yes, the Extensions panel showed the app twice:

Extensions panel showing two versions

The top one is the unpacked app I’ve been working on, and the lower one is the actual packaged application. I then removed the unpacked version and tried out the packed one. It worked!

But it’s still not really ready. If I create a new version there’s no way that Chrome will know about it. If I host the app on a web site I can configure the manifest so that Chrome will regularly check for updates and install them if they exist. But to make that happen I have to also create an XML file describing the current version of the app. I guess Chrome doesn’t want to have to download a whole app just to see if it’s updated, and prefers a small XML file for that. I have to put the URL for that XML file in the manifest. While I’m was at it I also added the URL of a home page for more information about the app and incremented the version number. The manifest file now looked like:

{
   "name": "Books to Buy",
   "description": "Keep a list of books to buy on Amazon, with their price and availability",
   "version": "0.0.0.2",
   "app": {
      "launch": {
         "local_path": "main.html"
      }
   },
   "icons": {
      "16":    "icon_16.png",
      "128":   "icon_128.png"
   },
   "homepage_url": "http://www.bibliote.ch/",
   "permissions": [
      "https://webservices.amazon.com/*"
   ],
   "update_url": "http://www.bibliote.ch/files/bookshelf.xml"
}

The homepage_url and update_url entries are new. Of course, since I’m referring to an XML file at a particular URL I’d better create that file and host it at the URL. The format of the XML file is pretty simple; I just copied an example and replaced values with the right ones for my app:

<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
  <app appid='mpcejinifahkdfhnfimbcckdllahpbmg'>
    <updatecheck codebase='http://www.bibliote.ch/files/bookshelf.crx' version='0.0.0.2' />
  </app>
</gupdate>

I had to fill in the correct codebase value with the URL I am hosting the app file itself at, the version value with the current version number, and appid with the correct value.

appid? What’s that?

Chrome assigns a unique ID to every application it creates. That’s the value needed here. To see it I just looked at the Extensions panel and clicked the gray arrow to the right of my application’s icon, then I cut and pasted it to here.

After I rebuilt the extension with the new manifest (I had to fill in the Private key file name this time, matching the one created the first time I packaged the app) I uploaded the XML and CRX files to the URLs shown in the manifest and XML file. I also set the Content-type of the CRX file to application/x-chrome-extension, though that’s probably not needed given that the file name ends in .crx. I uninstalled my app to get a clean environment and visited www.bibliote.ch/files/bookshelf.crx with Chrome. I was asked whether to install the app. When I agreed, it installed and worked file.

So, does auto-updating work? I changed the version numbers in the manifest and XML file, packaged the new version, and uploaded the new XML and CRX files. And, a little later, the new version showed up in my browser. Success!

If you want to try this yourself, the entire application directory I used for this is available here. You’ll have to create your own XML file by copying the one above and changing the values as needed.

I could stop here but there is one Chrome app feature I want and don’t yet have: synchronizing extensions across different browsers. From my reading of the documentation that should be working now, but it isn’t. Either applications like mine are treated special and not synchronized, or else you need to publish in the Chrome Web Store to get this functionality. I’d like to explore how that store works anyway, so I’ll try it out next time and see if synchronization starts working.

January 1, 2012

Chrome Web App Bookshelf – Part 5

Filed under: Uncategorized — Charles Engelke @ 6:21 pm

Note: this is part 5 of the Bookshelf Project I’m working on. Part 1 was a Chrome app “Hello, World” equivalent, part 2 added basic functionality, part 3 finally called an Amazon web service, and part 4 parsed the web service result. This part will actually reach the point of usefulness!

The app so far isn’t really useful because it doesn’t remember books to buy later. And that was the whole point. So instead of just displaying the response I’m going to save it persistently using the localStorage capability of HTML5. That’s simply an object (a property of the global window object) that retains its value even when a web page (or the browser as a whole) is closed. Each web site has its own localStorage area. There’s an API for it and it also can work pretty much like a regular JavaScript object. So I’m going to keep all the response data in it.

localStorage does have some pretty severe restrictions. The biggest one is that it can only store strings. At one time it was supposed to be able to store any object that could be serialized into a string, but as of now Chrome seems to only want strings there. So I’m going to have to serialize and deserialize all my objects to store them.

localStorage gives me a solution for my other problem, too. I can’t distribute the app with my Amazon Web Services credentials in it, but I can save the proper credentials persistently and let each user save his or her own values. So the app will have to have two faces now. One is the main app I’ve been working on, and the other is a simple one to use to enter the credentials. If there are no credentials in localStorage I’ll show the settings screen, otherwise I’ll show the main app.

So the main.html page now needs a body with two divs, one for each situation:

   <div id="settings">
   </div>
   <div id="application">
   </div>

It also needs to add a link to a stylesheet in the head of the document, as follows:

   <link rel="stylesheet" type="text/css" href="main.css" />

And, of course, it needs a stylesheet. It will start out very simple, just a rule to hide both divs. The JavaScript will show the proper div once it examines localStorage:

#settings, #application {
   display: none;
}

So, what about the main.js program? It starts out by again waiting for the document to be ready, declaring “global” variables, and setting up event handlers for buttons, but then it immediately checks localStorage to see whether to show the settings page or not:

   if (localStorage.accessKeyId && localStorage.secretAccessKey) {
      showApplication();
   } else {
      showSettings();
   }

The showSettings function is very simple:

   function showSettings(){
      $("#settings").show();
   }

All that is in that div are a couple of labeled data entry fields and a button to save the values entered into them. When that button is clicked, the handler just saves them in localStorage and starts the application:

   function saveSettings(){
      localStorage.accessKeyId = $("#accesskeyid").attr("value");
      localStorage.secretAccessKey = $("#secretaccesskey").attr("value");
      $("#settings").hide();
      showApplication();
   }

And showApplication just shows the proper div and creates an AWS object. The rest of its functionality happens when the button is clicked to add a new book to the list.

   function showApplication(){
      aws = new AWS(localStorage.accessKeyId, localStorage.secretAccessKey, "engelkecom-20");
      $("#application").show();
   }

That “engelkecom-20” is my AWS associate ID. I’ll hard code that so that any URLs created by the application include it. That way, should anyone ever use this app I have a chance to make some commissions from Amazon sales.

The remaining big difference is that the result from looking up an item at Amazon will be saved in localStorage, and the results div will be replaced by a results unordered list. When the app first starts up and each time a book is looked up that list will be redrawn. This is accomplished by first replacing the reference to insertResponse in the aws.itemLookup function call with a reference to a new function called saveResponse:

      function saveResponse(response){
         localStorage.setItem("asin_"+response.asin, JSON.stringify(response));
         displayBookList();
      }

I’m naming each item with the prefix asin_ followed by Amazon’s ASIN for the item. That serves two purposes. It lets me look up a book directly by Amazon’s unique ID when I need to, and it lets me recognize which fields in localStorage hold book data and which don’t. Since I can only reliably store strings, I use the build-in JSON.stringify function to convert the object to a string without losing any information.

The displayBookList function will also be called at the end of the new showApplication function. In each case, it clears out all the items in the results unordered list and adds all the saved items back to it:

   function displayBookList(){
      var books = [];
      var i, key;

      $("#results li").remove();

      for(i=0; i<localStorage.length; i++) {
         key = localStorage.key(i);
         if (key.substr(0,5)=="asin_") {
            books.push(JSON.parse(localStorage.getItem(key)));
         }
      }

      books.sort(byReleaseDate).forEach(function(book){
         insertBook(book);
      });
   }

This first removes all the list items in the results list, then builds an array of all the books found in localStorage, and then calls insertBook to insert each book in the (by now sorted) array into the results list. insertBook is pretty much the same as the old insertResponse function shown before, with just a bit different HTML markup in it, so I’m not going to include it here. Note the use of JSON.parse to convert the string back to a JavaScript object.

Does it all work? Let’s see. First, when first launched it should show the settings panel, and it does:

Settings panel

After credentials have been saved, it should show pretty much the old application:

App screen with no books

And, once a few books have been added, it should show a list of them:

App showing a list of books

And even if the browser is closed and later re-opened, it still shows the list of books.

This was my core goal for this app. It’s ugly in a lot of ways (not just appearance; the code could use a lot of cleaning up, too). But I’ll see to that later. Now it’s time to move on and look at how to distribute the app. First, I’ll package it and try that out. Then I’ll try hosting it at a known address. Finally, I’ll put it in the Chrome Web Store. I’ll also put up a zip file of all the code created so far, so anybody who likes can try things out.

All of that will start in my next post.

Blog at WordPress.com.