Alamofire Background Service, Global Manager? Global Authorisation Header? - ios

Can anyone tell me how to use the Alamofire background service correctly?
My attempts are:
Login View Controller
// Create a global variable for the manager
var manager = Alamofire.Manager()
var configuration = NSURLSessionConfiguration()
class LoginViewController: UIViewController {
// set the configuration for the manager
configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.xxx.app.backgroundService")
configuration.sharedContainerIdentifier = "com.xxx.app.backgroundService"
}
Now creating my login attempt, when successful go to the MainViewController and do here some more Requests.
manager.request(.POST, "\(serviceUrl)public/service/authenticate", parameters: ["email": txtEmail.text!, "password": txtPassword.text!]) {
......
}
Here ill get my token for all requests, so ill add the to my global configuration:
class MainViewController: UIViewController {
if let token: AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("token") {
configuration.HTTPAdditionalHeaders = ["Authorization": "Bearer \(token)"]
manager = Alamofire.Manager(configuration: configuration)
}
}
But now when ill logout and login again - ill get the following errors:
Warning: A background URLSession with identifier xxx.xxx.app.backgroundService already exists!
And the app crashes with:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Task created in a session that has been invalidated'
*** First throw call stack:
So i really dont know how do use the background-service to get the following result:
At login attempt, i dont need to add an authentication header. But then, i would like to add it to every request. All requests should run as background service.
Can anyone help me out, how to solve that in a correct way? Thanks in advance!

You have multiple problems. The first is that you need to pass the configuration object into the Manager initializer. Otherwise you are not using the configuration for the underlying URL session in the Manager instance.
The second issue is that you should NOT set header values on a configuration after it has been applied to a Manager instance. The Apple docs specifically call this out. We also call this out in our README. If you need to deal with authorization tokens, you need to pass them as a header attached to the actual request.
Update #1
Here's a small example showing how you could create a Manager instance that could be used globally.
class NetworkManager {
var authToken = "1234" // You'll need to update this as necessary
let manager: Manager = {
let configuration: NSURLSessionConfiguration = {
let identifier = "com.company.app.background-session"
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(identifier)
return configuration
}()
return Manager(configuration: configuration)
}()
static let sharedInstance = NetworkManager()
}
class ExampleClass {
func startNetworkRequest() {
let headers = ["Authorization": NetworkManager.sharedInstance.authToken]
NetworkManager.sharedInstance.manager.request(.GET, "https://httpbin.org/get", headers: headers)
.responseJSON { response in
debugPrint(response)
}
}
}
You'll still need to work out how to update the authToken when it expires, but this shows one way how you could use your manager instance throughout your codebase.

Related

Swift AlamoFire 5 won't execute Request

My problem is, when I execute the request it will happen nothing.
What I made:
I made an Api with .net core Api on a another PC and now I want to make a IOS App, that has access to the Api. (The API works I tested it with swagger and Insomnia) On the IOS app I made a put request with AlamoFire. And when I execute the request it's happening nothing. I watched the Network with Wireshark and there comes Nothing too.
When I Debug, it will step through the request Methode, but it goes nothing out to the API. And there are no errors.
// buttonClick will call this Methode
func requst() {
// testing parameters
let params: Parameters = [
"nfcId": 1,
"date" : "2021-04-12T09:47:12.053Z"
]
//
session.request("http://MyAPI/api/Guard/AddGuard", method: .put, parameters: params, headers: nil).validate(statusCode: 200 ..< 299)
}
I made an own Session wehere I pinned a certificate
private let certificates = [
"https://MyAPI:5001":
PinnedCertificatesTrustEvaluator(certificates: [Certificates.certificate], acceptSelfSignedCertificates: false, performDefaultValidation: true, validateHost: true)
]
private let session: Session
init(allHostsMustBeEvaluated: Bool) {
let serverTrustPolicy = ServerTrustManager(allHostsMustBeEvaluated: allHostsMustBeEvaluated, evaluators: certificates)
let config = URLSessionConfiguration.af.default
session = Session(configuration: config, serverTrustManager: serverTrustPolicy)
}
You haven't attached a response handler or called resume(), so no, the request isn't going to do anything. Adding something like responseDecodable will start the request automatically.

Moya - unable to call apis with authentication credentials

I am developing an iOS app using django rest framework for apis. But currently I am not be able to getting ahead when calling apis with authentication credentials.
I succeeded in calling the api using Postman and curl by setting Header as Authentication Bearer <token>.. but I continuously failed at calling it from iOS app. I am using Moya for calling api. And I don't know what I should do next.
What I tried: (when calling Moya)
let token = "abcde12345sometoken"
let plugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<AccountAPI>(plugins : [plugin])
provider.request(.getAccountProfile(oauth_id: oauth_id, provider: "facebook")) { (result) in
// doing something with result
}
and configured API as:
extension AccountAPI : TargetType, AccessTokenAuthorizable {
// codes conforming variables to TargetType protocol
public var authorizationType: AuthorizationType {
switch self {
case .getFacebookAccountToken:
return .none
default:
return .bearer
}
}
public var headers: [String: String]? {
switch self {
case .getFacebookAccountToken, .getEmailAccountToken: // post requests
return ["Content-type":"application/x-www-form-urlencoded"]
default:
return ["Content-type":"application/json"]
}
}
}
Is there anything I should consider when using Moya for authentication or maybe with Info.plist and so on?
Or the document says this approach is for JWT token, and maybe my method is not for JWT and something else..? Give me some advice!
For my case, I use
Moya 12.0.1
MultiTarget
example:
plugins = [AccessTokenPlugin(tokenClosure: {
let token = ...
return token
})]
MoyaProvider<MultiTarget>(
plugins: plugins
)
.request(MultiTarget(myAPI)) {
...
}
But it never calls tokenClosure
Solution
you need to add this extension
extension MultiTarget: AccessTokenAuthorizable {
public var authorizationType: AuthorizationType {
guard let target = target as? AccessTokenAuthorizable else { return .none }
return target.authorizationType
}
}
source: https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/AccessTokenPlugin.swift#L62
After a few hours of trying this and that.. I found out that it was the api endpoint redirects itself based on the content-language.. so the header that I set is dead when being redirected. So either setting the i18n url in advance or setting the content-language header would solve my problem.

How to cancel multiple networking requests using Moya

I am currently using Moya to structure my networking calls. Per their docs, I have configured it as the following:
enum SomeAPIService {
case endPoint1(with: Object)
case endPoint2(duration: Int)
}
When calling an endpoint (in this case, endPoint1), I do the following:
let provider = MoyaProvider<SomeAPIService>()
provider.request(.endPoint1(object: object)) { (result) in
switch result {
case let .success(moyaResponse):
finished(Result.success(value: moyaResponse.value))
case let .failure(error):
let backendError = BackendError(localizedTitle: "", localizedDescription: "Some error", code: moyaResponse.statusCode)
finished(Result.failure(error: backendError))
}
})
My goal is, upon the user performing an action, cancel all the networking requests that's happening.
Accordingly, Moya does allow one to cancel requests from the discussion here. From the most upvoted comment, someone mentioned let request_1 = MoyaRequestXXXXX and then ruest_1.cancel()
My problem is:
How would I keep pointer to the requests?
provider doesn't have a cancel() function - so how should I be calling it?
Any help is much appreciated.
Edit:
Per the helpful suggestion about using [Cancellable], I did the following:
(1) In my app's singleton instance called Operator, I added var requests = [Cancellable]()
(2) Every API call is added to the requests array as a Cancellable, like so:
let provider = MoyaProvider<SomeAPIService>()
Operator.shared.requests.append(provider as! Cancellable) //causing error
provider.request(.endPoint1(object: object)) { (result) in
//rest of the block omitted
I think I am not getting the syntax correct, and am adding the provider and not the request. However, since the request is itself a block, where would be the place to add the request?
The request method returns a Cancellable. From the documentation we can read:
The request() method returns a Cancellable, which has only one public function, cancel(), which you can use to cancel the request.
So according to this, I made a simple test and call:
var requests: [Cancellable] = []
#objc func doRequests() {
for i in 1...20 {
let request = provider.request(MyApi.someMethod) {
result in
print(result)
}
requests.append(request)
}
requests.forEach { cancellable in cancellable.cancel() } // here I go through the array and cancell each request.
requests.removeAll()
}
I set up a proxy using Charles and it seems to be working as expected. No request was sent - each request was cancelled.
So, the answer to your questions is:
You can keep it in [Cancellable] array.
Go through the array and cancel each request that you want to cancel.
EDIT
The main problem is that you adding the provider to the array and you try to map provider as Cancellable, so that cause the error.
You should add reqest to the array. Below you can see the implementation.
let provider = MoyaProvider<SomeAPIService>()
let request = provider.request(.endPoint1(object: object)) { // block body }
Operator.shared.requests.append(request)
//Then you can cancell your all requests.
I would just cancel the current provider session + tasks:
provider.manager.session.invalidateAndCancel()

secret or public key must be provided - IOS/Swift + Socket.io-JWT

My goal is very simple, authenticate a user via socket.io jwt. So that I could manipulate his/her data in real-time.
Clientside code
import SocketIOClientSwift
import KeychainAccess
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
// 1
let keychain = Keychain(server: "https://testing.herokuapp.com", protocolType: .HTTPS)
//2
var token: String {
get {
if let realToken = keychain["token"] {
return String(realToken)
} else {
return ""
}
}
}
//3
lazy var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "https://testing.herokuapp.com")!,
options: [.Log(true), .ConnectParams(["token": self.token])])
override init() {
super.init()
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
}
Just for you guys to understand
keychain purpose is just to store the token, let say I already saved
the token from user who logs in, then I will simply do this
keychain["token"] = "token", lets assume that the token has been
saved
var token is to get the token, because currently keychain store
the token in optional, thus i need to do optional binding.
Lastly I instantiate a socket object, so that I could pass the token
as the parameter, many people recommend to do this
.ConnectParams(["token": self.token])
So far so good, but right now, whenever I try to establish a connection in an app delegate ,
AppDelegate.Swift
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
SocketIOManager.sharedInstance.establishConnection()
}
I will received
Handling event: error with data: (
{
code = "invalid_token";
message = "secret or public key must be provided";
type = UnauthorizedError;
} )
For clarity, I don't want to include the serverside code because It's just a boilerplate code from this https://github.com/auth0/socketio-jwt
So, the real question is, what did I do wrong??
Looks like you didn't provide a secret in your server implementation:
socketioJwt.authorize({
secret: 'some secret here', // <--
handshake: true
})
inside your .env add JWT_SECRET=JWT_SECRET

Alamofire 3: cancel request with custom error

(Using iOS 9, Swift 2, Alamofire 3)
My app interacts with a REST service that requires a session token in the header. If the session token isn't already available before manager.request() is called, there's no reason to send the request only to have it fail. So I'd like to abort the request with my own error, and have the request's chained response handler take care of it - i.e. the caller wouldn't know that the request was actually never sent to the server.
What's the best way to do this with Alamofire 3?
Any way to have the same effect as Request.cancel() but with custom error, as below?:
static func request(method: Alamofire.Method, _ URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil) -> Request {
let api = apiManager.sharedInstance
guard let sessionToken = api.sessionToken else {
let req = api.alamofireManager.request(method, URLString, parameters)
req.cancel() // ***I'd like to do: req.cancelWithError(.NoSessionToken)
return req
}
// Create request and add session token to header
// ...
}

Resources