require('dotenv').config(); const { getRouter } = require('stremio-addon-sdk'); const addonInterface = require('./addon'); const express = require('express'); const path = require('path'); const http = require('http'); const winston = require('winston'); // Configure Winston logger const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.printf(({ level, message, timestamp }) => { return `${timestamp} ${level}: ${message}`; }) ), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] }); const app = express(); // Serve static files app.use(express.static(path.join(__dirname, 'public'))); // Basic error handling middleware app.use((err, req, res, next) => { logger.error('Unhandled error:', err); res.status(500).json({ error: 'Internal server error' }); }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); // Serve the installation page app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); let addonRouter = null; // Handle addon requests app.use('/:configuration?', (req, res, next) => { try { if (!addonRouter) { const configurationStr = req.params.configuration; if (configurationStr) { let configuration; try { const decodedStr = Buffer.from(configurationStr, 'base64').toString('utf-8'); configuration = JSON.parse(decodedStr); logger.debug('Decoded configuration:', { ...configuration, username: configuration.username ? '[REDACTED]' : undefined, password: configuration.password ? '[REDACTED]' : undefined }); } catch (e) { logger.error('Failed to decode configuration:', e); return res.status(400).json({ error: 'Invalid configuration format' }); } const { username, password } = configuration; if (!username || !password) { logger.warn('Missing required configuration parameters'); return res.status(400).json({ error: 'Missing username or password' }); } try { const addonWithConfig = addonInterface.setConfiguration({ username, password }); addonRouter = getRouter(addonWithConfig); logger.info('Addon router created with configuration'); } catch (e) { logger.error('Failed to create addon router:', e); return res.status(500).json({ error: 'Failed to initialize addon' }); } } else { // If no configuration, use default addon interface addonRouter = getRouter(addonInterface); logger.info('Addon router created with default configuration'); } } addonRouter(req, res, next); } catch (error) { logger.error('Error in addon middleware:', error); next(error); } }); // Handle 404 app.use((req, res) => { res.status(404).json({ error: 'Not found' }); }); function startServer(port) { return new Promise((resolve, reject) => { const server = http.createServer(app); // Handle server errors server.on('error', (error) => { if (error.code === 'EADDRINUSE') { logger.warn(`Port ${port} is in use, trying ${port + 1}`); resolve(startServer(port + 1)); } else { logger.error('Server error:', error); reject(error); } }); // Handle uncaught exceptions process.on('uncaughtException', (error) => { logger.error('Uncaught exception:', error); process.exit(1); }); // Handle unhandled promise rejections process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled Rejection at:', promise, 'reason:', reason); }); // Start the server server.listen(port, () => { logger.info(`Addon running on http://127.0.0.1:${port}`); resolve(server); }); }); } // Start the server with automatic port selection startServer(3000).catch(err => { logger.error('Failed to start server:', err); process.exit(1); }); // Export for testing module.exports = { app, startServer };