Swift (Xcode) REST API Connection using OAuth 1 - ios

Quick background, I am extremely new in this realm. I am aware that this type of question has been asked before and answered successfully. The issue that I am experiencing is caused by the inability to wrap my head around the process of establishing the connection. I have spent hours (into days) searching for the answer and I am still unsuccessful. This has become my "white whale" so to speak.
I am using Xcode 9 with Swift version 4. Many of the answer I come across use Objective-C and I cannot mix and match. So I would like to UNDERSTAND why I am unable to connect and the correct process to connect so I can write the code with the understanding of what I am doing. Lastly, I have signed up (and completed) a few paid Udemy courses to try and learn the process correctly. I have been able to connect to API sources but OAuth 1 is tripping me up. Any constructive help would be incredibly appreciated.
Background:
I am attempting to connect to the Fat Secret database. I would like to connect a search bar to the food.search functionality and also the food.get for another search bar.
Company- FatSecret
URL for API- platform.fatsecret.com/rest/server.api
URL to FatSecret documentation (I have gone through this MANY times)- http:// {space} platform.fatsecret. {space }com/api/Default. {space} aspx?screen=rapiauth
Parameters- Parameters {
oauth_consumer_key - consumer_key (I have a consumer key)
oauth_signature_method - "HMAC-SHA1"
oauth_timestamp - The date and time, expressed in the number of seconds since January 1, 1970 00:00:00 GMT. The timestamp value must be a positive integer and must be equal or greater than the timestamp used in previous requests
oauth_nonce - A randomly generated string for a request that can be combined with the timestamp to produce a unique value
oauth_version - Must be "1.0"
}
As I previously stated, the answer to my question is displayed above. I understand that part but I do not understand how to incorporate it into my code.
Past code-
let url = URL(string: "I am unable to post more than 2 links due to my rep so I put {space} in the above url to circumvent the error. I used the listed url from the parameters")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil { print("success")
} task.resume()
The above code is what I used to establish the connection. I receive "success" in the console so I expanded my parameters.
let url = URL(string: "I am unable to post more than 2 links due to my rep so I put {space} in the above url to circumvent the error. I used the listed url from the parameters")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil { print(error)
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
} catch {
} task.resume()
The above code produces nothing in the console. I believe (sorry for my ignorance) that the reason I am not getting a response is because I am not sending any authorization in the request, nor am I am sending in the correct encoding. I imagine that I can create the parameters by var/let statements and then call on those statements but I am not able to see the way to do that. I could likely also store all of my connection information in a different swift file or class and call on that when I need to access data. This base signature is required with every request. I have to imagine that best practice would be setting it up that way but again, I can't visualization the process. It becomes a trial and error process that results in incredible frustration.
Again, any help would be incredibly appreciated. I apologize for the length of this post. Thank you for taking the time to read this post.

This may be late but I have successfully managed to implement the FatSecret REST API and have created a small Xcode project that shows how I handled OAuth. The only calls that can be made are food.search and food.get. https://github.com/NicholasBellucci/FatSecretSwift

Related

Pattern for retrying URLSession dataTask?

I'm fairly new to iOS/Swift development and I'm working on an app that makes several requests to a REST API. Here's a sample of one of those calls which retrieves "messages":
func getMessages() {
let endpoint = "/api/outgoingMessages"
let parameters: [String: Any] = [
"limit" : 100,
"sortOrder" : "ASC"
]
guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
print("Failed to create URL!")
return
}
do {
var request = try URLRequest(url: url, method: .get)
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
print("Request failed with error: \(error)")
// TODO: retry failed request
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
// process data here
} else {
// TODO: retry failed request
}
}
}
task.resume()
} catch {
print("Failed to construct URL: \(error)")
}
}
Of course, it's possible for this request to fail for a number of different reasons (server is unreachable, request timed out, server returns something other than 200, etc). If my request fails, I'd like to have the ability to retry it, perhaps even with a delay before the next attempt. I didn't see any guidance on this scenario in Apple's documentation but I found a couple of related discussions on SO. Unfortunately, both of those were a few years old and in Objective-C which I've never worked with. Are there any common patterns or implementations for doing something like this in Swift?
This question is airing on the side of opinion-based, and is rather broad, but I bet most are similar, so here goes.
For data updates that trigger UI changes:
(e.g. a table populated with data, or images loading) the general rule of thumb is to notify the user in a non-obstructing way, like so:
And then have a pull-to-refresh control or a refresh button.
For background data updates that don't impact the user's actions or behavior:
You could easily add a retry counter into your request result depending on the code - but I'd be careful with this one and build out some more intelligent logic. For example, given the following status codes, you might want to handle things differently:
5xx: Something is wrong with your server. You may want to delay the retry for 30s or a minute, but if it happens 3 or 4 times, you're going to want to stop hammering your back end.
401: The authenticated user may no longer be authorized to call your API. You're not going to want to retry this at all; instead, you'd probably want to log the user out so the next time they use your app they're prompted to re-authenticate.
Network time-out/lost connection: Retrying is irrelevant until connection is re-established. You could write some logic around your reachability handler to queue background requests for actioning the next time network connectivity is available.
And finally, as we touched on in the comments, you might want to look at notification-driven background app refreshing. This is where instead of polling your server for changes, you can send a notification to tell the app to update itself even when it's not running in the foreground. If you're clever enough, you can have your server repeat notifications to your app until the app has confirmed receipt - this solves for connectivity failures and a myriad of other server response error codes in a consistent way.
I'd categorize three methods for handling retry:
Reachability Retry
Reachability is a fancy way of saying "let me know when network connection has changed". Apple has some snippets for this, but they aren't fun to look at — my recommendation is to use something like Ashley Mill's Reachability replacement.
In addition to Reachability, Apple provides a waitsForConnectivity (iOS 11+) property that you can set on the URLSession configuration. By setting it, you are alerted via the URLSessionDataDelegate when a task is waiting for a network connection. You could use that opportunity to enable an offline mode or display something to the user.
Manual Retry
Let the user decide when to retry the request. I'd say this is most commonly implemented using a "pull to refresh" gesture/UI.
Timed/Auto Retry
Wait for a few second and try again.
Apple's Combine framework provides a convenient way to retry failed network requests. See Processing URL Session Data Task Results with Combine
From Apple Docs: Life Cycle of a URL Session (deprecated)... your app should not retry [a request] immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.

iOS: How to detect if a user is subscribed to an auto-renewable subscription

Hopefully the title is self-explanatory. I'm trying to do something like this:
checkIfUserIsSubscribedToProduct(productID, transactionID: "some-unique-transaction-string", completion: { error, status in
if error == nil {
if status == .Subscribed {
// do something fun
}
}
}
does anything like the hypothetical code I've provided exist? I feel like I'm taking crazy pills
Edit
In similar questions I keep seeing a generic answer of "oh you gotta validate the receipt" but no explanation on how, or even what a receipt is. Could someone provide me with how to "validate the receipt"? I tried this tutorial but didn't seem to work.
Edit - For Bounty
Please address the following situation: A user subscribes to my auto-renewable subscription and gets more digital content because of it - cool, implemented. But how do I check whether that subscription is still valid (i.e. they did not cancel their subscription) each time they open the app? What is the simplest solution to check this? Is there something like the hypothetical code I provided in my question? Please walk me through this and provide any further details on the subject that may be helpful.
I know everyone was very concerned about me and how I was doing on this - fear not, solved my problem. Main problem was that I tried Apple's example code from the documentation, but it wasn't working so I gave up on it. Then I came back to it and implemented it with Alamofire and it works great. Here's the code solution:
Swift 3:
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
let requestContents: [String: Any] = [
"receipt-data": receipt!.base64EncodedString(options: []),
"password": "your iTunes Connect shared secret"
]
let appleServer = receiptURL?.lastPathComponent == "sandboxReceipt" ? "sandbox" : "buy"
let stringURL = "https://\(appleServer).itunes.apple.com/verifyReceipt"
print("Loading user receipt: \(stringURL)...")
Alamofire.request(stringURL, method: .post, parameters: requestContents, encoding: JSONEncoding.default)
.responseJSON { response in
if let value = response.result.value as? NSDictionary {
print(value)
} else {
print("Receiving receipt from App Store failed: \(response.result)")
}
}
As some comments pointed out there's a couple flaws with these answers.
Calling /verifyReceipt from the client isn't secure.
Comparing expiration dates against the device clock can be spoofed by changing the time (always a fun hack to try after cancelling a free trial :) )
There are some other tutorials of how to set up a server to handle the receipt verification, but this is only part of the problem. Making a network request to unpack and validate a receipt on every app launch can lead to issues, so there should be some caching too to keep things running smoothly.
The RevenueCat SDK provides a good out-of-the box solution for this.
A couple reasons why I like this approach:
Validates receipt server side (without requiring me to set up a server)
Checks for an "active" subscription with a server timestamp so can't be spoofed by changing the device clock
Caches the result so it's super fast and works offline
There's some more details in this question: https://stackoverflow.com/a/55404121/3166209
What it works down to is a simple function that you can call as often as needed and will return synchronously in most cases (since it's cached).
subscriptionStatus { (subscribed) in
if subscribed {
// Show that great pro content
}
}
What are you trying to achieve in particular? Do you want to check for a specific Apple ID?
I highly doubt that this is possible through the SDK. Referring to Is it possible to get the user's apple ID through the SDK? you can see that you can't even ask for the ID directly but rather services attached to it.
What would work is caching all transactions on your own server and search its database locally but that would require the app to ask for the user's Apple ID so the app could update the subscription state whenever it launches as it can check for IAP of the ID associated with the device.
However, the user could just type whatever he wanted - and it's unlikely to get this through Apple's app review process.
I am using MKSoreKit https://github.com/MugunthKumar/MKStoreKit for auto-renew subscriptions.but it is in objective c you can check the library code for solution.I am using it in my code and it is working fine.
using below method you can easily check subscription status..
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
It gets the apple id from device and I think that is user specific

Able to POST direct messages using Twitter's REST API, but trying to GET returns a 401 error

I am trying to get direct messages working in my app. I'm able to POST DMs just fine, but when I try to GET them from the endpoint https://api.twitter.com/1.1/direct_messages.json it returns a 401 - Unauthorized. I don't really understand how I can be authorized to send DMs but not get ones sent to me.
Here's how I'm authenticating initially:
if Twitter.sharedInstance().sessionStore.session() == nil {
Twitter.sharedInstance().logInWithCompletion { session, error in
if (session != nil) {
// successfully logged in, call loading functions
} else {
print("error: \(error!.localizedDescription)")
}
}
} else {
// already logged in, call loading functions
}
Every time I make a request using the REST API, it begins with
if let userID = Twitter.sharedInstance().sessionStore.session()?.userID {
let client = TWTRAPIClient(userID: userID)
The client is initialised the same way in both the POST and GET requests for DMs, yet the GET request fails.
As far as permissions go, I've checked that it has read/write/DM access according to Twitter, and successful requests return "x-access-level" = "read-write-directmessages";, so I think it's set properly.
I was concerned at one point that I might not be authenticating properly, since Twitter's documentation goes through the 3 step process for O-Auth and all I'm doing is telling the Twitter singleton to just log in... but I rationalised that away by assuming that those steps are all carried out in the logInWithCompletion function. And besides, if I wasn't authenticated properly I surely wouldn't be able to send DMs, right?
Any ideas on how I can fix this? I'm quite new so it may be something nice and simple! I've looked through some other questions, but they all seem to code the requests in full rather than using built-in methods like these - or have I got it all wrong?
Yeah, it was a stupid problem - I left the parameters blank since they are all marked as 'optional' - as in, a dictionary of ["" : ""]. I just set the paramaters in the request to nil, and now it works.

Request not sent

I'm having a weird problem when i consume my API from my app. Sometimes, for no reason, the request is just not sent, and it fails at the end of the time-out with the following error:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."
I have tried many API such as NSURLConnection delegates, NSURLSession and NSURLConnection.sendSynchronousRequest without success.
Here is a sample project i have made to highlight the issue. ConnectionBugApp
Here are the steps to reproduce:
Run the app in Xcode and stop debug just so the app is on your phone
Open the app, click Test Connection (it succeeds, loading wheel stops spinning right after)
Go to other apps like facebook/twitter/network games (somes that are a bit heavy) and switch to airplane mode a few times
Go back to my app and click Test Connection (loading wheel never stops)
A few details that might help:
If I use my server IP instead of my domain name, it succeeds
Issue only appears when on the LTE/4G network
Any ideas or workaround would be greatly appreciated ! Feel free to ask for more details.
Thanks
EDIT
I've edited the description a lot since i first posted it (hoping to make it cleaner and clearer), i'm sorry if some answers or comment don't really make sense anymore.
I have come across this issue when using an asynchronous request. It seems as though iOS limits the number of open connections to a single domain, such that all subsequent connections fail in the manner you have described.
If connections typically complete quickly, this possibly won't be an issue.
The solution is to limit the number of open connections to the same domain to prevent this from happening.
The answer posted by karlos works because the synchronisity of the connection blocks others from being opened.
Like mentioned in comments, I had DNSSEC (cache poisoning protection) enabled on my hosting service.
Disabling it, fixed the issue, even though that might not be a really nice solution. After a few weeks of searching, that'll be good enough.
I'll give the bounty to someone that can explain it, or who can provide a better solution.
In your code your request take default timeout is 60s, but you can change Request time out in your code as below.
in your NetworkItem class change time out.
init(request:NSMutableURLRequest){
self.request = request
self.request.timeoutInterval = 120
super.init()
}
Try the following code for the connection.This would help you.
let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
if error != nil || urlData!.length == 0
{
println("Error happend timeout======\(error?.code)!")
continue
}
else //else1
{if let httpResponse = response as? NSHTTPURLResponse
{
println("Status Code for successful---\(httpResponse.statusCode)")
// For example 502 is for Bad Gateway
if (httpResponse.statusCode == 502)
{
// Check the response code from your server and break
break
}
else
{
//Your code
}
}
}
You can get the list of HTTPS status code in the following link
StatusCode

POST Queries in Swift for given website

I am trying to make queries to get the fuel type and consumption of a specified car (the user enters both make and model) for an iOS app written in Swift.
The app is targeted for Spain, and I have found a website that allows the user to enter make and model, and it returns the details for that car (http://coches.idae.es/portal/BaseDatos/MarcaModelo.aspx). I have seen using the tool WireShark, that the query is based on POST instead of GET. But I am not quite sure how I can make the requests within the app I am developing, or how to handle the info that is sent to me back from the sender.
Is there any way to make those requests to the given website? If so, I would really appreciate some help on the subject, I am new in iOS development and am looking forward to learning as much as possible.
Thanks :)
Many people prefer to use AFNetworking for making HTTP requests. However you don't need to do that. You said that its a POST request. Setting that up is easy even without AFNetworking using NSMutableURLRequest. I'm assuming you have a link to the API and not just to the aspx page. My Spanish is pretty weak so I can't look up the API reference for you but here's how you can make the request and receive data from the server. You will have to put the correct values and parse the responses:
let request = NSMutableURLRequest(URL: NSURL(string: "/* Paste URL here */")!)
request.HTTPMethod = "POST"
// Do this as many times are required for filling in the headers.
request.addValue("/* The value for the HTTP header */", forHTTPHeaderField: "/*The header field like Accept-Type, etc..*/")
// If you need an HTTP body too then make the JSONObj as a dictionary or array or whatever and then
let data = NSJSONSerialization.dataWithJSONObject(JSONObj, options: [])
request.HTTPBody = data // This needs to be NSData.
// Now make the request.
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, { (data, response, error) -> Void in
if error == nil
{
assert(data != nil)
let JSON = NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [NSObject: AnyObject]
// If you are using swift 2 this needs to be in a do try catch statement.
// TODO: Use JSON for whatever.
}
else
{
print(error!.localizedDescription)
}
}
task?.resume()
Let me know if you have any other questions or if the API doesn't use JSON or is completely different.

Resources