UNPKG

panlippt

Version:

运行在浏览器中的ppt 演示框架

376 lines (334 loc) 12.9 kB
var fs = require('fs'); var URL = require('url'); var path = require('path'); var exec = require('child_process').exec; var mimes = require('./mime.json'); var $ = require('./helper'); var connect = require('connect'); var libDir = __dirname; var rootDir = path.join(libDir, '../') + path.sep; var templateDir = path.join(rootDir, 'template') + path.sep; var chokidar = require('chokidar'); var md_parser = require('./md_parser'); //session相关 var Cookie = require('cookie'); var parseSignedCookie = connect.utils.parseSignedCookie; var MemoryStore = connect.middleware.session.MemoryStore; var Session = connect.middleware.session.Session; //建立一个memory store的实例 var storeMemory = new MemoryStore({ reapInterval: 60000 * 10 }); module.exports.start = function(port, pptDir, host, argvObj) { port = parseInt(port, 10) || 8080; pptDir = (pptDir || path.join(__dirname, '../ppts')) + path.sep; try { pptDir = fs.realpathSync(pptDir) + path.sep; } catch (e) {} var app = startApp(port, pptDir, host, argvObj); var io = require('socket.io').listen(app, { log: false, origins: '*:*' //解决同源策略 }); //设置session io.set('authorization', function(handshakeData, accept) { // 通过客户端的cookie字符串来获取其session数据 var ccc = ''; if (handshakeData.headers && handshakeData.headers.cookie) { ccc = handshakeData.headers.cookie; } handshakeData.cookie = Cookie.parse(ccc); var connect_sid = parseSignedCookie(handshakeData.cookie['connect.sid'], 'wyq'); if (connect_sid) { storeMemory.get(connect_sid, function(error, session) { if (error) { // if we cannot grab a session, turn down the connection accept(error.message, false); } else { // save the session data and accept the connection handshakeData.session = new Session(handshakeData, session); handshakeData.connect_sid = connect_sid; accept(null, true); } }); } else { accept('nosession'); } }); //watcher对应的socket var watchSockets = {}; if (argvObj.watch) { //添加watch功能 var exclude = ["\\/\\.", "node_modules"]; var watcher = chokidar.watch(pptDir, { ignored: new RegExp(exclude.join('|')), persistent: true }); watcher.on('change', function() { sendChangedNotice(); }); io.of('/watcher').on('connection', function(socket) { watchSockets[socket.id] = socket; }); } //用户双屏通信 var now = Date.now(); var sockets = {}; var userMap = {}; io.of('/ppt').on('connection', function(socket) { //解析cookie中的sid var cookie = socket.handshake.headers.cookie; cookie = Cookie.parse(cookie || ''); var uid = cookie['connect.sid']; socket.uid = uid; sockets[uid] = socket; //监听添加map socket.on('control.addClient', function(data) { var targetUid = socket.targetUid = data.targetUid; var otherSocket = sockets[targetUid]; userMap[targetUid] = uid; userMap[uid] = targetUid; otherSocket && otherSocket.emit('system', { action: 'join', joinUid: uid }); }); //建立消息中转站 socket.on('transfer.data', function(data) { var uid = socket.uid; var targetUid = userMap[uid]; var otherSocket = sockets[targetUid]; otherSocket && otherSocket.emit('transfer.data', data); }); //将当前分配的uid告知客户端 socket.emit('UUID', uid); socket.on('disconnect', function() { var uid = socket.uid; var targetUid = userMap[uid]; var otherSocket = sockets[targetUid]; otherSocket && otherSocket.emit('system', { action: 'leave', leaveUid: uid }); delete sockets[uid]; }); }); function sendChangedNotice() { if (!Object.keys(watchSockets)) { return console.error('[watch-connect]', 'No client connected to socket.io'); } Object.keys(watchSockets).forEach(function(s) { if (watchSockets[s] && watchSockets[s].emit) { watchSockets[s].emit('file changed'); } }); } }; function startApp(port, dir, host, argvObj) { host = host || '0.0.0.0'; var staticDir = path.normalize(path.join(__dirname, '../assets')) + path.sep; var nodeModules = path.normalize(path.join(__dirname, '../node_modules')) + path.sep; var pptDir = dir; var now = Date.now(); var app = connect( connect.cookieParser(), connect.session({ secret: 'wyq', store: storeMemory }), function(req, res) { var url = URL.parse(req.url).pathname; var dirname = path.dirname(url); var realPath, ext; if (url === '/') { //根目录显示list pptlist(res, pptDir, argvObj); return; } else if (dirname === '/md') { //md文件解析 url = URL.parse(req.url).pathname; var basename = path.basename(url); try { basename = decodeURIComponent(basename); } catch (e) { basename = path.basename(url); } var queryObj = URL.parse(req.url, true).query; realPath = pptDir + basename; console.log('markdown', realPath, basename); markdown(realPath, basename, res, argvObj, queryObj); return; } else if (/^\/js\/mathjax/.test(dirname)) { //处理mathjax realPath = url.replace('/js/', nodeModules); ext = path.extname(realPath); ext = ext ? ext.slice(1) : 'unknown'; } else { //优先选择pptDir的静态资源 url = url.indexOf('/') === 0 ? url.substring(1) : url; realPath = pptDir + url; if (!fs.existsSync(realPath)) { realPath = staticDir + url; } ext = path.extname(realPath); ext = ext ? ext.slice(1) : 'unknown'; } assets(realPath, ext, url, res, argvObj); }).listen(port, host); return app.on('listening', function() { var server = app.address(); var address = server.address === '0.0.0.0' ? '127.0.0.1' : server.address; var url = ' http://' + address + ':' + server.port console.log('ppt directory:'.cyan + ' ' + pptDir); console.log('assets directory:'.cyan + ' ' + staticDir); console.log('panlippt server started:'.cyan + url.yellow); if (process.platform === 'win32') { exec('start' + url); } else { exec('open' + url); } }).on('error', function(e) { if (e.code === 'EADDRINUSE' || e.code === 'EACCES') { console.log('ERROR: '.red + 'port ' + port + ' is in use!'); } else { console.log('ERROR: '.red + 'server start ' + host + ':' + port + ' info : ' + e.code); } }); } function assets(realPath, ext, url, res, argvObj) { //静态资源 if (fs.existsSync(realPath)) { fs.readFile(realPath, 'binary', function(err, file) { if (err) { res.writeHead(500, { 'Powered-By': 'panliPPT', 'Content-Type': 'text/plain' }); res.end(err); } else { res.writeHead(200, { 'Powered-By': 'panliPPT', 'Content-Type': mimes[ext] || 'text/plain' }); res.write(file, 'binary'); res.end(); } }); } else { page404(res, url); } } function page404(res, url) { res.writeHead(404, { 'Powered-By': 'panliPPT', 'Content-Type': mimes.txt }); res.write('This request URL ' + url + ' was not found on this server.'); res.end(); } function markdown(realPath, url, res, argvObj, queryObj) { if (fs.existsSync(realPath)) { var content = fs.readFileSync(realPath, 'utf-8').toString(); try { var html = md_parser(content, function() {}, argvObj, queryObj); //添加socket的html代码 var extraHtml = ''; if (argvObj.watch) { extraHtml += getWatcherHtml(); } if (extraHtml.trim() !== '') { html = html.split('<!--placeholder-->').join(extraHtml); } res.writeHead(200, { 'Powered-By': 'panliPPT', 'Content-Type': mimes.html }); res.write(html); res.end(); } catch (e) { res.writeHead(500, { 'Powered-By': 'panliPPT', 'Content-Type': 'text/plain' }); res.end(e.toString()); } } else { page404(res, url); } } /** * 读取ppt列表 * @param {[type]} res [description] * @param {[type]} dir [description] * @return {[type]} [description] */ function pptlist(res, dir, argvObj) { var staticDir = path.join(__dirname, '../assets') + '/'; res.writeHead(200, { 'Powered-By': 'panliPPT', 'Content-Type': mimes.html }); var curPath = dir; var count = 0, list = ''; //遍历html文件 $.recurse(dir, function(realpath, rootdir, subdir, filename) { count++; var content = fs.readFileSync(realpath, 'utf-8').toString(); var title = content.match(/<title>(.*?)<\/title>/) || []; if (title[1]) { title = title[1]; } else { title = filename; } var url = filename; list += '<li><a class="star" href="' + url + '" target="_blank">' + title + '</a></li>'; }, '', '', function(filename) { return /\.htm[l]?/.test(filename); }); //遍历markdown文件 $.recurse(dir, function(realpath, rootdir, subdir, filename) { count++; var content = fs.readFileSync(realpath, 'utf-8').toString(); //第一个是封面 var cover = content.split('[slide]').shift(); var title = md_parser.parseCover(cover).title; if (!title) { title = filename; } var url = '/md/' + filename; list += '<li><a class="star" href="' + url + '" target="_blank">' + title + '</a>'; list += (argvObj.controller === 'socket' ? '' : '<a href="' + url + '?_multiscreen=1" target="_blank" title="多窗口打开" class="mult-link">多窗口</a><a href="' + url + '?controller=socket" target="_blank" title="用socket远程控制" class="socket-link">远程控制</a>') + '</li>'; }, '', '', function(filename) { return /\.(md|markdown)$/.test(filename); }); var json = require(path.join(__dirname, '../package.json')); var data = { version: json.version, site: json.site, date: Date.now(), list: list, dir: curPath }; //渲染模板 var html = $.renderFile(templateDir + 'list.ejs', data); res.write(html); if (count == 0) { //路径不存在ppt res.write('This directory ' + curPath + ' was not found html on this server.'); console.log(('This directory ' + curPath + ' was not found html on this server.').red); } res.end(); } function getWatcherHtml() { var html = ['<script src="/socket.io/socket.io.js"></script>', '<script>', '(function(window, document, io){', ' var socket = io.connect(location.host+"/watcher");', ' socket.on("file changed", reload).on("reconnect", reload);', ' function reload() { location.reload(); }', '}(window, document, io));', '</script>' ].join(''); return html; }