How to notify users about content change in a Quasar SPA app



  • I have a Quasar based web app (SPA). I would like to notify users about new content available, preferably by a Quasar QNotify message coming up in the browser (for all logged in users). It appears that the Quasar docs web site itself offers that kind of a mechanism, but I’m not sure how it can be implemented for my SPA.

    My Node backend code is the source of wisdom about new content. I see different possible mechanisms to achieve this - push notifications, service workers, browser notfications. I know these mechanisms are not Quasar functions, but I need to select the right mechanism and get it working together with my Quasar app.

    In https://quasar.dev/quasar-cli/developing-pwa/handling-service-worker#Interacting-with-Service-Worker there is documentation how to interact from a Quasar app with a service worker, but it seems to be specific to PWAs, while my Quasar app is SPA.

    Are there any pointers or examples how to achieve this?

    Update: Somebody else posted this question on Stackoverflow a while ago, but no answer:
    https://stackoverflow.com/questions/61546909/using-a-service-worker-in-a-quasar-spa



  • Basically, with service workers you can receive push messages without your app/website having focus on the user’s device (i.e. being in the background or even not running at all in case of native apps)

    For a simple “content updated” message this is too much imo. For a simple web app you could simply do a setInterval checking the backend regularly for updates or set up a websocket connection.



  • If you want browser notifications, the way of doing it is to convert your SPA to PWA. It’s not that hard, really. And then use the Notification web Api.

    https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API

    https://dzone.com/articles/how-to-add-real-web-push-notifications-to-your-web

    Another way is to use a socket connection to to send a messages from your Nodejs Server to your client (SPA).

    https://alligator.io/vuejs/vue-socketio/



  • @FrankM, @dobbel - thanks for sharing your adivse.

    The potential conversion from SPA to PWA, though @dobbel said it isn’t that hard, sounds like a big overall change to my app. Probably not justified if only for the sake of this small enhancement.

    But honestly I don’t undestand the impacts and pros and cons of PWA vs. SPA.

    I’m a bit scared that I have to do a lot of regression testing and potential changes to my build and deployment process (locally and on Heroku Cloud) for PWA.



  • @Mickey58 It’s not hard because Quasar makes it easy. From the server’s point of view( Heroku) I don’t think it’s any different compared to a normal website . The bigger challenge I think would be to use the Notifications Web Api. ( I have no experience in that).

    Anyways the socket connection would relativity easy to implement because you use nodejs.



  • Actually, you can use all techniques (service workers, websockets) in both PWAs and SPAs, it’s more a question of backend design imo.

    In my app I have a comment system. I notify users when a new comment has been posted in a thread a user has posted to or is currently viewing
    Here’s what I do:

    • when new comment is posted, i generate a notification for every user that has subscribed to the thread in the backend

    • push a message over websocket to all users that a new comment has been posted

    • when a user is currently using the app (has focus), websocket push is received by the active browser instance

    • generate a native browser event from the websocket message (new CustomEvent)

    • set up a listener for the custom event on the route where the comment thread is displayed

    • if the user is actually browsing the thread in question, just update it with the new comment

    • if not (i.e. is somewhere else in the app) and has subscribed to thread, display a QNotification or generic browser notification. Or display a counter in your navigation/toolbar, depends on you app

    • if the user reads the new comment (set up a scroll observer / intersection, Quasar has nice features for this), inform the backend about this and delete the notification on the backend if user has a subscription to thread

    • setup cronjob on backend for notifications not “seen” by the user (like every 5 minutes)

    • send a push message over firebase to subscribers

    • service worker in my app listens to firebase messages and generates a push message

    • when user reacts to push and navigates to the thread, this is handled by QIntersection and backend is informed that the user has “seen” the comment (like before)

    • after 30 minutes, send out E-Mail notification from backend

    This is probably overengineered for a simple “content updated” thing

    The basic approach is: notify active user via websocket, inactive user via push, offline users via E-Mail. Don’t do it all at once, this is super annyoing. Keep track in your backend about notifications actually being read

    Implement “priorities”. For example, I dont’t send push messages via firebase when someone has not actively subscribed to a thread. But I do of course update the UI when the user is browsing the thread currently



  • A nice thing is that you can implement event listeners on specific routes/components and a more generic event handling on app/main layout level to act as fallback in case the handler on the specific route did not trigger because the user is on another route. Just make sure you setup listeners onMounted() or onCreate() and remove them before the component is destroyed



  • @FrankM that’s a very neat system( a bit jelly)!

    Do you only send Firebase push messages to the client? Or do also (first) send your ‘own’ websocket push messages to active clients?

    I am wondering if you have a example how to create a generic browser message ( from the websocket message)?

    Any code samples from your awesome notification system is of course very appreciated!



  • Wow, I’m impressed by @FrankM’'s “layered” notification approach. Gives me a lot of food for thought.

    My app is less “collaborative” though, so I’ll implement at least initially a simpler mechanism.

    Currently I’m looking at this tutorial: https://thecodebarbarian.com/sending-web-push-notifications-from-node-js.html

    I’ll probably try to put the server part of that code into my backend server.

    I’m not sure yet about the client part of that code. Maybe I can integrate that into my static Express web server for Quasar? Or does it belong into the Quasar my-layout.vue main code (mounted or created part)? The other tutorial @dobbel pointed to, says it is normally put into the index.js file of the frontend code…you see that my understanding of it is still too limited.

    [Update: In my prototype mentioned further below, I put the client part (registration & subscription) into created() of my-layout.vue]

    Then I still have to figure out where to place and find the service worker code itself (which is in a separate worker.js file in the tutorial) within my Quasar frontend code structure.



  • @dobbel I send websocket messages first to “connected” users and firebase pushs to users not active for 5 minutes or so. For simple UI updates I only use websockets only (like updating counters and stuff)

    On the client, I subscribe to the websocket messages (I use vue-wamp) and upon receiving, I fire a native browser event that can be handled by regular event listeners. This is set up in a “created” hook on app level or main layout, i.e. components that are always active when the app has been initialized and has focus.

    $wamp is globally exported to my components by a Quasar boot file

    this.$wamp.subscribe('myChannel', function (payload, args, event) {
                 const data = payload[0]; //custom data sent from the backend via websocket
                 var ev = new CustomEvent(data.identifier, { 
                    detail: data, //pass the backend payload to the custom event also
                    bubbles: true,
                    cancelable: true
                 });
                 const handled = !document.dispatchEvent(ev) //fire event
                 if (handled) { 
                    //event has been handled/consumed by another (child) component in the app
                 } else {
                    //handle event on app level if needed, e.g. fallback to generic browser notification
                 }
              }
    

    In a component, I create event-handler like:

    created() {
       document.addEventListener("my-event-identifier", this.eventHandler, false)
    },
    beforeDestroy() {
       document.removeEventListener("my-event-identifier", this.eventHandler, false)
    },
    methods: {
       eventHandler(event) {
          console.log(event.detail)
          //do stuff with event
       }
    }
    


  • @FrankM, @dobbel - can you take a quick look at my last append? I’m still struggling a bit with the basics of web push notications, unsure where the client code pieces belong in a Quasar app (still SPA at this point).



  • @FrankM Thanks for the samples! It helps a lot to understand.

    Looking at you samples I wonder where and how the Firebase push events are handled?



  • @dobbel

    To listen to firebase messages you would setup somthing like:

    firebase.js boot file

    import * as firebase from "firebase/app"
    import "firebase/messaging"
    
    firebase.initializeApp({
        apiKey: process.env.FIREBASEKEY,
        authDomain: "my-app.firebaseapp.com",
        projectId: "my-app",
        messagingSenderId: "123456780",
        appId: "myappid"
    })
    
    
    export default ({Vue}) => {
        Vue.prototype.$messaging = firebase.messaging()
        Vue.prototype.$messaging.usePublicVapidKey(myPublicVapid)
    }
    

    In created hook on app level/main layout component:

    this.$messaging.onMessage((msg) => { //listen to firebase
    const ev = new CustomEvent(msg.data.identifier, { //msg.data is backend defined payload
                    detail: msg.data, 
                    bubbles: true,
                    cancelable: true
                 });
                 const handled = !document.dispatchEvent(ev) //fire event
                 if (handled) { 
                    //event has been handled/consumed by another (child) component in the app
                 } else {
                    //handle event on app level if needed, e.g. fallback to generic browser notification
                 }
    })
    


  • @FrankM, @dobbel - I have been prototpying push notifications for my existing SPA app.

    It has been a bit tedious to set everything up, but I got it working. It consists of 3 pieces:

    • Registering a service worker and subscribing to push notifications - currently this resides in the created() part of my Quasar main layout (my-layout.vue)
    • Service worker - I placed this temporarily in the /statics folder of my frontend code. This piece of code receives the push notifcations and does showNotification()
    • Backend code: This piece of code is still experimental, it fires a push notification using the NPM web-push package

    I got the overall mechanics working.

    I’m a bit disappointed to learn that it is only for a single user. In order to notify all or selected groups of users, the subscription details would have to be stored for every user in my backend database. In order to notify all users, I would have to iterate through these details and create a push notification for every user.

    This web push mechanism is pretty complicated for what it delivers.

    I will probably experiment next with web sockets,



  • @Mickey58 it’s complicated, but once you nail it, you can use the paradigm on several apps that needs it, i think go over @FrankM breakdown above, it’s quite extensive. it’s a sought out feature in most apps, good luck.



  • @Mickey58 With sockets can send the same push message to every client that is currently connected to you nodejs server.

    @FrankM Thanks again for the extensive samples, it gives great insight how to setup push notifications.

    next step implement native push notifications for mobile devices statusbar build with cordova or capacitor…



  • @dobbel - yes, I had overlooked initially that these push notifications are from server to individual clients. If you want them for all clients, it is on you to to loop through all subscriptions and send push messages to every single client.

    I think @FrankM 's solution is the most powerful one, but probably even more complicated than my prototype for push notifications (which needs web-push package on the server).

    I hope web sockets can do what I need.

    @metalsadman - do you happen to know how Quasar has implemented the notifications that new content is available on the Quasar docs site?



  • @Mickey58 The source code of the quasar doc app ( pwa + ssr) is in github if you want to know…

    It’s a Pwa update notification that will notifiy the user that the content has changed on server and asks in a dialog if you want to refresh now or later.( you don’t need to ask , you can also just refresh)



  • @dobbel, yes, I see an app.use for a service worker in the code at https://github.com/smolinari/quasar-docs/blob/master/src-ssr/index.js, but couldn’t find the service-worker.js itself yet.




Log in to reply