How can I handle 301 redirects in a service worker? - character-encoding

On my Persian language website, there are some problems in the handling of 301 redirects when the service worker is fetching some requests with Persian/Farsi characters.
As regular, when a user enters a keyword, as for example رامسر (Ramsar as English) submits an Ajax search form, a request as below sent to the server (Apache/Laravel):
https://www.example.com/s?search=%D8%B1%D8%A7%D9%85%D8%B3%D8%B1&gstnum=1
Note that %D8%B1%D8%A7%D9%85%D8%B3%D8%B1 is Unicode of رامسر.
So, at back-end this request redirects to another URL (301 Redirecting):
https://www.example.com/s/ramsar/%D8%A7%D8%AC%D8%A7%D8%B1%D9%87-%D9%88%DB%8C%D9%84%D8%A7-%D9%88-%D8%B3%D9%88%D8%A6%DB%8C%D8%AA-%D8%AF%D8%B1-%D8%B1%D8%A7%D9%85%D8%B3%D8%B1?gstnum=
The above is the encoding type of this URL:
https://www.example.com/s/ramsar/اجاره-ویلا-و-سوئیت-در-رامسر
But, when service-worker is running, it cannot set the correct character setting of respond URL, it returns:
https://www.example.com/s/ramsar/اجارÙ-ÙÛÙا-Ù-سÙئÛت-در-راÙسر
This is my code for the fetch method of service-worker:
self.addEventListener("fetch", function (event) {
if (event.request.method === "GET" && event.request.mode === "navigate") {
event.respondWith(async function () {
try {
var networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
...
}
}());
}
});
What can I do?

Related

Empty request body in onFetch method of serviceworker

I am trying to make my website work offline, and while offline any POST request should be saved to a local database. Currently I have this code in my serviceworker to catch any requests:
self.onfetch = (event) => {
switch (event.request.method) {
case 'GET':
return onGet(event);
case 'POST':
return onPost(event);
}
};
async function onPost(event) {
if (navigator.onLine){
return;
}
event.request.clone().formData().then((data) => {
console.log(data);
})
}
I am trying to read out the data from the request, but the console.log returns an empty object. When looking at the failed request in DevTools the FormData is available. Is it not possible to read out the body when intercepting the request?

Ignore ajax requests in service worker

I have an app with a basic 'shell' of HTML, CSS and JS. The main content of the page is loaded via multiple ajax calls to an API that is at another URL to the one my app is running on. I have set up a service-worker to cache the main 'shell' of the application:
var urlsToCache = [
'/',
'styles/main.css',
'scripts/app.js',
'scripts/apiService.js',
'third_party/handlebars.min.js',
'third_party/handlebars-intl.min.js'
];
and to respond with the cached version when requested. The problem I am having is that the response of my ajax calls are also being cached. I'm pretty sure that I need to add some code to the fetch event of the service-worker that always get them from the network rather than looking in the cache.
Here is my fetch event:
self.addEventListener('fetch', function (event) {
// ignore anything other than GET requests
var request = event.request;
if (request.method !== 'GET') {
event.respondWith(fetch(request));
return;
}
// handle other requests
event.respondWith(
caches.open(CACHE_NAME).then(function (cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function (response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
I'm not sure how I can ignore the requests to the API. I've tried doing this:
if (request.url.indexOf(myAPIUrl !== -1) {
event.respondWith(fetch(request));
return;
}
but according to the network tab in Chrome Dev Tools, all of these responses are still coming from the service-worker.
You do not have to use event.respondWith(fetch(request)) to handle requests that you want to ignore. If you return without calling event.respondWith browser will fetch the resource for you.
You can do something like:
if (request.method !== 'GET') { return; }
if (request.url.indexOf(myAPIUrl) !== -1) { return; }
\\ handle all other requests
event.respondWith(/* return promise here */);
IOW as long as you can determine synchronously that you don't want to handle the request you can just return from the handler and let the default request processing to take over. Check out this example.

How to make AngularJS throw error instead of silently failing when template cannot be retrieved? [duplicate]

In an AngularJS directive the templateUrl parameter is defined dinamically.
'templates/' + content_id + '.html'
I don't want to establish rules to check if content_id value is valid and manage it as 404 errors, i.e. if the template doesn't exist (server return a 404 error when loading the template) load template/404.html instead.
How can I do that?
Edited: The current answers suggest to use a response error interceptor. In this case ¿how can I know that the response is to a loading of this template?
You will need to write response error interceptor. Something like this:
app.factory('template404Interceptor', function($injector) {
return {
responseError: function(response) {
if (response.status === 404 && /\.html$/.test(response.config.url)) {
response.config.url = '404.html';
return $injector.get('$http')(response.config);
}
return $q.reject(response);
}
};
});
app.config(function($httpProvider) {
$httpProvider.interceptors.push('template404Interceptor');
});
Demo: http://plnkr.co/edit/uCpnT5n0PkWO53PVQmvR?p=preview
You can create an interceptor to monitor all requests made with the $http service and intercept any response errors. If you get a status 404 for any request made, simply redirect the user to error page(template/404.html in your case).
.factory('httpRequestInterceptor', function ($q) {
return {
'responseError': function(rejection) {
if(rejection.status === 404){
// do something on error
}
}
return $q.reject(rejection);
}
};
});
You would need to push the interceptor to $httpProvider in your config function.
myApp.config( function ($httpProvider, $interpolateProvider, $routeProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
});
Here's the demo
Cheers!

$http error handling in AngularJS

$http in my AngularJS project not able to recognize 40X(401,403,405...) errors on iOS.
I am using 1.2.10 AngularJS version and Cordova version 3.4.0.
Below is the code I am using:
TE_SERVICES.factory('hello',function ($http,$rootScope) {
return {
loginUser: function(userCredentials,successCallback,errorCallback){
$http({
method: "GET",
url: "data/example.json",
headers: {"Authorization":'Basic '+userCredentials},
}).then(function(response){
successCallback(response.data);
console.log("Success------"+JSON.stringify(response))
},function(data, status, headers, config){
errorCallback(data);
console.log("Error------"+JSON.stringify(data)+" "+status)
})
}
}
});
hello.loginUser($rootScope.encodedUserCredencials,function(persons) {
// success handler
}, function(data) {
// error handler
console.log(data.status+"===="+status)
});
data.status is returning 0 and status returns undefined.
Please help me to resolve this issue.
Tried to include the domain in whitelist on IOS.But no solution :( It still gives the same response.
But the same code works absolutely fine in Android.
Please help me to resolve this issue.
Thanks in advance :)
So you r using the $http from angular. Do you use the error callback or the second function in the then callback ?
Example
$http.get("someUrl")
.success(function(response){}) // if http code == 200
.error(function(response){}) // else
Or with then, that can take 2 functions. The first is the onSuccess, the second the onError function.
$http.get("someUrl")
.then(function(response){
// if http code == 200
},
function(response){
// else
});
The response parameter does also contain the error codes.
Consider using a $httpInterceptor to handle all errorcodes at the same place, instead handling them in every http callback.
UPDATE:
It seems, that the angular doc is incomplete/wrong for the success callback.
It doesnt pass 4 parameter there. It does pass a response object that contains all the information about request+response and the passed data.
Update to the edit:
Dont write callbacks by yourself. Use angular promises:
TE_SERVICES.factory('hello',function ($http,$rootScope) {
return {
loginUser: function(userCredentials){
return $http({
method: "GET",
url: "data/example.json",
headers: {"Authorization":'Basic '+userCredentials},
}).then(function(response){
return response.data;
},function(response){
return response;
});
}
}
});
hello.loginUser($rootScope.encodedUserCredencials)
.then(function(persons) { // success handler
}, function(data) { // error handler
console.log(data);
});
Try this and tell me if the console.log logs something.
I had exactly the same problem. Cordova app, angular js, IPhone and 401 requests are not received in angular js http interceptor. They work fine on android devices.
My finding was that IPhone browser is handling those at a higher lever and trying to use WWW-Authenticate information to do authentication. This is why the response does not get to angular.
The only solution I found, was to change my service to return 400 instead of 401 in case of an api request. In this case I return 400 with an error message that I handle on client side.
I hope this helps.
My issue with the 403 status code was that my backend returned a response with status 403 but the body of a response did not contain a JSON string. It contained just a string - Authentication Failed.
The rejection variable was an object Error.
I encoded the body and the rejection variable contains a valid response error object.
To handle HTTP errors I use interceptors.
$httpProvider.interceptors.push(function($q, $location, redirect, HTTP_CODES) {
return {
'responseError': function(rejection) {
if (rejection.status === HTTP_CODES.FORBIDDEN) {
redirect('/login', $location.url());
}
return $q.reject(rejection);
}
};
});

how to secure or set a rails style before_filter for all angular controllers?

I'm using angularjs for the front end and rails + devise for authentication on the backend.
On the front end I have added a responseInterceptor to redirect to the /#/sign_in page upon any 401 response from any xhr request and display a growl style pop-up message using toastr.
App.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('securityInterceptor');
}]);
App.factory('securityInterceptor', ['$injector', '$location', '$cookieStore', function ($injector,$location,$cookieStore) {
return function(promise) {
var $http = $injector.get('$http');
return promise.then(null, function(response){
if (response.status === 401) {
$cookieStore.remove('_angular_devise_user');
toastr.warning('You are logged out');
$location.path('/#/sign_in');
}
});
};
});
My problem is, when I click on a page that loads several xhr requests during the controllers initialization, for example:
var products = Product.query();
var categories = Category.query();
var variations = Variation.query();
These are needed for various navigation components and they all fire off in parallel, resulting in several duplicate growl-style messages.
Is there a way to make angular quit on the first 401 and stop execution of the rest of the controller from within the interceptor? In a traditional rails app, there would be a "before_filter" that stops regular execution, preventing the page and queries from loading... what's the best way to do this in angular?
I've been pondering about this problem for my own apps too. A sketch of my thoughts (NOT REAL IMPLEMENTATION, SO BEWARE):
A userData service keeps track of whether the user is logged in + other information (e.g. user name, real user name etc):
App.service("userData", function() {
var currentData = {
loggedIn: false
};
function getCurrent() {
return currentData;
}
// called when the user logs in with the user data
function loggedIn(userData) {
// the object is REPLACED to avoid race conditions, see explanation below
currentData = angular.extend({loggedIn: true}, userData);
}
return {
getCurrent: getCurrent,
loggedIn: loggedIn
};
});
The interceptors keep track of the currentData. If an interceptor receives HTTP 401 and the loggedIn flag is true, it changes the flag to false and redirects to the login view. If an interceptor receives HTTP 401 and the loggedIn flag is false, it does nothing besides rejecting the request, because another interceptor has done the view redirection.
When the user logs in, the currentData is replaced, so as to avoid situations with delayed responses (e.g. call1 and call2 are initiated, call1 responds 401; call2 also results in 401, but the delivery of the actual response is delayed; then the user logs in again; then call2 receives its 401; the second 401 should not overwrite the current state)
App.config(["$provide", "$httpProvider", function($provide, $httpProvider) {
$provide.factory("myHttpInterceptor", ["$q", "userData", "$cookieStore", "toastr", "$location",
function($q, userData, $cookieStore, toastr, $location) {
return {
request: function(config) {
config.currentUserData = userData.getCurrent();
return config;
},
responseError: function(rejection) {
if( rejection && rejection.status === 401 && rejection.config && rejection.config.currentUserData && rejection.config.currentUserData.loggedIn ) {
rejection.config.currentUserData.loggedIn = false;
$cookieStore.remove('_angular_devise_user');
toastr.warning('You are logged out');
$location.path('/#/sign_in');
}
return $q.reject(rejection);
}
};
}
]);
$httpProvider.interceptors.push("myHttpInterceptor");
});
Also note I am using the newer way to register interceptors, as $httpProvider.responseInterceptors seems to be deprecated.

Resources