I am building a test app using MailCore2 and GTMAppAuth, and I bumped into this error:
A stable connection to the server could not be established.
I followed various SO posts, like this, this and this, and so I think my code should be correct. My implementation are as follows:
//At AppDelegate
var currentAuthorizationFlow: OIDAuthorizationFlowSession?
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
{
if currentAuthorizationFlow!.resumeAuthorizationFlow(with: url) {
self.currentAuthorizationFlow = nil
return true
}
return false
}
class ViewController: UIViewController {
let kIssuer = "https://accounts.google.com"
let kClientID = "\(MY_CLIENTID).apps.googleusercontent.com"
let kRedirectURI = "com.googleusercontent.apps\(MY_CLIENTID):/oauthredirect"
let kExampleAuthorizerKey = "googleOAuthCodingKey"
override func viewDidLoad() {
super.viewDidLoad()
authenticateGmail()
}
func authenticateGmail() {
let issuer = URL(string: kIssuer)!
let redirectURI = URL(string: kRedirectURI)!
let appDelegate = UIApplication.shared.delegate as! AppDelegate
OIDAuthorizationService.discoverConfiguration(forIssuer: issuer) { (configuration, error) in
//handleError
if let configuration = configuration {
let scopes = [OIDScopeOpenID, OIDScopeProfile, "https://mail.google.com/"]
let request = OIDAuthorizationRequest(configuration: configuration, clientId: self.kClientID, scopes: scopes, redirectURL: redirectURI, responseType: OIDResponseTypeCode, additionalParameters: nil)
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self, callback: { (authState, error) in
//handleError
if let authState = authState {
if let accessToken = authState.lastTokenResponse?.accessToken {
NSLog("Successfully authenticated: %#", accessToken)
self.fetchEmailsFromGmail(accessToken: accessToken)
}
}
})
}
}
}
func fetchEmailsFromGmail(accessToken: String) {
let session = MCOIMAPSession()
session.hostname = "imap.gmail.com"
session.port = 993
session.username = "XXX#\(COMPANY_DOMAIN)"
session.authType = .xoAuth2
session.connectionType = .TLS
session.oAuth2Token = accessToken
let fetchFolderOperation = session.fetchAllFoldersOperation()
fetchFolderOperation?.start({ (error, folders) in
//handleError
if let folders = folders, !folders.isEmpty {
print(folders)
}
})
}
My implementation allows me to authenticate successfully, ie there is an accessToken printed. But when it attempts to fetch folders, it throws:
A stable connection to the server could not be established.
The thing is, it is possible to solve this by doing both the following:
allowing less secure app access via the Googles Accounts page
follow this solution here
However, these methods are not really safe methods in my opinion. I do not recall needing to do any of these when I connect up my email to other email clients, ie Outlook etc, so I do not view the above as real solutions.
Could anyone advice? Is there something wrong with my code, or I really have to resort to doing both steps above?
Related
I need to get a value from an extension before i click on a button that goes to another screen, how can i do that?
This is the IBAction inside viewController. When i click it makes a request to an API then send a value to global variable on the second screen:
#IBAction func enter(_ sender: UIButton) {
if let login = loginTextfield.text, let password = passwordTextfield.text {
loginManager.performLoginRequest(login, password)
resultsViewController.receivedToken = token
navigationController?.pushViewController(resultsViewController, animated: true)
}
}
extension LoginViewController: LoginManagerDelegate {
func didUpdateLogin(with login: LoginModel) -> (Bool, String) {
success = login.success
token = login.token
return (success, token)
}
}
Manager:
import Foundation
protocol LoginManagerDelegate {
func didUpdateLogin(with login: LoginModel) -> (Bool, String)
}
struct LoginManager {
var delegate: LoginManagerDelegate?
func performLoginRequest(_ login: String, _ password: String) {
let url = URL(string: "https://private-anon-1a0df64d9c-ibmfc.apiary-mock.com/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = """
{\n "username": "\(login)",\n "password": "\(password)"\n}
""".data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let response = response {
print(response)
if let data = data, let body = String(data: data, encoding: .utf8) {
print(body)
if let login = self.parseJSON(loginData: data) {
self.delegate?.didUpdateLogin(with: login)
}
}
} else {
print(error ?? "Unknown error")
}
}
task.resume()
}
func parseJSON(loginData: Data) -> LoginModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(LoginData.self, from: loginData)
let success = decodedData.success
let token = decodedData.data.token
let login = LoginModel(success: success, token: token)
return login
} catch {
print(error.localizedDescription)
return nil
}
}
}
My problem is, this extension is just being called after i click the button. This way resultsViewController.receivedToken is not getting the value from token.
So, how can i call didUpdateLogin (to pass it values to success and token) before clicking on the IBAction?
THe reason for this behaviour is the background thread you are using:
(1) You call loginManager.performLoginRequest(login, password) which then starts a background thread to actually work on that request.
(2) In the meantime your code continues to run, executing resultsViewController.receivedToken = token.
(3) Since (1) is not done yet, your token is still nil (or an old token).
One of many possible solutions:
Add a block to the parameters of performLoginRequest in which you call
resultsViewController.receivedToken = token
navigationController?.pushViewController(resultsViewController, animated: true)
This way you make sure that code is only called after(!) the login was successful because you wait for it. In the meantime you could show a loading spinner or something similar. Login is a task where a user simply has to wait, there is usually (depending on the app) no way around it.
The code could look something like this in the end:
loginManager.performLoginRequest(login, password) {
resultsViewController.receivedToken = token
navigationController?.pushViewController(resultsViewController, animated: true)
}
whereas your LoginManager would have a method like
func performLoginRequest(_ login: String,
_ password: String,
completion: #escaping () -> Void)
which is then used later in your Dispatch:
DispatchQueue.main.async {
let loginVC = LoginViewController()
loginVC.didUpdateLogin(login: login)
completion()
}
I am using scheme in my app to pass data from a second app to my app.
I am already able to open my app using it's scheme from second app.
But now I will like to parse the URL data that was sent from the second app. I found a tutorial online about implementing a method as seen below in my viewController class but this method is never triggered. Do i need to place it somewhere specific ? right now it's just in the viewController class.
I just started coding in Swift this week to create a POC, I have more of an Android background.
func application(app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:] ) -> Bool {
// Determine who sent the URL.
let sendingAppID = options[.sourceApplication]
print("source application = \(sendingAppID ?? "Unknown")")
// Process the URL.
guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
// let ro_response = components.path,
let params = components.queryItems else {
print("Invalid")
return false
}
if let serial = params.first(where: { $0.name == "serial" })?.value {
self.SERIAL = serial as String
} else {
return false;
}
if let otp = params.first(where: { $0.name == "otp" })?.value {
self.OTP = otp as String
} else {
return false;
}
return true
}
This is a method from UIApplicationDelegate so you have to implement it in your AppDelegate.
First I have to say:
Sandboxing is off
I did give the app full disk access for Mojave
I exported it and chose to sign it for manual distribution (without App Store)
Problem is, I try to create a file in /Library/Application support via FileManager.default.createFile what works in my home folders for example /Users/username/Library, so it shouldn't be a programming problem.
But I don't seem to have the permission to write to /Library... How can I grant my app those privileges?
All help is appreciated.
Thanks!
you could try to open a NSOpenPanel and explicitly get the permission for that folder. Here is some code to get you started.
public static func allow(folder: String, prompt: String, callback: #escaping (URL?) -> ())
{
let openPanel = NSOpenPanel()
openPanel.directoryURL = URL(string: folder)
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = false
openPanel.prompt = prompt
openPanel.beginSheetModal(for: self.window!) // use the window from your ViewController
{
result in
if result.rawValue == NSFileHandlingPanelOKButton
{
if let url = openPanel.url
{
self.store(url: url) // Store for later use.
}
}
}
}
public static func store(url: URL)
{
guard let path = self.path else { return }
do
{
let data = try url.bookmarkData(options: NSURL.BookmarkCreationOptions.withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
NSKeyedArchiver.archiveRootObject(self.folders, toFile: path)
}
catch
{
Swift.print("Error storing bookmarks")
}
}
I am developing an App for the iPhone using Xwebview which enables me to download a page then interact with the javascript on the downloaded page.
All works, but if the internet connection drops, a default local page is loaded, informing the user there is no internet connection. The page displays a retry button that, when pressed checks, the internet connection: if the connection is made the app tries to connect again to the external page and load the page into the webview.
I cannot get this to work: the code downloads the page (I can see this in my session data) but I can't get that page to load back into the webview.
override func viewDidLoad() {
super.viewDidLoad()
login()
}
func login()
{
// *********** Get stored hashkey **************
let hashcode = getHashcode()
// ********** Check network connection *********
let netConnection = Connection.isConnectedToNetwork()
print("net connection: ", netConnection)
if netConnection == true
{
if hashcode != "00000"
{
print("local key found", hashcode)
// We dont have local key
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
//webview.loadRequest(NSURLRequest(URL: NSURL(string: "about:blank")!))
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let url:NSURL = NSURL(string: serverLocation + onlineLoginApi)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let paramString = "/?username=username&password=password"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.downloadTaskWithRequest(request) {
(
let location, let response, let error) in
guard let _:NSURL = location, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let urlContents = try! NSString(contentsOfURL: location!, encoding: NSUTF8StringEncoding)
guard let _:NSString = urlContents else {
print("error")
return
}
print(urlContents)
}
task.resume()
// you must tell webview to load response
webview.loadRequest(request)
}
else{
print("local key found", hashcode)
// ********* Found local key go to site pass key over ************
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let req = NSMutableURLRequest(URL: NSURL(string:serverLocation + onlineLoginApi + "?hashcode=\(hashcode)")!)
req.HTTPMethod = "POST"
req.HTTPBody = "/?hashcode=\(hashcode)".dataUsingEncoding(NSUTF8StringEncoding)
NSURLSession.sharedSession().dataTaskWithRequest(req)
{ data, response, error in
if error != nil
{
//Your HTTP request failed.
print(error!.localizedDescription)
} else {
//Your HTTP request succeeded
print(String(data: data!, encoding: NSUTF8StringEncoding))
}
}.resume()
webview.loadRequest(req)
}
}
else{
// No connection to internet
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let root = NSBundle.mainBundle().resourceURL!
let url = root.URLByAppendingPathComponent("/www/error-no-connection.html")
webview.loadFileURL(url, allowingReadAccessToURL: root)
print("No internet connection")
}
}
class jsapi: NSObject {
// Reconnect button on interface
func retryConnection()
{
print("Reconnect clicked")
dispatch_async(dispatch_get_main_queue())
{
let netConnections = Connection.isConnectedToNetwork()
if netConnections == true {
let netalert = UIAlertView(title: "Internet on line", message: nil, delegate: nil, cancelButtonTitle: "OK")
netalert.show()
let url = self.serverLocation + self.onlineLoginApi
let hashcode = ViewController().getHashcode()
if(hashcode != "00000") {
let url = url + "?hashcode=\(hashcode)"
print("url: ", url)
}
ViewController().loadPagelive(url)
}
else{
let netalert = UIAlertView(title: "Internet off line", message: nil, delegate: nil, cancelButtonTitle: "OK")
netalert.show()
}
}
print("retryConnect end")
}
}
You try to perform the loadPagelive(url) on a new instance of your ViewController, not on the current one shown on the screen, that's why you don't see any update.
You should create a delegate or a completion block in order to execute code on you ViewController instance loaded on the screen: every time you do ViewController(), a new object is created.
You can try using the delegate pattern, which is simple to achieve. I will try to focus on the important part and create something that can be used with your existing code:
class ViewController: UIViewController {
let jsapi = jsapi() // You can use only 1 instance
override func viewDidLoad() {
super.viewDidLoad()
// Set your ViewController as a delegate, so the jsapi can update it
jsapi.viewController = self
login()
}
func loadPagelive(_ url: URL) {
// Load page, probably you already have it
}
}
class jsapi: NSObject {
weak var viewController: ViewController?
func retryConnection() {
// We check if the delegate is set, otherwise it won't work
guard viewController = viewController else {
print("Error: delegate not available")
}
[... your code ...]
// We call the original (and hopefully unique) instance of ViewController
viewController.loadPagelive(url)
}
}
I'm trying to make a network call to forecast.io for a weather forecast. My code looks like this:
class ViewController: UIViewController {
private let apiKey = "" // 32 letters and numbers
override func viewDidLoad() {
super.viewDidLoad()
let baseURL = NSURL(string: "https://developer.forecast.io/forecast/\(apiKey)/")
let forecastURL = NSURL(string: "37.8267,-122.423", relativeToURL: baseURL)
let weatherData = NSData(contentsOfURL: forecastURL!, options: nil, error: nil)
println(weatherData)
}
}
The println() just returns nil. When I log in to forecast.io I see I haven't yet made a call. What's wrong with my code?
The problem lies in url:
"https://developer.forecast.io/forecast/
should be
"https://api.forecast.io/forecast/