UNPKG

atozas-push-notification

Version:

Real-time push notifications across platforms using socket.io

339 lines 11.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AtozasPushNotificationServer = void 0; const socket_io_1 = require("socket.io"); const http_1 = require("http"); class AtozasPushNotificationServer { constructor(config = {}) { this.connectedUsers = new Map(); this.userSocketMap = new Map(); // userId -> socketId this.groups = new Map(); // groupId -> Set of userIds this.httpServer = (0, http_1.createServer)(); this.io = new socket_io_1.Server(this.httpServer, { cors: config.cors || { origin: "*", methods: ["GET", "POST"] }, allowEIO3: config.allowEIO3 || true, transports: config.transports || ['websocket', 'polling'] }); this.setupEventHandlers(); if (config.port) { this.listen(config.port); } } /** * Start the server on specified port */ listen(port) { this.httpServer.listen(port, () => { console.log(`Atozas Push Notification Server running on port ${port}`); }); } /** * Send notification to specific user(s), group(s), or broadcast to all */ sendNotification(params) { const { to, target, notification, options } = params; // Add timestamp if not provided if (!notification.timestamp) { notification.timestamp = Date.now(); } const payload = { notification, options }; switch (to) { case 'user': return this.sendToUser(target, payload); case 'group': return this.sendToGroup(target, payload); case 'broadcast': return this.sendBroadcast(payload); default: console.error('Invalid "to" parameter. Must be "user", "group", or "broadcast"'); return false; } } /** * Send notification to multiple users */ sendToUsers(userIds, notification, options) { const payload = { notification: { ...notification, timestamp: notification.timestamp || Date.now() }, options }; let success = true; userIds.forEach(userId => { if (!this.sendToUser(userId, payload)) { success = false; } }); return success; } /** * Send notification to multiple groups */ sendToGroups(groupIds, notification, options) { const payload = { notification: { ...notification, timestamp: notification.timestamp || Date.now() }, options }; let success = true; groupIds.forEach(groupId => { if (!this.sendToGroup(groupId, payload)) { success = false; } }); return success; } /** * Get list of connected users */ getConnectedUsers() { return Array.from(this.connectedUsers.values()).map(user => user.userInfo); } /** * Get user connection status */ isUserOnline(userId) { return this.userSocketMap.has(userId); } /** * Get users in a specific group */ getGroupUsers(groupId) { const group = this.groups.get(groupId); return group ? Array.from(group) : []; } /** * Get all groups */ getAllGroups() { return Array.from(this.groups.keys()); } /** * Add user to group programmatically */ addUserToGroup(userId, groupId) { if (!this.groups.has(groupId)) { this.groups.set(groupId, new Set()); } this.groups.get(groupId).add(userId); // Update user's groups if connected const socketId = this.userSocketMap.get(userId); if (socketId) { const user = this.connectedUsers.get(socketId); if (user) { user.groups.add(groupId); user.socket.join(groupId); } } return true; } /** * Remove user from group programmatically */ removeUserFromGroup(userId, groupId) { const group = this.groups.get(groupId); if (!group) return false; group.delete(userId); // Clean up empty groups if (group.size === 0) { this.groups.delete(groupId); } // Update user's groups if connected const socketId = this.userSocketMap.get(userId); if (socketId) { const user = this.connectedUsers.get(socketId); if (user) { user.groups.delete(groupId); user.socket.leave(groupId); } } return true; } /** * Get server statistics */ getStats() { return { connectedUsers: this.connectedUsers.size, totalGroups: this.groups.size, groupStats: Array.from(this.groups.entries()).map(([groupId, users]) => ({ groupId, userCount: users.size })) }; } /** * Disconnect user */ disconnectUser(userId, reason) { const socketId = this.userSocketMap.get(userId); if (!socketId) return false; const user = this.connectedUsers.get(socketId); if (user) { user.socket.disconnect(true); return true; } return false; } /** * Send notification to a specific user */ sendToUser(userId, payload) { const socketId = this.userSocketMap.get(userId); if (!socketId) { console.warn(`User ${userId} is not connected`); return false; } const user = this.connectedUsers.get(socketId); if (!user) { console.warn(`User ${userId} connection not found`); return false; } user.socket.emit('notification', payload); return true; } /** * Send notification to a group */ sendToGroup(groupId, payload) { const group = this.groups.get(groupId); if (!group || group.size === 0) { console.warn(`Group ${groupId} not found or empty`); return false; } this.io.to(groupId).emit('notification', payload); return true; } /** * Broadcast notification to all connected users */ sendBroadcast(payload) { this.io.emit('notification', payload); return true; } /** * Setup socket event handlers */ setupEventHandlers() { this.io.on('connection', (socket) => { console.log(`Client connected: ${socket.id}`); // Handle user authentication socket.on('authenticate', (userInfo) => { this.handleUserAuthentication(socket, userInfo); }); // Handle joining groups socket.on('join_group', (groupId) => { this.handleJoinGroup(socket, groupId); }); // Handle leaving groups socket.on('leave_group', (groupId) => { this.handleLeaveGroup(socket, groupId); }); // Handle notification acknowledgment socket.on('notification_ack', (notificationId) => { console.log(`Notification ${notificationId} acknowledged by ${socket.id}`); }); // Handle user status requests socket.on('request_user_status', (userId) => { const isOnline = this.isUserOnline(userId); socket.emit('user_status', { userId, online: isOnline }); }); // Handle disconnection socket.on('disconnect', (reason) => { this.handleUserDisconnection(socket, reason); }); // Handle errors socket.on('error', (error) => { console.error(`Socket error for ${socket.id}:`, error); }); }); } /** * Handle user authentication */ handleUserAuthentication(socket, userInfo) { // Remove existing connection for this user if exists const existingSocketId = this.userSocketMap.get(userInfo.userId); if (existingSocketId && existingSocketId !== socket.id) { const existingUser = this.connectedUsers.get(existingSocketId); if (existingUser) { existingUser.socket.disconnect(true); this.connectedUsers.delete(existingSocketId); } } // Add new connection this.connectedUsers.set(socket.id, { socket, userInfo, groups: new Set(), lastSeen: Date.now() }); this.userSocketMap.set(userInfo.userId, socket.id); console.log(`User authenticated: ${userInfo.userId} (${socket.id})`); // Rejoin groups if user was in any const userGroups = Array.from(this.groups.entries()) .filter(([_, users]) => users.has(userInfo.userId)) .map(([groupId]) => groupId); userGroups.forEach(groupId => { socket.join(groupId); this.connectedUsers.get(socket.id).groups.add(groupId); }); } /** * Handle joining a group */ handleJoinGroup(socket, groupId) { const user = this.connectedUsers.get(socket.id); if (!user) { console.warn(`User not authenticated for socket ${socket.id}`); return; } // Add to group if (!this.groups.has(groupId)) { this.groups.set(groupId, new Set()); } this.groups.get(groupId).add(user.userInfo.userId); user.groups.add(groupId); socket.join(groupId); console.log(`User ${user.userInfo.userId} joined group ${groupId}`); } /** * Handle leaving a group */ handleLeaveGroup(socket, groupId) { const user = this.connectedUsers.get(socket.id); if (!user) return; const group = this.groups.get(groupId); if (group) { group.delete(user.userInfo.userId); // Clean up empty groups if (group.size === 0) { this.groups.delete(groupId); } } user.groups.delete(groupId); socket.leave(groupId); console.log(`User ${user.userInfo.userId} left group ${groupId}`); } /** * Handle user disconnection */ handleUserDisconnection(socket, reason) { const user = this.connectedUsers.get(socket.id); if (user) { console.log(`User ${user.userInfo.userId} disconnected: ${reason}`); // Remove from user mapping this.userSocketMap.delete(user.userInfo.userId); // Note: We don't remove from groups on disconnect to allow rejoining // Groups are only cleaned up when explicitly leaving or on server restart } this.connectedUsers.delete(socket.id); console.log(`Client disconnected: ${socket.id}`); } } exports.AtozasPushNotificationServer = AtozasPushNotificationServer; //# sourceMappingURL=server.js.map