اذهب إلى المحتوى

السؤال

نشر

السلام عليكم،

أواجه مشكلة في نظام Realtime Notifications باستخدام NestJS + Socket.IO + React (Next.js) ولم أتمكن من معرفة سببها.

وصف المشكلة

اتصال الـ Socket يتم بنجاح.

المستخدم يُضاف إلى قائمة onlineUsers.

عند إنشاء إشعار يتم استدعاء NotificationGateway.sendNotification().

يتم العثور على الـ Socket الصحيح للمستخدم.

يتم تنفيذ emit("receive-notification") بدون أي أخطاء.

سجلات السيرفر تؤكد ذلك:

Socket Connected: ... Online Users: [...] FOUND SOCKETS = [...] EMIT TO ...

لكن في الواجهة الأمامية لا يتم استقبال حدث:

receive-notification

ولا يتم تنفيذ:

socket.on("receive-notification", ...)

الملفات التي أحتاج مراجعتها

Frontend

useNotification.ts

socket/socket.ts

useNotificationStore.ts

useAuthStore.ts

Backend

notification.gateway.ts

socket.adapter.ts

notification.service.ts

main.ts (خصوصًا تسجيل الـ WebSocket Adapter بواسطة app.useWebSocketAdapter(...))

ما تم التحقق منه

الاتصال بالـ Socket ناجح.

الـ Cookies تصل إلى الـ Socket Adapter.

socket.data.user يتم تعبئته.

المستخدم يُخزن داخل onlineUsers.

sendNotification() يُستدعى.

server.to(socketId).emit(...) يُنفذ.

لكن الحدث لا يصل إلى React، لذلك أحتاج المساعدة في معرفة سبب عدم استقبال حدث receive-notification رغم نجاح جميع الخطوات السابقة.

شكرًا لكم.

Qr menu.zip

Recommended Posts

  • 0
نشر

في socket.ts، كل مرة يتم استدعاء connectSocket()، يتم إضافة listeners جديدة لـ connect و connect_error وذلك يسبب تراكم listeners ويسبب مشاكل.

export const connectSocket = (): Socket => {
  const s = getSocket();
  s.on("connect", () => {        
    console.log(" SOCKET CONNECTED");
  });
  s.on("connect_error", (err) => { 
    console.log(" SOCKET ERROR:", err.message);
  });
};

يجب   إضافة listeners مرة واحدة فقط ومتابعة ذلك من خلال إنشاء متغير listenersAttached.

وكذلك   إضافة polling كـ fallback لإجبار Socket.IO على استخدام WebSocket فقط، وإن فشل الاتصال لثانية واحدة، لن يتم إعادة الاتصال بشكل صحيح.

عليك بتعديل الأجزاء التالية في الكود فقط:

import { io, Socket } from "socket.io-client";
import { useAuthStore } from "../stores/useAuthStore";
import { BACKEND_URL } from "../apis/request";

let socket: Socket | null = null;
let listenersAttached = false; 

export const getSocket = (): Socket => {
  if (socket) return socket;
  
  socket = io(BACKEND_URL, {
    autoConnect: false,
    withCredentials: true,
    transports: ["websocket", "polling"], 
  });
  
  if (!listenersAttached) {
    socket.on("connect", () => {
      console.log(" SOCKET CONNECTED, ID:", socket?.id);
    });
    socket.on("connect_error", (err) => {
      console.log(" SOCKET ERROR:", err.message);
    });
    socket.on("disconnect", () => {
      console.log("🔌 SOCKET DISCONNECTED");
    });
    listenersAttached = true;
  }
  
  return socket;
};

export const connectSocket = (): Socket => {
  const s = getSocket();
  const user = useAuthStore.getState().user;
  
  if (!user) {
    console.warn("Socket connection blocked: no authenticated user");
    return s;
  }
  
  if (!s.connected) {
    s.connect();
  }
  
  return s;
};

export const disconnectSocket = () => {
  if (socket?.connected) {
    socket.disconnect();
  }
  socket = null;
  listenersAttached = false; 
};

export default getSocket;

وفي useNotification.ts الـ User Reference يتغير في كل Render ولو s.user object يتم إنشاؤه من جديد في كل render، فإن الـ useEffect سيعيد تنفيذه في كل render وبالطبع يؤدي إلى إزالة وإضافة الـ listener بشكل مستمر.

استخدام selector للحصول على user.id فقط بدلاً من user object:

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
import type { Socket } from "socket.io-client";
import { INotification } from "../types/notification";
import { notificationApi } from "../apis/notification";
import { useNotificationStore } from "../stores/useNotificationStore";
import { useAuthStore } from "../stores/useAuthStore";
import { connectSocket } from "../socket/socket";

export const useNotification = (
  initialPage: number = 1,
  limit: number = 20,
) => {
  const {
    notifications,
    unreadCount,
    loading,
    isSocketConnected,
    setNotifications,
    setUnreadCount,
    setLoading,
    setSocketConnected,
    markAsReadLocal,
    markAllAsReadLocal,
    removeNotificationLocal,
    addIncomingNotification,
  } = useNotificationStore();

  const [notification, setNotification] = useState<INotification | null>(null);
  const [page, setPage] = useState(initialPage);
  const socketRef = useRef<Socket | null>(null);
  
  const userId = useAuthStore((s) => s.user?._id);


  useEffect(() => {
    if (!userId) return; 
    
    const socket = connectSocket();
    socketRef.current = socket;

    const handleConnect = () => setSocketConnected(true);
    const handleDisconnect = () => setSocketConnected(false);

    const handleReceiveNotification = (incoming: INotification) => {
      console.log("🔥 RECEIVE NOTIFICATION", incoming);
      addIncomingNotification(incoming);
      toast.success(incoming.title || "لديك إشعار جديد");
    };

    const handleNotificationRead = (data: { notificationId: string }) => {
      markAsReadLocal(data.notificationId);
    };

    socket.on("connect", handleConnect);
    socket.on("disconnect", handleDisconnect);
    socket.on("receive-notification", handleReceiveNotification);
    socket.on("notification-read", handleNotificationRead);

    if (socket.connected) {
      setSocketConnected(true);
    }

    return () => {
      socket.off("connect", handleConnect);
      socket.off("disconnect", handleDisconnect);
      socket.off("receive-notification", handleReceiveNotification);
      socket.off("notification-read", handleNotificationRead);
    };
  }, [userId, addIncomingNotification, markAsReadLocal, setSocketConnected]); 

};

وفي notification.gateway.ts، تأكد من أن الـ userId في onlineUsers يطابق الـ userId في الإشعار بإضافة console.log:

sendNotification(notification: {
  userId: string;
}) {
  const sockets = getUserSockets(notification.userId);
  
  console.log('📨 Sending notification to userId:', notification.userId);
  console.log('🔌 Online users:', onlineUsers);
  console.log('🎯 Found sockets:', sockets);
  
  if (!sockets.length) {
    console.warn('⚠️ No sockets found for userId:', notification.userId);
    return;
  }
  
  sockets.forEach((socket) => {
    console.log('📤 Emitting to socket:', socket.socketId);
    this.server
      .to(socket.socketId)
      .emit('receive-notification', notification);
  });
}

 

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...