How to notify users about content change in a Quasar SPA app
-
@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 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?
-
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 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.
-
@Mickey58 I think it’s generated by Workbox.
https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa
https://github.com/smolinari/quasar-docs/blob/master/quasar.conf.js
-
@dobbel - thanks, Workbox is one of those powerful PWA specific features that aren’t available yet to me because I’m still reluctant to make the move from SPA to PWA
-
@dobbel, @FrankM - I have now prototyped web sockets. Using socket.io on backend and frontend. The code is much simpler than the code for push notifications. And web sockets are easier to integrate with Quasar, because you can listen and handle socket events triggered by the backend in the frontend main component code (my-layout.vue) and directly react to such a socket event e.g. through a Quasar QNotify notification with a refresh action.
The only little caveat I have with these web sockets is this: It looks like it is not straightforward to exclude the client that created the new content from a “new content” notification. There is a so called “broadcast” feature in socket.io which excludes the “initiator”, but, unless I misunderstand the concept, the “initiator” is always the backend server, and it is up to the programmer to relate it to a user on a client, and exclude that client from the notfication. Not sure yet how to do that with web sockets.
-
@Mickey58 Instead of trying to prevent the message to be send to the creator. You could also keep track in the client that you are the creator and because of that the creator client will not respond to the push message for his own created content.
-
@dobbel - thanks, good hint. It looks like I hadn’t seen the forest (frontend solution) for the trees (socket APIs at the backend).
-
@Mickey58 Nice. I find websockets more fitting for UI update tasks than push notifications/service workers. The latter one are really only important if you need to notify users that are not currently using your app.
-
@FrankM - agree completely. Web sockets (compared to push notfications/service workers):
-
are more powerful, given the richness of the socket.io API (except this “notify disconnected users” which is not important to me)
-
are easier to set up
-
can be used with http for dev environments
-
can be easily integrated with Quasar. I now put the web socket setup into a Quasar boot file, so I have them available in all components through $socket.
-
are not dependent on experimental browser functions (push notifications still are classified “experimental”, afaik)
Thanks for your advise.
-