๐ 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.tokenduring connection
๐ Integration Flowโ
Step-by-step Connection Process
- Connect with JWT token
- Wait for
connection:establishedevent - Emit
join_notificationswith{ deviceId, platform } - Listen for
notificationevents - (Optional) Emit
mark_readto mark notifications as read - (Optional) Send periodic
heartbeatfor connection health
๐ป Frontend Implementationโ
JavaScript/Browser Example (ESM)โ
<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 = `
`;
// 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โ
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 confirmedwithdrawal_completed- Withdrawal processedtrade_executed- Trading order executedsecurity_alert- Security-related notificationssystem_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
๐ Related Documentationโ
- ๐ Authentication Explorer - Get JWT tokens
- ๐ค Profile Explorer - User management
- ๐ WebSocket Order Updates - Real-time trading data (Coming Soon)
- ๐ฑ Mobile Integration - SDK for mobile apps
๐ 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