Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions FLATELY_COMPLETE_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -756,20 +756,20 @@ function ProtectedRoute({ children }) {

### Socket.io Setup

#### Backend (`chat.socket.js`)
#### Backend (`chat.socket.ts`)

```javascript
function registerChatSocket(io) {
io.on("connection", (socket) => {
// Join conversation room
socket.on("join", (conversationId) => {
socket.on("joinRoom", (conversationId) => {
socket.join(conversationId);
});

// Handle sending messages
socket.on("send_message", async ({ conversationId, senderId, content }) => {
socket.on("sendMessage", async ({ conversationId, senderId, content }) => {
const msg = await chatService.sendMessage(conversationId, senderId, content);
io.to(conversationId).emit("new_message", msg);
io.to(conversationId).emit("message", msg);
});
});
}
Expand All @@ -788,7 +788,7 @@ export const socket = io("http://localhost:4000");
// Join conversation
useEffect(() => {
if (conversationId) {
socket.emit('join', conversationId);
socket.emit('joinRoom', conversationId);
}
}, [conversationId]);

Expand All @@ -798,13 +798,13 @@ useEffect(() => {
setMessages(prev => [...prev, msg]);
};

socket.on('new_message', handleNewMessage);
return () => socket.off('new_message', handleNewMessage);
socket.on('message', handleNewMessage);
return () => socket.off('message', handleNewMessage);
}, []);

// Send message
const handleSend = () => {
socket.emit('send_message', {
socket.emit('sendMessage', {
conversationId,
senderId: user.sub,
content: message
Expand Down
6 changes: 0 additions & 6 deletions backend/src/modules/chat/chat.socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ import { AuthenticatedSocket } from '../../middlewares/socket-auth.middleware';

const chatSocketEvents = {
joinCanonical: 'joinRoom',
joinAlias: 'join',
sendCanonical: 'sendMessage',
sendAlias: 'send_message',
messageCanonical: 'message',
messageAlias: 'new_message',
} as const;

function safeHandler(
Expand Down Expand Up @@ -57,13 +54,10 @@ export default function registerChatSocket(
};

io.to(conversationId).emit(chatSocketEvents.messageCanonical, payload);
io.to(conversationId).emit(chatSocketEvents.messageAlias, payload);
};

socket.on(chatSocketEvents.joinCanonical, safeHandler(authSocket, joinConversation));
socket.on(chatSocketEvents.joinAlias, safeHandler(authSocket, joinConversation));

socket.on(chatSocketEvents.sendCanonical, safeHandler(authSocket, handleSendMessage));
socket.on(chatSocketEvents.sendAlias, safeHandler(authSocket, handleSendMessage));
});
}
5 changes: 0 additions & 5 deletions backend/src/types/socket.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
export interface ServerToClientEvents {
message: (data: MessagePayload) => void;
// Temporary alias kept for backward compatibility with legacy frontend clients.
new_message: (data: MessagePayload) => void;
userOnline: (userId: string) => void;
userOffline: (userId: string) => void;
}
Expand All @@ -10,9 +8,6 @@ export interface ClientToServerEvents {
sendMessage: (data: SendMessagePayload) => void;
joinRoom: (roomId: string) => void;
leaveRoom: (roomId: string) => void;
// Temporary aliases kept for backward compatibility.
send_message: (data: SendMessagePayload) => void;
join: (conversationId: string) => void;
}

export interface MessagePayload {
Expand Down
23 changes: 4 additions & 19 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,27 +616,14 @@ const socket = io("http://localhost:4000");

| Event | Payload | Description |
|---|---|---|
| `joinRoom` | `conversationId: string` | Canonical event: join a conversation room |
| `sendMessage` | `{ conversationId, senderId, content }` | Canonical event: send a message |

Legacy aliases (still accepted):

| Event | Alias Of |
|---|---|
| `join` | `joinRoom` |
| `send_message` | `sendMessage` |
| `joinRoom` | `conversationId: string` | Join a conversation room |
| `sendMessage` | `{ conversationId, senderId, content }` | Send a message |

### Server → Client Events

| Event | Payload | Description |
|---|---|---|
| `message` | `{ id, senderId, content, createdAt, timestamp }` | Canonical event: new message broadcast |

Legacy alias (still emitted for backward compatibility):

| Event | Alias Of |
|---|---|
| `new_message` | `message` |
| `message` | `{ id, senderId, content, createdAt, timestamp }` | New message broadcast |

Payload contract notes:
- `createdAt` and `timestamp` are both ISO datetime strings.
Expand All @@ -648,9 +635,7 @@ Payload contract notes:
Client A sends:
socket.emit('sendMessage', { conversationId, senderId, content })
→ Server receives, persists to DB via prisma.message.create()
→ Server broadcasts canonical: io.to(conversationId).emit('message', payload)
→ Server also broadcasts alias: io.to(conversationId).emit('new_message', payload)
→ Client B can receive either event during compatibility window
→ Server broadcasts: io.to(conversationId).emit('message', payload)
```

---
Expand Down
20 changes: 3 additions & 17 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,6 @@ const data = await apiRequest({
// chat.socket.ts
io.on('connection', (socket) => {
socket.on('joinRoom', (conversationId) => socket.join(conversationId));
socket.on('join', (conversationId) => socket.join(conversationId)); // alias
socket.on('sendMessage', async ({ conversationId, senderId, content }) => {
const msg = await sendMessage(conversationId, senderId, content);
const payload = {
Expand All @@ -472,18 +471,6 @@ io.on('connection', (socket) => {
timestamp: msg.createdAt.toISOString(),
};
io.to(conversationId).emit('message', payload);
io.to(conversationId).emit('new_message', payload); // alias
});
socket.on('send_message', async ({ conversationId, senderId, content }) => {
const msg = await sendMessage(conversationId, senderId, content);
const payload = {
id: msg.id, senderId: msg.senderId,
content: msg.content,
createdAt: msg.createdAt.toISOString(),
timestamp: msg.createdAt.toISOString(),
};
io.to(conversationId).emit('message', payload);
io.to(conversationId).emit('new_message', payload); // alias
});
});
```
Expand All @@ -498,16 +485,15 @@ export const socket = io(runtimeConfig.socketUrl);
socket.emit('joinRoom', conversationId);
socket.emit('sendMessage', { conversationId, senderId, content });
socket.on('message', (msg) => setMessages(prev => [...prev, msg]));
socket.on('new_message', (msg) => setMessages(prev => [...prev, msg])); // alias
```

### Socket Event Contract

| Direction | Event | Payload |
|---|---|---|
| Client → Server | `joinRoom` (canonical), `join` (alias) | `conversationId: string` |
| Client → Server | `sendMessage` (canonical), `send_message` (alias) | `{ conversationId, senderId, content }` |
| Server → Client | `message` (canonical), `new_message` (alias) | `{ id, senderId, content, createdAt, timestamp }` |
| Client → Server | `joinRoom` | `conversationId: string` |
| Client → Server | `sendMessage` | `{ conversationId, senderId, content }` |
| Server → Client | `message` | `{ id, senderId, content, createdAt, timestamp }` |

---

Expand Down
5 changes: 0 additions & 5 deletions docs/backend-code-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,11 +491,6 @@ class ChatService {
|-------|---------|-------------|
| `message` | `{id, senderId, content, createdAt, timestamp}` | New message broadcast |

**Legacy Aliases** (backward compatibility):
- `join` → `joinRoom`
- `send_message` → `sendMessage`
- `new_message` → `message`

### Socket Registration

```typescript
Expand Down
2 changes: 1 addition & 1 deletion docs/frontend-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ Uses:
- GET /matches/me (thread list)
- GET /chat/:matchId (conversation bootstrap)
- Socket joinRoom + sendMessage
- Receives both message and new_message events for compatibility
- Receives message events

Behavior:
- connection status badge: connected/reconnecting/connecting/disconnected
Expand Down
22 changes: 11 additions & 11 deletions scratch/render_diagrams.html
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@

subgraph Chat
UC19[Open Conversation\nGET /chat/:matchId]
UC20[Send Message\nsocket.emit send_message]
UC21[Receive Message\nsocket.on new_message]
UC20[Send Message\nsocket.emit sendMessage]
UC21[Receive Message\nsocket.on message]
end

Guest --> UC1
Expand Down Expand Up @@ -484,16 +484,16 @@
M --> N[prisma.profile.findUnique otherUserId include user]
N --> O[Return conversation + messages + otherUser]
O --> P[Render chat UI with message bubbles]
P --> Q[socket.emit join conversationId]
P --> Q[socket.emit joinRoom conversationId]
Q --> R[Server: socket.join conversationId room]

R --> S{User action?}
S -->|Type + Send| T[Optimistic UI: append message immediately]
T --> U[socket.emit send_message\nconversationId · senderId · content]
T --> U[socket.emit sendMessage\nconversationId · senderId · content]
U --> V[Server: sendMessage conversationId senderId content]
V --> W[prisma.message.create]
W --> X[io.to conversationId .emit new_message\nid · senderId · content · timestamp]
X --> Y[Both clients receive new_message event]
W --> X[io.to conversationId .emit message\nid · senderId · content · timestamp]
X --> Y[Both clients receive message event]
Y --> Z[Append to messages array in UI]
Z --> S

Expand Down Expand Up @@ -649,18 +649,18 @@
CC->>DB: profile.findUnique(otherUserId, include: user)
DB-->>CC: Other user profile
CC-->>A: {conversation, messages[], otherUser}
A->>SIO: socket.emit("join", conversationId)
A->>SIO: socket.emit("joinRoom", conversationId)
SIO->>SIO: socket.join(room: conversationId)
B->>SIO: socket.emit("join", conversationId)
B->>SIO: socket.emit("joinRoom", conversationId)
SIO->>SIO: socket.join(room: conversationId)
Note over A,B: Both users now in same Socket.IO room
A->>A: Type message, optimistic UI append
A->>SIO: socket.emit("send_message",{conversationId,senderId,content})
A->>SIO: socket.emit("sendMessage",{conversationId,senderId,content})
SIO->>CS: sendMessage(conversationId, senderId, content)
CS->>DB: message.create({conversationId, senderId, content})
DB-->>CS: Persisted Message
SIO->>A: io.to(conversationId).emit("new_message",{id,senderId,content,timestamp})
SIO->>B: io.to(conversationId).emit("new_message",{id,senderId,content,timestamp})
SIO->>A: io.to(conversationId).emit("message",{id,senderId,content,timestamp})
SIO->>B: io.to(conversationId).emit("message",{id,senderId,content,timestamp})
B->>B: Append new message to UI
</div>
</div>
Expand Down