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)
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)
Respostas:
Aparentemente, isso ocorre por design. Quando o Safari (OS X ou iOS) está no modo de navegação privada, parece que localStorage
está disponível, mas tentar chamar setItem
gera 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 localStorage
no espaço para nome global, mas quando você chama setItem
, essa exceção é lançada. Todas as chamadas para removeItem
sã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;
}
}
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 */ }
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
window.sessionStorage
está correto. Certamente funciona no meu código. Na verdade, aponte a correção para o problema que você parece conhecer.
isLocalStorageNameSupported
e estava verificando window.sessionStorage
. Mesmo resultado final, mas foi um pouco confuso. A resposta foi editada para esclarecer.
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.');
}
}
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:
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();
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:
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 = {};
};
}
})();
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!
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 isLocalStorageAvailable
ser 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');
}
};
}
]);
Acabei de criar este repo para fornecer sessionStorage
e localStorage
apresenta para os navegadores não suportados ou deficientes.
Navegadores suportados
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.localStorage
para window.localStorage
se suportada ou cria um armazenamento de cookies. Conjuntos StorageService.sessionStorage
para window.sessionStorage
se suportada ou cria um no armazenamento de memória para SPA, o armazenamento de cookies com características sesión para não SPA.
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);
}
}
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.
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')
A resposta aceita parece inadequada em várias situações.
Para verificar se o localStorage
ou 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
var mod = 'test';
try {
sessionStorage.setItem(mod, mod);
sessionStorage.removeItem(mod);
return true;
} catch (e) {
return false;
}
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.