How to observe the angular 5 interceptor error in some component - rxjs

Hi I am new to angular 5 and followed some blogs to write the HTTP Interceptor.
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor() { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone(
{
headers: req.headers.set("headerName", "headerValue")
}
);
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if(error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
This is working fine. But what I need to do is to pass this error code to other components and print out a message on the screen for the user. One wy to do that is to create an observable but I am unable to implement that.
Any help is highly appreciated.

You can use a service to do that, by leveraging a Subject. Here's an example of using BehaviourSubject.
First you create a service. This service will be shared across the two classes:
export class BroadcastService {
public http404: BehaviorSubject<boolean>;
constructor() {
//initialize it to false
this.http404 = new BehaviorSubject<boolean>(false);
}
}
In your HttpInterceptor class, you inject the BroadcastService into it. To update the BehvaiourSubject, simply use .next():
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor(public broadcastService: BroadcastService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone({
headers: req.headers.set("headerName", "headerValue")
});
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if (error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
this.broadcastService.http404.next(true);
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
And in your app.component.ts, simply subscribe it using .asObservable(). You need to inject it too:
export class AppComponent implements ngOnInit {
constructor(public broadCastService: BroadcastService) {
}
OnInit() {
this.broadCastService.http404.asObservable().subscribe(values => {
console.log(values); // will return false if http error
});
}
}

Related

Express socket.io to netty socket.io in spring

I need to stream data from my backend (in spring) to my angular frontend.
I cant get the netty socket.io implementation working.
public ConnectListener onUserConnectWithSocket = new ConnectListener() {
#Override
public void onConnect(SocketIOClient socketIOClient) {
log.info("Client connected: " + socketIOClient.getSessionId());
socketIOClient.sendEvent("getAllDashboardData", generateRandomValues());
}
};
public DataListener<String> getAllDashboardData = new DataListener<String>() {
#Override
public void onData(SocketIOClient socketIOClient, String message, AckRequest ackRequest) throws Exception {
log.info("Message received: " + message);
socketIOClient.sendEvent("getAllDashboardData", generateRandomValues().toString());
}
};
when i have something like this, the EventListener never gets called (does not log User requested data). Hence, the onConnect logs that the frontend connected.
I tried out the frontend call using express!
This simple examples works perfect:
module.exports = (io) => {
io.on('connect', (socket) => {
console.log('user connected');
socket.on('getAllDashboardData', (data) => {
//send some data to client back
socket.emit('getAllDashboardData', {data: 'data'});
});
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
}
how could i write this in spring?
I also tested the backend with postman and it works fine!
The answer is:
import { Injectable } from '#angular/core';
const io = require('socket.io-client');
import {Observable} from "rxjs";
#Injectable({
providedIn: 'root'
})
export class SocketService {
socket: any;
readonly uri = 'ws://localhost:8085';
constructor() {
this.socket = io(this.uri);
}
listen(eventName: string) {
return new Observable((resolve) => {
this.socket.on(eventName, (data: any) => {
// this.socket.emit(eventName, data); maybe don't use this produces 1000 of calls
resolve.next(data);
});
});
}
emit(eventName: string, data: any) {
this.socket.emit(eventName, data);
}
}
and use socket.io-client version 2.3.0 to work with netty spring.

NESTJS Gateway / Websocket - how to send jwt access_token through socket.emit

I am using the default passport jwt AuthGuard for my project. That works for my post & get routes fine when setting the authentication header.
Now I want to use Nestjs Gateways as well with socket.io on the client-side, but I don't know how to send the access_token to the gateway?
That is basically my Gateway:
#WebSocketGateway()
export class UserGateway {
entityManager = getManager();
#UseGuards(AuthGuard('jwt'))
#SubscribeMessage('getUserList')
async handleMessage(client: any, payload: any) {
const results = await this.entityManager.find(UserEntity);
console.log(results);
return this.entityToClientUser(results);
}
And on the client I'm sending like this:
this.socket.emit('getUserList', users => {
console.log(users);
this.userListSub.next(users);
});
How and where do I add the jwt access_token? The documentation of nestjs misses that point completely for Websockets. All they say is, that the Guards work exactly the same for websockets as they do for post / get etc. See here
While the question is answered, I want to point out the Guard is not usable to prevent unauthorized users from establishing a connection.
It's only usable to guard specific events.
The handleConnection method of a class annotated with #WebSocketGateway is called before canActivate of your Guard.
I end up using something like this in my Gateway class:
async handleConnection(client: Socket) {
const payload = this.authService.verify(
client.handshake.headers.authorization,
);
const user = await this.usersService.findOne(payload.userId);
!user && client.disconnect();
}
For anyone looking for a solution. Here it is:
#UseGuards(WsGuard)
#SubscribeMessage('yourRoute')
async saveUser(socket: Socket, data: any) {
let auth_token = socket.handshake.headers.authorization;
// get the token itself without "Bearer"
auth_token = auth_token.split(' ')[1];
}
On the client side you add the authorization header like this:
this.socketOptions = {
transportOptions: {
polling: {
extraHeaders: {
Authorization: 'your token', // 'Bearer h93t4293t49jt34j9rferek...'
}
}
}
};
// ...
this.socket = io.connect('http://localhost:4200/', this.socketOptions);
// ...
Afterwards you have access to the token on every request serverside like in the example.
Here also the WsGuard I implemented.
#Injectable()
export class WsGuard implements CanActivate {
constructor(private userService: UserService) {
}
canActivate(
context: any,
): boolean | any | Promise<boolean | any> | Observable<boolean | any> {
const bearerToken = context.args[0].handshake.headers.authorization.split(' ')[1];
try {
const decoded = jwt.verify(bearerToken, jwtConstants.secret) as any;
return new Promise((resolve, reject) => {
return this.userService.findByUsername(decoded.username).then(user => {
if (user) {
resolve(user);
} else {
reject(false);
}
});
});
} catch (ex) {
console.log(ex);
return false;
}
}
}
I simply check if I can find a user with the username from the decoded token in my database with my user service. I am sure you could make this implementation cleaner, but it works.
Thanks! At the end i implemented a Guard that like the jwt guard puts the user inside the request. At the end I'm using the query string method from the socket client to pass the auth token This is my implementation:
import { CanActivate, ExecutionContext, Injectable, Logger } from '#nestjs/common';
import { WsException } from '#nestjs/websockets';
import { Socket } from 'socket.io';
import { AuthService } from '../auth/auth.service';
import { User } from '../auth/entity/user.entity';
#Injectable()
export class WsJwtGuard implements CanActivate {
private logger: Logger = new Logger(WsJwtGuard.name);
constructor(private authService: AuthService) { }
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
const client: Socket = context.switchToWs().getClient<Socket>();
const authToken: string = client.handshake?.query?.token;
const user: User = await this.authService.verifyUser(authToken);
client.join(`house_${user?.house?.id}`);
context.switchToHttp().getRequest().user = user
return Boolean(user);
} catch (err) {
throw new WsException(err.message);
}
}
}

Making an http call inside of switchmap

I am writing an angular app with firebase auth and a dotnet core backend. I am trying to create a service so that i can track the firebase uid and track if the user is an admin (which is obtained from the backend server). I can successfulyl get the uid from firebase with no issue, but when i try to get the user object from my api I get errors in the console.
Here is my code in my user service:
export class UserService {
uid = this.afAuth.authState.pipe(
map(authState => {
return !authState ? null : authState.uid;
})
);
isAdmin = this.uid.pipe(
switchMap(uid => {
if (uid) {
console.log("there is a uid")
this.httpClient.get<User>("https://localhost:44337/api/users/" + uid).subscribe(data => {
console.log(data.isAdmin); // prints 'true'
return observableOf(data.isAdmin);
});
} else {
return observableOf(false);
}
})
);
constructor(
private afAuth: AngularFireAuth,
private httpClient: HttpClient) { }
}
Inside a switchMap operator you should not directly subscribe to an observable. You should return the observable and the switchMap operator will handle the subscription for you.
switchMap(uid => {
if (uid) {
console.log("there is a uid")
return this.httpClient.get<User>("https://localhost:44337/api/users/" + uid).pipe(map(data => {
console.log(data.isAdmin); // prints 'true'
return data.isAdmin
}));
} else {
return of(false);
}
})

How to cancel request in Http Interceptor in angular5?

My angular app have a component called 'listdata.component.ts'.In ngOnInit() of this component there happens 2 api calls. But when 401 response got for the 2 requests a logout api call takes place 2 times. My interceptor code
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
notificationItem: any;
constructor(public authService: AuthGuardService, private router: Router, private notification: NotificationService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.authService.tokenValid()) {
const authReq = request.clone({ headers: request.headers.set("Authorization", 'Bearer ' + this.authService.getToken()) });
return next.handle(authReq).do((event: HttpEvent<any>) => {
}, err => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
if(authReq.url.indexOf('/logout') > -1){
localStorage.clear();
this.router.navigate(['login']);
}
else
{
this.authService.logout()
}
}
}
});
}
else{
return next.handle(request);
}
}
}
I want to cancel all the 401 requests after one 401 response and call logout for one time only.If takeUntil() operator can solve my issue?.Thanks in advance.

$templateCache in angular2?

I need an equivalent solution for the angular1.x $templateCache:
On App start I have to translate and configure all html views depending on the user's profile, role, permissions, language and current location. I want to accomplish this on the server side with ASP.NET Razor syntax and tools in one request (not in one for each component). This request should place all ready to use templates into angular2 client side cache. From now on every component which references its template will be served from this cache first, if available. In Angular1.x it was easily possible to load all the template in one request, separated by <script id="myView.html" type="text/ng-template">...</script>. Before placing them into the cache I had to compile each template by calling $compiler().
How can I accomplish this in Angular2?
One possible solution I could imagine would be if Angular2 supports Component's templateUrl as a function(). This way I could build my own cache.
After some more research and digging into the angular2 source code this $templateCache in Angular 2? pointed me to the right solution. I have to register a new custom Http and a custom XHR implementation via provide():
providers: [HTTP_PROVIDERS,
provide(Http, {
useFactory: (xhrBackend: XHRBackend, requestOptions: RequestOptions) => new HttpInterceptor(xhrBackend, requestOptions),
deps: [XHRBackend, RequestOptions]
}),
provide(XHR, {
useFactory: (http: Http) => new XHRInterceptor(http), deps: [Http]
})],
The XHRInterceptor (implementation of XHR interface) is injected and internally used by angular2 each time angular2 loads html temlates via Compontent's tempateUrl. Because we inject our custom Http implementation into XHRInterceptor constructor and delegate all get requests through HttpInterceptor we gain full control over all http requests from our application:
export class XHRInterceptor extends XHR {
constructor(private _http: Http) {
super()
}
get(url: string): Promise<string> {
var completer: PromiseCompleter<string> = PromiseWrapper.completer();
this._http.get(url).map(data=> {
return data.text();
}).subscribe( data => {
completer.resolve(data);
}, error=>{
completer.reject(`Failed to load ${url}`, null);
});
return completer.promise;
}
}
and this is my HttpInterceptor class:
export class HttpInterceptor extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
if (typeof url === "string") {
return this.interceptResult(super.request(this.interceptUrl(url), this.interceptOptions(options)));
} else {
return this.interceptResult(super.request(url, this.interceptOptions(options)));
}
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.interceptResult(super.get(this.interceptUrl(url), this.interceptOptions(options)));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.interceptResult(super.post(this.interceptUrl(url), body, this.interceptOptions(options)));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.interceptResult(super.put(this.interceptUrl(url), body, this.interceptOptions(options)));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.interceptResult(super.delete(this.interceptUrl(url), this.interceptOptions(options)));
}
interceptUrl(url: string): string {
// Do some stuff with the url....
//...
return url;
}
interceptOptions(options?: RequestOptionsArgs): RequestOptionsArgs {
// prepare options...
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
// insert some custom headers...
// options.headers.append('Content-Type', 'application/json');
return options;
}
interceptResult(observable: Observable<Response>): Observable<Response> {
// Do some stuff with the result...
// ...
return observable;
}
}

Resources