In AngularJS response error interceptors, is there any way to retrieve the scope which the current request is originated?
module.controller('myController', function($http, $scope) {
$scope.getData = function() {
// This scope is which the request is originated
$http.get('http://www.example.com/get', {
params: { 'id': '1234' }
}).success(function(data, status, headers, config) {
// ...
}).error(function(data, status, headers, config) {
// ...
});
}
});
module.factory('myInterceptor', function($q) {
return {
'responseError': function(response) {
// How can I get the scope of 'myController' here?
return $q.reject(response);
}
};
});
I don't think you can access originating scope from factory out of the box.
Factories are singletons and it is not recommended to play with scope in it.
Nevertheless, I believe you can pass the originating scope as part of param object from your controller.
params: { 'id': '1234', 'origScope': $scope }
update
after following discussion...
I think instead of accessing the scope here.
You publish the event from interceptor and have scope or associated controller listen it.
Refer this link for more information: http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Related
I have the follwing code
cy.intercept({
method: "GET",
url: `**/features`
}).as("getFeatures");
cy.wait('#getFeatures').then(({response}) => {
if(response?.statusCode ===200){
expect(response.body.features).to.exist;
expect(response.body.features).to.contain('feature01');
// hier I would like to return the response.body oder response.body.features as an object to interact with the ui based on its value (wether the feature is activated or not)
}
})
Saving the body into a file is not an option because the tests will run in paralel and different tests may access invalid values of feature endpoint
EDIT: because I need this method more than once. I want to return the value of Body as an object the solution I am looking for is like
function visitUrl() : Object {
//intercept
return body
}
You can use alias and save the response body and use it later on.
cy.intercept({
method: 'GET',
url: `**/features`,
}).as('getFeatures')
cy.wait('#getFeatures').then(({response}) => {
if (response?.statusCode === 200) {
expect(response.body.features).to.exist
expect(response.body.features).to.contain('feature01')
cy.wrap(response.body.features).as('responseBodyFeatures')
}
})
cy.get('#responseBodyFeatures').then((features) => {
cy.log(features) //logs the features section from response body
})
I am trying to figure out how to prevent a cors error from showing up in developer tools. The way I get the cors error is when I am using an application but in another tab/window I log out of that application but then go back to the other tab and try to do work. Below is my ajax call.
function RemoveScholarshipRequest(id, name) {
if (confirm("Are you sure you want to delete the scholarship request for " + name + "?")) {
var dataSource = $('#Pending').data('kendoGrid').dataSource;
$.ajax({
type: "POST",
url: '#Url.Action("RemoveRequest", "Admin")',
data: {id: id}
}).done(function (response, data, xhr) {
if (response.success) {
dataSource.read();
alert(response.responseText);
}
else if (!response.success) {
if (response.responseText === "Not Authenticated")
alert(response.responseText);
console.log("error", data.status);
//This shows status message eg. Forbidden
console.log("STATUS: "+JSON.stringify(xhr.status));
}
}).fail(function (response) {
console.log(response);
console.log(JSON.stringify(response));
//window.location.href = "/forms/ScholarshipDisbursement/Admin/PendingRequests";
});
}
}
The controller action that the above ajax method calls is below:
[AllowAnonymous]
[HttpPost]
public ActionResult RemoveRequest(string id)
{
if (!User.Identity.IsAuthenticated)
{
return Json(new { success = false, responseText = "Not Authenticated" }, JsonRequestBehavior.AllowGet);
}
if (User.IsInRole("Developer") || User.IsInRole("BannerAdmin"))
{
new ScholarshipRequestStore().DeleteScholarshipRequest(id);
return Json(new { success = true, responseText = "Successfully deleted" }, JsonRequestBehavior.AllowGet);
}
else
{
return Json(new { success = false, responseText = "You are not an authorized user" }, JsonRequestBehavior.AllowGet);
}
}
One way I get around the cors error is by putting AllowAnonymous on the method and then checking for authentication in the method itself but I don't really like that idea. Is there another way of resolving this issue?
Allow anonymous will not solve this, instead you need to send the allow origin header in your api. You can do this by enabling CORs in the startup class as follows
public void ConfigureServices(IServiceCollection services)
{
// Add Cors
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy"));
});
...
...
...
}
// This method gets called by the runtime. Use this method to configure
//the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Enable Cors
app.UseCors("MyPolicy");
//app.UseMvcWithDefaultRoute();
app.UseMvc();
...
...
...
}
and then using the "Enable cors" attribute on your controller
[EnableCors("MyPolicy")]
[AllowAnonymous]
[HttpPost]
public ActionResult RemoveRequest(string id)
read this for better idea https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.2
Note: I have allowed any origin to talk to the API, you can specify whatever origin you want like "https://example.com"
AllowAnonymous won't resolve a "cross-origin" request. The issue you are getting is due to tabbed browsing within your browser having a shared store of authenticated sessions. When you log out in tab 1, the session cookie is removed and then tab 2 is no longer authenticated. This is why AllowAnonymous "works" because without a current authenticated session, you're an anonymous user.
CORS, on the other hand, is when you allow calls to http://myservice.com to come from a different host like http://myclient.com. Anonymous access won't have any impact on that.
I am using React, React-Router and Superagent. I need authorization feature in my web application. Now, if the token is expired, I need the page redirect to login page.
I have put the ajax call functionality in a separated module and the token will be send on each request's header. In one of my component, I need fetch some data via ajax call, like below.
componentDidMount: function() {
api.getOne(this.props.params.id, function(err, data) {
if (err) {
this.setErrorMessage('System Error!');
} else if (this.isMounted()) {
this.setState({
user: data
});
}
}.bind(this));
},
If I got 401 (Unauthorized) error, maybe caused by token expired or no enough privilege, the page should be redirected to login page. Right now, in my api module, I have to use window.loication="#/login" I don't think this is a good idea.
var endCallback = function(cb, err, res) {
if (err && err.status == 401) {
return window.location('#/login');
}
if (res) {
cb(err, res.body);
} else {
cb(err);
}
};
get: function(cb) {
request
.get(BASE_URL + resources)
.end(endCallback.bind(null, cb));
},
But, I can't easily, call the react-router method in my api module. Is there an elegant way to implemented this easy feature? I don't want to add an error callback in every react components which need authorized.
I would try something like this:
Use the component's context to manipulate the router (this.context.router.transitionTo()).
Pass this method to the API callout as a param.
// component.js
,contextTypes: {
router: React.PropTypes.func
},
componentDidMount: function() {
api.getOne(this.props.params.id, this.context.router, function(err, data) {
if (err) {
this.setErrorMessage('System Error!');
} else if (this.isMounted()) {
this.setState({
user: data
});
}
}.bind(this));
},
// api.js
var endCallback = function(cb, router, err, res) {
if (err && err.status == 401) {
return router.transitionTo('login');
}
if (res) {
cb(err, res.body);
} else {
cb(err);
}
};
get: function(cb, router) {
request
.get(BASE_URL + resources)
.end(endCallback.bind(null, cb, router));
},
I know you didn't want a callback on each authenticated component, but I don't think there's any special react-router shortcuts to transition outside of the router.
The only other thing I could think of would be to spin up a brand new router on the error and manually send it to the login route. But I don't think that would necessarily work since it's outside of the initial render method.
I want to pass in the headers my token each time i make a request. the way i do it now is using:
$http.defaults.headers.common['auth_token'] = $localStorage.token;
How could i do that to make that sent to every request, and when it throws an error it should do a
$state.go('login')
If you want to add your token to each request, and respond to any errors, your best bet would be to use an Angular HTTP interceptor.
Subject to your needs, it might look something like this:
$httpProvider.interceptors.push(function ($q, $state, $localStorage) {
return {
// Add an interceptor for requests.
'request': function (config) {
config.headers = config.headers || {}; // Default to an empty object if no headers are set.
// Set the header if the token is stored.
if($localStorage.token) {
config.headers.common['auth_token'] = $localStorage.token;
}
return config;
},
// Add an interceptor for any responses that error.
'responseError': function(response) {
// Check if the error is auth-related.
if(response.status === 401 || response.status === 403) {
$state.go('login');
}
return $q.reject(response);
}
};
});
Hope this helps.
I still don't understand how the Promise API works. I want to know if there's a way to get a data whenever I need it without calling multiple HTTP request. Here's an exemple :
Session Service :
All it does is either get the session object (which contains datas) or get session ID which returns a number.
app.factory('sessionFactory', ['$resource', 'requestFactory',
function ($resource, requestFactory) {
var oSession = {};
var session = {
/**
* Get session ID
* #return {Number}
*/
sessionID: function () {
if (typeof oSession.id !== "undefined") {
return oSession.id;
} else {
return requestFactory.getObject('/application/current_session').then(function (response) {
oSession = response;
return oSession.id;
});
}
},
/**
* Get session object (GET)
* #return {Object} data in JSON format
*/
getCurrentSession: function () {
if (!oSession.id) {
return requestFactory.getObject('/application/current_session').then(function (response) {
oSession = response;
return oSession;
});
}
}
};
return session;
}]);
Request HTTP Service :
This service only does HTTP request.
app.factory('requestFactory', ['$http', '$q', '$timeout',
function ($http, $q, $timeout) {
return {
getObject: function (jsonURL, params) {
// $q service object
var deferred = $q.defer();
// regular ajax request
$http({
method: 'GET',
url: jsonURL,
params: params
})
.success(function (result, status, headers, config) {
// promise resolve
deferred.resolve(result);
})
.error(function (result, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject('Erreur request : ' + status);
});
return deferred.promise;
}
};
}]);
So to get my Session Object, I do sessionFactory.getCurrentSession with callback function(then...) and it works perfect. Later on, I only need to get the session ID so I would do sessionFactory.sessionID, but it works only if I add the callback function (then...), why is that? I thought my global JavaScript object oSession already has data since the first HTTP request.
I want to prevent doing a spaghetti code and keep the code as clean as possible, with a more object approach. Is it possible?
It looks like you're trying to do too much with the promise API. It's already built into the $http service so you shouldn't need to invoke it yourself. Try this instead:
app.factory('requestFactory', ['$http',
function ($http) {
return {
getObject: function (jsonURL, params) {
// regular ajax request
return $http({
method: 'GET',
url: jsonURL,
params: params
})
.success(function (result, status, headers, config) {
return result;
})
.error(function (result, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
throw new Error('Erreur request : ' + status);
});
}
};
}]);
By returning the result of the $http call, you are in fact returning a promise. You can then chain additional resolution code onto the return value. See the "Chaining Promises" section of the $q documentation.
IF you want to cache your previous http response so that it will not make an http call again
you can use angular-cached-resource
bower install angular-cached-resource
angular.module('myApp',['ngCachedResource'])
instead of $resource use $cachedResource, it will cache the network call to local storage, every time you make a call it will resolve immediately even though it makes a call to backend and updated the cache.
you can also use angular-cache it will cache all your http get calls you can set timeout as well exclude url in its configuration
bower install angular-cache