I want to implement a login function using AngularJS and my backend is in Rails. i decided to implement it using the $httpBackend but I have a problem.
When it gets into the $httpBackend function, it does update the token with the latest token from the database but i need to return the value to my services of which that doesnt seem to be happening. I know this has to do with promise and deferred etc but i am not well conversant with those.
SO this is my code
var authorized = false;
var token;
$httpBackend.whenPOST('https://login').respond(function(method, url, data) {
var loginDetails = data;
var d= $q.defer();
function startToken(loginDetails) {
getTokens.newToken(loginDetails).then(function(result) {
if(result.length > 0) {
var updateDB = "UPDATE preferences SET value='"+result[0].token+"' WHERE description='token'";
$cordovaSQLite.execute(db, updateDB).then(function(res) {
var updateDB1 = "UPDATE preferences SET value='true' WHERE description='logged_in'";
$cordovaSQLite.execute(db, updateDB1).then(function(res) {
var query = "SELECT description, value FROM preferences";
$cordovaSQLite.execute(db, query).then(function(res) {
if(res.rows.length > 0) {
if(res.rows.item(3).value!=null || res.rows.item(3).value!='') {
getTokens.getCRMToken(res.rows.item(2).value).then(function(resulttoken){
if(resulttoken[0].token == res.rows.item(3).value) {
token = res.rows.item(3).value;
}
d.resolve(token)
});
}
} else {
console.log("No results found");
}
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}
else {
console.log("reject")
d.reject(result);
}
}, 1000);
return d.promise;
}
var a = startToken(loginDetails).then(function(token) {
// in here the value for token is correct i then go ahead to set the value for authorized and resolve it
console.log(token)
if(token.length > 0){
console.log("authorized true")
authorized = true;
d.resolve(token, authorized)
}
else
{
console.log("authorized false")
authorized = false;
d.reject(token, authorized)
}
return d.promise;
})
// this is where i have my issue. all i want to do is to just check if the value for authorized is true, if yes, return the value for token.
//authorized = true;
//return [200 , { authorizationToken: token }];
});
Complete rewrite
Sadly, I think the short answer is that you cannot use promises with $httpBackend. See this discussion: https://github.com/angular/angular.js/issues/11245
In my original answer, I didn't recognize that you were using the $httpBackend mock as I merely concentrated on your incorrect promise code. The information on promises (https://docs.angularjs.org/api/ng/service/$q) is still valid.
The unit test version of ngMock does not handle promises. If you are using a version of $httpBackend which can handle promises, you need to return the promise, not the [200, "status"].
However, that said, in your rewrite, you also reuse the same promise after it has been resolved which is incorrect. You can only resolve or reject a defer once. So you need to either chain your then() functions or create a new defer. Also, you didn't actually need to create a defer since the newToken() function actually returns a promise.
Related
My Angular v1.x directive calls a function when a button is clicked and depending on the input I set some variables:
$scope.offerRespond = function() {
if (offer.i.offerResponse == 1) {
// set some variables
}
else if (offer.i.offerResponse == 0) {
$http({method: 'GET', url: frontbaseurl+'/offer_types/' + id + '.json'})
.then(function successCallback(response) {
$scope.reason = response.data.data.name;
}, function errorCallback() {
$scope.reason = "Unknown";
}
);
$http.post(frontbaseurl+'/purchases.json', JSON.stringify(dataObj))
.then(function(response){
// continue....
}
}
As can be seen, I make a GET request if the offerResponse == 0. This seems risky because the code continues to execute without before a response from the request is received. I know the approach is to use a 'promise' but how does that apply in this case if the GET request may not be made?
The problem as I see it is if I add a promise and the offerResponse == 1 the promise can't be fulfilled since the GET request would not have been made.
Any suggestions please?
I've had to do something similar in our angular 1.x app, using Typescript, like this:
public myFunc = async (id): Promise<void> => {
return new Promise<void>(async (resolve, reject) => {
if (this.offer.i.offerResponse === 1) {
// set some variables
resolve();
}
else if (this.offer.i.offerResponse === 0) {
try {
const response = await services.getOfferTypes(id);
this.reason = response.data.data.name;
resolve();
} catch (errorResponse) {
this.reason = "Unknown";
reject();
}
}
});
}
Notice I "encapsulated" your http request in a service function. I like separating api calls into their own service, to keep things clean and easy to read. This code is untested, but if I remember correctly it should work something like this.
I am using promise in angular for my web app like this-
var deferred = $q.defer();
On Success -
deferred.resolve(profile); // profile = JSON object
On Failure -
deferred.reject(1); // 1 or no value returned
At end of function -
return deferred.promise;
Then I am handing for this returned Promise Object to call another method.But it doesn't call. While if i use Callback(error,success) it works fine.Can somebody suggest what is wrong with my promise.
Code Snippet-
function open() { // for initializing DB,getting called from service
var deferred = $q.defer();
var options = {
Encryption: {
encryptKey: false, // optional encrypt primary key
secrets: [{
name: 'dddd',
key: 'xxxxxxxxxx'
}]
}
};
var schema = {
stores:[{
name:'profile',
encrypted: true
}]
};
var db = new ydn.db.Storage('nowconferdb', schema, options);
db.onReady(function() {
console.log('DB is initialized'); // getting this
profilestorage.setDB(db); // getting called and setting DB in profilestorage service
deferred.resolve(true);
});
db.addEventListener('fail', function (event) {
var err = event.getError();
if (err.name == 'versionchange') {
console.log('The application is updated, please refresh to upgrade.');
profilestorage.setup(db);
} else {
console.log('connection failed with ' + err.name + ' by ' + err.message);
db = null; // no operation can be placed to the database instance
}
deferred.reject(false);
});
return deferred.promise;
}
This is my calling method -
storageservice.open().then(function() {
console.log('post initializing storageservice'); // not getting it.
});
Thanks a lot for your sincere efforts.
What you should do is to call your function inside the success callback of the promise. Assumming you assign your promise to deferred variable:
deferred.then(
function (data) {
// Here you call your other function/method
// It will be called when 'deferred' promise is resolved
anotherFunction();
},
function (error) {
// Handle the error
}
);
I hope this helps, even though the information you give is not enough.
Environment:
MFPF v7.0
Eclipse: Luna SR.2 (4.4.2)
Windows 7
I face an strange issue. I am using adapter based authentication in one of my Angular based project.
The app authenticates well, but it repeatedly calls the submitSuccess.
I guess, it has something with the way Angular works, either I should use Challenge Handler as a Service or Controller. Because the way MobileFirst detects & handle instances of a/any handler objects. And that cause reference mis-match to dispose off or execute the relevant functions at appropriate time.
Currently I use it as a service.
Below is the challenge handler that I use.
define(['angular'], function(angular){
var loginChallengeHandler = angular.module('webApp.loginChallengeHandler', [])
.service('loginChallengeHandler', function(){
var _this = this;
_this.AuthRealmChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
_this.AuthRealmChallengeHandler.isCustomResponse = function(response) {
console.error("AuthRealmChallengeHandler.isCustomResponse:: " , response);
if (!response || !response.responseJSON || response.responseText === null) {
return false;
}
if (typeof(response.responseJSON.authRequired) !== 'undefined' || response.responseJSON.authRequired == true){
return true;
} else {
return false;
}
};
_this.AuthRealmChallengeHandler.handleChallenge = function(response){
console.error("AuthRealmChallengeHandler.handleChallenge:: " , response);
var authRequired = response.responseJSON.authRequired;
if (authRequired == true){
console.error("------Auth Required----- ");
_authenticationFailed(response);
} else if (authRequired == false){
console.error("------Auth PASSED ----- ");
//Now tell WL Authentication that user has been verified successfully so that it finishes the authentication process
_this.AuthRealmChallengeHandler.submitSuccess();
console.error("------ submitSuccess ----- ");
}
};
_this.AuthRealmChallengeHandler.userLogin = function(dataObjRef) {
var loginStatePromise = $q.defer();
_this.AuthRealmChallengeHandler.submitAdapterAuthentication(options,{
onFailure: function (error) {
loginStatePromise.resolve({ state:false , val: "" });
console.log("submitAdapterAuthentication Failed called ", error);
},
onSuccess: function(response) {
console.log("-> submitAdapterAuthentication onSuccess called " , response);
loginStatePromise.resolve({ state: _state , val: _msg });
},
timeout: 30000
});
return loginStatePromise.promise;
};
_this.AuthRealmChallengeHandler.logout = function (){
var userLogoutPromise = $q.defer();
WL.Client.logout("AdapterAuthRealm",{
onSuccess: function(){
console.log("onSuccess");
userLogoutPromise.resolve(true);
},
onFailure: function(){
console.log("onFailure");
userLogoutPromise.resolve(false);
},
timeout: 30000
});
return userLogoutPromise.promise;
};
var _authenticationFailed = function(response){
console.error("_authenticationFailed:: " , response);
//register failure request
_this.AuthRealmChallengeHandler.submitFailure();
};
});
return loginChallengeHandler;
});
I have also tried to bind the handler object with window object, so that it can access the handler's instance methods correctly.
Like:
window.AuthRealmChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
window.AuthRealmChallengeHandler.isCustomResponse = function(response) {
.
.
But still same issue.
I solved this issue and here is my solution for anyone facing similar issue in future.
Solution Description (few words)
As per my understanding, the IBM MobileFirst is expecting only one challenge-handler instance (the object that is create via createChallengeHandler function) to exists in the app. So most probably it assumes that the instance would be hooked into the window object.
Now based on this knowledge, we can see that above code is not working even we made instance through service ( i.e. singleton per angular app). Why ? Because, now the handler object becomes accessible via another reference, and this caused issues in resolving the handler references within the WL APIs.
So I just changed a bit of code (hooked it into window) so that WL APIs could reach the correct handler instance and clean-up the requests poll before marking the call successful and dispose off all the cached requests.
One more thing I would suggest.
Create only one handler instance in your client code
Create it as a service or factory - both are singletons in angularjs
Avoid using controllers, because there can be many controller instances within the angular app and it would lead to multiple handler references
And importantly trust IBM MobileFirst :)
Working Challenge Handler as Service
define(['angular'], function(angular){
'use strict';
var loginChallengeHandler = angular.module('webApp.loginChallengeHandler', [])
.service('loginChallengeHandler', function(){
//NOTE:- Below Must be bind with Window Object, otherwise will NOT work as per challenge handlers default behavior
window.AuthRealmChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
AuthRealmChallengeHandler.isCustomResponse = function(response) {
if (response && response.responseJSON && typeof (response.responseJSON.authStatus) === "string"){
return true;
} else {
return false;
}
};
AuthRealmChallengeHandler.handleChallenge = function(response){
var authStatus = response.responseJSON.authStatus;
if (authStatus === "failed"){
console.error("------Auth Required----- ");
_authenticationFailed(response);
} else if (authStatus === "passed"){
console.error("------Auth PASSED ----- ");
//do something here like change page etc.
//Now tell WL Authentication that user has been verified successfully so that it finishes the authentication process
AuthRealmChallengeHandler.submitSuccess();
}
};
AuthRealmChallengeHandler.userLogin = function(dataObjRef) {
var loginStatePromise = $q.defer();
AuthRealmChallengeHandler.submitAdapterAuthentication(options,{
onFailure: function (error) {
loginStatePromise.resolve({ state:false , val: "" });
},
onSuccess: function(response) {
loginStatePromise.resolve({ state: _state , val: _msg });
},
timeout: 30000
});
return loginStatePromise.promise;
};
AuthRealmChallengeHandler.logout = function (){
var userLogoutPromise = $q.defer();
WL.Client.logout("AdapterAuthRealm",{
onSuccess: function(){
//$state.go("home.login");
userLogoutPromise.resolve(true);
},
onFailure: function(){
userLogoutPromise.resolve(false);
},
timeout: 30000
});
return userLogoutPromise.promise;
};
var _authenticationFailed = function(response){
//register failure request
AuthRealmChallengeHandler.submitFailure();
};
});//end of service
return loginChallengeHandler;
});
Adapter
function onAuthRequired(headers, errorMessage){
errorMessage = errorMessage ? errorMessage : null;
return {
authStatus: "failed",
errorMessage: errorMessage
};
}
function Login(request){
if(request){
/* IF user credentials are Verified Correctly
* and user is authenticated then create User Identity
* and return success message if it is required by client app.
*/
userIdentity = {
userId: "abc",
displayName: "ABc",
attributes: {}
};
WL.Server.setActiveUser("AdapterAuthRealm", userIdentity);
WL.Logger.error("Auth Successful:");
return {
authStatus: "passed",
submitResponse: "send a Success message in case is required on client-side"
};
}else{
return onAuthRequired(null, "send an error message if required on client side");
}
}
I faced the same issue with adapter based authentication but I was using pure javascript, so no angular. From that I can tell you it's a MobileFirst issue and nothing related to angular.
This might sound contradictory to the documentations but don't call the submitSuccess function, just call your code on successful authentication. It will work fine and authenticate properly.
Also, make sure that you only have the security test set on the specific functions that you use after auth and not on the auth function itself.
Your code seems fine to me but I'm not that good in angular.
UPDATE
I think I solved it myself. Check my interceptor that I posted as a solution below.
ORIGINAL
I was wondering if it would be possible to write an http interceptor that can let the caller know how the request is doing.
Right now, when I want to call my backend, I wrap an $http call in a wrapper that sets attributes on an object I pass it:
publ.wrap = function(f, ctrl){
ctrl.busy = true;
ctrl.error = false;
return f()
.then(function(res){
ctrl.busy = false;
ctrl.result = res;
return res;
}).catch(function(err){
ctrl.busy = false;
ctrl.error = err;
ctrl.result = undefined;
})
};
publ.login = function(args, ctrl){
publ.wrap(function(){
return $http.post('http://localhost:3001/authenticate', {
username : args.username,
password : args.password
}).then(function(jwt){
$cookies.put('token', jwt);
})
}, ctrl);
};
In this case, I call login(authArgs, $scope.loginCtrl) in my login page controller. Then I use loginCtrl.busy, loginCtrl.result & loginCtrl.error in my login template.
I pretty much want every call I make to the backend to set these attributes and make them available to the views that initiate the request.
Using a wrapper function like this gets the job done, but I'm wondering if it can be done using an interceptor? It feels to me like that would provide a much cleaner request flow that doesn't require me to explicitly wrap all of my backend calls in my services.
Now I read up on httpInterceptors, and can't seem to find a way to have them set attributes on a user-provided object. The closes thing I found was this article that has an example ( Timestamp Marker (request and response interceptors) ) where they add attributes to the config object in both the request and response interceptor stages.They don't show how to access the config object inside the responseError stage or in the caller controller.
Any help would be greatly appreciated :)
I use Angular events to handle stuff like this- for example:
.controller('parentCtrl', function($scope,$rootScope) {
$rootScope.$on('loading',function(e,_statusObj.loading) {
$scope.loading = _statusObj.loading;
if(!!_statusObj.msg) {
alert(_statusObj.msg);
}
});
})
.controller('childCtrl', function($scope,$http) {
$scope.myAjaxCall = function(_url,_data) {
$scope.$emit('loading',{ loading: true});
$http.post(_url,_data).success(function(_response) {
$scope.$emit('loading',{ loading: false });
})
.error(function(_error) {
$scope.$emit('loading',{
loading : false,
msg : _error.message
});
});
}
});
I managed to get the interceptor working. Apparently we CAN access the config file in all interceptor phases:
/******************************************
SETUP BUSY/ERROR/DATA HTTP INTERCEPTOR
*******************************************/
.config(function($httpProvider){
$httpProvider.interceptors.push(function($q) {
return {
request : function(config) {
if(config.ctrl){
config.ctrl.busy = true;
config.ctrl.error = false;
config.ctrl.data = undefined;
}
return config;
},
response : function(response) {
if(response.config && response.config.ctrl){
response.config.ctrl.busy = false;
response.config.ctrl.data = response.data;
}
return response;
},
responseError : function(response){
// note: maybe use a different error message for different kinds of responses?
var error = response.status + " "+response.statusText+" - "+response.data;
if(response.config && response.config.ctrl){
response.config.ctrl.busy = false;
response.config.ctrl.error = error;
}
return $q.reject(error);
}
};
});
})
I'm trying to figure out if it is possible to use a $http interceptor to cancel a request before it even happens.
There is a button that triggers a request but if the user double-clicks it I do not want the same request to get triggered twice.
Now, I realize that there's several ways to solve this, and we do already have a working solution where we wrap $http in a service that keeps track of requests that are currently pending and simply ignores new requests with the same method, url and data.
Basically this is the behaviour I am trying to do with an interceptor:
factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.finally(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
}
}
}])
When I look at the API for $http interceptors it does not seem to be a way to achieve this. I have access to the config object but that's about it.
Am I attempting to step outside the boundaries of what interceptors can be used for here or is there a way to do it?
according to $http documentation, you can return your own config from request interceptor.
try something like this:
config(function($httpProvider) {
var cache = {};
$httpProvider.interceptors.push(function() {
return {
response : function(config) {
var key = createKey(config);
var cached = cache[key];
return cached ? cached : cached[key];
}
}
});
}
Very old question, but I'll give a shot to handle this situation.
If I understood correctly, you are trying to:
1 - Start a request and register something to refer back to it;
2 - If another request takes place, to the same endpoint, you want to retrieve that first reference and drop the request in it.
This might be handled by a request timeout in the $http config object. On the interceptor, you can verify it there's one registered on the current request, if not, you can setup one, keep a reference to it and handle if afterwards:
function DropoutInterceptor($injector) {
var $q = $q || $injector.get('$q');
var dropouts = {};
return {
'request': function(config) {
// I'm using the request's URL here to make
// this reference, but this can be bad for
// some situations.
if (dropouts.hasOwnProperty(config.url)) {
// Drop the request
dropouts[config.url].resolve();
}
dropouts[config.url] = $q.defer();
// If the request already have one timeout
// defined, keep it, othwerwise, set up ours.
config.timeout = config.timeout || dropouts[config.url];
return config;
},
'requestError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
},
'response': function(response) {
delete dropouts[response.config.url];
return response;
},
'responseError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
}
};
}