(INDEX) INDEX (2025)

Push Notification to Browser with WebPush, SSE, WebSocket and other methods (JS, React, Vue, Angular)

1. Push Notification is the most important template what define all software structure. My way.

I continue improving my frontend skills and now I want to summarize some information about Push notification from server to Browser. Firstly, this is most ancient and most important software development pattern. Concrete realization of this pattern a billion times more important that various stupid GOF pattern and finally this pattern define all architecture of whole application.

I described various solution about pushing notification since my blog created, for example:


2. Other WebHook methods from SQL to Backend

PostgreSQL and MySQL also has similar solutions tu push notification to Backend:

3. JS Frontend vs JS Backend.

There are a lot of special browser frontend technologies, Some of then I use often, some of them rare, for example this my blog based on WebComponent technology, Encapsulate HTML, CSS, and JS to WebComponent. Shadow DOM. Example of my WebComponent in this blog, but historically I changing my focus from ASP.NET to Browser Frontend technologies more and more, and as usually push notification is most important for select software architecture.

Firstly, this is my ancient table with difference between Frontend and Backend programming - Main 82 points of Browser and Node difference - you also can use this table to get list of Browser technologies and how different Backend programming from frontend programing .

4. Push Notification methods (from Backend to Browser).

5. Common Service Worker project template (Trace requests + CacheStorage API).

Service Worker is a separate type of worker that runs in the background - Service Workers, Service Worker can be different types Service Worker Types and working differently in each Browser. MDN ServiceWorker Cookbook.


Service Worker cannot directly display custom in-app notifications because it does not have access to the DOM, but Service Worker can use for various purposes, not only exactly for accept notification, usually Service Worker used for:


Frontend just need to register Service Worker


   4:      window.addEventListener('load', () => {
   5:          console.log('[Main Script] Page fully loaded. Registering Service Worker...');
   6:          navigator.serviceWorker.register('SW02.js')
   7:              .then(registration => {
   8:                  console.log('Service worker registered:', registration);
   9:   
  10:                  // Listen for controller change
  11:                  navigator.serviceWorker.addEventListener('controllerchange', () => {
  12:                      console.log('[Main Script] Service Worker is now controlling the page');
  13:                  });

And core of this service worker is intercept all request:


  47:  // Fetch event: Trace requests and serve cached resources
  48:  self.addEventListener('fetch', (event) => {
  49:      const url = event.request.url;
  50:      const method = event.request.method;
  51:   
  52:      // Log the request details
  53:      console.log(`[Service Worker] Fetching: ${method} ${url}`);
  54:   

To start project:

            # npm i http-server
            # npx http-server
            # http://localhost:4200
        

Now I decide to upload my ancient Service Worker project template to Github https://github.com/AAlex-11/JsServiceWorker.

6. Project template (Browser PushAPI + WebPush on Backend) to show Browser desktop notification.

This is concrete Service Worker implementation to push desktop notification to Browser (special predefined popup window in system notification area).

Frontend in this case just need to connect to Backend endpoint:


   1:  // Request permission for notifications
   2:  const permission = await Notification.requestPermission();
   3:  if (permission === 'granted') {
   4:      console.log('Notification permission granted.');
   5:   
   6:      // Subscribe to push notifications
   7:      const subscription = await registration.pushManager.subscribe({
   8:          userVisibleOnly: true,
   9:          applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
  10:      });
  11:      console.log('Push subscription successful:', subscription);
  12:   
  13:      // Send the subscription object to the server
  14:      await fetch('/subscribe', {
  15:          method: 'POST',
  16:          body: JSON.stringify(subscription),
  17:          headers: { 'Content-Type': 'application/json' }
  18:      });

Service Worker display desktop notification and post message to frontend


   1:  self.addEventListener('push', (event) => {
   2:      const payload = event.data ? event.data.json() : { title: 'New Notification', body: 'You have a new message!' };
   3:      console.log('Push event received:', payload);
   4:      // Display a notification
   5:      event.waitUntil(
   6:          self.registration.showNotification(payload.title, {
   7:              body: payload.body,
   8:              icon: payload.icon || '/icon.png',
   9:              data: { url: payload.url } // Optional: URL to open when the notification is clicked
  10:          })
  11:      );
  12:   
  13:      // Send a message to the client to show an in-app notification
  14:      self.clients.matchAll().then((clients) => {
  15:          clients.forEach((client) => {
  16:              client.postMessage({
  17:                  type: 'show-notification',
  18:                  body: payload.body
  19:              });
  20:          });
  21:      });
  22:  });

Backend periodically push notification to all subscriber


   1:  // Store subscriptions (in-memory for this example)
   2:  let subscriptions = [];
   3:   
   4:  // Endpoint to save subscriptions
   5:  app.post('/subscribe', (req, res) => {
   6:      const subscription = req.body;
   7:      subscriptions.push(subscription);
   8:      console.log('Subscription saved:', subscription);
   9:      res.status(201).json({ message: 'Subscription saved.' });
  10:  });
  11:   
  12:  // Function to send push notifications
  13:  function sendPushNotification(subscription, payload) {
  14:      webPush.sendNotification(subscription, payload)
  15:          .then(response => {
  16:              console.log('Push notification sent:', response);
  17:          })
  18:          .catch(error => {
  19:              console.error('Error sending push notification:', error);
  20:          });
  21:  }
  22:   
  23:  // Send notifications every 10 seconds
  24:  setInterval(() => {
  25:      const payload = JSON.stringify({
  26:          title: 'Periodic Notification',
  27:          body: 'This is a periodic push notification sent every 10 seconds.',
  28:          icon: '/icon.png',
  29:          url: 'http://127.0.0.1:3000/' // URL to open when the notification is clicked
  30:      });
  31:   
  32:      // Send notifications to all subscribers
  33:      subscriptions.forEach(subscription => {
  34:          sendPushNotification(subscription, payload);
  35:      });
  36:  }, 10000); // 10 seconds

Also Frontend show in-app pop-up window.


  19:      // Function to show a custom in-app notification
  20:      function showNotification(message) {
  21:          const notification = document.getElementById('notification');
  22:          const messageElement = document.getElementById('notification-message');
  23:          messageElement.textContent = message;
  24:          notification.style.display = 'block';
  25:      }
...
  86:      // Listen for messages from the Service Worker
  87:      navigator.serviceWorker.onmessage = (event) => {
  88:          if (event.data.type === 'show-notification') {
  89:              showNotification(event.data.body);
  90:          }
  91:      };

To start project:

            # npm i express web-push
            # npx web-push generate-vapid-keys
            # node server.js
            # http://localhost:3000
        

I have published project template to Github https://github.com/AAlex-11/JsWebPush.

7. Browser in-app PopUp + SSE Backend project template.

Service Worker is background task and can not directly manipulate DOM (but can send postMessage() to main browser rendering thread). But we can accept server notification with alternative way - SSE. We can define special endpoint in Backend and keep connection to that endpoint. (Notes for .NET developer - this looks as SignalR hub).

We have in Browser special class to working with SSE - EventSource, but can change direct way to working with Fetch API or RxJS.

Direct way to use EventSource looking something like this


   1:  document.getElementById('connect-btn').addEventListener('click', () => {
   2:    const eventSource = new EventSource('/sse');
   3:   
   4:    // Listen for messages from the server
   5:    eventSource.onmessage = (event) => {
   6:      const data = JSON.parse(event.data);
   7:      console.log('Received SSE message:', data);
   8:      showNotification(data.body);
   9:    };

For debugging purposes we can look from Browser to that endpoint, something like that.


   1:  async function connectToSSE() {
   2:      const response = await fetch('http://localhost:3000/sse');
   3:      const reader = response.body.getReader();
   4:      const decoder = new TextDecoder();
   5:   
   6:      while (true) {
   7:          const { value, done } = await reader.read();
   8:          if (done) break;
   9:   
  10:          const message = decoder.decode(value);
  11:          console.log('Received SSE message:', message);
  12:          showNotification(JSON.parse(message).body);
  13:      }
  14:  }
  15:   
  16:  document.getElementById('connect-btn').addEventListener('click', connectToSSE);

Backend periodically write something in defined endpoint


  17:  app.get('/sse', (req, res) => {
  18:      // Set headers for SSE
  19:      res.setHeader('Content-Type', 'text/event-stream');
  20:      res.setHeader('Cache-Control', 'no-cache');
  21:      res.setHeader('Connection', 'keep-alive');
  22:   
  23:      // Send a welcome message (optional)
  24:      res.write(`data: ${JSON.stringify({ message: 'Connected to SSE' })}\n\n`);
  25:   
  26:      // Send periodic notifications every 10 seconds
  27:      const intervalId = setInterval(() => {
  28:          const payload = JSON.stringify({
  29:              title: 'Periodic Notification',
  30:              body: 'This is a periodic notification sent every 10 seconds.'
  31:          });
  32:          console.log('Sending SSE message:', payload);
  33:          res.write(`data: ${payload}\n\n`);
  34:      }, 10000);

To start project:

            # npm i express
            # node server.js
            # http://localhost:3000
        

I have published project template to Github https://github.com/AAlex-11/JsSse.

8. SSE Angular declarative syntax + SSE Backend project template.

Angular has embedded feature to working with SSE - EventSource. Similar way has Vue and React.

Core of this program is RxJs observable subscription to EventSource object:


   1:  import { Injectable } from '@angular/core';
   2:  import { Observable } from 'rxjs';
   3:  
   4:  @Injectable({
   5:    providedIn: 'root',
   6:  })
   7:  export class SseService {
   8:    private eventSource!: EventSource ;
   9:  
  10:    constructor() {}
  11:  
  12:    // Connect to the SSE endpoint
  13:    connect(url: string):  Observable<MessageEvent> {
  14:      this.eventSource = new EventSource(url);
  15:  
  16:      return new Observable((observer) => {
  17:        // Handle incoming messages
  18:        this.eventSource.onmessage = (event) => {
  19:          observer.next(event);
  20:        };
  21:  
  22:        // Handle errors
  23:        this.eventSource.onerror = (error) => {
  24:          observer.error(error);
  25:        };
  26:      });
  27:    }
  28:  
  29:    // Disconnect from the SSE endpoint
  30:    disconnect() {
  31:      if (this.eventSource) {
  32:        this.eventSource.close();
  33:      }
  34:    }
  35:  }
  36:  
  37:  

With this standalone component and manual updating frontend.


   1:  import { Component, OnInit, OnDestroy, NgZone } from '@angular/core';
   2:  import { NgIf } from '@angular/common'; // Import NgIf for conditional rendering
   3:  import { SseService } from '../../services/sse.service'; // Import the SSE service
   4:  import { Subscription } from 'rxjs'; // Import Subscription for managing observables
   5:  
   6:  @Component({
   7:    selector: 'app-sse-messages', // Selector used in templates
   8:    standalone: true, // Mark the component as standalone
   9:    imports: [NgIf], // Import NgIf for use in the template
  10:    templateUrl: './sse-messages.component.html', // Link to the HTML template
  11:    styleUrls: ['./sse-messages.component.css'], // Link to the CSS file
  12:  })
  13:  export class SseMessagesComponent implements OnInit, OnDestroy {
  14:    message!: string; // Property to store the received SSE message
  15:    private sseSubscription!: Subscription; // Subscription to manage the SSE connection
  16:  
  17:    constructor(private sseService: SseService, private ngZone: NgZone) {} // Inject the SSE service
  18:  
  19:    ngOnInit() {
  20:      console.log('SseMessagesComponent initialized'); // Debug message
  21:  
  22:      // Connect to the SSE endpoint and subscribe to messages
  23:      this.sseSubscription = this.sseService
  24:        .connect('http://localhost:3000/sse')
  25:        .subscribe({
  26:          next: (event: MessageEvent) => {
  27:            console.log('Raw SSE event:', event)
  28:            const data = JSON.parse(event.data);
  29:            console.log('Parsed SSE data:', data)
  30:  
  31:            // Update the message property inside NgZone
  32:            this.ngZone.run(() => {
  33:              this.message = `${data.body} <br> Timestamp: ${event.timeStamp}`; // Update the message property with the "body" field
  34:              console.log('Updated message:', this.message); // Debug updated message
  35:            });
  36:  
  37:          },
  38:          error: (error) => {
  39:            console.error('SSE error:', error);
  40:          },
  41:          complete: () => {
  42:            console.log('SSE connection completed'); // Optional
  43:          },
  44:        });
  45:    }
  46:  
  47:    ngOnDestroy() {
  48:      // Clean up the subscription when the component is destroyed
  49:      if (this.sseSubscription) {
  50:        this.sseSubscription.unsubscribe();
  51:      }
  52:      this.sseService.disconnect(); // Disconnect from the SSE endpoint
  53:    }
  54:  }
  55:  
  56:  


To start project:

            # ng new sse-angular-app
            # cd sse-angular-app
            # ng generate service services/sse
            # ng generate component components/sse-messages
            # ng serve
            # node server.js
            # http://localhost:4200
        

I have published project template to Github https://github.com/AAlex-11/AngularSse.

9. WebSocket Push notification project template.

This project working without Express, on server present just only Socket server, that just listen socket connection to particular port, than send predefined HTML file with related mime type


  12:  const server = http.createServer((req, res) => {
  13:      let filePath = path.join(__dirname, 'public', req.url === '/' ? 'Frontend.htm' : req.url);
  14:      fs.readFile(filePath, (err, content) => {
  15:          if (err) {
  16:              res.writeHead(404, { 'Content-Type': 'text/plain' });
  17:              res.end('File not found');
  18:          } else {
  19:              res.writeHead(200, { 'Content-Type': getContentType(filePath) });
  20:              res.end(content);
  21:          }
  22:      });
  23:  });
  24:   
  25:  // Helper function to determine content type
  26:  function getContentType(filePath) {
  27:      const extname = path.extname(filePath);
  28:      switch (extname) {
  29:          case '.htm':
  30:          case '.html':

And listen connction


  44:  // Handle WebSocket connections
  45:  wss.on('connection', (ws) => {
  46:      console.log('A new client connected!');
  47:   
  48:      // Send a welcome message to the client
  49:      ws.send('Welcome! You are now connected to the server.');
  50:   
  51:      // Handle messages from the client
  52:      ws.on('message', (message) => {
  53:          console.log(`Received: ${message}`);
  54:   
  55:          // Broadcast the message to all connected clients
  56:          wss.clients.forEach((client) => {
  57:              if (client !== ws && client.readyState === WebSocket.OPEN) {
  58:                  client.send(`User says: ${message}`);
  59:              }
  60:          });
  61:      });

Interesting trouble, that WS library is Common.js, but in all this my test Node.js projects I used Module, therefore I made wrapper what can transform request to CommonJs to request to Module.


   1:  import { createRequire } from 'module';
   2:  const require = createRequire(import.meta.url);
   3:   
   4:  // Import the 'ws' library using CommonJS require
   5:  const WebSocket = require('ws');
   6:   
   7:  // Export the WebSocket class
   8:  export default WebSocket;

Frontend in this test project doing nothing, just WS.send value from input field.



To start project:

            # npm install ws
            # node server.js
            # http://localhost:3000
        

I have published project template to Github https://github.com/AAlex-11/JsWebSocketPush.

10. SSE project template with Vue.

I have no big experience with Vue, I just have no order to programming with Vue, but common idea of Vue the same as Angular, also Vue allow to use JS instead TS, that can be easy in sometimes.

Service code in Vue the same as Angular, Backend of course also the same, Code of component the same (with small differences, for example instead ngOnInit() in Angular we use created() in Vue), Html page the same, instead <app-root></app-root> in Angular we need to use <div id="app"></div>.

There are only one difference, instead main.ts in Angular:


   1:  import { bootstrapApplication } from '@angular/platform-browser'; // Import bootstrap function
   2:  import { appConfig } from './app/app.config'; // Import application configuration
   3:  import { AppComponent } from './app/app.component'; // Import the root component
   4:   
   5:  // Bootstrap the AppComponent with the appConfig
   6:  bootstrapApplication(AppComponent, appConfig)
   7:    .catch((err) => console.error(err)); // Log any errors during bootstrap
   8:   

We need to use Vue starter - App.vue:


   1:  <template>
   2:    <div id="app">
   3:      <SseMessages />
   4:    </div>
   5:  </template>
   6:   
   7:  <script>
   8:  import SseMessages from './components/SseMessages.vue';
   9:   
  10:  export default {
  11:    components: {
  12:      SseMessages,
  13:    },
  14:  };
  15:  </script>


To start project:

            # npm install -g @vue/cli
            # vue create sse-vue-project
            # cd sse-vue-project
            # npm install @vue/cli-service --save-dev
            # npm install vue-template-compiler --save-dev
            # npm install rxjs
            # npm run serve
            # node server.js
            # http://localhost:4200
        

I have published project template to Github https://github.com/AAlex-11/VueSse.


11. SSE project template with React.

Service code the same as in Angular and Vue, Backend the same, Component of course is React specific with return:


   1:  import React, { useState, useEffect } from 'react';
   2:  import { SseService } from '../../src/services/sseService';
   3:   
   4:  const SseMessages = () => {
   5:      const [message, setMessage] = useState(null);
   6:      const sseService = new SseService();
   7:   
   8:      useEffect(() => {
   9:          // Connect to the SSE endpoint
  10:          sseService.connect('http://localhost:3000/sse', (event) => {
  11:              const data = JSON.parse(event.data);
  12:              setMessage(`${data.body} <br> Timestamp: ${event.timeStamp}`);
  13:          }, (error) => {
  14:              console.error('SSE error:', error);
  15:          });
  16:   
  17:          // Cleanup on component unmount
  18:          return () => {
  19:              sseService.disconnect();
  20:          };
  21:      }, []); // Empty dependency array ensures this runs only once
  22:   
  23:      return (
  24:          <div>
  25:              <h1>SSE Messages</h1>
  26:              <div dangerouslySetInnerHTML={{ __html: message || 'Waiting for messages...' }} />
  27:          </div>
  28:      );
  29:  };
  30:   
  31:  export default SseMessages;

And project starter is React specific.


   1:  import React from 'react';
   2:  import SseMessages from './components/SseMessages';
   3:   
   4:  function App() {
   5:      return (
   6:          <div className="App">
   7:              <SseMessages />
   8:          </div>
   9:      );
  10:  }
  11:   
  12:  export default App;


To start project:

            # npx create-react-app sse-react-project
            # cd sse-react-project
            # npm start
            # node server.js
            # http://localhost:3000
        

I have published project template to Github https://github.com/AAlex-11/ReactSse.



Browser context:



FrontLearning context:



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23>  <24>  <25
Link to this page: http://www.vb-net.com/PushNotification/Index.htm
<TAGS>  <ARTICLES>  <FRONT>  <CORE>  <MVC>  <ASP>  <NET>  <DATA>  <TASK>  <XML>  <KIOSK>  <NOTES>  <SQL>  <LINUX>  <MONO>  <FREEWARE>  <DOCS> <TRAVELS> <FLOWERS> <RESUME> < THANKS ME>