Issue with synchronous operation somewhere down a chain RxJS observables subscription - rxjs

I have a synchronous operation that is run somewhere down a chain of RxJS observables subscription.
This synchronous operation sets data on local storage (synchronous) that is required further down the chain in order to perform a http call (asynchronous/observable).
Here is a summary of the sequence:
Async operation returning an observable called
Sync operation setting data on local storage
Async operation using local storage date and returning an observable
Final subscription
By the time 3. is called, it seems data is not available on local storage - supposed to have been set by 2.
The above is just a simplification of the issue.
Here is the full code (in typescript):
This is called by a form (located in a component):
resetPassword() {
this.submitted = true;
if (this.passwordResetForm.valid) {
this.route.params.map(params => params['userAccountToken'])
.switchMap(userAccountToken => {
return Observable.concat(
this.userAccountService.resetPassword(Object.assign(this.passwordResetForm.value.passwordReset, {token: userAccountToken})),
this.sessionService.signinByUserAccountToken(userAccountToken)
);
})
//Will require the UserAccountResolve below which will itself fail because 'x-auth-token' is not yet available on local storage
.subscribe(() => this.router.navigate(['/dashboard']));
}
}
from UserAccountService:
resetPassword(passwordResetForm) {
return this.http.put(this.urls.USER_ACCOUNT.RESET_PASSWORD, passwordResetForm);
}
from SessionService:
signinByUserAccountToken(userAccountToken: string) {
return this.http.post(format(this.urls.AUTHENTICATION.SIGNIN_BY_USER_ACCOUNT_TOKEN, {userAccountToken}), null)
.do(response => this.setPersonalInfo(response.headers.get('x-auth-token')));
}
private setPersonalInfo(sessionToken) {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
this.authenticated$.next(true);
}
UserAccountResolve:
import {Injectable} from '#angular/core';
import {Resolve, ActivatedRouteSnapshot} from '#angular/router';
import {UserAccount} from '../shared/models/useraccount.model';
import {AuthenticatedHttpClient} from '../shared/services/authenticated-http-client.service';
import {URLS} from '../urls/URLS';
#Injectable()
export class UserAccountResolve implements Resolve<UserAccount> {
private urls;
constructor(private authenticatedHttpClient: AuthenticatedHttpClient) {
this.urls = URLS;
}
resolve(route: ActivatedRouteSnapshot) {
//Will fail
return this.authenticatedHttpClient.get(this.urls.USER_ACCOUNT.USER_ACCOUNT)
.map(response => response.json());
}
}
AuthenticatedHttpClient:
#Injectable()
export class AuthenticatedHttpClient {
static createAuthorizationHeader(headers: Headers) {
//Is not available on local storage when required
headers.append('x-auth-token', localStorage.getItem('sessionToken'));
}
constructor(private http: Http) {
}
get(url) {
let headers = new Headers();
AuthenticatedHttpClient.createAuthorizationHeader(headers);
return this.http.get(url, {
headers: headers
});
}
...
Can someone please help?

If I understand your Problem correctly, you want to delay redirecting your user until both HTTP requests have been done. Currently you subscribe to your Observable, causing the redirect to happen as soon as it observes the first result, which is in this case the result of userAccountService.resetPassword().
If you just want to wait for all requests to finish, you can subscribe onCompleted like this:
observable.subscribe(
_ => {},
err => { /* Don't forget to handle errors */ },
() => this.router.navigate(['/dashboard']))
However having an Observable whose results you do not care about is a sign, that your App might need a refactor one of these days.

Related

Nest.js Websocket Gateway loosing socket connecition using redid broker

I have to implement websocket communication in my nest.js app. I've successfully setup the websocket gateway and I have tested it with postman. My code looks like this
export class SocketIOAdapter extends IoAdapter {
constructor(private app: INestApplicationContext, private configService: ConfigService) {
super(app);
}
createIOServer(port: number, options: ServerOptions) {
const clientPort = parseInt(this.configService.getOrThrow("PORT"));
const cors = {
origin: [
`http://localhost:${clientPort}`,
new RegExp(`/^http:\/\/192\.168\.1\.([1-9]|[1-9]\d):${clientPort}$/`),
],
};
const optionsWithCORS: ServerOptions = {
...options,
cors,
};
const server: Server = super.createIOServer(port, optionsWithCORS);
const orderRepository = this.app.get(OrderRepository);
server
.of("/orders")
.use(createTokenMiddleware(orderRepository));
return server;
}
}
const createTokenMiddleware =
(orderRepository: OrderRepository) =>
async (socket: Socket, next) => {
// here I run some logic using my order repository
next();
} catch {
next(new Error("FORBIDDEN"));
}
};
And
#WebSocketGateway({
namespace: "/orders",
})
#Injectable()
export class OrderGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
private readonly logger = new Logger(OrderGateway.name);
#WebSocketServer() io: Namespace;
afterInit(): void {
this.logger.log("Websocket Gateway initialized.");
}
async handleConnection(client: Socket) {
const sockets = this.io.sockets;
// here I run some logic to know which rooms to use for this client
const roomsToJoin = [...]
await client.join(roomsToJoin);
}
async handleDisconnect(client: Socket) {
this.logger.log(`Disconnected socket id: ${client.id}`);
}
public emitOrderStatusChangeNotification(order: OrderDTO) {
this.io
.to("Here I put some roomId that depends on order")
.emit("order_status_changed", JSON.stringify(order));
}
}
Now, whenever I want to send a notification, I inject the OrderGateway and call emitOrderStatusChangeNotification. This works fine, however, my app is deployed on several instances behind a load balancer. The latter breaks this approach as socket clients may be connected to a different server from the one I'm sending the notification. So, the next step to scale web sockets (as far as I understand) is to use a broker. I tried to use Redis pub/sub in the following way. I have this two classes:
#Injectable()
export class NotificationPublisherService {
constructor(#Inject("ORDER_NOTIFICATION_SERVICE") private client: ClientProxy) {}
async publishEvent(order: OrderDTO) {
console.log("will emit to redis");
this.client.emit(Constants.notificationEventName, order);
}
}
#Controller()
export class NotificationSuscriberController {
private readonly logger = new Logger(NotificationSuscriberController.name);
constructor(private readonly orderGateway: OrderGateway) {}
#EventPattern(Constants.notificationEventName)
async handleOrderStatusChangeEvent(order: OrderDTO) {
try {
this.orderGateway.emitOrderStatusChangeNotification(order);
} catch (err) {
this.logger.log("error sending notification");
}
}
As you can see, I'm injecting orderGateway in the class that have the method that handles the data from redis and in that handler I send the notification. Finally, I replaced all the invocations of emitOrderStatusChangeNotification to the publishEvent method of NotificationPublisherService. After doing this, the flow works well except from the last step. This means, the data is put on redis and read by the suscriber, which tries to send the websocket notification. However, when logging the connected clients for that room in emitOrderStatusChangeNotification method, I'm getting that there are no connected clients, even though I confirmed there where connected clients on that room (I did this by logging the list of connected clients after doing client.join in the handleConnection method of OrderGateway). My best guess is that an instance of OrderGateway handles the socket connection and a different instance of OrderGateway is processing the data from Redis broker. I tried to explicitly set the scope of the Gateway to Default to guarantee that my app has only one instance of OrderGateway (I also confirmed that it has not any request scoped dependency that could bubble up and make it not default scoped). It did not work and I'm out of ideas. Does anyone know what could be happening? Thanks in advance
EDIT
As Gregorio suggested in the answers, I had to extend my adapter as explained in the docs, the following code worked for me
export class SocketIOAdapter extends IoAdapter {
private adapterConstructor: ReturnType<typeof createAdapter>;
constructor(private app: INestApplicationContext, private configService: ConfigService) {
super(app);
}
async connectToRedis(): Promise<void> {
const pubClient = createClient({ url: "redis://localhost:6379" });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
this.adapterConstructor = createAdapter(pubClient, subClient);
}
createIOServer(port: number, options: ServerOptions) {
const clientPort = parseInt(this.configService.getOrThrow("PORT"));
const cors = {
origin: [
`http://localhost:${clientPort}`,
new RegExp(`/^http:\/\/192\.168\.1\.([1-9]|[1-9]\d):${clientPort}$/`),
],
};
const optionsWithCORS: ServerOptions = {
...options,
cors,
};
const server: Server = super.createIOServer(port, optionsWithCORS);
const orderRepository = this.app.get(OrderRepository);
server
.adapter(this.adapterConstructor)
.of(`/orders`)
.use(createTokenMiddleware(orderRepository));
return server;
}
}
const createTokenMiddleware =
(orderRepository: OrderRepository) =>
async (socket: Socket, next) => {
// here I run some logic using my order repository
next();
} catch {
next(new Error("FORBIDDEN"));
}
};
}
and in my main.ts
const redisIoAdapter = new SocketIOAdapter(app, configService);
await redisIoAdapter.connectToRedis();
Have you tried following this page from the nest.js docs? I think it might help you in what you're looking for. You should write in your SocketIOAdapter what it says there in order to connect with Redis, it is not necessary to have the NotificationPublisherService or the NPController.
https://docs.nestjs.com/websockets/adapter

Unit testing NestJS Observable Http Retry

I'm making a request to a 3rd party API via NestJS's built in HttpService. I'm trying to simulate a scenario where the initial call to one of this api's endpoints might return an empty array on the first try. I'd like to use RxJS's retryWhen to hit the api again after a delay of 1 second. I'm currently unable to get the unit test to mock the second response however:
it('Retries view account status if needed', (done) => {
jest.spyOn(httpService, 'post')
.mockReturnValueOnce(of(failView)) // mock gets stuck on returning this value
.mockReturnValueOnce(of(successfulView));
const accountId = '0812081208';
const batchNo = '39cba402-bfa9-424c-b265-1c98204df7ea';
const response =client.viewAccountStatus(accountId, batchNo);
response.subscribe(
data => {
expect(data[0].accountNo)
.toBe('0812081208');
expect(data[0].companyName)
.toBe('Some company name');
done();
},
)
});
My implementation is:
viewAccountStatus(accountId: string, batchNo: string): Observable<any> {
const verificationRequest = new VerificationRequest();
verificationRequest.accountNo = accountId;
verificationRequest.batchNo = batchNo;
this.logger.debug(`Calling 3rd party service with batchNo: ${batchNo}`);
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const response = this.httpService.post(url, verificationRequest, config)
.pipe(
map(res => {
console.log(res.data); // always empty
if (res.status >= 400) {
throw new HttpException(res.statusText, res.status);
}
if (!res.data.length) {
this.logger.debug('Response was empty');
throw new HttpException('Account not found', 404);
}
return res.data;
}),
retryWhen(errors => {
this.logger.debug(`Retrying accountId: ${accountId}`);
// It's entirely possible the first call will return an empty array
// So we retry with a backoff
return errors.pipe(
delayWhen(() => timer(1000)),
take(1),
);
}),
);
return response;
}
When logging from inside the initial map, I can see that the array is always empty. It's as if the second mocked value never happens. Perhaps I also have a solid misunderstanding of how observables work and I should somehow be trying to assert against the SECOND value that gets emitted? Regardless, when the observable retries, we should be seeing that second mocked value, right?
I'm also getting
: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
On each run... so I'm guessing I'm not calling done() in the right place.
I think the problem is that retryWhen(notifier) will resubscribe to the same source when its notifier emits.
Meaning that if you have
new Observable(s => {
s.next(1);
s.next(2);
s.error(new Error('err!'));
}).pipe(
retryWhen(/* ... */)
)
The callback will be invoked every time the source is re-subscribed. In your example, it will call the logic which is responsible for sending the request, but it won't call the post method again.
The source could be thought of as the Observable's callback: s => { ... }.
What I think you'll have to do is to conditionally choose the source, based on whether the error took place or not.
Maybe you could use mockImplementation:
let hasErr = false;
jest.spyOn(httpService, 'post')
.mockImplementation(
() => hasErr ? of(successView) : (hasErr = true, of(failView))
)
Edit
I think the above does not do anything different, where's what I think mockImplementation should look like:
let err = false;
mockImplementation(
() => new Observable(s => {
if (err) {
s.next(success)
}
else {
err = true;
s.next(fail)
}
})
)

redux test Actions must be plain objects. Use custom middleware for async actions

I am not using redux-thunk. this keeps error-ing and I am not sure how to fix it. The examples I see online use redux-thunk which I am not using
my repo is here and the file I am trying to test is in tests\actions\...
My action that is being called in the test
import axios from "axios";
var CancelToken = axios.CancelToken;
let fetch_cancel;
export const FETCH_CATEGORIES = "fetch_categories";
export async function fetchCategories() {
fetch_cancel && fetch_cancel();
const request = await axios.get(
`https://d1i9eedhsgvpdh.cloudfront.net/production-plentific-static/api-cache/find-a-pro/api/v1/categories/all.json`,
{
cancelToken: new CancelToken(function executor(c) {
// An executor function receives a cancel function as a parameter
fetch_cancel = c;
})
}
);
return {
type: FETCH_CATEGORIES,
payload: request
};
}
The error message means that your actions must be plain objects. For example:
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
If you make an async request, you can't just return an object, because you need to wait for the request to finish. If you return too early, you return a Promise.
However, I cannot reproduce your error in your sandbox.

Is using 500 response code for normal app flow viable? Designing popup messages with Angular7+Spring

From Java standpoint using Exceptions to handle EXPECTED outcomes is wrong (Exceptions should be what they are called).
For all my services I've created wrapper mechanism that basically gives details on failure if such happens (all returns are arbitrary Result<?>). Now I need to display this message on client browser in some popup.
With Angular there is HttpClient that actually supports catching http response errors:
https://angular.io/guide/http#error-handling
Is this a viable way of reporting error from server to client?
Is there a way in Angular to define some extractor that would split responses from backend API?
Say I'd make my whole REST API return bodies:
{
messageType: "", // Success, Failure, Warning
message: "Message",
content: {
...
}
}
And that way I could strip message, messageType in interceptor, display them in popup, and pass only content further as body?
A good way to capture all exceptions at service side using #ControllerAdvice and throw the user/feature specific exceptions with the expected exception message and status code in standard structure so that front end can have an exception controller to evaluate this exception message on a fly in popup message dynamic to any message from service.
Since I came to web from pure Java backends - Exceptions in JAVA are bad (generally speaking), why follow same bad practices (using errors as info) when using HTTP?
After some more reading I can say - don't unless its actually an error, e.g:
404 if client tries to access entity with ID that is not there.
500 when server ACTUALLY can't handle something (actual Exception).
Here is Angular part for my Result<?> system.
Interceptor:
import { Injectable } from '#angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse } from '#angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Globals } from '../globals.service';
#Injectable()
export class PopupInterceptor implements HttpInterceptor {
constructor(private globals: Globals) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.startsWith(this.globals.apiHost)) {
return next.handle(req)
.pipe(
map(e => {
if (e instanceof HttpResponse) {
const response = <Response>e.body;
// use response to do whatever - I am injecting popup service and pushing response.message there for display in component.
return e.clone({ body: response.result });
}
})
);
}
return next.handle(req);
}
}
interface Response {
type: string;
message: string;
result: any;
}
globals.service:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class Globals {
apiHost = '/api/';
}
app.module:
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: PopupInterceptor, multi: true }
],

Transform RxJs Observable of Observables into count of how many Observables are pending?

I'm making a service that has multiple methods triggering HTTP calls. I want to reveal an Observable to clients that tells them whether there are any pending calls.
I think this approach would generally would work, but feels hacky and not very RxJs-like. I've searched all over StackOverflow and through the RxJs docs, but can't find a better way. Any advice? Thanks!
let pendingCountSubject = new BehaviorSubject<number>(0);
let pendingCount$ = pendingCountSubject.asObservable();
let pendingSubject = new Subject<Observable<any>>();
pendingSubject.pipe(
finalize(() => {
pendingCountSubject.next(pendingCountSubject.value - 1);
}))
.subscribe();
function trackPendingObservable(obs) {
pendingCountSubject.next(pendingCountSubject.value + 1);
pendingSubject.next(obs);
}
trackPendingObservable(httpCall1);
trackPendingObservable(httpCall2);
trackPendingObservable(httpCall3);
And in template:
Pending calls: {{ pendingCount$ | async }}
If I get it right, your service has several methods that perform http calls, e.g.
httpCall1(): Observable<any> { // do stuff}
httpCall2(): Observable<any> { // do stuff}
httpCallN(): Observable<any> { // do stuff}
Somehow these methods run in parallel, maybe because they are called in a rapid synchronous sequence.
What you want to show is how many of them are "on fly" at a certain moment.
Something like this may work.
The service exposes the http call methods as well as a counter of the calls currently "on fly".
export class MyHttpService {
constructor(private http: HttpClient)
public callsOnFly = 0;
httpGenericCall(httpCall: Observable<any>) {
this.callsOnFly++;
return httpCall
.pipe(
finalize(() => this.callsOnFly--)
)
}
httpCall1() {
return this.httpGenericCall(this.http.get('http://my-server1'));
}
httpCall12() {
return this.httpGenericCall(this.http.get('http://my-server2'));
}
httpCallN() {
return this.httpGenericCall(this.http.get('http://my-serverN'));
}
}
The component can just display via interpolation the value of the counter
Pending calls: {{ httpService.callsOnFly }}
where I assume that the name of MyHttpService in the component is httpService

Resources