Why is localStorage sometimes null in MacOS/iOS devices right after availability check?
Asked Answered
G

3

9

I am experiencing a problem, which is not very frequent but still occurs on a regular basis, in a web application that makes use of local storage.

The problem is only occurring in iOS/macOS devices, regardless of browser, and the error that is being logged is the following:

TypeError: 'null' is not an object (evaluating 'localStorage.getItem')

Here's how the code works; there is a function (isLocalStorageSupported) that checks if local storage is supported, and there is another function (getLocalData) that uses it before trying to get data from the storage.

The error is thrown after the check for local storage completes by returning true, but the next line inexplicably (?) throws when trying to get a key from local storage, as the storage variable at that stage seems to be null.

It's unclear to me how this can happen, hence my question.

// The function that checks availability
const isLocalStorageSupported = (() => {
  let localStorageSupported = null;

  return () => {
    if (localStorageSupported !== null) {
      return localStorageSupported;
    }

    try {
      localStorage.setItem('check', 'check');
      localStorage.getItem('check');
      localStorage.removeItem('check');

      localStorageSupported = true;
    } catch (error) {
      localStorageSupported = false;
      console.error('localStorage not supported');
    }

    return localStorageSupported;
  };
})();

const getLocalData = (key) => {
  if (isLocalStorageSupported()) { // This check is successful

    let data = localStorage.getItem(key); // This line throws a TypeError!

    if (!data) {
      return;
    }

    return data;
  }
};
Goss answered 9/3, 2021 at 20:8 Comment(9)
What is key in this case? It should be a string or something that can be converted via .toString().Valadez
@Valadez Thanks for the comment. The key is a string.Goss
Just want to check I understand the situation right-- when you run the minimal example you've provided multiple times on the same iOS/MacOS browser, device, and other conditions, it sometimes works and sometimes doesn't?Valadez
@Valadez Yes, it fails on less than 1 percent of all orders, that's why it's so hard to debugGoss
I should also mention that it works for customers up until they are done with their order, then all of a sudden it throws this error. We are accessing storage on every order step.Goss
what is the point of the first if statement if (localStorageSupported !== null)?Peppie
@TheBombSquad The purpose is to do the check once. So it localStorageSupported has not been set yet (a.k.a. null) we proceed with the availability check, if it is something else than null it has been set and we can return the value (which is either true or false at that point)Goss
Did you ever find the cause @ProgrammerPer, we were thinking it could be incognito mode.Lovell
@Lovell no, I have since left this workplace and hence I wasn't able to follow up :)Goss
V
2

localStorage will NOT throw an error if the item doesn't exist. It will return null:

//Purpose of this is to generate a completely random key to prove my point
let randomKey = String.fromCharCode(...crypto.getRandomValues(new Uint8Array(100))).match(/[A-Z]|[a-z]|[0-9]/g).join('');

//Get the value associated with the key (there is none)
console.log(localStorage.getItem(randomKey)); //--> null

If you need to check if a browser supports localStorage, check this way:

let localStorageSupported = 'localStorage' in window;
//this is equal to !!window.localStorage or Boolean(window.localStorage)

if (!localStorageSupported) {
    alert('Get a new browser!');
}
Vesicate answered 12/3, 2021 at 22:3 Comment(5)
Thanks for providing your input. The problem is not that the item doesn't exist, the problem is that localStorage itself is null after the availability check. The availability test is in accordance with MDN guidelines: developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/…Goss
@Goss Okay, the reason might be with disabling cookies. Check this answer out.Vesicate
Yes that could be an explanation, however when I have tried to reproduce this kind of scenario I get a SecurityError, not the TypeError that was logged to Splunk. I should also mention that this error usually occurs during the last step in the order flow, after the customers have succesfully gone through completion and are done with their orders. I find it hard to explain how storage could have been working up until that point without crashing, as we are polling it in every order step. I can't see, either, why customers would suddenly disable cookies and storage after order completion.Goss
@ProgrammerPer, if it is a security error, make sure you are using valid httpsVesicate
Good point @quicVO, after disabling localstorage in this manner, with https on, the code still crashes in the check function as expected and is caught by the try/catch statement, and the function returns false. So the line that throws a TypeError as the described in the question is executing safely. So the question still remains.Goss
C
2

Technically : you are caching the result of your check, and return the cached result.

If localStorage is valid on the first call to isLocalStorageSupported(), and becomes invalid afterwards, without the page being reloaded, your function would still return true.

"invalid" could maybe be a change in the state of the built-in localStorage object, or more probably some function mesing with the global localStorage object.

Extra point, probably not relevant but worth noting :
your code snippet shows an acces to an unqualified variable named localStorage (e.g : not window.localStorage), which could be provided or handled by some code in your javascript framework.


The simplest suggestion would be to not cache the result.

To avoid clobbering a possible "check" key in localStorage, you may use a more distinctive key name (e.g : "__my_company_check__").

You can confirm whether your issue comes from this caching behavior (note: this suggestion still introduces an uncached check at each call site, it will allow you to decide if your issue comes from caching the test value) :

function uncachedCheck() { ... }
function cachedCheck() { ... }

if (uncachedCheck() !== cachedCheck()) {
    alert('state of localStorage has changed !')
    // feed extra information to Splunk ...
}

I don't have a good suggestion to fix the cached version of your check : perhaps check that localStorage is still the same object ? (I don't know how different browsers support localStorage, though)

let localStorageSupported = null;
let checkedValue = undefined;

try {
if ((localStorage === checkedValue) && (localStorageSupported !== null)) {
    return localStorageSupported;
}

checkedValue = localStorage;
...
Clink answered 19/3, 2021 at 8:30 Comment(7)
Thanks for your thought out answer. Since the bounty will soon expire, and you have come up with the most thorough explanation so far I will award it to you. I see why this situation theoretically could occur, and in that sense you have provided a reasonable explanation, but I have a hard time seeing why practically all of the customers that experience this issue successfully navigated through all ordersteps with full access to localStorage and then all of a sudden blocking cookies manually on order completion and in so doing caused the crash.Goss
(Continued) Since localStorage is readonly no one should be able to accidentally be setting it to null. To me there seems to be something else that could be causing this sudden / unexpected unavailability so I was hoping to see if someone had had the same issues, in order to identify the probable root cause.Goss
Do you have a polyfill for localStorage ? it could be that you see the issue on browsers where localStorage in not native, and this could fit the "blocking cookies" trogger (if the polyfill relies on cookies)Clink
We don't, but good point! Although sometimes we are iframed, maybe the framing party polyfills? Sometimes there is a redirect in the final step too, if that is a clue.Goss
@Goss : did you identify your issue ?Clink
Actually no :) The problem persists even if there is a cached check or not. The availability check passes and then the error is thrown when localStorage is accessed the next time, and there is only a fraction of a second from one access attempt to the next.Goss
Could browser extensions be interfering, perhaps suddenly disabling due to certain page triggers?Institutor
D
0

Since it's only for iOS (are you sure about all macOS browsers?), this could be Intelligent Tracking Prevention:

https://webkit.org/tracking-prevention/

Therefore, ITP deletes all cookies created in JavaScript and all other script-writeable storage after 7 days of no user interaction with the website.

Dissimilarity answered 16/2 at 18:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.