Local Storage is not supported with Safari in private mode

Sadly enough, we discovered today that our web application was crashing when browsing in private mode on Safari (this happened during a product demo of our CEO 😅). It was a bit unfortunate that we had to discover it like that and that none of us had tested it with Safari in private mode, a bit of an edge case.

It speaks for itself that you don't use things like local storage or session storage for important features but just as little improvements to features, progressive enhancement as we call it. We used it to remember a certain preference, so it was not really a major issue if it was not supported by older browsers like IE 7, which we don't support anyway.

However, Safari decided to throw an exception in private mode when trying to access local storage or session storage; QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota.

The issue is present on both desktop Safari (Mac OS X) and Mobile Safari (iOS). We didn't wrap it in a try catch and didn't catch the exception which made our application crash. That's really just my bad. Catching the exception will prevent it from crashing other application logic.

Set item in session storage:

try {
  sessionStorage.setItem('key', 'value');
} catch (e) {
  // handle exception e, provide fallback, ..
}

Get item from session storage:

try {
  sessionStorage.getItem('key');
} catch (e) {
  // handle exception e, provide fallback, ..
}

I made a small RequireJS wrapper class that handles the exceptions but doesn't provide fallback. In theory we could fallback to cookies, but it would be wasteful to polute every request with cookies for something like that. So I ended up not providing a fallback, it's just an enhancement for whoever supports it.

define([], function() {
  'use strict';

  var ClientStorage = {};

  /**
   * Try to set an object for a key in local storage
   * @param {string} k
   * @param {object|string} v
   * @return {bool}
   */
  ClientStorage.setItem = function (k, v) {
    try {
      localStorage.setItem(k, v);
    } catch (e) {
      console.log(e);
      return false;
    }

    return true;
  };

  /**
   * Try to get an object for a key in local storage
   * @param {string} k
   * @return {object|string|null}
   */
  ClientStorage.getItem = function (k) {
    try {
      return localStorage.getItem(k);
    } catch (e) {
      console.log(e);
    }

    return null;
  };

  /**
   * Try to set an object for a key in session storage
   * @param {string} k
   * @param {object|string} v
   * @return {bool}
   */
  ClientStorage.setSessionItem = function (k, v) {
    try {
      sessionStorage.setItem(k, v);
    } catch (e) {
      console.log(e);
      return false;
    }

    return true;
  };

  /**
   * Try to get an object for a key in session storage
   * @param {[type]} k [description]
   * @return {object|string|null}
   */
  ClientStorage.getSessionItem = function (k) {
    try {
      return sessionStorage.getItem(k);
    } catch (e) {
      console.log(e);
    }

    return null;
  };

  return ClientStorage;
});