XHR Requests only start when tab is focused on IOS 7 Safari - ios

Following use case:
User clicks button on tab 1
App opens popup tab 2 (about:blank)
Severeal XMLHttpRequest occur between app on tab1 and server
App uses that info to create a form POST request from tab1 with target=tab2 (action is another origin, eg. paypal)
Tab 2 loads form response
etc
This works pretty much anywhere. On iOS Safari 7 it seems not.
On iOS Safari 7, the popup opens but stays on about:blank, because the XMLHttpRequest doesn't start because the tab1 at that point is no longer focused (the browser shows tab2).
If the user switches back to tab1, the execution resumes and everything works. (but that's not really intuitive).
I cannot create the XMLHTTPRequest before opening the popup, because the popup has to be created as a direct consequence of a user action or it will end in the popup blocker. Therefore the flow "user click starts request, request callback opens popup" does not work.
Is there a way around this limitation other than restructuring the whole app (eg. loading the http requests from the popup)?

Related

React PWA Image Upload in Mobile Safari breaks application?

We were surprised we didn't find any mention of this anywhere online, so we're posting here in hopes we find a solution.
Using an iPhone with mobile safari is when we hit this issue running the 2 easy to follow tests below, one works, one doesn't.
Here is the link
https://pwa-react.netlify.com/
Here are the 2 tests we run (both listed in the link), one works when not in PWA mode, and the other fails when in PWA mode.
Test #1: Works Perfectly (Expected Behaviour)
Visit https://pwa-react.netlify.com/ from iPhone in mobile safari
1. Make sure you have google drive on the phone but not logged in.
2. Click "Choose File". It will show you the list of options to choose from.
3. Click "Browse" to look for the photo.
4. Click "Cancel" and you're back here.
5. Click "Choose File" it will still show you the list of options to choose from.
This works perfectly in mobile safari but NOT in PWA mode below.
Test #2: Does NOT Work (Unexpected Behaviour) (PWA)
Visit https://pwa-react.netlify.com/ from iPhone in mobile safari, hit the share
button, then add to home screen. This will add the PWA app on your phone. Open App.
1. Make sure you have google drive on the phone but not logged in.
2. Click "Choose File". It will show you the list of options to choose from.
3. Click "Browse" to look for the photo.
4. When it shows you the Google Drive logo with Sign In, double click the home
button, then go back to the PWA.
5. Click "Choose File" it will NOT show you the list of options to choose from.
This is now 100% broken.
The ONLY way to fix it is to go to Settings>Safari>Clear History and Website Data (all the way down)
How can we fix this so when the user hits "Choose File" it shows the list of
options to choose from in the PWA?
Screenshot #1: These are the options that appear in Test #1 and stop appearing in Test #2
Screenshot #2: This screen allows us to cancel in Test #1 but it disappears in Test #2
Any idea how to get Test #2 to work by allowing us to choose the upload options like in Screenshot #1 without breaking the app and having to go to safari settings to clear history and website data for it to function again?
PS - Here is the repository file pwa-react/src/App.js
We were facing almost exactly the same issue in our PWA, so first, off I want to thank you for helping us narrow down the cause.
After reviewing the iOS PWA lifecycle (article here) and a couple maddening hours of trial and error I was able to figure out a solution that is semi-acceptable.
My guess at what is happening when you leave the app mid-upload (Test #2) is that there is some internal context in how iOS handles PWA's that is not being reset, so when you go back and try to upload a file again it thinks that the upload dialog is already open.
The article mentions that opening external links without target=_blank will cause the PWA context to be deleted, so when the in-app browser closes, the PWA page reloads in the standalone window. I thought that might reset the "detached upload" context, and it ended up working.
So I created a page hosted on another domain, and linked to it below our upload button in the PWA:
// not sure the target={'_self'} is necessary but not risking it
<a href={'https://externalDomain.com/reset'} target={'_self'}>
Having Issues? Reset Upload
</a>
This works decently well, minus one issue. When you click this link it opens the in-app browser, but there is no "Done" button or navigation tools for the user to know how to exit. Linking back to the PWA does not work, because iOS detects that and does not reset the app context. What I did notice was that if I navigated to another page from the first external page (I originally just tested this with google.com), the "Done" button would show up, making it obvious how to exit.
With that knowledge, I guessed that you could probably just do window.history.pushState to achieve the same effect, which works. My final solution is below. It causes the entire app to reload when the user presses Done from the in-app browser, but that's far better than having them re-add to the home screen in my opinion.
const Reset: React.FC = props => {
React.useEffect(() => {
// Redirect any wayward users who find this page from outside the PWA
if (!window.matchMedia('(display-mode: standalone)').matches) {
navigate('/');
}
// push an additional page into history
const newUrl = `${window.location.href}?reset`;
window.history.pushState({ path: newUrl }, '', newUrl);
}, []);
return (
<Grid container>
<ArrowUpIcon />
<Typography variant={'h5'}>Press done above to return to App</Typography>
<Typography variant={'body1'}>Sorry for the inconvenience!</Typography>
</Grid>
);
};
Hope this helps! Would love to hear if it works for you.
Edit After Production Testing:
An additional important note is that your "reset" page must be on a completely different domain for this to work. Ran into this today in production, because our reset page was on a subdomain with the same root as the PWA, iOS was not resetting the entire PWA lifecycle.
SUMMARY
Key Issues:
Leaving an iOS PWA while any of the "file upload" dialogs are open ('Take Photo', 'Photo Library', or 'Browse') breaks the iOS PWA lifecycle.This breakage makes it impossible for the user to open any "file upload" dialogs when clicking on a file input.
In order to fix this issue, the PWA context must be completely reset.
It seems that the only ways to reset the PWA context are to restart the phone, delete the app and re-add it to the home screen, or to open an external link.
When opening an external link, the "Done" button that closes the iOS PWA embedded browser will not show on the initial page. The user must navigate to an additional external page in order for the "Done" button to show.
External links do not trigger a reset of the PWA context reset when they have target="_blank".
Solution:
In order for the user to be able to upload files again, the PWA context must be reset. The easiest way to do this (in my opinion) is to ask them to open an external link.
(In PWA): Present a link to the user to fix the fact that the upload dialog is not showing. The link destination must be a completely unrelated domain (not a subdomain) and must have target="_self" (issue #5).
(External Page): Once the user clicks on the link and the external page opens, there will be no visible way to leave the page (issue #4). To resolve this, you can use history.pushState to simulate navigating to an additional page.
(External Page - Bonus): To make it clear to the user that the issue has been resolved, add an arrow in the top left pointing to the "Done" button (as shown in my screenshot).

Web App Manifest: "start_url" doesn't work for Safari

The "start_url" works for Android browsers, but for iPhone, Safari always uses current page's URL and ignores the "start_url".
For example, the current page is https://test.com/index.html, on manifest the "start_url" is set to be "start_url": "index.html?flag=1", but when the page is added to home screen on Safari, it still uses https://test.com/index.html, without the parameter.
Is there a way to apply a different URL as the start up URL for Safari?
A hacky solution I've implemented here is to silently change the URL on the page you prompt the user to install the PWA with: history.pushState({}, "", "/"); (see: https://stackoverflow.com/a/56691294/827129)
This will update the URL but won't cause the page to reload or refresh the DOM, so you can see the page you're on as normal, but when the user goes to install the PWA (Add to home screen) it'll save the root URL, so that's what they'll see when they open the PWA.
Downsides here include:
the URL in the address bar will update (not a big deal on mobile)
if the user presses the back button they'll go "back" to the page you triggered the history.pushState() on, so they'll need to press back twice to actually go back
if the user refreshes the page they'll see the homepage
In my use case this is good enough, there might be extra solutions to handle these issues that could be applied on top of this solution to improve UX.
try this url format
"start_url": "./index.html?flag=1"

Opening apps and closing Safari tabs on iOS

We have an App that accepts donations and per Apple's guidelines (item 21.2) we can't do this in-app, it must open a webpage in Safari to perform the donation. We've got that bit working fine, and we can actually automatically invoke the App post donation and put the user right back where they left off. The trouble is that the Tab in Safari persists when the user returns to Safari later.
Is there a way to open an app from a webpage while simultaneously closing said webpage in Safari?
So apparently the way to do it is via Javascript. You can set the window location and then immediately close the window.
e.g.
window.location = "myapp://?stuff";
window.close;

Captive Wifi Popup: Click a link to open Safari

We are having an issue on our network with iOS devices (ipads, iphones etc): After connecting to the SSID, the iphone / ipad immediately opens the Captive Network Assistant (CNA) - it is like a scaled-down browser without navigation buttons etc - that displays our welcome page (ready for the user to authenticate his MAC in the network to give him internet). This CNA is a functin of iOS, and happens automatically.
I was looking around on this forum how to make the user open his Safari browser by clicking a link (while in the CNA), thus sending him away from the CNA and into Safari (which is where I would prefer him to be).
I searched phrases such as: "How to set a link to open in safari", but I get results about people using phonegap to do such a thing...
One user talked about using
window.open(myURL, '_system')
but I don't know if that is solving my issue.
Basically my plan is to:
- Have my Welcome Page (the page displayed after the user connected to my SSID) detect what type of browser the user's device has. My Welcome Page is hosted at a site which is in the Walled Garden of my controller, so every device can see that Welcome page (whether they have full internet access or not).
- If the user has an iOS device, the CNA browser opens automatically. My controller detects the device has a CNA browser and loads a simple page with the message (like): "click here to start to navigate"
- During the next few seconds (while the user is digesting the message), my controller gives the user's MAC full internet access
- When the user clicks that link in the CNA browser, the CNA closes and then opens Safari, and also forces Safari to open the destination Welcome Page (where I want my user to be, depending on the antenna to which he connected).
I really hope that all makes sense...
I found this link about the CNA browser in this forum:
Bypasses Apple Captive Network Assistant Login in iOS 7
But it talks having the user open Safari right from the start (disabling the CNA function), "hoping" that the user opens Safari (rather then going directly to one of his apps). I prefer that the user has to "click to continue", which makes Safari open and he sees my Welcome Page.
EDIT : this solution doesn't work after iOS 10.3. Not sure if that's a feature or a bug : https://forums.developer.apple.com/thread/75498
EDIT2 : Apparently it works again on iOS 11
There is a simple way to do that, if you have control over the captive portal server.
When connecting to a wifi network, iOS devices send a GET request to a bunch of predefined urls (http://www.apple.com/library/test/success.html, ...) , to see if there is Internet connectivity (wispr requests). The iOS device expects the answer <HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>. If the request succeeds, but the answer is different (for example your captive portal page), it triggers the CNA to open.
At this stage, the CNA thinks you are not connected and will show a Cancel button at the top. Every link will open inside the CNA and not in a Safari window, no matter what you do. The CNA is done so that you can get your user through the log-in process. In order to see if the log-in process is done and connection is finally established, the iOS device will now and then send another wispr request. This happens every 40 seconds, or whenever an HTTP request is emitted (navigation within the CNA).
When your server finally answers Success to a wispr request, the CNA will mark as connected, the button at the top will change into Done. At this stage, any link to an external url will close the CNA and open in Safari :)
So to summarize, you can achieve what you want with the following connection steps :
server answers initial wispr request with your CNA page.
the page opens in CNA. In that page, have some JavaScript that immediately triggers navigation within the CNA, for example : <script>window.location.reload(true)</script> this will trigger the iOS device to immediately send another wispr request
server answers this second wispr request with <HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>, this will trigger the CNA to mark as connected
have your CNA page to show a link to an external url
Extracted from an answer by Ryan at How can I open an external link in Safari not the app's UIWebView?
To have a specific link switch to Safari, simply add target="_system" to the link, as in
Link Text
Or to catch all links in your javascript that have target="_blank", include jQuery and pass them to window.open with the '_system' param. This will work on both iOS and Android.
$(document).on('click', 'a[target="_blank"]', function(ev) {
var url;
ev.preventDefault();
url = $(this).attr('href');
window.open(url, '_system');
});
Javascript does NOT work fully in CNA, it is disabled for security reason.
target="_system" is a safari feature, not a CNA one.
==> briefly, _system fails in CNA.

Invoking Safari on iOS without opening a new website

The situation is like this:
User opens app from a website using a custom urlscheme
User does stuff in the app
User clicks button in the app to return to the website in Safari.
I have tried opening a new tab containing a javascript:window.close() but this does not work on iOS 6.1.
So my question is: Is there a way to open Safari to view the website the user left from? Either with a working new tab that closes itself or a different route?
When you open the app with your custom url scheme, pass the actual page url as an argument.
mycustomUrlScheme://mydomain.com?objectid=1234&callback_url=encoded_url
In your app, handle the url for the content info and keep the page url to open it afterwards. It will make safari open a new tab. But that should be a good start.
As far as i understand you can do it.
user opens mobile safari for example http://www.example.com
user clicks a link that is appscheme://open and the application become active
user taps a button to open safari for example http://www.example.com?q=test
for the third step you can use [[UIApplication sharedApplication]openURL:url]

Resources