Erreur html5 localStorage avec Safari: «QUOTA_EXCEEDED_ERR: DOM Exception 22: Une tentative a été faite pour ajouter quelque chose au stockage qui a dépassé le quota.»

133

Mon application Web a des erreurs javascript dans la navigation privée iOS Safari:

JavaScript: erreur

indéfini

QUOTA_EXCEEDED_ERR: Exception DOM 22: Une tentative a été faite pour ajouter quelque chose au stockage ...

mon code:

localStorage.setItem('test',1)
Leiyonglin
la source
Utilisez une fonction de détection qui teste ce problème spécifique . Si le stockage n'est pas disponible, envisagez de shooter localStorage avec memoryStorage . avertissement: Je suis l'auteur des packages liés
Stijn de Witt
4
Salut les gens, j'aide à maintenir safaridriver. Ce problème est un bogue de longue date dans WebKit qui a été récemment corrigé. Le stockage local et le stockage de session fonctionnent désormais dans Safari 10.1 et versions ultérieures. Ce correctif affecte le mode normal de navigation privée et le mode d'automatisation (utilisé par WebDriver).
Brian Burg

Réponses:

183

Apparemment, c'est par conception. Lorsque Safari (OS X ou iOS) est en mode de navigation privée, il semble qu'il localStoragesoit disponible, mais essayer d'appeler setItemlève une exception.

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

Ce qui se passe, c'est que l'objet window est toujours exposé localStoragedans l'espace de noms global, mais lorsque vous appelez setItem, cette exception est levée. Tous les appels à removeItemsont ignorés.

Je pense que la solution la plus simple (bien que je n'ai pas encore testé ce navigateur croisé) serait de modifier la fonction isLocalStorageNameSupported()pour tester que vous pouvez également définir une valeur.

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;
    }
}
KingKongFrog
la source
1
Cela ne doit pas forcément être dû au mode incognito ... même si je suppose que l'OP n'a pas voulu stocker plusieurs mégaoctets de données;)
Christoph
5
Découvrez cet essentiel montrant un bref historique de la détection du stockage local par Paul Irish.
Mottie le
4
Donc, dans le cas où localStorage ne fonctionnera pas dans Safari, est-ce que tout stocker dans les cookies est la meilleure option?
Will Hitchcock
5
Semblable à l'exemple de Paul Irish, je suggère de passer return localStorageName in win && win[localStorageName];à return true. Ensuite, vous avez une fonction qui renvoie en toute sécurité true ou false en fonction de la disponibilité de localStorage. Par exemple:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT
1
Vérifié que le problème ne concerne pas seulement la fenêtre privée, mais également la fenêtre de safari normale.
codemirror
38

Le correctif publié sur le lien ci-dessus n'a pas fonctionné pour moi. Cela a fait:

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

Dérivé de http://m.cg/post/13095478393/detect-private-browsing-mode-in-mobile-safari-on-ios5

cyberwombat
la source
20
Une raison particulière pour laquelle vous (et @KingKongFrog) utilisez window.sessionStorage pour détecter si vous pouvez écrire dans localStorage ou sommes-nous dans un étrange cycle de copie-coller?
Yetti
@Yetti si vous avez remarqué une faute de frappe, pourquoi ne la corrigez-vous pas dans une modification ou dans votre commentaire? Autant que je sache, window.sessionStoragec'est correct. Cela fonctionne certainement dans mon code. Veuillez indiquer le correctif du problème que vous semblez connaître.
Novocaine
7
@Novocaine Mon commentaire soulignait qu'ils utilisent sessionStorage dans une fonction qui existe pour vérifier le support localStorage. Oui, cela fonctionnera probablement encore, mais, tel qu'il est écrit, le nom de la fonction est trompeur pour ce qui est réellement testé. J'ai choisi de commenter, plutôt que d'éditer, car je pensais qu'il me manquait quelque chose et j'espérais apprendre de ces gars. Malheureusement, ils n'ont pas répondu ou fait de correction, alors nous y voilà.
Yetti
3
@Yetti Merci pour cette clarification. Je vois de quoi vous parliez maintenant. ; -]
Novocaine
2
@DawsonToth non, c'était parce que j'ai appelé la fonction isLocalStorageNameSupportedet que je vérifiais window.sessionStorage. Même résultat final mais un peu déroutant. La réponse a été modifiée pour clarifier.
cyberwombat
25

Comme mentionné dans d'autres réponses, vous obtiendrez toujours l'erreur QuotaExceededError en mode navigateur privé Safari sur iOS et OS X lorsque localStorage.setItem(ou sessionStorage.setItem) est appelé.

Une solution consiste à effectuer une vérification try / catch ou Modernizr dans chaque instance d'utilisation setItem.

Cependant, si vous voulez un shim qui arrête simplement globalement cette erreur, pour empêcher le reste de votre JavaScript de se casser, vous pouvez utiliser ceci:

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.');
    }
}
philfreo
la source
11

Dans mon contexte, je viens de développer une abstraction de classe. Lorsque mon application est lancée, je vérifie si localStorage fonctionne en appelant getStorage () . Cette fonction renvoie également:

  • soit localStorage si localStorage fonctionne
  • ou une implémentation d'une classe personnalisée LocalStorageAlternative

Dans mon code, je n'appelle jamais localStorage directement. J'appelle cusSto global var, j'avais initialisé en appelant getStorage () .

De cette façon, cela fonctionne avec la navigation privée ou des versions spécifiques de Safari

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();
Pierre Le Roux
la source
2
Merci Pierre, ta réponse m'a inspiré. J'ai fini par emballer tout cela dans un joli module appelé memorystorage . Open Source bien sûr. Pour les autres personnes ayant le même problème, vérifiez-le, cela pourrait vous aider.
Stijn de Witt
Ha. J'ai fait la même chose (indépendamment). Cependant, utilisez toujours la variable localStorage (dans Safari, au moins, vous ne pouvez pas écraser la variable localStorage (elle est en lecture seule), mais vous pouvez réaffecter setItem / removeItem / getItem).
Ruben Martinez Jr.26
@StijndeWitt, comment puis-je accéder à mes valeurs de stockage dans d'autres pages? Par exemple, j'ai ceci dans mon helper.php var store = MemoryStorage ('my-app'); store.setItem ('myString', 'Bonjour MemoryStorage!'); Je souhaite accéder à la valeur de myString dans lecture.php. J'ai essayé de lancer le stockage de mémoire dans la page mais cela montre toujours un objet vide.
user1149244
@ user1149244 Memorystorage est local sur la page. Il simule l'API de stockage Web et, en tant que tel, peut être utilisé comme solution de secours lorsque localStorage et sessionStorage ne sont pas disponibles. Cependant, les données ne seront conservées que dans la mémoire de page (d'où le nom). Si vous avez besoin que les données soient conservées sur les pages, les cookies peuvent vous aider. Mais la quantité de données pouvant être stockée est très limitée. À part cela, il n'y a pas grand-chose à faire.
Stijn de Witt du
2
@ user1149244 N'est-ce pas censé stocker les valeurs dans le navigateur? Non, ça ne peut pas. Il existe 3 façons de stocker des éléments côté client d'une actualisation de page à l'autre: cookies, sessionStorage / localStorage et IndexedDB. Les deux derniers sont relativement nouveaux. sessionStorage et localStorage sont largement pris en charge, vous pouvez donc l'utiliser pratiquement partout. sauf en mode de navigation privée , qui est le sujet de ce problème. Les programmes sont tombés en panne car le stockage n'était pas là. memorystorage fournit simplement une solution de secours qui fonctionne toujours sur la page, mais qui ne peut pas réellement enregistrer les données. C'est un bout. Mais pas d'erreur.
Stijn de Witt
5

Il semble que Safari 11 change le comportement, et maintenant le stockage local fonctionne dans une fenêtre de navigateur privé. Hourra!

Notre application Web qui échouait dans la navigation privée Safari fonctionne désormais parfaitement. Cela a toujours bien fonctionné dans le mode de navigation privée de Chrome, qui a toujours permis d'écrire sur le stockage local.

Ceci est documenté dans les notes de mise à jour de Safari Technology Preview d' Apple - et dans les notes de mise à jour de WebKit - pour la version 29, qui date de mai 2017.

Plus précisément:

  • Correction de QuotaExceededError lors de l'enregistrement dans localStorage en mode de navigation privée ou dans les sessions WebDriver - r215315
karlbecker_com
la source
4

Pour développer les réponses des autres, voici une solution compacte qui n'expose / n'ajoute aucune nouvelle variable. Il ne couvre pas toutes les bases, mais il devrait convenir à la plupart des personnes qui souhaitent qu'une seule application de page reste fonctionnelle (malgré l'absence de persistance des données après le rechargement).

(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 = {};
        };
    }
})();
Jon
la source
3

J'ai eu le même problème en utilisant le framework Ionic (Angular + Cordova). Je sais que cela ne résout pas le problème, mais c'est le code pour Angular Apps basé sur les réponses ci-dessus. Vous disposerez d'une solution éphémère pour localStorage sur la version iOS de Safari.

Voici le 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

Profitez de votre codage!

jorgecasar
la source
1
Bien que cela ne réponde pas à la question, c'est la première chose qui est survenue lorsque j'ai recherché le problème sur Google. La prochaine étape aurait été de chercher la solution pour Angular mais grâce à ce commentaire je n'ai pas à aller ailleurs. Donc, je ne répondrai peut-être pas directement à la question, mais cela a été formidable pour moi et probablement pour les autres!
Leonard
2

Voici une solution pour AngularJS utilisant un IIFE et tirant parti du fait que les services sont des singletons .

Cela entraîne isLocalStorageAvailableune définition immédiate lorsque le service est injecté pour la première fois et évite d'exécuter inutilement la vérification à chaque fois que le stockage local doit être accédé.

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');
      }
    };
  }
]);
Pier-Luc Gendreau
la source
1

Je viens de créer cette prise en pension pour fournir sessionStorageet localStoragefonctionnalités pour les navigateurs non pris en charge ou à mobilité réduite.

Navigateurs pris en charge

  • IE5 +
  • Chrome toutes les versions
  • Mozilla toutes les versions
  • Yandex toutes les versions

Comment ça fonctionne

Il détecte la fonction avec le type de stockage.

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

Ensembles StorageService.localStoragede window.localStoragesi elle est prise en charge ou crée un stockage de cookies. Ensembles StorageService.sessionStoragede window.sessionStoragesi elle est prise en charge ou crée un stockage dans la mémoire pour SPA, le stockage des cookies avec des fonctionnalités de sesion pour non SPA.

Ahmet Can Güven
la source
1
Merci, votre bibliothèque a beaucoup aidé!
Mathieu
1

Voici une version de service Angular2 + pour une alternative de stockage mémoire, vous pouvez simplement l'injecter dans vos composants, sur la base de la réponse de Pierre Le Roux.

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);
    }

}
Gabriel Alack
la source
0

Ne l'utilisez pas s'il n'est pas pris en charge et pour vérifier le support, appelez simplement cette fonction

partage dans Es6 complet lecture et écriture localStorage Exemple avec contrôle de support

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);

Cela garantira que vos clés sont définies et récupérées correctement sur tous les navigateurs.

Tarandeep Singh
la source
0

J'ai créé un correctif pour le problème. Je vérifie simplement si le navigateur prend en charge localStorage ou sessionStorage ou non. Sinon, le moteur de stockage sera Cookie. Mais le côté négatif est que Cookie a une très petite mémoire de stockage :(

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')
Saddam H
la source
0

La réponse acceptée ne semble pas adéquate dans plusieurs situations.

Pour vérifier si le localStorageou sessionStoragesont pris en charge, j'utilise l'extrait suivant de 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);
    }
}

Utilisez cet extrait de code comme celui-ci et utilisez un cookie, par exemple:

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

J'ai créé le package fallbackstorage qui utilise cet extrait de code pour vérifier la disponibilité du stockage et le repli vers un MemoryStorage implémenté manuellement.

import {getSafeStorage} from 'fallbackstorage'

getSafeStorage().setItem('test', '1') // always work
transang
la source
-1
var mod = 'test';
      try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
      } catch (e) {
        return false;
      }
Naim DOGAN
la source
1
Vous souhaitez peut-être ajouter quelques mots d'explication?
bogl
-2

Le script suivant a résolu mon problème:

// 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, http://stackoverflow.com/a/25514935/1214183

    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();
}

Il vérifie si localStorage existe et peut être utilisé et dans le cas négatif, il crée un faux stockage local et l'utilise à la place du localStorage d'origine. Veuillez me faire savoir si vous avez besoin de plus amples informations.

Bogdan Mates
la source