Notifier.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { playSound, topicDisplayName, topicShortUrl, urlB64ToUint8Array } from "./utils";
  2. import { toNotificationParams } from "./notificationUtils";
  3. import prefs from "./Prefs";
  4. import routes from "../components/routes";
  5. /**
  6. * The notifier is responsible for displaying desktop notifications. Note that not all modern browsers
  7. * support this; most importantly, all iOS browsers do not support window.Notification.
  8. */
  9. class Notifier {
  10. async notify(subscription, notification) {
  11. if (!this.supported()) {
  12. return;
  13. }
  14. await this.playSound();
  15. const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic);
  16. const defaultTitle = topicDisplayName(subscription);
  17. console.log(`[Notifier, ${shortUrl}] Displaying notification ${notification.id}`);
  18. const registration = await this.serviceWorkerRegistration();
  19. await registration.showNotification(
  20. ...toNotificationParams({
  21. subscriptionId: subscription.id,
  22. message: notification,
  23. defaultTitle,
  24. topicRoute: new URL(routes.forSubscription(subscription), window.location.origin).toString(),
  25. })
  26. );
  27. }
  28. async playSound() {
  29. // Play sound
  30. const sound = await prefs.sound();
  31. if (sound && sound !== "none") {
  32. try {
  33. await playSound(sound);
  34. } catch (e) {
  35. console.log(`[Notifier] Error playing audio`, e);
  36. }
  37. }
  38. }
  39. async webPushSubscription(hasWebPushTopics) {
  40. if (!this.pushPossible()) {
  41. throw new Error("Unsupported or denied");
  42. }
  43. const pushManager = await this.pushManager();
  44. const existingSubscription = await pushManager.getSubscription();
  45. if (existingSubscription) {
  46. return existingSubscription;
  47. }
  48. // Create a new subscription only if there are new topics to subscribe to. It is possible that Web Push
  49. // was previously enabled and then disabled again in which case there would be an existingSubscription.
  50. // If, however, it was _not_ enabled previously, we create a new subscription if it is now enabled.
  51. if (hasWebPushTopics) {
  52. return pushManager.subscribe({
  53. userVisibleOnly: true,
  54. applicationServerKey: urlB64ToUint8Array(config.web_push_public_key),
  55. });
  56. }
  57. return undefined;
  58. }
  59. async pushManager() {
  60. return (await this.serviceWorkerRegistration()).pushManager;
  61. }
  62. async serviceWorkerRegistration() {
  63. const registration = await navigator.serviceWorker.getRegistration();
  64. if (!registration) {
  65. throw new Error("No service worker registration found");
  66. }
  67. return registration;
  68. }
  69. notRequested() {
  70. return this.supported() && Notification.permission === "default";
  71. }
  72. granted() {
  73. return this.supported() && Notification.permission === "granted";
  74. }
  75. denied() {
  76. return this.supported() && Notification.permission === "denied";
  77. }
  78. async maybeRequestPermission() {
  79. if (!this.supported()) {
  80. return false;
  81. }
  82. return new Promise((resolve) => {
  83. Notification.requestPermission((permission) => {
  84. resolve(permission === "granted");
  85. });
  86. });
  87. }
  88. supported() {
  89. return this.browserSupported() && this.contextSupported();
  90. }
  91. browserSupported() {
  92. return "Notification" in window;
  93. }
  94. pushSupported() {
  95. return config.enable_web_push && "serviceWorker" in navigator && "PushManager" in window;
  96. }
  97. pushPossible() {
  98. return this.pushSupported() && this.contextSupported() && this.granted() && !this.iosSupportedButInstallRequired();
  99. }
  100. /**
  101. * Returns true if this is a HTTPS site, or served over localhost. Otherwise the Notification API
  102. * is not supported, see https://developer.mozilla.org/en-US/docs/Web/API/notification
  103. */
  104. contextSupported() {
  105. return window.location.protocol === "https:" || window.location.hostname.match("^127.") || window.location.hostname === "localhost";
  106. }
  107. iosSupportedButInstallRequired() {
  108. // no PushManager when not installed, but it _is_ supported.
  109. return config.enable_web_push && "serviceWorker" in navigator && window.navigator.standalone === false;
  110. }
  111. }
  112. const notifier = new Notifier();
  113. export default notifier;