mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 17:26:17 +00:00
feat: implement WebSocket heartbeat mechanism and apply API rate limiter
This commit is contained in:
@@ -21,12 +21,19 @@ export class DashboardServer {
|
|||||||
private server: ReturnType<typeof createServer>
|
private server: ReturnType<typeof createServer>
|
||||||
private wss: WebSocketServer
|
private wss: WebSocketServer
|
||||||
private clients: Set<WebSocket> = new Set()
|
private clients: Set<WebSocket> = new Set()
|
||||||
|
private heartbeatInterval?: NodeJS.Timer
|
||||||
private dashboardLimiter = rateLimit({
|
private dashboardLimiter = rateLimit({
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
max: 100, // limit each IP to 100 requests per windowMs for dashboard UI
|
max: 100, // limit each IP to 100 requests per windowMs for dashboard UI
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
})
|
})
|
||||||
|
private apiLimiter = rateLimit({
|
||||||
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||||
|
max: 300, // reasonable cap for API interactions
|
||||||
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false,
|
||||||
|
})
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express()
|
this.app = express()
|
||||||
this.server = createServer(this.app)
|
this.server = createServer(this.app)
|
||||||
@@ -67,7 +74,7 @@ export class DashboardServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupRoutes(): void {
|
private setupRoutes(): void {
|
||||||
this.app.use('/api', apiRouter)
|
this.app.use('/api', this.apiLimiter, apiRouter)
|
||||||
|
|
||||||
// Health check
|
// Health check
|
||||||
this.app.get('/health', (_req, res) => {
|
this.app.get('/health', (_req, res) => {
|
||||||
@@ -107,15 +114,22 @@ export class DashboardServer {
|
|||||||
|
|
||||||
private setupWebSocket(): void {
|
private setupWebSocket(): void {
|
||||||
this.wss.on('connection', (ws: WebSocket) => {
|
this.wss.on('connection', (ws: WebSocket) => {
|
||||||
this.clients.add(ws)
|
const tracked = ws as WebSocket & { isAlive?: boolean }
|
||||||
|
tracked.isAlive = true
|
||||||
|
|
||||||
|
this.clients.add(tracked)
|
||||||
dashLog('WebSocket client connected')
|
dashLog('WebSocket client connected')
|
||||||
|
|
||||||
ws.on('close', () => {
|
tracked.on('pong', () => {
|
||||||
this.clients.delete(ws)
|
tracked.isAlive = true
|
||||||
|
})
|
||||||
|
|
||||||
|
tracked.on('close', () => {
|
||||||
|
this.clients.delete(tracked)
|
||||||
dashLog('WebSocket client disconnected')
|
dashLog('WebSocket client disconnected')
|
||||||
})
|
})
|
||||||
|
|
||||||
ws.on('error', (error) => {
|
tracked.on('error', (error) => {
|
||||||
dashLog(`WebSocket error: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
dashLog(`WebSocket error: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,7 +138,7 @@ export class DashboardServer {
|
|||||||
const status = dashboardState.getStatus()
|
const status = dashboardState.getStatus()
|
||||||
const accounts = dashboardState.getAccounts()
|
const accounts = dashboardState.getAccounts()
|
||||||
|
|
||||||
ws.send(JSON.stringify({
|
tracked.send(JSON.stringify({
|
||||||
type: 'init',
|
type: 'init',
|
||||||
data: {
|
data: {
|
||||||
logs: recentLogs,
|
logs: recentLogs,
|
||||||
@@ -133,6 +147,26 @@ export class DashboardServer {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Heartbeat to drop dead connections and keep memory clean
|
||||||
|
this.heartbeatInterval = setInterval(() => {
|
||||||
|
for (const client of this.clients) {
|
||||||
|
const tracked = client as WebSocket & { isAlive?: boolean }
|
||||||
|
if (tracked.isAlive === false) {
|
||||||
|
tracked.terminate()
|
||||||
|
this.clients.delete(tracked)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tracked.isAlive = false
|
||||||
|
try {
|
||||||
|
tracked.ping()
|
||||||
|
} catch (error) {
|
||||||
|
dashLog(`WebSocket ping error: ${error instanceof Error ? error.message : String(error)}`, 'error')
|
||||||
|
tracked.terminate()
|
||||||
|
this.clients.delete(tracked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
private interceptBotLogs(): void {
|
private interceptBotLogs(): void {
|
||||||
@@ -192,6 +226,10 @@ export class DashboardServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
|
if (this.heartbeatInterval) {
|
||||||
|
clearInterval(this.heartbeatInterval)
|
||||||
|
this.heartbeatInterval = undefined
|
||||||
|
}
|
||||||
this.wss.close()
|
this.wss.close()
|
||||||
this.server.close()
|
this.server.close()
|
||||||
dashLog('Server stopped')
|
dashLog('Server stopped')
|
||||||
|
|||||||
Reference in New Issue
Block a user