Use WebSockets for the screen. The screen directive works, but needs a serious cleanup.

This commit is contained in:
Simo Kinnunen
2014-12-10 14:26:08 +09:00
parent 5114a50992
commit e4114d87af
30 changed files with 350 additions and 247 deletions

View File

@@ -4,19 +4,19 @@ var logger = require('../../../util/logger')
module.exports = syrup.serial()
.dependency(require('./service'))
.dependency(require('./http'))
.define(function(options, service, http) {
.dependency(require('./screen'))
.define(function(options, service, screen) {
var log = logger.createLogger('device:plugins:display')
function fetch() {
log.info('Fetching display info')
return service.getDisplay(0)
.catch(function() {
log.info('Falling back to HTTP API')
return http.getDisplay(0)
log.info('Falling back to screen API')
return screen.info(0)
})
.then(function(display) {
display.url = http.getDisplayUrl(display.id)
display.url = screen.publicUrl
return display
})
}

View File

@@ -1,152 +0,0 @@
var util = require('util')
var assert = require('assert')
var http = require('http')
var Promise = require('bluebird')
var syrup = require('stf-syrup')
var request = Promise.promisifyAll(require('request'))
var httpProxy = require('http-proxy')
var logger = require('../../../util/logger')
var devutil = require('../../../util/devutil')
var lifecycle = require('../../../util/lifecycle')
var streamutil = require('../../../util/streamutil')
module.exports = syrup.serial()
.dependency(require('../support/adb'))
.dependency(require('../resources/remote'))
.define(function(options, adb, remote) {
var log = logger.createLogger('device:plugins:http')
var service = {
port: 2870
, privateUrl: null
, publicUrl: null
}
function openService() {
log.info('Launching HTTP API')
return devutil.ensureUnusedPort(adb, options.serial, service.port)
.timeout(10000)
.then(function() {
return adb.shell(options.serial, [
'exec'
, remote.bin
, '--lib', remote.lib
, '--listen-http', service.port
])
.timeout(10000)
.then(function(out) {
lifecycle.share('Remote shell', out)
streamutil.talk(log, 'Remote shell says: "%s"', out)
})
.then(function() {
return devutil.waitForPort(adb, options.serial, service.port)
.timeout(20000)
})
.then(function(conn) {
var ours = options.ports.pop()
, everyones = options.ports.pop()
, url = util.format('http://127.0.0.1:%d', ours)
// Don't need the connection
conn.end()
log.info('Opening device HTTP API forwarder on "%s"', url)
service.privateUrl = url
service.publicUrl = util.format(
'http://%s:%s'
, options.publicIp
, everyones
)
return adb.forward(
options.serial
, util.format('tcp:%d', ours)
, util.format('tcp:%d', service.port)
)
.timeout(10000)
.then(function() {
log.info(
'Opening HTTP API proxy on "http://%s:%s"'
, options.publicIp
, everyones
)
var resolver = Promise.defer()
function resolve() {
lifecycle.share('Proxy server', proxyServer, {
end: false
})
resolver.resolve()
}
function reject(err) {
resolver.reject(err)
}
function ignore() {
// No-op
}
var proxy = httpProxy.createProxyServer({
target: url
, ws: false
, xfwd: false
})
proxy.on('error', ignore)
var proxyServer = http.createServer(function(req, res) {
proxy.web(req, res)
})
.listen(everyones)
proxyServer.on('listening', resolve)
proxyServer.on('error', reject)
return resolver.promise.finally(function() {
proxyServer.removeListener('listening', resolve)
proxyServer.removeListener('error', reject)
})
})
})
})
}
return openService()
.then(function() {
return {
getDisplay: function(id) {
return request.getAsync({
url: util.format(
'%s/api/v1/displays/%d'
, service.privateUrl
, id
)
, json: true
})
.timeout(10000)
.then(function(args) {
var display = args[1]
assert.ok('id' in display, 'Invalid response from HTTP API')
// Fix rotation's old name
if ('orientation' in display) {
display.rotation = display.orientation
delete display.orientation
}
return display
})
}
, getDisplayUrl: function(id) {
return util.format(
'%s/api/v1/displays/%d/screenshot.jpg'
, service.publicUrl
, id
)
}
}
})
})

View File

@@ -0,0 +1,141 @@
var util = require('util')
var path = require('path')
var http = require('http')
var Promise = require('bluebird')
var syrup = require('stf-syrup')
var httpProxy = require('http-proxy')
var logger = require('../../../util/logger')
var lifecycle = require('../../../util/lifecycle')
var streamutil = require('../../../util/streamutil')
module.exports = syrup.serial()
.dependency(require('../support/adb'))
.dependency(require('../resources/minicap'))
.define(function(options, adb, minicap) {
var log = logger.createLogger('device:plugins:screen')
var plugin = Object.create(null)
plugin.devicePort = 9002
plugin.privatePort = options.ports.pop()
plugin.privateUrl = util.format(
'ws://127.0.0.1:%s'
, plugin.privatePort
)
plugin.publicPort = options.ports.pop()
plugin.publicUrl = util.format(
'ws://%s:%s'
, options.publicIp
, plugin.publicPort
)
function run(cmd) {
return adb.shell(options.serial, util.format(
'LD_LIBRARY_PATH=%s exec %s %s'
, path.dirname(minicap.lib)
, minicap.bin
, cmd
))
}
function startService() {
log.info('Launching screen service')
return run(util.format('-p %d', plugin.devicePort))
.timeout(10000)
.then(function(out) {
lifecycle.share('Screen shell', out)
streamutil.talk(log, 'Screen shell says: "%s"', out)
})
}
function forwardService() {
log.info('Opening WebSocket service on port %d', plugin.privatePort)
return adb.forward(
options.serial
, util.format('tcp:%d', plugin.privatePort)
, util.format('tcp:%d', plugin.devicePort)
)
.timeout(10000)
}
function startProxy() {
log.info('Starting WebSocket proxy on %s', plugin.publicUrl)
var resolver = Promise.defer()
function resolve() {
lifecycle.share('Proxy server', proxyServer, {
end: false
})
resolver.resolve()
}
function reject(err) {
resolver.reject(err)
}
function ignore() {
// No-op
}
var proxy = httpProxy.createProxyServer({
target: plugin.privateUrl
, ws: true
, xfwd: false
})
proxy.on('error', ignore)
var proxyServer = http.createServer()
proxyServer.on('listening', resolve)
proxyServer.on('error', reject)
proxyServer.on('request', function(req, res) {
proxy.web(req, res)
})
proxyServer.on('upgrade', function(req, socket, head) {
proxy.ws(req, socket, head)
})
proxyServer.listen(plugin.publicPort)
return resolver.promise.finally(function() {
proxyServer.removeListener('listening', resolve)
proxyServer.removeListener('error', reject)
})
}
return startService()
.then(forwardService)
.then(startProxy)
.then(function() {
plugin.info = function(id) {
return run(util.format('-d %d -i', id))
.then(streamutil.readAll)
.then(function(out) {
var match
if ((match = /^ERROR: (.*)$/.exec(out))) {
throw new Error(match[1])
}
try {
var info = JSON.parse(out)
// Compat for now, remove eventually
info.rotation = 0
info.fps = 0
info.secure = false
return info
}
catch (e) {
throw new Error(out.toString())
}
})
}
})
.return(plugin)
})