diff --git a/backend/src/constants/index.js b/backend/src/constants/index.js index 2614455..e9e9098 100644 --- a/backend/src/constants/index.js +++ b/backend/src/constants/index.js @@ -1,10 +1,14 @@ const { MATCH_STATUS, SUGGESTION_STATUS } = require('./statuses'); const { ACCOUNT_TIER, FAIRNESS_SUPPORTER_PENALTY, FAIRNESS_COMFORT_PENALTY } = require('./tiers'); +// Message validation +const MESSAGE_MAX_LENGTH = 2000; + module.exports = { MATCH_STATUS, SUGGESTION_STATUS, ACCOUNT_TIER, FAIRNESS_SUPPORTER_PENALTY, FAIRNESS_COMFORT_PENALTY, + MESSAGE_MAX_LENGTH, }; diff --git a/backend/src/socket/index.js b/backend/src/socket/index.js index ee7018d..c8d9e3b 100644 --- a/backend/src/socket/index.js +++ b/backend/src/socket/index.js @@ -2,6 +2,7 @@ const { Server } = require('socket.io'); const { verifyToken } = require('../utils/auth'); const { prisma } = require('../utils/db'); const { ACTIONS, log: activityLog } = require('../services/activityLog'); +const { MESSAGE_MAX_LENGTH } = require('../constants'); // Track active users in each event room const activeUsers = new Map(); // eventId -> Set of { socketId, userId, username, avatar } @@ -310,6 +311,19 @@ function initializeSocket(httpServer) { return socket.emit('error', { message: 'Not in an event room' }); } + // Validate message content + if (!content || typeof content !== 'string') { + return socket.emit('error', { message: 'Invalid message content' }); + } + + if (content.trim().length === 0) { + return socket.emit('error', { message: 'Message cannot be empty' }); + } + + if (content.length > MESSAGE_MAX_LENGTH) { + return socket.emit('error', { message: `Message too long. Maximum ${MESSAGE_MAX_LENGTH} characters allowed.` }); + } + const eventId = socket.currentEventId; const roomName = socket.currentEventRoom; @@ -434,6 +448,19 @@ function initializeSocket(httpServer) { // Send message to match room socket.on('send_match_message', async ({ matchId, content }) => { try { + // Validate message content + if (!content || typeof content !== 'string') { + return socket.emit('error', { message: 'Invalid message content' }); + } + + if (content.trim().length === 0) { + return socket.emit('error', { message: 'Message cannot be empty' }); + } + + if (content.length > MESSAGE_MAX_LENGTH) { + return socket.emit('error', { message: `Message too long. Maximum ${MESSAGE_MAX_LENGTH} characters allowed.` }); + } + const roomName = `match_${matchId}`; // Get match and its room diff --git a/frontend/src/components/chat/ChatInput.jsx b/frontend/src/components/chat/ChatInput.jsx index ce4a8e3..af48af9 100644 --- a/frontend/src/components/chat/ChatInput.jsx +++ b/frontend/src/components/chat/ChatInput.jsx @@ -1,7 +1,8 @@ import { Send } from 'lucide-react'; +import { MESSAGE_MAX_LENGTH } from '../../constants'; /** - * Chat Input component with send button + * Chat Input component with send button and character counter * * @param {string} value - Input value * @param {function} onChange - Change handler for input @@ -26,26 +27,42 @@ const ChatInput = ({ onSubmit(e); }; + const charCount = value.length; + const isNearLimit = charCount > MESSAGE_MAX_LENGTH * 0.8; // 80% of limit + const isOverLimit = charCount > MESSAGE_MAX_LENGTH; + return ( -
- - -
+
+
+ + +
+ + {/* Character counter - show when approaching limit */} + {isNearLimit && ( +
+ + {charCount}/{MESSAGE_MAX_LENGTH} + +
+ )} +
); }; diff --git a/frontend/src/constants/index.js b/frontend/src/constants/index.js index ffcf2c7..e2ff519 100644 --- a/frontend/src/constants/index.js +++ b/frontend/src/constants/index.js @@ -5,3 +5,6 @@ export { CONNECTION_STATE, SUGGESTION_TYPE, } from './statuses'; + +// Message validation +export const MESSAGE_MAX_LENGTH = 2000;