Xamarin IoS 12 update: WKWebView custom renderer issue - ios

I'm facing with a very weird issue with a Xamarin Forms application on IoS after the latest OS update. Basically this application uses a WebView renderer to navigate a company's intranet portal which has NTLM authentication. I implemented the web renderer to handle the authentication process and it was working fine till few week ago but after Apple's last update the webview has stopped working.
Unfortunately there is no error code, exception or explicit message from the SDK, and the authentication process is working (I can see it from the logs), by the way the webview's content is all blank.
I have tried to navigate to other websites (with no authentication) too (bing.com, google.com, xamarin.com) but the issue was still there.
Anyone has some suggestion or hint?
UPDATE
After a deep investigation I've finally found the problem! Actually it seems that after the deprecation of UIWebview when using a custom renderer to handle authentication scenarios you cannot skip the HTTP 200 response (as suggested before) otherwise the component won't load the html contents!
So this is what I had before in my custom renderer:
public override void DecidePolicy(WKWebView webView, WKNavigationResponse navigationResponse, Action<WKNavigationResponsePolicy> decisionHandler)
{
var url = navigationResponse?.Response?.Url;
if (url == null) return;
if (navigationResponse.Response is NSHttpUrlResponse)
{
var resp = navigationResponse.Response as NSHttpUrlResponse;
if (resp.StatusCode == 200) return;
HandleHttpResponse(resp);
decisionHandler(WKNavigationResponsePolicy.Allow);
}
else if (navigationResponse.Response is NSUrlResponse)
{
//todo handle this case too if we want to open excel files in webview
//UIApplication.SharedApplication.OpenUrl(url);
decisionHandler(WKNavigationResponsePolicy.Cancel);
}
}
and this is what I have changed:
var resp = navigationResponse.Response as NSHttpUrlResponse;
//if (resp.StatusCode == 200) return;
HandleHttpResponse(resp);
decisionHandler(WKNavigationResponsePolicy.Allow);
Just commenting the if for StatusCode 200 and let contine with decisionHandler it works... Very very weird!

Related

AppAuth caching issue / universal link not caught when logging out quickly

On iOS 13.5 with the latest AppAuth (1.4.0), I have a weird caching / universal link issue with logging in through AppAuth, logging out and logging back in again. Based on the documentation, I first discover the configuration from the server with AppAuth:
OIDAuthorizationService.discoverConfiguration(forIssuer: URL(string: "https://identityserver.example.com/")!) { ... }
Then, I build a new request:
let signinRedirectURL = URL(string: "https://portal.example.com/signin-oidc-ios")!
let request = OIDAuthorizationRequest(configuration: config,
clientId: "ios-app",
scopes: ["api"],
redirectURL: signinRedirectURL,
responseType: OIDResponseTypeCode,
additionalParameters: nil)
and present it:
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: viewController) { authState, error in self.processAuthState(authState, error) }
After logging in through the in-app browser popup, the universal link is processed:
if let authorizationFlow = appDelegate.currentAuthorizationFlow, authorizationFlow.resumeExternalUserAgentFlow(with: url) {
appDelegate.currentAuthorizationFlow = nil
} else {
print("...")
}
Finally I process the received authState:
func processAuthState(authState: OIDAuthState, error: Error) {
if let authState = authState, let token = authState.lastTokenResponse?.accessToken {
appDelegate.authState = authState
self.accessToken = token // stored later on for usage by REST API
} else {
print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
}
}
When logging out, I simply throw away the authState and currentAuthorizationFlow. Then, to log in again, the same process begins again.
The weird thing now is that AppAuth does not present a login in-app-browser popup with the login mask at https://identityserver.example.com/ as before in the first login attempt after each app launch, but instead it presents that same popup with the universal link like https://portal.example.com/signin-oidc-ios?code=abcdef&scope=api&state=xyz which was previously caught by iOS and forwarded to the app leading to the call to authorizationFlow.resumeExternalUserAgentFlow(with: url) from above.
Because we have not implemented the universal link fully yet, it leads to an error message, because the URL with the link is not supposed to be called in the browser in the moment but only to communicate the token to the app through the universal link mechanism.
Why does AppAuth or ASWebAuthenticationSession seemingly cache the last URL with an old token from the previous login attempt within the same app launch even though I throw away both the authState and currentAuthorizationFlow and create new ones? Is there something else I should do to "log out", clear the cookies etc?

How to get initial link for iOS using react native firebase dynamic links?

I am using react-native-firebase:: 5.6.0, I am having issue while getting initial link for iOS device. On android it's working fine. I am using "Firebase Dynamic Links" to redirect user inside login screen of my app if in case he is not logged in inside app, otherwise just opening app if he is already logged in.
It's working for android app but having an issue with ios app. I have used two function one is the get dynamic link if app is closed "getInitialLink" and another one is to check when app is opened "onLink".
This is function I am using after closing splash screen, only called once when opening app from closing state.
firebase.links().getInitialLink().then((url) => {
if (url && url === 'https://mycustomdomain.co.in') {
navigationToScreen(AUTH, INITIAL_SCREEN);
} else {
// INITIALIZE APP HERE
}
});
If app already opened I am getting dynamic link url value inside this function::
this.unsubscribeHandleOpenAppDynamicLinks = firebase.links().onLink(async (url) => {
let isLoggedIn = await AsyncStorage.getItem(LocalStorageKeys.IS_LOGGEDIN);
if (url) {
if ( isLoggedIn !== 'yes' && url === 'https://mycustomdomain.co.in') {
navigationToScreen(AUTH, INITIAL_SCREEN);
}
}
});
and clearing that listener on componentWillUnmount:: this.unsubscribeHandleOpenAppDynamicLinks();
In case of iOS only "onLink" function is working and I am
getting url value as "undefined". getInitialLink() function will
returns the URL that the app has been launched from. If the app was
not launched from a URL the return value will be null, but I am
getting "undefined" even when launching an app from url in case of iOS
only. I am getting url inside onLink() in case of iOS when app is
launched. Why this is happening??
Please suggest what I am doing wrong here.
If getInitialLink method does not work, it is either because of improper linking or due to Expo runtime. As an alternative, use Linking.getInitialURL method to get the initial URL. This requires a little bit of native code as well. This is because Linking module does not know how to interpret the shortened URL. So, we call the resolveShortLink method of Firebase SDK to get the embedded deep link. Once we receive the embedded deep link, we can handle it as usual in our app.
The native source code is documented in this article. But for completeness, I will post it here.
#import "FDLResolver.h"
#import <React/RCTLog.h>
#implementation FDLResolver
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(resolveShortLink:(NSString *)shortLink :(RCTPromiseResolveBlock)resolve
:(RCTPromiseRejectBlock)reject)
{
id completion = ^(NSURL *_Nullable dynamicLink, NSError *_Nullable error) {
if (!error && dynamicLink) {
resolve(dynamicLink.absoluteString);
} else {
reject(#"Error", #"Error in getting dynamic link", error);
}
};
NSURL *shortLinkURL = [NSURL URLWithString:shortLink];
[[FIRDynamicLinks dynamicLinks] resolveShortLink:shortLinkURL completion:completion];
}
#end
And Linking module code is below.
Linking.getInitialURL().then(url => {
if (url && url.includes('page.link')) {
const shortLink = url.replace('exps', 'https')
NativeModules.FDLResolver.resolveShortLink(shortLink)
.then(link => {
const linkParts = link.split('?')
const query = qs.parse(linkParts[1])
this.parseRouteUrl(query.deep_link_id)
})
}
})

Xamarin forms geolocator plugin working intermittently

We are trying to use the plugin "Xam.Plugin.Geolocator" in our Xamarin Forms project. The project is currently IOS only.
Our app returns a list of business based on the device users current location. We hit an API to return our JSON formatted list data and the API is functioning correctly.
We would like the list to update whenever the user pulls down, changes tab and when the page initially loads but currently this is only working once or twice in around 100 attempts. I've not found a pattern yet to why it's failing, or indeed when it works.
We set App Properties when the page loads, the tab is selected and the user refreshes like this -
public async void GetLocation()
{
try
{
locator = CrossGeolocator.Current;
if (locator.IsGeolocationAvailable && locator.IsGeolocationEnabled)
{
var position = await locator.GetPositionAsync();
App.Current.Properties["Longitude"] = position.Longitude.ToString();
App.Current.Properties["Latitude"] = position.Latitude.ToString();
}
else
{
await DisplayAlert("Location Error", "Unable to retrieve location at this time", "Cancel");
}
}catch(Exception e)
{
await DisplayAlert("Location Error", "Unable to retrieve location at this time","Cancel");
}
}
We call the above method in the three areas
1) when the page is loaded
public NearbyPage()
{
InitializeComponent();
GetLocation();
SetNearbyBusinesses();
NearbyBusinesses = new List<NearbyBusiness>();
SetViewData();
SetViewVisibility();
}
2) when the tab is clicked
protected override void OnAppearing()
{
base.OnAppearing();
GetLocation();
SetNearbyBusinesses();
NearbyLocationsView.ItemsSource = NearbyBusinesses;
NoLocationsView.ItemsSource = UserMessages;
SetViewVisibility();
}
3) when the user pulls down to refresh
public void RefreshData()
{
if (!CrossConnectivity.Current.IsConnected)
{
NoInternetMessage.IsVisible = true;
return;
}
GetLocation();
NoInternetMessage.IsVisible = false;
SetNearbyBusinesses();
NearbyLocationsView.ItemsSource = NearbyBusinesses;
NoLocationsView.ItemsSource = UserMessages;
SetViewVisibility();
_analyticsService.RecordEvent("Refresh Event: Refresh nearby businesses", AnalyticsEventCategory.UserAction);
}
Can anyone shed some light on what we're doing wrong or have experience with this plugin that can help us resolve this issue?
Thank you
EDIT
By "work", i mean that we'd like it to hit our API with the users current location data and return new results from the API every time the user pulls down to refresh, the page is loaded initially or when they press on a specific tab. Currently it works occasionally, very occasionally.
We can't debug with a phone connected to a macbook, as since we installed the geolocator plugin the app always crashes when connected. The app seems to work ok when deployed to a device, apart from the location stuff. We're currently deploying to test devices via Microsofts Mobile Centre.
Ok, so with the debugger always crashing and being unable to see any stack trace etc we took a few shots in the dark.
We've managed to get this working by adding async to our method signatures down through our code stack. This has resolved the issue and the geo location and refresh is working perfectly.
For example when we changed the above method 3. to refresh the data, it worked perfectly.
public async Task RefreshData()
{
if (!CrossConnectivity.Current.IsConnected)
{
NoInternetMessage.IsVisible = true;
return;
}
GetLocation();
NoInternetMessage.IsVisible = false;
SetNearbyBusinesses();
NearbyLocationsView.ItemsSource = NearbyBusinesses;
NoLocationsView.ItemsSource = UserMessages;
SetViewVisibility();
_analyticsService.RecordEvent("Refresh Event: Refresh nearby businesses", AnalyticsEventCategory.UserAction);
}
We refactored more of that code but adding async was what got it working.
I hope this helps someone else save some time.

Air for IOS Webview - apple app review says every tab and/or button launches mobile Safari.?

I pulling my hair out trying to figure out where I have gone wrong.
I created a very simple app for ios that uses webView to load certain webpages within app. from my knowledge and every ios air webView reference I have found online I have coded everything correctly. Runs beautifully on android.
apple app review says every tab and/or button launches mobile Safari.?
I don't see how this is possible because they even said my button that only has gotoAndPlay(2); apparently that navigates to Safari also. ?
here's the code I used for webView:
QMBTN.addEventListener(MouseEvent.CLICK, QMB);
function QMB(event:MouseEvent):void
{
webView.viewPort = new Rectangle( 0, 135, stage.stageWidth, 600 );
webView.stage = this.stage;
webView.loadURL( "http://mywebpageeurl.com.au" );
whiteBOX.gotoAndStop(2);
}
and this is the code for my internal frame nav.
Menu_BTN2.addEventListener(MouseEvent.CLICK, GoMenuSRC);
function GoMenuSRC(event:MouseEvent):void
{
webView.stage = null;
whiteBOX.gotoAndStop(1);
}
Am I missing something or ????
The only other thing I could think could be the culprit might be my error handler to handle errors when I click tel: or mailto: links on my webpages.
The code for the tel: / mailto: error handling.
// Error handle
var openLinksInDefaultBrowser = false;
//Check tel: func
function cdCTfunc():void
{
var NEWtelLNK = webView.location.substr(0,4);
if (NEWtelLNK=='tel:')
{
openLinksInDefaultBrowser = true;
}else{openLinksInDefaultBrowser = false;}
}
webView.addEventListener(LocationChangeEvent.LOCATION_CHANGING, function (ev:LocationChangeEvent):void
{
cdCTfunc();
if(openLinksInDefaultBrowser == false)
{
ev.preventDefault();
webView.loadURL(ev.location); //'ev.url' changed to 'ev.location'started with prerelease build - [07/20/10]
}
if (openLinksInDefaultBrowser == true)
{
trace('page loaded in default browser');
var phStr:String=ev.location;
var callPH:URLRequest= new URLRequest(phStr);
navigateToURL(callPH);
}
});
webView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, function (ev:LocationChangeEvent):void
{
if (webView.location.indexOf('authentication_complete') != -1)
{
trace('auth token is: ' + webView.location.substr(webView.location.indexOf('token=') + 6));
}
trace('the new location is: ' + webView.location);
trace(webView.location.substr(0,4));
});
webView.addEventListener('complete', function(ev:Event):void
{
trace('complete event');
});
webView.addEventListener('error', function(ev:ErrorEvent):void
{
var phStr:String=webView.location;
var callPH:URLRequest= new URLRequest(phStr);
navigateToURL(callPH);
webView.isHistoryBackEnabled;
webView.historyBack();
trace('SOME Bloody Error Loading The Page: ' + ev.text);
trace(openLinksInDefaultBrowser);
});
but I still don't see how this could cause a gotoAndStop(); to launch safari.
I am absolutely stumped.
Please Help?
Thank you in advance.
Before submitting your app for review you should run it through Testflight.
This allows you to test an 'AppStore' build of your binary so you will see exactly what the reviewer will see.
http://www.yeahbutisitflash.com/?p=7608

Strange behaviour of Geolocator.GetGeopositionAsync() after first launch

I'm writing Windows Phone 8 app that needs to get location of device (do not track changes, just get location). I added next code to the method OnNavigatedTo() of my start page but after launching app, the progress indicator does not hide even after 10 seconds timeout. But if I navigate to another page and then go back, everything works fine. This happens on the emulator, I don't have a real device. What am I doing wrong?
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
if(_geoPosition == null)
{
try
{
var geolocator = new Geolocator();
geolocator.DesiredAccuracyInMeters = 50;
_progressIndicator = new ProgressIndicator
{
IsIndeterminate = true,
Text = "Getting current location, please wait...",
IsVisible = true
};
SystemTray.SetIsVisible(this, true);
SystemTray.SetProgressIndicator(this, _progressIndicator);
_geoPosition = await geolocator.GetGeopositionAsync(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10));
_progressIndicator.IsVisible = false;
SystemTray.SetIsVisible(this, false);
}
catch (UnauthorizedAccessException)
{
MessageBox.Show("Location is disabled in phone settings");
}
}
}
Thanks!
UPD: just tried to add this code to empty project and it works fine. Tried to comment out some parts of OnNavigatedTo that I did not include to the snippet and found out that the reason somewhere in initialization of data source for this page. I'm sorry for false alarm.
Your code works fine for me, try the classic restart VS and the projecy!
The code should work, tested it with an emulator and a device (nokia 820).
Best of luck

Resources