Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Don't let localStorage/sessionStorage setItem throw errors in Safari Private Browsing Mode
// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem
// throw QuotaExceededError. We're going to detect this and just silently drop any calls to setItem
// to avoid the entire page breaking, without having to do a check at each usage of Storage.
if (typeof localStorage === 'object') {
try {
localStorage.setItem('localStorage', 1);
localStorage.removeItem('localStorage');
} catch (e) {
Storage.prototype._setItem = Storage.prototype.setItem;
Storage.prototype.setItem = function() {};
alert('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.');
}
}
@Download

This comment has been minimized.

Copy link

@Download Download commented Aug 21, 2015

I don't think an alert is much better than an error to be honest.

How about not using localStorage directly, but only through an alias:

var DB;  // our alias for localStorage
try {
    var x = 'test-localstorage-' + Date.now();
    localStorage.setItem(x, x);
    var y = localStorage.getItem(x);
    localStorage.removeItem(x);
    if (y !== x) {throw new Error();}
    DB = localStorage; // localStorage is fully functional!
} catch (e) {
    DB = new MemoryStorage('my-app'); // fall back to a memory-based implementation
}

// From here on out we can use DB without worries
DB.setItem('Test', 'testing');
alert(DB.getItem('Test'); // alerts 'testing'
for (var i=0; i<DB.length; i++) {
  console.info(DB.key(i) + ':' + DB.getItem(DB.key(i))); // logs all keys and values to the console
}
// ....

MemoryStorage is a lightweight, memory-backed implementation of the Web Storage API. It makes the app fully functional, except that all data is lost when the page is reloaded... But the lifetime of the data in a web storage is never guaranteed anyway and the user shouldn't be surprised because leaving no traces is the whole point of Private Browsing mode.

@lifeiscontent

This comment has been minimized.

Copy link

@lifeiscontent lifeiscontent commented Feb 20, 2016

'use strict';

// implement memory store spec'd to Storage prototype
(function(window) {
  var items = {};

  function MemoryStorage() {}

  MemoryStorage.prototype.getItem = function(key) {
    return items[key];
  };

  MemoryStorage.prototype.setItem = function(key, value) {
    items[key] = value;
  };

  MemoryStorage.prototype.key = function(index) {
    return Object.keys(items)[index];
  };

  MemoryStorage.prototype.get = function() {
    return items;
  };

  Object.defineProperty(MemoryStorage.prototype, "length", {
    get: function length() {
        return Object.keys(items).length;
    }
  });

  window.memoryStorage = new MemoryStorage();
})(window);

// helper function to swap to memory storage

function getStorage(storage) {
  var x = '__storage_test__';
  try {
    storage.setItem(x, x)
    storage.removeItem(x);
    return storage;
  } catch (e) {
    return getStorage.prototype.FALLBACK_STORAGE;
  }
}

getStorage.prototype.FALLBACK_STORAGE = memoryStorage;

var foo = {}; // force fallback
var storage = getStorage(foo);

storage.setItem('foo', 'bar');

console.log(storage.length) // returns 1
@philfreo

This comment has been minimized.

Copy link
Owner Author

@philfreo philfreo commented Feb 29, 2016

@Download (Sorry, just saw this)

I don't think an alert is much better than an error to be honest.

The problem is that the user doesn't get an error (unless they view the console). Most webpages simply silently fail.

Yep, using a wrapper is ideal, however if you have existing code (potentially outside your control) then you can't always modify code to use a wrapper instead of localStorage. If you, on the other hand, had a shim that overwrote Storage.prototype.setItem to work in-memory (so localStorage.setItem would still work), that would be nice.

@hazratgs

This comment has been minimized.

Copy link

@hazratgs hazratgs commented Apr 3, 2018

And you can also replace the standard methods:

  try {
      localStorage.setItem('localStorage', 1);
      localStorage.removeItem('localStorage');
  } catch (e) {
      const memorystorage = new MemoryStorage('localStorage')
      Storage.prototype.setItem = memorystorage.setItem.bind(memorystorage)
      Storage.prototype.getItem = memorystorage.getItem.bind(memorystorage)
      Storage.prototype.removeItem = memorystorage.removeItem.bind(memorystorage)
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment