From 0cbcfd6ee318d34ca8db317a251d3ca6175f2638 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Wed, 2 Jul 2025 18:32:48 +0200 Subject: [PATCH 1/2] add error handling and graceful shutdown --- apps/server/src/index.ts | 62 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index db60ac58..198e4120 100755 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,9 +1,9 @@ -import { parse } from 'node:url'; import { Connect, Identity, Inboxes, Messages, SpaceEvents, Utils } from '@graphprotocol/hypergraph'; import { bytesToHex, randomBytes } from '@noble/hashes/utils.js'; import cors from 'cors'; import { Effect, Exit, Schema } from 'effect'; -import express, { type Request, type Response } from 'express'; +import express, { type NextFunction, type Request, type Response } from 'express'; +import { parse } from 'node:url'; import WebSocket, { WebSocketServer } from 'ws'; import { addAppIdentityToSpaces } from './handlers/add-app-identity-to-spaces.js'; import { applySpaceEvent } from './handlers/applySpaceEvent.js'; @@ -68,6 +68,28 @@ app.use(express.json({ limit: '2mb' })); app.use(cors()); +// Request timeout middleware +app.use((req: Request, res: Response, next: NextFunction) => { + req.setTimeout(30000, () => { + res.status(408).json({ error: 'Request timeout' }); + }); + next(); +}); + +// Global error handling middleware +app.use((error: Error, req: Request, res: Response, next: NextFunction) => { + console.error('Unhandled error:', error); + res.status(500).json({ + error: 'Internal server error', + message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong', + }); +}); + +// 404 handler +app.use('*', (req: Request, res: Response) => { + res.status(404).json({ error: 'Route not found' }); +}); + app.get('/', (_req, res) => { res.send('Server is running (v0.0.10)'); }); @@ -557,6 +579,42 @@ const server = app.listen(PORT, () => { console.log(`Listening on port ${PORT}`); }); +// Global process error handlers +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); +}); + +process.on('uncaughtException', (error) => { + console.error('Uncaught Exception:', error); + // Graceful shutdown + server.close(() => { + console.log('Server closed due to uncaught exception'); + process.exit(1); + }); +}); + +// Graceful shutdown handlers +const gracefulShutdown = (signal: string) => { + console.log(`Received ${signal}. Starting graceful shutdown...`); + + server.close(() => { + console.log('HTTP server closed'); + webSocketServer.close(() => { + console.log('WebSocket server closed'); + process.exit(0); + }); + }); + + // Force close after 30 seconds + setTimeout(() => { + console.error('Could not close connections in time, forcefully shutting down'); + process.exit(1); + }, 30000); +}; + +process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); +process.on('SIGINT', () => gracefulShutdown('SIGINT')); + function broadcastSpaceEvents({ spaceId, event, From 4833f56f5de4a49ada5c75ee8d90020afd2f91c0 Mon Sep 17 00:00:00 2001 From: Nik Graf Date: Wed, 2 Jul 2025 18:53:06 +0200 Subject: [PATCH 2/2] follow AI recommendations --- apps/server/src/index.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 198e4120..0dfb23b0 100755 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,9 +1,9 @@ +import { parse } from 'node:url'; import { Connect, Identity, Inboxes, Messages, SpaceEvents, Utils } from '@graphprotocol/hypergraph'; import { bytesToHex, randomBytes } from '@noble/hashes/utils.js'; import cors from 'cors'; import { Effect, Exit, Schema } from 'effect'; import express, { type NextFunction, type Request, type Response } from 'express'; -import { parse } from 'node:url'; import WebSocket, { WebSocketServer } from 'ws'; import { addAppIdentityToSpaces } from './handlers/add-app-identity-to-spaces.js'; import { applySpaceEvent } from './handlers/applySpaceEvent.js'; @@ -70,26 +70,12 @@ app.use(cors()); // Request timeout middleware app.use((req: Request, res: Response, next: NextFunction) => { - req.setTimeout(30000, () => { + res.setTimeout(30000, () => { res.status(408).json({ error: 'Request timeout' }); }); next(); }); -// Global error handling middleware -app.use((error: Error, req: Request, res: Response, next: NextFunction) => { - console.error('Unhandled error:', error); - res.status(500).json({ - error: 'Internal server error', - message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong', - }); -}); - -// 404 handler -app.use('*', (req: Request, res: Response) => { - res.status(404).json({ error: 'Route not found' }); -}); - app.get('/', (_req, res) => { res.send('Server is running (v0.0.10)'); }); @@ -575,6 +561,20 @@ app.post('/accounts/:accountAddress/inboxes/:inboxId/messages', async (req, res) broadcastAccountInboxMessage({ accountAddress, inboxId, message: createdMessage }); }); +// Global error handling middleware +app.use((error: Error, req: Request, res: Response, next: NextFunction) => { + console.error('Unhandled error:', error); + res.status(500).json({ + error: 'Internal server error', + message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong', + }); +}); + +// 404 handler +app.use('*', (req: Request, res: Response) => { + res.status(404).json({ error: 'Route not found' }); +}); + const server = app.listen(PORT, () => { console.log(`Listening on port ${PORT}`); });