const http = require('http'); const fs = require('fs'); const path = require('path'); const ejs = require('ejs'); const os = require('os'); const QRCode = require('qrcode'); const { handleApiRequest } = require('./routes/api'); const { validateSession, authorize, acl } = require('./acl'); const WebSocket = require('ws'); const cookie = require('cookie'); const Session = require('./models/session'); const { v4: uuidv4 } = require('uuid'); const nms = require('./media-server'); const httpProxy = require('http-proxy'); const port = process.env.PORT || 3000; // Proxy setup const proxy = httpProxy.createServer({ target: 'http://localhost:8000/', changeOrigin: true }); const connections = {}; // Store connections with confirmation codes // Function to get the network address const getNetworkAddress = () => { if(process.env['DEVELOPMENT']) { const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const iface of interfaces[name]) { if (iface.family === 'IPv4' && !iface.internal) { return iface.address; } } } return 'localhost'; } else { return 'the.mk'; } }; // Logging function const logRequest = (req, res, startTime) => { const duration = new Date() - startTime; const logMessage = `${req.method} ${req.url} ${res.statusCode} - ${duration}ms`; console.log(logMessage); }; const renderPage = (res, template, data) => { const filePath = path.join(__dirname, 'views', 'layouts', 'master.ejs'); ejs.renderFile(filePath, { ...data, template }, (err, str) => { if (err) { console.error(err); // Log the error for debugging if (!res.headersSent) { res.writeHead(500, { 'Content-Type': 'text/plain' }); } res.end('Internal Server Error'); } else { if (!res.headersSent) { res.writeHead(200, { 'Content-Type': 'text/html' }); } res.end(str); } }); }; const serveStaticFile = (req, res) => { const filePath = path.join(__dirname, 'public', req.url); fs.readFile(filePath, (err, data) => { if (err) { if (!res.headersSent) { res.writeHead(404, { 'Content-Type': 'text/plain' }); } res.end('Not Found'); } else { const ext = path.extname(filePath); const contentType = ext === '.css' ? 'text/css' : 'application/javascript'; if (!res.headersSent) { res.writeHead(200, { 'Content-Type': contentType }); } res.end(data); } }); }; const parseCookies = (request) => { const list = {}; const cookieHeader = request.headers.cookie; if (cookieHeader) { cookieHeader.split(';').forEach((cookie) => { let [name, ...rest] = cookie.split('='); name = name.trim(); if (!name) return; const value = rest.join('=').trim(); if (!value) return; list[name] = decodeURIComponent(value); }); } return list; }; const server = http.createServer(async (req, res) => { const startTime = new Date(); const cookies = parseCookies(req); const token = cookies.token; res.on('finish', () => logRequest(req, res, startTime)); const session = await validateSession(token); const user = session ? session.data.user : null; console.log('Session:', session); // Debug: Print session data console.log('User:', user); // Debug: Print user data if (req.url.startsWith('/media')) { // Modify req.url to remove /media before proxying req.url = req.url.replace(/^\/media/, ''); // Proxy media requests to Node-Media-Server proxy.web(req, res); } else if (req.method === 'GET') { if (req.url.startsWith('/css/') || req.url.startsWith('/js/') || req.url.startsWith('/videos/') || req.url.startsWith('/audio/')) { serveStaticFile(req, res); } else { const permissions = acl[req.url]; const template = permissions ? permissions.template : null; const authorized = permissions ? authorize(req.url, 'GET', user ? user.role : null) : false; console.log('Permissions:', permissions); // Debug: Print permissions console.log('Authorized:', authorized); // Debug: Print authorization status if (req.url === '/') { const networkAddress = getNetworkAddress(); const url = `https://${networkAddress}`; const confirmationCode = uuidv4().split('-')[0]; // Simple confirmation code connections[confirmationCode] = { desktop: null, mobile: null }; let qrCodeDataUrl = ''; try { qrCodeDataUrl = await QRCode.toDataURL(`${url}/mobile?code=${confirmationCode}`); } catch (error) { console.error('Error generating QR code:', error); } renderPage(res, 'index.ejs', { title: 'Welcome to The Learning App', session: session || {}, qrCodeDataUrl, url, confirmationCode }); } else if (req.url.startsWith('/mobile')) { const code = new URL(req.url, `http://${req.headers.host}`).searchParams.get('code'); if (code && connections[code]) { renderPage(res, 'mobile.ejs', { title: 'Mobile View', session: session || {}, confirmationCode: code }); } else { renderPage(res, 'not_found.ejs', { title: 'Not Found', session: session || {} }); } } else if (template && authorized) { let sessions = []; if (req.url === '/sessions') { sessions = await Session.findAll(); } renderPage(res, template, { title: req.url.slice(1).toUpperCase(), session: session || {}, sessions }); } else if (authorized === false) { renderPage(res, 'unauthorized.ejs', { title: 'Unauthorized', session: session || {} }); } else { renderPage(res, 'not_found.ejs', { title: 'Not Found', session: session || {} }); } } } else if (req.method === 'POST') { if (req.url.startsWith('/api/')) { handleApiRequest(req, res); } else { if (!res.headersSent) { res.writeHead(404, { 'Content-Type': 'text/plain' }); } res.end('Not Found'); } } }); const wss = new WebSocket.Server({ noServer: true }); server.on('upgrade', async (request, socket, head) => { const cookies = parseCookies(request); const token = cookies.token; const session = await validateSession(token); if (!session) { socket.destroy(); } else { wss.handleUpgrade(request, socket, head, ws => { wss.emit('connection', ws, request, session); }); } }); wss.on('connection', (ws, request, session) => { ws.on('message', message => { const msg = JSON.parse(message); if (msg.type === 'confirm') { const code = msg.code; if (connections[code]) { connections[code].mobile = ws; ws.send(JSON.stringify({ type: 'confirmation', token: code, content: getCurrentLesson() })); if (connections[code].desktop) { connections[code].desktop.send(JSON.stringify({ type: 'confirm', content: getCurrentLesson() })); connections[code].desktop.send(JSON.stringify({ type: 'welcome' })); connections[code].mobile.send(JSON.stringify({ type: 'welcome' })); } } } else if (msg.type === 'quizAnswer' || msg.type === 'choice') { const token = msg.token; if (connections[token]) { connections[token].desktop.send(JSON.stringify(msg)); // Automatically proceed to the next lesson const nextLessonContent = nextLesson(); connections[token].desktop.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent })); connections[token].mobile.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent })); } } else if (msg.type === 'videoEnded') { const token = msg.token; if (connections[token]) { connections[token].mobile.send(JSON.stringify({ type: 'showControls' })); } } else if (msg.type === 'nextLesson') { const token = msg.token; if (connections[token]) { const nextLessonContent = nextLesson(); connections[token].desktop.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent })); connections[token].mobile.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent })); } } }); // This assumes desktop connections come first for (const code in connections) { if (connections[code].desktop === null) { connections[code].desktop = ws; break; } } if (session && session.data.user && session.data.user.role === 'admin') { const confirmationCode = uuidv4().split('-')[0]; // Simple confirmation code connections[confirmationCode] = { desktop: ws, mobile: null }; ws.send(JSON.stringify({ type: 'confirmationCode', code: confirmationCode })); } }); const lessonData = [ { title: 'Lesson 1', text: 'This is the content for lesson 1.', video: 'lesson1.mp4', quiz: { question: 'What is 1 + 1?', answer: '2', choices: ['A', 'B', 'C', 'D'] } }, { title: 'Lesson 2', text: 'This is the content for lesson 2.', video: 'lesson2.mp4', quiz: { question: 'What is 2 + 2?', answer: '4', choices: ['A', 'B', 'C', 'D'] } }, { title: 'Lesson 3', text: 'This is the content for lesson 3.', video: 'lesson3.mp4', quiz: { question: 'What is 3 + 3?', answer: '6', choices: ['A', 'B', 'C', 'D'] } }, { title: 'Lesson 4', text: 'This is the content for lesson 4.', video: 'lesson4.mp4', quiz: { question: 'What is 4 + 4?', answer: '8', choices: ['A', 'B', 'C', 'D'] } }, { title: 'Lesson 5', text: 'This is the content for lesson 5.', video: 'lesson5.mp4', quiz: { question: 'What is 5 + 5?', answer: '10', choices: ['A', 'B', 'C', 'D'] } } ]; let currentLesson = 0; function getCurrentLesson() { return lessonData[currentLesson]; } function nextLesson() { currentLesson = (currentLesson + 1) % lessonData.length; return getCurrentLesson(); } server.listen(port, () => { console.log(`Server is running on port ${port}`); nms.run(); // Start the Node-Media-Server });