mirror of
https://github.com/openstf/stf
synced 2025-10-04 02:09:32 +02:00
Add socket.io with authentication and start rebuilding the device list.
This commit is contained in:
parent
176310cb6d
commit
9ed2524a7f
17 changed files with 285 additions and 14 deletions
|
@ -5,7 +5,8 @@
|
||||||
"angular": "~1.2.9",
|
"angular": "~1.2.9",
|
||||||
"angular-route": "~1.2.9",
|
"angular-route": "~1.2.9",
|
||||||
"requirejs": "~2.1.10",
|
"requirejs": "~2.1.10",
|
||||||
"se7en-bootstrap-3": "git@ghe.amb.ca.local:stf/se7en-bootstrap-3.git"
|
"se7en-bootstrap-3": "git@ghe.amb.ca.local:stf/se7en-bootstrap-3.git",
|
||||||
|
"socket.io-client": "~0.9.16"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
}
|
}
|
||||||
|
|
19
lib/cli.js
19
lib/cli.js
|
@ -335,6 +335,12 @@ program
|
||||||
.option('-a, --auth-url <url>'
|
.option('-a, --auth-url <url>'
|
||||||
, 'URL to auth client'
|
, 'URL to auth client'
|
||||||
, String)
|
, String)
|
||||||
|
.option('-u, --connect-sub <endpoint>'
|
||||||
|
, 'sub endpoint'
|
||||||
|
, cliutil.list)
|
||||||
|
.option('-c, --connect-push <endpoint>'
|
||||||
|
, 'push endpoint'
|
||||||
|
, cliutil.list)
|
||||||
.action(function(options) {
|
.action(function(options) {
|
||||||
if (!options.secret) {
|
if (!options.secret) {
|
||||||
this.missingArgument('--secret')
|
this.missingArgument('--secret')
|
||||||
|
@ -342,12 +348,23 @@ program
|
||||||
if (!options.authUrl) {
|
if (!options.authUrl) {
|
||||||
this.missingArgument('--auth-url')
|
this.missingArgument('--auth-url')
|
||||||
}
|
}
|
||||||
|
if (!options.connectSub) {
|
||||||
|
this.missingArgument('--connect-sub')
|
||||||
|
}
|
||||||
|
if (!options.connectPush) {
|
||||||
|
this.missingArgument('--connect-push')
|
||||||
|
}
|
||||||
|
|
||||||
require('./roles/app')({
|
require('./roles/app')({
|
||||||
port: options.port
|
port: options.port
|
||||||
, secret: options.secret
|
, secret: options.secret
|
||||||
, ssid: options.ssid
|
, ssid: options.ssid
|
||||||
, authUrl: options.authUrl
|
, authUrl: options.authUrl
|
||||||
|
, groupTimeout: 10000
|
||||||
|
, endpoints: {
|
||||||
|
sub: options.connectSub
|
||||||
|
, push: options.connectPush
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -493,6 +510,8 @@ program
|
||||||
, '--port', options.appPort
|
, '--port', options.appPort
|
||||||
, '--secret', options.authSecret
|
, '--secret', options.authSecret
|
||||||
, '--auth-url', util.format('http://localhost:%d/', options.authPort)
|
, '--auth-url', util.format('http://localhost:%d/', options.authPort)
|
||||||
|
, '--connect-sub', options.bindAppPub
|
||||||
|
, '--connect-push', options.bindAppPull
|
||||||
])
|
])
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
log.error('app died', err.stack)
|
log.error('app died', err.stack)
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
var r = require('rethinkdb')
|
var r = require('rethinkdb')
|
||||||
|
|
||||||
var db = require('./')
|
var db = require('./')
|
||||||
|
var wire = require('../wire')
|
||||||
|
var wireutil = require('../util/wireutil')(wire)
|
||||||
|
|
||||||
module.exports.saveUserAfterLogin = function(user) {
|
module.exports.saveUserAfterLogin = function(user) {
|
||||||
return db.run(r.table('users').insert({
|
return db.run(r.table('users').get(user.email).update({
|
||||||
email: user.email
|
name: user.name
|
||||||
, name: user.name
|
, lastLoggedInAt: r.now()
|
||||||
, lastLogin: r.now()
|
|
||||||
}
|
|
||||||
, {
|
|
||||||
upsert: true
|
|
||||||
}))
|
}))
|
||||||
|
.then(function(stats) {
|
||||||
|
if (stats.skipped) {
|
||||||
|
return db.run(r.table('users').insert({
|
||||||
|
email: user.email
|
||||||
|
, name: user.name
|
||||||
|
, group: wireutil.makePrivateChannel()
|
||||||
|
, lastLoggedInAt: r.now()
|
||||||
|
, createdAt: r.now()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.loadUser = function(email) {
|
||||||
|
return db.run(r.table('users').get(email))
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.saveDeviceLog = function(serial, entry) {
|
module.exports.saveDeviceLog = function(serial, entry) {
|
||||||
|
|
194
lib/roles/app.js
194
lib/roles/app.js
|
@ -1,26 +1,40 @@
|
||||||
var url = require('url')
|
var url = require('url')
|
||||||
|
var http = require('http')
|
||||||
|
var events = require('events')
|
||||||
|
|
||||||
var express = require('express')
|
var express = require('express')
|
||||||
var validator = require('express-validator')
|
var validator = require('express-validator')
|
||||||
|
var socketio = require('socket.io')
|
||||||
|
var zmq = require('zmq')
|
||||||
|
var Promise = require('bluebird')
|
||||||
|
|
||||||
var logger = require('../util/logger')
|
var logger = require('../util/logger')
|
||||||
var pathutil = require('../util/pathutil')
|
var pathutil = require('../util/pathutil')
|
||||||
|
var wire = require('../wire')
|
||||||
|
var wireutil = require('../util/wireutil')(wire)
|
||||||
|
var dbapi = require('../db/api')
|
||||||
|
|
||||||
var auth = require('../middleware/auth')
|
var auth = require('../middleware/auth')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('app')
|
var log = logger.createLogger('app')
|
||||||
, app = express()
|
, app = express()
|
||||||
|
, server = http.createServer(app)
|
||||||
|
, io = socketio.listen(server)
|
||||||
|
, router = new events.EventEmitter()
|
||||||
|
|
||||||
app.set('view engine', 'jade')
|
app.set('view engine', 'jade')
|
||||||
app.set('views', pathutil.resource('app/views'))
|
app.set('views', pathutil.resource('app/views'))
|
||||||
app.set('strict routing', true)
|
app.set('strict routing', true)
|
||||||
app.set('case sensitive routing', true)
|
app.set('case sensitive routing', true)
|
||||||
|
app.set('trust proxy', true)
|
||||||
|
|
||||||
app.use(express.cookieParser())
|
io.set('log level', 1)
|
||||||
|
io.set('browser client', false)
|
||||||
|
|
||||||
|
app.use(express.cookieParser(options.secret))
|
||||||
app.use(express.cookieSession({
|
app.use(express.cookieSession({
|
||||||
secret: options.secret
|
key: options.ssid
|
||||||
, key: options.ssid
|
|
||||||
}))
|
}))
|
||||||
app.use(auth({
|
app.use(auth({
|
||||||
secret: options.secret
|
secret: options.secret
|
||||||
|
@ -33,10 +47,182 @@ module.exports = function(options) {
|
||||||
app.use('/static/lib', express.static(pathutil.resource('lib')))
|
app.use('/static/lib', express.static(pathutil.resource('lib')))
|
||||||
app.use('/static', express.static(pathutil.resource('app')))
|
app.use('/static', express.static(pathutil.resource('app')))
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
router.emit(
|
||||||
|
channel.toString()
|
||||||
|
, channel
|
||||||
|
, wire.Envelope.decode(data)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/partials/:name', function(req, res) {
|
||||||
|
var whitelist = {
|
||||||
|
'deviceList': true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (whitelist[req.params.name]) {
|
||||||
|
res.render('partials/' + req.params.name)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
res.send(404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.get('/', function(req, res) {
|
app.get('/', function(req, res) {
|
||||||
res.render('index')
|
res.render('index')
|
||||||
})
|
})
|
||||||
|
|
||||||
app.listen(options.port)
|
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) {
|
||||||
|
handshake.user = user
|
||||||
|
accept(null, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
accept(null, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
accept(null, false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})())
|
||||||
|
|
||||||
|
io.on('connection', function(socket) {
|
||||||
|
var channels = []
|
||||||
|
, group = socket.handshake.user.group
|
||||||
|
|
||||||
|
function messageListener(channel, wrapper) {
|
||||||
|
switch (wrapper.type) {
|
||||||
|
case wire.MessageType.JOIN_GROUP:
|
||||||
|
var message = wire.JoinGroupMessage.decode(wrapper.message)
|
||||||
|
socket.emit('join', message)
|
||||||
|
break
|
||||||
|
case wire.MessageType.LEAVE_GROUP:
|
||||||
|
var message = wire.LeaveGroupMessage.decode(wrapper.message)
|
||||||
|
socket.emit('leave', message)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
channels.push(wireutil.global)
|
||||||
|
router.on(wireutil.global, messageListener)
|
||||||
|
|
||||||
|
// User's private group
|
||||||
|
channels.push(group)
|
||||||
|
sub.subscribe(group)
|
||||||
|
router.on(group, messageListener)
|
||||||
|
|
||||||
|
// Clean up all listeners and subscriptions
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
channels.forEach(function(channel) {
|
||||||
|
router.removeListener(channel, messageListener)
|
||||||
|
sub.unsubscribe(channel)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('invite', function(data) {
|
||||||
|
push.send([wireutil.global, wireutil.makeGroupMessage(
|
||||||
|
group
|
||||||
|
, options.groupTimeout
|
||||||
|
, data
|
||||||
|
)])
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('kick', function(data) {
|
||||||
|
push.send([group, wireutil.makeUngroupMessage(
|
||||||
|
group
|
||||||
|
, data
|
||||||
|
)])
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('flick', function(data) {})
|
||||||
|
socket.on('back', function(data) {})
|
||||||
|
socket.on('forward', function(data) {})
|
||||||
|
socket.on('refresh', function(data) {})
|
||||||
|
socket.on('monkey.touchDown', function(data) {})
|
||||||
|
socket.on('monkey.touchMove', function(data) {})
|
||||||
|
socket.on('monkey.touchUp', function(data) {})
|
||||||
|
socket.on('monkey.keyDown', function(data) {})
|
||||||
|
socket.on('monkey.keyUp', function(data) {})
|
||||||
|
socket.on('monkey.press', function(data) {})
|
||||||
|
socket.on('monkey.type', function(data) {})
|
||||||
|
socket.on('monkey.back', function(data) {})
|
||||||
|
socket.on('monkey.home', function(data) {})
|
||||||
|
socket.on('monkey.menu', 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)
|
log.info('Listening on port %d', options.port)
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,6 +346,17 @@ module.exports = function(options) {
|
||||||
wireutil.makeJoinGroupMessage(options.serial)])
|
wireutil.makeJoinGroupMessage(options.serial)])
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case wire.MessageType.UNGROUP:
|
||||||
|
var message = wire.UngroupMessage.decode(wrapper.message)
|
||||||
|
, groupChannel = message.channel
|
||||||
|
if (devutil.matchesRequirements(identity, message.requirements)) {
|
||||||
|
channels.unregister(groupChannel)
|
||||||
|
log.info('Unsubscribing from group channel "%s"', groupChannel)
|
||||||
|
sub.unsubscribe(groupChannel)
|
||||||
|
push.send([groupChannel,
|
||||||
|
wireutil.makeLeaveGroupMessage(options.serial)])
|
||||||
|
}
|
||||||
|
break
|
||||||
case wire.MessageType.SHELL_COMMAND:
|
case wire.MessageType.SHELL_COMMAND:
|
||||||
var message = wire.ShellCommandMessage.decode(wrapper.message)
|
var message = wire.ShellCommandMessage.decode(wrapper.message)
|
||||||
log.info('Running shell command "%s"', message.command.join(' '))
|
log.info('Running shell command "%s"', message.command.join(' '))
|
||||||
|
|
|
@ -47,6 +47,10 @@ module.exports = function(wire) {
|
||||||
|
|
||||||
return wireutil.envelope(wire.MessageType.GROUP, message)
|
return wireutil.envelope(wire.MessageType.GROUP, message)
|
||||||
}
|
}
|
||||||
|
, makeUngroupMessage: function(requirements) {
|
||||||
|
var message = new wire.UngroupMessage(requirements)
|
||||||
|
return wireutil.envelope(wire.MessageType.UNGROUP, message)
|
||||||
|
}
|
||||||
, makeJoinGroupMessage: function(serial) {
|
, makeJoinGroupMessage: function(serial) {
|
||||||
var message = new wire.JoinGroupMessage(serial)
|
var message = new wire.JoinGroupMessage(serial)
|
||||||
return wireutil.envelope(wire.MessageType.JOIN_GROUP, message)
|
return wireutil.envelope(wire.MessageType.JOIN_GROUP, message)
|
||||||
|
|
|
@ -6,6 +6,7 @@ enum MessageType {
|
||||||
DEVICE_TYPE = 3;
|
DEVICE_TYPE = 3;
|
||||||
DEVICE_PROPERTIES = 4;
|
DEVICE_PROPERTIES = 4;
|
||||||
GROUP = 5;
|
GROUP = 5;
|
||||||
|
UNGROUP = 15;
|
||||||
JOIN_GROUP = 6;
|
JOIN_GROUP = 6;
|
||||||
LEAVE_GROUP = 7;
|
LEAVE_GROUP = 7;
|
||||||
PROBE = 8;
|
PROBE = 8;
|
||||||
|
@ -150,6 +151,11 @@ message GroupMessage {
|
||||||
repeated DeviceRequirement requirements = 3;
|
repeated DeviceRequirement requirements = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UngroupMessage {
|
||||||
|
required string channel = 1;
|
||||||
|
repeated DeviceRequirement requirements = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message JoinGroupMessage {
|
message JoinGroupMessage {
|
||||||
required string serial = 1;
|
required string serial = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
define([
|
define([
|
||||||
'angular'
|
'angular'
|
||||||
, './controllers/index'
|
, './controllers/index'
|
||||||
|
, './services/index'
|
||||||
]
|
]
|
||||||
, function(ng) {
|
, function(ng) {
|
||||||
return ng.module('app', [
|
return ng.module('app', [
|
||||||
'ngRoute'
|
'ngRoute'
|
||||||
, 'app.controllers'
|
, 'app.controllers'
|
||||||
|
, 'app.services'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
5
res/app/scripts/controllers/DeviceListCtrl.js
Normal file
5
res/app/scripts/controllers/DeviceListCtrl.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
define(['./module'], function(mod) {
|
||||||
|
mod.controller('DeviceListCtrl', ['$scope', '$http', 'devices', function($scope, $http) {
|
||||||
|
|
||||||
|
}])
|
||||||
|
})
|
|
@ -1,4 +1,5 @@
|
||||||
define([
|
define([
|
||||||
|
'./DeviceListCtrl'
|
||||||
]
|
]
|
||||||
, function() {
|
, function() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ require.config({
|
||||||
paths: {
|
paths: {
|
||||||
'angular': '../lib/angular/angular'
|
'angular': '../lib/angular/angular'
|
||||||
, 'angular-route': '../lib/angular-route/angular-route'
|
, 'angular-route': '../lib/angular-route/angular-route'
|
||||||
|
, 'socket.io': '../lib/socket.io-client/dist/socket.io'
|
||||||
}
|
}
|
||||||
, shim: {
|
, shim: {
|
||||||
'angular': {
|
'angular': {
|
||||||
|
|
|
@ -6,8 +6,8 @@ define(['./app'], function(app) {
|
||||||
$locationProvider.html5Mode(true)
|
$locationProvider.html5Mode(true)
|
||||||
$routeProvider
|
$routeProvider
|
||||||
.when('/', {
|
.when('/', {
|
||||||
templateUrl: 'partials/signin'
|
templateUrl: 'partials/deviceList'
|
||||||
, controller: 'SignInCtrl'
|
, controller: 'DeviceListCtrl'
|
||||||
})
|
})
|
||||||
.otherwise({
|
.otherwise({
|
||||||
redirectTo: '/'
|
redirectTo: '/'
|
||||||
|
|
5
res/app/scripts/services/devices.js
Normal file
5
res/app/scripts/services/devices.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
define(['./module'], function(mod) {
|
||||||
|
mod.factory('devices', ['io', function(io) {
|
||||||
|
return {}
|
||||||
|
}])
|
||||||
|
})
|
7
res/app/scripts/services/index.js
Normal file
7
res/app/scripts/services/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
define([
|
||||||
|
'./io'
|
||||||
|
, './devices'
|
||||||
|
]
|
||||||
|
, function() {
|
||||||
|
}
|
||||||
|
)
|
5
res/app/scripts/services/io.js
Normal file
5
res/app/scripts/services/io.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
define(['./module', 'socket.io'], function(mod, io) {
|
||||||
|
mod.factory('io', [function() {
|
||||||
|
return io.connect()
|
||||||
|
}])
|
||||||
|
})
|
3
res/app/scripts/services/module.js
Normal file
3
res/app/scripts/services/module.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
define(['angular'], function(ng) {
|
||||||
|
return ng.module('app.services', [])
|
||||||
|
})
|
1
res/app/views/partials/deviceList.jade
Normal file
1
res/app/views/partials/deviceList.jade
Normal file
|
@ -0,0 +1 @@
|
||||||
|
h1 Devices
|
Loading…
Add table
Add a link
Reference in a new issue