Limitations of Apple iOS captive portal web browser

At my employer Softhouse  (the best IT consultancy in Karlskrona, and we’re hiring!) I recently got the requirement to make an AngularJS web application work properly in the captive portal window on Apple iOS.

These requests are made with HTTP header:

User-Agent: CaptiveNetworkSupport-325.10.1 wispr

A captive portal is the login screen you get when you connect to many wifi access points at hotels, airports, and similar. The application works fine in a normal Safari browser on iOS, but not in the captive portal web browser.

The captive portal web browser is almost a complete web browser, but while normal Safari session on iOS can be debugged over USB, it is not possible to debug the captive portal web browser! (If you figure it out, please comment below!)

When I could not access the JavaScript console, I had to resort to debug by displaying application state on the web page.

Finally I discovered that the JavaScript application stopped working after calling Windows.sessionStorage.setItem().

Yes, for reasons hidden in the annals of history the AngularJS application depends on Windows.sessionStorage. The original developers probably had a good reason. Or at least some reason.

The session storage service in the application looked something like this:

return {
   get: function(key) {
      return sessionStorage.getItem(key);
   },
   set: function(key, val) {
      sessionStorage.setItem(key, val);
   },
   unset: function(key) {
      sessionStorage.removeItem(key);
   }
};

So maybe sessionStorage is not defined? I tried a simple:

if (sessionStorage) {
    return {
        get: function (key) {
            return sessionStorage.getItem(key);
        },
        set: function (key, val) {
            sessionStorage.setItem(key, val);
        },
        unset: function (key) {
            sessionStorage.removeItem(key);
        }
    };
} else {
    var session = {};
    return {
        get: function (key) {
            return session[key];
        },
        set: function (key, val) {
            session[key] = String(val);
        },
        unset: function (key) {
            delete session[key];
        }
    };
}

But, same error!

According to Apple’s own documentation on Key-Value Storage, Safari can throw an exception from sessionStorage.setItem() and a Stack Overflow comment states:

I found the same behaviour on Safari to be caused by private mode browsing. It seems that while on private browsing Safari does not allow local storage to be used at all.

Maybe the captive portal works like the private mode  browsing…?

So, let’s try this code:

var hasSessionStorage = !!sessionStorage;

if (hasSessionStorage) {
    try {
        sessionStorage.setItem("hasSessionStorage", true);
    }
    catch (e) {
        hasSessionStorage = false;
    }
}

if (hasSessionStorage) {
    return {
        get: function (key) {
            return sessionStorage.getItem(key);
        },
        set: function (key, val) {
            sessionStorage.setItem(key, val);
        },
        unset: function (key) {
            sessionStorage.removeItem(key);
        }
    };
} else {
    var session = {};
    return {
        get: function (key) {
            return session[key];
        },
        set: function (key, val) {
            session[key] = String(val);
        },
        unset: function (key) {
            delete session[key];
        }
    };
}

Finally, it works!

Leave a Reply

Your email address will not be published. Required fields are marked *