Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Sample colorized icon in desktop notification from browser

I am considering adding desktop notifications to my fork of InfCloud, and to do that I had to learn how to send desktop notifications. MDN did not disappoint!

You'll see that I do some very basic find-button logic to add the click function. Of course this is boilerplate and won't look exactly the same whenever I get around to adding it to InfCloud/CalDAVZap.

files/2023/11/listings/notifications.html (Source)

<html>
<header>
<title>Example page</title>
<script src="notifications.js">
</script>
</header>
<body>
<h1>example</h1>
Lorem ipsum, etc.
<button>Notify me!</button>
</body>
<footer>
</footer>
</html>
files/2023/11/listings/notifications.js (Source)
// vim: set et ts=3 sw=3 sts=3:
// Startdate: 2023-11-09 18:36
// Purpose: Reference implementation for sending a notification with a colorized image from this server
// Reference:
//    https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
//    https://www.w3docs.com/snippets/html/how-to-display-base64-images-in-html.html
//    https://stackoverflow.com/questions/28450471/convert-inline-svg-to-base64-string
const img = "/calendar/images/banner_calendar.svg";
const notification_attempts = 10;
const notification_ms = 200;
// Due to how browsers/css work with colors, you can pass #000000 or web safe color names.
var colors = Array("red","orange","yellow","green","blue","purple","black","white","brown");
function colorizeSvg(inSvg, oldColor, newColor) {
   // In case we have to do more than `s//g` in the future.
   return inSvg.replaceAll(oldColor, newColor);
}
function SendColorizedNotification(_title, _body, _icon, _tag, _color) {
   // given _icon as path on server, get contents and adjust the main color in it to desired color.
   // The 585858 is the specific color of the calendar.svg we intend to replace.
   const xhr = new XMLHttpRequest();
   xhr.open("GET",_icon);
   xhr.onload = () => {
      newSvg = colorizeSvg(xhr.responseText,"#585858",_color);
      encodedData = window.btoa(newSvg); // turn it into the base64 stream
      encodedData = "data:image/svg+xml;base64," + encodedData; // prepend the type of stream
      SendNotification(_title, _body, encodedData, _tag, _color);
   }
   xhr.send();
}
function SendNotification(_title, _body, _icon, _tag) {
   // From: https://developer.mozilla.org/en-US/docs/Web/API/notification
   if (!("Notification" in window)) {
      // Check if the browser supports notifications
      alert("This browser does not support desktop notification");
   } else if (Notification.permission === "granted") {
      // Check whether notification permissions have already been granted;
      // if so, create a notification
      const notification = new Notification(String(_title), { tag: _tag, body: _body, icon: _icon });
      // …
   } else if (Notification.permission !== "denied") {
      // We need to ask the user for permission
      Notification.requestPermission().then((permission) => {
         // If the user accepts, let's create a notification
         if (permission === "granted") {
            const notification = new Notification(String(_title), { tag: _tag, body: _body, icon: _icon });
            // …
         }
      });
   }
   // At last, if the user has denied notifications, and you
   // want to be respectful there is no need to bother them anymore.
}
function SendNotificationManyTimes(_title, _body, _icon, _tag) {
   // From: https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
   if (Notification?.permission === "granted") {
      // If the user agreed to get notified
      // Let's try to send ten notifications
      let i = 0;
      // Using an interval cause some browsers (including Firefox) are blocking notifications if there are too much in a certain time.
      const interval = setInterval(() => {
         // Thanks to the tag, we should only see the "Hi! 9" notification
         const n = new Notification(String(_title), { tag: _tag, body: _body, icon: _icon });
         //const n = new Notification(`Hi! breakfast ${i}`, { tag: "soManyNotification", body: "this is body?", icon: img });
         if (i === (notification_attempts-1)) {
            clearInterval(interval);
         }
         i++;
      }, notification_ms);
   } else if (Notification && Notification.permission !== "denied") {
      // If the user hasn't told if they want to be notified or not
      // Note: because of Chrome, we are not sure the permission property
      // is set, therefore it's unsafe to check for the "default" value.
      Notification.requestPermission().then((status) => {
         // If the user said okay
         if (status === "granted") {
            let i = 0;
            // Using an interval cause some browsers (including Firefox) are blocking notifications if there are too much in a certain time.
            const interval = setInterval(() => {
               // Thanks to the tag, we should only see the "Hi! 9" notification
               const n = new Notification(String(_title), { tag: _tag, body: _body, icon: _icon });
               if (i === (notification_attempts-1)) {
                  clearInterval(interval);
               }
               i++;
            }, notification_ms);
         } else {
            // Otherwise, we can fallback to a regular modal alert
            alert(String(_title)+": "+String(_body));
         }
      });
   } else {
      // If the user refuses to get notified, we can fallback to a regular modal alert
      alert(String(_title)+": "+String(_body));
   }
}
function getRandomColor() {
   return colors[Math.floor(Math.random()*colors.length)];
}
window.addEventListener("load", () => {
   const button = document.querySelector("button");
   button.addEventListener("click", () => {
      SendColorizedNotification("sample calendar appointment","in 10 minutes",img,"",getRandomColor());
   });
});

Comments