How to monitor http requests from WKWebView? - ios

So, it is pretty clear question I think. This question is about monitoring ajax requests. I tried it but didn't work as I wanted.
How can I call a function in my native iOS Obj-C application every time a request is sent, received and the browsing link is changed in WKWebView?

Ok, I found a way.
You can create a new class (let's call it MyURLProtocol) which has NSURLProtocol as subclass. Then add this function to MyURLProtocol:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
NSLog(#"URL = %#", request.URL.absoluteString);
return NO;
}
This function will be called each time your webview makes a request. And then you need to register this protocol with the loading system. In your Appdelegate.m file include your class and add/replace didFinishLaunchingWithOptions function with this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[MyURLProtocol class]];
return YES;
}
All set. Now you can edit canInitWithRequest function and do what you want with the request.

Related

Deep Link is not handled when app is closed React Native iOS app

I'm developing a React Native app for iOS.
This app has to be able to open deeplinks and it works fine when the app is opened in background.
When the app is close, the native iOS code (Objective-C) does not get the URL the app has been opened with.
As I understand how it works, I have to check the NSDictionary of the launchOptions to see if the app has been started through an URL. If the key corresponding to URL initialization exists then I return true to execute the following code
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:application openURL:url options:options];
return YES;
}
This is the function that has to bee executed to get the Initial Url which the app has been opened with. Here is my code of app didFinishWithLaunchOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([launchOptions objectForKey:UIApplicationLaunchOptionsURLKey]) {
return true;
}
}
The core issue of Intercom not handling deep links for me was that react-native-intercom does not pass an initial URL to an application through didFinishLaunchingWithOptions if it's opened from the background.
Instead react-native-intercom calls the openURL method immediately after the application is launched and the react-native part of the application misses this event.
The solution for this was to create a new thread in the openURL method. Lock it to stop the execution and then unlock the tread when React application gives a signal to the native code, that it's ready to handle deep link. The fix is slightly complicated, so I've created a separate repository, describing the problem and the solution.
The core idea can be described in code as:
dispatch_queue_t queue = dispatch_queue_create("<yourAppName>.openUrlQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
while (!DeepLink.canHandleDeepLinks) {
[DeepLink.canHandleDeepLinksLock wait];
}
[DeepLink.canHandleDeepLinksLock unlock];
dispatch_async(dispatch_get_main_queue(), ^{
// This method call will trigger the Linking event with URL to be dispatched to your 'javascript' code
[RCTLinkingManager application:application openURL:url options:options];
});
});
I've created a repository with the solution to this problem. Go, check it out.
While definitely not an answer, I've (terribly) worked around this issue (at least until a canonical solution is available) by changing (or if you don't already have, adding) the - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler { in AppDelegate.m to:
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
NSURL *url = [userActivity webpageURL];
BOOL result = [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
if([userActivity webpageURL]){
#if DEBUG
float seconds = 3.5;
#else
float seconds = 1.5;
#endif
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(seconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSURL *newUrl = [NSURL URLWithString:[NSString stringWithFormat:#"MYAPPSCHEME:/%#", url.path]];
[[UIApplication sharedApplication] openURL:newUrl options:#{} completionHandler:nil];
});
}
return result;
}
It basically checks if there's a URL available that caused the launch, and invokes an open URL call to the app after "things have settled down" (as in debug mode it takes longer to load, I've changed to 3.5 seconds which handles it just well). Change the seconds and of course MYAPPSCHEME accordingly. And YES, there is a single slash (/) after MYAPPSCHEME: instead of double as url.path seems to have a leading slash already. The reason for replacing http[s] with my app scheme is that for some reason it launches Safari instead of handling deep link if I leave http[s]:// (which is not the case when app is already running and an URL is handled). My app handles custom schemes the same way with regular http links so it works well, but make sure you set it up accordingly for it to work.
After making the above changes and recompiling (don't forget that you're at Objective-C part, not JS/React Native) it worked. I'd love to see an actual solution rather than a hacky workaround, but until then, this fixed it for me.

How to swizzle UIApplication in iOS?

I need to Swizzle UIApplication class methods especially "application:handleOpenURL:". I have added a category class of UIApplication. I have exchanged my own method with the original method but, it never triggered. The Swizzle class called very first time of app launch but, the swizzle method never triggered. I have attached the code for your reference.
- (BOOL) xxx_application: (UIApplication *) application handleOpenURL: (NSURL *) url {
NSLog(#"\n\n Swizzle handle open url..");
[self xxx_application:application handleOpenURL:url];
NSLog(#"URL: %#", url);
return YES; }
Can anyone please save my day? I tried of using some private library like "RSSwizzle" but, no help.
You don't need to swizzle this method, and it doesn't exist on UIApplication anyway. It is a method that is part of UIApplication's delegate protocol, meaning that (typically) your "app delegate" class should simply implement the method to have it called.

UIWebView loadRequest not firing in custom function

First, thanks to everyone that takes the time to answer questions. I have gotten so many quick answers to problems over the years from StackOverflow.
I'm new to Object C and iOS programming, but starting with what I think should be a super simple app. It receives a push notification (which works fine) and redirects to a webpage when it has figured out its appid.
The problem is that the while I can get my UIWebView to loadRequest in the viewDidLoad, the same code will not execute in another function.
Here's the code:
AppDelegate.m
// UFAppDelegate.m
#import "UFAppDelegate.h"
#import "UFViewController.h"
#implementation UFAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Add registration for remote notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
// ....
NSString *urlString = #"http://www.google.com";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(#"Navigating to URL: %#", url);
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
}
#end
ViewController.h:
// UFViewController.h
#import <UIKit/UIKit.h>
#interface UFViewController : UIViewController
- (void)handleOpenURL:(NSURL *)url;
#end
ViewController.m:
// UFViewController.m
#import "UFViewController.h"
#interface UFViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *UFHWebView;
- (void)handleOpenURL:(NSURL *)url;
#end
#implementation UFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"UFViewController.viewDidLoad started");
// This block that assigns the url and loads it works perfectly here... but not down below.
NSString *urlString = #"http://search.yahoo.com/";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.viewDidLoad completed");
}
- (void)handleOpenURL:(NSURL *)url
{
NSLog(#"UFViewController.handleOpenURL started");
NSLog(#"url = %#",url);
// The below loadRequest does not load!
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.handleOpenURL completed");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"Error - %#", error);
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return YES;
}
#end
When run as coded here, the yahoo page shows from the loadRequest in the didload, but the handleOpenURL one still does not fire. If I comment out the loadRequest out of viewDidLoad, a blank page shows up and the handleOpenURL still does not fire.
Here's the debug sequence. viewDidLoad fires and completes before receiving the AppID and manually firing handleOpenURL:
2013-12-12 15:28:32.606 UF[5896:60b] UFViewController.viewDidLoad started
2013-12-12 15:28:32.608 UF[5896:60b] UFViewController.viewDidLoad completed
2013-12-12 15:28:32.765 UF[5896:60b] Navigating to URL: http://www.google.com
2013-12-12 15:28:32.769 UF[5896:60b] UFViewController.handleOpenURL started
2013-12-12 15:28:32.773 UF[5896:60b] url = http://www.google.com
2013-12-12 15:28:32.775 UF[5896:60b] UFViewController.handleOpenURL completed
Any help appreciated!
Are you sure you have just one instance of UFViewController here?
-didRegisterForRemoteNotificationsWithDeviceToken initializes a new web view controller in a local variable, but I can't see how it's getting shown to the user - you aren't doing anything with the vc once it's created.
Here's what I think might be happening:
App starts. You have an instance of UFViewController in a storyboard. It loads, its -viewDidLoad is called, the log statements print and the webview loads correctly.
A little later, the -didRegisterForRemoteNotificationsWithDeviceToken: method is called. This instantiates a UFViewController, and calls the -handleOpenUrl method on it. The -handleOpenURL log methods fire. However, this controller has not been loaded into the view hierarchy, so its view is never instantiated, and its -viewDidLoad is never called.
This means that the webview that the -loadRequest: is being called on is nil. In Objective-C, it is valid to send a message to nil- unlike most other languages, no error or exception will be thrown. Instead, absolutely nothing happens, hence no web load.
The UFViewController in -handleOpenUrl is only referred to by a local variable within the method, so when the method completes, the last strong reference to it vanishes and it is released.
To check this is correct:
-NSLog(#"%#",[self description]); in your vc methods - this will log the address of the objects and tell you whether they are the same.
-NSLog the web controller's view, to check whether it's nil in handleOpenURL
To solve your problem, I suggest using an NSNotification posted by the AppDelegate and listened for by the UFViewController that is fully instantiated. So in the vc you'd do:
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(handleRegistration:) name:#"HandleRegistrationNotification" object:nil];
and then implement the handleRegistration method:
-(void)handleRegistration:(NSNotification*)notification
{
//your handleOpenUrl code
}
and also
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"HandleRegistrationNotification" object:nil];
}
(this bit is important because otherwise you may get a crash when the vc is dealloc'd)
Then, in the -didRegisterForRemoteNotifications: method:
[[NSNotificationCenter defaultCenter]postNotificationName:#"HandleRegistrationNotification" object:nil];
If you need to pass any information through the notification (eg urls) you can use the object parameter - this is accessible in the method called on the vc via the object property of the NSNotification parameter.
Full docs on notifications in Objective-C can be found here.
Some minor points - it's generally a bad idea to call your view controllers things like theView rather than theViewController since other people (or you in 6 months time) will end up thinking they're UIView s. Also the webview property should not start with a capital letter- that's generally only used for class names.
This should be called, "How to call a storyboard viewcontroller method from the appdelegate". And the answer is:
in AppDelegate.m:
#import "UFViewController.h"
...
UFViewController *rootViewController = (UFViewController*)self.window.rootViewController;
[rootViewController handleOpenURL:url];
In my code snippets above, this means replacing these lines
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
with the ones above. The difference is my original code instantiates a new UFViewController object into a local variable, while the correct code just gets a handle to the view controller already created in the storyboard and calls its method.

UIWebView load fails with custom URL scheme

I have a UIWebView that is handling an OAuth authentication flow in my app. The callback URL that the OAuth service uses points to a custom URL scheme I've set up in my app, let's call it mycustomurlscheme.
So once the user authenticates successfully, the OAuth service redirects them to the URL mycustomurlscheme://oauth/success?oauth_token=xxxxxxxxxxxxxxxxxxxx&oauth_verifier=xxxxxxxxxxxxxxxxxxxx. When the UIWebView requests this URL, the app behaves correctly; namely, the application:openURL:sourceApplication:annotation: method in my app delegate gets called, and I dismiss the UIWebView and continue on with the app.
However, I am logging load failures in the UIWebView (using webView:didFailLoadWithError:), and when the above process happens, I see the following in the console:
Error Domain=WebKitErrorDomain Code=102 "Frame load interrupted" UserInfo=0xa1a4810 {NSErrorFailingURLKey=mycustomurlscheme://oauth/success?oauth_token= xxxxxxxxxxxxxxxxxxxx&oauth_verifier=xxxxxxxxxxxxxxxxxxxx, NSErrorFailingURLStringKey=mycustomurlscheme://oauth/success?oauth_token= xxxxxxxxxxxxxxxxxxxx&oauth_verifier=xxxxxxxxxxxxxxxxxxxx, NSLocalizedDescription=Frame load interrupted}
I tried implementing the following UIWebViewDelegate method, thinking that intercepting the request would avoid the error, but the error still happens:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:#"mycustomurlscheme"]) {
[[UIApplication sharedApplication] openURL:request.URL];
return NO;
} else {
return YES;
}
}
This code then causes the following code in my app delegate to run:
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
NSNotification *notification = [NSNotification notificationWithName:kDSApplicationLaunchedWithURLNotification object:nil userInfo:#{kApplicationLaunchOptionsURLKey: url}];
[[NSNotificationCenter defaultCenter] postNotification:notification];
return YES;
}
This notification is picked up by the following block that continues the loading & launching of the app, post-authorization:
self.applicationLaunchNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kDSApplicationLaunchedWithURLNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
NSURL *url = [notification.userInfo valueForKey:kApplicationLaunchOptionsURLKey];
currentRequestToken.verifier = [DSAPIParametersFromQueryString([url query]) valueForKey:#"oauth_verifier"];
[[DSAPIManager sharedManager] acquireAccessTokenWithRequestToken:currentRequestToken success:^(DSAPIOAuth1Token * accessToken) {
[self finishLogin];
} failure:^(NSError *error) {
[self removeAsObserver];
[self showAlert:NSLocalizedString(#"Could not authorize the app. Please try again later.", nil)];
}];
}];
The finishLogin and removeAsObserver methods look like this:
- (void)finishLogin
{
[self removeAsObserver];
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self performSegueWithIdentifier:#"PostLoginSegue" sender:self];
}];
}
- (void)removeAsObserver
{
if (self.applicationLaunchNotificationObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:self.applicationLaunchNotificationObserver];
self.applicationLaunchNotificationObserver = nil;
}
}
Is it safe to ignore this error message? Anyone know why it's happening? My hunch is that the UIWebView thinks the URL has an invalid scheme, but the phone knows it's ok. If that's the case is there any way to tell the UIWebView (or the WebKit engine) that this scheme is valid?
The error here is actually the Frame load interrupted nothing to do with the url scheme. Using custom url scheme is perfectly possible and doesn't create any errors. I have them in many of my apps and have never had a problem with them.
I believe your issue is because of the [webView stoploading]; technically it will stop loading when you return NO so the [webView stopLoading]; is just causing an interruption in the process. Remove this line and let it continue to return NO and see what happens I am 99.9% sure this is the reason.
So your code would look like
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:#"mycustomurlscheme"]) {
// Dismiss the view controller, continue loading the app, etc.
return NO;
}
return YES; // No need for the else statement if this is the only thing to happen if it fails the if statement.
}
UPDATE
Based don comments removing [webView stopLoading]; didn't fix your issue. If this is the case and you make it into that if statement I suspect that something else is going on. Have you shared all your code? With this // Dismiss the view controller, continue loading the app, etc. in your code I suspect that you haven't shared everything in which case we can't help.
Check if there is anything for "UserAgent" in"standardUserDefaults". There are chances that UserAgent is getting appended in request URL. Or check for any "UserAgent" sent in url request.

runtime added application:openURL not fires

For some reason I develop static library for facebook connection.
For proper process authorizated URL appDelegate should have
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
method.
So when I code it in appDelegate.mm file all works well, method invokes and facebook session become authorized.
But I have to add this method to delegate in runtime, so I use follow code:
{
NSString* methodDescription;
methodDescription = #"B#:####";
UIApplication* app = [UIApplication sharedApplication];
bool res = class_addMethod([app.delegate class], #selector(application:openURL:sourceApplication:annotation:), (IMP)applicationIMP, [methodDescription UTF8String]);
NSLog(#"Result of add method is %d", res);
}
//here is implementation of new method:
bool applicationIMP(id self, SEL _cmd, UIApplication *application, NSURL *url, NSString *sourceApplication, id annotation)
{
NSLog(#"Log from applicationIMP injected method");
return [[LibSocial sharedInstance] FacebookHandleOpenURL:url];
}
this code successfully adds method (I see this method when calls class_copyMethodList):
2013-02-04 23:02:00.704 LibSocialTest[38167:19a03] Mathod[0] is application:openURL:sourceApplication:annotation:
But Facebook SDK doesn't fire this method after authentication, and I got FBSessionState FBSessionStateClosedLoginFailed.
Why this method didn't fired?
Update:
Even if I replace normally implemented method with custom implementation at runtime, all works well and Facebook SDK fires new method implementation. But if I didn't code normally this method, but add it at runtime, it isn't fires.
Update2
If I add method to appDelegate class before instantiating UIApplication (in main.m file), then injected method works (even there are no default implementation of method in appDelegate.mm file), but if I inject method after UIApplication was created (so instance of appDelegate class was created too), then injection of method doesn't affect on already instantiated instances of class.

Resources