mirror of
https://github.com/DeviceFarmer/stf.git
synced 2026-04-18 12:53:21 +02:00
Use WebSockets for the screen. The screen directive works, but needs a serious cleanup.
This commit is contained in:
@@ -17,8 +17,7 @@ module.exports = function(options) {
|
||||
log.info('Preparing device')
|
||||
return syrup.serial()
|
||||
.dependency(require('./plugins/solo'))
|
||||
.dependency(require('./plugins/screenshot'))
|
||||
.dependency(require('./plugins/http'))
|
||||
.dependency(require('./plugins/screen'))
|
||||
.dependency(require('./plugins/service'))
|
||||
.dependency(require('./plugins/display'))
|
||||
.dependency(require('./plugins/browser'))
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
141
lib/units/device/plugins/screen.js
Normal file
141
lib/units/device/plugins/screen.js
Normal 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)
|
||||
})
|
||||
92
lib/units/device/resources/minicap.js
Normal file
92
lib/units/device/resources/minicap.js
Normal file
@@ -0,0 +1,92 @@
|
||||
var util = require('util')
|
||||
|
||||
var Promise = require('bluebird')
|
||||
var syrup = require('stf-syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var pathutil = require('../../../util/pathutil')
|
||||
var devutil = require('../../../util/devutil')
|
||||
var streamutil = require('../../../util/streamutil')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/properties'))
|
||||
.dependency(require('../support/abi'))
|
||||
.define(function(options, adb, properties, abi) {
|
||||
var log = logger.createLogger('device:resources:minicap')
|
||||
|
||||
var resources = {
|
||||
bin: {
|
||||
src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) {
|
||||
return pathutil.vendor(util.format(
|
||||
'minicap/bin/%s/minicap%s'
|
||||
, supportedAbi
|
||||
, abi.pie ? '' : '-nopie'
|
||||
))
|
||||
}))
|
||||
, dest: '/data/local/tmp/minicap'
|
||||
, comm: 'minicap'
|
||||
, mode: 0755
|
||||
}
|
||||
, lib: {
|
||||
// @todo The lib ABI should match the bin ABI. Currently we don't
|
||||
// have an x86_64 version of the binary while the lib supports it.
|
||||
src: pathutil.requiredMatch(abi.all.map(function(supportedAbi) {
|
||||
return pathutil.vendor(util.format(
|
||||
'minicap/shared/android-%d/%s/minicap.so'
|
||||
, properties['ro.build.version.sdk']
|
||||
, supportedAbi
|
||||
))
|
||||
}))
|
||||
, dest: '/data/local/tmp/minicap.so'
|
||||
, mode: 0755
|
||||
}
|
||||
}
|
||||
|
||||
function removeResource(res) {
|
||||
return adb.shell(options.serial, ['rm', res.dest])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
return streamutil.readAll(out)
|
||||
})
|
||||
.return(res)
|
||||
}
|
||||
|
||||
function installResource(res) {
|
||||
return adb.push(options.serial, res.src, res.dest, res.mode)
|
||||
.timeout(10000)
|
||||
.then(function(transfer) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
transfer.on('error', reject)
|
||||
transfer.on('end', resolve)
|
||||
})
|
||||
})
|
||||
.return(res)
|
||||
}
|
||||
|
||||
function installAll() {
|
||||
return Promise.all([
|
||||
removeResource(resources.bin).then(installResource)
|
||||
, removeResource(resources.lib).then(installResource)
|
||||
])
|
||||
}
|
||||
|
||||
function stop() {
|
||||
return devutil.killProcsByComm(
|
||||
adb
|
||||
, options.serial
|
||||
, resources.bin.comm
|
||||
, resources.bin.dest
|
||||
)
|
||||
.timeout(15000)
|
||||
}
|
||||
|
||||
return stop()
|
||||
.then(installAll)
|
||||
.then(function() {
|
||||
return {
|
||||
bin: resources.bin.dest
|
||||
, lib: resources.lib.dest
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user