Retrieve json from a website after login on webview in swift - ios

I am working on an app which involves pulling course information from student's portal. On the user's side:
Student is brought to a webview showing the login page from university's website. (https://acorn.utoronto.ca/)
Student puts in the credentials (Utorid and password), clicks login, webview is closed, and student is brought back to the app and will see his/her courses nicely imported and arranged.
Meanwhile, the app should:
Go into user's personal portal page upon successful login and take the user back to app while process the following in background
Click on "Enrol & Manage" in the left sidebar (not sure if it is necessary, but when I view the source on my browser upon login, sometimes XHRs folder doesn't show unless click on that link)
Go to XHRs folder and find the json "enrolledCourses" (https://acorn.utoronto.ca/sws/rest/dashboard/courseRegistration/enrolledCourses)
Retrieve and parse the user's course info and make them available for the app
Source view when student just logged in
Source view when student click on "Enrol & Manage" in the left sidebar, as you can see now "XHRs" is displayed
The file "enrolledCourses" in "XHRs" which contains the course information
Here's my code for the ViewController so far:
import UIKit
import HTMLKit
import WebKit
class UtoridViewController: UIViewController, WKNavigationDelegate {
private let webView: WKWebView = {
let webView_result = WKWebView(frame: .zero)
return webView_result
}()
override func viewDidLoad() {
print("Acorn loading")
super.viewDidLoad()
view.addSubview(webView)
webView.frame = view.bounds
webView.navigationDelegate = self
// Covert URL_string into URL
guard let url = URL(string: Constants.ACORN_URL) else {
return
}
webView.load(URLRequest(url: url))
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
}
}
So far, I can get the user to the website, but I have no idea how to proceed...
Any help or guidance will be appreciated! Thanks in advance!!!

its impossible, data on another process...
why don't you want to send an http request to the server and get the required data? if I'm not mistaken, you can take cookies from the webView and use them already for the request
there is of course an option through getting html code or executing arbitrary JS code, but this is a very unstable solution

Related

While using webview in Xcode, application does to return to the main login destination page

While using web view to convert my webpage to an app using Xcode the login button does not return to the designated page after login it kind of stays stuck in the login page(The behaviours works okay in a browser though)
Similar behaviour has been observed whoever there is a return to a different page
This is for making my website accessible as an iOS app using web view
I tried using this code snippet:
var backNavigation: WKNavigation?
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if backNavigation?.isEqual(navigation) ?? false {
webView.reload()
backNavigation = nil}
The idea is to allow the application get redirected to the correct page as in the website after login or say cancellation. (The functionality works ok on the website)
I think the solution might be to Allow Arbitrary Loads to yes under app transport security settings. kindly check the link below:
Transport security has blocked a cleartext HTTP
try adding this code
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
let webview = WKWebView(frame: .zero, configuration: configuration)

Closes SFSafariViewController on a certain url

I am trying to close SFSafariViewController when I reach a certain page.
But I am unable to do it until the "Done" button is pressed explicitly
by the user.
What I want is to get the URL as soon as it reached a certain page and then dismiss the view controller.
Then I can pick the rest using this
func safariViewControllerDidFinish(_ controller: SFSafariViewController){
// do work here
}
Note: This question has been asked before but I was unable to find any
satisfactory answers
If you register a URI scheme for your app, you could have have a link on a page which takes you back to your app. At that point you'll be in
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
of your AppDelegate and you can do whatever you wish.
“Because since Safari View Controller has access to a user’s
credentials, synced across all of their devices with iCloud Keychain,
logging in is going to be a breeze.[…] It takes two steps. The first
is where you would’ve used your own in-app browser, just present an
instance of SFSafariViewController.
And once the user is finished logging in and the third-party web
service redirects back to your app with the custom URL scheme that you
fed it, you can accept that in your AppDelegate‘s handleOpenURL
method.
From there you can inspect the response and dismiss the instance of
SFSafariViewController because you know that the authentication is
done.
That’s it. Two steps.”
Source: http://asciiwwdc.com/2015/sessions/504#t=1558.026
Please note the handleOpenURL method is deprecated in favor of application:openURL:options:.
Maybe It's late But I think It will help anyone search for the same question
Listening to the SFSafariViewControllerDelegate delegate you can use this method inside
func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo URL: URL) {}
inside it you can make if condition on your certain page you need to take action for ... as example if I want to close it when reaching certain page I'will fo this
func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo URL: URL) {
if URL.absoluteString == "WHATEVER YOUR LINK IS"{
controller.dismiss(animated: true, completion: nil)
}
}
You cannot do that with a SFSafariViewController. Use a WKWebView instead and implement the WKNavigationDelegate method like optional func webView(_ webView: WKWebView,
didFinish navigation: WKNavigation!) and then read the current URL. Define your action based on that.
You can use this to close the view controller that houses your web view. You will unfortunately have to build your own navigation buttons (the ones the SFSafariViewController has), if you want to allow the user access to those. You don't have to provide them.

Instagram don't redirect to localhost in Swift but does in navigator

A week ago I had my Instagram login system working really fine. I don't know what happened (recent limitation in iOS or change something with Instagram API...) but I'm not able to login.
In my Instagram application, I have two URIs saved: http://localhost and http://localhost/
I've got my function to make the login. It's something like this:
func instagramLogIn()->Void{
webViewOutlet.loadRequest(URLRequest(url: URL(string: "https://api.instagram.com/oauth/authorize/?client_id=MY_CLIENT_ID&redirect_uri=http://localhost&response_type=token")!))
}
In run this method in viewDidAppear where Instagram login appears:
override func viewDidAppear(_ animated: Bool) {
instagramLogIn()
}
Of course webView outlet's delegate is set in self so I can listen to every change of the webView. So I do this now:
func webViewDidFinishLoad(_ webView: UIWebView){
let result = webView.request!.mainDocumentURL
print(result)
let result2:URL = URL(string: "/", relativeTo: result)!.absoluteURL
if result2.absoluteString == "http://localhost/"{
This is supposed to be run every time a request ends loading so the first method should redirect to http://localhost/#authToken=MY_AUTH_TOKEN. So in webViewDidFinishLoad() I check if the main URL is localhost and if so I keep doing things. I print "result" and "result2" and what I get is the first url to load (https://api.instagram.com/oauth/authorize/?client_id=MY_CLIENT_ID&redirect_uri=http://localhost&response_type=token) and "result2" prints "http://www.instagram.com".
It's not redirecting to http://localhost. A days ago it did.
What happen if I open the first url in Safari (in my Mac, not iOS)? It redirects perfectly and I get my auth_token.
So the question is: why is not redirecting in iOS? In Mac I've already an open session.
I don't know what's wrong with this. A couple or days it worked fine.

Intercept request with WKWebView

Now i'm using UIWebView and with canInitWithRequest: of NSURLProtocol i can intercept all requests and do with it what I want.
In the new WKWebView this method there isn't, and i not found something similar.
Has someone resolved this problem?
I see that after 5 years this question still generates curiosity, so I describe how I solved it and about some main problems I faced up.
As many who answered here, I have implemented WKURLSchemeHandler and used new schemes.
First of all the URL that wkwebview launches must not be HTTP (or HTTPS) but one of yours new schemes.
Example
mynewscheme://your-server-application.com
In you WKWebViewConfiguration conf, I set the handler:
[conf setURLSchemeHandler:[CustomSchemeHandler new] forURLScheme:#"mynewscheme"];
[conf setURLSchemeHandler:[CustomSchemeHandler new] forURLScheme:#"mynewschemesecure"];
In CustomSchemeHandler I have implemented webView:startURLSchemeTask: and webView:stopURLSchemeTask:.
In my case I check if the request is for a file that I just saved locally, otherwise I change actual protocol ("mynewscheme or "mynewschemesecure") with http (or https) and I make request by myself.
At this point I solved the "interception problem".
In this new way we have the webview "location" (location.href via javascript) with my new scheme and with it new problems started.
First problem is that my applications work mainly with javascript,
and document.cookie has stopped working. I'm using Cordova
framework, so I've develeped a plugin to set and get cookie to
replace document.cookie (I had to do this, because, obviously, I
have also http header set-cookie).
Second problem is that I've got a lot of "cross-origin" problems, then
I changed all my urls in relative url (or with new schemes)
Third problem is that browser automatically handle server port 80
and 443, omitting them, but has now stopped (maybe because of "not
http location"). In my server code I had to handle this.
Writing down these few rows I admit that it seems to was an easy problem to solve, but I ensure that find out a workaround, how to solve it and integrate with the infinite amount of code has been hard. Every step towards the solution corresponded to a new problem.
You can intercept requests on WKWebView since iOS 8.0 by implementing the decidePolicyFor: navigationAction: method for the WKNavigationDelegate
func webView(_ webView: WKWebView, decidePolicyFor
navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
//link to intercept www.example.com
// navigation types: linkActivated, formSubmitted,
// backForward, reload, formResubmitted, other
if navigationAction.navigationType == .linkActivated {
if navigationAction.request.url!.absoluteString == "http://www.example.com" {
//do stuff
//this tells the webview to cancel the request
decisionHandler(.cancel)
return
}
}
//this tells the webview to allow the request
decisionHandler(.allow)
}
there are many ways to implement intercepter request.
setup a local proxy, use WKNavigationDelegate
-[ViewController webView:decidePolicyForNavigationAction:decisionHandler:]
load new request and forward to local server, and at the local server side you can do something(eg. cache).
private api. use WKBrowsingContextController and custom URLProtocol
Class cls = NSClassFromString(#"WKBrowsingContextController");
SEL sel = NSSelectorFromString(#"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
// 把 http 和 https 请求交给 NSURLProtocol 处理
[(id)cls performSelector:sel withObject:#"http"];
[(id)cls performSelector:sel withObject:#"https"];
}
use KVO to get system http/https handler.(ios 11, *)
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
URLSchemeHandler *schemeHandler = [[URLSchemeHandler alloc] init];
[configuration setURLSchemeHandler:schemeHandler forURLScheme:#"test"];
NSMutableDictionary *handlers = [configuration valueForKey:#"_urlSchemeHandlers"];
handlers[#"http"] = schemeHandler;
handlers[#"https"] = schemeHandler;
all the three ways you probably need handle CORS & post bodies to be stripped,you overwrite change browser`s option request(
Preflighted_requests),you can custom http header to replace http body for post bodies to be stripped.
in iOS 11 WKWebView has come up with Custom Scheme Handler called WKURLSchemeHandler, which you can use to intercept the custom events.
for more info check out this project.
https://github.com/BKRApps/KRWebView
I know I am late but I am able to solve this problem. I can intercept each and every request even your http/https call using below trick. I can also trace the call made from html to server calls. I can also use this to render html with offline content.
Download the html of the website that we want to render in offline or online to intercept the request.
Either place the html in document directory of the user or place it inside the archive. But we should know the path of the html file.
Place all your js, cs, woff, font of our website at the same level as our base html. We need to given permission while loading the web view.
Then we have to register our own custom handler scheme with WKWebView. When wkwebview see the pattern "myhandler-webview" then it will give you control and you will get the callback to 'func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask)' delegate implementation. You can play around with url in this delegate like mentioned in point 6.
let configuration = WKWebViewConfiguration();
configuration.setURLSchemeHandler(self, forURLScheme: "myhandler-webview");
webView = WKWebView(frame: view.bounds, configuration: configuration);
Convert file scheme to the custom scheme (myhandler-webview) then load it with WKWebView
let htmlPath = Bundle.main.path(forResource: "index", ofType: "html")
var htmlURL = URL(fileURLWithPath: htmlPath!, isDirectory: false)
htmlURL = self.changeURLScheme(newScheme: "myhandler-webview", forURL: htmlURL)
self.webView.load(URLRequest(url: htmlURL))
Implement below methods of WKURLSchemeHandler protocol and handle didReceiveResponse, didReceiveData, didFinish delegate methods of WKURLSchemeTask.
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
print("Function: \(#function), line: \(#line)")
print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")
// You can find the url pattern by using urlSchemeTask.request.url. and create NSData from your local resource and send the data using 3 delegate method like done below.
// You can also call server api from this native code and return the data to the task.
// You can also cache the data coming from server and use it during offline access of this html.
// When you are returning html the the mime type should be 'text/html'. When you are trying to return Json data then we should change the mime type to 'application/json'.
// For returning json data you need to return NSHTTPURLResponse which has base classs of NSURLResponse with status code 200.
// Handle WKURLSchemeTask delegate methods
let url = changeURLScheme(newScheme: "file", forURL: urlSchemeTask.request.url!)
do {
let data = try Data(contentsOf: url)
urlSchemeTask.didReceive(URLResponse(url: urlSchemeTask.request.url!, mimeType: "text/html", expectedContentLength: data.count, textEncodingName: nil))
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
} catch {
print("Unexpected error when get data from URL: \(url)")
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
print("Function: \(#function), line: \(#line)")
print("==> \(urlSchemeTask.request.url?.absoluteString ?? "")\n")
}
Let me know if this explanation is not enough.
Objective c example mentioned below
intercepting request with wkwebview
One can use WKURLSchemeHandler to intercept each and every request to be loaded in WKWebView,
Only disadvantage is that you cannot register http or https scheme for interception,
Solution over that is,
Replace your http/https scheme of url with custom scheme url like xyz://
for e.g. https://google.com can be loaded like xyz://google.com
Now you will get a callback in WKURLSchemeHandler there you again replace it back to https and load data programmatically and call urlSchemeTask.didReceive(response)
This way each and every https request will come to your handler.
I am blindly taking guesses since I only have my windows computer with me. By reading the Apple Developer documentation here is information I gathered that might lead to some ideas on how to solve the question.
Based on WKWebView,
Set the delegate property to an object conforming to the WKUIDelegate protocol to track the loading of web content.
Also, I see we can set our navigationDelegate with the,
weak var navigationDelegate: WKNavigationDelegate? { get set }
The methods of the WKNavigationDelegate protocol help you implement custom behaviors that are triggered during a web view's process of accepting, loading, and completing a navigation request.
Then after we create and set our custom WKNavigationDelegate, we would override some methods to intercept something we might be looking for. I found the Responding to Server Actions section of some interest since they receive a WKNavigation as parameter. Moreover, you might want to skim through WKNavigationAction and WKNavigationResponse see if there is perhaps something which might help us achieve our goal.
BTW, I am just giving some ideas on what to try so that we can solve this question, ideas which might be 100% wrong cause I have not tried them myself.

Check if UIWebView login was successful

I am creating an app that would first make people login through a UIWebView to access the rest of the content of the app. More specifically they would be logging in through a web email server set through the company I work with. I want to know if it's first possible to do this, and if so how to confirm whether the user log in was successful?
For example I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://exchsvr02.companyname.com/owa/")
let request = NSURLRequest(URL: url!)
webView.loadRequest(request)
// user entered valid credentials in the login screen
// so push next page
// invalid credentials, ask user to retry, do not push screen
}

Resources