Dealing with SSE reconnection errors in Angular 8

Currently, as part of an Angular 8 project with Electron 6 and Ionic 4, we are in the evaluation phase of deciding whether to replace polling with either SSE (Server-sent events) or Web Sockets. My task involves researching SSE.

To test SSE, I set up a small Express application that generates random numbers. Everything is functioning properly, but I am facing challenges when it comes to reconnecting on server errors.

This is how my implementation looks:

private createSseSource(): Observable<MessageEvent> {
    return Observable.create(observer => {
      this.eventSource = new EventSource(SSE_URL);
      this.eventSource.onmessage = (event) => {
        this.zone.run(() => observer.next(event));
      };

      this.eventSource.onopen = (event) => {
        console.log('connection open');
      };

      this.eventSource.onerror = (error) => {
        console.log('decided not to take any action now');
        // this.zone.run(() => observer.error(error));
        // this.closeSseConnection();
        // this.reconnectOnError();
      };
    });
}

I attempted to implement the reconnectOnError() function based on this answer, but unfortunately, I couldn't get it to work. Eventually, I opted to skip the reconnectOnError() function, which seems to be the better approach. Rather than closing and reopening the connection or propagating the error to the observable, awaiting for the server to automatically reconnect upon running again appears more suitable.

My question is: Is this truly the best course of action? It is worth noting that the frontend app communicates exclusively with its own server, which cannot be accessed by another instance of the application (embedded device).

Answer №1

After noticing some interest in my question, I have decided to share the solution I came up with. In response to my question: "Is it advisable to omit the reconnect function?" Well, I can't say for sure :). However, this solution has worked effectively for me and has been successfully implemented in a production environment, providing a way to manage SSE reconnection to some degree.

Here is what I've done:

  1. Restructured the createSseSource function so that the return type is now void
  2. Rather than returning an observable, the data from SSE is directed towards subjects/NgRx actions
  3. Introduced public method openSseChannel and private method reconnectOnError for improved control
  4. Incorporated a private function processSseEvent to handle customized message types

Since NgRx is utilized on this project, every SSE message triggers a corresponding action, though this can be substituted by a ReplaySubject and exposed as an observable.

// Public function, establishes connection and returns true if successful
openSseChannel(): boolean {
  this.createSseEventSource();
  return !!this.eventSource;
}

// Sets up SSE event source and manages SSE events
protected createSseEventSource(): void {
  // Close event source if there is an existing instance in the SSE service
  if (this.eventSource) {
    this.closeSseConnection();
  }
  // Create a new channel, initialize a new EventSource
  this.eventSource = new EventSource(this.sseChannelUrl);

  // Process default event
  this.eventSource.onmessage = (event: MessageEvent) => {
    this.zone.run(() => this.processSseEvent(event));
  };

  // Add custom events
  Object.keys(SSE_EVENTS).forEach(key => {
    this.eventSource.addEventListener(SSE_EVENTS[key], event => {
      this.zone.run(() => this.processSseEvent(event));
    });
  });

  // Handle connection opened event
  this.eventSource.onopen = () => {
    this.reconnectFrequencySec = 1;
  };

  // Handle errors
  this.eventSource.onerror = (error: any) => {
    this.reconnectOnError();
  };
}

// Handles specific event types
private processSseEvent(sseEvent: MessageEvent): void {
  const parsed = sseEvent.data ? JSON.parse(sseEvent.data) : {};
  switch (sseEvent.type) {
    case SSE_EVENTS.STATUS: {
      this.store.dispatch(StatusActions.setStatus({ status: parsed }));
      // or
      // this.someReplaySubject.next(parsed);
      break;
    }
    // Add others if needed
    default: {
      console.error('Unknown event:', sseEvent.type);
      break;
    }
  }
}

// Manages reconnect attempts when the connection fails for any reason.
// const SSE_RECONNECT_UPPER_LIMIT = 64;
private reconnectOnError(): void {
  const self = this;
  this.closeSseConnection();
  clearTimeout(this.reconnectTimeout);
  this.reconnectTimeout = setTimeout(() => {
    self.openSseChannel();
    self.reconnectFrequencySec *= 2;
    if (self.reconnectFrequencySec >= SSE_RECONNECT_UPPER_LIMIT) {
      self.reconnectFrequencySec = SSE_RECONNECT_UPPER_LIMIT;
    }
  }, this.reconnectFrequencySec * 1000);
}

Since the SSE events are directed to subjects/actions, it doesn't matter if the connection is lost because the last event is preserved within the subject or store. Reconnection attempts can occur silently, ensuring seamless processing of newly sent data.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Erase a set of characters with the press of the backspace key (JavaScript)

Within my textarea, I have lines that start with a number and a period: <textarea autoFocus id="text-area"wrap="hard" defaultValue ={this.state.textAreaVal} onKeyUp={this._editTextArea}/> For example: Line 1 Line 2 Line 3 I've created a ...

Display the scrollbar in a Boostrap CSS table when the content exceeds the height of the div

I've been working on a Bootstrap page that looks like this: https://i.sstatic.net/kpHMM.png On the right side, there are two tables inside a div. The issue I'm facing is that when the tables contain too many elements, they grow in height instea ...

Using TypeScript interfaces to define key-value pairs for object properties

My goal is to accurately type situations where I need to map an array of objects to an array with the same objects, but using the property value as the index key. View code on playground interface ValueDefinition { name: string; } function getByName ...

Is it possible to pass a value to a jQuery show() function?

Is there a more concise way to achieve this? someBool ? $dom.show() : $dom.hide(); In jQuery, you can use the toggle function like toggle('class', someBool) to add or remove a class based on a condition. I'm curious if there is a similar f ...

I'm looking to transfer my stringified object into the HTML body. How can I achieve

When sending an HTML file to a client using the res.write() method, I also need to include an object within the HTML. However, when I stringify the object and add it along with the HTML, the JSON object ends up outside of the HTML structure. I require the ...

Creating or deleting multiple batches of entries in Firebase Realtime Database

I am currently utilizing Firebase real time database in the following way: createSoldLead(soldLead: SoldLeadModel): void { const soldLeadsReference = this.angularFireDatabase.list<SoldLeadModel>( `groups/${this.groupId}/soldLeads` ); ...

Unable to load the manually added module in the /node_modules/ folder

I'm trying to manually use a module that I placed in the /node_modules/ directory. After copying and pasting the files and installing dependencies with npm, I encountered an issue while using NWJS 0.16.0. When attempting var speech = require('sp ...

What is the best method for showing the price from this list of items?

{"item":{"icon":"ANOTHER LINK","icon_large":"ANOTHER LINK","id":385,"type":"Default","typeIcon":"ANOTHER LINK","name":"Shark","description":"I should be cautious when consuming this.","current":{"trend":"neutral","price":"1,239"},"today":{"trend":"positive ...

Async/Await moves on to the next function without waiting for the previous function to finish executing

I am developing a web application that requires querying my database multiple times. Each query depends on the data retrieved from the previous one, so I need to ensure each call completes before moving on to the next. I have attempted using async/await fo ...

Guide to utilizing exact matching functionality in ExpressJs router

In my ExpressJs application, I have defined two routes like so: router.get("/task/", Controller.retrieveAll); router.get("/task/seed/", Controller.seed); When I make a request to /task/seed/, the Controller.retrieveAll function is call ...

Obtain text content using JQuery and AJAX rather than retrieving the value

I am struggling with a dynamic table that needs to perform calculations before submitting, requiring the presence of values. However, I want to submit the text from the options instead of the values. Despite trying various approaches, none of them seem to ...

Tips for effectively unit testing the Angular @viewChild directive with ElementRef

In my component file, I have the following code snippet @ViewChild('clusterCard', { static: false }) clusterCard: ElementRef; highLightTheClusterCard(point: PickupClusterPoint) { if (point) { const card: HTMLElement = _get(this.cluster ...

Can you explain the functionality of this JavaScript registration code?

I am working on an ajax/jquery 1.3.2 based sign up form and I am looking to gain a deeper understanding of how to incorporate jquery into my projects by analyzing the code line by line. Could someone provide a detailed explanation of this code and break d ...

Managing numerous subscriptions to private subjects that are revealed by utilizing the asObservable() method

I'm using a familiar approach where an Angular service has a private Subject that provides a public Observable like this: private exampleSubject = new Subject<number>(); example$ = this.exampleSubject.asObservable(); In my particular situation, ...

Issue with Heroku not properly configuring cookies in a Node.js application

I have deployed my react app on xxx.herokuapp.com, and my node app on yyy.herokuapp.com. Both are functioning properly, and the backend is able to successfully send data to the front end. However, I am encountering an issue when trying to send a cookie. Th ...

I'm experiencing difficulty displaying my nested array in JavaScript

let array2 = ['Banana', ['Apples', ['Oranges'], 'Blueberries']]; document.write(array2[0][0]); In attempting to access the value Apples within this nested array, I encountered unexpected behavior. Initially, access ...

Encountering issues with dependencies while updating React results in deployment failure for the React app

Ever since upgrading React to version 18, I've been encountering deployment issues. Despite following the documentation and scouring forums for solutions, I keep running into roadblocks with no success. The errors displayed are as follows: $ npm i np ...

Converting an array value into JSON structure

I'm currently working on an email application that allows users to send messages. Everything is functioning well except for the recipients column. I temporarily hardcoded an email address to test the functionality, but in reality, the recipients field ...

Checking types during code execution

When working with TypeScript on Node.js, types are checked during compilation. However, once the code is compiled to JavaScript, type checking is no longer enforced. This can lead to unexpected errors, as in the following example: var test: number; test = ...

Why is Axios not being successfully registered as a global variable in this particular Vue application?

Recently, I have been delving into building a Single Page Application using Vue 3, TypeScript, and tapping into The Movie Database (TMDB) API. One of the hurdles I faced was managing Axios instances across multiple components. Initially, I imported Axios ...