A word game source code is placed in the file Main.js.
And now I am trying to add another file, Bookmarks.js (included in the web page before the Main.js file), which would have an object var bookmarks = {}; synchronized with local storage.
Unfortunately, when I call functions defined in Bookmarks.js from the Main.js, I get the error messages stating, that the bookmarks is null:
Here a text copy of the error message:
test?player=abcde:110 Uncaught TypeError: Cannot read property 'hasOwnProperty' of null
at getBookmark (test?player=abcde:110)
at updateBoard (test?player=abcde:244)
at setSelectedGid (test?player=abcde:159)
at rebuildMenu (test?player=abcde:197)
at WebSocket.ws.onmessage (test?player=abcde:151)
Please helpe me to understand, how can it even be - since the bookmarks variable is set at the very top of my Bookmarks.js file:
'use strict';
// a map for local storage: game id -> obj with "words" and "total" properties
var bookmarks = {};
function saveBookmarks(games) {
for (var gid in bookmarks) {
// Javascript: skip any inherited properties
if (!bookmarks.hasOwnProperty(gid)) {
continue;
}
// if games obj does not have a property gid,
// then remove it from bookmarks obj as well
if (!games.hasOwnProperty(gid)) {
delete bookmarks[gid];
}
}
try {
window.localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
} catch (ex) {
console.log(ex);
}
}
function getBookmark(gid) {
if (
// if the bookmark for the game id is not found
!bookmarks.hasOwnProperty(gid) ||
!bookmarks[gid].hasOwnProperty('words') ||
!bookmarks[gid].hasOwnProperty('total')) {
// then return an empty object
bookmarks[gid] = {
words: '',
total: 0
};
}
return bookmarks[gid];
}
function updateBookmark(gid, words, total, games) {
if (
// if the bookmark for the game id is not found or
!bookmarks.hasOwnProperty(gid) ||
!bookmarks[gid].hasOwnProperty('words') ||
!bookmarks[gid].hasOwnProperty('total') ||
// the bookmark is found, but its total score is lower
total > bookmarks[gid]['total']) {
// then save a bookmark obj with new values
bookmarks[gid] = {
words: words,
total: total
};
// and update the local storage
saveBookmarks(games);
}
return bookmarks[gid];
}
jQuery(document).ready(function($) {
try {
bookmarks = JSON.parse(window.localStorage.getItem('bookmarks'));
} catch (ex) {
console.log(ex);
}
});
You override the bookmarks value defined at the top of the file with:
jQuery(document).ready(function($) {
try {
bookmarks = JSON.parse(window.localStorage.getItem('bookmarks'));
} catch (ex) {
console.log(ex);
}
});
window.localStorage.getItem('bookmarks') will return null if the key does not exist. Then JSON.parse(null) will return null, which is then assigned to bookmarks.
Make sure to use the previous value if bookmarks is not defined in localStorage:
bookmarks = JSON.parse(window.localStorage.getItem('bookmarks')) || bookmarks;
I have built an website with routes, and I'm using localstorage to transfer data between routes.
So I have created onClick handler that storage the data in localstorage using JSON.stringify like this:
const [build, setBuild] = useState({section: ['build']});
const [promote, setPromote] = useState({section: ['promote']});
const [ad, setAd] = useState({section: []});
//..later on function that save localstorage and route to another page
localStorage.setItem('buildObject', JSON.stringify(build));
localStorage.setItem('adObject', JSON.stringify(ad));
localStorage.setItem('promoteObject', JSON.stringify(promote));
Next I'm luring the data from the storage like this:
useEffect(() => {
//Check if buildObject isn't empty
if (JSON.parse(localStorage.getItem('buildObject')) !== null) {
//Do something
}
if (JSON.parse(localStorage.getItem('promoteObject')) !== null) {
//Do something
}
if (JSON.parse(localStorage.getItem('adObject')) !== null) {
//Do something
}
}, []);
The problem is, that even if ad state is empty, it presents himselfs as not empty, and the if statement overrides.
How can I check if localstorage has object inside and if it's empty?
You have set localStorage.setItem('adObject', JSON.stringify(ad)) is object has empty data; You will get JSON.parse(localStorage.getItem('adObject')) the same the thing you have set before. Just check,
const adStorage = JSON.parse(localStorage.getItem('adObject'));
if (adStorage !== null && adStorage.section.length) {
//Do something
}
You can do it using for in loop:
const adData = JSON.parse(localStorage.getItem('adObject'));
for (const property in adData) {
if (ad.property.length) {
// do something
}
}
I am currently trying to add my own extension to showdown using the ghost blogging platform. I am trying to make it so someone can type map and then a uk postcode and have it rendered to a map, like so [map bh278bf]. I have made sure the maps.js extension has been added and works as I have tested it. However my Regex knowledge is practically non-existent. I have got the RegEx to work here in Regexr.com, but when I run it nothing happens, I have used the same codepen and it also doesn't work and I have no idea what to do. I need some assistance in identifying the string!
The Expression:
/(\[map )([A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]?[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)(\])/igm
The extension (maps.js)
(function(){
var maps = function(converter) {
return [
{
type: 'output',
filter: function(source) {
return source.replace(/(\[map )([A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]?[0-9][ABD-HJLN-UW-Z]{2}|GIR 0AA)(\])$/gim, function(match) {
return "<span>Map will go here</span>";
});
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.prettify = maps; }
// Server-side export
if (typeof module !== 'undefined') module.exports = maps;
}());
You were close but not there yet.
Your regex is not valid and should read:
/[map (GIR 0AA|A-PR-UWYZ ?[0-9][ABD-HJLNP-UW-Z]{2})]/ig
With i for case insensitiveness. See answer
https://stackoverflow.com/a/29302162/475884
The actual export is invalid where instead window.Showdown.extensions.prettify = maps you should have window.Showdown.extensions.maps = maps
Where you get
// https://stackoverflow.com/questions/164979/uk-postcode-regex-comprehensive
(function(){
var maps = function(converter) {
return [ {
type: 'lang',
filter: function(text) {
return text.replace(/\[map (GIR 0AA|[A-PR-UWYZ]([A-HK-Y]([0-9][A-Z]?|[1-9][0-9])|[1-9]([0-9]|[A-HJKPSTUW])?) ?[0-9][ABD-HJLNP-UW-Z]{2})\]/ig, 'map');
}
}];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.maps = maps; }
// Server-side export
if (typeof module !== 'undefined') module.exports = maps;
}());
You may have a look at the following fsfiddle for details.
I have a Node.js application that, upon initialisation, reads two tables from an SQL database and reconstructs their relationship in memory. They're used for synchronously looking up data that changes (very) infrequently.
Problem: Sometimes I can't access the data, even though the application reports successfully loading it.
Code:
constants.js
module.exports = {
ready: function () { return false; }
};
var log = sysLog('core', 'constants')
, Geo = require('../models/geo.js');
var _ready = false
, _countries = []
, _carriers = [];
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
var obj = country.toPlainObject()
, id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
_countries = countries;
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
_carriers = carriers;
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
var log = sysLog('core', 'utils')
, constants = require('./constants');
module.exports = {
getCountryByISO: function(iso) {
if (!iso) {
return;
}
if ('string' != typeof iso) {
throw new Error('getCountryByISO requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
switch (iso.length) {
case 2:
return _.findWhere(constants.countries(), { 'iso2' : iso.toUpperCase() });
case 3:
return _.findWhere(constants.countries(), { 'iso3' : iso.toUpperCase() });
default:
throw new Error('getCountryByISO requires a 2 or 3 letter ISO code');
}
},
getCarrierByCode: function(code) {
if (!code) {
return;
}
if ('string' != typeof code) {
throw new Error('getCarrierByCode requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'code' : code });
},
getCarrierByHandle: function(handle) {
if (!handle) {
return;
}
if ('string' != typeof handle) {
throw new Error('getCarrierByHandle requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'handle' : handle });
}
};
Use case
if (data.handle) {
carrier = utils.getCarrierByHandle(data.handle);
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}
What's going on: All errors are logged; as soon as I see an error (i.e. "Unknown carrier") in the logs, I check the SQL database to see if it should've been recognised. That has always been the case so far, so I check the debug log to see if data was loaded. I always see "Loaded X countries" and "Loaded Y carriers" with correct values and no sign of "Could not load constants" or any other kind of trouble.
This happens around 10% of the time I start the application and the problem persists (i.e. didn't seem to go away after 12+ hours) and seems to occur regardless of input, leading me to think that the data isn't referenced correctly.
Questions:
Is there something wrong in constants.js or am I doing something very obviously wrong? I've tried setting it up for cyclical loading (even though I am not aware of that happening in this case).
Why can't I (sometimes) access my data?
What can I do to figure out what's wrong?
Is there any way I can work around this? Is there anything else I could to achieve the desired behaviour? Hard-coding the data in constants.js is excluded.
Additional information:
constants.reload() is never actually called from outside of constants.js.
constants.js is required only in utils.js.
utils.js is required in app.js (application entry); all files required before it do not require it.
SQL access is done through an in-house library built on top of knex.js and bluebird; so far it's been very stable.
Versions:
Node.js v0.10.33
underscore 1.7.0
bluebird 2.3.11
knex 0.6.22
}).finally(function () {
_ready = true;
});
Code in a finally will always get called, regardless of if an error was thrown up the promise chain. Additionally, your reload().catch(/* ... */) clause will never be reached, because finally swallows the error.
Geo.Country.find() or Geo.Carrier.Descriptor.find() could throw an error, and _ready would still be set to true, and the problem of your countries and carriers not being set would persist.
This problem would not have occurred if you had designed your system without a ready call, as I described in my previous post. Hopefully this informs you that the issue here is really beyond finally swallowing a catch. The real issue is relying on side-effects; the modification of free variables results in brittle systems, especially when asynchrony is involved. I highly recommend against it.
Try this
var log = sysLog('core', 'constants');
var Geo = require('../models/geo.js');
var index;
var _countries;
var _carriers;
function reload() {
index = Object.create(null);
_countries = Geo.Country.find().map(function (country) {
var obj = country.toPlainObject();
var id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
});
_carriers = _countries.then(function(countries) {
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
});
});
return _carriers;
}
reload().done();
module.exports = {
reload : reload,
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
constants.reload() is never actually called from outside of
constants.js.
That's your issue. constants.reload() reads from a database, which is an aysnchronous process. Node's require() is a synchronous process. At the time constants.js is required in utils.js and the module.exports value is returned, your database query is still running. And at whatever point in time that app.js reaches the point where it calls a method from the utils module, that query could still be running, resulting in the error.
You could say that requiring utils.js has the side-effect of requiring constants.js, which has the side-effect of executing a database query, which has the side-effect of concurrently modifying the free variables _countries and _carriers.
Initialize _countries and _carriers as unresolved promises. Have reload() resolve them. Make the utils.js api async.
promises.js:
// ...
var Promise = require('bluebird');
var countriesResolve
, carriersResolve;
var _ready = false
, _countries = new Promise(function (resolve) {
countriesResolve = resolve;
})
, _carriers = new Promise(function (resolve) {
carriersResolve = resolve;
});
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
// ...
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
countriesResolve(countries);
return Geo.Carrier.Descriptor.find().map(function (carrier) {
// ...
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
carriersResolve(carriers);
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
getCarrierByHandle: function(handle) {
// ...
return constants.carriers().then(function (carriers) {
return _.findWhere(carriers, { 'handle' : handle });
});
}
Use case:
utils.getCarrierByHandle(data.handle).then(function (carrier) {
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}).then(function () {
// ... next step in application logic
});
This design will also eliminate the need for a ready method.
Alternatively, you could call constants.reload() on initialization and hang all possibly-dependent operations until it completes. This approach would also obsolete the ready method.
What can I do to figure out what's wrong?
You could have analyzed your logs and observed that "Loaded X countries" and "Loaded Y carriers" were sometimes written after "Unknown carrier", helping you realize that the success of utils.getCarrierByHandle() was a race condition.
My webapp have javascript errors in ios safari private browsing:
JavaScript:error
undefined
QUOTA_EXCEEDED_ERR:DOM Exception 22:An attempt was made to add something to storage...
my code:
localStorage.setItem('test',1)
Apparently this is by design. When Safari (OS X or iOS) is in private browsing mode, it appears as though localStorage is available, but trying to call setItem throws an exception.
store.js line 73
"QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."
What happens is that the window object still exposes localStorage in the global namespace, but when you call setItem, this exception is thrown. Any calls to removeItem are ignored.
I believe the simplest fix (although I haven't tested this cross browser yet) would be to alter the function isLocalStorageNameSupported() to test that you can also set some value.
https://github.com/marcuswestin/store.js/issues/42
function isLocalStorageNameSupported()
{
var testKey = 'test', storage = window.sessionStorage;
try
{
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return localStorageName in win && win[localStorageName];
}
catch (error)
{
return false;
}
}
The fix posted on above link did not work for me. This did:
function isLocalStorageNameSupported() {
var testKey = 'test', storage = window.localStorage;
try {
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
}
Derived from http://m.cg/post/13095478393/detect-private-browsing-mode-in-mobile-safari-on-ios5
As mentioned in other answers, you'll always get the QuotaExceededError in Safari Private Browser Mode on both iOS and OS X when localStorage.setItem (or sessionStorage.setItem) is called.
One solution is to do a try/catch or Modernizr check in each instance of using setItem.
However if you want a shim that simply globally stops this error being thrown, to prevent the rest of your JavaScript from breaking, you can use this:
https://gist.github.com/philfreo/68ea3cd980d72383c951
// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem
// throw QuotaExceededError. We're going to detect this and just silently drop any calls to setItem
// to avoid the entire page breaking, without having to do a check at each usage of Storage.
if (typeof localStorage === 'object') {
try {
localStorage.setItem('localStorage', 1);
localStorage.removeItem('localStorage');
} catch (e) {
Storage.prototype._setItem = Storage.prototype.setItem;
Storage.prototype.setItem = function() {};
alert('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.');
}
}
In my context, just developed a class abstraction.
When my application is launched, i check if localStorage is working by calling getStorage(). This function also return :
either localStorage if localStorage is working
or an implementation of a custom class LocalStorageAlternative
In my code i never call localStorage directly. I call cusStoglobal var, i had initialised by calling getStorage().
This way, it works with private browsing or specific Safari versions
function getStorage() {
var storageImpl;
try {
localStorage.setItem("storage", "");
localStorage.removeItem("storage");
storageImpl = localStorage;
}
catch (err) {
storageImpl = new LocalStorageAlternative();
}
return storageImpl;
}
function LocalStorageAlternative() {
var structureLocalStorage = {};
this.setItem = function (key, value) {
structureLocalStorage[key] = value;
}
this.getItem = function (key) {
if(typeof structureLocalStorage[key] != 'undefined' ) {
return structureLocalStorage[key];
}
else {
return null;
}
}
this.removeItem = function (key) {
structureLocalStorage[key] = undefined;
}
}
cusSto = getStorage();
It seems that Safari 11 changes the behavior, and now local storage works in a private browser window. Hooray!
Our web app that used to fail in Safari private browsing now works flawlessly. It always worked fine in Chrome's private browsing mode, which has always allowed writing to local storage.
This is documented in Apple's Safari Technology Preview release notes - and the WebKit release notes - for release 29, which was in May 2017.
Specifically:
Fixed QuotaExceededError when saving to localStorage in private browsing mode or WebDriver sessions - r215315
To expand on others' answers, here is a compact solution that doesn't expose/add any new variables. It doesn't cover all bases, but it should suit most people who just want a single page app to remain functional (despite no data persistence after reload).
(function(){
try {
localStorage.setItem('_storage_test', 'test');
localStorage.removeItem('_storage_test');
} catch (exc){
var tmp_storage = {};
var p = '__unique__'; // Prefix all keys to avoid matching built-ins
Storage.prototype.setItem = function(k, v){
tmp_storage[p + k] = v;
};
Storage.prototype.getItem = function(k){
return tmp_storage[p + k] === undefined ? null : tmp_storage[p + k];
};
Storage.prototype.removeItem = function(k){
delete tmp_storage[p + k];
};
Storage.prototype.clear = function(){
tmp_storage = {};
};
}
})();
I had the same problem using Ionic framework (Angular + Cordova). I know this not solve the problem, but it's the code for Angular Apps based on the answers above. You will have a ephemeral solution for localStorage on iOS version of Safari.
Here is the code:
angular.module('myApp.factories', [])
.factory('$fakeStorage', [
function(){
function FakeStorage() {};
FakeStorage.prototype.setItem = function (key, value) {
this[key] = value;
};
FakeStorage.prototype.getItem = function (key) {
return typeof this[key] == 'undefined' ? null : this[key];
}
FakeStorage.prototype.removeItem = function (key) {
this[key] = undefined;
};
FakeStorage.prototype.clear = function(){
for (var key in this) {
if( this.hasOwnProperty(key) )
{
this.removeItem(key);
}
}
};
FakeStorage.prototype.key = function(index){
return Object.keys(this)[index];
};
return new FakeStorage();
}
])
.factory('$localstorage', [
'$window', '$fakeStorage',
function($window, $fakeStorage) {
function isStorageSupported(storageName)
{
var testKey = 'test',
storage = $window[storageName];
try
{
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
}
catch (error)
{
return false;
}
}
var storage = isStorageSupported('localStorage') ? $window.localStorage : $fakeStorage;
return {
set: function(key, value) {
storage.setItem(key, value);
},
get: function(key, defaultValue) {
return storage.getItem(key) || defaultValue;
},
setObject: function(key, value) {
storage.setItem(key, JSON.stringify(value));
},
getObject: function(key) {
return JSON.parse(storage.getItem(key) || '{}');
},
remove: function(key){
storage.removeItem(key);
},
clear: function() {
storage.clear();
},
key: function(index){
storage.key(index);
}
}
}
]);
Source: https://gist.github.com/jorgecasar/61fda6590dc2bb17e871
Enjoy your coding!
Here's a solution for AngularJS using an IIFE and leveraging the fact that services are singletons.
This results in isLocalStorageAvailable being set immediately when the service is first injected and avoids needlessly running the check every time local storage needs to be accessed.
angular.module('app.auth.services', []).service('Session', ['$log', '$window',
function Session($log, $window) {
var isLocalStorageAvailable = (function() {
try {
$window.localStorage.world = 'hello';
delete $window.localStorage.world;
return true;
} catch (ex) {
return false;
}
})();
this.store = function(key, value) {
if (isLocalStorageAvailable) {
$window.localStorage[key] = value;
} else {
$log.warn('Local Storage is not available');
}
};
}
]);
The accepted answer seems not adequate in several situations.
To check whether the localStorage or sessionStorage are supported, I use the following snippet from MDN.
function storageAvailable(type) {
var storage;
try {
storage = window[type];
var x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
(storage && storage.length !== 0);
}
}
Use this snippet like this, and fallback to, for example, using cookie:
if (storageAvailable('localStorage')) {
// Yippee! We can use localStorage awesomeness
}
else {
// Too bad, no localStorage for us
document.cookie = key + "=" + encodeURIComponent(value) + expires + "; path=/";
}
I have made the fallbackstorage package which uses this snippet to check for the storage availability and fallback to a manually implemented MemoryStorage.
import {getSafeStorage} from 'fallbackstorage'
getSafeStorage().setItem('test', '1') // always work
I just created this repo to provide sessionStorage and localStorage features for unsupported or disabled browsers.
Supported browsers
IE5+
Chrome all versions
Mozilla all versions
Yandex all versions
How it works
It detects the feature with the storage type.
function(type) {
var testKey = '__isSupported',
storage = window[type];
try {
storage.setItem(testKey, '1');
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
};
Sets StorageService.localStorage to window.localStorage if it is supported or creates a cookie storage.
Sets StorageService.sessionStorage to window.sessionStorage if it is supported or creates a in memory storage for SPA, cookie storage with sesion features for non SPA.
Here is an Angular2+ service version for memory storage alternative, you can just inject into your components, based on Pierre Le Roux' answer.
import { Injectable } from '#angular/core';
// Alternative to localstorage, memory
// storage for certain browsers in private mode
export class LocalStorageAlternative {
private structureLocalStorage = {};
setItem(key: string, value: string): void {
this.structureLocalStorage[key] = value;
}
getItem(key: string): string {
if (typeof this.structureLocalStorage[key] !== 'undefined' ) {
return this.structureLocalStorage[key];
}
return null;
}
removeItem(key: string): void {
this.structureLocalStorage[key] = undefined;
}
}
#Injectable()
export class StorageService {
private storageEngine;
constructor() {
try {
localStorage.setItem('storage_test', '');
localStorage.removeItem('storage_test');
this.storageEngine = localStorage;
} catch (err) {
this.storageEngine = new LocalStorageAlternative();
}
}
setItem(key: string, value: string): void {
this.storageEngine.setItem(key, value);
}
getItem(key: string): string {
return this.storageEngine.getItem(key);
}
removeItem(key: string): void {
this.storageEngine.removeItem(key);
}
}
Don't use it if not supported and to check support just call this function
sharing in Es6 full read and write localStorage Example with support check
const LOCAL_STORAGE_KEY = 'tds_app_localdata';
const isSupported = () => {
try {
localStorage.setItem('supported', '1');
localStorage.removeItem('supported');
return true;
} catch (error) {
return false;
}
};
const writeToLocalStorage =
components =>
(isSupported ?
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(components))
: components);
const isEmpty = component => (!component || Object.keys(component).length === 0);
const readFromLocalStorage =
() => (isSupported ? JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || {} : null);
This will make sure your keys are set and retrieved properly on all browsers.
I have created a patch for the issue. Simply I am checking if the browser does support localStorage or sessionStorage or not. If not then the storage engine will be Cookie. But the negative side is Cookie have very tiny storage memory :(
function StorageEngine(engine) {
this.engine = engine || 'localStorage';
if(!this.checkStorageApi(this.engine)) {
// Default engine would be alway cooke
// Safari private browsing issue with localStorage / sessionStorage
this.engine = 'cookie';
}
}
StorageEngine.prototype.checkStorageApi = function(name) {
if(!window[name]) return false;
try {
var tempKey = '__temp_'+Date.now();
window[name].setItem(tempKey, 'hi')
window[name].removeItem(tempKey);
return true;
} catch(e) {
return false;
}
}
StorageEngine.prototype.getItem = function(key) {
if(['sessionStorage', 'localStorage'].includes(this.engine)) {
return window[this.engine].getItem(key);
} else if('cookie') {
var name = key+"=";
var allCookie = decodeURIComponent(document.cookie).split(';');
var cval = [];
for(var i=0; i < allCookie.length; i++) {
if (allCookie[i].trim().indexOf(name) == 0) {
cval = allCookie[i].trim().split("=");
}
}
return (cval.length > 0) ? cval[1] : null;
}
return null;
}
StorageEngine.prototype.setItem = function(key, val, exdays) {
if(['sessionStorage', 'localStorage'].includes(this.engine)) {
window[this.engine].setItem(key, val);
} else if('cookie') {
var d = new Date();
var exdays = exdays || 1;
d.setTime(d.getTime() + (exdays*24*36E5));
var expires = "expires="+ d.toUTCString();
document.cookie = key + "=" + val + ";" + expires + ";path=/";
}
return true;
}
// ------------------------
var StorageEngine = new StorageEngine(); // new StorageEngine('localStorage');
// If your current browser (IOS safary or any) does not support localStorage/sessionStorage, then the default engine will be "cookie"
StorageEngine.setItem('keyName', 'val')
var expireDay = 1; // for cookie only
StorageEngine.setItem('keyName', 'val', expireDay)
StorageEngine.getItem('keyName')
var mod = 'test';
try {
sessionStorage.setItem(mod, mod);
sessionStorage.removeItem(mod);
return true;
} catch (e) {
return false;
}
The following script solved my problem:
// Fake localStorage implementation.
// Mimics localStorage, including events.
// It will work just like localStorage, except for the persistant storage part.
var fakeLocalStorage = function() {
var fakeLocalStorage = {};
var storage;
// If Storage exists we modify it to write to our fakeLocalStorage object instead.
// If Storage does not exist we create an empty object.
if (window.Storage && window.localStorage) {
storage = window.Storage.prototype;
} else {
// We don't bother implementing a fake Storage object
window.localStorage = {};
storage = window.localStorage;
}
// For older IE
if (!window.location.origin) {
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
}
var dispatchStorageEvent = function(key, newValue) {
var oldValue = (key == null) ? null : storage.getItem(key); // `==` to match both null and undefined
var url = location.href.substr(location.origin.length);
var storageEvent = document.createEvent('StorageEvent'); // For IE, 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();
}
It checks if localStorage exists and can be used and in the negative case, it creates a fake local storage and uses it instead of the original localStorage.
Please let me know if you need further information.