diff --git a/src/Bootstrap.test.tsx b/src/Bootstrap.test.tsx
index effc0dc..99a9f74 100644
--- a/src/Bootstrap.test.tsx
+++ b/src/Bootstrap.test.tsx
@@ -55,6 +55,7 @@ test('persists user settings if none were already persisted', async () => {
colorMode: 'dark',
userId: 'abc123',
playSoundOnNewMessage: true,
+ showNotificationOnNewMessage: true,
})
})
diff --git a/src/Bootstrap.tsx b/src/Bootstrap.tsx
index 1cfba9c..91c3fcd 100644
--- a/src/Bootstrap.tsx
+++ b/src/Bootstrap.tsx
@@ -34,6 +34,7 @@ function Bootstrap({
userId: getUuid(),
colorMode: 'dark',
playSoundOnNewMessage: true,
+ showNotificationOnNewMessage: true,
})
const { userId } = userSettings
diff --git a/src/components/PeerNameDisplay/PeerNameDisplay.tsx b/src/components/PeerNameDisplay/PeerNameDisplay.tsx
index 10d5186..34b6128 100644
--- a/src/components/PeerNameDisplay/PeerNameDisplay.tsx
+++ b/src/components/PeerNameDisplay/PeerNameDisplay.tsx
@@ -1,5 +1,6 @@
import Typography, { TypographyProps } from '@mui/material/Typography'
-import { funAnimalName } from 'fun-animal-names'
+
+import { getPeerName } from './getPeerName'
interface PeerNameDisplayProps extends TypographyProps {
children: string
@@ -11,7 +12,7 @@ export const PeerNameDisplay = ({
}: PeerNameDisplayProps) => {
return (
- {funAnimalName(children)}
+ {getPeerName(children)}
)
}
diff --git a/src/components/PeerNameDisplay/getPeerName.ts b/src/components/PeerNameDisplay/getPeerName.ts
new file mode 100644
index 0000000..97cc8de
--- /dev/null
+++ b/src/components/PeerNameDisplay/getPeerName.ts
@@ -0,0 +1,5 @@
+import { funAnimalName } from 'fun-animal-names'
+
+export const getPeerName = (peerId: string) => {
+ return funAnimalName(peerId)
+}
diff --git a/src/components/PeerNameDisplay/index.ts b/src/components/PeerNameDisplay/index.ts
index 77f427c..593dfcf 100644
--- a/src/components/PeerNameDisplay/index.ts
+++ b/src/components/PeerNameDisplay/index.ts
@@ -1 +1,2 @@
export * from './PeerNameDisplay'
+export * from './getPeerName'
diff --git a/src/components/Room/Room.tsx b/src/components/Room/Room.tsx
index 0253378..265d779 100644
--- a/src/components/Room/Room.tsx
+++ b/src/components/Room/Room.tsx
@@ -12,6 +12,8 @@ import { PeerActions } from 'models/network'
import { UnsentMessage, ReceivedMessage } from 'models/chat'
import { MessageForm } from 'components/MessageForm'
import { ChatTranscript } from 'components/ChatTranscript'
+import { getPeerName } from 'components/PeerNameDisplay'
+import { NotificationService } from 'services/Notification'
export interface RoomProps {
appId?: string
@@ -45,6 +47,7 @@ export function Room({
roomId
)
+ // TODO: Move audio logic to a service
useEffect(() => {
;(async () => {
try {
@@ -117,9 +120,19 @@ export function Room({
receiveMessage(message => {
const userSettings = settingsContext.getUserSettings()
- !shellContext.tabHasFocus &&
- userSettings.playSoundOnNewMessage &&
- playNewMessageSound()
+
+ if (!shellContext.tabHasFocus) {
+ if (userSettings.playSoundOnNewMessage) {
+ playNewMessageSound()
+ }
+
+ if (userSettings.showNotificationOnNewMessage) {
+ NotificationService.showNotification(
+ `${getPeerName(message.authorId)}: ${message.text}`
+ )
+ }
+ }
+
setMessageLog([...messageLog, { ...message, timeReceived: Date.now() }])
})
diff --git a/src/contexts/SettingsContext.ts b/src/contexts/SettingsContext.ts
index 0cda8a0..25467d9 100644
--- a/src/contexts/SettingsContext.ts
+++ b/src/contexts/SettingsContext.ts
@@ -13,5 +13,6 @@ export const SettingsContext = createContext({
userId: '',
colorMode: 'dark',
playSoundOnNewMessage: true,
+ showNotificationOnNewMessage: true,
}),
})
diff --git a/src/models/settings.ts b/src/models/settings.ts
index ae32eb6..00b7a57 100644
--- a/src/models/settings.ts
+++ b/src/models/settings.ts
@@ -2,4 +2,5 @@ export interface UserSettings {
colorMode: 'dark' | 'light'
userId: string
playSoundOnNewMessage: boolean
+ showNotificationOnNewMessage: boolean
}
diff --git a/src/pages/PublicRoom/PublicRoom.tsx b/src/pages/PublicRoom/PublicRoom.tsx
index 24833c3..34da391 100644
--- a/src/pages/PublicRoom/PublicRoom.tsx
+++ b/src/pages/PublicRoom/PublicRoom.tsx
@@ -3,6 +3,7 @@ import { Room } from 'components/Room'
import { useParams } from 'react-router-dom'
import { ShellContext } from 'contexts/ShellContext'
+import { NotificationService } from 'services/Notification'
interface PublicRoomProps {
userId: string
@@ -12,6 +13,10 @@ export function PublicRoom({ userId }: PublicRoomProps) {
const { roomId = '' } = useParams()
const { setTitle } = useContext(ShellContext)
+ useEffect(() => {
+ NotificationService.requestPermission()
+ }, [])
+
useEffect(() => {
setTitle(`Room: ${roomId}`)
}, [roomId, setTitle])
diff --git a/src/pages/Settings/Settings.tsx b/src/pages/Settings/Settings.tsx
index 588fa38..7257b9a 100644
--- a/src/pages/Settings/Settings.tsx
+++ b/src/pages/Settings/Settings.tsx
@@ -3,8 +3,11 @@ import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
import Divider from '@mui/material/Divider'
-import { Switch } from '@mui/material'
+import Switch from '@mui/material/Switch'
+import FormGroup from '@mui/material/FormGroup'
+import FormControlLabel from '@mui/material/FormControlLabel'
+import { NotificationService } from 'services/Notification'
import { ShellContext } from 'contexts/ShellContext'
import { StorageContext } from 'contexts/StorageContext'
import { PeerNameDisplay } from 'components/PeerNameDisplay'
@@ -24,19 +27,38 @@ export const Settings = ({ userId }: SettingsProps) => {
isDeleteSettingsConfirmDiaglogOpen,
setIsDeleteSettingsConfirmDiaglogOpen,
] = useState(false)
- const { playSoundOnNewMessage } = getUserSettings()
+ const [, setIsNotificationPermissionDetermined] = useState(false)
+ const { playSoundOnNewMessage, showNotificationOnNewMessage } =
+ getUserSettings()
const persistedStorage = getPersistedStorage()
+ useEffect(() => {
+ ;(async () => {
+ await NotificationService.requestPermission()
+
+ // This state needs to be set to cause a rerender so that
+ // areNotificationsAvailable is up-to-date.
+ setIsNotificationPermissionDetermined(true)
+ })()
+ }, [])
+
useEffect(() => {
setTitle('Settings')
}, [setTitle])
const handlePlaySoundOnNewMessageChange = (
_event: ChangeEvent,
- value: boolean
+ playSoundOnNewMessage: boolean
) => {
- updateUserSettings({ playSoundOnNewMessage: value })
+ updateUserSettings({ playSoundOnNewMessage })
+ }
+
+ const handleShowNotificationOnNewMessageChange = (
+ _event: ChangeEvent,
+ showNotificationOnNewMessage: boolean
+ ) => {
+ updateUserSettings({ showNotificationOnNewMessage })
}
const handleDeleteSettingsClick = () => {
@@ -52,6 +74,8 @@ export const Settings = ({ userId }: SettingsProps) => {
window.location.reload()
}
+ const areNotificationsAvailable = NotificationService.permission === 'granted'
+
return (
{
>
Chat
- {' '}
- Play a sound when a new message is received
+ When a message is received in the background:
+
+
+ }
+ label="Play a sound"
+ />
+
+ }
+ label="Show a notification"
+ />
+
{
+ if (NotificationService.permission === 'granted') return
+
+ NotificationService.permission = await Notification.requestPermission()
+ }
+
+ static showNotification = (
+ message: string,
+ options?: NotificationOptions
+ ) => {
+ if (NotificationService.permission !== 'granted') return
+
+ new Notification(message, options)
+ }
+}
diff --git a/src/services/Notification/index.ts b/src/services/Notification/index.ts
new file mode 100644
index 0000000..934dbb2
--- /dev/null
+++ b/src/services/Notification/index.ts
@@ -0,0 +1 @@
+export * from './Notification'