How to set Ionic's LocalStorage as synchronization? - ionic-framework

Here's what I want: When I send a net request, hoping the intercept can add headers automatically.
So I made an intercept to realize the goal. I plan to get value by LocalStorage. Howerer the way of it's executive is asynchronous, so the request can't catch up with the storage.
What should I do next?
My code as belows:
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
console.log('into intercept');
let url = req.url;
let opuser;
this.storage.get('userInfo').then((val) => {
opuser = val.Noid; // get the value what I need
});
if (opuser === undefined) {
opuser = 'undefined';
}
const newReq = req.clone({
url: url
}).clone({
setHeaders: { opuser: 'undefined' }
});// set the headers
return next.handle(newReq).pipe(
mergeMap((event: any) => {
return of(event);
})
);
}

You should prepare your header before the interceptor will be called.
Good option:
When your application starts, get the header and store it in a provider. This way you will have synchronous/direct access to it.
opUserHeader: any;
setOpuserHeader(){
return this.storage.get('userInfo').then((val) => {
this.opUserHeader = val.Noid; // get the value what I need
});
}
getOpuserHeader(){
return this.opUserHeader ? this.opUserHeader : 'undefined';
}
and when you start the app, you can call the method from your service in app.component.ts or in the first page(your choice), and your header value will exist in memory:
headersService.setOpuserHeader().then(() => { console.log('Header is set')};
now the interceptor should look very clean and you can directly get the value:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Get the header from service, attach to the cloned request and continue
const opUserHeader = this.headersService.getOpuserHeader();
const authReq = req.clone({setHeaders: {opuser: opUserHeader}});
return next.handle(authReq);
}
Bad Option:
You can put your existing code in the localStorage promise and get the value from it for all requests but performance will be affected.
Edit for Part 2:
You don't really need the address inside the interceptor function but you should call the POST method with the correct qualifier.
A complex option can be to override the HttpClient and concatenate your address to all the methods(if you don't change the endpoint) or the easiest and most sustainable in your case would be to save the address in a service and require it everytime you need like so:
apiURL = 'http://120.XXX.XXX.XXX:81/';
getApiURL(){
return this.apiURL;
}
and everytime you call the HttpClient:
const apiURL = myService.getApiURL();
this.httpClient.post(apiURL + 'api/login', params);
Good luck!

Related

check if api endpoint exits before send request - axios

I have axios request in my vue application:
async get_banner(id:number) : Promise<any> {
return global.axios.get(`${process.env.VUE_APP_DOMAIN}/banners/${id}`)
}
it works while banner/${id} response exits, but I have situation when I should disable banner in my admin panel so api endpoint becomes empty. (not exits) so I am getting Request failed with status code 404 in my vue app console.
question is how to prevent error and know if url exits or not? what is best practice to do this?
You can't tell whether an API exists or not without trying it (or relying on another API to get status of the former API)
It's usually just a manner of handling the response properly. Usually this would look something like this...
getTheBanner(id){
this.errorMessage = null; // reset message on request init
get_banner(id)
.then(r => {
// handle success
this.results = r;
})
.catch(e => {
// handle error
if (e.status === 404){
// set error message
this.errorMessage = "Invalid banner Id";
}
})
}
then in your template you could have something like this
<div v-if="errorMessage" class="alert danger">{errorMessage}</div>
Explaination:
Yes, you're absolutely right. This is the default behavior of strapi. Whenever the response is empty it throws a 404 error. This is basically because the findOne method in service returns null to the controller and when the controller sends this to the boom module it returns a 404 Not Found error to the front end.
Solution:
Just override the find one method in the controller to return an empty object {} when the response is null.
Implementation
// Path - yourproject/api/banner/controllers/banner.js
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
/**
* Retrieve a record.
*
* #return {Object}
*/
async findOne(ctx) {
const { id } = ctx.params;
const entity = await strapi.services.restaurant.findOne({ id });
// in case no entity is found, just return emtpy object here.
if(!entity) return {};
return sanitizeEntity(entity, { model: strapi.models.restaurant });
},
};
Side Note:
There's no need to make any changes to the browser side axios implementation. You should always handle such cases in controller rather the client side.
Reference:
Backend Customizations

Javascript injection goes wrong

In our Android project (download manager) we need to show built-in web browser so we able to catch downloads there with the all data (headers, cookies, post data) so we can handle them properly.
Unfortunately, WebView control we use does not provide any way to access POST data of the requests it makes.
So we use a hacky way to get this data. We inject this javascript code in the each html code the browser loads:
<script language="JavaScript">
HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = formSubmitMonitor;
window.addEventListener('submit', function(e) {
formSubmitMonitor(e);
}, true);
function formSubmitMonitor(e) {
var frm = e ? e.target : this;
formSubmitMonitor_onsubmit(frm);
frm._submit();
}
function formSubmitMonitor_onsubmit(f) {
var data = "";
for (i = 0; i < f.elements.length; i++) {
var name = f.elements[i].name;
var value = f.elements[i].value;
//var type = f.elements[i].type;
if (name)
{
if (data !== "")
data += '&';
data += encodeURIComponent(name) + '=' + encodeURIComponent(value);
}
}
postDataMonitor.onBeforeSendPostData(
f.attributes['method'] === undefined ? null : f.attributes['method'].nodeValue,
new URL(f.action, document.baseURI).href,
data,
f.attributes['enctype'] === undefined ? null : f.attributes['enctype'].nodeValue);
}
XMLHttpRequest.prototype.origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
// these will be the key to retrieve the payload
this.recordedMethod = method;
this.recordedUrl = url;
this.origOpen(method, url, async, user, password);
};
XMLHttpRequest.prototype.origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
if (body)
{
postDataMonitor.onBeforeSendPostData(
this.recordedMethod,
this.recordedUrl,
body,
null);
}
this.origSend(body);
};
const origFetch = window.fetch;
window.fetch = function()
{
postDataMonitor.onBeforeSendPostData(
"POST",
"test",
"TEST",
null);
return origFetch.apply(this, arguments);
}
</script>
Generally, it works fine.
But in Google Mail web interface, it's not working for some unknown reason. E.g. when the user enters his login name and presses Next. I thought it's using Fetch API, so I've added interception for it too. But this did not help. Please note, that we do not need to intercept the user credentials, but we need to be able to intercept all, or nothing. Unfortunately, this is the way the whole system works there...
Addition #1.
I've found another way: don't override shouldInterceptRequest, but override onPageStarted instead and call evaluateJavascript there. That way it works even on Google Mail web site! But why the first method is not working then? We break HTML code somehow?

.Net Core: Validate Anti Forgery Token with Ionic front end

I have looked all over and have found similar solutions, but nothing that matches exactly what I'm working on.
We have a .net core MVC website with an API Controller for handling requests from an ionic mobile app which we are also developing.
In most cases, adding [ValidateAntiForgeryToken] to the API controller actions works. I have gone through the process of generating the token, passing it to Ionic, and storing it in the request headers for validation.
Here is the code I am using to fetch and store the token:
static XSRF_TOKEN_KEY: string = "X-XSRF-TOKEN";
static XSRF_TOKEN_NAME_KEY: string = "X-XSRF-TOKEN-NAME";
constructor(){}
static getXsrfToken(http: HTTP) : {tokenName: string, token: string} {
let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
if(!tokenName || !token){
this.fetchXsrfToken(http);
tokenName= window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
token = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
}
return {
tokenName: tokenName,
token: token
};
}
private static setXsrfToken({ token, tokenName }: { token: string, tokenName: string }) {
window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_KEY, token);
window.sessionStorage.setItem(ValidationManager.XSRF_TOKEN_NAME_KEY, tokenName);
}
private static fetchXsrfToken(http: HTTP) {
let token: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_KEY);
let tokenName: string = window.sessionStorage.getItem(ValidationManager.XSRF_TOKEN_NAME_KEY);
if (!token || !tokenName) {
let apiUrl: string = AppConfig.apiUrl + "/GetAntiforgeryToken";
http.get(apiUrl, {}, {})
.then(r => this.setXsrfToken(JSON.parse(r.data)))
.catch(r => console.error("Could not fetch XSRFTOKEN", r));
} else {
this.setXsrfToken({ token: token, tokenName: tokenName });
}
}
Here is the action in my controller that serves anti forgery tokens:
[HttpGet]
public override IActionResult GetAntiforgeryToken()
{
var tokens = _antiforgery.GetAndStoreTokens(HttpContext);
return new ObjectResult(new
{
token = tokens.RequestToken,
tokenName = tokens.HeaderName
});
}
I set the headers of the http plugin by calling this function from the view's associated typescript file:
initializeHttp() {
let token = ValidationManager.getXsrfToken(this.http);
this.http.setHeader(token.tokenName, token.token);
console.log("Http Initialized: ", token);
}
then any request I make with the http plugin is validated properly in the controller's action:
this.http.post(apiUrl, {}, {}).then(response => {
that.navCtrl.setRoot(HomePage);
});
Up to this point, everything works great. The problem arises when I try to use XmlHttpRequest to for a POST instead of the built-in http plugin:
let file = {
name: e.srcElement.files[0].name,
file: e.srcElement.files[0],
};
let formData: FormData = new FormData();
formData.append('file', file.file);
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open('POST', apiUrl, true);
console.log("setting request header: ", tokenVal); //verify that tokenVal is correct
xhr.setRequestHeader("X-XSRF-TOKEN", tokenVal);
xhr.send(formData);
If I remove the [ValidateAntiForgeryToken] attribute from the controller's action, the file is posted properly. However, nothing I have tried has worked with the attribute being included.
I believe the issue has something to do with the validation tokens being added to a cookie automatically by Ionic, and the cookie is passed along with the request from the http plugin. However, XMLHttpRequest does not pass the cookie along (and is unable to do so?).
I have read up on the subject quite a bit over the past few days but I admit that this validation is still mostly a black box to me. Is there a way to validate the request in my action using only the token which is passed up in the header?
The reason I am running into this problem is that I need to upload a file, which I was unable to do using the http plugin. There are solutions for uploading images using Ionic's file-transfer plugin, but it has been deprecated and the release notes suggest using XmlHttpRequest instead.
Other things I have tried:
I have found solutions for .net standard which use System.Web.Helpers.AntiForgery for custom validation on the server, but this namespace is not included in .net core and I could not find an equivalent.
I tried many different ways to post the file using the http plugin (since it has no issues validating the antiForgery token). Everything I tried resulted in the action being hit but the file being posted was always null. A solution which uploads a file using the http plugin would also be acceptable.
Why is it that I was able to spend two full days on this problem, but as soon as I post a question about it, I find the answer? Sometimes I think the internet gods are just messing with me.
As it turns out, the native http plugin has an uploadFile() function that I never saw mentioned anywhere else. Here's what the solution does:
Use the fileChooser plugin to select a file from the phone's storage
Use the filePath plugin to resolve the native filesystem path of the image.
Use http.uploadFile() instead of http.post()
This works because as mentioned above, I was able to properly set the validation token in the http plugin's header to be accepted by the controller.
And here is the code:
let apiUrl: string = AppConfig.apiUrl + "/UploadImage/";
this.fileChooser.open().then(
uri => {
this.filePath.resolveNativePath(uri).then(resolvedPath => {
loader.present();
this.http.uploadFile(apiUrl,{ },{ },resolvedPath, "image")
.then(result => {
loader.dismiss();
toastOptions.message = "File uploaded successfully!";
let toast = this.toastCtrl.create(toastOptions);
toast.present();
let json = JSON.parse(result.data);
this.event.imageUrl = json.imgUrl;
})
.catch(err => {
console.log("error: ", err);
loader.dismiss();
toastOptions.message = "Error uploading file";
let toast = this.toastCtrl.create(toastOptions);
toast.present();
});
});
}
).catch(
e => console.log(e)
);

Ionic 2 - Storage always empty until reloading the app

I am currently building an application with Ionic 2 and using the Storage plugin to hold my values which are pretty much just an API Token and user profile since the application pulls all data from an API.
I am testing the application via ionic serve because no native functions are used but now I am facing the problem that every time I store a value in the Storage the value is not accessible until I reload the app which is kind of annoying because after the user logs in he gets redirected to a page that requires the API token which is not available until I reload the app so the whole thing gets stuck in a loop.
Ionic Storage is using IndexedDB in the browser where I can see that the values have been stored when I check them with Chrome Developer tools.
I have been trying to figure out the issue but can't find any reason why the storage values are not available until reloading the app.
import { Injectable } from '#angular/core';
import { Storage } from '#ionic/storage';
import { HttpClientService } from './http-client-service';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthService {
constructor(public events: Events, public storage: Storage, public http: HttpClientService) {
//
}
login(user) {
var response = this.http.post('login', {
email: user.email,
password: user.password,
});
response.subscribe(data => {
this.storage.set('api_token', data.token);
console.log('raw : ' + data.token); // shows the api token
this.storage.get('api_token').then((value) => {
console.log('storage : '+ value); // is empty...
});
});
return response;
};
}
Edit: I managed to track down the issue to the storage running async which results in the token not being added to the headers.
createAuthorizationHeader(headers: Headers) {
// this does add the header in time
localStorage.setItem('api_token', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vYXBpLndpaHplLmRldi9sb2dpbiIsImlhdCI6MTQ4MTE4MzQyOCwiZXhwIjoxNDgxMTg3MDI4LCJuYmYiOjE0ODExODM0MjgsImp0aSI6IjdlNTE1WUEwWmE4NWc2QjUiLCJzdWIiOiIxIiwidXNlciI6eyJpZCI6MX19.T4KpqgCB8xU79vKyeLG4CJ0OHLpVI0j37JKIBJ_0CC4');
headers.append('Authorization', 'Bearer ' + localStorage.getItem('api_token'));
// this does not add the header in time
return this.storage.get('api_token').then((value) => {
headers.append('Authorization', 'Bearer ' + value);
});
}
getHeaders(path) {
let headers = new Headers();
headers.set('Accept', 'application/json');
headers.set('Content-Type', 'application/json');
if(!this.isGuestRoute(path)) {
this.createAuthorizationHeader(headers);
}
return new RequestOptions({ headers: headers });
}
get(path: string) {
return this._http.get(this.actionUrl + path, this.getHeaders(path))
.map(res => res.json())
.catch(this.handleError);
}
Alright, looked in the ionic docs and I do understand why you put them both underneath eachother since they also display it like that in the docs.
But Storage.set(key, value) :
Returns:
Promise that resolves when the value is set
This means that you cannot use it the way you are using it (hence why they added a comment with //or ....
Since resolving a Promise is asynchronous.
If you want to use the value like you're currently using it (which seems a bit odd but probably for you to test if the value is set correctly) you should use
this.storage.set('api_token', data.token).then(() => {
this.storage.get('api_token').then((value) => {
console.log('storage : '+ value); // is empty...
});
});
console.log('raw : ' + data.token); // shows the api token
If you would like some more information about why this happens, check out this SO answer (I prefer second one) Asynchronous vs synchronous execution, what does it really mean?

Angular2 return data from validation service after Http call

I have build a validation service for my registration form and one of the static methods is checking if the entered email is available by calling my API the following:
static emailAvailable(control){
let injector = ReflectiveInjector.resolveAndCreate([HTTP_PROVIDERS]);
let http = injector.get(Http);
let valid = "E-mail is available";
http.post('https://secretapi.com/email', JSON.stringify({ email: control.value }))
.map((res: Response) => res.json())
.subscribe(function(result){
if(result.success){
valid = result.success; //The console.log on the line below is correct, the one at the bottom of the script never changes.
console.log(valid);
return null; //Doesn't do anything?
}else{
valid = result.error; //The console.log on the line below is correct, the one at the bottom of the script never changes.
console.log(valid);
return { 'invalidEmailAddress': true }; //Doesn't do anything, just like the return above
}
});
console.log(valid); //Output always "E-mail is available"
}
It should return "null" to the form validator when the email is available. The last console.log at the bottom should output the message that it recieves in the subscribe call. This doesn't happen and I'm not sure why. For some reason everything that happens within the subscribe call is contained there and never reaches the validator. What should I change? I have no idea and been searching the web for hours now.
You have to return Observable or Promise from your validator:
return http.post('https://secretapi.com/email', ...
console.log(...) doesn't make any sense here, since it will be executed after the Observable has been created as an object, but not after the ajax call has bee made.
If you want to output something after a response has been received, you have to move it inside subscribe
So in the end this website had the right answer. Also important to notice with the Angular2 Form validator to put the Async validators in the third (3) parameter and not together in an array in the second (2) parameter. That took me about 3 hours to figure out.
function checkEmail(control: Control){
let injector = ReflectiveInjector.resolveAndCreate([HTTP_PROVIDERS]);
let http = injector.get(Http);
return new Observable((obs: any) => {
control
.valueChanges
.debounceTime(400)
.flatMap(value => http.post('https://secretapi.com/email', JSON.stringify({ email: control.value })))
.map((res: Response) => res.json())
.subscribe(
data => {
if(data.success){
obs.next(null);
obs.complete();
} else {
obs.next({ 'invalidEmailAddress': true });
obs.complete();
}
}
);
});
}
The validator should look something like this, with the first validators checking on required and if it's actually an email address and the last doing an async call to the server to see if it's not already in use:
this.registerForm = this.formBuilder.group({
'email': ['', [Validators.required, ValidationService.emailValidator], ValidationService.emailAvailable],
});