diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index f2207d194..ccc4c898a 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -14,6 +14,7 @@ import { StatefulComponent } from './StatefulComponent.js'; import { iconCheck, iconLinkIntact } from '../icons.js'; import { h } from '../renderer.js'; +import { isContextSecure } from '../utilities/browserContext.js'; /** * Represents a component that allows copying text to the clipboard. @@ -49,7 +50,7 @@ export class CopyToClipboardComponent extends StatefulComponent { * @returns {void} */ checkClipboardAvailability() { - if (!this.isContextSecure()) { + if (!isContextSecure()) { throw new Error('Clipboard not available in a non-secure context.'); } @@ -62,15 +63,6 @@ export class CopyToClipboardComponent extends StatefulComponent { } } - /** - * Checks if context is secure (HTTPS) - * - * @returns {boolean} Returns `true` if context is secure - */ - isContextSecure() { - return window.isSecureContext; - } - /** * Checks if the clipboard API is available in the user's browser. * diff --git a/Framework/Frontend/js/src/index.js b/Framework/Frontend/js/src/index.js index 3541c843e..27391cf72 100644 --- a/Framework/Frontend/js/src/index.js +++ b/Framework/Frontend/js/src/index.js @@ -26,6 +26,7 @@ export { default as switchCase } from './switchCase.js'; export { documentClickTaggedEventRegistry } from './utilities/documentClickTaggedEventRegistry.js'; export { buildUrl } from './utilities/buildUrl.js'; export { parseUrlParameters } from './utilities/parseUrlParameters.js'; +export { isContextSecure } from './utilities/browserContext.js'; // Formatters export { formatTimeDuration } from './formatter/formatTimeDuration.js'; @@ -54,3 +55,5 @@ export * from './icons.js'; export * from './chart.js'; export * from './Notification.js'; + +export * from './utilities/browserNotification.js'; diff --git a/Framework/Frontend/js/src/utilities/browserContext.js b/Framework/Frontend/js/src/utilities/browserContext.js new file mode 100644 index 000000000..9b73dbd15 --- /dev/null +++ b/Framework/Frontend/js/src/utilities/browserContext.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/* Global: window */ + +/** + * Checks whether the context is secure (HTTPS) + * @returns {boolean} Returns `true` if context is secure, `false` otherwise. + */ +export const isContextSecure = () => + window.isSecureContext; diff --git a/Framework/Frontend/js/src/utilities/browserNotification.js b/Framework/Frontend/js/src/utilities/browserNotification.js new file mode 100644 index 000000000..293e608b6 --- /dev/null +++ b/Framework/Frontend/js/src/utilities/browserNotification.js @@ -0,0 +1,104 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/* Global: window */ + +import { isContextSecure } from './browserContext.js'; + +/** + * Browser notification permission values. + * Mirrors the Notification API specification. + * @link https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission_static + */ +export const BrowserNotificationPermission = Object.freeze({ + GRANTED: 'granted', + DEFAULT: 'default', + DENIED: 'denied', +}); + +/** + * Get the current browser notification permission. + * @returns {BrowserNotificationPermission|undefined} One of {@link BrowserNotificationPermission}, or `undefined` if unsupported. + */ +export const getBrowserNotificationPermission = () => + window?.Notification?.permission; + +/** + * Request browser notification permission (async/await). + * @returns {Promise} One of {@link BrowserNotificationPermission}, or `undefined` if unsupported + */ +export const requestBrowserNotificationPermissions = async () => { + if (!isContextSecure()) { + return undefined; + } + + const permission = getBrowserNotificationPermission(); + if (permission === BrowserNotificationPermission.GRANTED) { + return permission; + } + + return await window.Notification?.requestPermission(); +}; + +/** + * Check if notifications can be shown immediately. + * @returns {boolean} `true` if the Notification API is available, the context is secure + * and the current {@link BrowserNotificationPermission} is {@link BrowserNotificationPermission.GRANTED}, `false` otherwise. + */ +export const areBrowserNotificationsGranted = () => + isContextSecure() && getBrowserNotificationPermission() === BrowserNotificationPermission.GRANTED; + +/** + * @typedef {object} NotificationOptions + * @property {string} title Notification title + * @property {string} [body] Notification body + * @property {string} [icon] Notification icon URL (defaults to server icon) + * @property {(event: Event) => void} [onclick] Triggered when the notification is clicked + * @property {(event: Event) => void} [onerror] Triggered if the notification fails to display + * @property {(event: Event) => void} [onshow] Triggered when the notification is shown + * @property {(event: Event) => void} [onclose] Triggered when the notification is closed + */ + +/** + * Show a native browser notification. + * @param {NotificationOptions} options - The browser notification options + * @returns {Notification|null} {@link Notification} instance, or `null` if unavailable + */ +export const showNativeBrowserNotification = (options) => { + if (!areBrowserNotificationsGranted()) { + return null; + } + + const { + title, + onclick, + onerror, + onshow, + onclose, + icon = '/favicon.ico', + ...notificationOptions + } = options; + if (!title) { + return null; + } + + const notification = new window.Notification(title, { icon, ...notificationOptions }); + Object.entries({ onclick, onerror, onshow, onclose }) + .filter(([_, eventFunc]) => typeof eventFunc === 'function') + .forEach(([eventName, eventFunc]) => { + notification[eventName] = eventFunc; + }); + + return notification; +};