Skip to content

Commit

Permalink
feat(#141): [wip] render direct message room
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyckahn committed Nov 22, 2024
1 parent 9c84b82 commit 41f3819
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 87 deletions.
37 changes: 22 additions & 15 deletions src/components/ChatTranscript/ChatTranscript.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react'
import Box from '@mui/material/Box'
import { useRef, useEffect, useState, useContext } from 'react'
import Box, { BoxProps } from '@mui/material/Box'
import useTheme from '@mui/material/styles/useTheme'

import { Message as IMessage, InlineMedia } from 'models/chat'
import { Message } from 'components/Message'
import { ShellContext } from 'contexts/ShellContext'

export interface ChatTranscriptProps extends HTMLAttributes<HTMLDivElement> {
export interface ChatTranscriptProps extends BoxProps {
messageLog: Array<IMessage | InlineMedia>
userId: string
}

export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => {
export const ChatTranscript = ({
messageLog,
userId,
sx,
}: ChatTranscriptProps) => {
const { showRoomControls } = useContext(ShellContext)
const theme = useTheme()
const boxRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -62,17 +66,20 @@ export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => {
<Box
ref={boxRef}
className="ChatTranscript"
sx={{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
overflow: 'auto',
pb: transcriptMinPadding,
pt: showRoomControls ? theme.spacing(10) : theme.spacing(2),
px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`,
transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`,
width: '100%',
}}
sx={[
{
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
overflow: 'auto',
pb: transcriptMinPadding,
pt: showRoomControls ? theme.spacing(10) : theme.spacing(2),
px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`,
transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`,
width: '100%',
},
...(Array.isArray(sx) ? sx : [sx]),
]}
>
{messageLog.map((message, idx) => {
const previousMessage = messageLog[idx - 1]
Expand Down
17 changes: 10 additions & 7 deletions src/components/Room/Room.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('Room', () => {
expect(textInput).toHaveValue('')
})

test('message is sent to peer', async () => {
test('message is sent to peers', async () => {
render(
<RouteStub>
<RoomStub
Expand All @@ -136,11 +136,14 @@ describe('Room', () => {
await userEvent.type(textInput, 'hello')
await userEvent.click(sendButton)

expect(mockMessagedSender).toHaveBeenCalledWith({
authorId: mockUserId,
text: 'hello',
timeSent: mockNowTime,
id: 'abc123',
})
expect(mockMessagedSender).toHaveBeenCalledWith(
{
authorId: mockUserId,
text: 'hello',
timeSent: mockNowTime,
id: 'abc123',
},
null
)
})
})
65 changes: 38 additions & 27 deletions src/components/Room/Room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface RoomProps {
userId: string
encryptionService?: typeof encryption
timeService?: typeof time
targetPeerId?: string
}

export function Room({
Expand All @@ -43,6 +44,7 @@ export function Room({
roomId,
password,
userId,
targetPeerId,
}: RoomProps) {
const theme = useTheme()
const settingsContext = useContext(SettingsContext)
Expand Down Expand Up @@ -72,9 +74,12 @@ export function Room({
publicKey,
encryptionService,
timeService,
targetPeerId,
}
)

const isDirectMessageRoom = typeof targetPeerId === 'string'

const handleMessageSubmit = async (message: string) => {
await sendMessage(message)
}
Expand Down Expand Up @@ -105,32 +110,34 @@ export function Room({
overflow: 'auto',
}}
>
<Zoom in={showRoomControls}>
<Box
sx={{
alignItems: 'flex-start',
display: 'flex',
justifyContent: 'center',
overflow: 'visible',
height: 0,
position: 'relative',
top: theme.spacing(1),
}}
>
<RoomAudioControls peerRoom={peerRoom} />
<RoomVideoControls peerRoom={peerRoom} />
<RoomScreenShareControls peerRoom={peerRoom} />
<RoomFileUploadControls
peerRoom={peerRoom}
onInlineMediaUpload={handleInlineMediaUpload}
/>
<Zoom in={showVideoDisplay} mountOnEnter unmountOnExit>
<span>
<RoomShowMessagesControls />
</span>
</Zoom>
</Box>
</Zoom>
{!isDirectMessageRoom && (
<Zoom in={showRoomControls}>
<Box
sx={{
alignItems: 'flex-start',
display: 'flex',
justifyContent: 'center',
overflow: 'visible',
height: 0,
position: 'relative',
top: theme.spacing(1),
}}
>
<RoomAudioControls peerRoom={peerRoom} />
<RoomVideoControls peerRoom={peerRoom} />
<RoomScreenShareControls peerRoom={peerRoom} />
<RoomFileUploadControls
peerRoom={peerRoom}
onInlineMediaUpload={handleInlineMediaUpload}
/>
<Zoom in={showVideoDisplay} mountOnEnter unmountOnExit>
<span>
<RoomShowMessagesControls />
</span>
</Zoom>
</Box>
</Zoom>
)}
<Box
sx={{
display: 'flex',
Expand All @@ -157,7 +164,11 @@ export function Room({
height: landscape ? '100%' : '40%',
}}
>
<ChatTranscript messageLog={messageLog} userId={userId} />
<ChatTranscript
messageLog={messageLog}
userId={userId}
sx={{ pt: 1 }}
/>
<Divider />
<Box>
<MessageForm
Expand Down
12 changes: 9 additions & 3 deletions src/components/Room/usePeerVerification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ShellContext } from 'contexts/ShellContext'
import { Peer, PeerVerificationState } from 'models/chat'
import { encryption } from 'services/Encryption'
import { PeerRoom } from 'lib/PeerRoom'
import { PeerAction } from 'models/network'
import { groupActionNamespace, PeerAction } from 'models/network'
import { verificationTimeout } from 'config/messaging'
import { usePeerNameDisplay } from 'components/PeerNameDisplay'

Expand All @@ -23,10 +23,16 @@ export const usePeerVerification = ({
const { getDisplayUsername } = usePeerNameDisplay()

const [sendVerificationTokenEncrypted, receiveVerificationTokenEncrypted] =
peerRoom.makeAction<ArrayBuffer>(PeerAction.VERIFICATION_TOKEN_ENCRYPTED)
peerRoom.makeAction<ArrayBuffer>(
PeerAction.VERIFICATION_TOKEN_ENCRYPTED,
groupActionNamespace
)

const [sendVerificationTokenRaw, receiveVerificationTokenRaw] =
peerRoom.makeAction<string>(PeerAction.VERIFICATION_TOKEN_RAW)
peerRoom.makeAction<string>(
PeerAction.VERIFICATION_TOKEN_RAW,
groupActionNamespace
)

const initPeerVerification = useCallback(
async (peer: Peer) => {
Expand Down
59 changes: 47 additions & 12 deletions src/components/Room/useRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useDebounce } from '@react-hook/debounce'

import { ShellContext } from 'contexts/ShellContext'
import { SettingsContext } from 'contexts/SettingsContext'
import { PeerAction } from 'models/network'
import { groupActionNamespace, PeerAction } from 'models/network'
import {
AudioState,
Message,
Expand Down Expand Up @@ -44,6 +44,7 @@ interface UseRoomConfig {
getUuid?: typeof uuid
encryptionService?: typeof encryption
timeService?: typeof time
targetPeerId?: string | null
}

interface UserMetadata extends Record<string, any> {
Expand All @@ -58,13 +59,16 @@ export function useRoom(
roomId,
userId,
publicKey,
targetPeerId = null,
getUuid = uuid,
encryptionService = encryption,
timeService = time,
}: UseRoomConfig
) {
const isPrivate = password !== undefined

const isDirectMessageRoom = typeof targetPeerId === 'string'

const {
peerList,
setPeerList,
Expand All @@ -76,8 +80,14 @@ export function useRoom(
customUsername,
updatePeer,
peerRoomRef,
messageLog: shellMessageLog,
setMessageLog: shellSetMessageLog,
} = useContext(ShellContext)

const messageLog = isDirectMessageRoom
? shellMessageLog.directMessageLog[targetPeerId] ?? []
: shellMessageLog.groupMessageLog

const [peerRoom] = useState(
() =>
peerRoomRef.current ??
Expand All @@ -89,9 +99,6 @@ export function useRoom(
const settingsContext = useContext(SettingsContext)
const { showActiveTypingStatus } = settingsContext.getUserSettings()
const [isMessageSending, setIsMessageSending] = useState(false)
const [messageLog, _setMessageLog] = useState<Array<Message | InlineMedia>>(
[]
)
const [newMessageAudio] = useState(() => new Audio('/sounds/new-message.aac'))

const { getDisplayUsername } = usePeerNameDisplay()
Expand All @@ -113,7 +120,10 @@ export function useRoom(
}
}

_setMessageLog(messages.slice(-messageTranscriptSizeLimit))
shellSetMessageLog(
messages.slice(-messageTranscriptSizeLimit),
targetPeerId
)
}

const [isShowingMessages, setIsShowingMessages] = useState(true)
Expand Down Expand Up @@ -174,8 +184,15 @@ export function useRoom(
]
)

const peerActionNamespace = isDirectMessageRoom
? targetPeerId.slice(0, 8)
: groupActionNamespace

const [sendTypingStatusChange, receiveTypingStatusChange] =
peerRoom.makeAction<TypingStatus>(PeerAction.TYPING_STATUS_CHANGE)
peerRoom.makeAction<TypingStatus>(
PeerAction.TYPING_STATUS_CHANGE,
peerActionNamespace
)

const [isTyping, setIsTypingDebounced, setIsTyping] = useDebounce(
false,
Expand All @@ -191,12 +208,23 @@ export function useRoom(

useEffect(() => {
return () => {
if (isDirectMessageRoom) return

sendTypingStatusChange({ isTyping: false })
peerRoom.leaveRoom()
peerRoomRef.current = null
setPeerList([])
shellSetMessageLog([], targetPeerId)
}
}, [peerRoom, setPeerList, sendTypingStatusChange, peerRoomRef])
}, [
peerRoom,
setPeerList,
sendTypingStatusChange,
peerRoomRef,
isDirectMessageRoom,
shellSetMessageLog,
targetPeerId,
])

useEffect(() => {
setPassword(password)
Expand All @@ -219,17 +247,23 @@ export function useRoom(
}, [isShowingMessages, setUnreadMessages])

const [sendPeerMetadata, receivePeerMetadata] =
peerRoom.makeAction<UserMetadata>(PeerAction.PEER_METADATA)
peerRoom.makeAction<UserMetadata>(
PeerAction.PEER_METADATA,
peerActionNamespace
)

const [sendMessageTranscript, receiveMessageTranscript] = peerRoom.makeAction<
Array<ReceivedMessage | ReceivedInlineMedia>
>(PeerAction.MESSAGE_TRANSCRIPT)
>(PeerAction.MESSAGE_TRANSCRIPT, peerActionNamespace)

const [sendPeerMessage, receivePeerMessage] =
peerRoom.makeAction<UnsentMessage>(PeerAction.MESSAGE)
peerRoom.makeAction<UnsentMessage>(PeerAction.MESSAGE, peerActionNamespace)

const [sendPeerInlineMedia, receivePeerInlineMedia] =
peerRoom.makeAction<UnsentInlineMedia>(PeerAction.MEDIA_MESSAGE)
peerRoom.makeAction<UnsentInlineMedia>(
PeerAction.MEDIA_MESSAGE,
peerActionNamespace
)

const { privateKey } = settingsContext.getUserSettings()

Expand All @@ -252,7 +286,8 @@ export function useRoom(
setIsTyping(false)
setIsMessageSending(true)
setMessageLog([...messageLog, unsentMessage])
await sendPeerMessage(unsentMessage)

await sendPeerMessage(unsentMessage, targetPeerId)

setMessageLog([
...messageLog,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Room/useRoomAudio.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext, useEffect, useCallback, useState } from 'react'

import { ShellContext } from 'contexts/ShellContext'
import { PeerAction } from 'models/network'
import { groupActionNamespace, PeerAction } from 'models/network'
import {
AudioState,
Peer,
Expand Down Expand Up @@ -40,7 +40,7 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {

const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction<
Partial<PeerAudioChannelState>
>(PeerAction.AUDIO_CHANGE)
>(PeerAction.AUDIO_CHANGE, groupActionNamespace)

receiveAudioChange((peerAudioChannelState, peerId) => {
setPeerList(peerList => {
Expand Down
Loading

0 comments on commit 41f3819

Please sign in to comment.