App doesn't seem to callback after authentication - swift

See i don't know if this is an error on soundcloud side or a problem with this lib
https://github.com/OAuthSwift/OAuthSwift
So basically i'm having issues with the authentication side of things with the soundcloud api, so i'm using a webview which is programatically created and attaching this to the same view which works perfectly.
The issue is once i try to authenticate using one of the options from soundcloud either Facebook, Gmail the screen is just blank and it doesn't seem to be authenticating when using the connect with Facebook or gmail the reason why i say it isn't authenticating is because nothing is being logged in the console... and the email screen seems to be stuck as you can see in the image below... There's no callback once the authentication is done and this does work since there is output in the console when i'm testing the data being returned.
func doOAuthSoundCloud(consumerKey: String, consumerSecret: String) {
let oauthswift = OAuth2Swift(
consumerKey: consumerKey,
consumerSecret: consumerSecret,
authorizeUrl: authorizeUrl,
accessTokenUrl: accessToken,
responseType: "code"
)
oauthswift.authorize_url_handler = AuthWebViewController()
let state: String = generateStateWithLength(20) as String
oauthswift.authorizeWithCallbackURL( NSURL(string: "oauthswiftexample://oauth-callback/soundcloud")!, scope: "", state: state, success: {
credential, response, parameters in
self.saveUserData(oauthswift,credential.oauth_token)
}, failure: { error in
print(error.localizedDescription)
})
}
func saveUserData(oauthswift: OAuth2Swift, _ oauthToken: String) {
oauthswift.client.get("https://api.soundcloud.com/me?oauth_token=\(oauthToken)",
success: {
data, response in
//let json = JSON(data: data)
print("Some data")
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
let userData = JSON(dataString!)
print(userData)
print("Some response")
print(response)
print("Extra data")
}, failure: { error in
print(error)
})
}

Related

Accessing Google API data from within 3 async callbacks and a function in SwiftUI

I know this question is asked a lot, but I can't figure out how to apply any answers to my program. Sorry in advance this async stuff makes absolutely zero sense to me.
Basically, I have a button in SwiftUI that, when pressed, calls a function that makes two API calls to Google Sheets using Alamofire and GoogleSignIn.
Button("Search") {
if fullName != "" {
print(SheetsAPI.nameSearch(name: fullName, user: vm.getUser()) ?? "Error")
}
}
This function should return the values of some cells on success or nil on an error. However, it only ever prints out "Error". Here is the function code.
static func nameSearch<S: StringProtocol>(name: S, advisory: S = "", user: GIDGoogleUser?) -> [String]? {
let name = String(name)
let advisory = String(advisory)
let writeRange = "'App Control'!A2:C2"
let readRange = "'App Control'!A4:V4"
// This function can only ever run when user is logged in, ! should be fine?
let user = user!
let parameters: [String: Any] = [
"range": writeRange,
"values": [
[
name,
nil,
advisory
]
]
]
// What I want to be returned
var data: [String]?
// Google Identity said use this wrapper so that the OAuth tokens refresh
user.authentication.do { authentication, error in
guard error == nil else { return }
guard let authentication = authentication else { return }
// Get the access token to attach it to a REST or gRPC request.
let token = authentication.accessToken
let headers: HTTPHeaders = ["Authorization": "Bearer \(token)"]
AF.request("url", method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString { response in
switch response.result {
case .success:
// I assume there is a better way to make two API calls...
AF.request("anotherURL", headers: headers).responseDecodable(of: NameResponseModel.self) { response2 in
switch response2.result {
case .success:
guard let responseData = response2.value else { return }
data = responseData.values[0]
// print(responseData.values[0]) works fine
case .failure:
print(response2.error ?? "Unknown error.")
data = nil
}
}
case .failure:
print(response.error ?? "Unknown error.")
data = nil
}
}
}
// Always returns nil, "Unknown error." never printed
return data
}
The model struct for my second AF request:
struct NameResponseModel: Decodable { let values: [[String]] }
An example API response for the second AF request:
{
"range": "'App Control'!A4:V4",
"majorDimension": "ROWS",
"values": [
[
"Bob Jones",
"A1234",
"Cathy Jones",
"1234 N. Street St. City, State 12345"
]
]
}
I saw stuff about your own callback function as a function parameter (or something along those lines) to handle this, but I was completely lost. I also looked at Swift async/await, but I don't know how that works with callback functions. Xcode had the option to refactor user.authentication.do { authentication, error in to let authentication = try await user.authentication.do(), but it threw a missing parameter error (the closure it previously had).
EDIT: user.authentication.do also returns void--another reason the refactor didn't work (I think).
There is probably a much more elegant way to do all of this so excuse the possibly atrocious way I did it.
Here is the link to Google Identity Wrapper info.
Thanks in advance for your help.
Solved my own problem.
It appears (according to Apple's async/await intro video) that when you have an unsupported callback that you need to run asynchronously, you wrap it in something called a Continuation, which allows you to manually resume the function on the thread, whether throwing or returning.
So using that code allows you to run the Google Identity token refresh with async/await.
private static func auth(_ user: GIDGoogleUser) async throws -> GIDAuthentication? {
typealias AuthContinuation = CheckedContinuation<GIDAuthentication?, Error>
return try await withCheckedThrowingContinuation { (continuation: AuthContinuation) in
user.authentication.do { authentication, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: authentication)
}
}
}
}
static func search(user: GIDGoogleUser) async throws {
// some code
guard let authentication = try await auth(user) else { ... }
// some code
}
I then ran that before using Alamofire's built-in async/await functionality for each request (here's one).
let dataTask = AF.request(...).serializingDecodable(NameResponseModel.self)
let response = try await dataTask.value
return response.values[0]

Cloud Function post request not sending error message properly to IOS app

I have an iOS app receiving json data from a google cloud function.
The data is retrieved and works perfectly when there is no error.
However, when the cloud function returns an error, the function below recognizes error as nil
Client:
func initialize() {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
let clientSecret = json["clientSecret"] as? String,
let publishableKey = json["publishableKey"] as? String else {
///////////
//ERROR is not being recognized
/////////
let message = error?.localizedDescription ?? "Failed to decode response from server."
print("Error loading page: \(message)")
return
}
})
task.resume()
}
Client Output:
Failed to decode response from server.
Server:
firebase.functions().httpsCallable('myFunction')(message)
.then(result => {
// this works perfectly and is recognized by the swift function //
res.send({publishableKey: publishableKey, clientSecret: clientSecret});
})
.catch(err => {
// this is not recognized by the swift function //
console.log(err) // full of data
return res.status(400).send({error: err});
});
Logs(for error case):
Function execution took 240 ms, finished with status code: 400
If your requests fails I think that your error will come into the response parameter and you have to decode it. I think that the error parameter will be different than nil only if the server can't be reached or function does not exist. So basically in the else clause you will have to search into the response parameter for error.

RestAPI response is not same as Postman Response

I am facing a very strange issue while calling RESTful API. I have a login API, I tested it in Postman and it's working perfectly fine. Here is a screenshot.
But once I call it using Alamofire, I get the response as "status :" 1 and "message" : 'Incorrect Credentials' It's the same email and password that I'm using in postman but still I get this response even though my email and password is correct. I have tested it on multiple email and passwords, and for every user it gives me same error.
Here is my login Function..
public func login(email: String, password: String, success: #escaping (UserData) -> (), failure: errorClosure)
{
let parameters: Parameters = [
"email": "\(email)",
"password": "\(password)"
]
session.request(Config.loginURL, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON
{ response in
switch response.result
{
case .success(_):
let json = response.value
print("JSON: \(json ?? "errr")")
MappingHelper.ModelFromResponse(UserData.self, response: response, success: { (result) in
success(result)
}, failure: failure)
case .failure(let error):
failure?(error)
}
}
}
This is how i'm calling it..
helper.login(email: email, password: password) { (UserData) in
print(UserData)
} failure: { (error) in
print(error)
}
Debugging..
The reason I am using session.request instead of AF.request is because when I use AF.request it throws me a certificate error.
The certificate for this server is invalid. You might be connecting to a server that is pretending to be "DOMAIN NAME" which could put your confidential information at risk.
So to bypass this error I created a session with the help of some answer from SO.
private let session: Session = {
let manager = ServerTrustManager(evaluators: ["******.com": DisabledTrustEvaluator()])
let configuration = URLSessionConfiguration.af.default
return Session(configuration: configuration, serverTrustManager: manager)
}()
I think the error is JSONEncoding.default because you don't want to send a JSON body according to your Postman screenshot. You'll want to use a url-form-encoded as defined here

Post to FB page using iOS SDK fails while Graph API Explorer works

still struggling with this d*** FB SDK :-/
Basically, I've got an app that is supposed to post content to a FB Page. To achieve that, I login using the FB sdk. Then I request authorisations as follow
LoginManager().logIn(permissions: ["pages_manage_posts", "pages_read_engagement", "pages_show_list"], viewController: controller) { result in
print("res \(result)")
switch result {
case .success:
// the pageId is in data>id
Defaults[\.facebookSwitchOn] = true
GraphRequest.init(graphPath: "me/accounts").start { (connexion, result, error) in
guard let result = result as? [String:Any],
let dataArray = result["data"] as? Array<Any>,
let data = dataArray.first as? [String:Any],
let pageId = data["id"] as? String,
let access = data["access_token"] as? String else { return }
print("\(pageId)")
Defaults[\.facebookPageId] = pageId
Defaults[\.facebookPageAccessToken] = access
}
completion(true)
case .failed(let error):
completion(false)
MessageManager.show(.basic(.custom(title: "Oups".local(), message: error.localizedDescription, buttonTitle: nil, configuration: MessageDisplayConfiguration.alert)), in: controller)
default: ()
}
}
I save the pageId and TokenID to be able to perform a POST request as follow
GraphRequest
.init(graphPath: "\(pageId)/photos",
// parameters: ["source" : image, "caption" : text, "access_token" : token, "published" : false],
parameters: ["caption" : contentDescription, "url" : "https://www.cdiscount.com/pdt2/9/2/8/1/700x700/889698377928/rw/figurine-funko-pop-deluxe-game-of-thrones-daen.jpg", "access_token" : token],
httpMethod: .post)
.start { (connexion, result, error) in
completion(Result.success(true))
}
However, I get a weird error telling that publish_actions has been deprecated.
I logged the request using Charles, and here it is https://ibb.co/89wPgKx.
Now here is the debug from the GraphAPI explorer :
curl -i -X POST \ "https://graph.facebook.com/v7.0/104226051340555/photos?caption=test%20message%20from%20Graph%20API%20for%20photo&url=https%3A%2F%2Fwww.cdiscount.com%2Fpdt2%2F9%2F2%2F8%2F1%2F700x700%2F889698377928%2Frw%2Ffigurine-funko-pop-deluxe-game-of-thrones-daen.jpg&access_token=<access token sanitized>"
Basically, it is the same request excepting the fact that parameters in the explorer are URL parameters and they are encapsulated in a json.
I can't understand why the graph explorer request succeeds while the SDK request fails.
I'm totally stuck :-/
Thanks for your help.

OAuthSwift Error -10 When Trying to Connect to Twitter

I am attempting to use OAuth 1 with the Twitter API using OAuthSwift. I have completed all of the setup steps while building successfully throughout, but on the last step I am getting an error. When I implement the following code, I get an error saying "The operations cannot be completed. (OAuthSwiftError error -10)". I am thinking it might have something to do with the callback URL but it is very unclear and there isn't much documentation on this error. Or maybe there is something wrong with my key or secret? I have copied them directly from the Twitter dev site.
let oauthswift = OAuth1Swift(
consumerKey: CONSUMER_KEY,
consumerSecret: CONSUMER_SECRET,
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizeUrl: "https://api.twitter.com/oauth/authorize",
accessTokenUrl: "https://api.twitter.com/oauth/access_token"
)
let _ = oauthswift.authorize(
withCallbackURL: URL(string: "oauth-swift://oauth-callback/twitter")!,
success: { credential, response, parameters in
print(credential.oauthToken)
print(credential.oauthTokenSecret)
print(parameters["user_id"])
// Do your request
},
failure: { error in
print(error.localizedDescription)
}
)
I needed to create class attributes. handle and oauthswift are declared as attributes of the class and now the code works. Revised code below:
var oauthswift: OAuth1Swift!
var handle: OAuthSwiftRequestHandle!
var newOAuthToken: String!
override func viewDidLoad() {
super.viewDidLoad()
print("BEFORE OAUTHSWIFT")
oauthswift = OAuth1Swift(
consumerKey: CONSUMER_KEY,
consumerSecret: CONSUMER_SECRET,
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizeUrl: "https://api.twitter.com/oauth/authorize",
accessTokenUrl: "https://api.twitter.com/oauth/access_token"
)
print("AFTER OAUTHSWIFT")
handle = oauthswift.authorize(
withCallbackURL: URL(string: "oauth-swift://oauth-callback/twitter")!,
success: { credential, response, parameters in
print("OAuthToken: \(credential.oauthToken)")
print("OAuthSecret: \(credential.oauthTokenSecret)")
print("User ID: \(parameters["user_id"]!)")
// Do your request
},
failure: { error in
print(error.localizedDescription)
print(self.handle)
}
)
// Do any additional setup after loading the view, typically from a nib.
}
This is not the answer to your question.
But try using the Fabric for install TwitterKit.
For me personally this is an easier way.