@warriorteam/zalo-personal
Version:
Unofficial Zalo Personal API for JavaScript - A powerful library for interacting with Zalo personal accounts with URL attachment support, auto-reply, product catalog, and business features
1,019 lines (809 loc) • 28.6 kB
Markdown
# Lắng Nghe Sự Kiện (Event Listener)
## Tổng Quan
SDK cung cấp hệ thống listener mạnh mẽ để lắng nghe các sự kiện real-time:
- Tin nhắn mới
- Sự kiện nhóm (thêm/xóa thành viên, đổi tên, v.v.)
- Sự kiện bạn bè (kết bạn, unfriend)
- Reaction, typing, seen, delivered
- Undo actions
## Khởi Tạo Listener
### 1. Cấu Hình Cơ Bản
```typescript
import { Zalo, CloseReason } from 'zalo-personal-sdk';
// Khởi tạo với selfListen để nghe tin nhắn của chính mình
const zalo = new Zalo({ selfListen: true });
const api = await zalo.login(credentials);
// Bắt đầu lắng nghe
await api.listener.start();
console.log('🎧 Listener đã khởi động!');
```
### 2. Xử Lý Kết Nối
```typescript
// Sự kiện kết nối
api.listener.on('connect', () => {
console.log('✅ Đã kết nối listener');
});
// Sự kiện mất kết nối
api.listener.on('disconnect', (reason: CloseReason) => {
console.log('❌ Mất kết nối:', reason);
switch (reason) {
case CloseReason.Normal:
console.log('Ngắt kết nối bình thường');
break;
case CloseReason.Error:
console.log('Lỗi kết nối');
break;
case CloseReason.Reconnect:
console.log('Đang reconnect...');
break;
}
});
// Sự kiện lỗi
api.listener.on('error', (error) => {
console.error('🚨 Listener error:', error);
});
```
### 3. Dừng Listener
```typescript
// Dừng lắng nghe
await api.listener.stop();
console.log('⏹️ Listener đã dừng');
// Hoặc sử dụng trong signal handler
process.on('SIGINT', async () => {
console.log('🛑 Đang tắt listener...');
await api.listener.stop();
process.exit(0);
});
```
## Lắng Nghe Tin Nhắn
### 1. Tin Nhắn Cơ Bản
```typescript
api.listener.on('message', (message) => {
const { threadId, threadType, data, senderId } = message;
console.log(`💬 Tin nhắn mới từ ${senderId}:`);
console.log(` Thread: ${threadId} (${threadType === 0 ? 'User' : 'Group'})`);
console.log(` Nội dung: ${data.content || 'File đính kèm'}`);
console.log(` Loại: ${data.msgType}`);
console.log(` Thời gian: ${new Date(data.ts).toLocaleString()}`);
});
```
### 2. Lọc Tin Nhắn Theo Loại
```typescript
api.listener.on('message', (message) => {
const { data, senderId, threadId, threadType } = message;
switch (data.msgType) {
case 'webchat':
console.log(`📝 Tin nhắn văn bản: ${data.content}`);
break;
case 'chat.photo':
console.log(`📷 Ảnh: ${data.href}`);
console.log(` Kích thước: ${data.width}x${data.height}`);
break;
case 'chat.video.msg':
console.log(`🎥 Video: ${data.href}`);
console.log(` Thời lượng: ${data.duration}s`);
break;
case 'chat.audio':
console.log(`🎵 Audio: ${data.href}`);
break;
case 'chat.file':
console.log(`📎 File: ${data.fileName}`);
console.log(` Kích thước: ${data.fileSize} bytes`);
break;
case 'chat.sticker':
console.log(`😀 Sticker: ${data.stickerId}`);
break;
case 'chat.link':
console.log(`🔗 Link: ${data.href}`);
break;
case 'chat.location':
console.log(`📍 Vị trí: ${data.latitude}, ${data.longitude}`);
break;
default:
console.log(`❓ Loại tin nhắn không xác định: ${data.msgType}`);
}
});
```
### 3. Auto Reply Bot
```typescript
api.listener.on('message', async (message) => {
const { threadId, threadType, data, senderId } = message;
// Bỏ qua tin nhắn của chính mình
const myId = await api.getOwnId();
if (senderId === myId) return;
// Bỏ qua nếu không phải tin nhắn văn bản
if (data.msgType !== 'webchat' || !data.content) return;
const text = data.content.toLowerCase().trim();
try {
if (text === '/help') {
const helpText = `
🤖 Bot Commands:
/help - Hiển thị hướng dẫn
/time - Xem thời gian hiện tại
/weather - Dự báo thời tiết
/ping - Test bot
/info - Thông tin bot
`.trim();
await api.sendMessage(helpText, threadId, threadType);
} else if (text === '/time') {
const now = new Date().toLocaleString('vi-VN');
await api.sendMessage(`🕐 Bây giờ là: ${now}`, threadId, threadType);
} else if (text === '/ping') {
await api.sendMessage('🏓 Pong!', threadId, threadType);
} else if (text === '/weather') {
await api.sendMessage('⛅ Hôm nay trời đẹp, nắng nhẹ 25°C', threadId, threadType);
} else if (text === '/info') {
const info = `
🤖 Zalo Bot SDK v1.0
📅 Khởi động: ${new Date().toLocaleDateString()}
💻 Node.js ${process.version}
`.trim();
await api.sendMessage(info, threadId, threadType);
} else if (text.startsWith('/echo ')) {
const echoText = data.content.substring(6);
await api.sendMessage(`🔊 ${echoText}`, threadId, threadType);
} else if (text.includes('hello') || text.includes('hi') || text.includes('chào')) {
await api.sendMessage('👋 Xin chào! Gõ /help để xem hướng dẫn.', threadId, threadType);
}
} catch (error) {
console.error('❌ Lỗi auto reply:', error.message);
}
});
```
### 4. Message Analytics
```typescript
class MessageAnalytics {
private stats = {
totalMessages: 0,
messagesByType: new Map<string, number>(),
messagesByUser: new Map<string, number>(),
messagesByThread: new Map<string, number>(),
dailyStats: new Map<string, number>()
};
constructor(api: any) {
api.listener.on('message', (message: any) => {
this.recordMessage(message);
});
}
recordMessage(message: any) {
const { data, senderId, threadId } = message;
const today = new Date().toDateString();
// Tổng tin nhắn
this.stats.totalMessages++;
// Theo loại
const currentTypeCount = this.stats.messagesByType.get(data.msgType) || 0;
this.stats.messagesByType.set(data.msgType, currentTypeCount + 1);
// Theo user
const currentUserCount = this.stats.messagesByUser.get(senderId) || 0;
this.stats.messagesByUser.set(senderId, currentUserCount + 1);
// Theo thread
const currentThreadCount = this.stats.messagesByThread.get(threadId) || 0;
this.stats.messagesByThread.set(threadId, currentThreadCount + 1);
// Theo ngày
const currentDayCount = this.stats.dailyStats.get(today) || 0;
this.stats.dailyStats.set(today, currentDayCount + 1);
}
getStats() {
return {
total: this.stats.totalMessages,
byType: Object.fromEntries(this.stats.messagesByType),
byUser: Object.fromEntries(this.stats.messagesByUser),
byThread: Object.fromEntries(this.stats.messagesByThread),
byDay: Object.fromEntries(this.stats.dailyStats)
};
}
printStats() {
const stats = this.getStats();
console.log('📊 Message Statistics:');
console.log(` Tổng tin nhắn: ${stats.total}`);
console.log(' Top message types:');
Object.entries(stats.byType)
.sort(([,a], [,b]) => (b as number) - (a as number))
.slice(0, 5)
.forEach(([type, count]) => {
console.log(` ${type}: ${count}`);
});
console.log(' Hôm nay:', stats.byDay[new Date().toDateString()] || 0);
}
}
// Sử dụng
const analytics = new MessageAnalytics(api);
// In thống kê mỗi 10 phút
setInterval(() => {
analytics.printStats();
}, 10 * 60 * 1000);
```
## Sự Kiện Nhóm
### 1. Quản Lý Thành Viên
```typescript
api.listener.on('group_event', (event) => {
const { groupId, actorId, targetId, type, data } = event;
switch (type) {
case 'group.member.add':
console.log(`👥 ${actorId} đã thêm ${targetId} vào nhóm ${groupId}`);
// Chào mừng thành viên mới
api.sendMessage(
`🎉 Chào mừng bạn ${targetId} tham gia nhóm!`,
groupId,
ThreadType.Group
);
break;
case 'group.member.remove':
console.log(`👋 ${actorId} đã xóa ${targetId} khỏi nhóm ${groupId}`);
break;
case 'group.member.left':
console.log(`🚪 ${targetId} đã rời nhóm ${groupId}`);
break;
case 'group.name.change':
console.log(`📝 ${actorId} đã đổi tên nhóm ${groupId} thành: ${data.newName}`);
break;
case 'group.avatar.change':
console.log(`🖼️ ${actorId} đã thay avatar nhóm ${groupId}`);
break;
case 'group.admin.add':
console.log(`👑 ${targetId} đã được thêm làm admin nhóm ${groupId}`);
break;
case 'group.admin.remove':
console.log(`👤 ${targetId} đã bị xóa khỏi admin nhóm ${groupId}`);
break;
}
});
```
### 2. Auto Moderation
```typescript
class GroupModerator {
private bannedWords = ['spam', 'hack', 'cheat'];
private warningCount = new Map<string, number>();
private api: any;
constructor(api: any) {
this.api = api;
api.listener.on('message', (message: any) => {
this.moderateMessage(message);
});
}
async moderateMessage(message: any) {
const { threadId, threadType, data, senderId } = message;
// Chỉ moderate trong nhóm
if (threadType !== 1) return;
// Bỏ qua nếu không phải tin nhắn văn bản
if (data.msgType !== 'webchat' || !data.content) return;
const content = data.content.toLowerCase();
// Kiểm tra từ cấm
const hasBannedWord = this.bannedWords.some(word => content.includes(word));
if (hasBannedWord) {
try {
// Xóa tin nhắn
await this.api.deleteMessage({
messageId: data.msgId,
threadId: threadId,
threadType: threadType
});
// Cảnh báo user
const warnings = (this.warningCount.get(senderId) || 0) + 1;
this.warningCount.set(senderId, warnings);
if (warnings >= 3) {
// Kick khỏi nhóm sau 3 lần cảnh báo
await this.api.removeUserFromGroup(threadId, senderId);
await this.api.sendMessage(
`🚫 ${senderId} đã bị kick khỏi nhóm do vi phạm nhiều lần.`,
threadId,
threadType
);
this.warningCount.delete(senderId);
} else {
await this.api.sendMessage(
`⚠️ @${senderId} Cảnh báo ${warnings}/3: Không sử dụng từ ngữ không phù hợp!`,
threadId,
threadType,
{
mentions: [{ pos: 2, len: senderId.length + 1, uid: senderId }]
}
);
}
console.log(`🛡️ Đã xóa tin nhắn vi phạm từ ${senderId}`);
} catch (error) {
console.error('❌ Lỗi moderation:', error.message);
}
}
}
addBannedWord(word: string) {
this.bannedWords.push(word.toLowerCase());
}
removeBannedWord(word: string) {
const index = this.bannedWords.indexOf(word.toLowerCase());
if (index > -1) {
this.bannedWords.splice(index, 1);
}
}
}
// Sử dụng
const moderator = new GroupModerator(api);
moderator.addBannedWord('badword');
```
## Sự Kiện Bạn Bè
### 1. Quản Lý Lời Mời Kết Bạn
```typescript
api.listener.on('friend_event', async (event) => {
const { type, fromId, toId, data } = event;
switch (type) {
case 'friend.request.received':
console.log(`👋 Nhận lời mời kết bạn từ ${fromId}`);
// Auto accept từ whitelist
const whitelist = ['trusted-user-1', 'trusted-user-2'];
if (whitelist.includes(fromId)) {
try {
await api.acceptFriendRequest(fromId);
console.log(`✅ Đã tự động chấp nhận kết bạn với ${fromId}`);
// Gửi tin nhắn chào mừng
await api.sendMessage(
'🎉 Chào mừng bạn! Cảm ơn bạn đã kết bạn với mình.',
fromId,
ThreadType.User
);
} catch (error) {
console.error('❌ Lỗi auto accept:', error.message);
}
}
break;
case 'friend.request.accepted':
console.log(`✅ ${toId} đã chấp nhận lời mời kết bạn`);
break;
case 'friend.removed':
console.log(`💔 ${fromId} đã unfriend`);
break;
case 'friend.blocked':
console.log(`🚫 ${fromId} đã chặn bạn`);
break;
}
});
```
### 2. Friend Request Manager
```typescript
class FriendRequestManager {
private api: any;
private autoAcceptRules: Array<(fromId: string, data: any) => boolean> = [];
constructor(api: any) {
this.api = api;
api.listener.on('friend_event', (event: any) => {
if (event.type === 'friend.request.received') {
this.handleFriendRequest(event);
}
});
}
addRule(rule: (fromId: string, data: any) => boolean) {
this.autoAcceptRules.push(rule);
}
async handleFriendRequest(event: any) {
const { fromId, data } = event;
try {
// Lấy thông tin người gửi
const userInfo = await this.api.getUserInfo([fromId]);
const user = userInfo.data[0];
console.log(`👋 Lời mời kết bạn từ: ${user.displayName}`);
console.log(` Lời nhắn: ${data.message || 'Không có'}`);
// Kiểm tra rules
const shouldAccept = this.autoAcceptRules.some(rule => rule(fromId, data));
if (shouldAccept) {
await this.api.acceptFriendRequest(fromId);
console.log(`✅ Đã tự động chấp nhận kết bạn với ${user.displayName}`);
// Gửi tin nhắn welcome
await this.api.sendMessage(
`🎉 Xin chào ${user.displayName}! Rất vui được kết bạn với bạn.`,
fromId,
ThreadType.User
);
} else {
console.log(`⏳ Lời mời từ ${user.displayName} cần xem xét thủ công`);
}
} catch (error) {
console.error('❌ Lỗi xử lý lời mời kết bạn:', error.message);
}
}
}
// Sử dụng
const friendManager = new FriendRequestManager(api);
// Rule: Accept nếu có từ khóa trong lời nhắn
friendManager.addRule((fromId, data) => {
const message = data.message?.toLowerCase() || '';
return message.includes('developer') || message.includes('programmer');
});
// Rule: Accept nếu tên có từ khóa
friendManager.addRule(async (fromId, data) => {
try {
const userInfo = await api.getUserInfo([fromId]);
const displayName = userInfo.data[0].displayName.toLowerCase();
return displayName.includes('dev') || displayName.includes('tech');
} catch {
return false;
}
});
```
## Sự Kiện Reaction và Typing
### 1. Reaction Events
```typescript
api.listener.on('reaction', (event) => {
const { messageId, threadId, reactorId, emoji, action } = event;
if (action === 'add') {
console.log(`❤️ ${reactorId} đã react ${emoji} vào tin nhắn ${messageId}`);
} else {
console.log(`💔 ${reactorId} đã bỏ react ${emoji} khỏi tin nhắn ${messageId}`);
}
});
```
### 2. Typing Events
```typescript
api.listener.on('typing', (event) => {
const { threadId, userId, isTyping } = event;
if (isTyping) {
console.log(`⌨️ ${userId} đang typing trong ${threadId}`);
} else {
console.log(`⏸️ ${userId} đã dừng typing trong ${threadId}`);
}
});
```
### 3. Seen/Delivered Events
```typescript
api.listener.on('seen', (event) => {
const { messageId, threadId, userId, timestamp } = event;
console.log(`👀 ${userId} đã seen tin nhắn ${messageId} lúc ${new Date(timestamp).toLocaleString()}`);
});
api.listener.on('delivered', (event) => {
const { messageId, threadId, userId, timestamp } = event;
console.log(`📩 Tin nhắn ${messageId} đã delivered đến ${userId}`);
});
```
## Sự Kiện Undo
```typescript
api.listener.on('undo', (event) => {
const { messageId, threadId, userId, undoType } = event;
switch (undoType) {
case 'message':
console.log(`🔄 ${userId} đã thu hồi tin nhắn ${messageId}`);
break;
case 'reaction':
console.log(`🔄 ${userId} đã thu hồi reaction`);
break;
default:
console.log(`🔄 ${userId} đã thu hồi: ${undoType}`);
}
});
```
## Utility Classes
### 1. Event Logger
```typescript
import fs from 'fs/promises';
class EventLogger {
private logFile: string;
private buffer: any[] = [];
private flushInterval: NodeJS.Timeout;
constructor(api: any, logFile: string = './events.log') {
this.logFile = logFile;
// Log tất cả events
const events = ['message', 'group_event', 'friend_event', 'reaction', 'typing', 'seen', 'delivered', 'undo'];
events.forEach(eventName => {
api.listener.on(eventName, (event: any) => {
this.logEvent(eventName, event);
});
});
// Flush buffer mỗi 30 giây
this.flushInterval = setInterval(() => {
this.flushBuffer();
}, 30000);
}
private logEvent(type: string, event: any) {
const logEntry = {
timestamp: new Date().toISOString(),
type,
event: JSON.stringify(event)
};
this.buffer.push(logEntry);
console.log(`📝 Logged ${type} event`);
// Flush nếu buffer đầy
if (this.buffer.length >= 100) {
this.flushBuffer();
}
}
private async flushBuffer() {
if (this.buffer.length === 0) return;
try {
const logs = this.buffer.map(entry =>
`[${entry.timestamp}] ${entry.type}: ${entry.event}`
).join('\n') + '\n';
await fs.appendFile(this.logFile, logs);
console.log(`💾 Flushed ${this.buffer.length} events to log`);
this.buffer = [];
} catch (error) {
console.error('❌ Lỗi ghi log:', error.message);
}
}
async close() {
if (this.flushInterval) {
clearInterval(this.flushInterval);
}
await this.flushBuffer();
}
}
// Sử dụng
const logger = new EventLogger(api);
// Cleanup khi tắt app
process.on('SIGINT', async () => {
await logger.close();
await api.listener.stop();
process.exit(0);
});
```
### 2. Event Router
```typescript
class EventRouter {
private routes = new Map<string, Array<(event: any) => void>>();
constructor(api: any) {
// Route tất cả events qua router
const events = ['message', 'group_event', 'friend_event', 'reaction', 'typing', 'seen', 'delivered', 'undo'];
events.forEach(eventName => {
api.listener.on(eventName, (event: any) => {
this.routeEvent(eventName, event);
});
});
}
on(pattern: string, handler: (event: any) => void) {
if (!this.routes.has(pattern)) {
this.routes.set(pattern, []);
}
this.routes.get(pattern)!.push(handler);
}
private routeEvent(eventName: string, event: any) {
// Tìm các route phù hợp
for (const [pattern, handlers] of this.routes.entries()) {
if (this.matchPattern(pattern, eventName, event)) {
handlers.forEach(handler => {
try {
handler(event);
} catch (error) {
console.error(`❌ Lỗi handler cho ${pattern}:`, error.message);
}
});
}
}
}
private matchPattern(pattern: string, eventName: string, event: any): boolean {
// Simple pattern matching
if (pattern === eventName) return true;
if (pattern === '*') return true;
// Pattern như "message:webchat"
if (pattern.includes(':')) {
const [eventPattern, typePattern] = pattern.split(':');
return eventPattern === eventName &&
event.data?.msgType === typePattern;
}
return false;
}
}
// Sử dụng
const router = new EventRouter(api);
// Route tin nhắn văn bản
router.on('message:webchat', (event) => {
console.log(`📝 Text message: ${event.data.content}`);
});
// Route tin nhắn ảnh
router.on('message:chat.photo', (event) => {
console.log(`📷 Image: ${event.data.href}`);
});
// Route tất cả sự kiện nhóm
router.on('group_event', (event) => {
console.log(`👥 Group event: ${event.type}`);
});
// Route tất cả sự kiện
router.on('*', (event) => {
console.log(`🎯 Any event received`);
});
```
## Reconnection và Error Handling
### 1. Auto Reconnect
```typescript
class ReliableListener {
private api: any;
private reconnectAttempts = 0;
private maxReconnectAttempts = 10;
private reconnectDelay = 5000;
private isReconnecting = false;
constructor(api: any) {
this.api = api;
this.setupListener();
}
private setupListener() {
this.api.listener.on('disconnect', (reason: CloseReason) => {
console.log(`❌ Listener disconnected: ${reason}`);
if (reason === CloseReason.Error && !this.isReconnecting) {
this.reconnect();
}
});
this.api.listener.on('connect', () => {
console.log('✅ Listener connected');
this.reconnectAttempts = 0;
this.isReconnecting = false;
});
this.api.listener.on('error', (error: any) => {
console.error('🚨 Listener error:', error.message);
if (!this.isReconnecting) {
this.reconnect();
}
});
}
private async reconnect() {
if (this.isReconnecting) return;
this.isReconnecting = true;
while (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`🔄 Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
try {
await this.api.listener.stop();
await new Promise(resolve => setTimeout(resolve, this.reconnectDelay));
await this.api.listener.start();
console.log('✅ Reconnected successfully');
return;
} catch (error) {
console.error(`❌ Reconnect attempt ${this.reconnectAttempts} failed:`, error.message);
// Exponential backoff
this.reconnectDelay *= 1.5;
}
}
console.error('💥 Max reconnect attempts reached. Listener stopped.');
this.isReconnecting = false;
}
async start() {
try {
await this.api.listener.start();
} catch (error) {
console.error('❌ Failed to start listener:', error.message);
this.reconnect();
}
}
}
// Sử dụng
const reliableListener = new ReliableListener(api);
await reliableListener.start();
```
### 2. Health Monitor
```typescript
class ListenerHealthMonitor {
private api: any;
private lastEventTime = Date.now();
private healthCheckInterval: NodeJS.Timeout;
private isHealthy = true;
constructor(api: any, checkIntervalMs: number = 60000) {
this.api = api;
// Track last event time
const events = ['message', 'group_event', 'friend_event', 'reaction', 'typing', 'seen', 'delivered'];
events.forEach(eventName => {
api.listener.on(eventName, () => {
this.lastEventTime = Date.now();
if (!this.isHealthy) {
console.log('💚 Listener health recovered');
this.isHealthy = true;
}
});
});
// Health check
this.healthCheckInterval = setInterval(() => {
this.checkHealth();
}, checkIntervalMs);
}
private checkHealth() {
const now = Date.now();
const timeSinceLastEvent = now - this.lastEventTime;
// Coi như unhealthy nếu không có event nào trong 5 phút
if (timeSinceLastEvent > 5 * 60 * 1000) {
if (this.isHealthy) {
console.log('💔 Listener appears unhealthy - no events for 5 minutes');
this.isHealthy = false;
// Send keep alive or restart listener
this.tryRecover();
}
}
}
private async tryRecover() {
try {
// Try keep alive first
await this.api.keepAlive();
console.log('📡 Keep alive sent');
// If still no events after 2 more minutes, restart listener
setTimeout(() => {
if (!this.isHealthy) {
console.log('🔄 Restarting listener due to health issues');
this.restartListener();
}
}, 2 * 60 * 1000);
} catch (error) {
console.error('❌ Keep alive failed:', error.message);
this.restartListener();
}
}
private async restartListener() {
try {
await this.api.listener.stop();
await new Promise(resolve => setTimeout(resolve, 5000));
await this.api.listener.start();
console.log('✅ Listener restarted');
} catch (error) {
console.error('❌ Failed to restart listener:', error.message);
}
}
stop() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
}
}
// Sử dụng
const healthMonitor = new ListenerHealthMonitor(api);
```
## Best Practices
### 1. Memory Management
```typescript
// Cleanup listeners khi không cần
const messageHandler = (message: any) => {
console.log('New message:', message.data.content);
};
api.listener.on('message', messageHandler);
// Cleanup
api.listener.off('message', messageHandler);
```
### 2. Error Boundaries
```typescript
function safeEventHandler(handler: Function) {
return (event: any) => {
try {
handler(event);
} catch (error) {
console.error('❌ Event handler error:', error.message);
console.error('Event data:', JSON.stringify(event, null, 2));
}
};
}
// Sử dụng
api.listener.on('message', safeEventHandler((message) => {
// Handler code có thể throw error
processMessage(message);
}));
```
### 3. Performance Monitoring
```typescript
class PerformanceMonitor {
private eventCounts = new Map<string, number>();
private lastReset = Date.now();
constructor(api: any) {
const events = ['message', 'group_event', 'friend_event', 'reaction'];
events.forEach(eventName => {
api.listener.on(eventName, () => {
const count = this.eventCounts.get(eventName) || 0;
this.eventCounts.set(eventName, count + 1);
});
});
// Report mỗi phút
setInterval(() => {
this.reportStats();
}, 60000);
}
private reportStats() {
const now = Date.now();
const elapsed = (now - this.lastReset) / 1000;
console.log(`📊 Events per second (last ${elapsed.toFixed(0)}s):`);
for (const [eventName, count] of this.eventCounts.entries()) {
const eps = (count / elapsed).toFixed(2);
console.log(` ${eventName}: ${eps} eps (${count} total)`);
}
// Reset counters
this.eventCounts.clear();
this.lastReset = now;
}
}
```
## Lưu Ý Quan Trọng
1. **Memory Leaks**: Cleanup listeners khi không cần thiết
2. **Rate Limiting**: Không xử lý quá nhiều events cùng lúc
3. **Error Handling**: Luôn wrap handlers trong try-catch
4. **Reconnection**: Implement auto-reconnect cho production
5. **Health Monitoring**: Monitor health của listener connection
6. **Performance**: Monitor performance và memory usage
7. **Logging**: Log events quan trọng để debug
8. **Cleanup**: Đảm bảo cleanup khi shutdown application