Get $state url in $http interceptor - angularjs

I am trying to hanlde my 404 errors properly in my Angular app.
The redirection works fine here but somehow I cannot manage to get the $state url here. The window.location.href gets printed jsut fine bu I get nothing for $injector.get('$state').current.url.
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push([
'$q',
'$rootScope',
'$injector',
function($q, $rootScope, $injector) {
return {
'request': function(config) {
return config;
},
'responseError': function(response) {
console.log(window.location.href);
console.log($injector.get('$state').current.url);
//404 error handling
if (response.status === 404)
$injector.get('$state').go('404')
//500 error handling
if (response.status === 500)
$injector.get('$state').go('500')
return response;
}
};
}]);
}])
I cannot figure out where the problem comes from. Does anyone has an idea?
Many thanks

What are you really trying to catch as an error : ?
a route change which failed (due to a failed ui-router resolve) ?
any http request ? (which is not linked to ui-router)
If it's a routeChange error, you should take a look at the ui-router $stateChangeError event which will get fired when any resolve fails
Example :
$rootScope.$on("$stateChangeError", function (event, to, toParams, from, fromParams, reason) {
// error handling code
// you can use params to retrieve from/to route
});
If you want to catch any http request like your code snippet, you're code can work only if ui-router had the time to iniatialized itself .. and you can't be sure of that if you don't use ui-router events.

Related

angularjs http interceptor to show error on loaded location path

I have an application for which I created an interceptor to handle token expirations after 15 minute inactivity, it successfully redirects to the login page after a token has expired, but Im not able to show the error after redirecting to the login page.
My question is, how can I show the user the token expired error on the login page, after the interceptor has redirected the app to that page.
Heres my redirector:
app
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $location, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$location.path("/login");
}
return $q.reject(rejection);
}
};
})
}])
.config(function(LoopBackResourceProvider) {    
LoopBackResourceProvider.setAuthHeader('X-Access-Token');
})
Finally and thanks to #forrestmid to point me in the right direction this is what I ended up doing.
on the http interceptor just added:
$location.path("/login").search({error: 'invalid_token'});
and then on the controller just had to do:
var queryString = $location.search();
$scope.errors = {};
if (queryString && queryString.error) {
$scope.errors = {
'invalid_token': {
code: 'invalid_token'
}
}
}
now on the template I already have logic to handle the error object so now it works fine :)
Referencing this post in regards to injecting the $state service into an HTTP interceptor:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $injector, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$injector.get('$state').go('app.login', {error: 'Token expired.'});
}
return $q.reject(rejection);
}
};
})
}]);
Assuming that you're using ui.router:
app.config(function($stateProvider){
$stateProvider
.state("app",{abstract: true})
.state("app.login", {
url: "/login",
params: {error: ""}
});
});
By default there will be no error when transitioning to the app.login state, but when there is a param error set to whatever, it can display the error. This will be in the $stateParams.error variable on your login page.
Let me know if I botched any of that code since I didn't test it. The line I think you want is the $injector line.

Maintain session using Interceptor cookies/ server-sessions

Firstly, apology if this question does not make sense. I am developing code for session management for my mean stack app. From last few days, i found lots of way to implement it which are using either cookies, sessions or http - headers. I tried to implement, but did not get success.
I successfully link the interceptor with my code. Code is listening to each req/res.
Here is some code:
app.js
angular.module('MyApp', [
'ngMaterial',
'ngMdIcons',
'ui.router',
'e3-core-ui.services',
'e3-core-ui.utils'
])
.config(['$stateProvider', '$routeProvider','$httpProvider','$mdThemingProvider', '$mdIconProvider', function($stateProvider, $routeProvider, $httpProvider, $mdThemingProvider, $mdIconProvider) {
$httpProvider.interceptors.push('YourHttpInterceptor');
...
Interceptor-code
angular.module('MyApp')
.factory('YourHttpInterceptor', ['$q',
function($q, ) {
return {
'request': function(config) {
console.log("req");
return config;
},
// Optional method
'response': function(response) {
// do something on response success
console.log("inside the response ");
return response;
},
// optional method
'responseError': function(rejection) {
// Here you can do something in response error, like handle errors, present error messages etc.
console.log("inside the response error ");
return $q.reject(rejection);
}
};
}]);
I will be very thankful for your time and help.
In Meanjs you have the authentication controller
mean/modules/users/client/controllers/authentication.client.controller.js
but if you want to use the authentication service in your interceptor, just be aware that injecting a dependency isn't that easy as doing it in a controller.
you'll need to use $injector
var AuthService = $injector.get('Auth');
then you'll have to be sure your user is authenticated and check that in your request function, something like
if (!Authentication.user) {
$location.path('/'); // or a login page
}

AngularJs : event listener on http requests

Since i'm using Oauth2 to protect my Api, i need to get a new access token before any http requets if the previous access token has expired.
I didn't used event listener much until now.
Here what i did for now (Please let me know if it is correct) :
ApplicationController.js :
app.controller('ApplicationController', function($rootScope, $scope, $localStorage, AuthService){
// Listening event apiRequested
$scope.$on('event:apiRequested', function(e) {
AuthService.token();
// Restore the access_token in case it has changed
access_token = $localStorage.getObject('access_token');
});
})
UserController.js :
$rootScope.$broadcast('event:apiRequested');
// Get Users around
return $http.post(domain+'/api/users?access_token='+access_token.key, data).then(function(response){
return response;
});
First thing i'm not sure about ... Does $http is processed if the event already executed entirely?
So since i'm not sure, i'm thinking about adding a callback.
Here the idea :
$rootScope.$broadcast('event:apiRequested', function(response){
if(response){
// Get Users around
return $http.post(domain+'/api/users?access_token='+access_token.key, data).then(function(response){
return response;
});
}
});
Please let me know if it is possible to do that or should i use something else than event listener for that case.
Why don't you use interceptors that is done to intercept HTTP request ?
In your case, you shall add this very specific behaviour into the "request" part.
See an interceptor exemple bellow:
var $myService; // Add a constant that store the service
$httpProvider.interceptors.push(['$location', '$injector', '$q', function($location, $injector, $q) {
return {
'request' : function(config){
console.log("intercept request", config.url,config)
// Your token shall be retreive in this part
return config
},
'response' : function(config){
$myService= $myService|| $injector.get('$myService'); // inject the service manually if constant is undefined
console.log("intercept response", config)
// Your token shall be retreive in this part
return config
},
'responseError': function(rejection) {
console.log("responseError intercepted" , rejection);
if (rejection.status === 403) {
return $q.reject(rejection);
} else if (rejection.status === 423) {
return $q.reject(rejection);
}else
return $q.reject(rejection);
}
};
}]);
Interceptors shall be defined into .config(["$httpProvider", function($httpProvider)

AngularJS ngResource $save feedback

Hello I am using ngResource $save method and I get two different behaviours, I don't understand why
First I'm using it in this way:
$scope.user = new User($scope.user);
$scope.user.$save(function () {
$window.location.href = //redirection here;
}, function (response) {
$scope.form.addErrors(response.data.errors);
});
Then I have another controller when I'm doing a similar operation, but even getting 404 or 422 errors from the server the first callback is executed and the errors callback is ignored.
Does anyone have any idea of this? I've been searching in Google for hours trying to find more documentation about $save but I'm still stuck with this problem.
Thank you.
Well, the problem was on an interceptor I am using to detect 401 (unauthorized errors)
here is the interceptor, notice that you must return $q.reject(response) otherwise the other callbacks are not called (in my case the error callback in ngResource.$save)
MyApp.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($window, $q) {
return {
'responseError': function(response) {
if (response.status == 401) { // Unathorized
$window.location.href = 'index.html';
}
// return response; <-- I was doing this before cancelling all errors
return $q.reject(response);
}
};
});
});

AngularJs Global Message Service and Authentication

I have a message system in place that has two methods show and clear. I want to use $http interceptors in order to handle authentication because my server is smart enough to know whether a user is authenticated or not per request and will tell me so by sending me me a status response of 401 or 403. So, I have something like this for my interceptor:
myModule.factory('statusInterceptor', function($location, MessageService, urlManager){
return {
responseError: function(response){
switch(response.status){
...
case 401:
case 403:
$location.url(urlManager.reverse('Login'));
MessageService.show('Dude, you need to log in first.');
break;
case 500:
break;
...
}
return response;
}
};
});
That works just fine on either a 401 or a 403 response as it shows the message as expected. The problem I'm having is clearing the message whenever the user logs in or goes to another route. I have a MainCtrl that is in charge of almost everything and one of the things that it is looking after is $routeChangeStart. My thought being that the view is changing to a different view, so I want to clear the messages at the beginning of the route switch. So, in my MainCtrl I have:
myControllers.controller('MainCtrl', function($scope, MessageService){
$scope.$on('$routeChangeStart', function(){
MessageService.clear();
});
});
Here's the way I expect the app to react:
A user has tried to do something without being authenticated by sending a request to the server
The server responds with either a 401 or 403 because the user is not authenticated.
The responseError function is fired calling my $location.url() method which, in my mind, should fire the $routeChangeStart, or even $locationChangeStart, events (I've tried both).
This should in turn fire my MessageService.clear() method and clear out any previous message.
The user is finally redirected to the login page with the correct "Dude, you need to log in first." message.
What really happens:
The user is redirected to the login page as expected, however, the error message does not display. When setting certain debug points in Chrome, I see that $location.path() is called immediately followed by MessageService.show('Dude...'), finally followed by the MessageService.clear() call.
Maybe you like this approach to that problem
http://jsfiddle.net/jYCPQ/1/
module.factory('authenticationInterceptor', function ($q, navigator, MessageService) {
return {
responseError: function (response) {
switch (response.status) {
case 403:
navigator.navigateTo("/login").then(function () {
MessageService.show('Dude, you need to log in first.');
});
return $q.reject(response);
}
return response;
}
};
});
module.factory("navigator", function ($location, $rootScope, $q) {
var navigateTo = function (url) {
var defered = $q.defer();
$location.url(url);
var unbind = $rootScope.$on('$routeChangeSuccess', function () {
defered.resolve();
unbind();
})
return defered.promise;
}
return {
navigateTo: navigateTo
}
});
regards
I finally found a solution and ended up with something like this:
In my interceptor function:
myModule.factory('statusInterceptor', function($rootScope){
return {
responseError: function(response){
switch(response.status){
...
case 401:
case 403:
$rootScope.$broadcast('event:notAuthenticated');
break;
case 500:
break;
...
}
return response;
}
};
});
And in my controller:
myControllers.controller('MainCtrl', function($scope, $location, MessageService){
$scope.$on('event:notAuthenticated', function(){
$location.path(urlManager.reverse('Login'));
var remove_this_binding = $scope.$on('$routeChangeSuccess', function(){
messageService.show('Dude, you need to log in first.');
remove_this_binding();
});
});
$scope.$on('$routeChangeStart', function(){
messageService.clear();
});
});
I was trying to find an angular way of having a callback associated with $location's path change and this is the best I could come up with. If anyone else has a better solution, I'm all ears.

Resources