Skip to content
Merged
3,173 changes: 1,532 additions & 1,641 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"mock-socket": "^9.3.1",
"next": "15.5.3",
"react": "19.1.0",
"react-dom": "19.1.0",
Expand Down
52 changes: 26 additions & 26 deletions src/entities/chat/model/mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChatMessage } from "../ui/MessageRow/MessageRow";
import { MessageProps } from "./types";
export const chats = [
{
chatId: 501,
Expand Down Expand Up @@ -62,117 +62,117 @@ export const chats = [
},
];

export const mockMessages: ChatMessage[] = [
export const mockMessages: MessageProps[] = [
// 🔹 [1] 첫 메시지 — 상대방 (프로필 보임, 시간 표시)
{
id: 1,
messageId: 1,
type: "text",
content: "안녕하세요! 이 물건 아직 있나요?",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:00:00Z",
isRead: true,
},

// 🔹 [2] 내 메시지 (시간 차 있음 → 시간 표시)
{
id: 2,
messageId: 2,
type: "text",
content: "네, 아직 있습니다 🙂",
isMine: true,
sendAt: "2025-10-31T10:01:00Z",
isRead: true,
},

// 🔹 [3] 상대방 이미지 메시지 (같은 유저, 같은 분 안 → 프로필 X, 시간 X)
{
id: 3,
messageId: 3,
type: "image",
content:
"https://chalddackimage.blob.core.windows.net/chalddackimage/150100000286_03.webp",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:01:30Z",
isRead: true,
},

// 🔹 [4] 상대방 텍스트 메시지 (같은 유저, 같은 분 → 프로필 X, 마지막 → 시간 표시)
{
id: 4,
messageId: 4,
type: "text",
content: "좋아요, 거래 원해요!",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:02:00Z",
isRead: true,
},

// 🔹 [5] 내 메시지 여러 개 (같은 분 → 앞은 showTime=false, 마지막만 true)
{
id: 5,
messageId: 5,
type: "text",
content: "좋아요. 어디서 거래할까요?",
isMine: true,
sendAt: "2025-10-31T10:03:10Z",
isRead: true,
},
{
id: 6,
messageId: 6,
type: "text",
content: "저는 강남역 근처 가능합니다.",
isMine: true,
sendAt: "2025-10-31T10:03:40Z",
isRead: true,
},
{
id: 7,
messageId: 7,
type: "text",
content: "시간은 언제쯤 괜찮으세요?",
isMine: true,
sendAt: "2025-10-31T10:04:00Z",
isRead: true,
},

// 🔹 [8] 상대방 메시지 (새로운 유저 → 프로필 보임, 시간 표시)
{
id: 8,
messageId: 8,
type: "text",
content: "오후 3시쯤 어떠세요?",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-10-31T10:05:00Z",
isRead: true,
},

// 🔹 [9] 내 메시지 (시간 간격 큼 → 시간 표시)
{
id: 9,
messageId: 9,
type: "text",
content: "좋습니다. 그때 뵐게요!",
isMine: true,
sendAt: "2025-10-31T10:10:00Z",
isRead: true,
},

// 🔹 [10] 날짜 변경 (새로운 날짜 → 날짜 구분선 확인용)
{
id: 10,
messageId: 10,
type: "text",
content: "안녕하세요, 어제 말씀드린 거래건입니다.",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-11-01T09:55:00Z",
isRead: true,
},
{
id: 11,
messageId: 11,
type: "text",
content: "오늘 일정 그대로죠?",
isMine: false,
profileImage:
"https://chalddackimage.blob.core.windows.net/chalddackimage/profile_d776b3ca-9871-4ad1-a2f6-e7676ac03052.jpeg",
sendAt: "2025-11-01T09:56:00Z",
isRead: true,
},
{
id: 12,
messageId: 12,
type: "text",
content: "네, 맞아요! 오후 3시 강남역에서 봬요.",
isMine: true,
sendAt: "2025-11-01T09:57:00Z",
isRead: true,
},
];
103 changes: 103 additions & 0 deletions src/entities/chat/model/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { MessageProps } from "./types";
import { useAuthStore } from "@/features/auth/model/auth.store";

export interface ChatSocketEvents {
onOpen?: () => void;
onMessage?: (message: MessageProps) => void;
onSystem?: (system: { type: string; message: string }) => void;
onClose?: (code: number, reason?: string) => void;
onError?: (event: Event) => void;
}

export class ChatSocket {
private socket: WebSocket | null = null;
private chatId: number;
private events: ChatSocketEvents;

constructor(chatId: number, events: ChatSocketEvents = {}) {
this.chatId = chatId;
this.events = events;
}

connect(): Promise<void> {
return new Promise((resolve, reject) => {
if (this.socket) {
console.warn("[Socket] Already connected");
resolve();
return;
}

const { accessToken } = useAuthStore.getState();
const wsUrl = `${
process.env.NEXT_PUBLIC_API_WS_URL || "ws://localhost:8000"
}/ws/chat/${this.chatId}?token=${accessToken}`;

this.socket = new WebSocket(wsUrl);

this.socket.onopen = () => {
console.log("[Socket] Connected");
this.events.onOpen?.();
resolve(); // ✅ 연결 완료
};

this.socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (["welcome", "system", "read"].includes(data.type)) {
this.events.onSystem?.(data);
return;
}

if (data.messageId && data.content) {
const msg: MessageProps = {
messageId: data.messageId,
type: data.type,
content: data.content,
isMine: false,
sendAt: data.createdAt,
isRead: false,
};
this.events.onMessage?.(msg);
}
} catch (err) {
console.error("[Socket] Message parse error:", err);
}
};

this.socket.onclose = (event) => {
console.warn("[Socket] Closed:", event.code);
this.events.onClose?.(event.code, event.reason);
this.socket = null;
};

this.socket.onerror = (err) => {
console.error("[Socket] Error:", err);
this.events.onError?.(err);
reject(err);
};
});
}

sendMessage(type: "text" | "image", content: string) {
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
console.warn("[Socket] Not connected");
return;
}

const payload = {
event: "send_message",
type,
content,
};

this.socket.send(JSON.stringify(payload));
}

leaveRoom() {
if (!this.socket) return;
console.log(this.socket);
this.socket.send(JSON.stringify({ event: "leave_room" }));
this.socket.close(1000, "User left");
this.socket = null;
}
}
15 changes: 15 additions & 0 deletions src/entities/chat/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@ export interface Chat {
otherNick: string;
otherImage: string;
}

export interface MessageProps {
messageId: number;
type: "text" | "image" | "system";
content: string;
sendAt: string;
isMine: boolean;
isRead: boolean;
}

export interface MessagesResponse {
messages: MessageProps[];
hasNext: boolean;
nextCursor: number | null;
}
Loading