node.jswebsocketdeployment

Unable to deliver server events through websockets in production


So I have a scenario that demands I send events from a server to client. I had tried with Server Side Events but I had the issue that I had when I moved to Websockets. Because of this issue I move from Heroku to Render. The only solution was permanent connection which I never had on Heroku. The scenario is at every point in a mobile wallet transaction I inform the user/client what is going on. For example waiting for response, Enter your PIN.These messages deliver on local dev environment but nothing in production. These dynamic events are delivered to all connected clients whoes readyState OPEN.

interface CustomWebSocket extends WebSocket {
  clientId?: string;
}

const customWs = ws as CustomWebSocket;
    customWs.clientId = clientId;
    // Store the client
    clients.add(customWs);
//event webhook
  app.post('/api/v1/payment/momo-event-webhook', async (req: Request, res: Response) => {
    const event = req.body;

    // Relay the event to all connected clients
    const broadcast = (clients: Set<WebSocket>, message: any) => {
      clients.forEach((client) => {
        const id = (client as CustomWebSocket).clientId?.toString();
        const messageItem = JSON.stringify({ clientId: id, event: message });
        console.log(`Sending message to client: ${id}`);
        if (client.readyState === WebSocket.OPEN) {
          client.send(messageItem, (error) => {
            if (error) {
              console.error(`Error sending message to client: ${id}`, error);
            }
          });
        }
      });
    };

    broadcast(clients, event);

    res.status(200).json({ success: true, message: 'Event received' });
  });

This is the definition

export const supplyPaymentEvent = async (message: string) => {
  return await fetch(process.env.PAYMENT_EVENT_ENDPOINT as string, {
    method: 'POST',
    body: JSON.stringify({ state: message }),
    headers: { 'Content-Type': 'application/json' },
  });
};

Then is how I call it in various controllers

await supplyPaymentEvent('saving subscription');

Everybody asks why I created a route and a controller for this feature but I need to call it in places that could deliver it to the websocket. Perhaps a function approach will do but I don't think that is my problem. I hope the issue is not from where I am trying to send the message


Solution

  • So when its said that you can run around naked in your house but you can't do that in public space else you can be arrested for indecent exposure.. That happened to me. I make a recursive call to my server and accessed a route on it. It was okay but in production that will not take off let alone fly. I had to refactor my supplyPaymentEvent and I left that post route alone. Maybe I may use if to send message to the server.

    import { CustomWebSocket } from '../index';
    import WebSocket from 'ws';
    
    export const supplyPaymentEvent = (clients: Set<WebSocket>, message: string) => {
      clients.forEach((client) => {
        const id = (client as unknown as CustomWebSocket).clientId?.toString();
        const messageItem = JSON.stringify({ clientId: id, event: { state: message } });
        console.log(`Sending message to client: ${id}`);
        if (client.readyState === WebSocket.OPEN) {
          client.send(messageItem, (err) => {
            if (err) {
              console.error(`Error sending message to client ${id}:`, err);
            }
          });
        }
      });
    };
    

    Please note that clients was initialized upon websocket server initialization

    interface WebSocketServerRequest extends Request {
      wss?: WebSocketServer;
    }
    wss.on('connection', (ws: WebSocket, req: WebSocketServerRequest) => {
        // Generate a unique ID for the client and add it to the map
        const clientId = req.headers['sec-websocket-key'] as string;
        // Save clientId in ws instance
    const customWs = ws as CustomWebSocket;
        customWs.clientId = clientId;
        // Store the client
        clients.add(customWs);
    app.locals.clients = clients;
    });
    

    And Inside Payment controller I have this

    export const momoMakePayment = async (req: GetUserAuthInfoRequest, res: Response) => {
      try {
        //acquiring web socket clients
        const clients = req.app.locals.clients;
        const userId = req.user && req.user.userId;
        //check if user exist
        const userExist = await checkAccountUserExists({ id: userId });
        await supplyPaymentEvent(clients, 'saving subscription');
      } catch (error) {
        return res.status(500).json({ success: false, message: 'Internal server error =>' + error });
      }
    };