Intercept http and add headers - IONIC - ionic4

I'm running a Ionic 4 app which contains an http interceptor to add some loading between the requests.
I also have a credential service which validates whether there is a token in my storage or not.
this is my interceptor
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.requests.push(req);
this.loaderService.isLoading.next(true);
return new Observable(observer => {
const subscription = next.handle(req)
.subscribe(
event => {
if (event instanceof HttpResponse) {
this.removeRequest(req);
observer.next(event);
}
},
(err: Error ) => {
this.alertservice.info(err.message, 'id1');
this.removeRequest(req);
observer.error(err);
},
() => {
this.removeRequest(req);
observer.complete();
});
// remove request from queue when cancelled
return () => {
this.removeRequest(req);
subscription.unsubscribe();
};
});
}
It works, but when I'm trying to add the following lines, it stops working and redirects to my error page and consoles nothing, I can't track it.
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
//console.log(this.credentialService.credentials.token);
// console.log(authToken);
Can anyone help me understand why does it happen, and how to fix?

I just figured it out and feel stupid,
this.credentialService.credentials was null since there is none before logging in, so the interceptor sends me to error section, to fix, I first validate if it's not null, then add headers properly:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let clonedReq: any;
if (this.credentialService.credentials !== null) {
const authToken = this.credentialService.credentials.token;
clonedReq = req.clone({headers: req.headers.set('Authorization', authToken)});
} else {
clonedReq = req.clone({headers: req.headers.delete('Authorization')});
}
{...}

Related

Fastify reply was can not be sent

export class SessionController {
constructor(private sessionService: SessionService) {}
#Post('login')
async login(
#Req() req: FastifyRequest,
#Body() params: LoginDto,
#Res() reply: FastifyReply,
) {
params.ipAddress = req.socket.remoteAddress; /* ::1 */
this.sessionService
.signin(params)
.then((user) => {
reply.header('Authorization', `Bearer ${user.access_token}`);
reply.code(201).send(user);
})
.catch((error) => {
console.log(error instanceof HttpException);
reply.status(error.status).send({ errors: [error.message] });
});
}
}
When I hit this API, I received an error which was reply was already sent, Here I did not use the reply.sent() function two times, In this nest js application I have used the frame framework under the hood, If I run this same function with express it works fine.
{"level":40,"time":1663844608468,"pid":5951,"hostname":"yavar","reqId":"req-1","err":{"type":"FastifyError","message":"Reply was already sent.","stack":"FastifyError: Reply was already sent.\n at Reply.send (/home/bennison/Documents/project/nest-lib/node_modules/fastify/lib/reply.js:118:26)\n at /home/bennison/Documents/project/nest-lib/src/user/user.controller.ts:35:15\n at processTicksAndRejections (node:internal/process/task_queues:96:5)","name":"FastifyError","code":"FST_ERR_REP_ALREADY_SENT","statusCode":500},"msg":"Reply already sent"}
If I use the async and await method instead of then catch it works fine. If anyone knows the answer, please explain to me what mistake have I made here.
To fix that issue, we need to call the method in the return statement. here call the sessionService in the return statement.
export class SessionController {
constructor(private sessionService: SessionService) {}
#Post('login')
async login(
#Req() req: FastifyRequest,
#Body() params: LoginDto,
#Res() reply: FastifyReply,
) {
params.ipAddress = req.socket.remoteAddress; /* ::1 */
return this.sessionService
.signin(params)
.then((user) => {
reply.header('Authorization', `Bearer ${user.access_token}`);
reply.code(201).send(user);
})
.catch((error) => {
console.log(error instanceof HttpException);
reply.status(error.status).send({ errors: [error.message] });
});
}
}

RN "TypeError: Network request failed" - production - random

I know some questions about the subject has been opened here and there, but my issue is different :
all the other ones appear in dev mode, in my case it's in production,
a very big percentage of requests pass, a few of them is TypeError: Network request failed - but sometimes for critical requests
it's random, not always the same request. Sometimes it passes, sometimes not.
it appears to three on my projects, one is on AWS the other one on Clever-Cloud, both are projects between 1000 and 5000 users, servers are quite too big for what they do - I think I removed the risk of a server fault. Even if... I can reproduce locally when I don't start the api locally. So it's like the api is not responding, but as I said, I don't think so.
I have no clue where to dig anymore...
I can give you my API.js service file, maybe you'll find what's wrong ?
import URI from 'urijs';
import { Platform } from 'react-native';
import NetInfo from '#react-native-community/netinfo';
import { getUserToken, wipeData } from '../utils/data';
import { SCHEME, MW_API_HOST } from '../config';
import deviceInfoModule from 'react-native-device-info';
import { capture } from '../utils/sentry';
const unauthorisedHandler = (navigation) => {
wipeData();
navigation.reset({ index: 0, routes: [{ name: 'Auth' }] });
};
const checkNetwork = async (test = false) => {
const isConnected = await NetInfo.fetch().then((state) => state.isConnected);
if (!isConnected || test) {
await new Promise((res) => setTimeout(res, 1500));
return false;
}
return true;
};
class ApiService {
host = MW_API_HOST;
scheme = SCHEME;
getUrl = (path, query) => {
return new URI().host(this.host).scheme(this.scheme).path(path).setSearch(query).toString();
};
execute = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
try {
const config = {
method,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
appversion: deviceInfoModule.getBuildNumber(),
appdevice: Platform.OS,
currentroute: this.navigation?.getCurrentRoute?.()?.name,
...headers,
},
body: body ? JSON.stringify(body) : null,
};
const url = this.getUrl(path, query);
console.log('url: ', url);
const canFetch = await checkNetwork();
if (!canFetch) return;
let response;
// To try to avoid mysterious `TypeError: Network request failed` error
// that throws an error directly
// we try catch and try one more time.
try {
response = await fetch(url, config);
} catch (e) {
if (e?.toString().includes('Network request failed')) {
// try again
await new Promise((res) => setTimeout(res, 250));
console.log('try again because Network request failed');
response = await fetch(url, config);
} else {
throw e;
}
}
if (!response.ok) {
if (response.status === 401) {
const token = await getUserToken();
if (token) unauthorisedHandler(API.navigation);
return response;
}
}
if (response.json) return await response.json();
return response;
} catch (e) {
capture(e, { extra: { method, path, query, headers, body } });
return { ok: false, error: "Sorry, an error occured, technical team has been warned." };
}
};
executeWithToken = async ({ method = 'GET', path = '', query = {}, headers = {}, body = null }) => {
const token = await getUserToken();
if (token) headers.Authorization = token;
return this.execute({ method, path, query, headers, body });
};
get = async (args) => this.executeWithToken({ method: 'GET', ...args });
post = async (args) => this.executeWithToken({ method: 'POST', ...args });
put = async (args) => this.executeWithToken({ method: 'PUT', ...args });
delete = async (args) => this.executeWithToken({ method: 'DELETE', ...args });
}
const API = new ApiService();
export default API;
Talking with experts here and there, it seems that it's normal : internet network is not 100% reliable, so sometimes, request fail, for a reason that we can't anticipate (tunnel, whatever).
I ended up using fetch-retry and I still have a few of those, but much less !

use middleware in get request conditionally

i am using express routes. this is my request
import { Router, Request, Response, NextFunction } from "express";
import { celebrate, Joi, Segments } from "celebrate";
const route = Router();
export default (app: Router) => {
route.get("/:id",
celebrate({
[Segments.PARAMS]: Joi.object({
id: Joi.string().required().label("Store Id")
})
}),
basicAuth, //a middleware
asyncHandler(async (req: Request, res: Response, next: NextFunction) => {
// some operations.
return res.status(202).json(storeDetails);
})
);
}
here how can i use the basicAuth middleware conditionally such that basicAuth is used when id=="me" else another middleware isAuth is used.
You can just call the middleware manually if your conditional is met:
route.get("/:id",
celebrate({
[Segments.PARAMS]: Joi.object({
id: Joi.string().required().label("Store Id")
})
}),
asyncHandler(async (req: Request, res: Response, next: NextFunction) => {
if (req.params.id === "me") {
// call basicAuth middleware manually
basicAuth(req, res, err => {
if (err) {
// send error response here
next(err);
} else {
// continue processing here now that basicAuth has been verified
}
});
} else {
// not "me"
// some operations.
return res.status(202).json(storeDetails);
}
})
);
}

How to handle message sent from server to client with RSocket?

I try to use RSocketRequester to send a message from the server to the specific client, but I don't know how to handle it on the frontend. The server is Spring Webflux with the controller like this:
data class Message(val message: String)
#Controller
class RSocketController {
private val log = LoggerFactory.getLogger(RSocketController::class.java)
#MessageMapping("say.hello")
fun sayHello(message: String): Flux<Message> {
log.info("say hello {}", message)
return Flux.just(Message("server says hello"))
}
#MessageMapping("say.hi")
fun sayHi(message: String, rSocketRequester: RSocketRequester): Flux<Message> {
log.info("say hi {}", message)
rSocketRequester
.route("say.hello")
.data(Message("server says hi hello ;)"))
.send()
.subscribe()
return Flux.just(Message("server says hi!!"))
}
}
On the frontend I use rsocket-js. The sayHello method works just fine (request-stream), but when I call the sayHi method I want to send two messages from the server. The first one to say.hello endpoint, and the second to say.hi endpoint. I've got rsocket-js implementation like this:
sayHello() {
console.log("say hello");
this.requestStream("say.hello");
},
sayHi() {
console.log("say hi");
this.requestStream("say.hi");
},
connect() {
const transport = new RSocketWebSocketClient({
url: "ws://localhost:8080/rsocket"
});
const client = new RSocketClient({
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
keepAlive: 60000,
lifetime: 180000,
dataMimeType: "application/json",
metadataMimeType: "message/x.rsocket.routing.v0"
},
transport
});
client.connect().subscribe({
onComplete: socket => {
this.socket = socket;
console.log("complete connection");
},
onError: error => {
console.log("got connection error");
console.error(error);
},
onSubscribe: cancel => {
console.log("subscribe connection");
console.log(cancel);
}
});
},
requestStream(url) {
if (this.socket) {
this.socket
.requestStream({
data: url + " from client",
metadata: String.fromCharCode(url.length) + url
})
.subscribe({
onComplete: () => console.log("requestStream done"),
onError: error => {
console.log("got error with requestStream");
console.error(error);
},
onNext: value => {
// console.log("got next value in requestStream..");
console.log("got data from sever");
console.log(value.data);
},
// Nothing happens until `request(n)` is called
onSubscribe: sub => {
console.log("subscribe request Stream!");
sub.request(2147483647);
// sub.request(3);
}
});
} else {
console.log("not connected...");
}
}
I can see both messages in Google Chrome DevTools -> Network -> rsocket. So the client receives them but I can't catch in the code the one sent by RSocketRequester.
It seems that the server uses fireAndForget method. How to handle it on the client side?
As #VladMamaev said, we can provide a responder to the client like in this example https://github.com/rsocket/rsocket-js/blob/master/packages/rsocket-examples/src/LeaseClientExample.js#L104
For me, fireAndForget method is enough.
export class EchoResponder {
constructor(callback) {
this.callback = callback;
}
fireAndForget(payload) {
this.callback(payload);
}
}
import { EchoResponder } from "~/assets/EchoResponder";
...
const messageReceiver = payload => {
//do what you want to do with received message
console.log(payload)
};
const responder = new EchoResponder(messageReceiver);
connect() {
const transport = new RSocketWebSocketClient({
url: "ws://localhost:8080/rsocket"
});
const client = new RSocketClient({
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
keepAlive: 60000,
lifetime: 180000,
dataMimeType: "application/json",
metadataMimeType: "message/x.rsocket.routing.v0"
},
responder: responder,
transport
});

Ionic 2 clearing App cache on logout

My login function consists of a http request (an irrelevant one just for the check) with the entered credentials. This way I can resolve the request or get a rejection which I handle by not pushing the next component with the NavController onto the stack.
On logout, the credentials, which are saved in the Ionic Storage, are deleted. Now starts the problem: Any credentials that are saved now in the storage seems not to be used by the login request as the request doesn't throw an Auth exception. Only after clearing the Browser cache it works again.
This all happened by serving the app in a web browser and on the phone.
How is it possible to clear the App cache (not only the View/Component cache) with Ionic 2 to prevent this behaviour? There is no documentation or question concerning this problem existent at the moment.
Auth Service:
#Injectable()
export class AuthService {
HAS_LOGGED_IN = 'hasLoggedIn';
constructor(private storage: Storage) {
// this.rest = rest;
console.log('auth');
}
setCredentials(credentials) {
this.storage.set('username', credentials.username);
this.storage.set('password', credentials.password);
}
logout(): void {
this.storage.remove('username');
this.storage.remove('password');
this.storage.remove(this.HAS_LOGGED_IN);
}
hasLoggedIn() {
return this.storage.get(this.HAS_LOGGED_IN).then( value => {
console.log('hasLoggedIN value: ' + value);
return value === true;
});
}
}
Login Component:
#Component({
selector: 'page-login',
templateUrl: 'login.html',
})
export class LoginPage {
model: any;
HAS_LOGGED_IN: string = 'hasLoggedIn';
constructor(private navCtrl: NavController,
private viewCtrl: ViewController,
private auth: AuthService,
private toastCtrl: ToastController,
private rest: RestService,
private storage: Storage) {
}
ionViewDidLoad() {
this.model = {};
}
ionViewWillEnter() {
this.viewCtrl.showBackButton(false);
this.displayTab(false);
}
login() {
console.log(this.model);
console.log('login() claled');
this.displayTab(true);
this.auth.setCredentials(this.model);
this.rest.getEntryPoint().then(data => {
console.log(data);
this.storage.set(this.HAS_LOGGED_IN, true);
this.navCtrl.push(OverviewPage);
}).catch(err => {
this.storage.set(this.HAS_LOGGED_IN, false);
console.log('Error:');
console.log(err);
this.navCtrl.push(LoginPage).then(response => {
console.log(response);
console.log(this.navCtrl);
console.log('pushed login 1');
});
});
}
validate(items: boolean) {
if (items) {
let toast = this.toastCtrl.create({
message: 'Passwort und Benutzername sind zwingend',
duration: 3000,
position: 'bottom',
});
toast.onDidDismiss(() => {
console.log('Dismissed toast');
});
toast.present();
}
}
private displayTab(display: boolean) {
let elements = document.querySelectorAll('.tabbar');
if (elements != null) {
Object.keys(elements).map((key) => {
elements[key].style.transform = display ? 'translateY(0)' : 'translateY(70px)';
});
}
}
}
Intercepting Http methods (this service is used in another service that makes the actual Rest calls):
#Injectable()
export class HttpInterceptorService {
constructor(#Inject(Http) private http: Http, private storage: Storage) {
this.http = http;
console.log('interceptor');
}
get(url) {
return new Promise((resolve, reject) => {
let headers = new Headers();
this.createAuthorizationHeader(headers).then(() => {
return this.http.get(url, {
headers: headers,
}).subscribe(data => {
resolve(data.json());
}, err => {
reject(err);
});
});
});
}
put(url: string, attributes?) {
return new Promise((resolve, reject) => {
let headers = new Headers();
this.createAuthorizationHeader(headers).then(() => {
return this.http.put(url, (attributes) ? attributes : {}, {
headers: headers,
}).subscribe(data => {
resolve(data.json());
}, err => {
reject(err);
});
});
});
}
post(url: string, data) {
return new Promise((resolve, reject) => {
let headers = new Headers();
this.createAuthorizationHeader(headers).then(() => {
return this.http.post(url, data, {
headers: headers,
}).subscribe(output => {
resolve(output.json());
}, err => {
reject(err);
});
});
});
}
private createAuthorizationHeader(headers: Headers): Promise<void> {
console.log('creating auth header');
return new Promise(resolve => {
this.storage.get('username')
.then( username => {
this.storage.get('password')
.then( password => {
headers.append('Authorization', 'Basic ' +
btoa(username + ':' + password));
resolve();
});
});
});
}
}
The other components are using hasLoggedIn() function to check if we are logged in or not when switching back to the App. If not logged in anymore (storage cleaned in any way) we get back to the LoginPage component.
The problem was a session header in the response that I somehow didn't see beforehand.
My solution is a simple Cookies clearing:
window.cookies.clear(function() {});