Push notification_created socket event for instant bell badge updates

Original task: Make the bell badge update instantly when a chat
notification arrives.

Changes:
- artifacts/api-server/src/routes/conversations.ts: After
  createMessageNotifications inserts the rows, emit a
  `notification_created` event to each recipient's `user:{id}` room
  (carrying type/relatedId/relatedType for future routing).
- artifacts/teaboy-os/src/hooks/use-notifications-socket.ts: New hook
  that opens a global socket connection (when the user is logged in)
  and invalidates getListNotificationsQueryKey() and
  getGetHomeStatsQueryKey() whenever `notification_created` fires.
- artifacts/teaboy-os/src/App.tsx: Mounts the hook via a small
  NotificationsSocketBridge component inside AuthProvider so the
  listener is active on every page (home, notifications, services,
  etc.), not just /chat.

Notes:
- Socket join already happens server-side on connect
  (`socket.join('user:{userId}')`), so no API server topology change
  was needed.
- Pre-existing TS errors in api-server and teaboy-os are unrelated to
  this change.
This commit is contained in:
Riyadh
2026-04-21 10:41:01 +00:00
parent 6316893c0e
commit 999d1a3e50
3 changed files with 48 additions and 0 deletions
@@ -903,6 +903,15 @@ async function createMessageNotifications(input: {
relatedType: "conversation",
})),
);
const { io } = await import("../index.js");
for (const r of recipients) {
io.to(`user:${r.userId}`).emit("notification_created", {
type: "chat",
relatedId: input.conversationId,
relatedType: "conversation",
});
}
}
router.post("/conversations/:id/read", requireAuth, async (req, res): Promise<void> => {
+7
View File
@@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { AuthProvider } from "@/contexts/AuthContext";
import { useNotificationsSocket } from "@/hooks/use-notifications-socket";
import NotFound from "@/pages/not-found";
import LoginPage from "@/pages/login";
import RegisterPage from "@/pages/register";
@@ -23,9 +24,15 @@ const queryClient = new QueryClient({
},
});
function NotificationsSocketBridge() {
useNotificationsSocket();
return null;
}
function Router() {
return (
<AuthProvider>
<NotificationsSocketBridge />
<Switch>
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
@@ -0,0 +1,32 @@
import { useEffect } from "react";
import { io } from "socket.io-client";
import { useQueryClient } from "@tanstack/react-query";
import {
getListNotificationsQueryKey,
getGetHomeStatsQueryKey,
} from "@workspace/api-client-react";
import { useAuth } from "@/contexts/AuthContext";
const BASE = import.meta.env.BASE_URL.replace(/\/$/, "");
export function useNotificationsSocket() {
const { user } = useAuth();
const queryClient = useQueryClient();
useEffect(() => {
if (!user) return;
const socket = io(window.location.origin, {
path: `${BASE}/api/socket.io`,
});
socket.on("notification_created", () => {
queryClient.invalidateQueries({ queryKey: getListNotificationsQueryKey() });
queryClient.invalidateQueries({ queryKey: getGetHomeStatsQueryKey() });
});
return () => {
socket.disconnect();
};
}, [user, queryClient]);
}