UNPKG

simple-breakpad-server

Version:
381 lines (354 loc) 12.3 kB
(function() { var Crashreport, Sequelize, Symfile, addr, bodyParser, busboy, config, crashreportToApiJson, crashreportToViewJson, db, exphbs, express, hbsPaginate, methodOverride, moment, paginate, path, run, streamToArray, symfileToViewJson, titleCase, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; config = require('./config'); moment = require('moment'); bodyParser = require('body-parser'); methodOverride = require('method-override'); path = require('path'); express = require('express'); exphbs = require('express-handlebars'); hbsPaginate = require('handlebars-paginate'); paginate = require('express-paginate'); Crashreport = require('./model/crashreport'); Symfile = require('./model/symfile'); db = require('./model/db'); titleCase = require('title-case'); busboy = require('connect-busboy'); streamToArray = require('stream-to-array'); Sequelize = require('sequelize'); addr = require('addr'); crashreportToApiJson = function(crashreport) { var json, k, v; json = crashreport.toJSON(); for (k in json) { v = json[k]; if (Buffer.isBuffer(json[k])) { json[k] = "/crashreports/" + json.id + "/files/" + k; } } return json; }; crashreportToViewJson = function(report) { var fields, hidden, json, k, name, ref, v, value; hidden = ['id', 'updated_at']; fields = { id: report.id, props: {} }; ref = Crashreport.attributes; for (name in ref) { value = ref[name]; if (value.type instanceof Sequelize.BLOB) { fields.props[name] = { path: "/crashreports/" + report.id + "/files/" + name }; } } json = report.toJSON(); for (k in json) { v = json[k]; if (indexOf.call(hidden, k) >= 0) { } else if (Buffer.isBuffer(json[k])) { } else if (k === 'created_at') { fields.props['created'] = moment(v).fromNow(); } else if (v instanceof Date) { fields.props[k] = moment(v).fromNow(); } else { fields.props[k] = v != null ? v : 'not present'; } } return fields; }; symfileToViewJson = function(symfile) { var fields, hidden, json, k, v; hidden = ['id', 'updated_at', 'contents']; fields = { id: symfile.id, contents: symfile.contents, props: {} }; json = symfile.toJSON(); for (k in json) { v = json[k]; if (indexOf.call(hidden, k) >= 0) { } else if (k === 'created_at') { fields.props['created'] = moment(v).fromNow(); } else if (v instanceof Date) { fields.props[k] = moment(v).fromNow(); } else { fields.props[k] = v != null ? v : 'not present'; } } return fields; }; db.sync().then(function() { return Symfile.findAll().then(function(symfiles) { return Promise.all(symfiles.map(function(s) { return Symfile.saveToDisk(s); })).then(run); }); })["catch"](function(err) { console.error(err.stack); return process.exit(1); }); run = function() { var app, baseUrl, breakpad, bsStatic, hbs, port; app = express(); breakpad = express(); hbs = exphbs.create({ defaultLayout: 'main', partialsDir: path.resolve(__dirname, '..', 'views'), layoutsDir: path.resolve(__dirname, '..', 'views', 'layouts'), helpers: { paginate: hbsPaginate, reportUrl: function(id) { return "/crashreports/" + id; }, symfileUrl: function(id) { return "/symfiles/" + id; }, titleCase: titleCase } }); breakpad.set('json spaces', 2); breakpad.set('views', path.resolve(__dirname, '..', 'views')); breakpad.engine('handlebars', hbs.engine); breakpad.set('view engine', 'handlebars'); breakpad.use(bodyParser.json()); breakpad.use(bodyParser.urlencoded({ extended: true })); breakpad.use(methodOverride()); baseUrl = config.get('baseUrl'); port = config.get('port'); app.use(baseUrl, breakpad); bsStatic = path.resolve(__dirname, '..', 'node_modules/bootstrap/dist'); breakpad.use('/assets', express["static"](bsStatic)); app.use(function(err, req, res, next) { if (err.message == null) { console.log('warning: error thrown without a message'); } console.trace(err); return res.status(500).send("Bad things happened:<br/> " + (err.message || err)); }); breakpad.use(busboy()); breakpad.post('/crashreports', function(req, res, next) { var props, streamOps; props = {}; streamOps = []; props.ip = addr(req, ['127.0.0.1', '::ffff:127.0.0.1']); req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { return streamOps.push(streamToArray(file).then(function(parts) { var buffers, i, j, part, ref; buffers = []; for (i = j = 0, ref = parts.length - 1; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) { part = parts[i]; buffers.push(part instanceof Buffer ? part : new Buffer(part)); } return Buffer.concat(buffers); }).then(function(buffer) { if (fieldname in Crashreport.attributes) { return props[fieldname] = buffer; } })); }); req.busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) { if (fieldname === 'prod') { return props['product'] = val; } else if (fieldname === 'ver') { return props['version'] = val; } else if (fieldname in Crashreport.attributes) { return props[fieldname] = val.toString(); } }); req.busboy.on('finish', function() { return Promise.all(streamOps).then(function() { return Crashreport.create(props).then(function(report) { return res.json(crashreportToApiJson(report)); }); })["catch"](function(err) { return next(err); }); }); return req.pipe(req.busboy); }); breakpad.get('/', function(req, res, next) { return res.redirect('/crashreports'); }); breakpad.use(paginate.middleware(10, 50)); breakpad.get('/crashreports', function(req, res, next) { var attributes, findAllQuery, limit, name, offset, page, ref, value; limit = req.query.limit; offset = req.offset; page = req.query.page; attributes = []; ref = Crashreport.attributes; for (name in ref) { value = ref[name]; if (!(value.type instanceof Sequelize.BLOB)) { attributes.push(name); } } findAllQuery = { order: 'created_at DESC', limit: limit, offset: offset, attributes: attributes }; return Crashreport.findAndCountAll(findAllQuery).then(function(q) { var count, fields, pageCount, records, viewReports; records = q.rows; count = q.count; pageCount = Math.ceil(count / limit); viewReports = records.map(crashreportToViewJson); fields = viewReports.length ? Object.keys(viewReports[0].props) : []; return res.render('crashreport-index', { title: 'Crash Reports', crashreportsActive: true, records: viewReports, fields: fields, pagination: { hide: pageCount <= 1, page: page, pageCount: pageCount } }); }); }); breakpad.use(paginate.middleware(10, 50)); breakpad.get('/symfiles', function(req, res, next) { var findAllQuery, limit, offset, page; limit = req.query.limit; offset = req.offset; page = req.query.page; findAllQuery = { order: 'created_at DESC', limit: limit, offset: offset }; return Symfile.findAndCountAll(findAllQuery).then(function(q) { var count, fields, pageCount, records, viewSymfiles; records = q.rows; count = q.count; pageCount = Math.ceil(count / limit); viewSymfiles = records.map(symfileToViewJson); fields = viewSymfiles.length ? Object.keys(viewSymfiles[0].props) : []; return res.render('symfile-index', { title: 'Symfiles', symfilesActive: true, records: viewSymfiles, fields: fields, pagination: { hide: pageCount <= 1, page: page, pageCount: pageCount } }); }); }); breakpad.get('/symfiles/:id', function(req, res, next) { return Symfile.findById(req.params.id).then(function(symfile) { if (symfile == null) { resturn(res.send(404, 'Symfile not found')); } if ('raw' in req.query) { res.set('content-type', 'text/plain'); res.send(symfile.contents.toString()); return res.end(); } else { return res.render('symfile-view', { title: 'Symfile', symfile: symfileToViewJson(symfile) }); } }); }); breakpad.get('/crashreports/:id', function(req, res, next) { return Crashreport.findById(req.params.id).then(function(report) { if (report == null) { return res.send(404, 'Crash report not found'); } return Crashreport.getStackTrace(report, function(err, stackwalk) { var fields; if (err != null) { return next(err); } fields = crashreportToViewJson(report).props; return res.render('crashreport-view', { title: 'Crash Report', stackwalk: stackwalk, product: fields.product, version: fields.version, fields: fields }); }); }); }); breakpad.get('/crashreports/:id/stackwalk', function(req, res, next) { return Crashreport.findById(req.params.id).then(function(report) { if (report == null) { return res.send(404, 'Crash report not found'); } return Crashreport.getStackTrace(report, function(err, stackwalk) { if (err != null) { return next(err); } res.set('Content-Type', 'text/plain'); return res.send(stackwalk.toString('utf8')); }); }); }); breakpad.get('/crashreports/:id/files/:filefield', function(req, res, next) { return Crashreport.findById(req.params.id).then(function(crashreport) { var contents, field, filename; if (crashreport == null) { return res.status(404).send('Crash report not found'); } field = req.params.filefield; contents = crashreport.get(field); if (!Buffer.isBuffer(contents)) { return res.status(404).send('Crash report field is not a file'); } filename = config.get("customFields:filesById:" + field + ":downloadAs") || field; filename = filename.replace('{{id}}', req.params.id); res.setHeader('content-disposition', "attachment; filename=\"" + filename + "\""); return res.send(contents); }); }); breakpad.get('/api/crashreports', function(req, res, next) { var name, ref, value, where; where = {}; ref = Crashreport.attributes; for (name in ref) { value = ref[name]; if (!(value.type instanceof Sequelize.BLOB)) { if (req.query[name]) { where[name] = req.query[name]; } } } return Crashreport.count({ where: where }).then(function(result) { return res.json({ count: result }); }).error(next); }); breakpad.use(busboy()); breakpad.post('/symfiles', function(req, res, next) { return Symfile.createFromRequest(req, function(err, symfile) { var symfileJson; if (err != null) { return next(err); } symfileJson = symfile.toJSON(); delete symfileJson.contents; return res.json(symfileJson); }); }); app.listen(port); return console.log("Listening on port " + port); }; }).call(this);