Files
comfyui-serverless/frontend/src/index.ts
Debian 8f050b41a0
All checks were successful
Build and Push Frontend Docker Image / build (push) Successful in 57s
Build and Push Docker Image / build (push) Successful in 30m18s
Fix stuck processing jobs and increase timeouts
Background Job Processor:
- Add src/services/jobProcessor.ts that polls RunPod every 30s for stuck jobs
- Automatically completes or fails jobs that were abandoned (user navigated away)
- Times out jobs after 25 minutes

Client-Side Resume:
- Add GET /api/generate/pending endpoint to fetch user's processing jobs
- Add checkPendingJobs() that runs on login/page load
- Show notification banner when user has jobs generating in background
- Add "View Progress" button to resume polling for a job

Timeout Increases (10min → 25min):
- src/utils/validators.ts: request validation max/default
- src/config.ts: RUNPOD_MAX_TIMEOUT_MS default
- public/js/app.js: client-side polling maxTime
- src/services/jobProcessor.ts: background processor timeout

CI/CD Optimization:
- Add paths-ignore to backend build.yaml to skip rebuilds on frontend-only changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:36:53 +00:00

143 lines
3.7 KiB
TypeScript

import express from 'express';
import session from 'express-session';
import helmet from 'helmet';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { config } from './config.js';
import { initDatabase, closeDatabase } from './db/index.js';
import { createInitialAdmin } from './services/initService.js';
import { SQLiteSessionStore } from './services/sessionService.js';
import { startJobProcessor, stopJobProcessor } from './services/jobProcessor.js';
import { apiRateLimiter } from './middleware/rateLimit.js';
import { errorHandler } from './middleware/errorHandler.js';
import { logger } from './utils/logger.js';
import authRoutes from './routes/auth.js';
import userRoutes from './routes/users.js';
import contentRoutes from './routes/content.js';
import generateRoutes from './routes/generate.js';
import healthRoutes from './routes/health.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
// Trust proxy (for Cloudflare)
if (config.trustProxy) {
app.set('trust proxy', 1);
}
// Security headers
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'blob:'],
mediaSrc: ["'self'", 'data:', 'blob:'],
connectSrc: ["'self'"],
},
},
crossOriginEmbedderPolicy: false,
}));
// Body parsing
app.use(express.json({ limit: '15mb' }));
app.use(express.urlencoded({ extended: true }));
// Session middleware
const sessionStore = new SQLiteSessionStore();
app.use(session({
secret: config.sessionSecret,
name: 'sid',
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
httpOnly: true,
secure: config.isProduction,
sameSite: 'strict',
maxAge: config.sessionMaxAge,
},
}));
// Rate limiting for API routes
app.use('/api/', apiRateLimiter);
// API routes
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
app.use('/api/content', contentRoutes);
app.use('/api/generate', generateRoutes);
// Health check routes (no rate limiting)
app.use('/health', healthRoutes);
// Static files
app.use(express.static(join(__dirname, '..', 'public')));
// SPA fallback - serve index.html for all non-API routes
app.get('*', (req, res, next) => {
if (req.path.startsWith('/api/') || req.path.startsWith('/health')) {
return next();
}
res.sendFile(join(__dirname, '..', 'public', 'index.html'));
});
// Error handler
app.use(errorHandler);
// Startup
async function start() {
try {
logger.info('Starting ComfyUI Frontend Service...');
// Initialize database
initDatabase();
logger.info({ dbPath: config.dbPath }, 'Database initialized');
// Create initial admin user if needed
await createInitialAdmin();
// Start background job processor
startJobProcessor();
// Start server
const server = app.listen(config.port, () => {
logger.info({ port: config.port, env: config.nodeEnv }, 'Server started');
});
// Graceful shutdown
const shutdown = async (signal: string) => {
logger.info({ signal }, 'Shutdown signal received');
server.close(() => {
logger.info('HTTP server closed');
stopJobProcessor();
sessionStore.close();
closeDatabase();
logger.info('Database closed');
process.exit(0);
});
// Force exit after 10 seconds
setTimeout(() => {
logger.error('Forced shutdown after timeout');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
} catch (error) {
logger.error({ error }, 'Failed to start server');
process.exit(1);
}
}
start();