Introducing MemoryStorage

Web Storage API

web-storage
HTML5 gave us this beautiful thing: consistent cross-browser local storage of ‘vast amounts’ of data on the visitor’s computer in the form of the Web Storage API. The default quota is 5MB, which is huge compared to cookies. I’m using it in the development of Bridal App. It allows the app to respond near-instant to user actions and continue to function even when offline. All modern browsers support it (on desktop as well as on mobile) so life is great. Right?

To support or not to support

incognito
Well, as always in web development, it turns out there is support and ‘support’. The problems arise with the browsing mode that all major browsers now feature called ‘Incognito’ or ‘Private Browsing’ or ‘InPrivate’ mode. In this mode, the user’s browsing session will leave no traces behind on his computer once he closes his browser window. Naturally, this mode is quite at odds with Web Storage storing MB’s of data on his local system. So browser vendors had to come up with a solution for this. They had already solved this problem with cookies, which also leave traces behind on your computer, by just allowing the websites to write these cookies and just throwing them away after the browsing session no matter how long their expiration date was set for. So it seemed reasonable to expect them to do the same with localStorage. And they did. Almost all of them…

Safari in Private Browsing mode

safari
Unfortunately, Safari opted to provide a ‘working’ localStorage object, but with it’s quota set to 0. This means it will always be empty and any attempt to write to it will fail with an out-of-quota exception. Although I sorta understand the reasoning behind the quota of 0, in practice it is very inconvenient and even has some privacy-related side-effects. For one, many applications thought they were just going to write a little bit of data to localStorage and would never reach the quota of 5MB (which is practically universally the default value) and so wrote code like this:

if ('localStorage' in window) {
  // Yippie! Here's for progress!
  localStorage.setItem('user-settings', 'some very important user settings');
}

All these applications will fail when opened in Safari’s Private Browsing mode. It took developers a while to figure out how to feature-detect localStorage, in part because Safari gives us a fully functional, but completely useless localStorage object. It’s empty and when we write to it we get an exception. You may wonder why they did not just leave it completely out, so at least the feature-detect would be simple. Also, localStorage has been used as a messaging channel, allowing multiple pages/tabs on the same domain to communicate with each other using localStorage as a drop-box for messages. This is not possible with the zero-quota solution Safari went with. Also a side-effect of this is that we can now detect Private Browsing mode on Safari due to this. It’s the only browser that does this: give us an empty object which is ‘full’. Want to scare people in Private Browsing mode? Show them a message saying: ‘YOU ARE NOT PRIVATE. your activity has been logged’. I’m not going to do this, but someone, somewhere probably is. I wonder whether Safari will persist with their strategy if that happens, but until then we have this weird situation.

So what to do?

Polyfill it! What else? 🙂
Of course there is an issue, because the localStorage object is read-only and can’t be replaced. So hopefully you did not write all your code directly to the localStorage object but used some read or write functions instead. Our basic strategy is like this:

  1. Detect whether we have a working localStorage
  2. If not, create a polyfill in it’s stead
  3. Assign the working storage object to some variable which we’ll be using when we need to read or write something.

I’m using Rhaboo in Bridal App and I had to hack it to make it work on Safari in Private Browsing mode and MemoryStorage is a direct result of that work. What started out as a small stub is now a separate project so it can be used by anyone who needs a polyfill for localStorage.

Usage example

Here is how you would feature-detect a working localStorage and implement a fallback to MemoryStorage if it’s not available:

var DB;
try {
    var x = 'test_localstorage_available_' + Date.now();
    localStorage.setItem(x, x);
    var y = localStorage.getItem(x);
    localStorage.removeItem(x);
    if (x !== y) {throw new Error();}
    DB = localStorage;
}
catch(e) {
    DB = new MemoryStorage('my-cool-app');
}

If the code reaches the line DB = localStorage, all is well. If it does however throw an exception, the catch block will be reached and we instantiate a new MemoryStorage object instead. From that point on we can write to DB in the save knowledge that it will not fail (as long as we stay under our quota of 5MB). The string 'my-cool-app' is meant as a unique ID for the storage space, providing for an isolated storage area. Only other MemoryStorage objects with the same ID will share the same storage. If no ID is provided, a global storage space will be used.

I’m inviting everyone for feedback on this project.

Project homepage

The GitHub page

NPM module page

Issue tracker

3 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s