I had this little extension on Alamofire 3, it was used to get certain tokens and data from the default response.
public extension Alamofire.Request {
private func authorizationHandler(queue: dispatch_queue_t? = nil, completionHandler: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> Void) -> Self {
return response { (req, res, data, error) in
if let headers = res?.allHeaderFields {
if let id = headers["x-uid"] as? String {
oHTTPEnvironment.x_uid = id
}
if let authToken = headers["x-access-token"] as? String {
oHTTPEnvironment.currentMutableToken = oHTTPEnvironment.nextMutableToken
oHTTPEnvironment.nextMutableToken = authToken
}
}
dispatch_async(queue ?? dispatch_get_main_queue(), {
completionHandler(req!, res, data, error)
})
}
}
}
And now in Alamofire 4 and Swift 3 it get's an error on the "return response { (req, res, data, error) in"
The error is:
Cannot call value of non-function type 'HTTPURLResponse'
I even tried to do:
private func authorizationHandler(queue: DispatchQueue? = nil, completionHandler: (NSURLRequest, HTTPURLResponse?, NSData?, NSError?) -> Void) -> Self {
return response { resp in
debugPrint(resp)
}
}
But it gives the same error.
Related
I'm trying to show in a view parsed data from an API using SwiftUI.
I'm fetching correctly the data using this code:
import UIKit
class APIController: ObservableObject {
func apiCall() {
// URL
let url = URL(string: "https://geek-jokes.p.rapidapi.com/api?format=json")
guard url != nil else {
print("Error creating url object")
return
}
// URL Rquest
var request = URLRequest(url: url!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
// Specify the header
let headers = [
"x-rapidapi-key": "d1363cbe66msh266920b6366eaacp1f87dfjsn050e3f8e58e2",
"x-rapidapi-host": "geek-jokes.p.rapidapi.com"
]
request.allHTTPHeaderFields = headers
// Specify the body
// let jsonObject = [
// "sign": "Aries"
// ] as [String:Any]
//
// do {
// let requestBody = try JSONSerialization.data(withJSONObject: jsonObject, options: .fragmentsAllowed)
//
// request.httpBody = requestBody
// }
// catch {
// print("Error creating the data object from json")
// }
// Set the request type
request.httpMethod = "GET"
// Get the URLSession
let session = URLSession.shared
// Create the data task
let dataTask = session.dataTask(with: request) { data, response, error in
// Check for errors
if error == nil && data != nil {
// Try to parse out the JSON data
let decoder = JSONDecoder()
do {
let jokesData = try decoder.decode(JokesData.self, from: data!)
print(jokesData)
}
catch {
print("Error in JSON parsing")
}
// Try to print out the JSON data
// do {
// let dictionary = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String:Any]
// print(dictionary)
//
// }
// catch {
// print("Error parsing response data")
// }
}
}
// Fire off the data task
dataTask.resume()
}
}
Then I'm not sure if the data is correctly imported in JokesData.swift
import foundation
struct JokesData: Codable {
var joke:String = ""
}
And when I try to load the data in JokesView.swift, nothing appear :(
import SwiftUI
struct JokesView: View {
var jokesData: JokesData
#StateObject var viewRouter: ViewRouter
var body: some View {
Text(jokesData.joke)
.foregroundColor(.black)
}
}
If someone has an idea of what I did wrong, it would be very helpful.
In APIController add a property
#Published var jokesData = JokesData()
Replace the do - catch block in the completion handler with
do {
let jokesData = try decoder.decode(JokesData.self, from: data!)
print(jokesData)
DispatchQueue.main.async {
self.jokesData = jokesData
}
}
catch {
print(error)
}
it assigns the received data to the publishing jokesData property.
In the view create a #StateObject of the controller, in the onAppear modifier load the data, the view will be updated automatically.
import SwiftUI
struct JokesView: View {
#StateObject var apiController = APIController()
var body: some View {
Text(apiController.jokesData.joke)
.foregroundColor(.black)
.onAppear() {
apiController.apiCall()
}
}
}
confirmBtn.addTarget(self, action: #selector(changePassword(email:currentPassword:newPassword:completion:)), for: .touchUpInside)
}
func changePassword(email: String, currentPassword: String, newPassword: String, completion: #escaping (Error?) -> Void) {
let credential = EmailAuthProvider.credential(withEmail: email, password: currentPassword)
Auth.auth().currentUser?.reauthenticate(with: credential, completion: { (result, error) in
if let error = error {
completion(error)
}
else {
Auth.auth().currentUser?.updatePassword(to: newPassword, completion: { (error) in
completion(error)
})
}
})
}
how Selector send parameter error is Argument of '#selector' refers to instance method 'changePassword(email:currentPassword:newPassword:completion:)' that is not exposed to Objective-C
i don't understand
I meet something strange and found no answer.
I met the warning as below:
Possible Unhandled Promise Rejection(id:0)
TypeError: undefined is not a function (evaluating '_api2.default.getResp(URL, "GET")'....
Below is shortened code like what my source code is.
The "Pass Code" works fine and "Warning Code" shows warning as above.
Why Api.getResp goes wrong but Api.loggedin.getResp works OK? They are so similar!!
Warning Code
const GetData = {
async fetchFeed() {
console.log(`Api = ${Api}`); // Api = [Object Object]
console.log(`Api.getResp = ${Api.getResp}`); // Api.getRest = undefined
const { success, content } = await Api.getResp(`${URL}`, 'GET');
if (success) {
.....
}
},
};
module.exports = FetchApi;
Pass Code
const GetData2 = {
async syncData(): void {
const { success, content } = await Api.loggedin.getResp(`${URL}`, 'GET');
.....
}
}
API
const Api = {
async getResp(url: string, method: string): Object {
try {
const response = await fetch(url,
{ method,
null,
{ 'content-type' : 'application/json' },
});
......
} catch (error) {
console.log(`fetch url: ${url}, error: ${error}`);
}
return result;
},
loggedin: {
async getResp(url: string, method: string): Object {
const accessToken = await this._getAccessToken();
....
return result;
},
},
I am trying to implement expressjs like features to httprouter package .
I create a struct type mounter
type Mounter struct {
BasePath string
Routes []*Route
}
and a Route struct which represents subRoutes
type Route struct {
Path string
Method string
Func Handle
}
type Handle func(http.ResponseWriter, *http.Request, Params)
type Params interface{}
i have a NewRoutes Function which is the main thing i wanted to port from expressjs new routes does the same thing as express.Router
func NewRoutes(base string) (mounter *Mounter) {
mounter = &Mounter{
BasePath: base,
}
return
}
and i have get post put delete methods under *Mounter
//GET request handler
func (mounter *Mounter) GET(path string, Func Handle) {
mounter.Routes = append(mounter.Routes, &Route{path, "get", Func})
}
//POST request handler
func (mounter *Mounter) POST(path string, Func Handle) {
mounter.Routes = append(mounter.Routes, &Route{path, "post", Func})
}
//PUT request handler
func (mounter *Mounter) PUT(path string, Func Handle) {
mounter.Routes = append(mounter.Routes, &Route{path, "put", Func})
}
//DELETE request handler
func (mounter *Mounter) DELETE(path string, Func Handle) {
mounter.Routes = append(mounter.Routes, &Route{path, "delete", Func})
}
and finally i have a Mount method which mounts the router to the actual router
func (mounter *Mounter) Mount(router *rtr.Router) {
mounter.BasePath = strings.TrimSuffix(mounter.BasePath, "/")
for _, route := range mounter.Routes {
path := route.Path
if !strings.HasSuffix(path, "/") {
path += "/"
}
path = mounter.BasePath + path
switch strings.ToLower(route.Method) {
case "get":
router.GET(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
route.Func(res, req, params)
})
case "post":
router.POST(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
route.Func(res, req, params)
})
case "delete":
router.DELETE(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
route.Func(res, req, params)
})
case "put":
router.PUT(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
route.Func(res, req, params)
})
}
}
}
everything works pretty nice and the methods are working fine too if i try to send a post request to a get endpoint it gives a nice 404 but the only issue is it always responds with the handler of last added member regardless of subpath so
package api
var ApiRouter = express.NewRoutes("/api/")
func init() {
ApiRouter.GET("/", func(res http.ResponseWriter, req *http.Request, _ express.Params) {
fmt.Fprintln(res, "testget/")
})
ApiRouter.GET("/pt", func(res http.ResponseWriter, req *http.Request, _ express.Params) {
fmt.Fprintln(res, "pt")
})
ApiRouter.POST("/test", func(res http.ResponseWriter, req *http.Request, _ express.Params) {
fmt.Fprintln(res, "test/post")
})
}
package main
func main() {
router := express.New()
api.ApiRouter.Mount(router)
for _, route := range api.ApiRouter.Routes {
fmt.Println(*route)
}
router.ServeFiles("/public/*filepath", http.Dir("./public/"))
http.ListenAndServe(":1024", router)
}
Will always respond test/post and the output of the range i am doing above for test purposes is
So do you have any idea why it uses always the same function to respond but recognizes paths perfectly?
The problem lies in Mount method, which is known as Iteration Variables and Closures. Declare a new variable for capturing route e.g.
thisRoute := route //capture iteration variable
switch strings.ToLower(route.Method) {
case "get":
router.GET(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
thisRoute.Func(res, req, params)
})
case "post":
router.POST(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
thisRoute.Func(res, req, params)
})
//...
}
I have created angular service, where I'm registering challengeHandler this way:
azureChallengeHandler = WL.Client.createChallengeHandler(realm);
azureChallengeHandler.isCustomResponse = function (response) {
...
};
azureChallengeHandler.handleChallenge = function (response) {
...
};
So i'm logging in with this function:
WL.Client.login(realm, options)
And the first time it works ok, isCustomResponse gets called, returns "true", then handleChallenge gets called.
But after logging out with this function:
WL.Client.logout(realm, options)
When I try to login again, isCustomResponse gets called and still returns "true", but handleChallenge is not firing.
How can I fix that?
After calling WL.Client.reloadApp() or reloading app itself I can login again, but it's not a suitable solution.
UPDATE:
Here is adapter code:
function onAuthRequired(headers) {
return customLoginResponse(true, false, false);
}
function customLoginResponse(authRequired, azureTokenRequired, wrongTenant) {
return {
authRequired: authRequired,
azureTokenRequired: azureTokenRequired,
realm: 'AzureAuth',
wrongTenant: wrongTenant
};
}
function onLogout(){
WL.Server.setActiveUser("AzureAuth", null);
WL.Logger.debug("Logged out");
}
function submitLogout(uuid, orgId, ssogroup){
WL.Server.invokeProcedure({
adapter: "AzureTokenSqlAdapter",
procedure: "removeRefreshToken",
parameters: [uuid, orgId, ssogroup]
});
onLogout();
}
function submitLogin(uuid, orgId, ssogroup, code) {
var tokenObject = getTokens(code);
if (tokenObject.id_token) {
var jwtParsed = parseJWT(tokenObject.id_token);
var tenantId = jwtParsed.tid;
var invocationResult = WL.Server.invokeProcedure({
adapter: "AzureTokenSqlAdapter",
procedure: "checkTenant",
parameters: [orgId, tenantId]
});
if (!invocationResult.tenantRegistered) {
return customLoginResponse(true, true, true);
}
}
return authUser(tokenObject, uuid, orgId, ssogroup);
}
And here is the client code:
function azureAuthService($q, _, $state) {
var loginPromise;
azureChallengeHandler = WL.Client.createChallengeHandler(realm);
//first response after protected call
azureChallengeHandler.isCustomResponse = function (response) {
if (!response || !response.responseJSON || response.responseText === null) {
return false;
}
return response.responseJSON.realm == realm;
};
//when isCustomResponse returns true
azureChallengeHandler.handleChallenge = function (response) {
WL.Logger.debug("challenge handler -- handleChallenge");
var authRequired = response.responseJSON.authRequired;
var azureTokenRequired = response.responseJSON.azureTokenRequired;
var wrongTenant = response.responseJSON.wrongTenant;
if (wrongTenant) {
loginPromise.reject('wrong tenant');
} else if (authRequired && azureTokenRequired) {
fullLogin();
} else if (authRequired) {
fastLogin();
} else {
loginPromise.resolve();
}
};
azureChallengeHandler.handleFailure = function (error) {
console.log('failure');
console.log(error);
};
return {
init: init,
login: login,
logout: logout
};
function init(config) {
ssogroup = config.ssogroup;
orgId = config.orgId;
}
function login() {
loginPromise = $q.defer();
WL.Client.login(realm, {
onSuccess: function(info) {
loginPromise.resolve();
},
onFailure: function(error) {
loginPromise.reject();
}
});
return loginPromise.promise;
}
function logout() {
var logoutPromise = $q.defer();
var invocationData = {
adapter : 'AzureAuth',
procedure : 'submitLogout',
parameters : [device.uuid, orgId, ssogroup]
};
WL.Client.invokeProcedure(invocationData).then(function () {
WL.Client.logout(realm, {
onSuccess: function () {
logoutPromise.resolve();
},
onFailure: function () {
logoutPromise.reject();
}
});
}, function () {
logoutPromise.reject();
});
return logoutPromise.promise;
}
}
fastLogin and fullLogin is functions that perform some work and finally call
var options = {
parameters: [device.uuid, orgId, ssogroup, transitionAuthObject.requestToken],
adapter: "AzureAuth",
procedure: "submitLogin"
};
azureChallengeHandler.submitAdapterAuthentication(options);
Can't see your fullLogin() and fastLogin() methods so it's hard to say for sure. Make sure that you're calling challengeHandler's submitSuccess() or submitFailure() methods after successful/failed authentication. The authentication framework keeps a queue of requests/responses that require authentication. After successful/failed authentication you need to invoke submitSuccess/submitFailure on challenge handler in order for authentication framework to remove your requests from queue and process it. In case you're not doing so the request remains in the queue and once you're sending a new request that triggers authentication it is put into queue but not handled since there's another request already waiting for authentication.