blob: 4b4816565b48c50f5a4b095b32d98279c2b2b743 [file] [log] [blame]
var net = require('net')
var Promise = require('bluebird')
var syrup = require('stf-syrup')
var _ = require('lodash')
var wire = require('../../../../wire')
var logger = require('../../../../util/logger')
var lifecycle = require('../../../../util/lifecycle')
var streamutil = require('../../../../util/streamutil')
var wireutil = require('../../../../wire/util')
var ForwardManager = require('./util/manager')
module.exports = syrup.serial()
.dependency(require('../../support/adb'))
.dependency(require('../../support/router'))
.dependency(require('../../support/push'))
.dependency(require('../../resources/minirev'))
.dependency(require('../group'))
.define(function(options, adb, router, push, minirev, group) {
var log = logger.createLogger('device:plugins:forward')
var plugin = Object.create(null)
var manager = new ForwardManager()
function startService() {
log.info('Launching reverse port forwarding service')
return adb.shell(options.serial, [
'exec'
, minirev.bin
])
.timeout(10000)
.then(function(out) {
lifecycle.share('Forward shell', out)
streamutil.talk(log, 'Forward shell says: "%s"', out)
})
}
function connectService(times) {
function tryConnect(times, delay) {
return adb.openLocal(options.serial, 'localabstract:minirev')
.timeout(10000)
.catch(function(err) {
if (/closed/.test(err.message) && times > 1) {
return Promise.delay(delay)
.then(function() {
return tryConnect(times - 1, delay * 2)
})
}
return Promise.reject(err)
})
}
log.info('Connecting to reverse port forwarding service')
return tryConnect(times, 100)
}
function awaitServer() {
return connectService(5)
.then(function(conn) {
conn.end()
return true
})
}
plugin.createForward = function(id, forward) {
log.info(
'Creating reverse port forward "%s" from ":%d" to "%s:%d"'
, id
, forward.devicePort
, forward.targetHost
, forward.targetPort
)
return connectService(1)
.then(function(out) {
var header = new Buffer(4)
header.writeUInt16LE(0, 0)
header.writeUInt16LE(forward.devicePort, 2)
out.write(header)
return manager.add(id, out, forward)
})
}
plugin.removeForward = function(id) {
log.info('Removing reverse port forward "%s"', id)
manager.remove(id)
return Promise.resolve()
}
plugin.connect = function(options) {
var resolver = Promise.defer()
var conn = net.connect({
host: options.targetHost
, port: options.targetPort
})
function connectListener() {
resolver.resolve(conn)
}
function errorListener(err) {
resolver.reject(err)
}
conn.on('connect', connectListener)
conn.on('error', errorListener)
return resolver.promise.finally(function() {
conn.removeListener('connect', connectListener)
conn.removeListener('error', errorListener)
})
}
plugin.reset = function() {
manager.removeAll()
}
group.on('leave', plugin.reset)
var pushForwards = _.debounce(
function() {
push.send([
wireutil.global
, wireutil.envelope(new wire.ReverseForwardsEvent(
options.serial
, manager.listAll()
))
])
}
, 200
)
manager.on('add', pushForwards)
manager.on('remove', pushForwards)
return startService()
.then(awaitServer)
.then(function() {
router
.on(wire.ForwardTestMessage, function(channel, message) {
var reply = wireutil.reply(options.serial)
plugin.connect(message)
.then(function(conn) {
conn.end()
push.send([
channel
, reply.okay('success')
])
})
.catch(function() {
push.send([
channel
, reply.fail('fail_connect')
])
})
})
.on(wire.ForwardCreateMessage, function(channel, message) {
var reply = wireutil.reply(options.serial)
plugin.createForward(message.id, message)
.then(function() {
push.send([
channel
, reply.okay('success')
])
})
.catch(function(err) {
log.error('Reverse port forwarding failed', err.stack)
push.send([
channel
, reply.fail('fail_forward')
])
})
})
.on(wire.ForwardRemoveMessage, function(channel, message) {
var reply = wireutil.reply(options.serial)
plugin.removeForward(message.id)
.then(function() {
push.send([
channel
, reply.okay('success')
])
})
.catch(function(err) {
log.error('Reverse port unforwarding failed', err.stack)
push.send([
channel
, reply.fail('fail')
])
})
})
})
.return(plugin)
})