How to detect buggy IndexedDB implementations

Posted by filed under Browser Struggle, Goodies to go, How to.

After years of struggling with persistent client side storage, IndexedDB is finally available on all major browsers nowadays. All good, right? Well, almost. There are some implementations out there that are either buggy (Safari) or are lacking features (IE). It’s not a show-stopper if you’re just looking for a simple key-value store, but if you want to make use of more IndexedDB features, you need to know what implementation you are currently dealing with.

The obvious solutions at hand for detecting this are either doing user-agent sniffing, or running extensive feature tests. Both seem, well, less than optimal. But, there’s a way in between!

By feature-detecting specific implementation bugs, one can deduce the implementation type. And, thanks to the glorious interwebs, some nice folks have done this and posted their solutions online. Let me summarize and explain what they did:

Detecting IE

IE’s implementation is quite good, but it lacks a couple of features. Mainly, these are compound indexes and multi-entry indexes – bummer if you need those. Here’s the detection code:

try {
  IDBKeyRange.only([1]);
} catch (e) {
  // IE!!
}

[Code found in this SO post: http://stackoverflow.com/a/26779525]

The reason this works is due to another limitation on IE: You can’t have arrays as keys. This issue is unique to IE’s implementation, so if you catch this, you’re on IE.

Detecting Safari

Safari’s implementation has quite a couple of bugs, seemingly spread around at random. Sadly, none of those can be detected in a synchronous fashion, but no need to test for all of them. Here’s the detection code:

var openRequest = indexedDB.open('test', 1);

openRequest.onupgradeneeded = function (evt) {
  var db = evt.target.result;
  db.createObjectStore('one', {
    keyPath: 'key'
  });
  db.createObjectStore('two', {
    keyPath: 'key'
  });
};

openRequest.onsuccess = function (evt) {
  var db = evt.target.result;
  var transaction;
  try {
    transaction = db.transaction(['one', 'two'], 'readwrite');
  } catch (e) {
    // Safari!!
    return;
  }

  transaction.oncomplete = function () {
    db.close();
    // All good
  };
};

[Code found in this Gist by Nolan Lawson: http://bl.ocks.org/nolanlawson/raw/c83e9039edf2278047e9/]

This is a pretty annoying one, and it’s unique to Safari – as well as not fixed. If you catch this, you’re on Safari.

Making a helper function out of those

Let’s put those two tests in a convenient function, and wrap it in a UMD wrapper so it can be consumed via script tag, as well as via module loaders. Oh, and let’s make sure this also can be run inside a worker:

(function (name, definition, global) {
  if (typeof define === 'function') {
    define(definition);
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = definition();
  } else {
    global[name] = definition();
  }
})('detectIDB', function () {

  var detectIDB = function (onResult, dbName) {
    if (typeof onResult != 'function') {
      throw new Error('No result handler given.');
    }
    dbName = dbName || '__detectIDB_test';

    var env = typeof window == 'object' ? window : self;
    var idb = env.indexedDB || env.webkitIndexedDB || env.mozIndexedDB;
    var keyRange = env.IDBKeyRange || env.webkitIDBKeyRange || env.mozIDBKeyRange;

    // IE detection idea found at http://stackoverflow.com/a/26779525
    try {
      keyRange.only([1]);
    } catch (e) {
      onResult(detectIDB.IE);
      return;
    }

    // Safari detection by Nolan Lawson: http://bl.ocks.org/nolanlawson/raw/c83e9039edf2278047e9/
    var openRequest = idb.open(dbName, 1);

    openRequest.onupgradeneeded = function (evt) {
      var db = evt.target.result;
      db.createObjectStore('one', {
        keyPath: 'key'
      });
      db.createObjectStore('two', {
        keyPath: 'key'
      });
    };

    openRequest.onsuccess = function (evt) {
      var db = evt.target.result;
      var transaction;
      try {
        transaction = db.transaction(['one', 'two'], 'readwrite');
      } catch (e) {
        onResult(detectIDB.SAFARI);
        return;
      }

      transaction.oncomplete = function () {
        db.close();
        onResult(detectIDB.COMPATIBLE);
      };
    };
  };

  detectIDB.COMPATIBLE = 'compatible';
  detectIDB.IE = 'IE';
  detectIDB.SAFARI = 'Safari';

  return detectIDB;

}, this);

Sadly, Promises are not available on IE, that’s why there’s the good old callback style.

Usage

<!DOCTYPE html>
<title>Detecting buggy IndexedDB implementations</title>

<script type="text/javascript" src="detectIDB.js"></script>
<script type="text/javascript">
  detectIDB(function (result) {
    switch (result) {
      case detectIDB.COMPATIBLE:
        alert('IndexedDB fully compatible!');
        break;
      case detectIDB.IE:
        alert('This is an IE, some features are missing.');
        break;
      case detectIDB.SAFARI:
        alert('This is a Safari, there are bugs in this implementation.');
        break;
    }
  });
</script>

Download

This is available on GitHub, together with the usage example: https://gist.github.com/jensarps/15f270874889e1717b3d

[Comments are automatically closed after 30 days.]

Comment via Google+