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:
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!
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.
Very interesting Dave! Thanks for sharing this piece of information!
Muchas gracias! Me ayudaste a solucionar un gran problema que tenia en mi aplicación. Genio!
Glad to hear that it was useful to you! Have a nice day!
Thanks Dave for the information and also thanks David for article