Saltar al contenido principal

๐Ÿ”” Notificaciones In-App โ€” Integraciรณn Frontend (SwapBits)

Guรญa mรญnima para conectar un frontend web al servicio de notificaciones en tiempo real.


๐ŸŒ Connection Detailsโ€‹

๐Ÿ“ก WebSocket Endpoint

  • WS URL (namespace): wss://swapbits.site/notifications
  • Path: /ws
  • Transport: websocket
  • Authentication: JWT token in auth.token during connection

๐Ÿš€ Integration Flowโ€‹

Step-by-step Connection Process

  1. Connect with JWT token
  2. Wait for connection:established event
  3. Emit join_notifications with { deviceId, platform }
  4. Listen for notification events
  5. (Optional) Emit mark_read to mark notifications as read
  6. (Optional) Send periodic heartbeat for connection health

๐Ÿ’ป Frontend Implementationโ€‹

JavaScript/Browser Example (ESM)โ€‹

Complete WebSocket notifications integration
<script type="module">
import { io } from "https://cdn.skypack.dev/socket.io-client";

const JWT = window.localStorage.getItem("access_token");

const socket = io("wss://swapbits.site/notifications", {
path: "/ws",
transports: ["websocket"],
auth: { token: JWT },
autoConnect: true,
timeout: 15000
});

socket.on("connection:established", () => {
console.log("๐ŸŽ‰ Connected to notifications service");

socket.emit("join_notifications", {
deviceId: `web-${crypto.randomUUID()}`,
platform: "web"
});
});

socket.on("joined_notifications", (data) => {
console.log("โœ… Joined notifications room:", data);

// Start heartbeat every 30 seconds
setInterval(() => {
socket.emit("heartbeat");
}, 30000);
});

socket.on("notification", (notification) => {
console.log("๐Ÿ”” New notification:", notification);

// Display notification in your UI
displayNotification(notification);

// Optional: Mark as read immediately
socket.emit("mark_read", {
notificationIds: [notification.id]
});
});

socket.on("notifications_marked_read", (data) => {
console.log("๐Ÿ“– Notifications marked as read:", data);
// Update UI state if needed
updateNotificationState(data.notificationIds);
});

socket.on("heartbeat_ack", (data) => {
console.log("๐Ÿ’“ Heartbeat acknowledged:", data.timestamp);
});

socket.on("auth:error", (error) => {
console.error("๐Ÿ”’ Authentication error:", error);
// Handle auth errors (redirect to login, refresh token, etc.)
handleAuthError(error);
});

socket.on("connect_error", (error) => {
console.error("โŒ Connection error:", error);
});

socket.on("disconnect", (reason) => {
console.log("๐Ÿ”Œ Disconnected:", reason);
});

// Helper functions
function displayNotification(notification) {
// Create notification element
const notifEl = document.createElement('div');
notifEl.className = 'notification-item';
notifEl.innerHTML = `
<div class="notification-content">
<h4>${notification.data.title}</h4>
<p>${notification.data.message}</p>
<small>${new Date(notification.timestamp).toLocaleString()}</small>
${notification.data.actionUrl ? `
<a href="${notification.data.actionUrl}" class="notification-action">
${notification.data.actionText || 'View'}
</a>
` : ''}
</div>
`;

// Add to notifications container
document.getElementById('notifications-container')?.appendChild(notifEl);

// Optional: Show browser notification
if (Notification.permission === 'granted') {
new Notification(notification.data.title, {
body: notification.data.message,
icon: '/favicon.ico'
});
}
}

function updateNotificationState(notificationIds) {
notificationIds.forEach(id => {
const element = document.querySelector(`[data-notification-id="${id}"]`);
if (element) {
element.classList.add('read');
}
});
}

function handleAuthError(error) {
switch (error.code) {
case 'MISSING_TOKEN':
// Redirect to login
window.location.href = '/login';
break;
case 'INVALID_TOKEN':
// Try to refresh token
refreshAuthToken().then(() => {
socket.connect();
}).catch(() => {
window.location.href = '/login';
});
break;
default:
console.error('Unknown auth error:', error);
}
}
</script>

React Integration Exampleโ€‹

React hook for notifications
import { useEffect, useState } from 'react';
import { io, Socket } from 'socket.io-client';

interface Notification {
id: string;
type: string;
priority: string;
timestamp: number;
isPending: boolean;
data: {
title: string;
message: string;
actionUrl?: string;
actionText?: string;
metadata?: any;
};
}

export function useNotifications(accessToken: string) {
const [socket, setSocket] = useState<Socket | null>(null);
const [notifications, setNotifications] = useState<Notification[]>([]);
const [isConnected, setIsConnected] = useState(false);

useEffect(() => {
if (!accessToken) return;

const socketInstance = io("wss://swapbits.site/notifications", {
path: "/ws",
transports: ["websocket"],
auth: { token: accessToken },
autoConnect: true,
timeout: 15000
});

socketInstance.on("connection:established", () => {
setIsConnected(true);
socketInstance.emit("join_notifications", {
deviceId: `web-${crypto.randomUUID()}`,
platform: "web"
});
});

socketInstance.on("joined_notifications", () => {
// Start heartbeat
const heartbeatInterval = setInterval(() => {
socketInstance.emit("heartbeat");
}, 30000);

return () => clearInterval(heartbeatInterval);
});

socketInstance.on("notification", (notification: Notification) => {
setNotifications(prev => [notification, ...prev]);

// Show browser notification if permitted
if (Notification.permission === 'granted') {
new Notification(notification.data.title, {
body: notification.data.message,
icon: '/favicon.ico'
});
}
});

socketInstance.on("notifications_marked_read", (data) => {
setNotifications(prev =>
prev.filter(n => !data.notificationIds.includes(n.id))
);
});

socketInstance.on("auth:error", (error) => {
console.error("Auth error:", error);
setIsConnected(false);
});

socketInstance.on("disconnect", () => {
setIsConnected(false);
});

setSocket(socketInstance);

return () => {
socketInstance.disconnect();
};
}, [accessToken]);

const markAsRead = (notificationIds: string[]) => {
if (socket) {
socket.emit("mark_read", { notificationIds });
}
};

return {
notifications,
isConnected,
markAsRead
};
}

// Usage in component
function NotificationsComponent() {
const accessToken = localStorage.getItem('access_token') || '';
const { notifications, isConnected, markAsRead } = useNotifications(accessToken);

return (
<div className="notifications">
<div className="connection-status">
Status: {isConnected ? '๐ŸŸข Connected' : '๐Ÿ”ด Disconnected'}
</div>

<div className="notifications-list">
{notifications.map(notification => (
<div key={notification.id} className="notification-item">
<h4>{notification.data.title}</h4>
<p>{notification.data.message}</p>
<small>{new Date(notification.timestamp).toLocaleString()}</small>
{notification.data.actionUrl && (
<a href={notification.data.actionUrl}>
{notification.data.actionText || 'View'}
</a>
)}
<button onClick={() => markAsRead([notification.id])}>
Mark as Read
</button>
</div>
))}
</div>
</div>
);
}

๐Ÿ“ก Event Contractsโ€‹

Connection Eventsโ€‹

๐Ÿ”Œ connection:established (server โ†’ client)

Sent when the WebSocket connection is successfully established.

{
"userId": 59067317,
"room": "notifications_59067317",
"timestamp": "2025-10-17T19:26:27.000Z"
}

๐Ÿ“ฅ join_notifications (client โ†’ server)

Join the notifications room for receiving real-time notifications.

{
"deviceId": "web-6f0b1f1b-9f84-4f0a-8a09-7b8f8b2a3a44",
"platform": "web"
}

โœ… joined_notifications (server โ†’ client)

Confirmation that you've successfully joined the notifications room.

{
"userId": 59067317,
"room": "notifications_59067317",
"deviceId": "web-6f0b1f1b-9f84-4f0a-8a09-7b8f8b2a3a44",
"platform": "web",
"timestamp": "2025-10-17T19:26:28.000Z"
}

Notification Eventsโ€‹

๐Ÿ”” notification (server โ†’ client)

Real-time notification pushed to connected clients.

{
"id": "notif_1760729187915_x0sbsqkza",
"type": "deposit_confirmed",
"priority": "high",
"timestamp": 1760729187906,
"isPending": false,
"data": {
"title": "Depรณsito Confirmado",
"message": "Tu depรณsito de 0.00049 BNB ha sido confirmado en BSC",
"actionUrl": "/wallet",
"actionText": "Ver Wallet",
"metadata": {
"amount": 0.00049,
"currency": "BNB",
"txHash": "0x5c50bbb7cca4077b0e124dab411fb08085173c81e25aa3c401784b8b5bb2417f",
"network": "BSC",
"blockNumber": 64972818,
"walletAddress": "0xe6fa47b14c42c91170bae748deb2c9d67fbb8915",
"transactionType": "incoming",
"isSignificant": false
}
}
}

Notification Types:

  • deposit_confirmed - Cryptocurrency deposit confirmed
  • withdrawal_completed - Withdrawal processed
  • trade_executed - Trading order executed
  • security_alert - Security-related notifications
  • system_maintenance - System maintenance notifications

๐Ÿ“– mark_read (client โ†’ server)

Mark one or more notifications as read.

{
"notificationIds": ["notif_1760729187915_x0sbsqkza"]
}

โœ… notifications_marked_read (server โ†’ client)

Confirmation that notifications have been marked as read.

{
"notificationIds": ["notif_1760729187915_x0sbsqkza"],
"userId": 59067317,
"timestamp": "2025-10-17T19:26:31.317Z"
}

Health Check Eventsโ€‹

๐Ÿ’“ Heartbeat Events

Client โ†’ Server: heartbeat (no payload)

Server โ†’ Client: heartbeat_ack

{ "timestamp": "2025-10-17T19:27:00.000Z" }

Purpose: Maintain connection health and detect disconnections Frequency: Every 30 seconds (recommended)

Error Eventsโ€‹

๐Ÿ”’ auth:error (server โ†’ client)

Authentication errors during connection or operation.

Missing Token:

{ "code": "MISSING_TOKEN", "message": "Auth token required" }

Invalid Token:

{ "code": "INVALID_TOKEN", "message": "Token expired or invalid" }

Invalid Payload:

{ "code": "INVALID_TOKEN_PAYLOAD", "message": "Token payload missing userId" }

๐Ÿ› ๏ธ Best Practicesโ€‹

๐Ÿ’ก Implementation Tips

Connection Management:

  • Always check token validity before connecting
  • Implement reconnection logic for network interruptions
  • Handle authentication errors gracefully

Performance:

  • Limit the number of notifications stored in memory
  • Implement notification batching for high-frequency events
  • Use proper cleanup in React useEffect

User Experience:

  • Request notification permissions on user interaction
  • Provide visual indicators for connection status
  • Allow users to configure notification preferences

Security:

  • Never log sensitive token information
  • Validate notification data before displaying
  • Implement proper error boundaries


๐Ÿ†˜ Troubleshootingโ€‹

๐Ÿ”ง Common Issues & Solutions

Problem: Connection fails with auth error Solution: Verify JWT token is valid and not expired

Problem: Not receiving notifications Solution: Ensure you've called join_notifications after connection:established

Problem: Connection drops frequently Solution: Implement heartbeat mechanism and reconnection logic

Problem: Browser notifications not showing Solution: Request notification permissions and check browser settings

Problem: Memory leaks in React Solution: Properly cleanup socket connections in useEffect return

Problem: Duplicate notifications Solution: Check for multiple socket instances, ensure proper cleanup