Error de html5 localStorage con Safari: "QUOTA_EXCEEDED_ERR: DOM Excepción 22: se intentó agregar algo al almacenamiento que superó la cuota".

133

Mi aplicación web tiene errores de JavaScript en la navegación privada de ios safari:

JavaScript: error

indefinido

QUOTA_EXCEEDED_ERR: DOM Excepción 22: se intentó agregar algo al almacenamiento ...

mi código:

localStorage.setItem('test',1)
leiyonglin
fuente
Utilice una función de detección que prueba este problema específico . Si el almacenamiento no está disponible, considere shimming localStorage con memoryStorage . descargo de responsabilidad: soy el autor de los paquetes vinculados
Stijn de Witt
44
Hola amigos, ayudo a mantener safaridriver. Este problema es un error de larga data en WebKit que se corrigió recientemente. El almacenamiento local y el almacenamiento de sesión ahora funcionan en Safari 10.1 y versiones posteriores. Esta solución afecta el modo de navegación privada normal y el modo de automatización (utilizado por WebDriver).
Brian Burg

Respuestas:

183

Aparentemente esto es por diseño. Cuando Safari (OS X o iOS) está en modo de navegación privada, parece que localStorageestá disponible, pero intentar llamar setItemarroja una excepción.

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

Lo que sucede es que el objeto de la ventana aún se expone localStorageen el espacio de nombres global, pero cuando llama setItem, se produce esta excepción. Cualquier llamada aremoveItem se ignora.

Creo que la solución más simple (aunque todavía no he probado este navegador cruzado) sería alterar la función isLocalStorageNameSupported() para probar que también puede establecer algún valor.

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
fuente
1
Esto no necesariamente se debe al modo de incógnito ... aunque supongo que el OP no quería almacenar varios megabytes de datos;)
Christoph
55
Echa un vistazo a esta esencia que muestra una breve historia de detección de almacenamiento local por Paul Irish.
Mottie
44
Entonces, en el caso de que localStorage no funcione en Safari, ¿almacenar todo en cookies es la siguiente mejor opción?
Will Hitchcock
55
Similar al ejemplo de Paul Irish, sugiero cambiar return localStorageName in win && win[localStorageName];a return true. Entonces tiene una función que devuelve con seguridad verdadero o falso dependiendo de la disponibilidad localStorage. Por ejemplo:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT
1
Verificó que el problema no es solo con la ventana privada sino también con la ventana de safari normal.
codemirror
38

La solución publicada en el enlace anterior no funcionó para mí. Esto hizo:

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

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

cyberwombat
fuente
20
¿Alguna razón en particular por la que usted (y @KingKongFrog) está usando window.sessionStorage para detectar si puede escribir en localStorage o estamos en un extraño ciclo de error tipográfico de copiar y pegar?
Yetti
@Yetti si ha notado un error tipográfico, ¿por qué no lo corrige en una edición o en su comentario? Hasta donde yo sé window.sessionStoragees correcto. Ciertamente funciona en mi código. En realidad, señale la solución al problema que parece conocer.
Novocaína
77
@Novocaine Mi comentario señalaba que están utilizando sessionStorage en una función que existe para verificar el soporte localStorage. Sí, probablemente seguirá funcionando, pero, tal como está escrito, el nombre de la función es engañoso para lo que realmente se está probando. Elegí comentar, en lugar de editar, porque pensé que me faltaba algo y esperaba aprender de estos tipos. Desafortunadamente, no han respondido o hecho una corrección, así que aquí estamos.
Yetti
3
@Yetti Gracias por aclarar. Ahora veo de qué estabas hablando. ; -]
Novocaína
2
@DawsonToth no, fue porque llamé a la función isLocalStorageNameSupportedy estaba comprobando window.sessionStorage. El mismo resultado final pero fue un poco confuso. La respuesta fue editada para aclarar.
cyberwombat
25

Como se menciona en otras respuestas, siempre obtendrá QuotaExceededError en el modo de navegador privado Safari tanto en iOS como en OS X cuando localStorage.setItem(osessionStorage.setItem se llama ).

Una solución es hacer una prueba try / catch o Modernizr en cada instancia de usosetItem .

Sin embargo, si desea una cuña que simplemente detenga globalmente este error, para evitar que el resto de su JavaScript se rompa, puede usar esto:

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
fuente
11

En mi contexto, acabo de desarrollar una abstracción de clase. Cuando se inicia mi aplicación, compruebo si localStorage funciona llamando a getStorage () . Esta función también devuelve:

  • ya sea localStorage si localStorage está funcionando
  • o una implementación de una clase personalizada LocalStorageAlternative

En mi código, nunca llamo a localStorage directamente. Llamo a cusSto global var, lo había inicializado llamando a getStorage () .

De esta manera, funciona con navegación privada o versiones específicas 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
fuente
2
Gracias Pierre, tu respuesta me inspiró. Terminé empaquetando todo en un buen módulo llamado memorystorage . Código abierto, por supuesto. Para otras personas con el mismo problema, échale un vistazo que podría ayudarte.
Stijn de Witt
Decir ah. Hice lo mismo (independientemente). Sin embargo, todavía utilizo la variable localStorage (en Safari, al menos, no puede sobrescribir la variable localStorage (es 'solo lectura'), pero puede reasignar setItem / removeItem / getItem).
Ruben Martinez Jr.,
@StijndeWitt, ¿cómo puedo acceder a mis valores de almacenamiento en otras páginas? Por ejemplo, tengo esto en mi helper.php var store = MemoryStorage ('my-app'); store.setItem ('myString', 'Hello MemoryStorage!'); Quiero acceder al valor de myString en lecture.php. Intenté iniciar el almacenamiento en memoria en la página, pero aún así muestra un objeto vacío.
user1149244
@ user1149244 Memorystorage es local en la página. Simula la API de almacenamiento web y, como tal, se puede utilizar como alternativa cuando localStorage y sessionStorage no están disponibles. Sin embargo, los datos solo se conservarán en la memoria de la página (de ahí el nombre). Si necesita que se retengan datos en todas las páginas, las cookies pueden ayudarlo. Pero es muy limitada en la cantidad de datos que se pueden almacenar. Aparte de eso, no hay mucho que se pueda hacer.
Stijn de Witt
2
@ user1149244 ¿No se supone que esto almacena los valores en el navegador? No, no puede Hay 3 formas de almacenar cosas del lado del cliente de una página a otra: cookies, sessionStorage / localStorage e IndexedDB. Los dos últimos son relativamente nuevos. sessionStorage y localStorage son ampliamente compatibles para que pueda usarlo básicamente en todas partes. excepto en el modo de navegación privada , que es de lo que se trata este problema. Los programas se rompieron porque el almacenamiento no estaba allí. memorystorage solo proporciona un respaldo que siempre funciona mientras está en la página, pero en realidad no puede guardar los datos. Es un trozo. Pero no hay error.
Stijn de Witt
5

Parece que Safari 11 cambia el comportamiento, y ahora el almacenamiento local funciona en una ventana privada del navegador. ¡Hurra!

Nuestra aplicación web que solía fallar en la navegación privada de Safari ahora funciona perfectamente. Siempre funcionó bien en el modo de navegación privada de Chrome, que siempre ha permitido escribir en el almacenamiento local.

Esto está documentado en las notas de lanzamiento de Safari Technology Preview de Apple, y en las notas de lanzamiento de WebKit , para el lanzamiento 29, que fue en mayo de 2017.

Específicamente:

  • Se corrigió el error QuotaExceededError al guardar en localStorage en modo de navegación privada o sesiones de WebDriver - r215315
karlbecker_com
fuente
4

Para ampliar las respuestas de otros, aquí hay una solución compacta que no expone / agrega ninguna variable nueva. No cubre todas las bases, pero debería adaptarse a la mayoría de las personas que solo desean que una aplicación de una sola página permanezca funcional (a pesar de que no persiste la información después de la recarga).

(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
fuente
3

Tuve el mismo problema usando Ionic Framework (Angular + Cordova). Sé que esto no resuelve el problema, pero es el código para Angular Apps basado en las respuestas anteriores. Tendrá una solución efímera para localStorage en la versión iOS de Safari.

Aquí está el código:

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

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

¡Disfruta tu codificación!

jorgecasar
fuente
1
Aunque esto no responde a la pregunta, es lo primero que surgió cuando busqué en Google el problema. El siguiente paso habría sido buscar la solución para Angular, pero gracias a este comentario no tengo que ir a otro lado. Por lo tanto, puede que no responda directamente a la pregunta, ¡pero ha sido excelente para mí y probablemente para otros!
Leonard el
2

Aquí hay una solución para AngularJS utilizando un IIFE y aprovechando el hecho de que los servicios son únicos .

Esto isLocalStorageAvailablese configura inmediatamente cuando se inyecta el servicio por primera vez y evita ejecutar innecesariamente la verificación cada vez que se necesita acceder al almacenamiento local.

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
fuente
1

He creado esta cesión temporal para proveer sessionStoragey localStoragecaracterísticas de los navegadores no compatibles o discapacitados.

Navegadores compatibles

  • IE5 +
  • Chrome todas las versiones
  • Mozilla todas las versiones
  • Yandex todas las versiones

Cómo funciona

Detecta la función con el tipo de almacenamiento.

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

Conjuntos StorageService.localStoragea window.localStoragesi es compatible o crea un almacenamiento de cookies. Conjuntos StorageService.sessionStoragea window.sessionStoragesi es compatible o crea un almacenamiento en memoria para SPA, el almacenamiento de cookies con características sesion para no SPA.

Ahmet Can Güven
fuente
1
¡Gracias, su biblioteca ayudó mucho!
Mathieu
1

Aquí hay una versión de servicio Angular2 + para la alternativa de almacenamiento de memoria, puede simplemente inyectar en sus componentes, según la respuesta 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
fuente
0

No lo use si no es compatible y para verificar el soporte solo llame a esta función

compartir en Es6 ejemplo completo de lectura y escritura localStorage con verificación de soporte

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

Esto asegurará que sus claves estén configuradas y recuperadas correctamente en todos los navegadores.

Tarandeep Singh
fuente
0

He creado un parche para el problema. Simplemente estoy comprobando si el navegador admite localStorage o sessionStorage o no. De lo contrario, el motor de almacenamiento será Cookie. Pero el lado negativo es que las cookies tienen una memoria de almacenamiento muy pequeña :(

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
fuente
0

La respuesta aceptada parece no adecuada en varias situaciones.

Para verificar si el localStorageo sessionStorageson compatibles, utilizo el siguiente fragmento 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);
    }
}

Utilice este fragmento como este y recurra a, por ejemplo, el uso de cookies:

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

He creado el paquete fallbackstorage que utiliza este fragmento para verificar la disponibilidad de almacenamiento y el respaldo a un MemoryStorage implementado manualmente.

import {getSafeStorage} from 'fallbackstorage'

getSafeStorage().setItem('test', '1') // always work
transang
fuente
-1
var mod = 'test';
      try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
      } catch (e) {
        return false;
      }
Naim DOGAN
fuente
1
¿Quizás quiera agregar algunas palabras de explicación?
bogl
-2

El siguiente script resolvió mi problema:

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

Comprueba si localStorage existe y puede usarse y, en el caso negativo, crea un almacenamiento local falso y lo usa en lugar del localStorage original. Avíseme si necesita más información.

Bogdan Mates
fuente