Source

services/NotificationManager.ts

// RECEIVED notification format
//
// message: "",
// title: "" (optional)


// SENT notification format
//
// id: UUID,
// message: "",
// title: "", (optional)
// level: warn|success|information|error
export type Subscriber = {
    id: string,
    pushHandle: (notification: Notification) => any | undefined
}

type NotificationLevels = "warn" | "success" | "information" | "error"

export type Notification = {
    id?: string,
    message: string,
    title?: string,
    level?: NotificationLevels
}

/**
 * The NotificationManager is a centralised means to send / receive notifications within an SPA.
 *
 * The export instantiates a NotificationManager to provide a singleton instance of the manager, so that all subscribers
 * and all senders are using the same manager.
 *
 * The instantiated notification manager can be imported from the standard webapp library, i.e.
 *
 * ```
 *      import { notifications } from '@jcu/spark'`
 * ```
 *
 * The `notifications` object can then be used to attach subscribers to receive notifications:
 *
 * ```
 *      notifications.subscribe('myNotifID', (notification) => {//Do Something})
 * ```
 *
 *  It can also be used to send notifications to any existing subscribers:
 *
 * ```
 *      notifications.notify({
 *          title: 'Email not saved',
 *          message: 'Your profile settings changed; check your profile to see the updated information.'
 *      })
 *  ```
 *
 *  By default, the JCU web app framework comes with a popup notification layer automatically subscribed. Therefore all
 *  SPAs have capacity for popup notifications to be sent to the user.
 *
 *  @category Services
 */
class NotificationManager {
    /**
     * List of objects defining the currently subscribed consumers.
     */
    subscribers: Subscriber[] = []

    /**
     * Provides the ability for notification consumers to subscribe to the notification engine. Consumers provide a
     * function handle that takes in a Notification object, allowing the consumer to make decisions about how to handle
     * the Notification.
     *
     * @param {string} id Identity of notification consumer to subscribe to notifications
     * @param {function} pushHandle Handle to the consumer function that takes in a Notification
     */
    subscribe(id: string, pushHandle: (notification: Notification) => any | undefined) {
        this.subscribers.push({id, pushHandle})
    }

    /**
     * Unsubscribe a consumer from receiving notifications based on the provided ID.
     *
     * @param {string} id Identity of notification consumer to unsubscribe
     */
    unsubscribe(id: string) {
        this.subscribers = this.subscribers.filter((subscriber) => subscriber.id !== id)
    }

    /**
     * Publish a Notification to all current subscribers. Generates UID for each notification for unique identification.
     *
     * @param {Notification} notification Notification to send out to subscribers
     * @param {Object} options Extra notification options, mostly used by subscribers
     */
    publishNotification(notification: Notification, options = {}) {
        //TODO: Proper UUID generation
        notification.id = Math.random().toString(36).substring(2, 8) + Math.random().toString(36).substring(2, 8);

        for (let subscriber in this.subscribers) {
            // the 'as' notation means it is type checked for DEFINED keys, but won't error on undefined extra keys (e.g. ...options)
            this.subscribers[subscriber].pushHandle({...notification, ...options} as Notification)
        }
    }

    /**
     * Wrapper function for sending a notification with a level of "warn".
     *
     * Can take a Notification object or a string. If a string is provided, it is converted into the message portion of a
     * standard Notification.
     *
     * @param {Notification | string} notification Notification / message to send out to subscribers
     * @param {Object} options Extra notification options, mostly used by subscribers
     */
    warn(notification: Notification | string, options = {}) {
        let message, title

        if (typeof notification === "string") {
            message = notification
        } else {
            message = notification.message
            title = notification.title
        }

        const newNotification: Notification = {
            level: "warn",
            message,
            title
        }
        this.publishNotification(newNotification, options)
    }

    /**
     * Wrapper function for sending a notification with a level of "error".
     *
     * Can take a Notification object or a string. If a string is provided, it is converted into the message portion of a
     * standard Notification.
     *
     * @param {Notification | string} notification Notification / message to send out to subscribers
     * @param {Object} options Extra notification options, mostly used by subscribers
     */
    error(notification: Notification | string, options = {}) {
        let message, title

        if (typeof notification === "string") {
            message = notification
        } else {
            message = notification.message
            title = notification.title
        }

        const newNotification: Notification = {
            level: "error",
            message,
            title
        }
        this.publishNotification(newNotification, options)
    }

    /**
     * Wrapper function for sending a notification with a level of "information".
     *
     * Can take a Notification object or a string. If a string is provided, it is converted into the message portion of a
     * standard Notification.
     *
     * @param {Notification | string} notification Notification / message to send out to subscribers
     * @param {Object} options Extra notification options, mostly used by subscribers
     */
    notify(notification: Notification | string, options = {}) {
        let message, title

        if (typeof notification === "string") {
            message = notification
        } else {
            message = notification.message
            title = notification.title
        }

        const newNotification: Notification = {
            level: "information",
            message,
            title
        }
        this.publishNotification(newNotification, options)
    }

    /**
     * Wrapper function for sending a notification with a level of "success".
     *
     * Can take a Notification object or a string. If a string is provided, it is converted into the message portion of a
     * standard Notification.
     *
     * @param {Notification | string} notification Notification / message to send out to subscribers
     * @param {Object} options Extra notification options, mostly used by subscribers
     */
    success(notification: Notification | string, options = {}) {
        let message, title

        if (typeof notification === "string") {
            message = notification
        } else {
            message = notification.message
            title = notification.title
        }

        const newNotification: Notification = {
            level: "success",
            message,
            title
        }
        this.publishNotification(newNotification, options)
    }
}

// Export singleton instantiation of NotificationManager so that all importers get the same manager.
export let notifications = new NotificationManager()