atozas-push-notification
Version:
Real-time push notifications across platforms using socket.io
339 lines • 11.2 kB
JavaScript
"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