html5 localStorage error with Safari: "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."
Asked Answered
S

15

134

My webapp have javascript errors in ios safari private browsing:

JavaScript:error

undefined

QUOTA_EXCEEDED_ERR:DOM Exception 22:An attempt was made to add something to storage...

my code:

localStorage.setItem('test',1)
Salena answered 28/1, 2013 at 4:16 Comment(2)
Use a feature detect that tests for this specific issue. If storage is not available, consider shimming localStorage with memoryStorage. disclaimer: I am the author of the linked packagesAmaty
Hi folks, I help maintain safaridriver. This issue is a longstanding bug in WebKit that was recently fixed. Local storage and session storage now work in Safari 10.1 and later. This fix affects normal Private Browsing mode and Automation mode (used by WebDriver).Kerrin
A
187

Apparently this is by design. When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage is available, but trying to call setItem throws an exception.

store.js line 73
"QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."

What happens is that the window object still exposes localStorage in the global namespace, but when you call setItem, this exception is thrown. Any calls to removeItem are ignored.

I believe the simplest fix (although I haven't tested this cross browser yet) would be to alter the function isLocalStorageNameSupported() to test that you can also set some value.

https://github.com/marcuswestin/store.js/issues/42

function isLocalStorageNameSupported() 
{
    var testKey = 'test', storage = window.sessionStorage;
    try 
    {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return localStorageName in win && win[localStorageName];
    } 
    catch (error) 
    {
        return false;
    }
}
Alexi answered 28/1, 2013 at 4:18 Comment(8)
This must not necessarily be due to incognito mode... although I guess the OP did not want to store several megabytes of data ;)Ferminafermion
Check out this gist showing a brief history of detecting local storage by Paul Irish.Precarious
So in the case that localStorage is not going to work in Safari, is storing everything in cookies the next best option?Gouache
Similar to the Paul Irish example, I suggest changing return localStorageName in win && win[localStorageName]; to return true. Then you have a function which safely returns true or false depending on localStorage availability. For example: if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }Endometriosis
I just tested on the iOS9 Safari beta and localstorage is still not accessible when in Private mode. Thanks Apple!Ernaline
Verified that the issue is not only with private window but normal safari window also.Beaton
What is the win variable in the last line of the try block? Is that a typo meant to be window?Conflation
Safari and IOS browsers have been terribly buggy in supporting these basic but important storage features. Lots of headache and pains for developers in the past and sometimes even now.Payroll
O
38

The fix posted on above link did not work for me. This did:

function isLocalStorageNameSupported() {
  var testKey = 'test', storage = window.localStorage;
  try {
    storage.setItem(testKey, '1');
    storage.removeItem(testKey);
    return true;
  } catch (error) {
    return false;
  }
}

Derived from http://m.cg/post/13095478393/detect-private-browsing-mode-in-mobile-safari-on-ios5

Opheliaophelie answered 11/7, 2013 at 22:40 Comment(5)
Any particular reason you (and @KingKongFrog) are using window.sessionStorage to detect if you can write to localStorage or are we in a weird copy-paste typo cycle?Susurrus
@Susurrus if you've noticed a typo, why don't you correct it either in an edit or in your comment? As far as I'm aware window.sessionStorage is correct. It certainly works in my code. Please actually point out the fix to the problem you appear to know about.Carvajal
@Carvajal My comment was pointing out that they are using sessionStorage in a function that exists to check for localStorage support. Yes, it will likely still work, but, as written, the function name is misleading for what is actually being tested. I chose to comment, rather than edit, because I thought I was missing something and hoped to learn from these guys. Unfortunately, they haven't replied or made a correction so here we are.Susurrus
@Susurrus I'm guessing what you're wondering about is why window.localStorage vs simply localStorage? If so, that's not a typo -- it's a safe way to check if localStorage is actually defined, since it'll also hang off of the window object in JavaScript. If you simply checked if (localStorage) {} that would throw an exception, but if (window.localStorage) { behaves as expected. I know it's been a couple years, but you asked a couple years after the answer, so... why not. :)Premiere
@DawsonToth no it was because I called the function isLocalStorageNameSupported and was checking window.sessionStorage. Same end result but was a little confusing. The answer was edited to clarify.Opheliaophelie
A
25

As mentioned in other answers, you'll always get the QuotaExceededError in Safari Private Browser Mode on both iOS and OS X when localStorage.setItem (or sessionStorage.setItem) is called.

One solution is to do a try/catch or Modernizr check in each instance of using setItem.

However if you want a shim that simply globally stops this error being thrown, to prevent the rest of your JavaScript from breaking, you can use this:

https://gist.github.com/philfreo/68ea3cd980d72383c951

// 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.');
    }
}
Ashliashlie answered 22/11, 2014 at 19:17 Comment(0)
C
11

In my context, just developed a class abstraction. When my application is launched, i check if localStorage is working by calling getStorage(). This function also return :

  • either localStorage if localStorage is working
  • or an implementation of a custom class LocalStorageAlternative

In my code i never call localStorage directly. I call cusStoglobal var, i had initialised by calling getStorage().

This way, it works with private browsing or specific Safari versions

function getStorage() {

    var storageImpl;

     try { 
        localStorage.setItem("storage", ""); 
        localStorage.removeItem("storage");
        storageImpl = localStorage;
     }
     catch (err) { 
         storageImpl = new LocalStorageAlternative();
     }

    return storageImpl;

}

function LocalStorageAlternative() {

    var structureLocalStorage = {};

    this.setItem = function (key, value) {
        structureLocalStorage[key] = value;
    }

    this.getItem = function (key) {
        if(typeof structureLocalStorage[key] != 'undefined' ) {
            return structureLocalStorage[key];
        }
        else {
            return null;
        }
    }

    this.removeItem = function (key) {
        structureLocalStorage[key] = undefined;
    }
}

cusSto = getStorage();
Czarina answered 16/7, 2013 at 15:42 Comment(7)
Thanks Pierre, your answer inspired me. I ended up packaging it all up in a nice module called memorystorage. Open Source of course. For other people with the same issue, check it out it might help you.Amaty
Ha. I did the same thing (independently). Still use the localStorage variable though (in Safari, at least, you can't overwrite the localStorage variable (it's 'read-only'), but you can reassign setItem/removeItem/getItem).Peele
@StijndeWitt, how can I access my storage values in other pages? For example, I have this in my helper.php var store = MemoryStorage('my-app'); store.setItem('myString', 'Hello MemoryStorage!'); I want to access the value of myString in lecture.php. I tried initiating memorystorage in the page but still it shows an empty object.Daguerre
@Daguerre Memorystorage is local to the page. It simulates the Web Storage API and as such can be used as a fallback for when localStorage and sessionStorage are not available. However the data will only be retained in the page memory (hence the name). If you need data to be retained across pages, cookies may help you. But it's very limited in amount of data that can be stored. Other than that there is not much that can be done.Amaty
Thank you @StijndeWitt. I debugged the library but i'm somehow confuse the result is always empty in line 35 everytime ill refresh the page. Isn't this supposedly store the values in the browser? So if the browser is close accidentally, once i re-opened it i can just use getItem to fetch and populate the values before the browser was close. This is the code : // try to get existing store var result = storage[id] (this is always empty ) even though i opened the page for second time or more.Daguerre
I have problem with Cookie in Safari or IOS phone thats why I'm looking around for an alternativeDaguerre
@Daguerre Isn't this supposedly store the values in the browser? No, it can't. There are 3 ways to store stuff client-side from one page refresh to the other: cookies, sessionStorage/localStorage and IndexedDB. The last two are relatively new. sessionStorage and localStorage are broadly supported so you can use it basically everywhere. except in private browsing mode, which is what this issue is about. Programs broke because storage was not there. memorystorage just provides a fallback that always works while on the page, but it can't actually save the data. It's a stub. But no error.Amaty
B
7

It seems that Safari 11 changes the behavior, and now local storage works in a private browser window. Hooray!

Our web app that used to fail in Safari private browsing now works flawlessly. It always worked fine in Chrome's private browsing mode, which has always allowed writing to local storage.

This is documented in Apple's Safari Technology Preview release notes - and the WebKit release notes - for release 29, which was in May 2017.

Specifically:

  • Fixed QuotaExceededError when saving to localStorage in private browsing mode or WebDriver sessions - r215315
Bega answered 31/7, 2018 at 14:49 Comment(0)
S
4

To expand on others' answers, here is a compact solution that doesn't expose/add any new variables. It doesn't cover all bases, but it should suit most people who just want a single page app to remain functional (despite no data persistence after reload).

(function(){
    try {
        localStorage.setItem('_storage_test', 'test');
        localStorage.removeItem('_storage_test');
    } catch (exc){
        var tmp_storage = {};
        var p = '__unique__';  // Prefix all keys to avoid matching built-ins
        Storage.prototype.setItem = function(k, v){
            tmp_storage[p + k] = v;
        };
        Storage.prototype.getItem = function(k){
            return tmp_storage[p + k] === undefined ? null : tmp_storage[p + k];
        };
        Storage.prototype.removeItem = function(k){
            delete tmp_storage[p + k];
        };
        Storage.prototype.clear = function(){
            tmp_storage = {};
        };
    }
})();
Sightless answered 25/2, 2016 at 9:33 Comment(0)
M
3

I had the same problem using Ionic framework (Angular + Cordova). I know this not solve the problem, but it's the code for Angular Apps based on the answers above. You will have a ephemeral solution for localStorage on iOS version of Safari.

Here is the code:

angular.module('myApp.factories', [])
.factory('$fakeStorage', [
    function(){
        function FakeStorage() {};
        FakeStorage.prototype.setItem = function (key, value) {
            this[key] = value;
        };
        FakeStorage.prototype.getItem = function (key) {
            return typeof this[key] == 'undefined' ? null : this[key];
        }
        FakeStorage.prototype.removeItem = function (key) {
            this[key] = undefined;
        };
        FakeStorage.prototype.clear = function(){
            for (var key in this) {
                if( this.hasOwnProperty(key) )
                {
                    this.removeItem(key);
                }
            }
        };
        FakeStorage.prototype.key = function(index){
            return Object.keys(this)[index];
        };
        return new FakeStorage();
    }
])
.factory('$localstorage', [
    '$window', '$fakeStorage',
    function($window, $fakeStorage) {
        function isStorageSupported(storageName) 
        {
            var testKey = 'test',
                storage = $window[storageName];
            try
            {
                storage.setItem(testKey, '1');
                storage.removeItem(testKey);
                return true;
            } 
            catch (error) 
            {
                return false;
            }
        }
        var storage = isStorageSupported('localStorage') ? $window.localStorage : $fakeStorage;
        return {
            set: function(key, value) {
                storage.setItem(key, value);
            },
            get: function(key, defaultValue) {
                return storage.getItem(key) || defaultValue;
            },
            setObject: function(key, value) {
                storage.setItem(key, JSON.stringify(value));
            },
            getObject: function(key) {
                return JSON.parse(storage.getItem(key) || '{}');
            },
            remove: function(key){
                storage.removeItem(key);
            },
            clear: function() {
                storage.clear();
            },
            key: function(index){
                storage.key(index);
            }
        }
    }
]);

Source: https://gist.github.com/jorgecasar/61fda6590dc2bb17e871

Enjoy your coding!

Matrices answered 26/8, 2014 at 18:51 Comment(1)
Though this doesn't answer the question this is the first thing that came up when I googled the issue. The next step would have been to search for the solution for Angular but thanks to this comment I don't have to go elsewhere. So, might not directly answer the question but has been great for me and likely for others!Antelope
C
2

Here's a solution for AngularJS using an IIFE and leveraging the fact that services are singletons.

This results in isLocalStorageAvailable being set immediately when the service is first injected and avoids needlessly running the check every time local storage needs to be accessed.

angular.module('app.auth.services', []).service('Session', ['$log', '$window',
  function Session($log, $window) {
    var isLocalStorageAvailable = (function() {
      try {
        $window.localStorage.world = 'hello';
        delete $window.localStorage.world;
        return true;
      } catch (ex) {
        return false;
      }
    })();

    this.store = function(key, value) {
      if (isLocalStorageAvailable) {
        $window.localStorage[key] = value;
      } else {
        $log.warn('Local Storage is not available');
      }
    };
  }
]);
Croce answered 20/3, 2015 at 16:43 Comment(0)
L
2

The accepted answer seems not adequate in several situations.

To check whether the localStorage or sessionStorage are supported, I use the following snippet from MDN.

function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

Use this snippet like this, and fallback to, for example, using cookie:

if (storageAvailable('localStorage')) {
  // Yippee! We can use localStorage awesomeness
}
else {
  // Too bad, no localStorage for us
  document.cookie = key + "=" + encodeURIComponent(value) + expires + "; path=/";
}

I have made the fallbackstorage package which uses this snippet to check for the storage availability and fallback to a manually implemented MemoryStorage.

import {getSafeStorage} from 'fallbackstorage'

getSafeStorage().setItem('test', '1') // always work
Lofton answered 25/7, 2020 at 8:59 Comment(0)
L
1

I just created this repo to provide sessionStorage and localStorage features for unsupported or disabled browsers.

Supported browsers

  • IE5+
  • Chrome all versions
  • Mozilla all versions
  • Yandex all versions

How it works

It detects the feature with the storage type.

function(type) {
    var testKey = '__isSupported',
        storage = window[type];
    try {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return true;
    } catch (error) {
        return false;
    }
};

Sets StorageService.localStorage to window.localStorage if it is supported or creates a cookie storage. Sets StorageService.sessionStorage to window.sessionStorage if it is supported or creates a in memory storage for SPA, cookie storage with sesion features for non SPA.

Lynching answered 14/11, 2017 at 7:20 Comment(0)
A
1

Here is an Angular2+ service version for memory storage alternative, you can just inject into your components, based on Pierre Le Roux' answer.

import { Injectable } from '@angular/core';

// Alternative to localstorage, memory
// storage for certain browsers in private mode
export class LocalStorageAlternative {
    private  structureLocalStorage = {};

    setItem(key: string, value: string): void {
        this.structureLocalStorage[key] = value;
    }

    getItem(key: string): string {
        if (typeof this.structureLocalStorage[key] !== 'undefined' ) {
            return this.structureLocalStorage[key];
        }
        return null;
    }

    removeItem(key: string): void {
        this.structureLocalStorage[key] = undefined;
    }
}

@Injectable()
export class StorageService {
    private storageEngine;

    constructor() {
        try {
            localStorage.setItem('storage_test', '');
            localStorage.removeItem('storage_test');
            this.storageEngine = localStorage;
        } catch (err) {
            this.storageEngine = new LocalStorageAlternative();
        }
    }

    setItem(key: string, value: string): void {
        this.storageEngine.setItem(key, value);
    }

    getItem(key: string): string {
        return this.storageEngine.getItem(key);
    }

    removeItem(key: string): void {
        this.storageEngine.removeItem(key);
    }

}
Affiance answered 28/3, 2019 at 20:37 Comment(0)
W
0

Don't use it if not supported and to check support just call this function

sharing in Es6 full read and write localStorage Example with support check

const LOCAL_STORAGE_KEY = 'tds_app_localdata';

const isSupported = () => {
  try {
    localStorage.setItem('supported', '1');
    localStorage.removeItem('supported');
    return true;
  } catch (error) {
    return false;
  }
};


const writeToLocalStorage =
  components =>
    (isSupported ?
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(components))
      : components);

const isEmpty = component => (!component || Object.keys(component).length === 0);

const readFromLocalStorage =
  () => (isSupported ? JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || {} : null);

This will make sure your keys are set and retrieved properly on all browsers.

Won answered 10/8, 2017 at 18:41 Comment(0)
I
0

I have created a patch for the issue. Simply I am checking if the browser does support localStorage or sessionStorage or not. If not then the storage engine will be Cookie. But the negative side is Cookie have very tiny storage memory :(

function StorageEngine(engine) {
    this.engine = engine || 'localStorage';

    if(!this.checkStorageApi(this.engine)) {
        // Default engine would be alway cooke
        // Safari private browsing issue with localStorage / sessionStorage
        this.engine = 'cookie';
    }
}

StorageEngine.prototype.checkStorageApi = function(name) {
    if(!window[name]) return false;
    try {
        var tempKey = '__temp_'+Date.now();
        window[name].setItem(tempKey, 'hi')
        window[name].removeItem(tempKey);
        return true;
    } catch(e) {
        return false;
    }
}

StorageEngine.prototype.getItem = function(key) {
    if(['sessionStorage', 'localStorage'].includes(this.engine)) {
        return window[this.engine].getItem(key);
    } else if('cookie') {
        var name = key+"=";
        var allCookie = decodeURIComponent(document.cookie).split(';');
        var cval = [];
        for(var i=0; i < allCookie.length; i++) {
            if (allCookie[i].trim().indexOf(name) == 0) {
                cval = allCookie[i].trim().split("=");
            }   
        }
        return (cval.length > 0) ? cval[1] : null;
    }
    return null;
}

StorageEngine.prototype.setItem = function(key, val, exdays) {
    if(['sessionStorage', 'localStorage'].includes(this.engine)) {
        window[this.engine].setItem(key, val);
    } else if('cookie') {
        var d = new Date();
        var exdays = exdays || 1;
        d.setTime(d.getTime() + (exdays*24*36E5));
        var expires = "expires="+ d.toUTCString();
        document.cookie = key + "=" + val + ";" + expires + ";path=/";
    }
    return true;
}


// ------------------------
var StorageEngine = new StorageEngine(); // new StorageEngine('localStorage');
// If your current browser (IOS safary or any) does not support localStorage/sessionStorage, then the default engine will be "cookie"

StorageEngine.setItem('keyName', 'val')

var expireDay = 1; // for cookie only
StorageEngine.setItem('keyName', 'val', expireDay)
StorageEngine.getItem('keyName')
Impertinent answered 17/12, 2019 at 20:28 Comment(0)
S
-1
var mod = 'test';
      try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
      } catch (e) {
        return false;
      }
Stork answered 4/10, 2018 at 11:26 Comment(1)
Maybe you want to add a few words of explanation?Pappas
S
-2

The following script solved my problem:

// Fake localStorage implementation. 
// Mimics localStorage, including events. 
// It will work just like localStorage, except for the persistant storage part. 

var fakeLocalStorage = function() {
  var fakeLocalStorage = {};
  var storage; 

  // If Storage exists we modify it to write to our fakeLocalStorage object instead. 
  // If Storage does not exist we create an empty object. 
  if (window.Storage && window.localStorage) {
    storage = window.Storage.prototype; 
  } else {
    // We don't bother implementing a fake Storage object
    window.localStorage = {}; 
    storage = window.localStorage; 
  }

  // For older IE
  if (!window.location.origin) {
    window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
  }

  var dispatchStorageEvent = function(key, newValue) {
    var oldValue = (key == null) ? null : storage.getItem(key); // `==` to match both null and undefined
    var url = location.href.substr(location.origin.length);
    var storageEvent = document.createEvent('StorageEvent'); // For IE, https://mcmap.net/q/47373/-can-i-fake-a-storage-event-in-my-jasmine-unit-tests

    storageEvent.initStorageEvent('storage', false, false, key, oldValue, newValue, url, null);
    window.dispatchEvent(storageEvent);
  };

  storage.key = function(i) {
    var key = Object.keys(fakeLocalStorage)[i];
    return typeof key === 'string' ? key : null;
  };

  storage.getItem = function(key) {
    return typeof fakeLocalStorage[key] === 'string' ? fakeLocalStorage[key] : null;
  };

  storage.setItem = function(key, value) {
    dispatchStorageEvent(key, value);
    fakeLocalStorage[key] = String(value);
  };

  storage.removeItem = function(key) {
    dispatchStorageEvent(key, null);
    delete fakeLocalStorage[key];
  };

  storage.clear = function() {
    dispatchStorageEvent(null, null);
    fakeLocalStorage = {};
  };
};

// Example of how to use it
if (typeof window.localStorage === 'object') {
  // Safari will throw a fit if we try to use localStorage.setItem in private browsing mode. 
  try {
    localStorage.setItem('localStorageTest', 1);
    localStorage.removeItem('localStorageTest');
  } catch (e) {
    fakeLocalStorage();
  }
} else {
  // Use fake localStorage for any browser that does not support it.
  fakeLocalStorage();
}

It checks if localStorage exists and can be used and in the negative case, it creates a fake local storage and uses it instead of the original localStorage. Please let me know if you need further information.

Stuck answered 28/7, 2016 at 12:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.