diff --git a/package.json b/package.json index 93c8290..41ed765 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "classnames": "^2.3.1", "cross-env": "^7.0.3", "framer-motion": "4.1.17", + "howler": "^2.2.4", "i18next": "^21.2.4", "i18next-browser-languagedetector": "^6.1.2", "linkify-react": "^3.0.3", @@ -40,6 +41,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^10.4.9", "@testing-library/user-event": "^7.2.1", + "@types/howler": "^2.2.11", "@types/luxon": "^2.0.8", "eslint": "8.15.0", "eslint-config-airbnb": "^18.2.1", diff --git a/src/hooks/useGetActiveChats.ts b/src/hooks/useGetActiveChats.ts index 19721b8..fa4a40a 100644 --- a/src/hooks/useGetActiveChats.ts +++ b/src/hooks/useGetActiveChats.ts @@ -9,16 +9,16 @@ const useGetActiveChats = (): void => { const dispatch = useAppDispatch(); useEffect(() => { - const sseInstance = sse('cs-get-all-active-chats'); - - sseInstance.onMessage((data: Chat[]) => { + const onMessage = (data: Chat[]) => { if (data !== undefined) { dispatch(setActiveChats({ customerSupportId, data, checkNewMessages: true })); } - }); + }; + + const events = sse('cs-get-all-active-chats', onMessage); return () => { - sseInstance.close(); + events.close(); }; }, [customerSupportId, dispatch]); }; diff --git a/src/hooks/useGetNewMessages.ts b/src/hooks/useGetNewMessages.ts index ea09ff0..c5b72b2 100644 --- a/src/hooks/useGetNewMessages.ts +++ b/src/hooks/useGetNewMessages.ts @@ -12,14 +12,18 @@ const useGetNewMessages = (chatId: string | undefined): void => { useEffect(() => { if (!chatId || chatId === '-1' || !isAuthenticated || !selectedActiveChat) return undefined; - const sseInstance = sse(`cs-get-new-messages?chatId=${chatId}&lastRead=${lastReadMessageDate.split('+')[0]}`); - sseInstance.onMessage((data: MessageModel[]) => { + const onMessage = (data: MessageModel[]) => { dispatch(addNewMessages(data)); - }); + }; + + const events = sse( + `cs-get-new-messages?chatId=${chatId}&lastRead=${lastReadMessageDate.split('+')[0]}`, + onMessage + ); return () => { - sseInstance.close(); + events.close(); }; }, [isAuthenticated, dispatch, lastReadMessageDate, chatId, selectedActiveChat]); }; diff --git a/src/hooks/useNewMessageNotification.ts b/src/hooks/useNewMessageNotification.ts index 6bdcf10..077c17f 100644 --- a/src/hooks/useNewMessageNotification.ts +++ b/src/hooks/useNewMessageNotification.ts @@ -1,23 +1,22 @@ import { useEffect } from 'react'; import { useSelector } from 'react-redux'; -import useSound from 'use-sound'; import { RootState, useAppDispatch } from '../store'; import { resetNewMessagesAmount } from '../slices/chats.slice'; -import dingMp3 from '../static/ding.mp3'; +import { useDing } from './useSound'; const useNewMessageNotification = (): void => { const newMessagesAmount = useSelector((state: RootState) => state.chats.newMessagesAmount); - const [ding] = useSound(dingMp3); + const [ding] = useDing(); const dispatch = useAppDispatch(); const title = 'Bürokratt'; - + useEffect(() => { const onVisibilityChange = () => { document.title = title; dispatch(resetNewMessagesAmount()); }; - document.addEventListener('visibilitychange', onVisibilityChange); + document.addEventListener('visibilitychange', onVisibilityChange, false); return () => { document.removeEventListener('visibilitychange', onVisibilityChange); @@ -26,8 +25,8 @@ const useNewMessageNotification = (): void => { useEffect(() => { if (newMessagesAmount === 0) return; + ding?.play(); document.title = `(${newMessagesAmount}) uus sõnum! - ${title}`; - ding(); }, [newMessagesAmount]); }; diff --git a/src/hooks/useSound.ts b/src/hooks/useSound.ts new file mode 100644 index 0000000..6c81e0a --- /dev/null +++ b/src/hooks/useSound.ts @@ -0,0 +1,30 @@ +import { useEffect, useState } from 'react'; +import { Howl } from 'howler'; +import ding from '../static/ding.mp3'; + +export const useAudio = (audiosrc: string) => { + const [audio, setAudio] = useState(null); + + useEffect(() => { + const howl = new Howl({ + src: audiosrc, + onloaderror: (soundId, error) => console.error(soundId, error), + onplayerror: (soundId, error) => { + console.error(soundId, error); + howl.once('unlock', () => howl.play()); + }, + }); + + setAudio(howl); + + return () => { + howl.unload(); + } + }, []); + + return [audio] as const; +} + +export const useDing = () => { + return useAudio(ding); +} diff --git a/src/services/sse.service.ts b/src/services/sse.service.ts index fcdf934..46b10b7 100644 --- a/src/services/sse.service.ts +++ b/src/services/sse.service.ts @@ -2,34 +2,24 @@ import { RuuterResponse } from '../model/ruuter-response.model'; const ruuterUrl = window._env_.RUUTER_API_URL; -interface SseInstance { - onMessage: (handleData: (data: T) => void) => void; - close: () => void; -} - -const sse = (url: string): SseInstance => { +const sse = (url: string, onMessage: (data: T) => void) => { const eventSource = new EventSource(`${ruuterUrl}/sse/${url}`, { withCredentials: true }); - const onMessage = (handleData: (data: T) => void) => { - eventSource.onmessage = (event: MessageEvent) => { - const response = JSON.parse(event.data); + eventSource.onmessage = (event: MessageEvent) => { + const response = JSON.parse(event.data); - if (response.statusCodeValue === 200) { - const ruuterResponse = response.body as RuuterResponse; - if (ruuterResponse.data) handleData(Object.values(ruuterResponse.data)[0] as T); - } - }; - }; - - const close = () => { - eventSource.close(); - }; + if (response.statusCodeValue === 200) { + const ruuterResponse = response.body as RuuterResponse; + if (ruuterResponse?.data) + onMessage(Object.values(ruuterResponse.data)[0] as T); + } + } eventSource.onerror = () => { - eventSource.close(); - }; + console.error('SSE error'); + } - return { onMessage, close }; -}; + return eventSource; +} export default sse; diff --git a/src/static/ding.mp3 b/src/static/ding.mp3 index 917c9b8..af75c65 100644 Binary files a/src/static/ding.mp3 and b/src/static/ding.mp3 differ