html5 erro localStorage no Safari: “QUOTA_EXCEEDED_ERR: DOM Exceção 22: Foi feita uma tentativa de adicionar algo ao armazenamento que excedeu a cota.”

133

Meu webapp tem erros de javascript na navegação privada do ios safari:

JavaScript: erro

Indefinido

QUOTA_EXCEEDED_ERR: DOM Exceção 22: Foi feita uma tentativa de adicionar algo ao armazenamento ...

meu código:

localStorage.setItem('test',1)
leiyonglin
fonte
Use um recurso que detecta testes para esse problema específico . Se o armazenamento não estiver disponível, considere calçar localStorage com memoryStorage . exoneração de responsabilidade: Eu sou o autor dos pacotes vinculados
Stijn de Witt
4
Oi pessoal, eu ajudo a manter o safaridriver. Esse problema é um bug antigo do WebKit que foi corrigido recentemente. O armazenamento local e o armazenamento de sessão agora funcionam no Safari 10.1 e posterior. Essa correção afeta o modo normal de navegação privada e o modo de automação (usado pelo WebDriver).
Brian Burg

Respostas:

183

Aparentemente, isso ocorre por design. Quando o Safari (OS X ou iOS) está no modo de navegação privada, parece que localStorageestá disponível, mas tentar chamar setItemgera uma exceção.

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

O que acontece é que o objeto de janela ainda é exposto localStorageno espaço para nome global, mas quando você chama setItem, essa exceção é lançada. Todas as chamadas para removeItemsão ignoradas.

Acredito que a correção mais simples (embora ainda não testei esse navegador cruzado) seria alterar a função isLocalStorageNameSupported()para testar se você também pode definir algum 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
fonte
1
Isto não tem de ser necessariamente devido ao modo anônimo ... embora eu acho que o OP não deseja armazenar vários megabytes de dados;)
Christoph
5
Confira esta essência mostrando um breve histórico de detecção de armazenamento local por Paul Irish.
Mottie
4
Portanto, no caso de o localStorage não funcionar no Safari, armazenar tudo em cookies é a próxima melhor opção?
Will Hitchcock
5
Semelhante ao exemplo de Paul Irish, sugiro mudar return localStorageName in win && win[localStorageName];para return true. Então você tem uma função que retorna com segurança verdadeira ou falsa, dependendo da disponibilidade do localStorage. Por exemplo:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT
1
Verificou-se que o problema não é apenas na janela privada, mas também na janela normal do safari.
Codemirror 13/06/19
38

A correção postada no link acima não funcionou para mim. Isso fez:

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
fonte
20
Algum motivo específico para você (e @KingKongFrog) estarem usando o window.sessionStorage para detectar se é possível gravar no localStorage ou estamos em um ciclo estranho de erro de copiar e colar?
Yetti
@Yetti, se você notou um erro de digitação, por que não corrigi-lo em uma edição ou em seu comentário? Tanto quanto sei, window.sessionStorageestá correto. Certamente funciona no meu código. Na verdade, aponte a correção para o problema que você parece conhecer.
Novocaine
7
@Novocaine Meu comentário foi apontado que eles estão usando sessionStorage em uma função que existe para verificar o suporte ao localStorage. Sim, provavelmente ainda funcionará, mas, como está escrito, o nome da função é enganoso para o que realmente está sendo testado. Eu escolhi comentar, em vez de editar, porque pensei que estava faltando alguma coisa e esperava aprender com esses caras. Infelizmente, eles não responderam ou fizeram uma correção, então aqui estamos.
Yetti
3
@Yetti Obrigado por esclarecer. Eu vejo o que você estava falando agora. ; -]
Novocaine
2
@DawsonToth não, foi porque eu chamei a função isLocalStorageNameSupportede estava verificando window.sessionStorage. Mesmo resultado final, mas foi um pouco confuso. A resposta foi editada para esclarecer.
precisa
25

Conforme mencionado em outras respostas, você sempre obterá o QuotaExceededError no modo Navegador privado do Safari no iOS e no OS X quando localStorage.setItem(ou sessionStorage.setItem) for chamado.

Uma solução é fazer uma verificação try / catch ou Modernizr em cada instância de uso setItem.

No entanto, se você quiser um calço que simplesmente impeça o lançamento global desse erro, para impedir que o restante do JavaScript seja interrompido, você pode usar o seguinte:

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

No meu contexto, apenas desenvolvi uma abstração de classe. Quando meu aplicativo é iniciado, verifico se localStorage está funcionando chamando getStorage () . Esta função também retorna:

  • localStorage se localStorage estiver funcionando
  • ou uma implementação de uma classe personalizada LocalStorageAlternative

No meu código, eu nunca chamo localStorage diretamente. Eu chamo cusSto var global, eu havia inicializado chamando getStorage () .

Dessa forma, ele funciona com navegação privada ou versões específicas do 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
fonte
2
Obrigado Pierre, sua resposta me inspirou. Acabei empacotando tudo em um bom módulo chamado armazenamento de memória . Código aberto, é claro. Para outras pessoas com o mesmo problema, verifique se ele pode ajudá-lo.
Stijn de Witt
Ha. Eu fiz a mesma coisa (independentemente). Ainda use a variável localStorage (no Safari, pelo menos, você não pode sobrescrever a variável localStorage (é 'somente leitura'), mas é possível reatribuir setItem / removeItem / getItem).
Ruben Martinez Jr.
@StijndeWitt, como posso acessar meus valores de armazenamento em outras páginas? Por exemplo, eu tenho isso no meu helper.php var store = MemoryStorage ('my-app'); store.setItem ('myString', 'Olá MemoryStorage!'); Quero acessar o valor de myString em lecture.php. Tentei iniciar o armazenamento de memória na página, mas ele ainda mostra um objeto vazio.
user1149244
@ user1149244 O armazenamento de memória é local para a página. Ele simula a API de armazenamento na Web e, como tal, pode ser usado como um substituto para quando localStorage e sessionStorage não estiverem disponíveis. No entanto, os dados serão retidos apenas na memória da página (daí o nome). Se você precisar reter dados nas páginas, os cookies podem ajudá-lo. Mas é muito limitado na quantidade de dados que podem ser armazenados. Fora isso, não há muito que possa ser feito.
Stijn de Witt
2
@ user1149244 Isso não é supostamente armazenar os valores no navegador? Não, não pode. Existem três maneiras de armazenar coisas do lado do cliente de uma atualização de página para outra: cookies, sessionStorage / localStorage e IndexedDB. Os dois últimos são relativamente novos. sessionStorage e localStorage são amplamente suportados, para que você possa usá-lo basicamente em qualquer lugar. exceto no modo de navegação privada , que é esse o problema. Os programas foram interrompidos porque o armazenamento não estava lá. O memorystorage apenas fornece um fallback que sempre funciona na página, mas na verdade não pode salvar os dados. É um esboço. Mas sem erro.
Stijn de Witt
5

Parece que o Safari 11 altera o comportamento e agora o armazenamento local funciona em uma janela do navegador privada. Viva!

Nosso aplicativo da web que costumava falhar na navegação privada do Safari agora funciona perfeitamente. Sempre funcionou bem no modo de navegação privada do Chrome, que sempre permitia gravar no armazenamento local.

Isso está documentado nas notas de versão do Safari Technology Preview da Apple - e nas notas de versão do WebKit - da versão 29, que foi em maio de 2017.

Especificamente:

  • Corrigido QuotaExceededError ao salvar em localStorage no modo de navegação privada ou em sessões WebDriver - r215315
karlbecker_com
fonte
4

Para expandir as respostas de outras pessoas, aqui está uma solução compacta que não expõe / adiciona novas variáveis. Ele não cobre todas as bases, mas deve ser adequado para a maioria das pessoas que desejam que um aplicativo de página única permaneça funcional (apesar de não persistir os dados após a 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
fonte
3

Eu tive o mesmo problema usando o framework Ionic (Angular + Cordova). Sei que isso não resolve o problema, mas é o código para os aplicativos angulares com base nas respostas acima. Você terá uma solução efêmera para o LocalStorage na versão iOS do Safari.

Aqui está o 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);
            }
        }
    }
]);

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

Aproveite a sua codificação!

jorgecasar
fonte
1
Embora isso não responda à pergunta, esta é a primeira coisa que surgiu quando pesquisei a questão no Google. O próximo passo seria procurar a solução para o Angular, mas, graças a esse comentário, não preciso ir a outro lugar. Portanto, pode não responder diretamente à pergunta, mas foi ótimo para mim e provavelmente para outros!
Leonard
2

Aqui está uma solução para o AngularJS usando um IIFE e aproveitando o fato de que os serviços são singletons .

Isso resulta em isLocalStorageAvailableser definido imediatamente quando o serviço é injetado pela primeira vez e evita desnecessariamente a execução da verificação toda vez que o armazenamento local precisar ser acessado.

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

Acabei de criar este repo para fornecer sessionStoragee localStorageapresenta para os navegadores não suportados ou deficientes.

Navegadores suportados

  • IE5 +
  • Chrome todas as versões
  • Mozilla todas as versões
  • Yandex todas as versões

Como funciona

Ele detecta o recurso com o tipo de armazenamento.

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

Conjuntos StorageService.localStoragepara window.localStoragese suportada ou cria um armazenamento de cookies. Conjuntos StorageService.sessionStoragepara window.sessionStoragese suportada ou cria um no armazenamento de memória para SPA, o armazenamento de cookies com características sesión para não SPA.

Ahmet Can Güven
fonte
1
Obrigado, sua biblioteca ajudou muito!
Mathieu
1

Aqui está uma versão do serviço Angular2 + para alternativa de armazenamento em memória, você pode simplesmente injetar em seus componentes, com base na resposta 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
fonte
0

Não use se não for suportado e, para verificar o suporte, chame esta função

compartilhando no Es6 leitura e gravação completa localStorage Example with check 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);

Isso garantirá que suas chaves sejam definidas e recuperadas corretamente em todos os navegadores.

Tarandeep Singh
fonte
0

Eu criei um patch para o problema. Simplesmente, estou verificando se o navegador suporta localStorage ou sessionStorage ou não. Caso contrário, o mecanismo de armazenamento será Cookie. Mas o lado negativo é que o Cookie possui uma memória de armazenamento muito pequena :(

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

A resposta aceita parece inadequada em várias situações.

Para verificar se o localStorageou sessionStorageé suportado, eu uso o seguinte trecho do 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 este trecho como este e faça o fallback para, por exemplo, usando o cookie:

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

Fiz o pacote fallbackstorage que usa esse trecho para verificar a disponibilidade de armazenamento e fallback para um MemoryStorage implementado manualmente.

import {getSafeStorage} from 'fallbackstorage'

getSafeStorage().setItem('test', '1') // always work
transang
fonte
-1
var mod = 'test';
      try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
      } catch (e) {
        return false;
      }
Naim DOGAN
fonte
1
Talvez você queira adicionar algumas palavras de explicação?
bogl
-2

O script a seguir resolveu meu 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();
}

Ele verifica se o localStorage existe e pode ser usado e, no caso negativo, cria um armazenamento local falso e o usa em vez do localStorage original. Entre em contato se precisar de mais informações.

Bogdan Mates
fonte