fix: message sending fallback + iOS zoom on input + openOrCreatePrivate
- MessageInput: HTTP fallback when WebSocket not OPEN (prevents silent failures) - WsClient: add trySend() returning bool to detect WS availability - Backend: POST /api/messages/chat/:chatId endpoint for HTTP message send - openOrCreatePrivate: use openChat() for consistency after refreshing chat list - iOS fix: inputs get font-size:16px on mobile to prevent Safari auto-zoom Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,55 @@ export default async function messageRoutes(app: FastifyInstance) {
|
||||
}));
|
||||
});
|
||||
|
||||
// Send text message via HTTP (fallback when WS unavailable)
|
||||
app.post('/chat/:chatId', async (req, reply) => {
|
||||
const { id: userId } = req.user as { id: string };
|
||||
const { chatId } = req.params as { chatId: string };
|
||||
const { content, type = 'text', replyToId } = req.body as any;
|
||||
|
||||
if (!content?.trim()) return reply.status(400).send({ error: 'Empty message' });
|
||||
|
||||
const { rows: [member] } = await pool.query(
|
||||
'SELECT role, can_send_messages FROM chat_members WHERE chat_id = $1 AND user_id = $2',
|
||||
[chatId, userId]
|
||||
);
|
||||
if (!member) return reply.status(403).send({ error: 'Not a member' });
|
||||
if (!member.can_send_messages && member.role === 'member') return reply.status(403).send({ error: 'No permission' });
|
||||
|
||||
const { rows: [chat] } = await pool.query('SELECT type FROM chats WHERE id = $1', [chatId]);
|
||||
if (chat.type === 'channel' && !['owner', 'admin'].includes(member.role)) return reply.status(403).send({ error: 'No permission' });
|
||||
|
||||
const { rows: [msg] } = await pool.query(
|
||||
`INSERT INTO messages (chat_id, sender_id, content, type, reply_to_id)
|
||||
VALUES ($1,$2,$3,$4,$5) RETURNING *`,
|
||||
[chatId, userId, content.trim(), type, replyToId || null]
|
||||
);
|
||||
|
||||
const { rows: [sender] } = await pool.query(
|
||||
'SELECT id, username, display_name, avatar_color FROM users WHERE id = $1', [userId]
|
||||
);
|
||||
|
||||
const fullMsg = {
|
||||
id: msg.id, chatId: msg.chat_id, content: msg.content, type: msg.type,
|
||||
isDeleted: false, isEdited: false, createdAt: msg.created_at,
|
||||
replyTo: null, attachments: [],
|
||||
sender: { id: sender.id, username: sender.username, displayName: sender.display_name, avatarColor: sender.avatar_color }
|
||||
};
|
||||
|
||||
// Broadcast via WS to all members
|
||||
const { connections } = await import('../ws.js');
|
||||
const { rows: members } = await pool.query('SELECT user_id FROM chat_members WHERE chat_id = $1', [chatId]);
|
||||
const wsMsg = JSON.stringify({ type: 'new_message', payload: fullMsg });
|
||||
for (const m of members) {
|
||||
const sockets = connections.get(m.user_id);
|
||||
if (sockets) sockets.forEach((ws: any) => { try { if (ws.readyState === 1) ws.send(wsMsg); } catch {} });
|
||||
}
|
||||
|
||||
await pool.query('UPDATE chat_members SET last_read_at = NOW() WHERE chat_id = $1 AND user_id = $2', [chatId, userId]);
|
||||
|
||||
return reply.status(201).send(fullMsg);
|
||||
});
|
||||
|
||||
// Upload file and send as message
|
||||
app.post('/chat/:chatId/upload', async (req, reply) => {
|
||||
const { id: userId } = req.user as { id: string };
|
||||
|
||||
Reference in New Issue
Block a user