actions/notification-builder.js

const log = require('../log')('notification-action-builder');
const { randomUUID } = require('../utils');

/**
 * Gets the <code>NotificationAction</code> from the {@link https://www.openhab.org/addons/integrations/openhabcloud/ openHAB Cloud Connector} add-on.
 *
 * If the openHAB Cloud Connector is not installed, a warning is logged and <code>null</code> is returned.
 *
 * @private
 * @return {*|null}
 */
function _getNotificationAction () {
  try {
    return Java.type('org.openhab.io.openhabcloud.NotificationAction');
  } catch (e) {
    if (e.name === 'TypeError') {
      log.warn('NotificationAction is not available. Make sure you have installed the openHAB Cloud Connector.');
      return null;
    }
    throw e;
  }
}

class NotificationType {
  static BROADCAST = 'BROADCAST';
  static LOG = 'LOG';
  static STANDARD = 'STANDARD';
  static HIDE_BROADCAST = 'HIDE_BROADCAST';
  static HIDE_STANDARD = 'HIDE_STANDARD';
}

/**
 * Notification builder to create and send openHAB Cloud notifications.
 *
 * Do NOT use directly, use {@link actions.notificationBuilder} instead.
 */
class NotificationBuilder {
  #type = NotificationType.BROADCAST;
  #userIds = [];
  #message = '';
  #icon = null;
  #tag = null;
  #title = null;
  #referenceId = null;
  #onClickAction = null;
  #mediaAttachmentUrl = null;
  #actionButtons = [];

  /**
   * @hideconstructor
   * @param {string} [message] the optional body of the notification
   */
  constructor (message) {
    if (message !== undefined) this.#message = message;
  }

  /**
   * Sets the type of the notification to log notification.
   *
   * @return {NotificationBuilder}
   */
  logOnly () {
    this.#type = NotificationType.LOG;
    return this;
  }

  /**
   * Hides notifications with the specified reference ID or the specified tag.
   *
   * Reference ID has precedence over tag.
   * If no reference ID or tag is set, an error is thrown when {@link send} is called.
   *
   * @return {NotificationBuilder}
   */
  hide () {
    this.#type = (this.#userIds.length > 0 ? NotificationType.HIDE_STANDARD : NotificationType.HIDE_BROADCAST);
    return this;
  }

  /**
   * Adds a user ID, which usually is the mail address of an openHAB Cloud user, to send the notification to.
   *
   * If no user ID is specified, a broadcast notification is sent.
   *
   * @param {...string} emailAddress
   * @return {NotificationBuilder}
   */
  addUserId (...emailAddress) {
    this.#userIds = this.#userIds.concat(emailAddress);
    this.#type = (this.#type === NotificationType.HIDE_BROADCAST ? NotificationType.HIDE_STANDARD : NotificationType.STANDARD);
    return this;
  }

  /**
   * Sets the icon for the notification.
   *
   * See {@link https://www.openhab.org/docs/configuration/items.html#icon-sources} for valid icon sources.
   * Please note that not all push notification clients support displaying icons.
   *
   * @param {string} icon
   * @return {NotificationBuilder}
   */
  withIcon (icon) {
    this.#icon = icon;
    return this;
  }

  /**
   * Sets the tag for the notification.
   *
   * The tag is used for grouping notifications when displaying in the app and to hide/remove groups of notifications.
   *
   * @param {string} severity
   * @return {NotificationBuilder}
   */
  withTag (severity) {
    this.#tag = severity;
    return this;
  }

  /**
   * Sets the title for the notification.
   *
   * @param {string} title
   * @return {NotificationBuilder}
   */
  withTitle (title) {
    this.#title = title;
    return this;
  }

  /**
   * Sets the reference ID for the notification.
   *
   * The reference ID is a user-supplied identifier, that can be used to update or remove existing notifications with the same reference ID.
   *
   * @param {string} referenceId
   * @return {NotificationBuilder}
   */
  withReferenceId (referenceId) {
    this.#referenceId = referenceId;
    return this;
  }

  /**
   * Sets the action to be executed when the user clicks on the notification.
   *
   * The on click action is not supported by log notifications.
   *
   * @param {string} action the action using the syntax as described in {@link https://www.openhab.org/addons/integrations/openhabcloud/#action-syntax openHAB Cloud Connector: Action Syntax}
   * @return {NotificationBuilder}
   */
  withOnClickAction (action) {
    this.#onClickAction = action;
    return this;
  }

  /**
   * Sets the URL to a media attachment to be displayed with the notification.
   *
   * This URL must be reachable by the push notification client and the client needs to support media attachments.
   * Media attachments are not supported by log notifications.
   *
   * @param {string} mediaAttachmentUrl the media attachment URL as described in {@link https://www.openhab.org/addons/integrations/openhabcloud/ openHAB Cloud Connector}
   * @return {NotificationBuilder}
   */
  withMediaAttachmentUrl (mediaAttachmentUrl) {
    this.#mediaAttachmentUrl = mediaAttachmentUrl;
    return this;
  }

  /**
   * Adds an action button to the notification.
   *
   * Please note that due to limitations in Android and iOS only three action buttons are supported.
   * Action buttons are obviously not supported by log notifications.
   *
   * @param {string} label the title of the action button
   * @param {string} action the action using the syntax as described in {@link https://www.openhab.org/addons/integrations/openhabcloud/#action-syntax openHAB Cloud Connector: Action Syntax}
   * @return {NotificationBuilder}
   */
  addActionButton (label, action) {
    if (this.#actionButtons.length >= 3) {
      throw new Error('Only 3 action buttons are supported.');
    }
    this.#actionButtons.push(`${label}=${action}`);
    return this;
  }

  /**
   * Sends the notification.
   *
   * If no reference ID is set, a random reference ID is generated.
   * In case the openHAB Cloud Connector is not installed, a warning is logged and the notification is not sent.
   *
   * @return {string|null} the reference ID of the notification or `null` log notifications and when hiding notifications
   * @throws {Error} if {@link hide} was called and no reference ID or tag is set
   */
  send () {
    while (this.#actionButtons.length < 3) {
      this.#actionButtons.push(null);
    }

    switch (this.#type) {
      case NotificationType.BROADCAST:
        if (this.#referenceId === null) this.#referenceId = randomUUID();
        // parameters: message, icon, tag, title, referenceId, onClickAction, mediaAttachmentUrl, actionButton1, actionButton2, actionButton3
        _getNotificationAction()?.sendBroadcastNotification(this.#message, this.#icon, this.#tag, this.#title, this.#referenceId, this.#onClickAction, this.#mediaAttachmentUrl, ...this.#actionButtons);
        return this.#referenceId;
      case NotificationType.LOG:
        // parameters: message, icon, tag
        _getNotificationAction()?.sendLogNotification(this.#message, this.#icon, this.#tag);
        return null;
      case NotificationType.STANDARD:
        if (this.#referenceId === null) this.#referenceId = randomUUID();
        this.#userIds.forEach((userId) => {
          // parameters: userId, message, icon, tag, title, referenceId, onClickAction, mediaAttachmentUrl, actionButton1, actionButton2, actionButton3
          _getNotificationAction()?.sendNotification(userId, this.#message, this.#icon, this.#tag, this.#title, this.#referenceId, this.#onClickAction, this.#mediaAttachmentUrl, ...this.#actionButtons);
        });
        return this.#referenceId;
      case NotificationType.HIDE_BROADCAST:
        if (this.#referenceId === null && this.#tag === null) throw new Error('Reference ID or tag must be set for hiding notifications.');
        // referenceId has precedence over tag
        if (this.#referenceId !== null) {
          // parameters: referenceId
          _getNotificationAction()?.hideBroadcastNotificationByReferenceId(this.#referenceId);
        } else {
          // parameters: tag
          _getNotificationAction()?.hideBroadcastNotificationByTag(this.#tag);
        }
        return null;
      case NotificationType.HIDE_STANDARD:
        if (this.#referenceId === null && this.#tag === null) throw new Error('Reference ID or tag must be set for hiding notifications.');
        // referenceId has precedence over tag
        if (this.#referenceId !== null) {
          // parameters: userId, referenceId
          this.#userIds.forEach((userId) => {
            _getNotificationAction()?.hideNotificationByReferenceId(userId, this.#referenceId);
          });
        } else {
          // parameters: userId, tag
          this.#userIds.forEach((userId) => {
            _getNotificationAction()?.hideNotificationByTag(userId, this.#tag);
          });
        }
        return null;

      default:
        throw new Error(`Unknown NotificationType: ${this.type}`);
    }
  }
}

module.exports = {
  _getNotificationAction,
  /**
   * Creates a new notification builder for openHAB Cloud notifications, which are sent as push notifications to registered devices.
   *
   * This requires the {@link https://www.openhab.org/addons/integrations/openhabcloud/ openHAB Cloud Connector} add-on to be installed.
   *
   * There are three types of notifications:
   *
   * Broadcast notifications, which are sent to all openHAB Cloud users,
   * standard notifications, which are sent to a openHAB Cloud users specified by their email addresses,
   * and log notifications, which are only sent to the notification log and not shown as a push notification.
   *
   * In addition to that, notifications can be updated later be re-using the same reference ID and hidden/removed either by reference ID or tag.
   *
   * @memberof actions
   * @param {string} [message] the body of the notification
   * @return {NotificationBuilder}
   */
  notificationBuilder: (message) => new NotificationBuilder(message)
};