diff --git a/libs/room/src/service/workers.rs b/libs/room/src/service/workers.rs index 9451b4b..f8dbe38 100644 --- a/libs/room/src/service/workers.rs +++ b/libs/room/src/service/workers.rs @@ -207,7 +207,10 @@ where let semaphore = worker_semaphore.clone(); tokio::spawn(async move { - let _permit = semaphore.acquire().await.expect("semaphore closed"); + let Ok(_permit) = semaphore.acquire().await else { + tracing::warn!(task_id = %task_id, "semaphore closed, skipping task"); + return; + }; let result = execute(task_id, task_service.clone()).await; diff --git a/src/components/room/RoomMessageSearch.tsx b/src/components/room/RoomMessageSearch.tsx index cbb3d92..7451398 100644 --- a/src/components/room/RoomMessageSearch.tsx +++ b/src/components/room/RoomMessageSearch.tsx @@ -5,6 +5,7 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { Search, X, Loader2, ArrowUp } from 'lucide-react'; import { client } from '@/client/client.gen'; import type { RoomMessageResponse } from '@/client'; +import { toast } from 'sonner'; interface SearchResult { messages: RoomMessageResponse[]; @@ -40,13 +41,13 @@ export function RoomMessageSearch({ roomId, onSelectMessage, onClose }: RoomMess url: `/api/rooms/${roomId}/messages/search`, params: { q: searchQuery, limit: 20, offset: searchOffset }, }); - const data = (resp.data as any)?.data as SearchResult | undefined; + const data = resp.data?.data as SearchResult | undefined; if (data) { setResults(data); setOffset(searchOffset); } } catch (err) { - console.error('Search failed:', err); + toast.error('Search failed. Please try again.'); } finally { setIsSearching(false); } @@ -66,9 +67,11 @@ export function RoomMessageSearch({ roomId, onSelectMessage, onClose }: RoomMess const highlightText = (text: string, searchQuery: string) => { if (!searchQuery.trim()) return text; - const regex = new RegExp(`(${searchQuery})`, 'gi'); + // Escape regex special characters to prevent regex injection / ReDoS. + const escaped = searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`(${escaped})`, 'gi'); const parts = text.split(regex); - return parts.map((part, i) => + return parts.map((part, i) => regex.test(part) ? ( {part} ) : (