production-taskbar-client / src / renderer / features / informing / informingSlice.js
informingSlice.js
Raw
import { createSlice } from "@reduxjs/toolkit";
import DOMPurify from "dompurify";
import _ from "lodash";

const defaultCloseDelay = 45; // in min

const addUrlAbsolutePath = (content) => {
  const url = new URL(process.env.BACKEND_URL);
  content = content.replaceAll(/src="/g, `src="${url.origin}`);
  content = content.replaceAll(/url\("/g, `url("${url.origin}`);
  return content;
};

const replaceHtmlTag = (content) => {
  return content.replaceAll(/body/g, "div");
};

const prepareContent = (content) => {
  content = addUrlAbsolutePath(content);
  content = replaceHtmlTag(content);
  return DOMPurify.sanitize(content);
};

const parseTitle = (content) => {
  const re = /(?<=<title>).*?(?=<\/title>)/;
  const match = content.match(re);
  if (match) return match[0];
  return "";
};

export const informingSlice = createSlice({
  name: "informing",
  initialState: {
    notificationId: undefined,
    content: undefined,
    needConfirmation: false,
    closeDelay: defaultCloseDelay,
    closeTimer: defaultCloseDelay,
    isOverlay: false,
    title: "Notification",
    queue: [],
  },
  reducers: {
    newNotification: (state, action) => {
      if (action.payload && !_.isEmpty(action.payload)) {
        const { text } = action.payload;
        const {
          notification_id: notificationId,
          content,
          need_confirmation: needConfirmation,
          close_delay: closeDelay,
          is_overlay: isOverlay,
        } = JSON.parse(text);

        const title = parseTitle(content);
        const prepared = prepareContent(content);

        // if notification is showing now and is'nt overlay, pu`t in queue
        if (state.notificationId && !state.isOverlay) {
          const n = _.find(state.queue, { notificationId });
          if (!n && state.notificationId !== notificationId && !isOverlay)
            state.queue.push({
              title,
              content: prepared,
              notificationId,
              needConfirmation,
              closeDelay,
              isOverlay,
            });
        } else {
          state.title = title;
          state.content = prepared;
          state.notificationId = notificationId;
          state.needConfirmation = needConfirmation;
          state.closeDelay = closeDelay;
          state.closeTimer = closeDelay;
          state.isOverlay = isOverlay;
        }
      }
    },
    clearNotification: (state) => {
      // get next notification from queue or clear
      if (!_.isEmpty(state.queue)) {
        const n = state.queue.shift();
        state.notificationId = n.notificationId;
        state.content = n.content;
        state.needConfirmation = n.needConfirmation;
        state.closeDelay = n.closeDelay;
        state.closeTimer = n.closeDelay;
        state.isOverlay = n.isOverlay;
        state.title = n.title;
      } else {
        state.notificationId = undefined;
        state.content = undefined;
        state.needConfirmation = false;
        state.closeDelay = defaultCloseDelay;
        state.closeTimer = defaultCloseDelay;
        state.isOverlay = false;
        state.title = "";
      }
    },
    decrementCloseTimer: (state, { payload }) => {
      if (state.closeTimer > 0) state.closeTimer -= payload;
    },
  },
});

export const { newNotification, clearNotification, decrementCloseTimer } =
  informingSlice.actions;

export default informingSlice.reducer;