Files
stf-DeviceFarmer/lib/roles/app.js
Gunther Brunner d381d90446 - Added benchmark comparing JPEG, PNG, WEBP, DDS DXT1, and CRN DXT1 formats.
- Pixi Canvas and WebGL render was added for quick benchmarking but it's going away soon, because WebGL is even slower than Canvas for big textures. The browsers nowadays use OpenGL in the backend for rendering Canvas.
- There is 3 different WebGL Renders but they need adjusting so they work with Non Power Of Two textures.
- For now the winner is JPEG+Canvas, however CRN+WebGL will be better almost for sure if server side supports it.
2014-02-28 16:41:49 +09:00

427 lines
12 KiB
JavaScript

var url = require('url')
var http = require('http')
var events = require('events')
var path = require('path')
var express = require('express')
var validator = require('express-validator')
var socketio = require('socket.io')
var zmq = require('zmq')
var Promise = require('bluebird')
var _ = require('lodash')
var logger = require('../util/logger')
var pathutil = require('../util/pathutil')
var wire = require('../wire')
var wireutil = require('../wire/util')
var wirerouter = require('../wire/router')
var dbapi = require('../db/api')
var auth = require('../middleware/auth')
var webpack = require('../middleware/webpack')
var cors = require('cors')
module.exports = function(options) {
var log = logger.createLogger('app')
, app = express()
, server = http.createServer(app)
, io = socketio.listen(server)
, channelRouter = new events.EventEmitter()
app.set('view engine', 'jade')
app.set('views', pathutil.resource('app/views'))
app.set('strict routing', true)
app.set('case sensitive routing', true)
app.set('trust proxy', true)
io.set('log level', 1)
io.set('browser client', false)
app.use(cors())
app.use('/static/lib', express.static(pathutil.resource('lib')))
app.use('/static', express.static(pathutil.resource('app')))
if (!options.disableWatch) {
app.use(webpack)
}
app.use(express.cookieParser(options.secret))
app.use(express.cookieSession({
key: options.ssid
}))
app.use(auth({
secret: options.secret
, authUrl: options.authUrl
}))
app.use(express.json())
app.use(express.urlencoded())
app.use(express.csrf())
app.use(validator())
// Output
var push = zmq.socket('push')
options.endpoints.push.forEach(function(endpoint) {
log.info('Sending output to %s', endpoint)
push.connect(endpoint)
})
// Input
var sub = zmq.socket('sub')
options.endpoints.sub.forEach(function(endpoint) {
log.info('Receiving input from %s', endpoint)
sub.connect(endpoint)
})
// Establish always-on channels
;[wireutil.global].forEach(function(channel) {
log.info('Subscribing to permanent channel "%s"', channel)
sub.subscribe(channel)
})
sub.on('message', function(channel, data) {
channelRouter.emit(channel.toString(), channel, data)
})
app.get('/partials/*', function(req, res) {
var whitelist = {
'devices/index': true
, 'devices/control': true
, 'devices/screen': true
}
if (whitelist.hasOwnProperty(req.params[0])) {
res.render(path.join('partials', req.params[0]))
}
else {
res.send(404)
}
})
app.get('/', function(req, res) {
res.render('index')
})
app.get('/api/v1/user', function(req, res) {
res.json({
success: true
, user: req.user
})
})
app.get('/api/v1/group', function(req, res) {
dbapi.loadGroupMembers(req.user.email)
.then(function(cursor) {
return Promise.promisify(cursor.toArray, cursor)()
.then(function(list) {
res.json({
success: true
, group: {
members: list
}
})
})
})
.catch(function(err) {
log.error('Failed to load group: ', err.stack)
res.json(500, {
success: false
})
})
})
app.get('/api/v1/devices', function(req, res) {
dbapi.loadDevices()
.then(function(cursor) {
return Promise.promisify(cursor.toArray, cursor)()
.then(function(list) {
res.json({
success: true
, devices: list
})
})
})
.catch(function(err) {
log.error('Failed to load device list: ', err.stack)
res.json(500, {
success: false
})
})
})
app.get('/api/v1/devices/:serial', function(req, res) {
dbapi.loadDevice(req.params.serial)
.then(function(device) {
if (device) {
res.json({
success: true
, device: device
})
}
else {
res.json(404, {
success: false
})
}
})
.catch(function(err) {
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
res.json(500, {
success: false
})
})
})
io.set('authorization', (function() {
var parse = Promise.promisify(express.cookieParser(options.secret))
return function(handshake, accept) {
parse(handshake, {})
.then(function() {
if (handshake.signedCookies[options.ssid]) {
handshake.session = handshake.signedCookies[options.ssid]
return dbapi.loadUser(handshake.session.jwt.email)
.then(function(user) {
if (user) {
handshake.user = user
accept(null, true)
}
else {
accept(null, false)
}
})
}
else {
accept(null, false)
}
})
.catch(function(err) {
accept(null, false)
})
}
})())
io.on('connection', function(socket) {
var channels = []
, user = socket.handshake.user
function joinChannel(channel) {
channels.push(channel)
channelRouter.on(channel, messageListener)
sub.subscribe(channel)
}
function leaveChannel(channel) {
_.pull(channels, channel)
channelRouter.removeListener(channel, messageListener)
sub.unsubscribe(channel)
}
function createTouchHandler(klass) {
return function(channel, data) {
push.send([
channel
, wireutil.envelope(new klass(
data.x
, data.y
))
])
}
}
function createKeyHandler(klass) {
return function(channel, data) {
push.send([
channel
, wireutil.envelope(new klass(
data.key
))
])
}
}
var messageListener = wirerouter()
.on(wire.DevicePresentMessage, function(channel, message) {
socket.emit('device.add', {
serial: message.serial
, present: true
})
})
.on(wire.DeviceAbsentMessage, function(channel, message) {
socket.emit('device.remove', {
serial: message.serial
, present: false
})
})
.on(wire.JoinGroupMessage, function(channel, message) {
socket.emit('device.change', message)
})
.on(wire.LeaveGroupMessage, function(channel, message) {
socket.emit('device.change', {
serial: message.serial
, owner: null
})
})
.on(wire.DeviceStatusMessage, function(channel, message) {
socket.emit('device.change', message)
})
.on(wire.DeviceIdentityMessage, function(channel, message) {
socket.emit('device.change', message)
})
.on(wire.TransactionProgressMessage, function(channel, message) {
socket.emit('tx.progress', channel.toString(), message)
})
.on(wire.TransactionDoneMessage, function(channel, message) {
socket.emit('tx.done', channel.toString(), message)
})
.handler()
// Global messages
//
// @todo Use socket.io to push global events to all clients instead
// of listening on every connection, otherwise we're very likely to
// hit EventEmitter's leak complaints (plus it's more work)
channelRouter.on(wireutil.global, messageListener)
// User's private group
joinChannel(user.group)
new Promise(function(resolve, reject) {
socket.on('disconnect', resolve)
// Touch events
.on('input.touchDown', createTouchHandler(wire.TouchDownMessage))
.on('input.touchMove', createTouchHandler(wire.TouchMoveMessage))
.on('input.touchUp', createTouchHandler(wire.TouchUpMessage))
.on('input.tap', createTouchHandler(wire.TapMessage))
// Key events
.on('input.keyDown', createKeyHandler(wire.KeyDownMessage))
.on('input.keyUp', createKeyHandler(wire.KeyUpMessage))
.on('input.keyPress', createKeyHandler(wire.KeyPressMessage))
.on('input.type', function(channel, data) {
push.send([
channel
, wireutil.envelope(new wire.TypeMessage(
data.text
))
])
})
// Transactions
.on('group.invite', function(channel, responseChannel, data) {
joinChannel(responseChannel)
push.send([
channel
, wireutil.transaction(
responseChannel
, new wire.GroupMessage(
new wire.OwnerMessage(
user.email
, user.name
, user.group
)
, options.groupTimeout
, wireutil.toDeviceRequirements(data)
)
)
])
})
.on('group.kick', function(channel, responseChannel, data) {
joinChannel(responseChannel)
push.send([
channel
, wireutil.transaction(
responseChannel
, new wire.UngroupMessage(
wireutil.toDeviceRequirements(data)
)
)
])
})
.on('tx.cleanup', function(channel) {
leaveChannel(channel)
})
.on('shell.command', function(channel, responseChannel, data) {
joinChannel(responseChannel)
push.send([
channel
, wireutil.transaction(
responseChannel
, new wire.ShellCommandMessage(data)
)
])
})
.on('shell.keepalive', function(channel, data) {
push.send([
channel
, wireutil.envelope(new wire.ShellKeepAliveMessage(data))
])
})
})
.finally(function() {
// Clean up all listeners and subscriptions
channelRouter.removeListener(wireutil.global, messageListener)
channels.forEach(function(channel) {
channelRouter.removeListener(channel, messageListener)
sub.unsubscribe(channel)
})
})
.catch(function(err) {
// Cannot guarantee integrity of client
log.error(
'Client had an error, disconnecting due to probable loss of integrity'
, err.stack
)
socket.disconnect(true)
})
/*
socket.on('flick', function(data) {})
socket.on('back', function(data) {})
socket.on('forward', function(data) {})
socket.on('refresh', function(data) {})
socket.on('internal.relaunch', function(data) {})
socket.on('browser.open', function(data) {})
socket.on('chrome.open', function(data) {})
socket.on('browser.clear', function(data) {})
socket.on('chrome.clear', function(data) {})
socket.on('internal.clear', function(data) {})
socket.on('selenium.setCookie', function(data) {})
socket.on('selenium.deleteCookie', function(data) {})
socket.on('selenium.deleteAllCookies', function(data) {})
socket.on('debug.benchmark.pull.start', function(data) {})
socket.on('debug.benchmark.pull.stop', function(data) {})
socket.on('logcat', function(data) {})
socket.on('debug.benchmark.pull.rate', function(data) {})
socket.on('cpu.monitor.load', function(data) {})
socket.on('safeExecute', function(data) {})
socket.on('eval', function(data) {})
socket.on('safeEval', function(data) {})
socket.on('executeAsync', function(data) {})
socket.on('safeExecuteAsync', function(data) {})
socket.on('execute', function(data) {})
socket.on('screen', function(data) {})
socket.on('screenshot', function(data) {})
socket.on('selenium.screenshot', function(data) {})
socket.on('url', function(data) {})
socket.on('selenium.allCookies', function(data) {})
socket.on('forward.unset', function(data) {})
socket.on('forward.list', function(data) {})
*/
//this._react 'forward.test', (data = {}) =>
// this._runTransaction 'forward.test',
// this._insertOptionalIp data, 'targetHost'
//this._react 'forward.set', (data = {}) =>
// this._runTransaction 'forward.set',
// this._insertOptionalIp data, 'targetHost'
//this._react 'selenium.weinre', =>
// this._runTransaction 'selenium.weinre',
// targetHost: conf.weinre.httpHost
// targetPort: conf.weinre.httpPort
})
server.listen(options.port)
log.info('Listening on port %d', options.port)
}