Service call in angular request interceptor causes error - angularjs

I want to use request interceptor in my app to add verify code into requests (for CSRF protection). My code looks like this now:
$httpProvider.interceptors.push(function($q, $injector, AppConst) {
return {
request: function(request) {
var VerifyCodeService = $injector.get('VerifyCodeService');
var verifyCodeUrl = AppConst.apiUrl + '/app/verifyCode'
if(request.url!=verifyCodeUrl && request.data!=undefined){
VerifyCodeService.getCode()
.then(function(data) {
if (AppConst.serviceResponseOk==data.result) {
request.data.verifyCode = data.verifyCode;
return request;
} else {
console.log('error');
return request;
}
}, function(error) {
console.log('error:' + error);
return request;
});
} else {
return request;
}
}
};
});
But for some reason I keep getting this error:
TypeError: Cannot read property 'headers' of undefined
at serverRequest (angular.js:10028)
at processQueue (angular.js:14567)
at angular.js:14583
at Scope.$eval (angular.js:15846)
at Scope.$digest (angular.js:15657)
at Scope.$apply (angular.js:15951)
at done (angular.js:10364)
at completeRequest (angular.js:10536)
at XMLHttpRequest.requestLoaded (angular.js:10477)
Anyone knows whats happening?

If I read this right, you are making a XHR which triggers another XHR to get the CSRF token and add it to the original request's data object. I think the issue is the second XHR. This second request is async just like the first, so you can't just return from the then method.
Try returning the promise generated by your second XHR in your if statement (which is resolved in the inner then invocation after the second XHR by return request;).
if(request.url!=verifyCodeUrl && request.data!=undefined){
var secondXhr = VerifyCodeService.getCode()
.then(function(data) {
if (AppConst.serviceResponseOk==data.result) {
request.data.verifyCode = data.verifyCode;
return request;
} else {
console.log('error');
return request;
}
}, function(error) {
console.log('error:' + error);
return request;
});
return secondXhr;
} else {
return request;
}
From the Interceptors section of the Angular docs (bolding added):
request: interceptors get called with a http config object. The
function is free to modify the config object or create a new one. The
function needs to return the config object directly, or a promise
containing the config or a new config object.
On a different note, it looks like you are introducing a significant delay into each XHR since you are essentially blocking each request until the token is returned.
A different implementation where each response returns the new token for the next request in a header could work (as long as you don't have concurrent requests(!)), or CSRF token per page load (if you have full page reloads) might be options. I'm sure there are suggestions on the internets if you want to make that change.

Related

AngularJs http request priorities and http interceptors

I have been looking at an application I made a while back and there is a particular page where the details are being loaded last. Because of this, it seems to be queuing the request (there are more than 6 others before it) and that is causing the page to be slow.
I figured I could find a solution to prioritize these requests and I found this:
How to prioritize requests in angular $http service?
So I created my version of it and added it to my interceptors:
// Add our auth interceptor to handle authenticated requests
$httpProvider.interceptors.push('authInterceptor');
$httpProvider.interceptors.push('httpPriorityInterceptor');
The interceptor looks like this:
function factory($injector, $q) {
var requestStack = [], // request stack
$http = null; // http service to be lazy loaded
return {
request: request,
responseError: responseError
};
//////////////////////////////////////////////////
function request(config) {
// Lazy load $http service
if (!$http) {
$http = $injector.get('$http');
}
if (!config.hasBeenRequested) {
config.hasBeenRequested = true;
config.priority = config.priority || 3;
console.log(config);
// add a copy of the configuration
// to prevent it from copying the timeout property
requestStack.push(angular.copy(config));
// sort each configuration by priority
requestStack = requestStack.sort(sort);
// cancel request by adding a resolved promise
config.timeout = $q.when();
}
// return config
return config;
}
function responseError(rejection) {
// check if there are requests to be processed
if (requestStack.length > 0) {
requestStack.reduceRight(function(promise, config) {
return promise.finally(function() {
return $http(config);
});
}, $q.when());
requestStack.length = 0;
}
// return rejected request
return $q.reject(rejection);
}
//////////////////////////////////////////////////
function sort(config1, config2) {
return config1.priority < config2.priority;
}
}
The problem is, it seems to be intercepting template requests too. I have no issue with that, but they are not resolving. Instead I get a lot of errors:
Error: [$templateRequest:tpload] Failed to load template: app/accounts/accounts.html (HTTP status: -1 )
Has anyone encountered this before? Is there something I can do to fix this?
you should know that every request such as html files , css file and ... comes into interceptor.
in your case you dont need to prioritize this files. so you can filter your request like:
if (config.url.toString().toLowerCase().includes("api")) {
//place your functionality
}

Cache API calls using AngularJS interceptor

Indent is to cache API calls by intercepting request and response using AngularJS interceptor. Below is my current code. I don't know what to do if the request exist in the cache. Is this feasible? Is this the right way to do it?
app.factory('apiCacheMiddleware', apiCacheMiddleware);
function apiCacheMiddleware($cacheFactory) {
var cache = $cacheFactory('apiCache');
var interceptor = {
request: function(config) {
console.log(config)
if(config.method === 'GET' && cache.get(config.url)){
// What to return from here???
}
return config;
},
response: function(response) {
console.log(response);
if(response.config.method === 'GET'){
cache.put(response.config.url, response.data);
}
return response;
}
};
return interceptor;
};
What you need to do (if you still happen to need it) is to assign the cache you created with $cacheFactory to the request (config.cache = cache; – it will probably be useful to check the previous value and take some decision upon it). On the response then, you will fill in that cache as you already do, and AngularJS will take care of the rest.

Angular : intercept specific request with $resource

I'm new to Angular, and am working on an interceptor. I created an angular factory to get some data from an API like that :
app.factory('Connection',['$resource',function($resource) {
return $resource('url',{param1: '1',param2: '55'},);
}]);
I also created the interceptor which looks like that :
app.factory('connectionInterceptor', function($q,$location) {
var connectionInterceptor = {
response: // code here
responseError: // code here
};
return connectionInterceptor;
});
The interceptor works well. But it intercepts every http request I do, and I'd like to make it work for a specific $resource. I read in angular $resource doc that there is a way to make it by adding an interceptor action/param to $resource. So I tried :
app.factory('Connection',['$resource',function($resource) {
return $resource('http://localhost:8080/api/login',{user: '1',password: '55'}, {},
query: {
method : 'GET',
interceptor : 'connectionInterceptor'
}
});
}]);
which didn't work. The thrown error is : Error in resource configuration for action query. Expected response to contain an object but got an array.
What did I miss ?
As you said, interceptors are globally set. I had to add a test to my response to check the $resource URL and add some specific treatment.
module.factory('interceptor', function() {
var interceptor = {
response: function(response) {
if (response.config.url.startsWith('my url')) {
// some treatment
}
else
// other treatment
return response;
}
return connectionInterceptor;
});

Can a request interceptor create a http response in angularJS

I am in process of creating Offline mode in an app. Whenever a http request is send, I would want an interceptor to detect the network state. If the state is no connectivity, I would want to create a mock response and make it feel like as if the response is coming from a server.
You could check if you are online or not by reading the status of the response in your interceptor, if its a 401 / 501/ etc:
var interceptor = ['$rootScope', '$q', function ($rootScope, $q) {
function success(response) {
return response;
}
function error(response) {
var status = response.status; // error code
if ((status >= 400) && (status < 500)) {
$rootScope.broadcast("AuthError", status);
return;
}
if ((status >= 500) && (status < 600)) {
$rootScope.broadcast("ServerError", status);
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}]
There's another way, using html5, but I guess will not work on some browsers. This is done using navigator.onLine property, like:
if (navigator.onLine) {
//I'm online
} else {
I'm not online
}
(https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine)
If you throw an object inside your request interceptor this will trigger a call to the responseError interceptor, passing the throwed object as its argument.
You have to find a way notify responseError interceptor that this is not a real error and that it should return a custom response.
The responseError interceptor can choose to recover error returning a response object o fail returning a rejected promise (check angular's $http interceptor docs)
EDIT: If you throw an object from request interceptor you can't avoid an error message in console. It's better to return a rejected promise passing config object as value and put extra information in it for responseError detect the special situation.

Cancelling a request with a $http interceptor?

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);
}
};
}

Resources