[RAPID] 메신저 기능 구현 및 UI/UX 개선
- 사용자 온라인 상태 표시 (온라인/자리비움/오프라인) 디스코드 스타일 - 채팅방별 알림 ON/OFF 토글 (Bell 아이콘, localStorage 저장) - 파일 업로드 실시간 소켓 브로드캐스트 및 한글 파일명 깨짐 수정 - FAB 읽지않음 배지 버그 수정 (메신저 닫혀있을 때 markAsRead 차단) - 타이핑 도트 애니메이션, 날짜 오늘/어제 표시 - 입력창 bordered box, DM 편집 버튼 숨김 - 메신저 설정 버튼 제거, 새 대화 시작하기 Empty state CTA - useMessengerSocket 소켓 중복 생성 방지 (MessengerModal로 이동) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> [RAPID-micro] 추적 파일 정리 및 메신저 소소한 변경 - .omc/state/ 파일 git 추적 제거 (.gitignore 이미 설정됨) - db/checkpoints/ gitignore 추가 - globals.css: 메신저 메시지 시간 폰트 스타일 추가 - useMessenger.ts: fileMimeType 필드 및 API_BASE_URL import 추가 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,14 +112,19 @@ class MessengerController {
|
||||
return res.status(400).json({ success: false, message: 'room_id is required.' });
|
||||
}
|
||||
|
||||
const io = getIo();
|
||||
const savedFiles = [];
|
||||
for (const file of files) {
|
||||
// Use a readable placeholder as content to avoid filename encoding issues
|
||||
const isImage = file.mimetype.startsWith('image/');
|
||||
const content = isImage ? '[이미지]' : '[파일]';
|
||||
|
||||
// Create a file message
|
||||
const message = await messengerService.sendMessage(
|
||||
roomId,
|
||||
user.userId,
|
||||
user.companyCode!,
|
||||
file.originalname,
|
||||
content,
|
||||
'file'
|
||||
);
|
||||
|
||||
@@ -131,6 +136,11 @@ class MessengerController {
|
||||
mimeType: file.mimetype,
|
||||
});
|
||||
|
||||
message.files = [savedFile];
|
||||
|
||||
// Broadcast to room so recipients receive it in real-time
|
||||
io.to(`${user.companyCode}:${roomId}`).emit('new_message', message);
|
||||
|
||||
savedFiles.push({ message, file: savedFile });
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ interface AuthenticatedSocket extends Socket {
|
||||
};
|
||||
}
|
||||
|
||||
// In-memory presence store: userId → { companyCode, status }
|
||||
const presenceStore = new Map<string, { companyCode: string; status: 'online' | 'away' }>();
|
||||
|
||||
export function initMessengerSocket(io: Server) {
|
||||
// JWT authentication middleware
|
||||
io.use((socket, next) => {
|
||||
@@ -35,6 +38,30 @@ export function initMessengerSocket(io: Server) {
|
||||
const { userId, companyCode } = socket.data;
|
||||
console.log(`[Messenger] User connected: ${userId}`);
|
||||
|
||||
// Join company presence room and broadcast online status
|
||||
const presenceRoom = `${companyCode}:presence`;
|
||||
socket.join(presenceRoom);
|
||||
presenceStore.set(userId, { companyCode, status: 'online' });
|
||||
socket.to(presenceRoom).emit('user_status', { userId, status: 'online' });
|
||||
|
||||
// Send current online users list to newly connected socket
|
||||
const currentPresence: Record<string, string> = {};
|
||||
for (const [uid, info] of presenceStore.entries()) {
|
||||
if (info.companyCode === companyCode) {
|
||||
currentPresence[uid] = info.status;
|
||||
}
|
||||
}
|
||||
socket.emit('presence_list', currentPresence);
|
||||
|
||||
// set_status: client emits when tab focus changes
|
||||
socket.on('set_status', (data: { status: 'online' | 'away' }) => {
|
||||
const entry = presenceStore.get(userId);
|
||||
if (entry) {
|
||||
entry.status = data.status;
|
||||
io.to(presenceRoom).emit('user_status', { userId, status: data.status });
|
||||
}
|
||||
});
|
||||
|
||||
// join_rooms: subscribe to all user's rooms
|
||||
socket.on('join_rooms', async () => {
|
||||
try {
|
||||
@@ -99,6 +126,7 @@ export function initMessengerSocket(io: Server) {
|
||||
socket.to(`${companyCode}:${data.room_id}`).emit('user_stop_typing', {
|
||||
room_id: data.room_id,
|
||||
user_id: userId,
|
||||
user_name: socket.data.userName,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,6 +164,8 @@ export function initMessengerSocket(io: Server) {
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log(`[Messenger] User disconnected: ${userId}`);
|
||||
presenceStore.delete(userId);
|
||||
io.to(presenceRoom).emit('user_status', { userId, status: 'offline' });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user