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!

5 comments

  1. I ran into an issue today where the captive portal would not appear at all on iOS, but worked on Android. What I found was that, strangely, there is a 128KB limit to the size of the HTML resource fed to the captive browser. The HTML can reference external resources/images that bring the total over 128KB, it’s just the initial resource that has this limitation. This is totally undocumented (like just about everything else iOS captive portal related) and hadn’t seen anybody else who’d come to this conclusion, so maybe it will help somebody some day.

  2. Muchas gracias! Me ayudaste a solucionar un gran problema que tenia en mi aplicación. Genio!

Leave a comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.