Skip to main content
This guide explains how to integrate Socket.io to receive real-time notifications when cryptocurrency transactions are confirmed.

Overview

The Crypto Shop Backend uses Socket.io to push real-time notifications to clients when:
  • TRX payment transactions are confirmed on the blockchain
  • Order status changes from “pending” to “completed”
  • Product stock is updated

Socket.io Server Configuration

From src/config/socket.js:5, the server is configured with CORS support:
import { Server } from 'socket.io';

let io;

export const initializeSocket = (server) => {
  io = new Server(server, {
    cors: {
      origin: ['http://localhost:3000', 'http://localhost:5173', process.env.FRONTEND_URL],
      credentials: true,
      methods: ['GET', 'POST']
    }
  });

  io.on('connection', (socket) => {
    console.log(`[Socket] User connected: ${socket.id}`);

    socket.on('join-user', (userId) => {
      socket.join(`user:${userId}`);
      console.log(`[Socket] User ${userId} joined room user:${userId}`);
    });

    socket.on('disconnect', () => {
      console.log(`[Socket] User disconnected: ${socket.id}`);
    });
  });

  return io;
};

Connection Flow

1

User Connects

Client establishes WebSocket connection to the server.
2

Join User Room

Client emits join-user event with their user ID to join a private room.
3

Wait for Events

Client listens for transaction:confirmed events.
4

Receive Notification

When a transaction is confirmed, server emits event to the user’s room.

Client Setup

Installation

Install the Socket.io client library:
npm install socket.io-client

Basic Connection

import { io } from 'socket.io-client';

// Connect to the server
const socket = io('http://localhost:3000', {
  withCredentials: true,
  transports: ['websocket', 'polling']
});

// Wait for connection
socket.on('connect', () => {
  console.log('Connected to server:', socket.id);
  
  // Join your user room
  const userId = '507f1f77bcf86cd799439010'; // Your user ID
  socket.emit('join-user', userId);
  console.log('Joined user room:', userId);
});

// Listen for transaction confirmations
socket.on('transaction:confirmed', (data) => {
  console.log('Transaction confirmed!', data);
  
  // data contains:
  // - orderId: The order that was confirmed
  // - txHash: Transaction hash on blockchain
  // - message: Confirmation message
  // - timestamp: When it was confirmed
});

// Handle disconnection
socket.on('disconnect', () => {
  console.log('Disconnected from server');
});

Transaction Confirmation Event

From src/config/socket.js:37, when a transaction is confirmed:
export const emitTransactionConfirmed = (userId, orderId, txHash) => {
  const io = getIO();
  io.to(`user:${userId}`).emit('transaction:confirmed', {
    orderId,
    txHash,
    message: 'Your purchase has been confirmed. You can now place new orders.',
    timestamp: new Date()
  });
};

Event Data Structure

{
  "orderId": "507f1f77bcf86cd799439013",
  "txHash": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "message": "Your purchase has been confirmed. You can now place new orders.",
  "timestamp": "2024-01-20T14:35:22.123Z"
}

Complete Integration Example

import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';

export function useTransactionNotifications(userId) {
  const [socket, setSocket] = useState(null);
  const [notifications, setNotifications] = useState([]);

  useEffect(() => {
    // Create socket connection
    const newSocket = io('http://localhost:3000', {
      withCredentials: true,
      transports: ['websocket', 'polling']
    });

    newSocket.on('connect', () => {
      console.log('Socket connected');
      newSocket.emit('join-user', userId);
    });

    newSocket.on('transaction:confirmed', (data) => {
      console.log('Transaction confirmed:', data);
      
      // Add to notifications
      setNotifications(prev => [...prev, data]);
      
      // Show browser notification
      if ('Notification' in window && Notification.permission === 'granted') {
        new Notification('Payment Confirmed', {
          body: data.message,
          icon: '/icon.png'
        });
      }
      
      // Play sound
      const audio = new Audio('/notification.mp3');
      audio.play();
    });

    newSocket.on('disconnect', () => {
      console.log('Socket disconnected');
    });

    setSocket(newSocket);

    // Cleanup
    return () => {
      newSocket.disconnect();
    };
  }, [userId]);

  return { socket, notifications };
}

// Usage in component
function App() {
  const userId = '507f1f77bcf86cd799439010';
  const { notifications } = useTransactionNotifications(userId);

  return (
    <div>
      <h2>Notifications</h2>
      {notifications.map((notif, index) => (
        <div key={index}>
          Order {notif.orderId} confirmed!
          <br />
          TX: {notif.txHash}
        </div>
      ));
    </div>
  );
}

When Notifications are Sent

From src/services/transactionListener.js:72, notifications are sent when:
  1. A pending transaction receives 21 confirmations on the blockchain
  2. The transaction type is “purchase”
  3. The order status is “pending”
if (status && status.confirmed) {
  // Update transaction status
  transaction.status = 'confirmed';
  transaction.confirmations = CONFIRMATIONS_REQUIRED;
  await transaction.save();

  if (transaction.orderId) {
    const order = await Order.findById(transaction.orderId);
    if (order) {
      if (transaction.type === 'purchase' && order.status === 'pending') {
        order.status = 'completed';
        await order.save();

        // Reduce stock
        for (const item of order.products) {
          await Product.findByIdAndUpdate(
            item.productId,
            { $inc: { stock: -item.quantity } }
          );
        }

        // Send notification
        emitTransactionConfirmed(
          transaction.userId.toString(),
          transaction.orderId.toString(),
          transaction.transactionHash
        );
      }
    }
  }
}

Room-based Messaging

The system uses room-based messaging to ensure users only receive their own notifications:
  • Each user joins a private room: user:{userId}
  • Notifications are emitted only to the specific user’s room
  • Multiple clients can connect with the same user ID and all will receive notifications

Error Handling

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  withCredentials: true,
  transports: ['websocket', 'polling'],
  reconnection: true,
  reconnectionDelay: 1000,
  reconnectionAttempts: 5
});

socket.on('connect', () => {
  console.log('Connected successfully');
});

socket.on('connect_error', (error) => {
  console.error('Connection error:', error.message);
});

socket.on('disconnect', (reason) => {
  console.log('Disconnected:', reason);
  
  if (reason === 'io server disconnect') {
    // Server disconnected, reconnect manually
    socket.connect();
  }
});

socket.on('reconnect', (attemptNumber) => {
  console.log('Reconnected after', attemptNumber, 'attempts');
  
  // Re-join user room
  const userId = '507f1f77bcf86cd799439010';
  socket.emit('join-user', userId);
});

socket.on('reconnect_error', (error) => {
  console.error('Reconnection error:', error);
});

socket.on('reconnect_failed', () => {
  console.error('Reconnection failed after maximum attempts');
});

Browser Notifications

Request permission and show browser notifications:
// Request notification permission
async function requestNotificationPermission() {
  if ('Notification' in window) {
    const permission = await Notification.requestPermission();
    return permission === 'granted';
  }
  return false;
}

// Setup socket with browser notifications
async function setupNotifications(userId) {
  const hasPermission = await requestNotificationPermission();
  
  const socket = io('http://localhost:3000', {
    withCredentials: true
  });

  socket.on('connect', () => {
    socket.emit('join-user', userId);
  });

  socket.on('transaction:confirmed', (data) => {
    console.log('Transaction confirmed:', data);
    
    if (hasPermission) {
      const notification = new Notification('Payment Confirmed', {
        body: `Order ${data.orderId} has been confirmed!`,
        icon: '/icon.png',
        badge: '/badge.png',
        tag: data.orderId,
        requireInteraction: false
      });

      notification.onclick = () => {
        window.focus();
        window.location.href = `/orders/${data.orderId}`;
      };
    }
  });

  return socket;
}

Testing Notifications

To test the notification system:
1

Create an Order

Use the Creating Orders guide to create an order.
2

Connect Socket.io

Connect your client and join your user room.
3

Pay for Order

Use the Payment Processing guide to pay.
4

Wait for Confirmation

The transaction listener checks every 15 seconds. You should receive a notification when the transaction is confirmed (21 confirmations on TRON blockchain).

Socket.io Events Reference

join-userJoin a user-specific room to receive notifications.
socket.emit('join-user', userId);
Parameters:
  • userId (string): The user’s MongoDB ObjectId

Production Considerations

For production deployments:
  • Use secure WebSocket connections (wss://)
  • Set secure: true in CORS configuration
  • Add rate limiting for socket events
  • Implement authentication middleware for socket connections
  • Use Redis adapter for horizontal scaling
  • Monitor socket connections and events

Next Steps

Payment Processing

Understand how payments trigger notifications

Creating Orders

Learn how to create orders that can be paid

Build docs developers (and LLMs) love