-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(web): WebRTC을 사용한 라이브 페이지 초기 구현 (#165)
* chore(web): 레이아웃 컴포넌트 초기 진입시에만 렌더링 되도록 수정 * feat(web): Live 타입 모듈로 추가 * chore(web): 오픈비두 환경변수 추가 * refactor(web): 기존 컴포넌트 내부에 선언된 api들을 tanstack-query로 이관 * refactor(web): 기존 Live 사용하던 타입들 모듈로 재사용 * refactor(web): 기존 컨벤션에 맞춰 네임스페이스 방식으로 Import 변경 * design(ui): FormLabel padding 수정 * design(ui): FormAttachment 모바일 스타일 수정 * feat(web): Live Form State 추가 * refactor(web): Ui Form 패키지 사용 * chore(web): Live 종료시 invalidQuery 실행 삭제 * chore(web): Live 리스트 Api 스펙 변경 적용 * pull feature_live * private room password input modal add, chat stomp get userNumbers * none * disconnect fix * openvidu disconnection 구현 * pc version live complite * fix(web): lint error --------- Co-authored-by: [email protected] <[email protected]> Co-authored-by: doyupKim <[email protected]>
- Loading branch information
1 parent
9a0340f
commit 000fc55
Showing
29 changed files
with
1,730 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
.chatTextarea:focus { | ||
outline: none; | ||
} | ||
|
||
.chatTextarea::placeholder { | ||
color: #8E8E95; | ||
font-weight: 500; | ||
font-family: 'Pretendard'; | ||
} | ||
|
||
.chatTextarea { | ||
resize: none; | ||
border: none; | ||
height: 100%; | ||
font-weight: 500; | ||
font-family: 'Pretendard'; | ||
width: 100%; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
import * as React from 'react'; | ||
import liveCss from 'public/css/live.module.css'; | ||
import { Client, StompSubscription } from '@stomp/stompjs'; | ||
import { Button, Wrapper } from '@supercarmarket/ui'; | ||
import { getSession, useSession } from 'next-auth/react'; | ||
import { css } from 'styled-components'; | ||
|
||
interface Props { | ||
data: Live.LiveRoomDto | null | undefined; | ||
isBroad: boolean; | ||
setLiveViewCount: (broad: number) => void; | ||
} | ||
|
||
interface messageType { | ||
type: string; | ||
sender: string; | ||
channelId: string; | ||
data: string; | ||
} | ||
|
||
function ChatInfo(props: Props) { | ||
const [chats, setChats] = React.useState<messageType[]>([]); | ||
const [stomp, setStomp] = React.useState<Client>(); | ||
const [subscribes, setSubscribes] = React.useState<StompSubscription>(); | ||
const [userName, setUserName] = React.useState(''); | ||
|
||
const textAreaRef = React.useRef<HTMLTextAreaElement>(null); | ||
const chatWrapRef = React.useRef<HTMLDivElement>(null); | ||
|
||
const joinChat = async () => { | ||
const session = await getSession(); | ||
|
||
if (!session?.accessToken) throw 'require logged in'; | ||
setUserName(session.nickname); | ||
|
||
const client = new Client({ | ||
brokerURL: `wss://back.doyup.shop/ws`, | ||
connectHeaders: { | ||
ACCESS_TOKEN: `${session.accessToken}`, | ||
}, | ||
reconnectDelay: 5000, | ||
heartbeatIncoming: 4000, | ||
heartbeatOutgoing: 4000, | ||
}); | ||
|
||
setStomp(client); | ||
|
||
client.onConnect = function (frame) { | ||
const subscribe = client.subscribe( | ||
`/sub/${props.data?.sessionId}`, | ||
(frame) => { | ||
const getMessage = JSON.parse(frame.body); | ||
if (getMessage.participantNumber) { | ||
props.setLiveViewCount(parseInt(getMessage.participantNumber)); | ||
} | ||
if (getMessage.sender !== 'server') { | ||
setChats((prevState: messageType[]) => { | ||
return prevState.concat([getMessage]); | ||
}); | ||
setTimeout(() => { | ||
if (chatWrapRef.current) { | ||
chatWrapRef.current.scrollTop = | ||
chatWrapRef.current.scrollHeight; | ||
} | ||
}, 100); | ||
} | ||
} | ||
); | ||
|
||
setSubscribes(subscribe); | ||
|
||
client.publish({ | ||
destination: `/pub/chat/${props.data?.sessionId}`, | ||
body: `{ | ||
"type": "ENTER", | ||
"sender": "${session.nickname}", | ||
"channelId": "${props.data?.sessionId}", | ||
"data": "'${session.nickname}' 님이 접속하셨습니다." | ||
}`, | ||
}); | ||
}; | ||
|
||
client.onStompError = function (frame) { | ||
console.log('Broker reported error: ' + frame.headers['message']); | ||
console.log('Additional details: ' + frame.body); | ||
}; | ||
|
||
client.activate(); | ||
}; | ||
|
||
const sendChat = () => { | ||
if (stomp && textAreaRef.current && textAreaRef.current.value.length > 0) { | ||
stomp.publish({ | ||
destination: `/pub/chat/${props.data?.sessionId}`, | ||
body: `{ | ||
"type": "TALK", | ||
"sender": "${userName}", | ||
"channelId": "${props.data?.sessionId}", | ||
"data": "${ | ||
textAreaRef.current?.value.replaceAll(/(\n|\r\n)/g, '<br>') ?? '' | ||
}" | ||
}`, | ||
headers: { | ||
'content-type': 'application/json', | ||
}, | ||
}); | ||
} | ||
if (textAreaRef.current) textAreaRef.current.value = ''; | ||
}; | ||
|
||
const exitChat = () => { | ||
if (stomp) { | ||
stomp.publish({ | ||
destination: `/pub/chat/${props.data?.sessionId}`, | ||
body: `{ | ||
"type": "EXIT", | ||
"sender": "${userName}", | ||
"channelId": "${props.data?.sessionId}", | ||
"data": "'${userName}' 님이 종료하셨습니다." | ||
}`, | ||
}); | ||
} | ||
setTimeout(() => { | ||
if (chatWrapRef.current) { | ||
chatWrapRef.current.scrollTop = chatWrapRef.current.scrollHeight; | ||
} | ||
stomp?.deactivate(); | ||
}, 100); | ||
}; | ||
|
||
React.useEffect(() => { | ||
joinChat(); | ||
}, []); | ||
|
||
React.useEffect(() => { | ||
if (stomp) { | ||
textAreaRef.current?.addEventListener('keypress', (event) => { | ||
if (event.key === 'Enter' && !event.shiftKey) { | ||
event.preventDefault(); | ||
sendChat(); | ||
} | ||
}); | ||
} | ||
return () => { | ||
exitChat(); | ||
}; | ||
}, [stomp]); | ||
|
||
return ( | ||
<Wrapper.Item | ||
css={css` | ||
margin-left: 16px; | ||
width: 304px; | ||
`} | ||
> | ||
<div | ||
style={{ | ||
height: '500px', | ||
overflowY: 'auto', | ||
}} | ||
ref={chatWrapRef} | ||
> | ||
{chats.map((data, idx) => { | ||
if (data.type === 'ENTER' || data.type === 'EXIT') { | ||
return ( | ||
<InitUserChat chat={data.data} key={`InitUserChat_${idx}`} /> | ||
); | ||
} | ||
if (data.sender === userName) { | ||
return <MyChat chat={data.data} key={`MyChat_${idx}`} />; | ||
} | ||
return ( | ||
<UserChat | ||
nickname={data.sender} | ||
chat={data.data} | ||
key={`UserChat_${idx}`} | ||
/> | ||
); | ||
})} | ||
</div> | ||
<Wrapper.Item | ||
css={css` | ||
{ | ||
padding: 12px 16px; | ||
border: 1px solid #c3c3c7; | ||
display: flex; | ||
border-radius: 4px; | ||
height: 97px; | ||
align-items: flex-end; | ||
} | ||
`} | ||
> | ||
<textarea | ||
placeholder="채팅을 남겨보세요" | ||
className={liveCss.chatTextarea} | ||
maxLength={100} | ||
ref={textAreaRef} | ||
/> | ||
<Button | ||
style={{ | ||
width: '72px', | ||
height: '38px', | ||
}} | ||
onClick={sendChat} | ||
> | ||
등록 | ||
</Button> | ||
</Wrapper.Item> | ||
</Wrapper.Item> | ||
); | ||
} | ||
|
||
const UserChat = ({ nickname, chat }: { nickname: string; chat: string }) => { | ||
return ( | ||
<div | ||
style={{ | ||
backgroundColor: '#F7F7F8', | ||
borderRadius: '4px 16px 16px 16px', | ||
padding: '12px 16px', | ||
gap: '8px', | ||
marginBottom: '10px', | ||
width: 'fit-content', | ||
}} | ||
> | ||
<span style={{ fontSize: '14px', lineHeight: '150%' }}>{nickname}</span>{' '} | ||
<span dangerouslySetInnerHTML={{ __html: chat }} /> | ||
</div> | ||
); | ||
}; | ||
|
||
const MyChat = ({ chat }: { chat: string }) => { | ||
return ( | ||
<div | ||
style={{ | ||
backgroundColor: '#EBE6DE', | ||
borderRadius: '16px 4px 16px 16px', | ||
padding: '12px 16px', | ||
gap: '8px', | ||
marginBottom: '10px', | ||
marginLeft: 'auto', | ||
width: 'fit-content', | ||
}} | ||
> | ||
<span dangerouslySetInnerHTML={{ __html: chat }} /> | ||
</div> | ||
); | ||
}; | ||
|
||
const InitUserChat = ({ chat }: { chat: string }) => { | ||
return ( | ||
<div | ||
style={{ | ||
borderRadius: '16px 4px 16px 16px', | ||
padding: '12px 16px', | ||
gap: '8px', | ||
marginBottom: '10px', | ||
fontSize: '14px', | ||
}} | ||
> | ||
{chat} | ||
</div> | ||
); | ||
}; | ||
|
||
export default ChatInfo; |
Oops, something went wrong.