EventSource stop recieving any update from server - javascript

I have a strange bug concerning EventSource.
I have a server that is permanently sending some event to the UI via EventSource. Until some days ago, everything was working fine.
Recently there was an update, and the server now send new data on some channel.
The thing is, for reason I haven't yet found out, sometimes, EventSource now stop working.
The connection is still open, the status of the request is still pending and not closed, and no error on console at all. The server is also still streaming event to the UI. But EventSource just don't update anymore. I also tried to hit directly with a curl request, but he do not get any update, until I refresh manually.
here is my Client Code =>
import { Injectable, HostListener, OnDestroy } from '#angular/core';
import { environment } from 'src/environments/environment';
#Injectable({
providedIn: 'root'
})
export class SSEService implements OnDestroy {
private eventSource: EventSource;
private callbacks: Map<string, (e: any) => void> = new Map<string, (e: any) => void>();
init(channel: string) {
if (this.eventSource) { return; }
console.log('SSE Channel Start');
this.eventSource = new EventSource(`${environment.SSE_URL}?channel=${channel}`);
}
protected callListener(cb: (d) => void): (e) => void {
const callee = ({data}): void => {
try {
const d = JSON.parse(data);
cb(d.message);
} catch (e) {
console.error(e, data);
}
};
return callee;
}
private addEventToEventSrc(event: string, callback: any, owner: string): void {
console.log(`Subscribed to ⇢ ${event} (owned by: ${owner})`);
const cb = this.callListener(callback);
this.eventSource.addEventListener(event, cb);
if (!this.callbacks.get(`${event}_${owner}`)) {
this.callbacks.set(`${event}_${owner}`, cb);
}
}
subscribe(event: string, callback: any, owner?: string): void {
if (!this.eventSource) { return; }
if (!owner) { owner = 'default'; }
if (!event) { event = 'message'; }
this.addEventToEventSrc(event, callback, owner);
}
unsubscribe(event: string, owner?: string): void {
if (!this.eventSource) { return; }
if (!owner) { owner = 'default'; }
if (!event) { event = 'message'; }
if (this.callbacks.get(`${event}_${owner}`)) {
console.log(`Unsubscribed to ⇢ ${event} (owned by: ${owner})`);
this.eventSource.removeEventListener(event, this.callbacks.get(`${event}_${owner}`));
}
this.callbacks.delete(`${event}_${owner}`);
}
#HostListener('window:beforeunload')
onBrowserClose() {
this.clearAll();
}
ngOnDestroy() {
this.clearAll();
}
clearAll() {
if (this.eventSource) {
console.log('SSE Channel Closed');
this.eventSource.close();
this.eventSource = null;
}
this.callbacks = new Map<string, (e: any) => void>();
}
}
I tried to log the data received, to put try catch... but it is never showing any error, it just stop getting updates.
The data send from the server my be the issue, but the server do not stop stream, so my EventSource should either crash or keep going.
but right now it's silently cutting any updates.
If you could give some idea of where the cause might be I would be very grateful

It turned out that, my Client Implementation of EventSource is correct.
Server side, we had 1 server and a dispatcher, the server was sending a lot of update on the same channel with very short delay to the dispatcher. EventSource was considering all those message as a Single big message, and was stopping the refresh until he considered the message ended. But this was not happening, so it got stuck in open trying to download.

Related

How to return data from Nestjs socket.io gateway

I want to connect to my NestJs application with socket. I defined the gateway as below:
#WebSocketGateway()
export class MyGateway implements OnModuleInit{
constructor(private readonly gatewayService: GatewayService) {}
#WebSocketServer()
server: Server;
onModuleInit() {
this.server.on('connection', (socket) => {
console.log(socket.id);
console.log('a user connected')
});
}
#SubscribeMessage('newSession')
async onNewSession(#MessageBody() createSessionRequest: CreateSessionRequest) : Promise<AddressGenerated> {
const addressGenerated = this.gatewayService.createSession(createSessionRequest);
return addressGenerated;
}
}
I'm using postman to call the socket but it doesn't return the addressGenerated. if I define something as below:
#SubscribeMessage('newSession')
async onNewSession(#MessageBody() createSessionRequest: CreateSessionRequest) : Promise<AddressGenerated> {
const addressGenerated = this.gatewayService.createSession(createSessionRequest);
var b58;
var hex;
await addressGenerated.then(function(result) {
b58 = result.base58;
hex = result.hex;
})
this.server.emit('onSession', {
msg: "New Session Created",
content: {
b58: b58,
hex: hex,
},
});
return addressGenerated;
}
So that I can listen to onSession event and get data but I don't want to listen to some other event at the time of connecting to socket. how should I do that? also the documentation said it is possible to return data but it doesn't work for me. I appreciate it if anyone could help me.

Firebase Firestore: globally detect when there are pending writes

I've got a simple requirement to show a warning to the user if they leave the browser window while a pending write is happening in Firestore using a beforeunload listener:
window.addEventListener('beforeunload', e => {
if (NO_PENDING_SAVES) return;
e.preventDefault();
e.returnValue =
'Your changes are still being saved. Are you sure you want to leave?';
}, {
capture: true,
});
In Firestore using the Web SDK, how do I detect whether there are pending saves or not globally? There is a waitForPendingWrites() method on the Firestore object, but it would require polling and it's also asynchronous, so it won't work inside of beforeunload.
Solved doing this (async way) using Angular Framework and dependency "#angular/fire": "^7.4.1" :
export class FirebaseService {
constructor(private db: AngularFirestore) {
}
pendingWrites(): Observable<any> {
return defer(() => this.db.firestore.waitForPendingWrites())
}
And then:
export class PendingWritesService {
hasPendingWrites$: Observable<boolean>;
constructor(
private firebaseService: FirebaseService
){
this.hasPendingWrites$ = this.somethingPending$();
}
somethingPending$(): Observable<boolean> {
const timeEmmiter = timer(1000);
return race(timeEmmiter, this.firebaseService.pendingWrites()).pipe(
switchMap((pendingWrites, noPendingWrites) => {
const arePendingsWrites = 0;
return of(pendingWrites === arePendingsWrites);
})
)
}
}

Trying to solve a practice problem sending and receiving messages with setTimeOut, setInterval, and callbacks

I'm working on a practice problem using setTimeOut and callbacks for the first time. This is the situation:
You receive a JavaScript object 'recordMap' of the following
type: { [recordName: string]: Client }
Client is an interface allowing to a) send a text message, b) receive a text message by registering a callback function.
interface Client {
sendMessage: (message: string) => void;
//to send messages to the Client
onMessage: (callback: (message: string) => void) =>
void; // a callback for receiving messages from the
Client
}
Public method 'initialize' will initialize the tracking of active Clients.
The goal is to make sure that no records in 'recordMap' are kept for inactive Clients.
//to register Send a 'ping' message to all Clients every 10 seconds. Register a callback function to receive messages from Clients.
Sending and receiving messages is an asynchronous form of communication - give Clients 3 seconds to reply to your message.
For every Client who hasn't replied with a 'pong' message, remove his record from 'recordMap'.
type RecordMap = { [recordName: string]: Client };
class RecordTracker {
constructor (private recordMap: RecordMap) {}
public initialize (): void {
...?
}
public getRecordMap (): RecordMap {
return { ...this.recordMap };
}
}
I feel like I need to use setInterval for a message every 10 seconds and setTimeOut to send and receive the message, but not sure where to go from there.
I'm not really sure where to go with this one. Do I need to create some dummy clients to call? This is what I have so far:
```
interface Client {
sendMessage: (message: string) => void;
onMessage: (callback: (message: string) => void) => void;
}
type RecordMap = { [recordName: string]: Client };
class RecordTracker {
constructor (private recordMap: RecordMap) {}
public initialize (): void {
setInterval(() => {
const sendMessage = (message) => {
//????
}
}, 10000)
}
public getRecordMap (): RecordMap {
return { ...this.recordMap };
}
}```
I would start by designing a function that accepts a Client and checks to see whether it's still active. Once you've ironed out that part, it should be straightforward to wire it into some kind of regular testing regime.
Since sending a message is asynchronous, our function is async. And, since we want to forcibly resolve it, we'll explicitly create a Promise so that we can resolve it after a timeout.
Here's a sketch of what I have in mind:
async function getIsClientActive( client, timeoutMs ) {
return new Promise(resolve => {
// set up the timeout
setTimeout(() => resolve(false), timeoutMs)
// set up handler to listen for pong
const pongReceiver = ( msg ) => {
if(msg === 'pong') resolve(true)
}
client.onMessage = pongReceiver
// send the ping
client.sendMessage('ping')
})
}
One big caveat: my getIsClientActive assumes it's safe to hijack each client's onMessage handler. If that's not safe, you could either temporarily store the current handler in RAM during the ping operation (simpler), or set up an EventEmitter for each Client that fires "message" events and which the ping operation could temporarily subscribe to (more robust)
One thing I want to stress: it will make your job easier if this client-probing operation can remain ignorant of the data structures your program uses to track all known clients.

How to implement a paging solution using RxJs?

What is the best way to implement a paging solution in RxJs?
If want to have an observable that emits new data based on an event (could be a click, or a JavaScript function call). For instance if I have an Observable retrieving data from a Web API, and I don't want it to just keep on hammering away HTTP requests, I only want that to happen on an event triggered by the subscriber. Scenarios could be infinite scrolls (event triggered by scrolling), classic paging (event triggered by user clicking on next page) etc.
This is the solution I came up with based on the comments and using RxJs 6.3.3
export default class Test extends React.Component<any, any> {
private subscription: Subscription;
public componentDidMount() {
const client = new MyClient();
let source = client.get('/data');
const buttonNotifier = defer(() => {
console.log("waiting");
return fromEventPattern(
(handler) => { this.next = handler; }
).pipe(tap(() =>
console.log("sending more data")
));
});
const pagedData: Observable<{Value:any, NextLink:string}> = source.pipe(
expand(({ NextLink }) => NextLink ?
buttonNotifier.pipe(take(1), concatMap(() => client.get(NextLink))) :
empty()));
this.subscription = pagedData.subscribe(
result => {
this.setState({
Value: result.Value,
});
},
error => {
this.setState({
Error: `ERROR: ${error}`
});
},
() => {
this.setState({
Done: `DONE`
});
}
);
}
public componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
private next: Function;
public render(): React.ReactElement<any> {
return (<button onClick={()=> {this.next();}}>Next</button>);
}
}

Angular Transfer State not preventing repeat http calls

I have the http request being made a service which is injected onto my component and subscribed to from there. Since I introduced server side rendering with angular universal to my application, the results on the page are repeated at least twice.
I have method which is called on click, which performs the http request to facebook's api
getAlbum(albumId: number) {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY, null as any);
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, res as any);
});
}
}
I declared the const variable below the imports
const ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
And I also declared the property
albumNames: any;
I am assuming I have done all of the imports right I have the code on github in the gallery component.
You are on the right pass, you just need to handle your service differently if you are on the server or the browser side to perform your queries only once and not twice.
Pseudo logic:
If server -> Do http request -> Set value in transfer-state
If browser -> Get value from transfer-state
To do so, you could for example enhance your Service like following:
#Injectable()
export class FacebookEventsService {
const ALBUM_PHOTOS_KEY: StateKey<number>;
constructor(#Inject(PLATFORM_ID) private platformId: Object, private http: HttpClient) {
this.ALBUM_PHOTOS_KEY = makeStateKey('albumPhotos');
}
getBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
// Here we check if server or browser side
if (isPlatformServer(this.platformId)) {
return this.getServerBachaDiffFacebookEvents();
} else {
return this.getBrowserBachaDiffFacebookEvents();
}
}
getServerBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return this.http.get(this.facebookEventsUrl)
.map(res => {
// Here save also result in transfer-state
this.transferState.set(ALBUM_PHOTOS_KEY, calendarEvents);
});
}
getBrowserBachaDiffFacebookEvents(): Observable<CalendarEvent[]> {
return new Observable(observer => {
observer.next(this.transferState.get(ALBUM_PHOTOS_KEY, null));
});
}
}
UPDATE
To use this logic you would also need:
TransferHttpCacheModule (to be initialized in app.module.ts).
TransferHttpCacheModule installs a Http interceptor that avoids
duplicate HttpClient requests on the client, for requests that were
already made when the application was rendered on the server side.
https://github.com/angular/universal/tree/master/modules/common
ServerTransferStateModule on the server side and BrowserTransferStateModule on the client side to use TransferState
https://angular.io/api/platform-browser/TransferState
P.S.: Note that if you do so and enhance your server, of course you would not need anymore to set the value in transfer-state in your getAlbum() method you displayed above
UPDATE 2
If you want to handle the server and browser side as you did in your gallery.component.ts, you could do something like the following:
getAlbum(albumId: number) {
if (isPlatformServer(this.platformId)) {
if (!this.albumPhotos) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.bachataPicsArray = res;
this.state.set(ALBUM_PHOTOS_KEY, null);
});
}
} else {
this.albumPhotos = this.state.get(ALBUM_PHOTOS_KEY,null);
}
}
UPDATE 3
The thing is, your action getAlbum is never called on the server side. This action is only used on the browser side, once the page is rendered, when the user click on a specific action. Therefore, using transfer-state in that specific case isn't correct/needed.
Furthermore not sure that the Observable in your service was correctly subscribed.
Here what to change to make it running:
gallery.component.ts
getAlbum(albumId: number) {
this.facebookService.getBachadiffAlbumPhotos(albumId).subscribe(res => {
this.albumPhotos = res;
});
}
facebook-events.service.ts
getBachadiffAlbumPhotos(albumId: number): Observable<Object> {
this.albumId = albumId;
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
return Observable.fromPromise(this.getPromiseBachaDiffAlbumPhotos(albumId));
}
private getPromiseBachaDiffAlbumPhotos(albumId: number): Promise<{}> {
return new Promise((resolve, reject) => {
this.facebookAlbumPhotosUrl = `https://graph.facebook.com/v2.11/${this.albumId}/photos?limit=20&fields=images,id,link,height,width&access_token=${this.accessToken}`;
let facebookPhotos: FacebookPhoto[] = new Array();
let facebookPhoto: FacebookPhoto;
const params: HttpParams = new HttpParams();
this.http.get(this.facebookAlbumPhotosUrl, {params: params})
.subscribe(res => {
let facebookPhotoData = res['data'];
for (let photo of facebookPhotoData) {
facebookPhotos.push(
facebookPhoto = {
id: photo.id,
image: photo.images[3].source,
link: photo.link,
height: photo.height,
width: photo.width
});
}
resolve(facebookPhotos);
}, (error) => {
reject(error);
});
});
}
UPDATE 4
ngOnInit is executed on the server side, this means that my very first answer here has to be use in this case.
Furthermore, also note that on the server side you doesn't have access to the window, therefore calling $
With gallery.component.ts you could do something like this to run only the http queries once but this won't solve all your problems, I think it will still need further improvements.
ngOnInit() {
if (isPlatformServer(this.platformId)) {
this.facebookService.getBachadiffFacebookVideos().subscribe(res => {
this.bachataVidsArray = res;
this.state.set(VIDEOS_KEY, res as any);
});
this.facebookService.getBachadiffFacebookLastClassPictures().subscribe(res => {
this.bachataPicsArray = res;
this.state.set(LAST_CLASS_PICTURES_KEY, res as any);
});
this.facebookService.getBachadiffAlbumNames().subscribe(res => {
this.bachataAlbumHeaderNames = res;
this.state.set(ALBUM_NAMES_KEY, res as any);
});
} else {
$('ul.tabs').tabs();
this.bachataVidsArray = this.state.get(VIDEOS_KEY, null as any);
this.bachataPicsArray = this.state.get(LAST_CLASS_PICTURES_KEY, null as any);
this.bachataAlbumHeaderNames = this.state.get(ALBUM_NAMES_KEY, null as any);
}
}

Categories

Resources