a solution to retrieve information when they're no internet connection - swift

On my app I have a form which the user can complete and send it to us with a SOAP request.
the form sended:
But I have a problem, I they're no internet connection I can't send the data by using my SOAP request.
So firstly I save the request in Core Data, so here it's ok... And when the user is on the app I do a scheduled request to see if they're a request in Core Data to send them.
But what about if the app is not running ? In Android I use background service, but with Apple it's impossible:
Android Services equivalent in iOS Swift
Someone have an idea to retrieve the data ?
Soap function:
func soapCall(fonction:String, soapMessageParamsTab:[SoapMessageParams], completion: #escaping (_ strData: NSString)-> ()/*, completion: #escaping (_ result: Int, _ resultContactWebId: Int)->()*/){
let soapRequest = AEXMLDocument()
let attributes = ["xmlns:SOAP-ENV" : "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:ns1" : namespace, "xmlns:xsd" : "http://www.w3.org/2001/XMLSchema", "xmlns:xsi" : "http://www.w3.org/2001/XMLSchema-instance", "xmlns:SOAP-ENC" : "http://schemas.xmlsoap.org/soap/encoding/", "SOAP-ENV:encodingStyle" : "http://schemas.xmlsoap.org/soap/encoding/"]
let envelope = soapRequest.addChild(name: "SOAP-ENV:Envelope", attributes: attributes)
let body = envelope.addChild(name: "SOAP-ENV:Body")
let sendLead = body.addChild(name: "ns1:"+fonction)
for obj in soapMessageParamsTab {
sendLead.addChild(name: obj.soapName, value: obj.soapValue, attributes: ["xsi:type" : "xsd:"+obj.soapType])
}
//sendLead.addChild(name: "xml", value: messageSoap, attributes: ["xsi:type" : "xsd:string"])
let soapMessage = soapRequest.xmlString
let messageSoapMinify = minify(soapMessage)
// URL Soap
let urlString = soapServiceURL
let url = URL(string: urlString)
var theRequest = URLRequest(url: url!)
// Taille SoapMessage
let msgLength = String(messageSoapMinify.characters.count)
// Soap Login
let username = soapUsername
let password = soapPassword
let loginString = NSString(format: "%#:%#", username, password)
let loginData: Data = loginString.data(using: String.Encoding.utf8.rawValue)!
let base64LoginString = loginData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters)
// Requete Soap
theRequest.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
theRequest.addValue("Keep-Alive", forHTTPHeaderField: "Connection")
theRequest.addValue(msgLength, forHTTPHeaderField: "Content-Length")
theRequest.addValue("urn:ResponseAction", forHTTPHeaderField: "SOAPAction")
theRequest.httpMethod = "POST"
theRequest.httpBody = messageSoapMinify.data(using: String.Encoding.utf8, allowLossyConversion: true)
theRequest.addValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
// print("Request is \(theRequest.allHTTPHeaderFields!)")
let session = Foundation.URLSession.shared
let task = session.dataTask(with: theRequest, completionHandler: {data, response, error -> Void in
if error != nil
{
print(error?.localizedDescription)
}
//print("Response Login: \(response)")
//print("Error Login: \(error)")
let strData = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)!
//print("Body Login: \(strData)")
completion(strData)
})
task.resume()
}

Using NSURLSession with background configuration you should be ok.
Note: If the app is explicitly killed by the user the scheduled request will be cancelled. This trick works just if the app was killed by the system for any reason (low memory, crash etc...). There is nothing you can do if the app was killed by the user. The background fetch might be a solution, but it will be decided by iOS if and when to awake the app for the opportunistic fetch.
This could help you:
https://blog.newrelic.com/2016/01/13/ios9-background-execution/
Edit:
This tutorial could be useful as well: https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started

Related

Swift: Mailgun 401 response code--No valid API key provided

So I have my send email function as seen below:
func email() {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: "https://api.mailgun.net/v3/sandbox################################/messages")! as URL)
request.httpMethod = "POST"
let credentials = "api:key-################################-########-########"
request.setValue("Basic \(credentials.toBase64())", forHTTPHeaderField: "Authorization")
let data = "from: Swift Email <(test#test.com)>&to: [myemail#gmail.com,(myemail#gmail.com)]&subject:Hello&text:Testing_some_Mailgun_awesomness"
request.httpBody = data.data(using: String.Encoding.ascii)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
So I guess either my API key is wrong or my request URL is wrong. To find my request URL, I went to https://app.mailgun.com/app/dashboard and then scrolled down to the Sending Domains section and copied that. To get my API key I went to https://app.mailgun.com/app/account/security/api_keys and just copied the Private API Key. I'm really not sure why I'm getting this invalid code--thank you in advance if you figure it out!
Side note: Not really sure if the data constant is set up right (in terms of missing or having too many parens), so if you could check that too that would be phenomenal.
I have a software called "paw" it helps forming REST API call for Xcode, curl, php, etc.
I don't know if this could help you
class MyRequestController {
func sendRequest(somevar: String, completion: #escaping (Books) -> Void) {
/* Configure session, choose between:
* defaultSessionConfiguration
* ephemeralSessionConfiguration
* backgroundSessionConfigurationWithIdentifier:
And set session-wide properties, such as: HTTPAdditionalHeaders,
HTTPCookieAcceptPolicy, requestCachePolicy or timeoutIntervalForRequest.
*/
let sessionConfig = URLSessionConfiguration.default
/* Create session, and optionally set a URLSessionDelegate. */
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
/* Create the Request:
(POST https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages)
*/
guard var URL = URL(string: "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages") else {return}
var request = URLRequest(url: URL)
request.httpMethod = "POST"
// Headers
request.addValue("Basic YXBpOllPVVJfQVBJX0tFWQ==", forHTTPHeaderField: "Authorization")
request.addValue("multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__", forHTTPHeaderField: "Content-Type")
// Body
let bodyString = "--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"from\"\r\n\r\n'Excited User <mailgun#YOUR_DOMAIN_NAME>'\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"to\"\r\n\r\nYOU#YOUR_DOMAIN_NAME\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"to\"\r\n\r\nbar#example.com\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"subject\"\r\n\r\n'Hello'\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n'Testing some Mailgun awesomeness!'\r\n--__X_PAW_BOUNDARY__--\r\n"
request.httpBody = bodyString.data(using: .utf8, allowLossyConversion: true)
/* Start a new Task */
let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if (error == nil) {
// Success
let statusCode = (response as! HTTPURLResponse).statusCode
print("URL Session Task Succeeded: HTTP \(statusCode)")
}
else {
// Failure
print("URL Session Task Failed: %#", error!.localizedDescription);
}
})
task.resume()
session.finishTasksAndInvalidate()
}
}
**** and you CALL this function a bit like this
MyRequestController().sendRequest(somevar: "something")
take a look at https://www.youtube.com/watch?v=44APgBnapag for more details
this tutorial show how to do REST API calls with Xcode, this example it scan a barcode, send the scanned code via a function that calls the API and return infos...

Swift - proper way to wait for login request to complete before resuming execution

I have the following code which performs a login to a web server, retrieves an API token, and then allows the app to continue. After this code executes, the the calling function uses the login_result to report to the user whether they successfully logged in or need to try again.
Right now, I just have the app sleep for 3 seconds to make sure I have response before moving on. Otherwise, the login results handler starts executing without an answer (because the server hasn't had time to respond).
Obviously, this is a horrible way to do things. What is the correct / swift idiomatic way to handle this?
func remote_login (email: String, password: String) -> Bool {
login_result = false
var jsonResults: NSDictionary = [:]
let account = Account()
let url = NSURL(string: "\(base_api_url())/login?email=\(email)&password=\(password)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
account.setCredentials(jsonResults) // we store an API token returned from a successful login; we don't store the users credentials locally
self.login_result = true
}
catch {
print("\n\n\n Login Error \n\n\n")
}
}) // task
task.resume()
sleep(3) //need time for the response to come back...s/b far more elegant way to do this.
return self.login_result
}
Use a closure as your function argument, like so:
func remote_login(email: String, password: String, completion((Bool) -> Void)) {
Then, call the closure when finished (after JSON parsing) like this:
self.login_result = true
completion(login_result)
So, when you use the function, it will look like this:
remote_login("email", "password", {
success in
//Logged in successfully
}
The closure will be called when the HTTP request finishes.
As best I can see:
func remote_login (email: String, password: String, myFunctionCompletionHandler: ()->()) -> Bool {
login_result = false
var jsonResults: NSDictionary = [:]
let account = Account()
let url = NSURL(string: "\(base_api_url())/login?email=\(email)&password=\(password)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
// we store an API token returned from a successful login; we don't store the users credentials locally
account.setCredentials(jsonResults)
myFunctionCompletionHandler()
// is this needed as it's handled by the handler?
self.login_result = true
}
catch {
myFunctionCompletionHandler()
print("\n\n\n Login Error \n\n\n")
}
}) // task
task.resume()
return self.login_result
}
You might have to refactor things to handle the completionHandler a little better as I've just literally "plonked it in" but that should do the trick :)..
Just handle and pass the "failed and passed" correctly across to it :)

ERROR code 201: missing user password when creating anonymous user: Parse.com REST API

I am trying to sign up an anonymous user using REST API. I found several discussion regarding linking Twitter/Facebook users that share the same problem but none gave this solution.
According to Parse.com REST API Doc, you only need to provide some sort of id to the authData, i.e UDID, but it complains that there is no password. This is the error:
"{"code":201,"error":"missing user password"}
This is my code:
func registerAnonymisUserWithComplitionHandler(deviceUDID:String, onComplition: RESTResponse){
let request = NSMutableURLRequest()
request.HTTPMethod = "POST"
request.addValue("APP ID", forHTTPHeaderField: "X-Parse-Application-Id")
request.addValue("API Key", forHTTPHeaderField: "X-Parse-REST-API-Key")
request.addValue("1", forHTTPHeaderField: "X-Parse-Revocable-Session")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let urlString = "https://api.parse.com"
let baseURL = NSURL(string: urlString)
let url = NSURL(string: "/1/users", relativeToURL: baseURL!)
request.URL = url
let param = ["authData": ["anonymous":["id":deviceUDID]]]
let session = NSURLSession.sharedSession()
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(param, options: .PrettyPrinted)
} catch {
print("error")
}
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
if (error == nil){
do {
let tmpUserDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject]
print(tmpUserDictionary)
let statusResponse = response as! NSHTTPURLResponse
onComplition(jsonDictionary: tmpUserDictionary, response: statusResponse, error: nil)
} catch {
}
} else {
onComplition(jsonDictionary: nil, response: nil, error: error)
}
}
task.resume()
}
Thanks to Muhammad Junaid Butt, I have fixed a small issue in the param and it works.
Check the attached screenshot. I used REST API of PARSE to create an Anonymous user and it is working fine. (I used Postman for testing this).

NSURLSession: It is possible with no authentication NSURLSession ( Swift )

I would like with an API -Key and authenticate user . I have the message " Session URL Task Succeeded : HTTP 200 " get in the Console . Unfortunately, I always get the message of the page " {" message " : " . Authorization has been denied for this request " } " . I spent hours looking for and found a solution . I ask for your help . Here is my code :
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
let URL = NSURL(string: "https://api.testhomepage.com/api/contact")
let request = NSMutableURLRequest(URL: URL!)
// Headers
request.addValue("ARRAffinity=259dfjiehfs315d7249df8805c7895a98c4cbd3327ffdcb82aaa16317f2e6", forHTTPHeaderField: "Cookie")
request.addValue("Basic dWVzQGp1bGl0ZafdgfvcfgeDcyZS1hZWEyLTQzYmVmMjFhNDMxZQ==", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("api.testhomepage.com", forHTTPHeaderField: "Host")
request.addValue("//api.testhomepage.com/api/contact: POST", forHTTPHeaderField: "https")
request.HTTPMethod = "GET"
// JSON Body
/* Start a new Task */
let task = session.dataTaskWithRequest(request) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if (error == nil) {
// Success
let statusCode = (response as! NSHTTPURLResponse).statusCode
print("URL Session Task Succeeded: HTTP \(statusCode)")
let myURLString = "https://api.testhomepage.com/api/contact"
if let myURL = NSURL(string: myURLString) {
var error: NSError?
let myhtml = try! NSString(contentsOfURL: myURL, encoding: NSUTF8StringEncoding)
if let error = error {
print("Error : \(error)")
} else {
print("HTML : \(myhtml)")
}
} else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
}
}
else {
// Failure
print("URL Session Task Failed: %#", error!.localizedDescription);
}
}
task.resume()
}
This is in the console:
URL Session Task Succeeded: HTTP 200
HTML : {"Message":"Authorization has been denied for this request."}
use request.setvalue("", forHTTPHeaderField: "") instead of request.addValue("", forHTTPHeaderField: "")
You cannot populate the Authentication header yourself, period. That header is owned by the URL loading system, and is populated based on credentials in the user's keychain.
You can learn about how to do authentication by reading the authentication chapter of URL Session Programming Guide. Note that the examples are for NSURLConnection, but the rest of the text applies to both.
Alternatively, you can store the credentials in the keychain as an Internet Password item, as described in Keychain Services Programming Guide.

Status code 400 while making POST request in swift

I keep getting this error which I don't know how to resolve!
I'm trying to parse access token from a web API and for that I've set up post request.
Somehow it's throwing an error on 'responseString' line.
import UIKit
import Foundation
class ViewController: UIViewController, NSURLConnectionDelegate {
#IBOutlet var userName: UITextField!
#IBOutlet var password: UITextField!
#IBAction func submit(sender: AnyObject) {
let postString = "username=" + userName.text + "&password=" + password.text + "&grant_type=password";
let data: NSData = postString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = data.base64EncodedStringWithOptions(nil)
let postLength=NSString(format: "%ld", data.length)
let url = NSURL(string: "http://www.myurl.com/Token")
let request = NSMutableURLRequest(URL: url!);
request.HTTPMethod = "POST"
request.HTTPBody = data
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue(base64LoginString, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {data, response, error in
if error != nil {
println("error = \(error)")
return
}
//print out response object
println("******* response = \(response)")
//print out response body // THIS LINE THROWS STATUS CODE 400 ERROR
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("******* response data = \(responseString)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
if let parseJSON = json {
var token = parseJSON["access_token"] as? String
println("token: \(token)")
}
}
task.resume()
}
This is the error
******* response = <NSHTTPURLResponse: 0x7ff1eb4496d0> { URL: https://www.myurl.com/Token } { status code: 400, headers {
"Cache-Control" = "no-cache";
"Content-Length" = 34;
"Content-Type" = "application/json;charset=UTF-8";
Date = "Mon, 10 Aug 2015 20:16:14 GMT";
Expires = "-1";
Pragma = "no-cache";
"X-Powered-By" = "ASP.NET";
} }
Your error seems to be incorrect usage of basic auth format. It should be like this:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
where
QWxhZGRpbjpvcGVuIHNlc2FtZQ==
is username:password in base64
So in your case it is like this :
let postString = userName.text + ":" + password.text ;
let data: NSData = postString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = data.base64EncodedStringWithOptions(nil)
let postLength=NSString(format: "%ld", data.length)
let url = NSURL(string: "http://www.myurl.com/Token")
let request = NSMutableURLRequest(URL: url!);
request.HTTPMethod = "POST"
request.HTTPBody = ""
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("Basic " + base64LoginString, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("0", forHTTPHeaderField: "Content-Length")
btw it would be a lot easier to find your error if you provide your api url
The error code 400 says that the server doesn't understand for request, maybe the request is malformed. This is very likely a client side issue. However, I've written a simple python http server, and used the code above (with only change to the URL entry point), everything works. I strongly suggest that you should check the API documentation on the backend and find out why your backend server doesn't recognize your request. Your client side code seems correct.
Try this on your own:
Create myserver.py, and add the following lines to it:
from flask import Flask, request
app = Flask(__name__)
#app.route('/Token', methods=['POST'])
def hello_world():
print(request.form)
return 'Hello World!'
if __name__ == '__main__':
app.run()
Run this python script from terminal, and change http://www.myurl.com to http://127.0.0.1:5000, you'll see that the request gets through, and the server can correctly parse your username password, etc.
It's always a good idea to try to setup your own local web server to mock the backend during debugging/development according to backend API specification.
try this one
//first make json object.you can use your object for this one.
let JSONObject: [String : AnyObject] = [
"DeviceHash" : UUIDValue,
"DeviceName" : deviceNameValue!,
"SerialKey": serialKeyValue!,
]
if NSJSONSerialization.isValidJSONObject(JSONObject) {
var request: NSMutableURLRequest = NSMutableURLRequest()
let url = "Your Url"
var err: NSError?
request.URL = NSURL(string: url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: NSJSONWritingOptions())
print(JSONObject)
} catch {
print("bad things happened")
}
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue()) {(response, data, error) -> Void in
let httpResponse = response as! NSHTTPURLResponse
if error != nil {
print("error")
}else {
print(response)
}
}
}