iPhone application cache and XMLHttpRequest - iphone

I have a WebApp that I've been try to make work offline. The WebApp is too big, even minified, to simply use the application cache (things download but I eventually get a window.applicationCache error). I'm trying to use XMLHttpRequest to get the larger scripts and main html and keep them in localStorage and just keep a small loader script in the application cache. The problem I'm seeing is that the XMLHttpRequest returns a network error when the loader script is being served locally. When the the cache is downloading no error is returned and it works fine. When I turn off the application cache the loader works fine, but of course then I need the network to get the loader.
I tried setRequestHeader("Cache-Control", "no-cache") but that didn't help.
Anybody have a clue?

What does your network: section in your manifest look like?
I found that if I weren't allowing wildcard network traffic it wouldn't load with XMLHttpRequest. So changing it to:
Network:
*
did the trick for us.

I think I found a solution. It would probably work for others.
I split the loader into two separate HTML files: one that uses XMLHttpRequest to get all the required files and put them in localStorage (the loader) and another that simply reads the files from localStorage and writes them into the document (the booter) with appropriate wrappers (e.g. ). The booter has a manifest file to keep it in the application cache. The loader does not. The user first invokes the booter. If the booter finds files already in localStorage it does it's thing. Otherwise, it uses location.replace() to invoke the loader. The loader loads the files from the server using XMLHttpRequest and puts them in localStorage, and then re-invokes the booter using location.replace(). This seems to not cause an network error.
In order to run offline, the user must invoke the booter in the iPhone Safari browser (which invokes the loader, which re-ivokes the booter) which boots the WebApp. In Safari, the user must then add the WebApp (the booter link) to their Home Screen (using the "+" button at the bottom). When offline the user can get to the app from the Home Screen icon. It takes a few seconds to re-render, but it's fully functional after that. It's the same delay when online. Invoking the link from the iPhone Safari browser will not work offline, though it will work online.
The booter monitors the application cache's "updateready" event so that when online and the when iPhone detects a change in the booter's manifest file and downloads a new booter, it will swap the new cache (window.applicationCache.swapCache()) and invoke the loader using location.replace() again. I also add an alert() to let the user know something funky is going on. So changing the manifest file (I mean making some bytes different, not just tweaking the modify time) will cause clients to get new files when online.
Interestingly, I noticed that localStorage set up in Safari is not available to the same page served from invoking the Home Screen icon, even though the cookies transfer! So the first time the booter is invoked from the icon it will reload the files even though they were previously loaded in Safari. Also, I had to explicitly prevent the loader from being cached as it was not reloading from the server when the rest of the files were updated.

You are correct. Ultimately it was the network section in the manifest.
I thought the site where the application was loaded from was included automatically and you didn't need to mess with it, but it's not true. You need to put the site in the network section.

Related

IONIC 4: How to call iOs file resource from custom script within app

Here is the situation:
Ionic 4: (cli-5.2.7)
XCode 10.3
We have a custom eReader that lives in our assets folder.
When we want to download an eBook from our server and read it, we download the file and save it using the Native File plugin.
When we start up the reader, we send it the container in which we want the reader to be created and built, and an internal url to the file resource we want to read.
After window.Ionic.WebView.convertFileSrc, the file ref looks like:
ionic://localhost/_app_file_/var/mobile/Containers/Data/Application/[APP-ID]/Library/NoCloud/[BOOK-ID]/vol-001-chapter-006.xhtml
The reader will then build the iframe and send a simple XMHttpRequest to the provided url, then place the resulting html into the iframe.
The problem is that it only works on first load of the eBook from our server
I can see the file being downloaded, and written to its own folder in the apps file system (File.dataDirectory).
When I debug the XMLHttpRequest in our eReader.js, I can see the call being made to the resource and the HTML returning to the request, which is then rendered on the page.
BUT, if I close the app, reopen it, and try to access the already-downloaded book, the XMLHttpRequest in our eReader.js returns nothing.
I can verify that the file exists at the resource location after a reload, but the XMLHttpRequest acts like it cannot find anything at the provided location.
AND, this is only on iOs. Android works as intended.
My questions is, what am I missing? Is this something to do with the iOs permissions or persistence?
Any help would be appreciated.

Invalidating an HTML5 cache (manifest) completely?

I have an iPhone web-app, and I want to know how to force a cache refresh.
My cache manifest is this:
CACHE MANIFEST
index.html
file1.css
file1.js
index.html is the meat of the application, so I put that in the cache. At this point, I seem to be boned, as I can't figure out how to get iPhone to invalidate the cache. Even going to Settings > Safari > Clear Cache doesn't work, although I'd like to be able to control this programatically. Removing index.html from the manifest and re-adding it seems to work, but I would have to know that all my clients had a clean hit of the updated manifest.
How do I cache index.html and still have it updated when it changes?
Off the top of my head, any change to the manifest will do the trick - and manifests can contain comments starting with #. Just add a random comment and it'll work.
It's a useful property, when I worked on an HTML5 application in a git repository I used to have the manifest automatically regenerated with a comment containing the HEAD hash after each commit so that the changes always propagate to the users.
There seems to be a bug in WebKit Browsers preventing them to reload the website on manifest changes, see this link
Have no clue for a workaround except when I call the index.html directly.
No chance for a iPad/iPhone-WebApp pinned to do the job...
Yes, you can use JavaScript to force Safari to reload cached resource files.
According to Apple, modifying the cache manifest file will cause Safari to reload any changed resource files. But those reloaded files won't be used by the browser until the user visits the site a second time. That delay can a pain, especially during development.
To force Safari to reload the cache contents immediately, Apple says you can use this JavaScript to manipulate the applicationCache object:
function updateSite(event) {
window.applicationCache.swapCache();
}
window.applicationCache.addEventListener('updateready', updateSite, false);

GWT Caching Concept

Can someone explain to me in simple term the concept of caching in GWT. I have read this in many places but may be due to my limited knowledge, i'm not being able to understand it.
Such as nocache.js, cache.js
or other things such as making the client cache files forever or how to make files cached by the client and then if file get changed on the server only then the client download these files again
Generally, there are 3 type of files -
Cache Forever
Cache for some time
Never Cache
Some files can never be cached, and will always fall in the "never cache" bucket. But the biggest performance wins comes from systematically converting files in the second bucket to files that can be cached forever. GWT makes it easy to do this in various ways.
The <md5>.cache.js files are safe to cache forever. If they ever change, GWT will rename the file, and so the browser will be forced to download it again.
The .nocache.js file should never be cached. This file is modified even if you change a single line of code and recompile. The nocache.js contains the links of the <md5>.cache.js, and therefore it is important that the browser always has the latest version of this file.
The third bucket contains images, css and any other static resources that are part of your application. CSS files are always changing, so you cannot tell the browser 'cache forever'. But if you use ClientBundle / CssResource, GWT will manage the file for you. Every time you change the CSS, GWT will rename the file, and therefore the browser will be forced to download it again. This lets you set strong cache headers to get the best performance.
In summary -
For anything that matches .cache., set a far-in-the-future expires header, effectively telling the browser to cache it forever.
For anything that matches .nocache., set cache headers that force the browser to re-validate the resource with the server.
For everything else, you should set a short expires header depending on how often you change resources.
Try to use ClientBundle / CssResource; this automatically renames your resources to *.cache bucket
This blog post has a good overview of the GWT bootstrapping process (and many other parts of the GWT system, incidentally), which has a lot to do with what gets cached and why.
Basically, the generated nocache.js file is a relatively small bit of JS whose sole purpose is to decide which generated permutation should be downloded.
Each individual permutation consists of the implementation of your app specific to the browser, language, etc., of the user. This is a lot more code than the simple bootstrapping code, and thus needs to be cached for your app to respond quickly. These are the cache.html files that get generated by the GWT compiler.
When you recompile and deploy your app, your users will download the nocache.js file as normal, but this will tell their browsers to download a new cache.html file with the app's new features. This will now be cached as well for the next time they load your app.

UIWebView - remote html loading local images

I'm trying to develop an application using UIWebView. The app is loading remote URL's, so in order to make it quick I want it to use the images included in the application bundle. I was wondering if there is a way to do it without forcing the application to manually replace the address of each image to point into local resources?
I doubt it, unfortunately. Browser security models in general (and this applies to browsers on the desktop as well of course) don't allow file:// scheme URLs to be used in the context of any other scheme (http://) page. If this was allowed, then arbitrary sites you browse to could load local files and possible access private user data, which would be Bad. Local images with the file:// scheme will only work in a page that is itself in the file:// domain.
I'm not sure how to tell you to accomplish this, but perhaps someone else has a clever idea.
You can use this:
Stop Images from loading in UIWebView
...which sounds horrendous, but is actually quite easy to do, and lets you replace requests for the remote URL with a hit to the local URL.
You'll have to convert the URLs manually, of course.

UIWebView and NSURLCache have a troubled relationship

I am trying to solve 2 things in my UIWebView:
Load local assets in place of remote ones, under the right conditions
Load remote assets as normal if there are no local ones or they are outdated
Allow the webview to go "back" without needing to reload it's html form the server
So to handle #1 and #2, I implemented a custom subclass NSURLCache. It gets called when the webview requests assets, and I can send along the local ones instead, aborting the server call for the asset. I even have a little timestamping system that knows if the local version is old or not to figure out which one to use.
But #3 is throwing me for a loop. It seems that regardless of cache-control headers, UIWebView will ALWAYS reload the HTML when going back.
So I tried to force caching of the HTML in my custom NSURLCache class. It seems to work, and returns the instance of NSCachedURLResponse with the HTML I want, but the UIWebView fails to notice and loads it from the server anyway. After some searching I see there is some "inconsistencies" with UIWebView and the url cache, and I guess this is one. Works great for assets, but not so great for the HTML that makes up the page it's trying to display.
So then I decided that since this may not work, do it manually. I created a stack of urls, and their data that I fetch manually and load it with loadData:MIMEType:textEncodingName:baseURL:. Lo and behold it seemed to work great! I could now go back, the web view delegate would intercept the load, and tell it to load my manually fetched data instead, and the server would not be hit.
However, then I noticed that the NSURLCache was no longer being used for my assets. It seems it only asks for cached assets if you load content with loadRequest: and not by loading data or an html string with the baseUrl set properly.
So despite all my efforts I cannot make a web view conditionally load local assets AND navigate back without hitting the server. It seems like I have a few bugs here in the SDK.
UIWebView ignores the servers Cache-Control header for HTML content
UIWebView asks for a NSCachedURLResponse's for the main HTML request but is then ignored and discarded.
UIWebView asks the NSURLCache for assets if you load it up with a request, but when loading with HTML or data it bypasses the cache completely and requests it directly from the remote server.
So first up, anyone see a solution to these things? And second, any reason that I am being stupid and these are not actually SDK bugs?
To tackle your first #3:
While it may seem a little unorthodox, how about persisting the downloaded HTML to an NSUserDefaults object as a flat string, and restoring it (conditionally) later when you need it?
I've abandoned this. There were just too many gotchas. Instead I just load new page in via ajax. And instead of [webview goBack] I have some custom javascript to do cool stuff for me on [webview stringByEvaluatingJavascriptFromString:#"goBack()"]