mirror of
https://github.com/openstf/stf
synced 2025-10-04 10:19:30 +02:00
Merge branch 'master' of https://github.com/openstf/stf into s3insteadoftmp
This commit is contained in:
commit
bbbd8f688c
180 changed files with 5434 additions and 1532 deletions
14
.eslintrc
Normal file
14
.eslintrc
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"comma-style": [2, "first"],
|
||||||
|
"no-extra-semi": 2,
|
||||||
|
"no-underscore-dangle": 0,
|
||||||
|
"quotes": [2, "single"],
|
||||||
|
"semi": [2, "never"],
|
||||||
|
"space-before-blocks": [2, "always"],
|
||||||
|
"strict": [0, "function"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/*.tgz
|
/*.tgz
|
||||||
/.bowerrc
|
/.bowerrc
|
||||||
|
/.dockerignore
|
||||||
/.editorconfig
|
/.editorconfig
|
||||||
/.env
|
/.env
|
||||||
/.gitignore
|
/.gitignore
|
||||||
|
@ -9,11 +10,12 @@
|
||||||
/.jscsrc
|
/.jscsrc
|
||||||
/.npmignore
|
/.npmignore
|
||||||
/.npmrc
|
/.npmrc
|
||||||
|
/.travis.yml
|
||||||
|
/docker
|
||||||
/Dockerfile
|
/Dockerfile
|
||||||
/bower.json
|
/bower.json
|
||||||
/component.json
|
/component.json
|
||||||
/gulpfile.js
|
/gulpfile.js
|
||||||
/node_modules/
|
|
||||||
/npm-debug.log
|
/npm-debug.log
|
||||||
/res/bower_components/
|
/res/bower_components/
|
||||||
/res/test/
|
/res/test/
|
||||||
|
|
63
.travis.yml
63
.travis.yml
|
@ -1,24 +1,61 @@
|
||||||
language: node_js
|
language: cpp
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
node_js:
|
|
||||||
- "0.12"
|
|
||||||
- "0.10"
|
|
||||||
- "iojs"
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
packages:
|
packages:
|
||||||
- libzmq3-dev
|
- libzmq3-dev
|
||||||
- libprotobuf-dev
|
- libprotobuf-dev
|
||||||
- graphicsmagick
|
- graphicsmagick
|
||||||
- rethinkdb
|
- rethinkdb
|
||||||
script:
|
- g++-4.9
|
||||||
- gulp build
|
- yasm
|
||||||
|
|
||||||
|
env:
|
||||||
|
matrix:
|
||||||
|
- NODE_VERSION=0.12
|
||||||
|
- NODE_VERSION=4
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- os: osx
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- rm -rf ~/.nvm && git clone --depth 1 https://github.com/creationix/nvm.git ~/.nvm
|
||||||
|
- source ~/.nvm/nvm.sh
|
||||||
|
- nvm install $NODE_VERSION
|
||||||
|
- node --version
|
||||||
|
- npm --version
|
||||||
|
- if [ "${TRAVIS_OS_NAME}" == "linux" ]; then export CXX=g++-4.9; fi
|
||||||
|
- if [ "${TRAVIS_OS_NAME}" == "osx" ]; then brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config; fi
|
||||||
|
|
||||||
|
install:
|
||||||
|
- npm install
|
||||||
|
- export PATH=$PWD/node_modules/.bin:$PATH
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- npm install -g bower
|
# - rethinkdb --daemon
|
||||||
- bower install
|
|
||||||
|
script:
|
||||||
|
- npm test
|
||||||
|
# - ./bin/stf local
|
||||||
|
- gulp build
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
# - killall rethinkdb
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- node_modules
|
- node_modules
|
||||||
- res/bower_components
|
- res/bower_components
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
slack: openstf:qu01BtEgttJOrGGsRxKBJwki
|
slack: openstf:qu01BtEgttJOrGGsRxKBJwki
|
||||||
|
|
39
Dockerfile
39
Dockerfile
|
@ -1,11 +1,4 @@
|
||||||
FROM openstf/base:v1.0.1
|
FROM openstf/base:v1.0.6
|
||||||
|
|
||||||
# Add a user for the app.
|
|
||||||
RUN useradd --system \
|
|
||||||
--no-create-home \
|
|
||||||
--shell /usr/sbin/nologin \
|
|
||||||
--home-dir /app \
|
|
||||||
stf
|
|
||||||
|
|
||||||
# Sneak the stf executable into $PATH.
|
# Sneak the stf executable into $PATH.
|
||||||
ENV PATH /app/bin:$PATH
|
ENV PATH /app/bin:$PATH
|
||||||
|
@ -18,15 +11,31 @@ WORKDIR /app
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Copy app source.
|
# Copy app source.
|
||||||
COPY . /app/
|
COPY . /tmp/build/
|
||||||
|
|
||||||
# Get the rest of the dependencies and build.
|
# Give permissions to our build user.
|
||||||
RUN export PATH=/app/node_modules/.bin:$PATH && \
|
RUN mkdir -p /app && \
|
||||||
npm install && \
|
chown -R stf-build:stf-build /tmp/build /app
|
||||||
bower install --allow-root && \
|
|
||||||
gulp build
|
|
||||||
|
|
||||||
# Switch to weak user.
|
# Switch over to the build user.
|
||||||
|
USER stf-build
|
||||||
|
|
||||||
|
# Run the build.
|
||||||
|
RUN set -x && \
|
||||||
|
cd /tmp/build && \
|
||||||
|
export PATH=$PWD/node_modules/.bin:$PATH && \
|
||||||
|
npm install --loglevel http && \
|
||||||
|
npm pack && \
|
||||||
|
tar xzf stf-*.tgz --strip-components 1 -C /app && \
|
||||||
|
bower cache clean && \
|
||||||
|
npm prune --production && \
|
||||||
|
mv node_modules /app && \
|
||||||
|
npm cache clean && \
|
||||||
|
rm -rf ~/.node-gyp && \
|
||||||
|
cd /app && \
|
||||||
|
rm -rf /tmp/*
|
||||||
|
|
||||||
|
# Switch to the app user.
|
||||||
USER stf
|
USER stf
|
||||||
|
|
||||||
# Show help by default.
|
# Show help by default.
|
||||||
|
|
|
@ -17,7 +17,7 @@ It is currently being used at [CyberAgent](https://www.cyberagent.co.jp/en/) to
|
||||||
|
|
||||||
* OS support
|
* OS support
|
||||||
- Android
|
- Android
|
||||||
* Supports versions 2.3.3 (SDK level 10) to 5.1 (SDK level 22), plus Android M Developer Preview
|
* Supports versions 2.3.3 (SDK level 10) to 5.1 (SDK level 22), plus Android M Developer Preview 3
|
||||||
* Supports Wear 5.1 (but not 5.0 due to missing permissions)
|
* Supports Wear 5.1 (but not 5.0 due to missing permissions)
|
||||||
* Supports Fire OS, CyanogenMod, and other heavily Android based distributions
|
* Supports Fire OS, CyanogenMod, and other heavily Android based distributions
|
||||||
* `root` is **not** required for any current functionality
|
* `root` is **not** required for any current functionality
|
||||||
|
@ -43,6 +43,7 @@ It is currently being used at [CyberAgent](https://www.cyberagent.co.jp/en/) to
|
||||||
* Run any `adb` command locally, including shell access
|
* Run any `adb` command locally, including shell access
|
||||||
* [Android Studio](http://developer.android.com/tools/studio/index.html) and other IDE support, debug your app while watching the device screen on your browser
|
* [Android Studio](http://developer.android.com/tools/studio/index.html) and other IDE support, debug your app while watching the device screen on your browser
|
||||||
* Supports [Chrome remote debug tools](https://developer.chrome.com/devtools/docs/remote-debugging)
|
* Supports [Chrome remote debug tools](https://developer.chrome.com/devtools/docs/remote-debugging)
|
||||||
|
- Experimental VNC support (work in progress)
|
||||||
* Manage your device inventory
|
* Manage your device inventory
|
||||||
- See which devices are connected, offline/unavailable (indicating a weak USB connection), unauthorized or unplugged
|
- See which devices are connected, offline/unavailable (indicating a weak USB connection), unauthorized or unplugged
|
||||||
- See who's using a device
|
- See who's using a device
|
||||||
|
@ -65,14 +66,15 @@ As the product has evolved from an internal tool running in our internal network
|
||||||
* [GraphicsMagick](http://www.graphicsmagick.org/) (for resizing screenshots)
|
* [GraphicsMagick](http://www.graphicsmagick.org/) (for resizing screenshots)
|
||||||
* [ZeroMQ](http://zeromq.org/) libraries installed
|
* [ZeroMQ](http://zeromq.org/) libraries installed
|
||||||
* [Protocol Buffers](https://github.com/google/protobuf) libraries installed
|
* [Protocol Buffers](https://github.com/google/protobuf) libraries installed
|
||||||
|
* [yasm](http://yasm.tortall.net/) installed (for compiling embedded [libjpeg-turbo](https://github.com/sorccu/node-jpeg-turbo))
|
||||||
* [pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/) so that Node.js can find the libraries
|
* [pkg-config](http://www.freedesktop.org/wiki/Software/pkg-config/) so that Node.js can find the libraries
|
||||||
|
|
||||||
Note that you need these dependencies even if you've installed STF directly from [NPM](https://www.npmjs.com/), because they can't be included.
|
Note that you need these dependencies even if you've installed STF directly from [NPM](https://www.npmjs.com/), because they can't be included in the package.
|
||||||
|
|
||||||
On OS X, you can use [homebrew](http://brew.sh/) to install most of the dependencies:
|
On OS X, you can use [homebrew](http://brew.sh/) to install most of the dependencies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install rethinkdb graphicsmagick zeromq protobuf pkg-config
|
brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config
|
||||||
```
|
```
|
||||||
|
|
||||||
On Windows you're on your own. In theory you might be able to get STF installed via [Cygwin](https://www.cygwin.com/) or similar, but we've never tried. In principle we will not provide any Windows installation support, but please do send a documentation pull request if you figure out what to do.
|
On Windows you're on your own. In theory you might be able to get STF installed via [Cygwin](https://www.cygwin.com/) or similar, but we've never tried. In principle we will not provide any Windows installation support, but please do send a documentation pull request if you figure out what to do.
|
||||||
|
|
35
bower.json
35
bower.json
|
@ -2,47 +2,46 @@
|
||||||
"name": "stf",
|
"name": "stf",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "1.4.3",
|
"angular": "~1.4.7",
|
||||||
"angular-cookies": "1.4.3",
|
"angular-cookies": "~1.4.7",
|
||||||
"angular-route": "1.4.3",
|
"angular-route": "~1.4.7",
|
||||||
"angular-sanitize": "1.4.3",
|
"angular-sanitize": "~1.4.7",
|
||||||
"angular-animate": "1.4.3",
|
"angular-animate": "~1.4.7",
|
||||||
"angular-touch": "1.4.3",
|
"angular-touch": "~1.4.7",
|
||||||
"lodash": "~3.10.1",
|
"lodash": "~3.10.1",
|
||||||
"oboe": "~2.1.2",
|
"oboe": "~2.1.2",
|
||||||
"ng-table": "~0.8.1",
|
"ng-table": "~1.0.0-beta.7",
|
||||||
"angular-gettext": "~2.1.0",
|
"angular-gettext": "~2.1.0",
|
||||||
"angular-ui-ace": "~0.2.3",
|
"angular-ui-ace": "~0.2.3",
|
||||||
"angular-bootstrap": "~0.13.2",
|
|
||||||
"angular-dialog-service": "~5.2.6",
|
"angular-dialog-service": "~5.2.6",
|
||||||
"ng-file-upload": "~2.0.5",
|
"ng-file-upload": "~2.0.5",
|
||||||
"angular-growl-v2": "JanStevens/angular-growl-2#~0.7.3",
|
"angular-growl-v2": "JanStevens/angular-growl-2#~0.7.3",
|
||||||
"underscore.string": "~3.1.1",
|
"underscore.string": "~3.2.2",
|
||||||
"bootstrap": "~3.3.5",
|
"bootstrap": "~3.3.5",
|
||||||
"font-lato-2-subset": "~0.4.0",
|
"font-lato-2-subset": "~0.4.0",
|
||||||
"packery": "~1.4.2",
|
"packery": "~1.4.3",
|
||||||
"draggabilly": "~1.2.4",
|
"draggabilly": "~1.2.4",
|
||||||
"angular-elastic": "~2.5.0",
|
"angular-elastic": "~2.5.1",
|
||||||
"angular-hotkeys": "chieffancypants/angular-hotkeys#~1.4.5",
|
"angular-hotkeys": "chieffancypants/angular-hotkeys#~1.6.0",
|
||||||
"angular-borderlayout": "git://github.com/filearts/angular-borderlayout.git#7c9716aebd9260763f798561ca49d6fbfd4a5c67",
|
"angular-borderlayout": "git://github.com/filearts/angular-borderlayout.git#7c9716aebd9260763f798561ca49d6fbfd4a5c67",
|
||||||
"angular-ui-bootstrap": "~0.13.2",
|
"angular-ui-bootstrap": "~0.14.2",
|
||||||
"ng-context-menu": "~1.0.1",
|
"ng-context-menu": "AdiDahan/ng-context-menu#~1.0.5",
|
||||||
"components-font-awesome": "~4.4.0",
|
"components-font-awesome": "~4.4.0",
|
||||||
"epoch": "~0.6.0",
|
"epoch": "~0.6.0",
|
||||||
"ng-epoch": "~1.0.7",
|
"ng-epoch": "~1.0.7",
|
||||||
"eventEmitter": "~4.2.11",
|
"eventEmitter": "~4.3.0",
|
||||||
"angular-ladda": "~0.3.1",
|
"angular-ladda": "~0.3.1",
|
||||||
"d3": "~3.5.6",
|
"d3": "~3.5.6",
|
||||||
"spin.js": "~2.3.2"
|
"spin.js": "~2.3.2"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular-mocks": "1.4.3"
|
"angular-mocks": "~1.4.7"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"angular": "1.4.3",
|
"angular": "~1.4.7",
|
||||||
"d3": "~3.5.5",
|
"d3": "~3.5.5",
|
||||||
"spin.js": "~2.3.2",
|
"spin.js": "~2.3.2",
|
||||||
"angular-bootstrap": "~0.13.2"
|
"eventEmitter": "~4.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,12 @@ The app role can contain any of the following units. You may distribute them as
|
||||||
* [stf-triproxy-dev.service](#stf-triproxy-devservice)
|
* [stf-triproxy-dev.service](#stf-triproxy-devservice)
|
||||||
* [stf-websocket@.service](#stf-websocketservice)
|
* [stf-websocket@.service](#stf-websocketservice)
|
||||||
|
|
||||||
|
### Database role
|
||||||
|
|
||||||
|
The database role requires the following units, UNLESS you already have a working RethinkDB server/cluster running somewhere. In that case you simply will not have this role, and should point your [rethinkdb-proxy-28015.service](#rethinkdb-proxy-28015service) to that server instead.
|
||||||
|
|
||||||
|
* [rethinkdb.service](#rethinkdbservice)
|
||||||
|
|
||||||
### Proxy role
|
### Proxy role
|
||||||
|
|
||||||
The proxy role ties all HTTP-based units together behind a common reverse proxy. See [nginx configuration](#nginx-configuration) for more information.
|
The proxy role ties all HTTP-based units together behind a common reverse proxy. See [nginx configuration](#nginx-configuration) for more information.
|
||||||
|
@ -91,13 +97,29 @@ ExecStart=/usr/bin/docker run --rm \
|
||||||
ExecStop=-/usr/bin/docker stop -t 2 %p
|
ExecStop=-/usr/bin/docker stop -t 2 %p
|
||||||
```
|
```
|
||||||
|
|
||||||
### `rethinkdb-proxy-28015.service`
|
### `rethinkdb.service`
|
||||||
|
|
||||||
You need a single instance of the `rethinkdb-proxy-28015.service` unit on each host where you have another unit that needs to access the database. Having a local proxy simplifies configuration for other units and allows the `AUTHKEY` to be specified only once.
|
As mentioned before, you only need this unit if you do not have an existing RethinkDB cluster. This configuration is provided as an example, and will get you going, but is not very robust or secure.
|
||||||
|
|
||||||
|
If you need to expand your RethinkDB cluster beyond one server you may encounter problems that you'll have to solve by yourself, we're not going to help with that. There are many ways to configure the unit, this is just one possibility! Note that if you end up not using `--net host`, you will then have to give `rethinkdb` the `--canonical-address` option with the server's real IP, and expose the necessary ports somehow.
|
||||||
|
|
||||||
|
You will also have to:
|
||||||
|
|
||||||
|
1. Modify the `--cache-size` as you please. It limits the amount of memory RethinkDB uses and is given in megabytes, but is not an absolute limit! Real usage can be slightly higher.
|
||||||
|
2. Update the version number in `rethinkdb:2.1.1` for the latest release. We don't use `rethinkdb:latest` here because then you might occasionally have to manually rebuild your indexes after an update and not even realize it, bringing the whole system effectively down.
|
||||||
|
3. The `AUTHKEY` environment variable is only for convenience when linking. So, the first time you set things up, you will have to access http://DB_SERVER_IP:8080 after starting the unit and run the following command:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
r.db('rethinkdb').table('cluster_config').get('auth').update({auth_key: 'newkey'})
|
||||||
|
```
|
||||||
|
|
||||||
|
More information can be found [here](https://rethinkdb.com/docs/security/). You will then need to replace `YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY` in the the rest of the units with the real authentication key.
|
||||||
|
|
||||||
|
Here's the unit configuration itself.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=RethinkDB proxy/28015
|
Description=RethinkDB
|
||||||
After=docker.service
|
After=docker.service
|
||||||
Requires=docker.service
|
Requires=docker.service
|
||||||
|
|
||||||
|
@ -105,7 +127,39 @@ Requires=docker.service
|
||||||
EnvironmentFile=/etc/environment
|
EnvironmentFile=/etc/environment
|
||||||
TimeoutStartSec=0
|
TimeoutStartSec=0
|
||||||
Restart=always
|
Restart=always
|
||||||
ExecStartPre=/usr/bin/docker pull ctlc/ambassador:latest
|
ExecStartPre=/usr/bin/docker pull rethinkdb:2.1.1
|
||||||
|
ExecStartPre=-/usr/bin/docker kill %p
|
||||||
|
ExecStartPre=-/usr/bin/docker rm %p
|
||||||
|
ExecStartPre=/usr/bin/mkdir -p /srv/rethinkdb
|
||||||
|
ExecStartPre=/usr/bin/chattr -R +C /srv/rethinkdb
|
||||||
|
ExecStart=/usr/bin/docker run --rm \
|
||||||
|
--name %p \
|
||||||
|
-v /srv/rethinkdb:/data \
|
||||||
|
-e "AUTHKEY=YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY" \
|
||||||
|
--net host \
|
||||||
|
rethinkdb:2.1.1 \
|
||||||
|
rethinkdb --bind all \
|
||||||
|
--cache-size 8192
|
||||||
|
ExecStop=-/usr/bin/docker stop -t 10 %p
|
||||||
|
```
|
||||||
|
|
||||||
|
### `rethinkdb-proxy-28015.service`
|
||||||
|
|
||||||
|
You need a single instance of the `rethinkdb-proxy-28015.service` unit on each host where you have another unit that needs to access the database. Having a local proxy simplifies configuration for other units and allows the `AUTHKEY` to be specified only once.
|
||||||
|
|
||||||
|
Note that the `After` condition also specifies the [rethinkdb.service](#rethinkdbservice) unit just in case you're on a low budget and want to run the RethinkDB unit on the same server as the rest of the units, which by the way is NOT recommended at all.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=RethinkDB proxy/28015
|
||||||
|
After=docker.service rethinkdb.service
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=/etc/environment
|
||||||
|
TimeoutStartSec=0
|
||||||
|
Restart=always
|
||||||
|
ExecStartPre=/usr/bin/docker pull openstf/ambassador:latest
|
||||||
ExecStartPre=-/usr/bin/docker kill %p
|
ExecStartPre=-/usr/bin/docker kill %p
|
||||||
ExecStartPre=-/usr/bin/docker rm %p
|
ExecStartPre=-/usr/bin/docker rm %p
|
||||||
ExecStart=/usr/bin/docker run --rm \
|
ExecStart=/usr/bin/docker run --rm \
|
||||||
|
@ -113,7 +167,7 @@ ExecStart=/usr/bin/docker run --rm \
|
||||||
-e "AUTHKEY=YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY" \
|
-e "AUTHKEY=YOUR_RETHINKDB_AUTH_KEY_HERE_IF_ANY" \
|
||||||
-p 28015 \
|
-p 28015 \
|
||||||
-e RETHINKDB_PORT_28015_TCP=tcp://rethinkdb.stf.example.org:28015 \
|
-e RETHINKDB_PORT_28015_TCP=tcp://rethinkdb.stf.example.org:28015 \
|
||||||
ctlc/ambassador:latest
|
openstf/ambassador:latest
|
||||||
ExecStop=-/usr/bin/docker stop -t 10 %p
|
ExecStop=-/usr/bin/docker stop -t 10 %p
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -146,7 +200,7 @@ ExecStart=/usr/bin/docker run --rm \
|
||||||
--name %p-%i \
|
--name %p-%i \
|
||||||
--link rethinkdb-proxy-28015:rethinkdb \
|
--link rethinkdb-proxy-28015:rethinkdb \
|
||||||
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
|
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
|
||||||
-p 127.0.0.1:%i:3000 \
|
-p %i:3000 \
|
||||||
openstf/stf:latest \
|
openstf/stf:latest \
|
||||||
stf app --port 3000 \
|
stf app --port 3000 \
|
||||||
--auth-url https://stf.example.org/auth/mock/ \
|
--auth-url https://stf.example.org/auth/mock/ \
|
||||||
|
@ -160,7 +214,7 @@ You may have to change the `--auth-url` depending on which authentication method
|
||||||
|
|
||||||
You have multiple options here. STF currently provides authentication units for [OAuth 2.0](http://oauth.net/2/) and [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol), plus a mock implementation that simply asks for a name and an email address.
|
You have multiple options here. STF currently provides authentication units for [OAuth 2.0](http://oauth.net/2/) and [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol), plus a mock implementation that simply asks for a name and an email address.
|
||||||
|
|
||||||
Since the other providers require quite a bit of configuration, we'll simply set up a mock auth unit here. If you'd rather use the real providers, see `stf auth-oauth2 --help` and `stf auth-ldap --help` for the required variables.
|
Since the other providers require quite a bit of configuration, we'll simply set up a mock auth unit here. If you'd rather use the real providers, see `stf auth-oauth2 --help` and `stf auth-ldap --help` for the required variables. Note that if your OAuth 2 provider uses a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`.
|
||||||
|
|
||||||
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-auth@3200.service` runs on port 3200). You can have multiple instances running on the same host by using different ports.
|
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-auth@3200.service` runs on port 3200). You can have multiple instances running on the same host by using different ports.
|
||||||
|
|
||||||
|
@ -180,7 +234,7 @@ ExecStartPre=-/usr/bin/docker rm %p-%i
|
||||||
ExecStart=/usr/bin/docker run --rm \
|
ExecStart=/usr/bin/docker run --rm \
|
||||||
--name %p-%i \
|
--name %p-%i \
|
||||||
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
|
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
|
||||||
-p 127.0.0.1:%i:3000 \
|
-p %i:3000 \
|
||||||
openstf/stf:latest \
|
openstf/stf:latest \
|
||||||
stf auth-mock --port 3000 \
|
stf auth-mock --port 3000 \
|
||||||
--app-url https://stf.example.org/
|
--app-url https://stf.example.org/
|
||||||
|
@ -259,6 +313,8 @@ This is a template unit, meaning that you'll need to start it with an instance i
|
||||||
|
|
||||||
Note that you cannot have more than one provider unit running on the same host, as they would compete over which one gets to control the devices. In the future we might add a negotiation protocol to allow for relatively seamless upgrades.
|
Note that you cannot have more than one provider unit running on the same host, as they would compete over which one gets to control the devices. In the future we might add a negotiation protocol to allow for relatively seamless upgrades.
|
||||||
|
|
||||||
|
Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=STF provider
|
Description=STF provider
|
||||||
|
@ -293,7 +349,7 @@ ExecStop=-/usr/bin/docker stop -t 10 %p-%i
|
||||||
|
|
||||||
**Requires** the `rethinkdb-proxy-28015.service` unit on the same host.
|
**Requires** the `rethinkdb-proxy-28015.service` unit on the same host.
|
||||||
|
|
||||||
The reaper unit receives heartbeat events from device workers, and marks lost devices as absent until a heartbeat is received again. The purpose of this unit is to ensure the integrity of the present/absent flag in the database, in case a provider shuts down unexpectedly or another unexpected failure occurs. It loads the current state from the database on startup and keeps keeps patching its internal view as events are routed to it.
|
The reaper unit receives heartbeat events from device workers, and marks lost devices as absent until a heartbeat is received again. The purpose of this unit is to ensure the integrity of the present/absent flag in the database, in case a provider shuts down unexpectedly or another unexpected failure occurs. It loads the current state from the database on startup and keeps patching its internal view as events are routed to it.
|
||||||
|
|
||||||
Note that it doesn't make sense to have more than one reaper running at once, as they would just duplicate the events.
|
Note that it doesn't make sense to have more than one reaper running at once, as they would just duplicate the events.
|
||||||
|
|
||||||
|
@ -327,6 +383,8 @@ The APK storage plugin loads raw blobs from the main storage unit and allows add
|
||||||
|
|
||||||
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-storage-plugin-apk@3300.service` runs on port 3300). You can have multiple instances running on the same host by using different ports.
|
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-storage-plugin-apk@3300.service` runs on port 3300). You can have multiple instances running on the same host by using different ports.
|
||||||
|
|
||||||
|
Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=STF APK storage plugin
|
Description=STF APK storage plugin
|
||||||
|
@ -342,7 +400,7 @@ ExecStartPre=-/usr/bin/docker kill %p-%i
|
||||||
ExecStartPre=-/usr/bin/docker rm %p-%i
|
ExecStartPre=-/usr/bin/docker rm %p-%i
|
||||||
ExecStart=/usr/bin/docker run --rm \
|
ExecStart=/usr/bin/docker run --rm \
|
||||||
--name %p-%i \
|
--name %p-%i \
|
||||||
-p 127.0.0.1:%i:3000 \
|
-p %i:3000 \
|
||||||
openstf/stf:latest \
|
openstf/stf:latest \
|
||||||
stf storage-plugin-apk --port 3000 \
|
stf storage-plugin-apk --port 3000 \
|
||||||
--storage-url https://stf.example.org/
|
--storage-url https://stf.example.org/
|
||||||
|
@ -355,6 +413,8 @@ The image storage plugin loads raw blobs from the main storage unit and and allo
|
||||||
|
|
||||||
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-storage-plugin-image@3400.service` runs on port 3400). You can have multiple instances running on the same host by using different ports.
|
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-storage-plugin-image@3400.service` runs on port 3400). You can have multiple instances running on the same host by using different ports.
|
||||||
|
|
||||||
|
Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=STF image storage plugin
|
Description=STF image storage plugin
|
||||||
|
@ -370,7 +430,7 @@ ExecStartPre=-/usr/bin/docker kill %p-%i
|
||||||
ExecStartPre=-/usr/bin/docker rm %p-%i
|
ExecStartPre=-/usr/bin/docker rm %p-%i
|
||||||
ExecStart=/usr/bin/docker run --rm \
|
ExecStart=/usr/bin/docker run --rm \
|
||||||
--name %p-%i \
|
--name %p-%i \
|
||||||
-p 127.0.0.1:%i:3000 \
|
-p %i:3000 \
|
||||||
openstf/stf:latest \
|
openstf/stf:latest \
|
||||||
stf storage-plugin-image --port 3000 \
|
stf storage-plugin-image --port 3000 \
|
||||||
--storage-url https://stf.example.org/
|
--storage-url https://stf.example.org/
|
||||||
|
@ -397,7 +457,7 @@ ExecStartPre=-/usr/bin/docker rm %p-%i
|
||||||
ExecStart=/usr/bin/docker run --rm \
|
ExecStart=/usr/bin/docker run --rm \
|
||||||
--name %p-%i \
|
--name %p-%i \
|
||||||
-v /mnt/storage:/data \
|
-v /mnt/storage:/data \
|
||||||
-p 127.0.0.1:%i:3000 \
|
-p %i:3000 \
|
||||||
openstf/stf:latest \
|
openstf/stf:latest \
|
||||||
stf storage-temp --port 3000 \
|
stf storage-temp --port 3000 \
|
||||||
--save-dir /data
|
--save-dir /data
|
||||||
|
@ -476,6 +536,8 @@ The websocket unit provides the communication layer between client-side JavaScri
|
||||||
|
|
||||||
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-websocket@3600.service` runs on port 3600). You can have multiple instances running on the same host by using different ports.
|
This is a template unit, meaning that you'll need to start it with an instance identifier. In this example configuration the identifier is used to specify the exposed port number (i.e. `stf-websocket@3600.service` runs on port 3600). You can have multiple instances running on the same host by using different ports.
|
||||||
|
|
||||||
|
Furthermore, if you're using a self-signed cert, you may have to add `-e "NODE_TLS_REJECT_UNAUTHORIZED=0"` to the `docker run` command. Don't forget to end the line with `\`.
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=STF websocket
|
Description=STF websocket
|
||||||
|
@ -493,7 +555,7 @@ ExecStart=/usr/bin/docker run --rm \
|
||||||
--name %p-%i \
|
--name %p-%i \
|
||||||
--link rethinkdb-proxy-28015:rethinkdb \
|
--link rethinkdb-proxy-28015:rethinkdb \
|
||||||
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
|
-e "SECRET=YOUR_SESSION_SECRET_HERE" \
|
||||||
-p 127.0.0.1:%i:3000 \
|
-p %i:3000 \
|
||||||
openstf/stf:latest \
|
openstf/stf:latest \
|
||||||
stf websocket --port 3000 \
|
stf websocket --port 3000 \
|
||||||
--storage-url https://stf.example.org/ \
|
--storage-url https://stf.example.org/ \
|
||||||
|
@ -688,8 +750,8 @@ http {
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /auth/mock/ {
|
location /auth/ {
|
||||||
proxy_pass http://stf_auth/auth/mock/;
|
proxy_pass http://stf_auth/auth/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /s/image/ {
|
location /s/image/ {
|
||||||
|
|
17
doc/VNC.md
Normal file
17
doc/VNC.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# VNC
|
||||||
|
|
||||||
|
## Implementation details
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
#### According to the spec
|
||||||
|
|
||||||
|
VNC authentication is very weak by default, and doesn't encrypt traffic in any way. It works by sending a random 16-byte challenge to the user, who then encrypts with his/her password and sends back the 16-byte result. The server then encrypts the challenge as well, and checks whether the result sent by the client matches the server's result. Passwords are required to be 8 characters long. Shorter passwords are padded with zeroes and longer passwords simply truncated. Both the server and the client have to know the password. There are no usernames.
|
||||||
|
|
||||||
|
#### The way we do it
|
||||||
|
|
||||||
|
Since the authentication is very weak anyway, we might as well exploit it. The problem with the spec method is that since there's no username, it's difficult to know *who* wants to connect to a device. The only place for any kind of information is the password, but without knowing the password we can't decrypt the challenge response to see the contents. While we could go through our whole user database encrypting the challenge with each user's password, that doesn't really scale in the long run, especially since we're interested in having per-device passwords as well (more on that later).
|
||||||
|
|
||||||
|
Instead, we send over a *static* challenge, e.g. 16 zeroes, every time. Then we simply identify the user by the returned challenge response itself, which is both unique and constant for each password. This makes the authentication more susceptible to eavesdropping since responses from previous sessions can be reused, but given the already weak nature of basic VNC authentication this shouldn't be a massive downgrade, and the app should be running inside an internal network anyway. For real security, all connections should be over a secure tunnel.
|
||||||
|
|
||||||
|
Furthermore, each password is only valid for a single device. This will enable interesting proxying and/or load balancing opportunities in the future as we should be able to expose every single device in the system via a single port if desired.
|
|
@ -24,7 +24,8 @@ COPY . /app/
|
||||||
RUN export PATH=/app/node_modules/.bin:$PATH && \
|
RUN export PATH=/app/node_modules/.bin:$PATH && \
|
||||||
npm install && \
|
npm install && \
|
||||||
bower install --allow-root && \
|
bower install --allow-root && \
|
||||||
gulp build
|
gulp build && \
|
||||||
|
npm prune --production
|
||||||
|
|
||||||
# Switch to weak user.
|
# Switch to weak user.
|
||||||
USER stf
|
USER stf
|
||||||
|
|
137
gulpfile.js
137
gulpfile.js
|
@ -1,16 +1,16 @@
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
var gulp = require('gulp')
|
var gulp = require('gulp')
|
||||||
var gutil = require('gulp-util')
|
var gutil = require('gulp-util')
|
||||||
var jshint = require('gulp-jshint')
|
var jshint = require('gulp-jshint')
|
||||||
var jsonlint = require('gulp-jsonlint')
|
var jsonlint = require('gulp-jsonlint')
|
||||||
var standard = require('gulp-standard')
|
var standard = require('gulp-standard')
|
||||||
var webpack = require('webpack')
|
var webpack = require('webpack')
|
||||||
var ngAnnotatePlugin = require('ng-annotate-webpack-plugin')
|
|
||||||
var webpackConfig = require('./webpack.config').webpack
|
var webpackConfig = require('./webpack.config').webpack
|
||||||
var webpackStatusConfig = require('./res/common/status/webpack.config')
|
var webpackStatusConfig = require('./res/common/status/webpack.config')
|
||||||
var gettext = require('gulp-angular-gettext')
|
var gettext = require('gulp-angular-gettext')
|
||||||
var jade = require('gulp-jade')
|
var jade = require('gulp-jade')
|
||||||
var del = require('del')
|
var del = require('del')
|
||||||
var runSequence = require('run-sequence').use(gulp)
|
|
||||||
//var protractor = require('gulp-protractor')
|
//var protractor = require('gulp-protractor')
|
||||||
var protractor = require('./res/test/e2e/helpers/gulp-protractor-adv')
|
var protractor = require('./res/test/e2e/helpers/gulp-protractor-adv')
|
||||||
var protractorConfig = './res/test/protractor.conf'
|
var protractorConfig = './res/test/protractor.conf'
|
||||||
|
@ -22,30 +22,42 @@ var run = require('gulp-run')
|
||||||
|
|
||||||
gulp.task('jshint', function () {
|
gulp.task('jshint', function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
'lib/**/*.js', 'res/app/**/*.js', 'res/auth-ldap/**/*.js',
|
'lib/**/*.js'
|
||||||
'res/auth-mock/**/*.js', 'res/common/**/*.js', 'res/test/**/*.js',
|
, 'res/app/**/*.js'
|
||||||
'*.js'
|
, 'res/auth-ldap/**/*.js'
|
||||||
])
|
, 'res/auth-mock/**/*.js'
|
||||||
|
, 'res/common/**/*.js'
|
||||||
|
, 'res/test/**/*.js'
|
||||||
|
, '*.js'
|
||||||
|
])
|
||||||
.pipe(jshint())
|
.pipe(jshint())
|
||||||
.pipe(jshint.reporter('jshint-stylish'))
|
.pipe(jshint.reporter('jshint-stylish'))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('jsonlint', function () {
|
gulp.task('jsonlint', function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
'.jshintrc', 'res/.jshintrc', '.bowerrc', '.yo-rc.json', '*.json'
|
'.jshintrc'
|
||||||
])
|
, 'res/.jshintrc'
|
||||||
|
, '.bowerrc'
|
||||||
|
, '.yo-rc.json'
|
||||||
|
, '*.json'
|
||||||
|
])
|
||||||
.pipe(jsonlint())
|
.pipe(jsonlint())
|
||||||
.pipe(jsonlint.reporter())
|
.pipe(jsonlint.reporter())
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('jscs', function () {
|
gulp.task('jscs', function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
'lib/**/*.js', 'res/app/**/*.js', 'res/auth-ldap/**/*.js',
|
'lib/**/*.js'
|
||||||
'res/auth-mock/**/*.js', 'res/common/**/*.js', 'res/test/**/*.js',
|
, 'res/app/**/*.js'
|
||||||
'*.js'
|
, 'res/auth-ldap/**/*.js'
|
||||||
])
|
, 'res/auth-mock/**/*.js'
|
||||||
|
, 'res/common/**/*.js'
|
||||||
|
, 'res/test/**/*.js'
|
||||||
|
, '*.js'
|
||||||
|
])
|
||||||
.pipe(jscs())
|
.pipe(jscs())
|
||||||
});
|
})
|
||||||
|
|
||||||
gulp.task('standard', function () {
|
gulp.task('standard', function () {
|
||||||
// Check res/app for now
|
// Check res/app for now
|
||||||
|
@ -58,27 +70,23 @@ gulp.task('standard', function () {
|
||||||
|
|
||||||
gulp.task('lint', ['jshint', 'jsonlint'])
|
gulp.task('lint', ['jshint', 'jsonlint'])
|
||||||
gulp.task('test', ['lint', 'run:checkversion'])
|
gulp.task('test', ['lint', 'run:checkversion'])
|
||||||
|
gulp.task('build', ['clean', 'webpack:build'])
|
||||||
gulp.task('build', function (cb) {
|
|
||||||
runSequence('clean', 'webpack:build', cb)
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('run:checkversion', function () {
|
gulp.task('run:checkversion', function () {
|
||||||
gutil.log('Checking STF version...')
|
gutil.log('Checking STF version...')
|
||||||
|
|
||||||
return run('./bin/stf -V').exec()
|
return run('./bin/stf -V').exec()
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('karma_ci', function (done) {
|
gulp.task('karma_ci', function (done) {
|
||||||
karma.start({
|
karma.start({
|
||||||
configFile: __dirname + karmaConfig,
|
configFile: path.join(__dirname, karmaConfig)
|
||||||
singleRun: true
|
, singleRun: true
|
||||||
}, done)
|
}, done)
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('karma', function (done) {
|
gulp.task('karma', function (done) {
|
||||||
karma.start({
|
karma.start({
|
||||||
configFile: __dirname + karmaConfig
|
configFile: path.join(__dirname, karmaConfig)
|
||||||
}, done)
|
}, done)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -88,32 +96,37 @@ if (gutil.env.multi) {
|
||||||
protractorConfig = './res/test/protractor-appium.conf'
|
protractorConfig = './res/test/protractor-appium.conf'
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task('webdriver-update', protractor.webdriver_update)
|
gulp.task('webdriver-update', protractor.webdriverUpdate)
|
||||||
gulp.task('webdriver-standalone', protractor.webdriver_standalone)
|
gulp.task('webdriver-standalone', protractor.webdriverStandalone)
|
||||||
gulp.task('protractor-explorer', function (callback) {
|
gulp.task('protractor-explorer', function (callback) {
|
||||||
protractor.protractor_explorer({
|
protractor.protractorExplorer({
|
||||||
url: require(protractorConfig).config.baseUrl
|
url: require(protractorConfig).config.baseUrl
|
||||||
}, callback)
|
}, callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('protractor', ['webdriver-update'], function (callback) {
|
gulp.task('protractor', ['webdriver-update'], function (callback) {
|
||||||
gulp.src(["./res/test/e2e/**/*.js"])
|
gulp.src(['./res/test/e2e/**/*.js'])
|
||||||
.pipe(protractor.protractor({
|
.pipe(protractor.protractor({
|
||||||
configFile: protractorConfig,
|
configFile: protractorConfig
|
||||||
debug: gutil.env.debug,
|
, debug: gutil.env.debug
|
||||||
suite: gutil.env.suite
|
, suite: gutil.env.suite
|
||||||
}))
|
}))
|
||||||
.on('error', function (e) {
|
.on('error', function (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}).on('end', callback)
|
})
|
||||||
|
.on('end', callback)
|
||||||
})
|
})
|
||||||
|
|
||||||
// For piping strings
|
// For piping strings
|
||||||
function fromString(filename, string) {
|
function fromString(filename, string) {
|
||||||
var src = stream.Readable({objectMode: true})
|
/* eslint no-underscore-dangle: 0 */
|
||||||
|
var src = new stream.Readable({objectMode: true})
|
||||||
src._read = function () {
|
src._read = function () {
|
||||||
this.push(new gutil.File({
|
this.push(new gutil.File({
|
||||||
cwd: '', base: '', path: filename, contents: new Buffer(string)
|
cwd: ''
|
||||||
|
, base: ''
|
||||||
|
, path: filename
|
||||||
|
, contents: new Buffer(string)
|
||||||
}))
|
}))
|
||||||
this.push(null)
|
this.push(null)
|
||||||
}
|
}
|
||||||
|
@ -122,20 +135,14 @@ function fromString(filename, string) {
|
||||||
|
|
||||||
|
|
||||||
// For production
|
// For production
|
||||||
gulp.task("webpack:build", function (callback) {
|
gulp.task('webpack:build', function (callback) {
|
||||||
var myConfig = Object.create(webpackConfig)
|
var myConfig = Object.create(webpackConfig)
|
||||||
myConfig.plugins = myConfig.plugins.concat(
|
myConfig.plugins = myConfig.plugins.concat(
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env": {
|
'process.env': {
|
||||||
"NODE_ENV": JSON.stringify('production')
|
'NODE_ENV': JSON.stringify('production')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//new webpack.optimize.DedupePlugin(),
|
|
||||||
//new ngAnnotatePlugin({
|
|
||||||
// add: true,
|
|
||||||
//})
|
|
||||||
// TODO: mangle when ngmin works
|
|
||||||
//new webpack.optimize.UglifyJsPlugin({mangle: false})
|
|
||||||
)
|
)
|
||||||
myConfig.devtool = false
|
myConfig.devtool = false
|
||||||
|
|
||||||
|
@ -144,7 +151,7 @@ gulp.task("webpack:build", function (callback) {
|
||||||
throw new gutil.PluginError('webpack:build', err)
|
throw new gutil.PluginError('webpack:build', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gutil.log("[webpack:build]", stats.toString({
|
gutil.log('[webpack:build]', stats.toString({
|
||||||
colors: true
|
colors: true
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -157,17 +164,14 @@ gulp.task("webpack:build", function (callback) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task("webpack:others", function (callback) {
|
gulp.task('webpack:others', function (callback) {
|
||||||
var myConfig = Object.create(webpackStatusConfig)
|
var myConfig = Object.create(webpackStatusConfig)
|
||||||
myConfig.plugins = myConfig.plugins.concat(
|
myConfig.plugins = myConfig.plugins.concat(
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env": {
|
'process.env': {
|
||||||
"NODE_ENV": JSON.stringify('production')
|
'NODE_ENV': JSON.stringify('production')
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
new webpack.optimize.DedupePlugin()
|
|
||||||
// new ngminPlugin(),
|
|
||||||
// new webpack.optimize.UglifyJsPlugin({mangle: false})
|
|
||||||
)
|
)
|
||||||
myConfig.devtool = false
|
myConfig.devtool = false
|
||||||
|
|
||||||
|
@ -176,19 +180,25 @@ gulp.task("webpack:others", function (callback) {
|
||||||
throw new gutil.PluginError('webpack:others', err)
|
throw new gutil.PluginError('webpack:others', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gutil.log("[webpack:others]", stats.toString({
|
gutil.log('[webpack:others]', stats.toString({
|
||||||
colors: true
|
colors: true
|
||||||
}))
|
}))
|
||||||
callback()
|
callback()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('translate', ['translate:compile'])
|
gulp.task('translate', [
|
||||||
|
'translate:extract'
|
||||||
|
, 'translate:push'
|
||||||
|
, 'translate:pull'
|
||||||
|
, 'translate:compile'
|
||||||
|
])
|
||||||
|
|
||||||
gulp.task('jade', function (cb) {
|
gulp.task('jade', function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
'./res/**/*.jade', '!./res/bower_components/**'
|
'./res/**/*.jade'
|
||||||
])
|
, '!./res/bower_components/**'
|
||||||
|
])
|
||||||
.pipe(jade({
|
.pipe(jade({
|
||||||
locals: {
|
locals: {
|
||||||
// So res/views/docs.jade doesn't complain
|
// So res/views/docs.jade doesn't complain
|
||||||
|
@ -201,16 +211,18 @@ gulp.task('jade', function (cb) {
|
||||||
.pipe(gulp.dest('./tmp/html/'))
|
.pipe(gulp.dest('./tmp/html/'))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('translate:extract', ['jade'], function (cb) {
|
gulp.task('translate:extract', ['jade'], function () {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
'./tmp/html/**/*.html', './res/**/*.js', '!./res/bower_components/**',
|
'./tmp/html/**/*.html'
|
||||||
'!./res/build/**'
|
, './res/**/*.js'
|
||||||
])
|
, '!./res/bower_components/**'
|
||||||
|
, '!./res/build/**'
|
||||||
|
])
|
||||||
.pipe(gettext.extract('stf.pot'))
|
.pipe(gettext.extract('stf.pot'))
|
||||||
.pipe(gulp.dest('./res/common/lang/po/'))
|
.pipe(gulp.dest('./res/common/lang/po/'))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('translate:compile', ['translate:pull'], function (cb) {
|
gulp.task('translate:compile', function () {
|
||||||
return gulp.src('./res/common/lang/po/**/*.po')
|
return gulp.src('./res/common/lang/po/**/*.po')
|
||||||
.pipe(gettext.compile({
|
.pipe(gettext.compile({
|
||||||
format: 'json'
|
format: 'json'
|
||||||
|
@ -218,21 +230,16 @@ gulp.task('translate:compile', ['translate:pull'], function (cb) {
|
||||||
.pipe(gulp.dest('./res/common/lang/translations/'))
|
.pipe(gulp.dest('./res/common/lang/translations/'))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('translate:push', ['translate:extract'], function () {
|
gulp.task('translate:push', function () {
|
||||||
gutil.log('Pushing translation source to Transifex...')
|
gutil.log('Pushing translation source to Transifex...')
|
||||||
|
|
||||||
return run('tx push -s').exec()
|
return run('tx push -s').exec()
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('translate:pull', ['translate:push'], function () {
|
gulp.task('translate:pull', function () {
|
||||||
gutil.log('Pulling translations from Transifex...')
|
gutil.log('Pulling translations from Transifex...')
|
||||||
|
|
||||||
return run('tx pull').exec()
|
return run('tx pull').exec()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gulp.task('clean', function (cb) {
|
gulp.task('clean', function (cb) {
|
||||||
del(['./tmp', './res/build'], cb)
|
del(['./tmp', './res/build'], cb)
|
||||||
})
|
})
|
||||||
|
|
49
lib/cli.js
49
lib/cli.js
|
@ -15,7 +15,7 @@ program
|
||||||
.version(pkg.version)
|
.version(pkg.version)
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('provider [serial..]')
|
.command('provider [serial...]')
|
||||||
.description('start provider')
|
.description('start provider')
|
||||||
.option('-s, --connect-sub <endpoint>'
|
.option('-s, --connect-sub <endpoint>'
|
||||||
, 'sub endpoint'
|
, 'sub endpoint'
|
||||||
|
@ -68,12 +68,15 @@ program
|
||||||
, 'adb connect URL pattern'
|
, 'adb connect URL pattern'
|
||||||
, String
|
, String
|
||||||
, '${publicIp}:${publicPort}')
|
, '${publicIp}:${publicPort}')
|
||||||
|
.option('--vnc-initial-size <size>'
|
||||||
|
, 'initial VNC size'
|
||||||
|
, cliutil.size
|
||||||
|
, [600, 800])
|
||||||
.option('--mute-master'
|
.option('--mute-master'
|
||||||
, 'whether to mute master volume when devices are being used')
|
, 'whether to mute master volume when devices are being used')
|
||||||
.action(function() {
|
.option('--lock-rotation'
|
||||||
var serials = cliutil.allUnknownArgs(arguments)
|
, 'whether to lock rotation when devices are being used')
|
||||||
, options = cliutil.lastArg(arguments)
|
.action(function(serials, options) {
|
||||||
|
|
||||||
if (!options.connectSub) {
|
if (!options.connectSub) {
|
||||||
this.missingArgument('--connect-sub')
|
this.missingArgument('--connect-sub')
|
||||||
}
|
}
|
||||||
|
@ -101,6 +104,7 @@ program
|
||||||
, '--connect-push', options.connectPush.join(',')
|
, '--connect-push', options.connectPush.join(',')
|
||||||
, '--screen-port', ports.shift()
|
, '--screen-port', ports.shift()
|
||||||
, '--connect-port', ports.shift()
|
, '--connect-port', ports.shift()
|
||||||
|
, '--vnc-port', ports.shift()
|
||||||
, '--public-ip', options.publicIp
|
, '--public-ip', options.publicIp
|
||||||
, '--group-timeout', options.groupTimeout
|
, '--group-timeout', options.groupTimeout
|
||||||
, '--storage-url', options.storageUrl
|
, '--storage-url', options.storageUrl
|
||||||
|
@ -109,8 +113,10 @@ program
|
||||||
, '--screen-ws-url-pattern', options.screenWsUrlPattern
|
, '--screen-ws-url-pattern', options.screenWsUrlPattern
|
||||||
, '--connect-url-pattern', options.connectUrlPattern
|
, '--connect-url-pattern', options.connectUrlPattern
|
||||||
, '--heartbeat-interval', options.heartbeatInterval
|
, '--heartbeat-interval', options.heartbeatInterval
|
||||||
|
, '--vnc-initial-size', options.vncInitialSize.join('x')
|
||||||
]
|
]
|
||||||
.concat(options.muteMaster ? ['--mute-master'] : []))
|
.concat(options.muteMaster ? ['--mute-master'] : [])
|
||||||
|
.concat(options.lockRotation ? ['--lock-rotation'] : []))
|
||||||
}
|
}
|
||||||
, endpoints: {
|
, endpoints: {
|
||||||
sub: options.connectSub
|
sub: options.connectSub
|
||||||
|
@ -139,6 +145,13 @@ program
|
||||||
.option('--connect-port <port>'
|
.option('--connect-port <port>'
|
||||||
, 'port allocated to adb connect'
|
, 'port allocated to adb connect'
|
||||||
, Number)
|
, Number)
|
||||||
|
.option('--vnc-port <port>'
|
||||||
|
, 'port allocated to vnc'
|
||||||
|
, Number)
|
||||||
|
.option('--vnc-initial-size <size>'
|
||||||
|
, 'initial VNC size'
|
||||||
|
, cliutil.size
|
||||||
|
, [600, 800])
|
||||||
.option('--connect-url-pattern <pattern>'
|
.option('--connect-url-pattern <pattern>'
|
||||||
, 'adb connect URL pattern'
|
, 'adb connect URL pattern'
|
||||||
, String
|
, String
|
||||||
|
@ -172,6 +185,8 @@ program
|
||||||
, 10000)
|
, 10000)
|
||||||
.option('--mute-master'
|
.option('--mute-master'
|
||||||
, 'whether to mute master volume when devices are being used')
|
, 'whether to mute master volume when devices are being used')
|
||||||
|
.option('--lock-rotation'
|
||||||
|
, 'whether to lock rotation when devices are being used')
|
||||||
.action(function(serial, options) {
|
.action(function(serial, options) {
|
||||||
if (!options.connectSub) {
|
if (!options.connectSub) {
|
||||||
this.missingArgument('--connect-sub')
|
this.missingArgument('--connect-sub')
|
||||||
|
@ -188,6 +203,9 @@ program
|
||||||
if (!options.connectPort) {
|
if (!options.connectPort) {
|
||||||
this.missingArgument('--connect-port')
|
this.missingArgument('--connect-port')
|
||||||
}
|
}
|
||||||
|
if (!options.vncPort) {
|
||||||
|
this.missingArgument('--vnc-port')
|
||||||
|
}
|
||||||
if (!options.storageUrl) {
|
if (!options.storageUrl) {
|
||||||
this.missingArgument('--storage-url')
|
this.missingArgument('--storage-url')
|
||||||
}
|
}
|
||||||
|
@ -208,8 +226,11 @@ program
|
||||||
, screenPort: options.screenPort
|
, screenPort: options.screenPort
|
||||||
, connectUrlPattern: options.connectUrlPattern
|
, connectUrlPattern: options.connectUrlPattern
|
||||||
, connectPort: options.connectPort
|
, connectPort: options.connectPort
|
||||||
|
, vncPort: options.vncPort
|
||||||
|
, vncInitialSize: options.vncInitialSize
|
||||||
, heartbeatInterval: options.heartbeatInterval
|
, heartbeatInterval: options.heartbeatInterval
|
||||||
, muteMaster: options.muteMaster
|
, muteMaster: options.muteMaster
|
||||||
|
, lockRotation: options.lockRotation
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -866,7 +887,7 @@ program
|
||||||
})
|
})
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('local [serial..]')
|
.command('local [serial...]')
|
||||||
.description('start everything locally')
|
.description('start everything locally')
|
||||||
.option('--bind-app-pub <endpoint>'
|
.option('--bind-app-pub <endpoint>'
|
||||||
, 'app pub endpoint'
|
, 'app pub endpoint'
|
||||||
|
@ -976,6 +997,10 @@ program
|
||||||
.option('--user-profile-url <url>'
|
.option('--user-profile-url <url>'
|
||||||
, 'URL to external user profile page'
|
, 'URL to external user profile page'
|
||||||
, String)
|
, String)
|
||||||
|
.option('--vnc-initial-size <size>'
|
||||||
|
, 'initial VNC size'
|
||||||
|
, cliutil.size
|
||||||
|
, [600, 800])
|
||||||
.option('--mute-master'
|
.option('--mute-master'
|
||||||
, 'whether to mute master volume when devices are being used')
|
, 'whether to mute master volume when devices are being used')
|
||||||
.option('--use-s3'
|
.option('--use-s3'
|
||||||
|
@ -991,10 +1016,10 @@ program
|
||||||
, 's3 endpoint'
|
, 's3 endpoint'
|
||||||
, String
|
, String
|
||||||
, 's3-ap-northeast-1.amazonaws.com')
|
, 's3-ap-northeast-1.amazonaws.com')
|
||||||
.action(function() {
|
.option('--lock-rotation'
|
||||||
|
, 'whether to lock rotation when devices are being used')
|
||||||
|
.action(function(serials, options) {
|
||||||
var log = logger.createLogger('cli:local')
|
var log = logger.createLogger('cli:local')
|
||||||
, args = arguments
|
|
||||||
, options = cliutil.lastArg(args)
|
|
||||||
, procutil = require('./util/procutil')
|
, procutil = require('./util/procutil')
|
||||||
|
|
||||||
// Each forked process waits for signals to stop, and so we run over the
|
// Each forked process waits for signals to stop, and so we run over the
|
||||||
|
@ -1054,10 +1079,12 @@ program
|
||||||
, util.format('http://localhost:%d/', options.poorxyPort)
|
, util.format('http://localhost:%d/', options.poorxyPort)
|
||||||
, '--adb-host', options.adbHost
|
, '--adb-host', options.adbHost
|
||||||
, '--adb-port', options.adbPort
|
, '--adb-port', options.adbPort
|
||||||
|
, '--vnc-initial-size', options.vncInitialSize.join('x')
|
||||||
]
|
]
|
||||||
.concat(options.allowRemote ? ['--allow-remote'] : [])
|
.concat(options.allowRemote ? ['--allow-remote'] : [])
|
||||||
.concat(options.muteMaster ? ['--mute-master'] : [])
|
.concat(options.muteMaster ? ['--mute-master'] : [])
|
||||||
.concat(cliutil.allUnknownArgs(args)))
|
.concat(options.lockRotation ? ['--lock-rotation'] : [])
|
||||||
|
.concat(serials))
|
||||||
|
|
||||||
// auth
|
// auth
|
||||||
, procutil.fork(__filename, [
|
, procutil.fork(__filename, [
|
||||||
|
|
|
@ -100,6 +100,27 @@ dbapi.lookupUserByAdbFingerprint = function(fingerprint) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbapi.lookupUserByVncAuthResponse = function(response, serial) {
|
||||||
|
return db.run(r.table('vncauth').getAll([response, serial], {
|
||||||
|
index: 'responsePerDevice'
|
||||||
|
})
|
||||||
|
.eqJoin('userId', r.table('users'))('right')
|
||||||
|
.pluck('email', 'name', 'group'))
|
||||||
|
.then(function(cursor) {
|
||||||
|
return cursor.toArray()
|
||||||
|
})
|
||||||
|
.then(function(groups) {
|
||||||
|
switch (groups.length) {
|
||||||
|
case 1:
|
||||||
|
return groups[0]
|
||||||
|
case 0:
|
||||||
|
return null
|
||||||
|
default:
|
||||||
|
throw new Error('Found multiple users with the same VNC response')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
dbapi.loadGroup = function(email) {
|
dbapi.loadGroup = function(email) {
|
||||||
return db.run(r.table('devices').getAll(email, {
|
return db.run(r.table('devices').getAll(email, {
|
||||||
index: 'owner'
|
index: 'owner'
|
||||||
|
|
|
@ -14,6 +14,17 @@ module.exports = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
, vncauth: {
|
||||||
|
primaryKey: 'password'
|
||||||
|
, indexes: {
|
||||||
|
response: null
|
||||||
|
, responsePerDevice: {
|
||||||
|
indexFunction: function(row) {
|
||||||
|
return [row('response'), row('deviceId')]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
, devices: {
|
, devices: {
|
||||||
primaryKey: 'serial'
|
primaryKey: 'serial'
|
||||||
, indexes: {
|
, indexes: {
|
||||||
|
|
|
@ -5,7 +5,7 @@ var webpack = require('webpack')
|
||||||
var mime = require('mime')
|
var mime = require('mime')
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var _ = require('lodash')
|
var _ = require('lodash')
|
||||||
var MemoryFileSystem = require('webpack/node_modules/memory-fs')
|
var MemoryFileSystem = require('memory-fs')
|
||||||
|
|
||||||
var logger = require('../../../util/logger')
|
var logger = require('../../../util/logger')
|
||||||
var lifecycle = require('../../../util/lifecycle')
|
var lifecycle = require('../../../util/lifecycle')
|
||||||
|
|
|
@ -20,6 +20,7 @@ module.exports = function(options) {
|
||||||
.dependency(require('./plugins/solo'))
|
.dependency(require('./plugins/solo'))
|
||||||
.dependency(require('./plugins/screen/stream'))
|
.dependency(require('./plugins/screen/stream'))
|
||||||
.dependency(require('./plugins/screen/capture'))
|
.dependency(require('./plugins/screen/capture'))
|
||||||
|
.dependency(require('./plugins/vnc'))
|
||||||
.dependency(require('./plugins/service'))
|
.dependency(require('./plugins/service'))
|
||||||
.dependency(require('./plugins/browser'))
|
.dependency(require('./plugins/browser'))
|
||||||
.dependency(require('./plugins/store'))
|
.dependency(require('./plugins/store'))
|
||||||
|
@ -38,6 +39,7 @@ module.exports = function(options) {
|
||||||
.dependency(require('./plugins/ringer'))
|
.dependency(require('./plugins/ringer'))
|
||||||
.dependency(require('./plugins/wifi'))
|
.dependency(require('./plugins/wifi'))
|
||||||
.dependency(require('./plugins/sd'))
|
.dependency(require('./plugins/sd'))
|
||||||
|
.dependency(require('./plugins/filesystem'))
|
||||||
.define(function(options, heartbeat, solo) {
|
.define(function(options, heartbeat, solo) {
|
||||||
if (process.send) {
|
if (process.send) {
|
||||||
// Only if we have a parent process
|
// Only if we have a parent process
|
||||||
|
|
74
lib/units/device/plugins/filesystem.js
Normal file
74
lib/units/device/plugins/filesystem.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
var syrup = require('stf-syrup')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
var logger = require('../../../util/logger')
|
||||||
|
var wire = require('../../../wire')
|
||||||
|
var wireutil = require('../../../wire/util')
|
||||||
|
|
||||||
|
module.exports = syrup.serial()
|
||||||
|
.dependency(require('../support/adb'))
|
||||||
|
.dependency(require('../support/router'))
|
||||||
|
.dependency(require('../support/push'))
|
||||||
|
.dependency(require('../support/storage'))
|
||||||
|
.define(function(options, adb, router, push, storage) {
|
||||||
|
var log = logger.createLogger('device:plugins:filesystem')
|
||||||
|
var plugin = Object.create(null)
|
||||||
|
|
||||||
|
plugin.retrieve = function(file) {
|
||||||
|
log.info('Retrieving file "%s"', file)
|
||||||
|
|
||||||
|
return adb.stat(options.serial, file)
|
||||||
|
.then(function(stats) {
|
||||||
|
return adb.pull(options.serial, file)
|
||||||
|
.then(function(transfer) {
|
||||||
|
// We may have add new storage plugins for various file types
|
||||||
|
// in the future, and add proper detection for the mimetype.
|
||||||
|
// But for now, let's just use application/octet-stream for
|
||||||
|
// everything like it's 2001.
|
||||||
|
return storage.store('blob', transfer, {
|
||||||
|
filename: path.basename(file)
|
||||||
|
, contentType: 'application/octet-stream'
|
||||||
|
, knownLength: stats.size
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
router.on(wire.FileSystemGetMessage, function(channel, message) {
|
||||||
|
var reply = wireutil.reply(options.serial)
|
||||||
|
plugin.retrieve(message.file)
|
||||||
|
.then(function(file) {
|
||||||
|
push.send([
|
||||||
|
channel
|
||||||
|
, reply.okay('success', file)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.warn('Unable to retrieve "%s"', message.file, err.stack)
|
||||||
|
push.send([
|
||||||
|
channel
|
||||||
|
, reply.fail(err.message)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.on(wire.FileSystemListMessage, function(channel, message) {
|
||||||
|
var reply = wireutil.reply(options.serial)
|
||||||
|
adb.readdir(options.serial, message.dir)
|
||||||
|
.then(function(files) {
|
||||||
|
push.send([
|
||||||
|
channel
|
||||||
|
, reply.okay('success', files)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.warn('Unable to list directory "%s"', message.dir, err.stack)
|
||||||
|
push.send([
|
||||||
|
channel
|
||||||
|
, reply.fail(err.message)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugin
|
||||||
|
})
|
|
@ -104,6 +104,7 @@ module.exports = syrup.serial()
|
||||||
|
|
||||||
plugin.on('leave', function() {
|
plugin.on('leave', function() {
|
||||||
service.pressKey('home')
|
service.pressKey('home')
|
||||||
|
service.thawRotation()
|
||||||
service.releaseWakeLock()
|
service.releaseWakeLock()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ module.exports = syrup.serial()
|
||||||
.dependency(require('./options'))
|
.dependency(require('./options'))
|
||||||
.define(function(options, adb, minicap, display, screenOptions) {
|
.define(function(options, adb, minicap, display, screenOptions) {
|
||||||
var log = logger.createLogger('device:plugins:screen:stream')
|
var log = logger.createLogger('device:plugins:screen:stream')
|
||||||
var plugin = Object.create(null)
|
|
||||||
|
|
||||||
function FrameProducer(config) {
|
function FrameProducer(config) {
|
||||||
EventEmitter.call(this)
|
EventEmitter.call(this)
|
||||||
|
@ -443,9 +442,9 @@ module.exports = syrup.serial()
|
||||||
|
|
||||||
return createServer()
|
return createServer()
|
||||||
.then(function(wss) {
|
.then(function(wss) {
|
||||||
var broadcastSet = new BroadcastSet()
|
|
||||||
var frameProducer = new FrameProducer(
|
var frameProducer = new FrameProducer(
|
||||||
new FrameConfig(display.properties, display.properties))
|
new FrameConfig(display.properties, display.properties))
|
||||||
|
var broadcastSet = frameProducer.broadcastSet = new BroadcastSet()
|
||||||
|
|
||||||
broadcastSet.on('nonempty', function() {
|
broadcastSet.on('nonempty', function() {
|
||||||
frameProducer.start()
|
frameProducer.start()
|
||||||
|
@ -455,37 +454,26 @@ module.exports = syrup.serial()
|
||||||
frameProducer.stop()
|
frameProducer.stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
broadcastSet.on('insert', function(id) {
|
||||||
|
// If two clients join a session in the middle, one of them
|
||||||
|
// may not release the initial size because the projection
|
||||||
|
// doesn't necessarily change, and the producer doesn't Getting
|
||||||
|
// restarted. Therefore we have to call onStart() manually
|
||||||
|
// if the producer is already up and running.
|
||||||
|
switch (frameProducer.runningState) {
|
||||||
|
case FrameProducer.STATE_STARTED:
|
||||||
|
broadcastSet.get(id).onStart(frameProducer)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
display.on('rotationChange', function(newRotation) {
|
display.on('rotationChange', function(newRotation) {
|
||||||
frameProducer.updateRotation(newRotation)
|
frameProducer.updateRotation(newRotation)
|
||||||
})
|
})
|
||||||
|
|
||||||
frameProducer.on('start', function() {
|
frameProducer.on('start', function() {
|
||||||
var message = util.format(
|
broadcastSet.keys().map(function(id) {
|
||||||
'start %s'
|
return broadcastSet.get(id).onStart(frameProducer)
|
||||||
, JSON.stringify(frameProducer.banner)
|
|
||||||
)
|
|
||||||
|
|
||||||
broadcastSet.keys().forEach(function(id) {
|
|
||||||
var ws = broadcastSet.get(id)
|
|
||||||
switch (ws.readyState) {
|
|
||||||
case WebSocket.OPENING:
|
|
||||||
// This should never happen.
|
|
||||||
log.warn('Unable to send banner to OPENING client "%s"', id)
|
|
||||||
break
|
|
||||||
case WebSocket.OPEN:
|
|
||||||
// This is what SHOULD happen.
|
|
||||||
ws.send(message)
|
|
||||||
break
|
|
||||||
case WebSocket.CLOSING:
|
|
||||||
// Ok, a 'close' event should remove the client from the set
|
|
||||||
// soon.
|
|
||||||
break
|
|
||||||
case WebSocket.CLOSED:
|
|
||||||
// This should never happen.
|
|
||||||
log.warn('Unable to send banner to CLOSED client "%s"', id)
|
|
||||||
broadcastSet.remove(id)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -493,32 +481,7 @@ module.exports = syrup.serial()
|
||||||
var frame
|
var frame
|
||||||
if ((frame = frameProducer.nextFrame())) {
|
if ((frame = frameProducer.nextFrame())) {
|
||||||
Promise.settle([broadcastSet.keys().map(function(id) {
|
Promise.settle([broadcastSet.keys().map(function(id) {
|
||||||
return new Promise(function(resolve, reject) {
|
return broadcastSet.get(id).onFrame(frame)
|
||||||
var ws = broadcastSet.get(id)
|
|
||||||
switch (ws.readyState) {
|
|
||||||
case WebSocket.OPENING:
|
|
||||||
// This should never happen.
|
|
||||||
return reject(new Error(util.format(
|
|
||||||
'Unable to send frame to OPENING client "%s"', id)))
|
|
||||||
case WebSocket.OPEN:
|
|
||||||
// This is what SHOULD happen.
|
|
||||||
ws.send(frame, {
|
|
||||||
binary: true
|
|
||||||
}, function(err) {
|
|
||||||
return err ? reject(err) : resolve()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
case WebSocket.CLOSING:
|
|
||||||
// Ok, a 'close' event should remove the client from the set
|
|
||||||
// soon.
|
|
||||||
return
|
|
||||||
case WebSocket.CLOSED:
|
|
||||||
// This should never happen.
|
|
||||||
broadcastSet.remove(id)
|
|
||||||
return reject(new Error(util.format(
|
|
||||||
'Unable to send frame to CLOSED client "%s"', id)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})]).then(next)
|
})]).then(next)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -534,12 +497,74 @@ module.exports = syrup.serial()
|
||||||
wss.on('connection', function(ws) {
|
wss.on('connection', function(ws) {
|
||||||
var id = uuid.v4()
|
var id = uuid.v4()
|
||||||
|
|
||||||
|
function wsStartNotifier() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
var message = util.format(
|
||||||
|
'start %s'
|
||||||
|
, JSON.stringify(frameProducer.banner)
|
||||||
|
)
|
||||||
|
|
||||||
|
switch (ws.readyState) {
|
||||||
|
case WebSocket.OPENING:
|
||||||
|
// This should never happen.
|
||||||
|
log.warn('Unable to send banner to OPENING client "%s"', id)
|
||||||
|
break
|
||||||
|
case WebSocket.OPEN:
|
||||||
|
// This is what SHOULD happen.
|
||||||
|
ws.send(message, function(err) {
|
||||||
|
return err ? reject(err) : resolve()
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case WebSocket.CLOSING:
|
||||||
|
// Ok, a 'close' event should remove the client from the set
|
||||||
|
// soon.
|
||||||
|
break
|
||||||
|
case WebSocket.CLOSED:
|
||||||
|
// This should never happen.
|
||||||
|
log.warn('Unable to send banner to CLOSED client "%s"', id)
|
||||||
|
broadcastSet.remove(id)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsFrameNotifier(frame) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
switch (ws.readyState) {
|
||||||
|
case WebSocket.OPENING:
|
||||||
|
// This should never happen.
|
||||||
|
return reject(new Error(util.format(
|
||||||
|
'Unable to send frame to OPENING client "%s"', id)))
|
||||||
|
case WebSocket.OPEN:
|
||||||
|
// This is what SHOULD happen.
|
||||||
|
ws.send(frame, {
|
||||||
|
binary: true
|
||||||
|
}, function(err) {
|
||||||
|
return err ? reject(err) : resolve()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
case WebSocket.CLOSING:
|
||||||
|
// Ok, a 'close' event should remove the client from the set
|
||||||
|
// soon.
|
||||||
|
return
|
||||||
|
case WebSocket.CLOSED:
|
||||||
|
// This should never happen.
|
||||||
|
broadcastSet.remove(id)
|
||||||
|
return reject(new Error(util.format(
|
||||||
|
'Unable to send frame to CLOSED client "%s"', id)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ws.on('message', function(data) {
|
ws.on('message', function(data) {
|
||||||
var match
|
var match
|
||||||
if ((match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data))) {
|
if ((match = /^(on|off|(size) ([0-9]+)x([0-9]+))$/.exec(data))) {
|
||||||
switch (match[2] || match[1]) {
|
switch (match[2] || match[1]) {
|
||||||
case 'on':
|
case 'on':
|
||||||
broadcastSet.insert(id, ws)
|
broadcastSet.insert(id, {
|
||||||
|
onStart: wsStartNotifier
|
||||||
|
, onFrame: wsFrameNotifier
|
||||||
|
})
|
||||||
break
|
break
|
||||||
case 'off':
|
case 'off':
|
||||||
broadcastSet.remove(id)
|
broadcastSet.remove(id)
|
||||||
|
@ -563,6 +588,7 @@ module.exports = syrup.serial()
|
||||||
lifecycle.observe(function() {
|
lifecycle.observe(function() {
|
||||||
frameProducer.stop()
|
frameProducer.stop()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return frameProducer
|
||||||
})
|
})
|
||||||
.return(plugin)
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -365,7 +365,7 @@ module.exports = syrup.serial()
|
||||||
plugin.rotate = function(rotation) {
|
plugin.rotate = function(rotation) {
|
||||||
return runAgentCommand(
|
return runAgentCommand(
|
||||||
apk.wire.MessageType.SET_ROTATION
|
apk.wire.MessageType.SET_ROTATION
|
||||||
, new apk.wire.SetRotationRequest(rotation, false)
|
, new apk.wire.SetRotationRequest(rotation, options.lockRotation || false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
290
lib/units/device/plugins/vnc/index.js
Normal file
290
lib/units/device/plugins/vnc/index.js
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
var net = require('net')
|
||||||
|
var util = require('util')
|
||||||
|
var os = require('os')
|
||||||
|
|
||||||
|
var syrup = require('stf-syrup')
|
||||||
|
var Promise = require('bluebird')
|
||||||
|
var uuid = require('node-uuid')
|
||||||
|
var jpeg = require('jpeg-turbo')
|
||||||
|
|
||||||
|
var logger = require('../../../../util/logger')
|
||||||
|
var grouputil = require('../../../../util/grouputil')
|
||||||
|
var wire = require('../../../../wire')
|
||||||
|
var wireutil = require('../../../../wire/util')
|
||||||
|
var lifecycle = require('../../../../util/lifecycle')
|
||||||
|
|
||||||
|
var VncServer = require('./util/server')
|
||||||
|
var VncConnection = require('./util/connection')
|
||||||
|
var PointerTranslator = require('./util/pointertranslator')
|
||||||
|
|
||||||
|
module.exports = syrup.serial()
|
||||||
|
.dependency(require('../../support/router'))
|
||||||
|
.dependency(require('../../support/push'))
|
||||||
|
.dependency(require('../screen/stream'))
|
||||||
|
.dependency(require('../touch'))
|
||||||
|
.dependency(require('../group'))
|
||||||
|
.dependency(require('../solo'))
|
||||||
|
.define(function(options, router, push, screenStream, touch, group, solo) {
|
||||||
|
var log = logger.createLogger('device:plugins:vnc')
|
||||||
|
|
||||||
|
function vncAuthHandler(data) {
|
||||||
|
log.info(
|
||||||
|
'VNC authentication attempt using "%s"'
|
||||||
|
, data.response.toString('hex')
|
||||||
|
)
|
||||||
|
|
||||||
|
var resolver = Promise.defer()
|
||||||
|
|
||||||
|
function notify() {
|
||||||
|
group.get()
|
||||||
|
.then(function(currentGroup) {
|
||||||
|
push.send([
|
||||||
|
solo.channel
|
||||||
|
, wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(
|
||||||
|
options.serial
|
||||||
|
, data.response.toString('hex')
|
||||||
|
, currentGroup.group
|
||||||
|
))
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.catch(grouputil.NoGroupError, function() {
|
||||||
|
push.send([
|
||||||
|
solo.channel
|
||||||
|
, wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(
|
||||||
|
options.serial
|
||||||
|
, data.response.toString('hex')
|
||||||
|
))
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinListener(newGroup, identifier) {
|
||||||
|
if (!data.response.equals(new Buffer(identifier || '', 'hex'))) {
|
||||||
|
resolver.reject(new Error('Someone else took the device'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function autojoinListener(identifier, joined) {
|
||||||
|
if (data.response.equals(new Buffer(identifier, 'hex'))) {
|
||||||
|
if (joined) {
|
||||||
|
resolver.resolve()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolver.reject(new Error('Device is already in use'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.on('join', joinListener)
|
||||||
|
group.on('autojoin', autojoinListener)
|
||||||
|
router.on(wire.VncAuthResponsesUpdatedMessage, notify)
|
||||||
|
|
||||||
|
notify()
|
||||||
|
|
||||||
|
return resolver.promise
|
||||||
|
.timeout(5000)
|
||||||
|
.finally(function() {
|
||||||
|
group.removeListener('join', joinListener)
|
||||||
|
group.removeListener('autojoin', autojoinListener)
|
||||||
|
router.removeListener(wire.VncAuthResponsesUpdatedMessage, notify)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function createServer() {
|
||||||
|
log.info('Starting VNC server on port %d', options.vncPort)
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
name: options.serial
|
||||||
|
, width: options.vncInitialSize[0]
|
||||||
|
, height: options.vncInitialSize[1]
|
||||||
|
, security: [{
|
||||||
|
type: VncConnection.SECURITY_VNC
|
||||||
|
, challenge: new Buffer(16).fill(0)
|
||||||
|
, auth: vncAuthHandler
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
var vnc = new VncServer(net.createServer({
|
||||||
|
allowHalfOpen: true
|
||||||
|
}), opts)
|
||||||
|
|
||||||
|
var listeningListener, errorListener
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
listeningListener = function() {
|
||||||
|
return resolve(vnc)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorListener = function(err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vnc.on('listening', listeningListener)
|
||||||
|
vnc.on('error', errorListener)
|
||||||
|
|
||||||
|
vnc.listen(options.vncPort)
|
||||||
|
})
|
||||||
|
.finally(function() {
|
||||||
|
vnc.removeListener('listening', listeningListener)
|
||||||
|
vnc.removeListener('error', errorListener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return createServer()
|
||||||
|
.then(function(vnc) {
|
||||||
|
vnc.on('connection', function(conn) {
|
||||||
|
log.info('New VNC connection from %s', conn.conn.remoteAddress)
|
||||||
|
|
||||||
|
var id = util.format('vnc-%s', uuid.v4())
|
||||||
|
|
||||||
|
var connState = {
|
||||||
|
lastFrame: null
|
||||||
|
, lastFrameTime: null
|
||||||
|
, frameWidth: 0
|
||||||
|
, frameHeight: 0
|
||||||
|
, sentFrameTime: null
|
||||||
|
, updateRequests: 0
|
||||||
|
, frameConfig: {
|
||||||
|
format: jpeg.FORMAT_RGB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pointerTranslator = new PointerTranslator()
|
||||||
|
|
||||||
|
pointerTranslator.on('touchdown', function(event) {
|
||||||
|
touch.touchDown(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
pointerTranslator.on('touchmove', function(event) {
|
||||||
|
touch.touchMove(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
pointerTranslator.on('touchup', function(event) {
|
||||||
|
touch.touchUp(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
pointerTranslator.on('touchcommit', function() {
|
||||||
|
touch.touchCommit()
|
||||||
|
})
|
||||||
|
|
||||||
|
function maybeSendFrame() {
|
||||||
|
if (!connState.updateRequests) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connState.lastFrame) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connState.lastFrameTime === connState.sentFrameTime) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var decoded = jpeg.decompressSync(
|
||||||
|
connState.lastFrame, connState.frameConfig)
|
||||||
|
|
||||||
|
conn.writeFramebufferUpdate([
|
||||||
|
{ xPosition: 0
|
||||||
|
, yPosition: 0
|
||||||
|
, width: decoded.width
|
||||||
|
, height: decoded.height
|
||||||
|
, encodingType: VncConnection.ENCODING_RAW
|
||||||
|
, data: decoded.data
|
||||||
|
}
|
||||||
|
, { xPosition: 0
|
||||||
|
, yPosition: 0
|
||||||
|
, width: decoded.width
|
||||||
|
, height: decoded.height
|
||||||
|
, encodingType: VncConnection.ENCODING_DESKTOPSIZE
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
connState.updateRequests = 0
|
||||||
|
connState.sentFrameTime = connState.lastFrameTime
|
||||||
|
}
|
||||||
|
|
||||||
|
function vncStartListener(frameProducer) {
|
||||||
|
return new Promise(function(resolve/*, reject*/) {
|
||||||
|
connState.frameWidth = frameProducer.banner.virtualWidth
|
||||||
|
connState.frameHeight = frameProducer.banner.virtualHeight
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function vncFrameListener(frame) {
|
||||||
|
return new Promise(function(resolve/*, reject*/) {
|
||||||
|
connState.lastFrame = frame
|
||||||
|
connState.lastFrameTime = Date.now()
|
||||||
|
maybeSendFrame()
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupLeaveListener() {
|
||||||
|
conn.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.on('authenticated', function() {
|
||||||
|
screenStream.updateProjection(
|
||||||
|
options.vncInitialSize[0], options.vncInitialSize[1])
|
||||||
|
screenStream.broadcastSet.insert(id, {
|
||||||
|
onStart: vncStartListener
|
||||||
|
, onFrame: vncFrameListener
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('fbupdaterequest', function() {
|
||||||
|
connState.updateRequests += 1
|
||||||
|
maybeSendFrame()
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('formatchange', function(format) {
|
||||||
|
var same = os.endianness() === 'BE'
|
||||||
|
=== Boolean(format.bigEndianFlag)
|
||||||
|
switch (format.bitsPerPixel) {
|
||||||
|
case 8:
|
||||||
|
connState.frameConfig = {
|
||||||
|
format: jpeg.FORMAT_GRAY
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 24:
|
||||||
|
connState.frameConfig = {
|
||||||
|
format: ((format.redShift > format.blueShift) === same)
|
||||||
|
? jpeg.FORMAT_BGR
|
||||||
|
: jpeg.FORMAT_RGB
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 32:
|
||||||
|
connState.frameConfig = {
|
||||||
|
format: ((format.redShift > format.blueShift) === same)
|
||||||
|
? (format.blueShift === 0
|
||||||
|
? jpeg.FORMAT_BGRX
|
||||||
|
: jpeg.FORMAT_XBGR)
|
||||||
|
: (format.redShift === 0
|
||||||
|
? jpeg.FORMAT_RGBX
|
||||||
|
: jpeg.FORMAT_XRGB)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('pointer', function(event) {
|
||||||
|
pointerTranslator.push(event)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('close', function() {
|
||||||
|
screenStream.broadcastSet.remove(id)
|
||||||
|
group.removeListener('leave', groupLeaveListener)
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('userActivity', function() {
|
||||||
|
group.keepalive()
|
||||||
|
})
|
||||||
|
|
||||||
|
group.on('leave', groupLeaveListener)
|
||||||
|
})
|
||||||
|
|
||||||
|
lifecycle.observe(function() {
|
||||||
|
vnc.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
535
lib/units/device/plugins/vnc/util/connection.js
Normal file
535
lib/units/device/plugins/vnc/util/connection.js
Normal file
|
@ -0,0 +1,535 @@
|
||||||
|
var util = require('util')
|
||||||
|
var os = require('os')
|
||||||
|
var crypto = require('crypto')
|
||||||
|
|
||||||
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
var debug = require('debug')('vnc:connection')
|
||||||
|
var Promise = require('bluebird')
|
||||||
|
|
||||||
|
var PixelFormat = require('./pixelformat')
|
||||||
|
|
||||||
|
function VncConnection(conn, options) {
|
||||||
|
this.options = options
|
||||||
|
|
||||||
|
this._bound = {
|
||||||
|
_errorListener: this._errorListener.bind(this)
|
||||||
|
, _readableListener: this._readableListener.bind(this)
|
||||||
|
, _endListener: this._endListener.bind(this)
|
||||||
|
, _closeListener: this._closeListener.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._buffer = null
|
||||||
|
this._state = 0
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_VERSION)
|
||||||
|
|
||||||
|
this._serverVersion = VncConnection.V3_008
|
||||||
|
this._serverSupportedSecurity = this.options.security
|
||||||
|
this._serverSupportedSecurityByType =
|
||||||
|
this.options.security.reduce(
|
||||||
|
function(map, method) {
|
||||||
|
map[method.type] = method
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
, Object.create(null)
|
||||||
|
)
|
||||||
|
this._serverWidth = this.options.width
|
||||||
|
this._serverHeight = this.options.height
|
||||||
|
this._serverPixelFormat = new PixelFormat({
|
||||||
|
bitsPerPixel: 32
|
||||||
|
, depth: 24
|
||||||
|
, bigEndianFlag: os.endianness() === 'BE' ? 1 : 0
|
||||||
|
, trueColorFlag: 1
|
||||||
|
, redMax: 255
|
||||||
|
, greenMax: 255
|
||||||
|
, blueMax: 255
|
||||||
|
, redShift: 16
|
||||||
|
, greenShift: 8
|
||||||
|
, blueShift: 0
|
||||||
|
})
|
||||||
|
this._serverName = this.options.name
|
||||||
|
|
||||||
|
this._clientVersion = null
|
||||||
|
this._clientShare = false
|
||||||
|
this._clientPixelFormat = this._serverPixelFormat
|
||||||
|
this._clientEncodingCount = 0
|
||||||
|
this._clientEncodings = []
|
||||||
|
this._clientCutTextLength = 0
|
||||||
|
|
||||||
|
this._authChallenge = this.options.challenge || crypto.randomBytes(16)
|
||||||
|
|
||||||
|
this.conn = conn
|
||||||
|
.on('error', this._bound._errorListener)
|
||||||
|
.on('readable', this._bound._readableListener)
|
||||||
|
.on('end', this._bound._endListener)
|
||||||
|
.on('close', this._bound._closeListener)
|
||||||
|
|
||||||
|
this._blockingOps = []
|
||||||
|
|
||||||
|
this._writeServerVersion()
|
||||||
|
this._read()
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(VncConnection, EventEmitter)
|
||||||
|
|
||||||
|
VncConnection.V3_003 = 3003
|
||||||
|
VncConnection.V3_007 = 3007
|
||||||
|
VncConnection.V3_008 = 3008
|
||||||
|
|
||||||
|
VncConnection.SECURITY_NONE = 1
|
||||||
|
VncConnection.SECURITY_VNC = 2
|
||||||
|
|
||||||
|
VncConnection.SECURITYRESULT_OK = 0
|
||||||
|
VncConnection.SECURITYRESULT_FAIL = 1
|
||||||
|
|
||||||
|
VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT = 0
|
||||||
|
VncConnection.CLIENT_MESSAGE_SETENCODINGS = 2
|
||||||
|
VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST = 3
|
||||||
|
VncConnection.CLIENT_MESSAGE_KEYEVENT = 4
|
||||||
|
VncConnection.CLIENT_MESSAGE_POINTEREVENT = 5
|
||||||
|
VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT = 6
|
||||||
|
|
||||||
|
VncConnection.SERVER_MESSAGE_FBUPDATE = 0
|
||||||
|
|
||||||
|
var StateReverse = Object.create(null), State = {
|
||||||
|
STATE_NEED_CLIENT_VERSION: 10
|
||||||
|
, STATE_NEED_CLIENT_SECURITY: 20
|
||||||
|
, STATE_NEED_CLIENT_INIT: 30
|
||||||
|
, STATE_NEED_CLIENT_VNC_AUTH: 31
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE: 40
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT: 50
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS: 60
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE: 61
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST: 70
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_KEYEVENT: 80
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_POINTEREVENT: 90
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT: 100
|
||||||
|
, STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE: 101
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.ENCODING_RAW = 0
|
||||||
|
VncConnection.ENCODING_DESKTOPSIZE = -223
|
||||||
|
|
||||||
|
Object.keys(State).map(function(name) {
|
||||||
|
VncConnection[name] = State[name]
|
||||||
|
StateReverse[State[name]] = name
|
||||||
|
})
|
||||||
|
|
||||||
|
VncConnection.prototype.end = function() {
|
||||||
|
this.conn.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype.writeFramebufferUpdate = function(rectangles) {
|
||||||
|
var chunk = new Buffer(4)
|
||||||
|
chunk[0] = VncConnection.SERVER_MESSAGE_FBUPDATE
|
||||||
|
chunk[1] = 0
|
||||||
|
chunk.writeUInt16BE(rectangles.length, 2)
|
||||||
|
this._write(chunk)
|
||||||
|
|
||||||
|
rectangles.forEach(function(rect) {
|
||||||
|
var rchunk = new Buffer(12)
|
||||||
|
rchunk.writeUInt16BE(rect.xPosition, 0)
|
||||||
|
rchunk.writeUInt16BE(rect.yPosition, 2)
|
||||||
|
rchunk.writeUInt16BE(rect.width, 4)
|
||||||
|
rchunk.writeUInt16BE(rect.height, 6)
|
||||||
|
rchunk.writeInt32BE(rect.encodingType, 8)
|
||||||
|
this._write(rchunk)
|
||||||
|
|
||||||
|
switch (rect.encodingType) {
|
||||||
|
case VncConnection.ENCODING_RAW:
|
||||||
|
this._write(rect.data)
|
||||||
|
break
|
||||||
|
case VncConnection.ENCODING_DESKTOPSIZE:
|
||||||
|
this._serverWidth = rect.width
|
||||||
|
this._serverHeight = rect.height
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(util.format(
|
||||||
|
'Unsupported encoding type', rect.encodingType))
|
||||||
|
}
|
||||||
|
}, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._error = function(err) {
|
||||||
|
this.emit('error', err)
|
||||||
|
this.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._errorListener = function(err) {
|
||||||
|
this._error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._endListener = function() {
|
||||||
|
this.emit('end')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._closeListener = function() {
|
||||||
|
this.emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeServerVersion = function() {
|
||||||
|
// Yes, we could just format the string instead. Didn't feel like it.
|
||||||
|
switch (this._serverVersion) {
|
||||||
|
case VncConnection.V3_003:
|
||||||
|
this._write(new Buffer('RFB 003.003\n'))
|
||||||
|
break
|
||||||
|
case VncConnection.V3_007:
|
||||||
|
this._write(new Buffer('RFB 003.007\n'))
|
||||||
|
break
|
||||||
|
case VncConnection.V3_008:
|
||||||
|
this._write(new Buffer('RFB 003.008\n'))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeSupportedSecurity = function() {
|
||||||
|
var chunk = new Buffer(1 + this._serverSupportedSecurity.length)
|
||||||
|
|
||||||
|
chunk[0] = this._serverSupportedSecurity.length
|
||||||
|
this._serverSupportedSecurity.forEach(function(security, i) {
|
||||||
|
chunk[1 + i] = security.type
|
||||||
|
})
|
||||||
|
|
||||||
|
this._write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeSecurityResult = function(result, reason) {
|
||||||
|
var chunk
|
||||||
|
switch (result) {
|
||||||
|
case VncConnection.SECURITYRESULT_OK:
|
||||||
|
chunk = new Buffer(4)
|
||||||
|
chunk.writeUInt32BE(result, 0)
|
||||||
|
this._write(chunk)
|
||||||
|
break
|
||||||
|
case VncConnection.SECURITYRESULT_FAIL:
|
||||||
|
chunk = new Buffer(4 + 4 + reason.length)
|
||||||
|
chunk.writeUInt32BE(result, 0)
|
||||||
|
chunk.writeUInt32BE(reason.length, 4)
|
||||||
|
chunk.write(reason, 8, reason.length)
|
||||||
|
this._write(chunk)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeServerInit = function() {
|
||||||
|
debug('server pixel format', this._serverPixelFormat)
|
||||||
|
var chunk = new Buffer(2 + 2 + 16 + 4 + this._serverName.length)
|
||||||
|
chunk.writeUInt16BE(this._serverWidth, 0)
|
||||||
|
chunk.writeUInt16BE(this._serverHeight, 2)
|
||||||
|
chunk[4] = this._serverPixelFormat.bitsPerPixel
|
||||||
|
chunk[5] = this._serverPixelFormat.depth
|
||||||
|
chunk[6] = this._serverPixelFormat.bigEndianFlag
|
||||||
|
chunk[7] = this._serverPixelFormat.trueColorFlag
|
||||||
|
chunk.writeUInt16BE(this._serverPixelFormat.redMax, 8)
|
||||||
|
chunk.writeUInt16BE(this._serverPixelFormat.greenMax, 10)
|
||||||
|
chunk.writeUInt16BE(this._serverPixelFormat.blueMax, 12)
|
||||||
|
chunk[14] = this._serverPixelFormat.redShift
|
||||||
|
chunk[15] = this._serverPixelFormat.greenShift
|
||||||
|
chunk[16] = this._serverPixelFormat.blueShift
|
||||||
|
chunk[17] = 0 // padding
|
||||||
|
chunk[18] = 0 // padding
|
||||||
|
chunk[19] = 0 // padding
|
||||||
|
chunk.writeUInt32BE(this._serverName.length, 20)
|
||||||
|
chunk.write(this._serverName, 24, this._serverName.length)
|
||||||
|
this._write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._writeVncAuthChallenge = function() {
|
||||||
|
var vncSec = this._serverSupportedSecurityByType[VncConnection.SECURITY_VNC]
|
||||||
|
debug('vnc auth challenge', vncSec.challenge)
|
||||||
|
this._write(vncSec.challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._readableListener = function() {
|
||||||
|
this._read()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._read = function() {
|
||||||
|
Promise.all(this._blockingOps).bind(this)
|
||||||
|
.then(this._unguardedRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._auth = function(type, data) {
|
||||||
|
var security = this._serverSupportedSecurityByType[type]
|
||||||
|
this._blockingOps.push(
|
||||||
|
security.auth(data).bind(this)
|
||||||
|
.then(function() {
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_INIT)
|
||||||
|
this._writeSecurityResult(VncConnection.SECURITYRESULT_OK)
|
||||||
|
this.emit('authenticated')
|
||||||
|
this._read()
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
this._writeSecurityResult(
|
||||||
|
VncConnection.SECURITYRESULT_FAIL, 'Authentication failure')
|
||||||
|
this.end()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._unguardedRead = function() {
|
||||||
|
var chunk, lo, hi
|
||||||
|
while (this._append(this.conn.read())) {
|
||||||
|
do {
|
||||||
|
debug('state', StateReverse[this._state])
|
||||||
|
chunk = null
|
||||||
|
switch (this._state) {
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_VERSION:
|
||||||
|
if ((chunk = this._consume(12))) {
|
||||||
|
if ((this._clientVersion = this._parseVersion(chunk)) === null) {
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug('client version', this._clientVersion)
|
||||||
|
this._writeSupportedSecurity()
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_SECURITY)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_SECURITY:
|
||||||
|
if ((chunk = this._consume(1))) {
|
||||||
|
if ((this._clientSecurity = this._parseSecurity(chunk)) === null) {
|
||||||
|
this._writeSecurityResult(
|
||||||
|
VncConnection.SECURITYRESULT_FAIL, 'Unimplemented security type')
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug('client security', this._clientSecurity)
|
||||||
|
if (!(this._clientSecurity in this._serverSupportedSecurityByType)) {
|
||||||
|
this._writeSecurityResult(
|
||||||
|
VncConnection.SECURITYRESULT_FAIL, 'Unsupported security type')
|
||||||
|
this.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (this._clientSecurity) {
|
||||||
|
case VncConnection.SECURITY_NONE:
|
||||||
|
this._auth(VncConnection.SECURITY_NONE)
|
||||||
|
return
|
||||||
|
case VncConnection.SECURITY_VNC:
|
||||||
|
this._writeVncAuthChallenge()
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_VNC_AUTH)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_VNC_AUTH:
|
||||||
|
if ((chunk = this._consume(16))) {
|
||||||
|
this._auth(VncConnection.SECURITY_VNC, {
|
||||||
|
response: chunk
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_INIT:
|
||||||
|
if ((chunk = this._consume(1))) {
|
||||||
|
this._clientShare = chunk[0]
|
||||||
|
debug('client shareFlag', this._clientShare)
|
||||||
|
this._writeServerInit()
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE:
|
||||||
|
if ((chunk = this._consume(1))) {
|
||||||
|
switch (chunk[0]) {
|
||||||
|
case VncConnection.CLIENT_MESSAGE_SETPIXELFORMAT:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_SETENCODINGS:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_FBUPDATEREQUEST:
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_KEYEVENT:
|
||||||
|
this.emit('userActivity')
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_POINTEREVENT:
|
||||||
|
this.emit('userActivity')
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT)
|
||||||
|
break
|
||||||
|
case VncConnection.CLIENT_MESSAGE_CLIENTCUTTEXT:
|
||||||
|
this.emit('userActivity')
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this._error(new Error(util.format(
|
||||||
|
'Unsupported message type %d', chunk[0])))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETPIXELFORMAT:
|
||||||
|
if ((chunk = this._consume(19))) {
|
||||||
|
// [0b, 3b) padding
|
||||||
|
this._clientPixelFormat = new PixelFormat({
|
||||||
|
bitsPerPixel: chunk[3]
|
||||||
|
, depth: chunk[4]
|
||||||
|
, bigEndianFlag: chunk[5]
|
||||||
|
, trueColorFlag: chunk[6]
|
||||||
|
, redMax: chunk.readUInt16BE(7, true)
|
||||||
|
, greenMax: chunk.readUInt16BE(9, true)
|
||||||
|
, blueMax: chunk.readUInt16BE(11, true)
|
||||||
|
, redShift: chunk[13]
|
||||||
|
, greenShift: chunk[14]
|
||||||
|
, blueShift: chunk[15]
|
||||||
|
})
|
||||||
|
// [16b, 19b) padding
|
||||||
|
debug('client pixel format', this._clientPixelFormat)
|
||||||
|
this.emit('formatchange', this._clientPixelFormat)
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS:
|
||||||
|
if ((chunk = this._consume(3))) {
|
||||||
|
// [0b, 1b) padding
|
||||||
|
this._clientEncodingCount = chunk.readUInt16BE(1, true)
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_SETENCODINGS_VALUE:
|
||||||
|
lo = 0
|
||||||
|
hi = 4 * this._clientEncodingCount
|
||||||
|
if ((chunk = this._consume(hi))) {
|
||||||
|
this._clientEncodings = []
|
||||||
|
while (lo < hi) {
|
||||||
|
this._clientEncodings.push(chunk.readInt32BE(lo, true))
|
||||||
|
lo += 4
|
||||||
|
}
|
||||||
|
debug('client encodings', this._clientEncodings)
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_FBUPDATEREQUEST:
|
||||||
|
if ((chunk = this._consume(9))) {
|
||||||
|
this.emit('fbupdaterequest', {
|
||||||
|
incremental: chunk[0]
|
||||||
|
, xPosition: chunk.readUInt16BE(1, true)
|
||||||
|
, yPosition: chunk.readUInt16BE(3, true)
|
||||||
|
, width: chunk.readUInt16BE(5, true)
|
||||||
|
, height: chunk.readUInt16BE(7, true)
|
||||||
|
})
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_KEYEVENT:
|
||||||
|
if ((chunk = this._consume(7))) {
|
||||||
|
// downFlag = chunk[0]
|
||||||
|
// [1b, 3b) padding
|
||||||
|
// key = chunk.readUInt32BE(3, true)
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_POINTEREVENT:
|
||||||
|
if ((chunk = this._consume(5))) {
|
||||||
|
this.emit('pointer', {
|
||||||
|
buttonMask: chunk[0]
|
||||||
|
, xPosition: chunk.readUInt16BE(1, true) / this._serverWidth
|
||||||
|
, yPosition: chunk.readUInt16BE(3, true) / this._serverHeight
|
||||||
|
})
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT:
|
||||||
|
if ((chunk = this._consume(7))) {
|
||||||
|
// [0b, 3b) padding
|
||||||
|
this._clientCutTextLength = chunk.readUInt32BE(3)
|
||||||
|
this._changeState(
|
||||||
|
VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case VncConnection.STATE_NEED_CLIENT_MESSAGE_CLIENTCUTTEXT_VALUE:
|
||||||
|
if ((chunk = this._consume(this._clientCutTextLength))) {
|
||||||
|
// value = chunk
|
||||||
|
this._changeState(VncConnection.STATE_NEED_CLIENT_MESSAGE)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(util.format('Impossible state %d', this._state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (chunk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._parseVersion = function(chunk) {
|
||||||
|
if (chunk.equals(new Buffer('RFB 003.008\n'))) {
|
||||||
|
return VncConnection.V3_008
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.equals(new Buffer('RFB 003.007\n'))) {
|
||||||
|
return VncConnection.V3_007
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.equals(new Buffer('RFB 003.003\n'))) {
|
||||||
|
return VncConnection.V3_003
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._parseSecurity = function(chunk) {
|
||||||
|
switch (chunk[0]) {
|
||||||
|
case VncConnection.SECURITY_NONE:
|
||||||
|
case VncConnection.SECURITY_VNC:
|
||||||
|
return chunk[0]
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._changeState = function(state) {
|
||||||
|
this._state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._append = function(chunk) {
|
||||||
|
if (!chunk) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('in', chunk)
|
||||||
|
|
||||||
|
if (this._buffer) {
|
||||||
|
this._buffer = Buffer.concat(
|
||||||
|
[this._buffer, chunk], this._buffer.length + chunk.length)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._buffer = chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._consume = function(n) {
|
||||||
|
var chunk
|
||||||
|
|
||||||
|
if (!this._buffer) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < this._buffer.length) {
|
||||||
|
chunk = this._buffer.slice(0, n)
|
||||||
|
this._buffer = this._buffer.slice(n)
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n === this._buffer.length) {
|
||||||
|
chunk = this._buffer
|
||||||
|
this._buffer = null
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
VncConnection.prototype._write = function(chunk) {
|
||||||
|
debug('out', chunk)
|
||||||
|
this.conn.write(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = VncConnection
|
14
lib/units/device/plugins/vnc/util/pixelformat.js
Normal file
14
lib/units/device/plugins/vnc/util/pixelformat.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
function PixelFormat(values) {
|
||||||
|
this.bitsPerPixel = values.bitsPerPixel
|
||||||
|
this.depth = values.depth
|
||||||
|
this.bigEndianFlag = values.bigEndianFlag
|
||||||
|
this.trueColorFlag = values.trueColorFlag
|
||||||
|
this.redMax = values.redMax
|
||||||
|
this.greenMax = values.greenMax
|
||||||
|
this.blueMax = values.blueMax
|
||||||
|
this.redShift = values.redShift
|
||||||
|
this.greenShift = values.greenShift
|
||||||
|
this.blueShift = values.blueShift
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PixelFormat
|
66
lib/units/device/plugins/vnc/util/pointertranslator.js
Normal file
66
lib/units/device/plugins/vnc/util/pointertranslator.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
|
||||||
|
function PointerTranslator() {
|
||||||
|
this.previousEvent = null
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(PointerTranslator, EventEmitter)
|
||||||
|
|
||||||
|
PointerTranslator.prototype.push = function(event) {
|
||||||
|
if (event.buttonMask & 0xFE) {
|
||||||
|
// Non-primary buttons included, ignore.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previousEvent) {
|
||||||
|
var buttonChanges = event.buttonMask ^ this.previousEvent.buttonMask
|
||||||
|
|
||||||
|
// If the primary button changed, we have an up/down event.
|
||||||
|
if (buttonChanges & 1) {
|
||||||
|
// If it's pressed now, that's a down event.
|
||||||
|
if (event.buttonMask & 1) {
|
||||||
|
this.emit('touchdown', {
|
||||||
|
contact: 1
|
||||||
|
, x: event.xPosition
|
||||||
|
, y: event.yPosition
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
// It's not pressed, so we have an up event.
|
||||||
|
else {
|
||||||
|
this.emit('touchup', {
|
||||||
|
contact: 1
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Otherwise, if we're still holding the primary button down,
|
||||||
|
// that's a move event.
|
||||||
|
else if (event.buttonMask & 1) {
|
||||||
|
this.emit('touchmove', {
|
||||||
|
contact: 1
|
||||||
|
, x: event.xPosition
|
||||||
|
, y: event.yPosition
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If it's the first event we get and the primary button's pressed,
|
||||||
|
// it's a down event.
|
||||||
|
if (event.buttonMask & 1) {
|
||||||
|
this.emit('touchdown', {
|
||||||
|
contact: 1
|
||||||
|
, x: event.xPosition
|
||||||
|
, y: event.yPosition
|
||||||
|
})
|
||||||
|
this.emit('touchcommit')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previousEvent = event
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PointerTranslator
|
52
lib/units/device/plugins/vnc/util/server.js
Normal file
52
lib/units/device/plugins/vnc/util/server.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
var debug = require('debug')('vnc:server')
|
||||||
|
|
||||||
|
var VncConnection = require('./connection')
|
||||||
|
|
||||||
|
function VncServer(server, options) {
|
||||||
|
this.options = options
|
||||||
|
|
||||||
|
this._bound = {
|
||||||
|
_listeningListener: this._listeningListener.bind(this)
|
||||||
|
, _connectionListener: this._connectionListener.bind(this)
|
||||||
|
, _closeListener: this._closeListener.bind(this)
|
||||||
|
, _errorListener: this._errorListener.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.server = server
|
||||||
|
.on('listening', this._bound._listeningListener)
|
||||||
|
.on('connection', this._bound._connectionListener)
|
||||||
|
.on('close', this._bound._closeListener)
|
||||||
|
.on('error', this._bound._errorListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.inherits(VncServer, EventEmitter)
|
||||||
|
|
||||||
|
VncServer.prototype.close = function() {
|
||||||
|
this.server.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype.listen = function() {
|
||||||
|
this.server.listen.apply(this.server, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._listeningListener = function() {
|
||||||
|
this.emit('listening')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._connectionListener = function(conn) {
|
||||||
|
debug('connection', conn.remoteAddress, conn.remotePort)
|
||||||
|
this.emit('connection', new VncConnection(conn, this.options))
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._closeListener = function() {
|
||||||
|
this.emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
VncServer.prototype._errorListener = function(err) {
|
||||||
|
this.emit('error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = VncServer
|
|
@ -14,7 +14,7 @@ module.exports = syrup.serial()
|
||||||
.dependency(require('../support/properties'))
|
.dependency(require('../support/properties'))
|
||||||
.dependency(require('../support/abi'))
|
.dependency(require('../support/abi'))
|
||||||
.define(function(options, adb, properties, abi) {
|
.define(function(options, adb, properties, abi) {
|
||||||
var log = logger.createLogger('device:resources:minicap')
|
logger.createLogger('device:resources:minicap')
|
||||||
|
|
||||||
var resources = {
|
var resources = {
|
||||||
bin: {
|
bin: {
|
||||||
|
|
|
@ -17,7 +17,7 @@ module.exports = syrup.serial()
|
||||||
pathutil.vendor('STFService/wire.proto'))
|
pathutil.vendor('STFService/wire.proto'))
|
||||||
|
|
||||||
var resource = {
|
var resource = {
|
||||||
requiredVersion: '1.0.1'
|
requiredVersion: '1.0.2'
|
||||||
, pkg: 'jp.co.cyberagent.stf'
|
, pkg: 'jp.co.cyberagent.stf'
|
||||||
, main: 'jp.co.cyberagent.stf.Agent'
|
, main: 'jp.co.cyberagent.stf.Agent'
|
||||||
, apk: pathutil.vendor('STFService/STFService.apk')
|
, apk: pathutil.vendor('STFService/STFService.apk')
|
||||||
|
@ -79,9 +79,9 @@ module.exports = syrup.serial()
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return promiseutil.periodicNotify(
|
return promiseutil.periodicNotify(
|
||||||
adb.install(options.serial, resource.apk)
|
adb.install(options.serial, resource.apk)
|
||||||
, 10000
|
, 20000
|
||||||
)
|
)
|
||||||
.timeout(60000)
|
.timeout(65000)
|
||||||
})
|
})
|
||||||
.progressed(function() {
|
.progressed(function() {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
var syrup = require('stf-syrup')
|
var syrup = require('stf-syrup')
|
||||||
|
|
||||||
var zmq = require('zmq')
|
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
|
|
||||||
var logger = require('../../../util/logger')
|
var logger = require('../../../util/logger')
|
||||||
var srv = require('../../../util/srv')
|
var srv = require('../../../util/srv')
|
||||||
|
var zmqutil = require('../../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = syrup.serial()
|
module.exports = syrup.serial()
|
||||||
.define(function(options) {
|
.define(function(options) {
|
||||||
var log = logger.createLogger('device:support:push')
|
var log = logger.createLogger('device:support:push')
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
var push = zmq.socket('push')
|
var push = zmqutil.socket('push')
|
||||||
|
|
||||||
return Promise.map(options.endpoints.push, function(endpoint) {
|
return Promise.map(options.endpoints.push, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
var syrup = require('stf-syrup')
|
var syrup = require('stf-syrup')
|
||||||
|
|
||||||
var zmq = require('zmq')
|
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
|
|
||||||
var logger = require('../../../util/logger')
|
var logger = require('../../../util/logger')
|
||||||
var wireutil = require('../../../wire/util')
|
var wireutil = require('../../../wire/util')
|
||||||
var srv = require('../../../util/srv')
|
var srv = require('../../../util/srv')
|
||||||
var lifecycle = require('../../../util/lifecycle')
|
require('../../../util/lifecycle')
|
||||||
|
var zmqutil = require('../../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = syrup.serial()
|
module.exports = syrup.serial()
|
||||||
.define(function(options) {
|
.define(function(options) {
|
||||||
var log = logger.createLogger('device:support:sub')
|
var log = logger.createLogger('device:support:sub')
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
var sub = zmq.socket('sub')
|
var sub = zmqutil.socket('sub')
|
||||||
|
|
||||||
return Promise.map(options.endpoints.sub, function(endpoint) {
|
return Promise.map(options.endpoints.sub, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var zmq = require('zmq')
|
|
||||||
|
|
||||||
var logger = require('../../util/logger')
|
var logger = require('../../util/logger')
|
||||||
var wire = require('../../wire')
|
var wire = require('../../wire')
|
||||||
|
@ -8,12 +7,13 @@ var wireutil = require('../../wire/util')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
var srv = require('../../util/srv')
|
var srv = require('../../util/srv')
|
||||||
var dbapi = require('../../db/api')
|
var dbapi = require('../../db/api')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('log-db')
|
var log = logger.createLogger('log-db')
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
var sub = zmq.socket('sub')
|
var sub = zmqutil.socket('sub')
|
||||||
Promise.map(options.endpoints.sub, function(endpoint) {
|
Promise.map(options.endpoints.sub, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ var util = require('util')
|
||||||
|
|
||||||
var Hipchatter = require('hipchatter')
|
var Hipchatter = require('hipchatter')
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var zmq = require('zmq')
|
|
||||||
|
|
||||||
var logger = require('../../util/logger')
|
var logger = require('../../util/logger')
|
||||||
var wire = require('../../wire')
|
var wire = require('../../wire')
|
||||||
|
@ -10,6 +9,7 @@ var wirerouter = require('../../wire/router')
|
||||||
var wireutil = require('../../wire/util')
|
var wireutil = require('../../wire/util')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
var srv = require('../../util/srv')
|
var srv = require('../../util/srv')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
|
|
||||||
var COLORS = {
|
var COLORS = {
|
||||||
1: 'gray'
|
1: 'gray'
|
||||||
|
@ -28,7 +28,7 @@ module.exports = function(options) {
|
||||||
, timer
|
, timer
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
var sub = zmq.socket('sub')
|
var sub = zmqutil.socket('sub')
|
||||||
Promise.map(options.endpoints.sub, function(endpoint) {
|
Promise.map(options.endpoints.sub, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var zmq = require('zmq')
|
|
||||||
|
|
||||||
var logger = require('../../util/logger')
|
var logger = require('../../util/logger')
|
||||||
var wire = require('../../wire')
|
var wire = require('../../wire')
|
||||||
|
@ -8,6 +7,7 @@ var wireutil = require('../../wire/util')
|
||||||
var dbapi = require('../../db/api')
|
var dbapi = require('../../db/api')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
var srv = require('../../util/srv')
|
var srv = require('../../util/srv')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('processor')
|
var log = logger.createLogger('processor')
|
||||||
|
@ -17,7 +17,7 @@ module.exports = function(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// App side
|
// App side
|
||||||
var appDealer = zmq.socket('dealer')
|
var appDealer = zmqutil.socket('dealer')
|
||||||
Promise.map(options.endpoints.appDealer, function(endpoint) {
|
Promise.map(options.endpoints.appDealer, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -37,7 +37,7 @@ module.exports = function(options) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Device side
|
// Device side
|
||||||
var devDealer = zmq.socket('dealer')
|
var devDealer = zmqutil.socket('dealer')
|
||||||
Promise.map(options.endpoints.devDealer, function(endpoint) {
|
Promise.map(options.endpoints.devDealer, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -124,12 +124,46 @@ module.exports = function(options) {
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
log.error(
|
log.error(
|
||||||
'Unable to lookup user by fingerprint "%s"'
|
'Unable to lookup user by ADB fingerprint "%s"'
|
||||||
, message.fingerprint
|
, message.fingerprint
|
||||||
, err.stack
|
, err.stack
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.on(wire.JoinGroupByVncAuthResponseMessage, function(channel, message) {
|
||||||
|
dbapi.lookupUserByVncAuthResponse(message.response, message.serial)
|
||||||
|
.then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
devDealer.send([
|
||||||
|
channel
|
||||||
|
, wireutil.envelope(new wire.AutoGroupMessage(
|
||||||
|
new wire.OwnerMessage(
|
||||||
|
user.email
|
||||||
|
, user.name
|
||||||
|
, user.group
|
||||||
|
)
|
||||||
|
, message.response
|
||||||
|
))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
else if (message.currentGroup) {
|
||||||
|
appDealer.send([
|
||||||
|
message.currentGroup
|
||||||
|
, wireutil.envelope(new wire.JoinGroupByVncAuthResponseMessage(
|
||||||
|
message.serial
|
||||||
|
, message.response
|
||||||
|
))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
log.error(
|
||||||
|
'Unable to lookup user by VNC auth response "%s"'
|
||||||
|
, message.response
|
||||||
|
, err.stack
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
.on(wire.JoinGroupMessage, function(channel, message, data) {
|
.on(wire.JoinGroupMessage, function(channel, message, data) {
|
||||||
dbapi.setDeviceOwner(message.serial, message.owner)
|
dbapi.setDeviceOwner(message.serial, message.owner)
|
||||||
appDealer.send([channel, data])
|
appDealer.send([channel, data])
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
var adb = require('adbkit')
|
var adb = require('adbkit')
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var zmq = require('zmq')
|
|
||||||
var _ = require('lodash')
|
var _ = require('lodash')
|
||||||
var EventEmitter = require('eventemitter3').EventEmitter
|
var EventEmitter = require('eventemitter3').EventEmitter
|
||||||
|
|
||||||
|
@ -11,6 +10,7 @@ var wirerouter = require('../../wire/router')
|
||||||
var procutil = require('../../util/procutil')
|
var procutil = require('../../util/procutil')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
var srv = require('../../util/srv')
|
var srv = require('../../util/srv')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('provider')
|
var log = logger.createLogger('provider')
|
||||||
|
@ -70,7 +70,7 @@ module.exports = function(options) {
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
var push = zmq.socket('push')
|
var push = zmqutil.socket('push')
|
||||||
Promise.map(options.endpoints.push, function(endpoint) {
|
Promise.map(options.endpoints.push, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -86,7 +86,7 @@ module.exports = function(options) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
var sub = zmq.socket('sub')
|
var sub = zmqutil.socket('sub')
|
||||||
Promise.map(options.endpoints.sub, function(endpoint) {
|
Promise.map(options.endpoints.sub, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -316,7 +316,7 @@ module.exports = function(options) {
|
||||||
|
|
||||||
// Spawn a device worker
|
// Spawn a device worker
|
||||||
function spawn() {
|
function spawn() {
|
||||||
var allocatedPorts = ports.splice(0, 2)
|
var allocatedPorts = ports.splice(0, 4)
|
||||||
, proc = options.fork(device, allocatedPorts)
|
, proc = options.fork(device, allocatedPorts)
|
||||||
, resolver = Promise.defer()
|
, resolver = Promise.defer()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var zmq = require('zmq')
|
|
||||||
|
|
||||||
var logger = require('../../util/logger')
|
var logger = require('../../util/logger')
|
||||||
var wire = require('../../wire')
|
var wire = require('../../wire')
|
||||||
|
@ -9,6 +8,7 @@ var dbapi = require('../../db/api')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
var srv = require('../../util/srv')
|
var srv = require('../../util/srv')
|
||||||
var TtlSet = require('../../util/ttlset')
|
var TtlSet = require('../../util/ttlset')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('reaper')
|
var log = logger.createLogger('reaper')
|
||||||
|
@ -19,7 +19,7 @@ module.exports = function(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
var sub = zmq.socket('sub')
|
var sub = zmqutil.socket('sub')
|
||||||
Promise.map(options.endpoints.sub, function(endpoint) {
|
Promise.map(options.endpoints.sub, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -41,7 +41,7 @@ module.exports = function(options) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
var push = zmq.socket('push')
|
var push = zmqutil.socket('push')
|
||||||
Promise.map(options.endpoints.push, function(endpoint) {
|
Promise.map(options.endpoints.push, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
|
|
@ -138,6 +138,10 @@ module.exports = function(options) {
|
||||||
app.get('/s/blob/:id/:name', function(req, res) {
|
app.get('/s/blob/:id/:name', function(req, res) {
|
||||||
var file = storage.retrieve(req.params.id)
|
var file = storage.retrieve(req.params.id)
|
||||||
if (file) {
|
if (file) {
|
||||||
|
if (typeof req.query.download !== 'undefined') {
|
||||||
|
res.set('Content-Disposition',
|
||||||
|
'attachment; filename="' + path.basename(file.name) + '"')
|
||||||
|
}
|
||||||
res.set('Content-Type', file.type)
|
res.set('Content-Type', file.type)
|
||||||
res.sendFile(file.path)
|
res.sendFile(file.path)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
var zmq = require('zmq')
|
|
||||||
|
|
||||||
var logger = require('../../util/logger')
|
var logger = require('../../util/logger')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
|
|
||||||
module.exports = function(options) {
|
module.exports = function(options) {
|
||||||
var log = logger.createLogger('triproxy')
|
var log = logger.createLogger('triproxy')
|
||||||
|
@ -17,18 +16,18 @@ module.exports = function(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// App/device output
|
// App/device output
|
||||||
var pub = zmq.socket('pub')
|
var pub = zmqutil.socket('pub')
|
||||||
pub.bindSync(options.endpoints.pub)
|
pub.bindSync(options.endpoints.pub)
|
||||||
log.info('PUB socket bound on', options.endpoints.pub)
|
log.info('PUB socket bound on', options.endpoints.pub)
|
||||||
|
|
||||||
// Coordinator input/output
|
// Coordinator input/output
|
||||||
var dealer = zmq.socket('dealer')
|
var dealer = zmqutil.socket('dealer')
|
||||||
dealer.bindSync(options.endpoints.dealer)
|
dealer.bindSync(options.endpoints.dealer)
|
||||||
dealer.on('message', proxy(pub))
|
dealer.on('message', proxy(pub))
|
||||||
log.info('DEALER socket bound on', options.endpoints.dealer)
|
log.info('DEALER socket bound on', options.endpoints.dealer)
|
||||||
|
|
||||||
// App/device input
|
// App/device input
|
||||||
var pull = zmq.socket('pull')
|
var pull = zmqutil.socket('pull')
|
||||||
pull.bindSync(options.endpoints.pull)
|
pull.bindSync(options.endpoints.pull)
|
||||||
pull.on('message', proxy(dealer))
|
pull.on('message', proxy(dealer))
|
||||||
log.info('PULL socket bound on', options.endpoints.pull)
|
log.info('PULL socket bound on', options.endpoints.pull)
|
||||||
|
|
|
@ -3,7 +3,6 @@ var events = require('events')
|
||||||
var util = require('util')
|
var util = require('util')
|
||||||
|
|
||||||
var socketio = require('socket.io')
|
var socketio = require('socket.io')
|
||||||
var zmq = require('zmq')
|
|
||||||
var Promise = require('bluebird')
|
var Promise = require('bluebird')
|
||||||
var _ = require('lodash')
|
var _ = require('lodash')
|
||||||
var request = Promise.promisifyAll(require('request'))
|
var request = Promise.promisifyAll(require('request'))
|
||||||
|
@ -17,6 +16,7 @@ var dbapi = require('../../db/api')
|
||||||
var datautil = require('../../util/datautil')
|
var datautil = require('../../util/datautil')
|
||||||
var srv = require('../../util/srv')
|
var srv = require('../../util/srv')
|
||||||
var lifecycle = require('../../util/lifecycle')
|
var lifecycle = require('../../util/lifecycle')
|
||||||
|
var zmqutil = require('../../util/zmqutil')
|
||||||
var cookieSession = require('./middleware/cookie-session')
|
var cookieSession = require('./middleware/cookie-session')
|
||||||
var ip = require('./middleware/remote-ip')
|
var ip = require('./middleware/remote-ip')
|
||||||
var auth = require('./middleware/auth')
|
var auth = require('./middleware/auth')
|
||||||
|
@ -31,7 +31,7 @@ module.exports = function(options) {
|
||||||
, channelRouter = new events.EventEmitter()
|
, channelRouter = new events.EventEmitter()
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
var push = zmq.socket('push')
|
var push = zmqutil.socket('push')
|
||||||
Promise.map(options.endpoints.push, function(endpoint) {
|
Promise.map(options.endpoints.push, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -47,7 +47,7 @@ module.exports = function(options) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Input
|
// Input
|
||||||
var sub = zmq.socket('sub')
|
var sub = zmqutil.socket('sub')
|
||||||
Promise.map(options.endpoints.sub, function(endpoint) {
|
Promise.map(options.endpoints.sub, function(endpoint) {
|
||||||
return srv.resolve(endpoint).then(function(records) {
|
return srv.resolve(endpoint).then(function(records) {
|
||||||
return srv.attempt(records, function(record) {
|
return srv.attempt(records, function(record) {
|
||||||
|
@ -826,6 +826,26 @@ module.exports = function(options) {
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
.on('fs.retrieve', function(channel, responseChannel, data) {
|
||||||
|
joinChannel(responseChannel)
|
||||||
|
push.send([
|
||||||
|
channel
|
||||||
|
, wireutil.transaction(
|
||||||
|
responseChannel
|
||||||
|
, new wire.FileSystemGetMessage(data)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.on('fs.list', function(channel, responseChannel, data){
|
||||||
|
joinChannel(responseChannel)
|
||||||
|
push.send([
|
||||||
|
channel
|
||||||
|
, wireutil.transaction(
|
||||||
|
responseChannel
|
||||||
|
, new wire.FileSystemListMessage(data)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.finally(function() {
|
.finally(function() {
|
||||||
// Clean up all listeners and subscriptions
|
// Clean up all listeners and subscriptions
|
||||||
|
|
|
@ -2,12 +2,9 @@ module.exports.list = function(val) {
|
||||||
return val.split(/\s*,\s*/g).filter(Boolean)
|
return val.split(/\s*,\s*/g).filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.allUnknownArgs = function(args) {
|
module.exports.size = function(val) {
|
||||||
return [].slice.call(args, 0, -1).filter(Boolean)
|
var match = /^(\d+)x(\d+)$/.exec(val)
|
||||||
}
|
return match ? [+match[1], +match[2]] : undefined
|
||||||
|
|
||||||
module.exports.lastArg = function(args) {
|
|
||||||
return args[args.length - 1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.range = function(from, to) {
|
module.exports.range = function(from, to) {
|
||||||
|
|
44
lib/util/vncauth.js
Normal file
44
lib/util/vncauth.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
var crypto = require('crypto')
|
||||||
|
|
||||||
|
// See http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
|
||||||
|
function reverseByteBits(b) {
|
||||||
|
return (((b * 0x0802 & 0x22110) |
|
||||||
|
(b * 0x8020 & 0x88440)) * 0x10101 >> 16 & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverseBufferByteBits(b) {
|
||||||
|
var result = new Buffer(b.length)
|
||||||
|
|
||||||
|
for (var i = 0; i < result.length; ++i) {
|
||||||
|
result[i] = reverseByteBits(b[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePassword(password) {
|
||||||
|
var key = new Buffer(8).fill(0)
|
||||||
|
|
||||||
|
// Make sure the key is always 8 bytes long. VNC passwords cannot be
|
||||||
|
// longer than 8 bytes. Shorter passwords are padded with zeroes.
|
||||||
|
reverseBufferByteBits(password).copy(key, 0, 0, 8)
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
function encrypt(challenge, password) {
|
||||||
|
var key = normalizePassword(password)
|
||||||
|
, iv = new Buffer(0).fill(0)
|
||||||
|
|
||||||
|
// Note: do not call .final(), .update() is the one that gives us the
|
||||||
|
// desired result.
|
||||||
|
return crypto.createCipheriv('des-ecb', key, iv).update(challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.encrypt = encrypt
|
||||||
|
|
||||||
|
function verify(response, challenge, password) {
|
||||||
|
return encrypt(challenge, password).equals(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.verify = verify
|
23
lib/util/zmqutil.js
Normal file
23
lib/util/zmqutil.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// ISSUE-100 (https://github.com/openstf/stf/issues/100)
|
||||||
|
|
||||||
|
// In some networks TCP Connection dies if kept idle for long.
|
||||||
|
// Setting TCP_KEEPALIVE option true, to all the zmq sockets
|
||||||
|
// won't let it die
|
||||||
|
|
||||||
|
var zmq = require('zmq')
|
||||||
|
|
||||||
|
var log = require('./logger').createLogger('util:zmqutil')
|
||||||
|
|
||||||
|
module.exports.socket = function() {
|
||||||
|
var sock = zmq.socket.apply(zmq, arguments)
|
||||||
|
|
||||||
|
try {
|
||||||
|
sock.setsockopt(zmq.ZMQ_TCP_KEEPALIVE, 1)
|
||||||
|
sock.setsockopt(zmq.ZMQ_TCP_KEEPALIVE_IDLE, 300000)
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.warn('ZeroMQ library too old, no support for TCP keepalive options')
|
||||||
|
}
|
||||||
|
|
||||||
|
return sock
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ enum MessageType {
|
||||||
PhysicalIdentifyMessage = 29;
|
PhysicalIdentifyMessage = 29;
|
||||||
JoinGroupMessage = 11;
|
JoinGroupMessage = 11;
|
||||||
JoinGroupByAdbFingerprintMessage = 69;
|
JoinGroupByAdbFingerprintMessage = 69;
|
||||||
|
JoinGroupByVncAuthResponseMessage = 90;
|
||||||
|
VncAuthResponsesUpdatedMessage = 91;
|
||||||
AutoGroupMessage = 70;
|
AutoGroupMessage = 70;
|
||||||
AdbKeysUpdatedMessage = 71;
|
AdbKeysUpdatedMessage = 71;
|
||||||
KeyDownMessage = 12;
|
KeyDownMessage = 12;
|
||||||
|
@ -72,6 +74,16 @@ enum MessageType {
|
||||||
AccountRemoveMessage = 55;
|
AccountRemoveMessage = 55;
|
||||||
SdStatusMessage = 61;
|
SdStatusMessage = 61;
|
||||||
ReverseForwardsEvent = 72;
|
ReverseForwardsEvent = 72;
|
||||||
|
FileSystemListMessage = 81;
|
||||||
|
FileSystemGetMessage = 82;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileSystemListMessage {
|
||||||
|
required string dir = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message FileSystemGetMessage {
|
||||||
|
required string file = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Envelope {
|
message Envelope {
|
||||||
|
@ -261,9 +273,18 @@ message JoinGroupByAdbFingerprintMessage {
|
||||||
optional string currentGroup = 4;
|
optional string currentGroup = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message JoinGroupByVncAuthResponseMessage {
|
||||||
|
required string serial = 1;
|
||||||
|
required string response = 2;
|
||||||
|
optional string currentGroup = 4;
|
||||||
|
}
|
||||||
|
|
||||||
message AdbKeysUpdatedMessage {
|
message AdbKeysUpdatedMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message VncAuthResponsesUpdatedMessage {
|
||||||
|
}
|
||||||
|
|
||||||
message LeaveGroupMessage {
|
message LeaveGroupMessage {
|
||||||
required string serial = 1;
|
required string serial = 1;
|
||||||
required OwnerMessage owner = 2;
|
required OwnerMessage owner = 2;
|
||||||
|
|
67
package.json
67
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "stf",
|
"name": "stf",
|
||||||
"version": "1.0.9",
|
"version": "1.0.10",
|
||||||
"description": "Smartphone Test Farm",
|
"description": "Smartphone Test Farm",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"adb",
|
"adb",
|
||||||
|
@ -36,39 +36,41 @@
|
||||||
"aws-sdk": "^2.2.3",
|
"aws-sdk": "^2.2.3",
|
||||||
"bluebird": "^2.9.34",
|
"bluebird": "^2.9.34",
|
||||||
"body-parser": "^1.13.3",
|
"body-parser": "^1.13.3",
|
||||||
"chalk": "~1.0.0",
|
"chalk": "~1.1.1",
|
||||||
"commander": "^2.7.1",
|
"commander": "^2.9.0",
|
||||||
"compression": "^1.5.2",
|
"compression": "^1.5.2",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
"csurf": "^1.7.0",
|
"csurf": "^1.7.0",
|
||||||
|
"debug": "^2.2.0",
|
||||||
"eventemitter3": "^0.1.6",
|
"eventemitter3": "^0.1.6",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"express-validator": "^2.14.0",
|
"express-validator": "^2.17.1",
|
||||||
"formidable": "^1.0.17",
|
"formidable": "^1.0.17",
|
||||||
"gm": "^1.17.0",
|
"gm": "^1.17.0",
|
||||||
"hipchatter": "^0.2.0",
|
"hipchatter": "^0.2.0",
|
||||||
"http-proxy": "^1.9.0",
|
"http-proxy": "^1.11.2",
|
||||||
"in-publish": "^2.0.0",
|
"in-publish": "^2.0.0",
|
||||||
"jade": "^1.9.2",
|
"jade": "^1.9.2",
|
||||||
|
"jpeg-turbo": "^0.3.0",
|
||||||
"jws": "^3.1.0",
|
"jws": "^3.1.0",
|
||||||
"ldapjs": "git+https://github.com/mcavage/node-ldapjs.git#acc1ca8f4314fd9d67561feabc8ce4c235076a5e",
|
"ldapjs": "git+https://github.com/mcavage/node-ldapjs.git#acc1ca8f4314fd9d67561feabc8ce4c235076a5e",
|
||||||
"lodash": "^3.10.1",
|
"lodash": "^3.10.1",
|
||||||
"markdown-serve": "^0.3.2",
|
"markdown-serve": "^0.3.2",
|
||||||
"mime": "^1.3.4",
|
"mime": "^1.3.4",
|
||||||
"minimatch": "^2.0.10",
|
"minimatch": "^3.0.0",
|
||||||
"my-local-ip": "^1.0.0",
|
"my-local-ip": "^1.0.0",
|
||||||
"node-uuid": "^1.4.3",
|
"node-uuid": "^1.4.3",
|
||||||
"passport": "^0.2.1",
|
"passport": "^0.3.0",
|
||||||
"passport-oauth2": "^1.1.2",
|
"passport-oauth2": "^1.1.2",
|
||||||
"protobufjs": "^3.8.2",
|
"protobufjs": "^3.8.2",
|
||||||
"proxy-addr": "^1.0.7",
|
"proxy-addr": "^1.0.7",
|
||||||
"request": "^2.60.0",
|
"request": "^2.65.0",
|
||||||
"request-progress": "^0.3.1",
|
"request-progress": "^0.3.1",
|
||||||
"rethinkdb": "^2.0.2",
|
"rethinkdb": "^2.0.2",
|
||||||
"semver": "^5.0.1",
|
"semver": "^5.0.1",
|
||||||
"serve-favicon": "^2.2.0",
|
"serve-favicon": "^2.2.0",
|
||||||
"serve-static": "^1.9.2",
|
"serve-static": "^1.9.2",
|
||||||
"socket.io": "1.3.6",
|
"socket.io": "1.3.7",
|
||||||
"split": "^1.0.0",
|
"split": "^1.0.0",
|
||||||
"stf-appstore-db": "^1.0.0",
|
"stf-appstore-db": "^1.0.0",
|
||||||
"stf-browser-db": "^1.0.2",
|
"stf-browser-db": "^1.0.2",
|
||||||
|
@ -77,68 +79,67 @@
|
||||||
"stf-wiki": "^1.0.0",
|
"stf-wiki": "^1.0.0",
|
||||||
"temp": "^0.8.1",
|
"temp": "^0.8.1",
|
||||||
"transliteration": "^0.1.1",
|
"transliteration": "^0.1.1",
|
||||||
"ws": "^0.7.2",
|
"ws": "^0.8.0",
|
||||||
"zmq": "^2.12.0"
|
"zmq": "^2.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^1.4.0",
|
|
||||||
"aws-sdk": "^2.1.46",
|
"aws-sdk": "^2.1.46",
|
||||||
"bower": "^1.3.12",
|
"async": "^1.4.2",
|
||||||
|
"bower": "^1.6.3",
|
||||||
"chai": "^3.2.0",
|
"chai": "^3.2.0",
|
||||||
"css-loader": "^0.14.0",
|
"css-loader": "^0.20.1",
|
||||||
"del": "^1.2.0",
|
"del": "^2.0.1",
|
||||||
"event-stream": "^3.3.0",
|
"event-stream": "^3.3.2",
|
||||||
"exports-loader": "^0.6.2",
|
"exports-loader": "^0.6.2",
|
||||||
"extract-text-webpack-plugin": "^0.8.2",
|
"extract-text-webpack-plugin": "^0.8.2",
|
||||||
"file-loader": "^0.8.1",
|
"file-loader": "^0.8.1",
|
||||||
"gulp": "^3.8.11",
|
"gulp": "^3.8.11",
|
||||||
"gulp-angular-gettext": "^2.1.0",
|
"gulp-angular-gettext": "^2.1.0",
|
||||||
"gulp-jade": "^1.0.0",
|
"gulp-jade": "^1.0.0",
|
||||||
"gulp-jscs": "^2.0.0",
|
"gulp-jscs": "^3.0.0",
|
||||||
"gulp-jshint": "^1.11.2",
|
"gulp-jshint": "^1.11.2",
|
||||||
"gulp-jsonlint": "^1.0.2",
|
"gulp-jsonlint": "^1.0.2",
|
||||||
"gulp-protractor": "^1.0.0",
|
"gulp-protractor": "^1.0.0",
|
||||||
"gulp-run": "^1.6.10",
|
"gulp-run": "^1.6.10",
|
||||||
"gulp-standard": "^4.5.3",
|
"gulp-standard": "^5.1.0",
|
||||||
"gulp-util": "^3.0.4",
|
"gulp-util": "^3.0.4",
|
||||||
"html-loader": "^0.3.0",
|
"html-loader": "^0.3.0",
|
||||||
"imports-loader": "^0.6.3",
|
"imports-loader": "^0.6.5",
|
||||||
"jasmine-core": "^2.3.4",
|
"jasmine-core": "^2.3.4",
|
||||||
"jasmine-reporters": "^2.0.5",
|
"jasmine-reporters": "^2.0.5",
|
||||||
"jshint": "^2.6.3",
|
"jshint": "^2.6.3",
|
||||||
"jshint-loader": "^0.8.3",
|
"jshint-loader": "^0.8.3",
|
||||||
"jshint-stylish": "^2.0.0",
|
"jshint-stylish": "^2.0.0",
|
||||||
"json-loader": "^0.5.1",
|
"json-loader": "^0.5.1",
|
||||||
"karma": "^0.13.3",
|
"karma": "^0.13.11",
|
||||||
"karma-chrome-launcher": "^0.2.0",
|
"karma-chrome-launcher": "^0.2.0",
|
||||||
"karma-firefox-launcher": "^0.1.4",
|
"karma-firefox-launcher": "^0.1.4",
|
||||||
"karma-ie-launcher": "^0.2.0",
|
"karma-ie-launcher": "^0.2.0",
|
||||||
"karma-jasmine": "^0.3.5",
|
"karma-jasmine": "^0.3.5",
|
||||||
"karma-junit-reporter": "^0.3.3",
|
"karma-junit-reporter": "^0.3.4",
|
||||||
"karma-opera-launcher": "^0.2.0",
|
"karma-opera-launcher": "^0.3.0",
|
||||||
"karma-phantomjs-launcher": "^0.2.0",
|
"karma-phantomjs-launcher": "^0.2.1",
|
||||||
"karma-safari-launcher": "^0.1.1",
|
"karma-safari-launcher": "^0.1.1",
|
||||||
"karma-webpack": "^1.6.0",
|
"karma-webpack": "^1.6.0",
|
||||||
"less": "^2.4.0",
|
"less": "^2.4.0",
|
||||||
"less-loader": "^2.1.0",
|
"less-loader": "^2.1.0",
|
||||||
"ng-annotate-webpack-plugin": "^0.1.2",
|
"memory-fs": "^0.2.0",
|
||||||
"node-libs-browser": "^0.5.2",
|
"node-libs-browser": "^0.5.2",
|
||||||
"node-sass": "^3.2.0",
|
"node-sass": "^3.3.3",
|
||||||
"phantomjs": "^1.9.17",
|
"phantomjs": "^1.9.18",
|
||||||
"protractor": "^2.0.0",
|
"protractor": "^2.5.1",
|
||||||
"protractor-html-screenshot-reporter": "0.0.21",
|
"protractor-html-screenshot-reporter": "0.0.21",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"run-sequence": "^1.1.2",
|
"sass-loader": "^3.0.0",
|
||||||
"sass-loader": "^1.0.4",
|
|
||||||
"script-loader": "^0.6.1",
|
"script-loader": "^0.6.1",
|
||||||
"sinon": "^1.14.1",
|
"sinon": "^1.16.1",
|
||||||
"sinon-chai": "^2.7.0",
|
"sinon-chai": "^2.7.0",
|
||||||
"socket.io-client": "1.3.6",
|
"socket.io-client": "1.3.7",
|
||||||
"style-loader": "^0.12.3",
|
"style-loader": "^0.12.3",
|
||||||
"template-html-loader": "^0.0.3",
|
"template-html-loader": "^0.0.3",
|
||||||
"url-loader": "^0.5.5",
|
"url-loader": "^0.5.5",
|
||||||
"webpack": "^1.10.5",
|
"webpack": "^1.12.2",
|
||||||
"webpack-dev-server": "^1.7.0"
|
"webpack-dev-server": "^1.12.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
|
|
|
@ -40,10 +40,3 @@ div[angular-packery]:after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.packery-item.is-dragging,
|
|
||||||
.packery-item.is-positioning-post-drag {
|
|
||||||
/*border-color: red;*/
|
|
||||||
/*background: #09F;*/
|
|
||||||
/*z-index: 2;*/
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ module.exports = function basicModeDirective($rootScope, BrowserInfo) {
|
||||||
return {
|
return {
|
||||||
restrict: 'AE',
|
restrict: 'AE',
|
||||||
link: function (scope, element) {
|
link: function (scope, element) {
|
||||||
$rootScope.basicMode = !!BrowserInfo.mobile // CHECK: use .mobile instead of .small
|
$rootScope.basicMode = !!BrowserInfo.mobile
|
||||||
if ($rootScope.basicMode) {
|
if ($rootScope.basicMode) {
|
||||||
element.addClass('basic-mode')
|
element.addClass('basic-mode')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
.basic-mode {
|
|
||||||
/*background: red;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.basic-mode .devices-icon-view {
|
.basic-mode .devices-icon-view {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -10,11 +6,6 @@
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.basic-mode .stf-vnc-bottom .btn-lg {
|
|
||||||
/*padding: 5px;*/
|
|
||||||
/*font-size: 12px;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.basic-mode .stf-vnc-bottom .btn-primary:hover,
|
.basic-mode .stf-vnc-bottom .btn-primary:hover,
|
||||||
.basic-mode .stf-vnc-bottom .btn-primary.active {
|
.basic-mode .stf-vnc-bottom .btn-primary.active {
|
||||||
background: #007aff;
|
background: #007aff;
|
||||||
|
@ -27,8 +18,6 @@
|
||||||
|
|
||||||
.basic-mode .basic-remote-control {
|
.basic-mode .basic-remote-control {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
/*width: 320px;*/
|
|
||||||
/*height: 485px;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.basic-mode .stf-device-list .device-search {
|
.basic-mode .stf-device-list .device-search {
|
||||||
|
|
|
@ -29,8 +29,6 @@ module.exports = function BrowserInfoServiceFactory() {
|
||||||
var windowWidth = window.screen.width < window.outerWidth ?
|
var windowWidth = window.screen.width < window.outerWidth ?
|
||||||
window.screen.width : window.outerWidth
|
window.screen.width : window.outerWidth
|
||||||
return windowWidth < 800
|
return windowWidth < 800
|
||||||
// return !!(window.matchMedia &&
|
|
||||||
// window.matchMedia('only screen and (max-width: 760px)').matches)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
addTest('mobile', function () {
|
addTest('mobile', function () {
|
||||||
|
@ -59,38 +57,6 @@ module.exports = function BrowserInfoServiceFactory() {
|
||||||
|
|
||||||
addTest('ua', navigator.userAgent)
|
addTest('ua', navigator.userAgent)
|
||||||
|
|
||||||
|
|
||||||
//function hasEvent() {
|
|
||||||
// return (function (undefined) {
|
|
||||||
// function isEventSupportedInner(eventName, element) {
|
|
||||||
// var isSupported
|
|
||||||
// if (!eventName) {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// if (!element || typeof element === 'string') {
|
|
||||||
// element = createElement(element || 'div')
|
|
||||||
// }
|
|
||||||
// eventName = 'on' + eventName
|
|
||||||
// isSupported = eventName in element
|
|
||||||
// return isSupported
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return isEventSupportedInner
|
|
||||||
// })()
|
|
||||||
//}
|
|
||||||
// var domPrefixes = 'Webkit Moz O ms'.toLowerCase().split(' ')
|
|
||||||
// addTest('pointerevents', function () {
|
|
||||||
// var bool = false
|
|
||||||
// var i = domPrefixes.length
|
|
||||||
// bool = hasEvent('pointerdown')
|
|
||||||
// while (i-- && !bool) {
|
|
||||||
// if (hasEvent(domPrefixes[i] + 'pointerdown')) {
|
|
||||||
// bool = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return bool
|
|
||||||
// })
|
|
||||||
|
|
||||||
addTest('devicemotion', 'DeviceMotionEvent' in window)
|
addTest('devicemotion', 'DeviceMotionEvent' in window)
|
||||||
|
|
||||||
addTest('deviceorientation', 'DeviceOrientationEvent' in window)
|
addTest('deviceorientation', 'DeviceOrientationEvent' in window)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
describe('BrowserInfo', function() {
|
describe('BrowserInfo', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function(BrowserInfo) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(BrowserInfo.doSomething()).toEqual('something');
|
//expect(BrowserInfo.doSomething()).toEqual('something');
|
||||||
|
|
||||||
}));
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
div.stf-badge-icon
|
div.stf-badge-icon
|
||||||
//i.fa.fa-warning.stf-badge-icon-warning(popover='I appeared on mouse enter!', popover-placement='bottom', popover-trigger='mouseenter')
|
|
||||||
i.fa.fa-warning.stf-badge-icon-warning(tooltip-placement='bottom', tooltip='An error has ocurred')
|
i.fa.fa-warning.stf-badge-icon-warning(tooltip-placement='bottom', tooltip='An error has ocurred')
|
||||||
|
|
|
@ -3,8 +3,6 @@ module.exports = function counterDirective($timeout) {
|
||||||
replace: false,
|
replace: false,
|
||||||
scope: true,
|
scope: true,
|
||||||
link: function (scope, element, attrs) {
|
link: function (scope, element, attrs) {
|
||||||
// TODO: use $$rAF later
|
|
||||||
|
|
||||||
var el = element[0]
|
var el = element[0]
|
||||||
var num, refreshInterval, duration, steps, step, countTo, increment
|
var num, refreshInterval, duration, steps, step, countTo, increment
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
require('./help-icon.css')
|
|
||||||
|
|
||||||
module.exports = function clearButtonDirective() {
|
module.exports = function clearButtonDirective() {
|
||||||
return {
|
return {
|
||||||
restrict: 'EA',
|
restrict: 'EA',
|
||||||
|
|
|
@ -8,7 +8,6 @@ module.exports = angular.module('stf/common-ui', [
|
||||||
require('./notifications').name,
|
require('./notifications').name,
|
||||||
require('./ng-enter').name,
|
require('./ng-enter').name,
|
||||||
require('./tooltips').name,
|
require('./tooltips').name,
|
||||||
//require('./tree').name,
|
|
||||||
require('./modals').name,
|
require('./modals').name,
|
||||||
require('./include-cached').name,
|
require('./include-cached').name,
|
||||||
require('./text-focus-select').name,
|
require('./text-focus-select').name,
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('FatalMessageService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name));
|
||||||
|
|
||||||
it('should ...', inject(function(FatalMessageService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(FatalMessageService.doSomething()).toEqual('something');
|
//expect(FatalMessageService.doSomething()).toEqual('something');
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
module.exports = angular.module('stf.add-adb-key-modal', [
|
module.exports = angular.module('stf.add-adb-key-modal', [
|
||||||
require('stf/common-ui/modals/common').name,
|
require('stf/common-ui/modals/common').name
|
||||||
//require('stf/keys/add-adb-key').name
|
|
||||||
])
|
])
|
||||||
.factory('AddAdbKeyModalService', require('./add-adb-key-modal-service'))
|
.factory('AddAdbKeyModalService', require('./add-adb-key-modal-service'))
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
//require('angular-dialog-service/dialogs')
|
|
||||||
//require('angular-dialog-service/dialogs.css')
|
|
||||||
require('./modals.css')
|
require('./modals.css')
|
||||||
|
|
||||||
module.exports = angular.module('stf.modals.common', [
|
module.exports = angular.module('stf.modals.common', [
|
||||||
|
|
|
@ -50,7 +50,6 @@
|
||||||
.modal-size-80p .modal-dialog {
|
.modal-size-80p .modal-dialog {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
/*max-height: 800px;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-size-80p .modal-body {
|
.modal-size-80p .modal-body {
|
||||||
|
|
|
@ -19,7 +19,6 @@ module.exports = function ServiceFactory($modal, $sce) {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
template: require('./external-url-modal.jade'),
|
template: require('./external-url-modal.jade'),
|
||||||
controller: ModalInstanceCtrl,
|
controller: ModalInstanceCtrl,
|
||||||
// size: 'lg',
|
|
||||||
windowClass: 'modal-size-80p',
|
windowClass: 'modal-size-80p',
|
||||||
resolve: {
|
resolve: {
|
||||||
title: function() {
|
title: function() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('ExternalUrlModalService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name));
|
||||||
|
|
||||||
it('should ...', inject(function(ExternalUrlModalService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(FatalMessageService.doSomething()).toEqual('something');
|
//expect(FatalMessageService.doSomething()).toEqual('something');
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ module.exports =
|
||||||
$scope.ok = function () {
|
$scope.ok = function () {
|
||||||
$modalInstance.close(true)
|
$modalInstance.close(true)
|
||||||
$route.reload()
|
$route.reload()
|
||||||
//$location.path('/control/' + device.serial)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('FatalMessageService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name));
|
||||||
|
|
||||||
it('should ...', inject(function(FatalMessageService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(FatalMessageService.doSomething()).toEqual('something');
|
//expect(FatalMessageService.doSomething()).toEqual('something');
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ module.exports = function ServiceFactory($modal) {
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
template: require('./lightbox-image.jade'),
|
template: require('./lightbox-image.jade'),
|
||||||
controller: ModalInstanceCtrl,
|
controller: ModalInstanceCtrl,
|
||||||
windowClass: 'modal-size-xl', // TODO: Make width dynamic adjusting
|
windowClass: 'modal-size-xl',
|
||||||
resolve: {
|
resolve: {
|
||||||
title: function() {
|
title: function() {
|
||||||
return title
|
return title
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('LightboxImageService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name));
|
||||||
|
|
||||||
it('should ...', inject(function(LightboxImageService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(XLightboxImageService.doSomething()).toEqual('something');
|
//expect(XLightboxImageService.doSomething()).toEqual('something');
|
||||||
|
|
||||||
|
|
|
@ -7,4 +7,3 @@
|
||||||
.modal-body
|
.modal-body
|
||||||
img(ng-if='imageUrl', ng-src='{{imageUrl}}')
|
img(ng-if='imageUrl', ng-src='{{imageUrl}}')
|
||||||
nothing-to-show(message='{{"No photo available"|translate}}', icon='fa-picture-o', ng-if='!imageUrl')
|
nothing-to-show(message='{{"No photo available"|translate}}', icon='fa-picture-o', ng-if='!imageUrl')
|
||||||
// TODO: replace !imageUrl here with a image-not-available='imageIsNotPresent = true' directive
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('SocketDisconnectedService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./index').name))
|
beforeEach(angular.mock.module(require('./index').name))
|
||||||
|
|
||||||
it('should ...', inject(function(SocketDisconnectedService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(SocketDisconnectedService.doSomething()).toEqual('something')
|
//expect(SocketDisconnectedService.doSomething()).toEqual('something')
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ describe('VersionUpdateService', function() {
|
||||||
beforeEach(angular.mock.module(require('ui-bootstrap').name));
|
beforeEach(angular.mock.module(require('ui-bootstrap').name));
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name));
|
||||||
|
|
||||||
it('should ...', inject(function(VersionUpdateService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(VersionUpdateService.doSomething()).toEqual('something');
|
//expect(VersionUpdateService.doSomething()).toEqual('something');
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
require('./native-autocomplete.css')
|
|
||||||
|
|
||||||
module.exports = angular.module('stf.native-autocomplete', [
|
|
||||||
|
|
||||||
])
|
|
||||||
.directive('nativeAutocomplete', require('./native-autocomplete-directive'))
|
|
|
@ -1,13 +0,0 @@
|
||||||
module.exports = function nativeAutocompleteDirective() {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
replace: true,
|
|
||||||
scope: {
|
|
||||||
|
|
||||||
},
|
|
||||||
template: require('./native-autocomplete.jade'),
|
|
||||||
link: function (scope, element, attrs) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
describe('nativeAutocomplete', function () {
|
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
|
||||||
|
|
||||||
var scope, compile;
|
|
||||||
|
|
||||||
beforeEach(inject(function ($rootScope, $compile) {
|
|
||||||
scope = $rootScope.$new();
|
|
||||||
compile = $compile;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should ...', function () {
|
|
||||||
|
|
||||||
/*
|
|
||||||
To test your directive, you need to create some html that would use your directive,
|
|
||||||
send that through compile() then compare the results.
|
|
||||||
|
|
||||||
var element = compile('<div native-autocomplete name="name">hi</div>')(scope);
|
|
||||||
expect(element.text()).toBe('hello, world');
|
|
||||||
*/
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,3 +0,0 @@
|
||||||
.stf-native-autocomplete {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
div.stf-native-autocomplete
|
|
|
@ -1,7 +0,0 @@
|
||||||
input(
|
|
||||||
type='text',
|
|
||||||
native-autocomplete,
|
|
||||||
ng-model='text',
|
|
||||||
typeahead='["text1", "text2"]',
|
|
||||||
history='20'
|
|
||||||
)
|
|
|
@ -3,7 +3,6 @@
|
||||||
top: 60px;
|
top: 60px;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
float: right;
|
float: right;
|
||||||
/*width: 320px;*/
|
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@ module.exports = function refreshPageDirective($window) {
|
||||||
scope.reloadWindow = function () {
|
scope.reloadWindow = function () {
|
||||||
$window.location.reload()
|
$window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: reload with $route.reload()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
require('./table.css')
|
require('./table.css')
|
||||||
require('script!ng-table/dist/ng-table')
|
require('script!ng-table/dist/ng-table')
|
||||||
//require('ng-table/ng-table.css')
|
|
||||||
|
|
||||||
module.exports = angular.module('stf/common-ui/table', [
|
module.exports = angular.module('stf/common-ui/table', [
|
||||||
'ngTable'
|
'ngTable'
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
.ng-table th {
|
.ng-table th {
|
||||||
/*text-align: center;*/
|
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
//require('angular-tree-control/css/tree-control.css')
|
|
||||||
//require('./tree.css')
|
|
||||||
//require('angular-tree-control')
|
|
||||||
|
|
||||||
module.exports = angular.module('stf.tree', [
|
|
||||||
// 'treeControl'
|
|
||||||
])
|
|
||||||
.factory('TreeService', require('./tree-service'))
|
|
|
@ -1,52 +0,0 @@
|
||||||
module.exports = function () {
|
|
||||||
var treeService = {}
|
|
||||||
|
|
||||||
var tree = [
|
|
||||||
{name: 'glossary', children: [
|
|
||||||
{name: 'title'}
|
|
||||||
]}
|
|
||||||
]
|
|
||||||
|
|
||||||
function createTreeFromJSON(tree, json) {
|
|
||||||
|
|
||||||
|
|
||||||
(function updateRecursive(item) {
|
|
||||||
if (item.iconSrc) {
|
|
||||||
item.iconSrcFullpath = 'some value..';
|
|
||||||
}
|
|
||||||
_.each(item.items, updateRecursive);
|
|
||||||
})(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$scope.treeOptions = {
|
|
||||||
nodeChildren: 'children',
|
|
||||||
dirSelectable: true,
|
|
||||||
injectClasses: {
|
|
||||||
ul: "a1",
|
|
||||||
li: "a2",
|
|
||||||
liSelected: "a7",
|
|
||||||
iExpanded: "a3",
|
|
||||||
iCollapsed: "a4",
|
|
||||||
iLeaf: "a5",
|
|
||||||
label: "a6",
|
|
||||||
labelSelected: "a8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.treeData = [
|
|
||||||
{ "name": "Joe", "age": "21", "children": [
|
|
||||||
{ "name": "Smith", "age": "42", "children": [] },
|
|
||||||
{ "name": "Gary", "age": "21", "children": [
|
|
||||||
{ "name": "Jenifer", "age": "23", "children": [
|
|
||||||
{ "name": "Dani", "age": "32", "children": [] },
|
|
||||||
{ "name": "Max", "age": "34", "children": [] }
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
]},
|
|
||||||
{ "name": "Albert", "age": "33", "children": [] },
|
|
||||||
{ "name": "Ron", "age": "29", "children": [] }
|
|
||||||
];
|
|
||||||
|
|
||||||
return treeService
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
.stf-tree {
|
|
||||||
|
|
||||||
}
|
|
|
@ -157,9 +157,10 @@ module.exports = function ControlServiceFactory(
|
||||||
return sendTwoWay('device.reboot')
|
return sendTwoWay('device.reboot')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rotate = function(rotation) {
|
this.rotate = function(rotation, lock) {
|
||||||
return sendOneWay('display.rotate', {
|
return sendOneWay('display.rotate', {
|
||||||
rotation: rotation
|
rotation: rotation,
|
||||||
|
lock: lock
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +225,18 @@ module.exports = function ControlServiceFactory(
|
||||||
return sendTwoWay('screen.capture')
|
return sendTwoWay('screen.capture')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.fsretrieve = function(file){
|
||||||
|
return sendTwoWay('fs.retrieve', {
|
||||||
|
file: file,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fslist = function(dir){
|
||||||
|
return sendTwoWay('fs.list', {
|
||||||
|
dir: dir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.checkAccount = function(type, account) {
|
this.checkAccount = function(type, account) {
|
||||||
return sendTwoWay('account.check', {
|
return sendTwoWay('account.check', {
|
||||||
type: type
|
type: type
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
describe('FilterStringService', function() {
|
describe('FilterStringService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function(FilterStringService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(FilterStringService.doSomething()).toEqual('something');
|
//expect(FilterStringService.doSomething()).toEqual('something')
|
||||||
|
|
||||||
}));
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,9 +2,9 @@ describe('install', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name))
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function($filter) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
var filter = $filter('installError')
|
//var filter = $filter('installError')
|
||||||
|
|
||||||
//expect(filter('input')).toEqual('output')
|
//expect(filter('input')).toEqual('output')
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
describe('LogcatService', function() {
|
describe('LogcatService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function(LogcatService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(LogcatService.doSomething()).toEqual('something');
|
//expect(LogcatService.doSomething()).toEqual('something')
|
||||||
|
|
||||||
}));
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('NativeUrlService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name))
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function(NativeUrlService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(NativeUrlService.doSomething()).toEqual('something')
|
//expect(NativeUrlService.doSomething()).toEqual('something')
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
describe('PortForwardingService', function() {
|
describe('PortForwardingService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function(PortForwardingService) {
|
it('should ...', inject(function() {
|
||||||
expect(1).toBe(1)
|
//expect(PortForwardingService.doSomething()).toEqual('something')
|
||||||
//expect(PortForwardingService.doSomething()).toEqual('something');
|
|
||||||
|
|
||||||
}));
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
describe('ScopedHotkeysService', function() {
|
describe('ScopedHotkeysService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function(ScopedHotkeysService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(ScopedHotkeysService.doSomething()).toEqual('something');
|
//expect(ScopedHotkeysService.doSomething()).toEqual('something')
|
||||||
|
|
||||||
}));
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,10 @@ var frame = {
|
||||||
current: 0
|
current: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FastImageRender () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var imageRender = new FastImageRender(
|
var imageRender = new FastImageRender(
|
||||||
canvasElement
|
canvasElement
|
||||||
, {
|
, {
|
||||||
|
|
|
@ -3,8 +3,7 @@ module.exports = function screenKeyboardDirective() {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
template: require('./screen-keyboard.jade'),
|
template: require('./screen-keyboard.jade'),
|
||||||
link: function (scope, element) {
|
link: function (scope, element) {
|
||||||
var input = element.find('input')
|
element.find('input')
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = function screenTouchDirective() {
|
module.exports = function screenTouchDirective() {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
link: function (scope, element, attrs) {
|
link: function () {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ module.exports = function textHistoryDirective() {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
template: '',
|
template: '',
|
||||||
link: function (scope, element, attrs) {
|
link: function () {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ describe('TimelineService', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name));
|
beforeEach(angular.mock.module(require('./').name));
|
||||||
|
|
||||||
it('should ...', inject(function(TimelineService) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
//expect(TimelineService.doSomething()).toEqual('something');
|
//expect(TimelineService.doSomething()).toEqual('something');
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ describe('upload', function() {
|
||||||
|
|
||||||
beforeEach(angular.mock.module(require('./').name))
|
beforeEach(angular.mock.module(require('./').name))
|
||||||
|
|
||||||
it('should ...', inject(function($filter) {
|
it('should ...', inject(function() {
|
||||||
|
|
||||||
var filter = $filter('uploadError')
|
//var filter = $filter('uploadError')
|
||||||
|
|
||||||
//expect(filter('input')).toEqual('output')
|
//expect(filter('input')).toEqual('output')
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,6 @@ module.exports = function ActivityCtrl($scope, gettext, TimelineService) {
|
||||||
serial: $scope.device.serial
|
serial: $scope.device.serial
|
||||||
})
|
})
|
||||||
|
|
||||||
// $scope.timeline.push({
|
|
||||||
// title: title,
|
|
||||||
// message: message,
|
|
||||||
// serial: angular.copy($scope.device.serial),
|
|
||||||
// time: Date.now()
|
|
||||||
// })
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
.widget-container.scrollableX.messages.stf-activity(ng-controller='ActivityCtrl')
|
.widget-container.scrollableX.messages.stf-activity(ng-controller='ActivityCtrl')
|
||||||
//.heading
|
|
||||||
i.fa
|
|
||||||
span(translate) Activity
|
|
||||||
.widget-content.padded
|
.widget-content.padded
|
||||||
|
|
||||||
ul(ng-repeat='line in timeline.lines')
|
ul(ng-repeat='line in timeline.lines')
|
||||||
|
@ -24,27 +21,3 @@
|
||||||
div
|
div
|
||||||
refresh-page
|
refresh-page
|
||||||
|
|
||||||
|
|
||||||
//a(href='/')
|
|
||||||
.status.unread
|
|
||||||
//i.fa.fa-exclamation-triangle.fa-2x.activity-icon
|
|
||||||
h2.activity-title WebSocket Disconnected
|
|
||||||
span.activity-date 2014/04/30 18:33:22
|
|
||||||
|
|
||||||
p.pull-left Socket connection was lost, try again reloading the page.
|
|
||||||
.activity-buttons.pull-right
|
|
||||||
refresh-page
|
|
||||||
.clearfix
|
|
||||||
//li.list-group-item
|
|
||||||
.reviewer-info
|
|
||||||
i.fa.fa-mobile.fa-2x.activity-icon
|
|
||||||
h5.activity-title 'Nexus 5' Disconnected
|
|
||||||
em.activity-date.pull-right 2014/04/30 15:33:22
|
|
||||||
.review-text
|
|
||||||
p.pull-left Device was disconnected because it timed out.
|
|
||||||
.activity-buttons.pull-right
|
|
||||||
button.btn.btn-sm.btn-primary-outline(ng-click='')
|
|
||||||
i.fa.fa-refresh
|
|
||||||
span(translate) Reconnect device
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,9 @@
|
||||||
div(ng-include='"control-panes/advanced/input/input.jade"')
|
div(ng-include='"control-panes/advanced/input/input.jade"')
|
||||||
.col-md-6
|
.col-md-6
|
||||||
div(ng-include='"control-panes/advanced/port-forwarding/port-forwarding.jade"')
|
div(ng-include='"control-panes/advanced/port-forwarding/port-forwarding.jade"')
|
||||||
|
.row
|
||||||
|
.col-md-6
|
||||||
|
div(ng-include='"control-panes/advanced/vnc/vnc.jade"')
|
||||||
|
|
||||||
.col-md-6
|
.col-md-6
|
||||||
div(ng-include='"control-panes/advanced/maintenance/maintenance.jade"')
|
div(ng-include='"control-panes/advanced/maintenance/maintenance.jade"')
|
||||||
|
|
|
@ -4,6 +4,7 @@ module.exports = angular.module('stf.advanced', [
|
||||||
require('./input').name,
|
require('./input').name,
|
||||||
// require('./run-js').name,
|
// require('./run-js').name,
|
||||||
// require('./usb').name,
|
// require('./usb').name,
|
||||||
|
require('./vnc').name,
|
||||||
require('./port-forwarding').name,
|
require('./port-forwarding').name,
|
||||||
require('./maintenance').name
|
require('./maintenance').name
|
||||||
])
|
])
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
div
|
div
|
||||||
h6(translate) Special Keys
|
h6(translate) Special Keys
|
||||||
div.special-keys-buttons
|
div.special-keys-buttons
|
||||||
button(tooltip='{{ "Power" | translate }}', ng-click='press("power")').btn.btn-danger
|
button(tooltip='{{ "Power" | translate }}', ng-click='press("power")').btn.btn-danger.btn-xs
|
||||||
i.fa.fa-power-off
|
i.fa.fa-power-off
|
||||||
button(tooltip='{{ "Camera" | translate }}', ng-click='press("camera")').btn.btn-primary
|
button(tooltip='{{ "Camera" | translate }}', ng-click='press("camera")').btn.btn-primary.btn-xs
|
||||||
i.fa.fa-camera
|
i.fa.fa-camera
|
||||||
button(tooltip='{{ "Switch Charset" | translate }}', ng-click='press("switch_charset")').btn.btn-primary.btn-info
|
button(tooltip='{{ "Switch Charset" | translate }}', ng-click='press("switch_charset")').btn.btn-primary.btn-info.btn-xs
|
||||||
i.fa Aa
|
i.fa Aa
|
||||||
button(tooltip='{{ "Search" | translate }}', ng-click='press("search")').btn.btn-primary
|
button(tooltip='{{ "Search" | translate }}', ng-click='press("search")').btn.btn-primary.btn-xs
|
||||||
i.fa.fa-search
|
i.fa.fa-search
|
||||||
|
|
||||||
h6(translate) Volume
|
h6(translate) Volume
|
||||||
|
@ -38,58 +38,8 @@
|
||||||
i.fa.fa-step-forward
|
i.fa.fa-step-forward
|
||||||
button(tooltip='{{ "Fast Forward" | translate }}', ng-click='press("media_fast_forward")').btn.btn-primary.btn-xs
|
button(tooltip='{{ "Fast Forward" | translate }}', ng-click='press("media_fast_forward")').btn.btn-primary.btn-xs
|
||||||
i.fa.fa-fast-forward
|
i.fa.fa-fast-forward
|
||||||
//h6 Physical Media
|
//h6 D-pad
|
||||||
//.btn-group
|
//table.special-keys-dpad-buttons
|
||||||
button(tooltip='{{ "Play" | translate }}', ng-click='press("KEYCODE_MEDIA_PLAY")').btn.btn-primary.btn-xs
|
|
||||||
i.fa.fa-play
|
|
||||||
button(tooltip='{{ "Pause" | translate }}', ng-click='press("KEYCODE_MEDIA_PAUSE")').btn.btn-primary.btn-xs
|
|
||||||
i.fa.fa-pause
|
|
||||||
button(tooltip='{{ "Close" | translate }}', ng-click='press("KEYCODE_MEDIA_CLOSE")').btn.btn-primary.btn-xs
|
|
||||||
i.fa.fa-sign-out
|
|
||||||
button(tooltip='{{ "Eject" | translate }}', ng-click='press("KEYCODE_MEDIA_EJECT")').btn.btn-primary.btn-xs
|
|
||||||
i.fa.fa-eject
|
|
||||||
button(tooltip='{{ "Record" | translate }}', ng-click='press("KEYCODE_MEDIA_RECORD")').btn.btn-primary.btn-xs
|
|
||||||
i.fa.fa-circle
|
|
||||||
//h6(translate) Other Keys
|
|
||||||
//div.special-other-keys-buttons
|
|
||||||
button(ng-click='press("KEYCODE_APP_SWITCH")').btn.btn-default.btn-xs
|
|
||||||
i.fa App Switch
|
|
||||||
button(ng-click='press("KEYCODE_MANNER_MODE")').btn.btn-default.btn-xs
|
|
||||||
i.fa Manner Mode
|
|
||||||
button(ng-click='press("KEYCODE_3D_MODE")').btn.btn-default.btn-xs
|
|
||||||
i.fa 3D Mode
|
|
||||||
button(ng-click='press("KEYCODE_CONTACTS")').btn.btn-default.btn-xs
|
|
||||||
i.fa Contacts
|
|
||||||
button(ng-click='press("KEYCODE_CALENDAR")').btn.btn-default.btn-xs
|
|
||||||
i.fa Calendar
|
|
||||||
button(ng-click='press("KEYCODE_MUSIC")').btn.btn-default.btn-xs
|
|
||||||
i.fa Music
|
|
||||||
button(ng-click='press("KEYCODE_CALCULATOR")').btn.btn-default.btn-xs
|
|
||||||
i.fa Calculator
|
|
||||||
button(ng-click='press("KEYCODE_ZENKAKU_HANKAKU")').btn.btn-default.btn-xs
|
|
||||||
i.fa 全角/半角
|
|
||||||
button(ng-click='press("KEYCODE_EISU")').btn.btn-default.btn-xs
|
|
||||||
i.fa 英数
|
|
||||||
button(ng-click='press("KEYCODE_MUHENKAN")').btn.btn-default.btn-xs
|
|
||||||
i.fa 無変換
|
|
||||||
button(ng-click='press("KEYCODE_HENKAN")').btn.btn-default.btn-xs
|
|
||||||
i.fa 変換
|
|
||||||
button(ng-click='press("KEYCODE_KATAKANA_HIRAGANA")').btn.btn-default.btn-xs
|
|
||||||
i.fa カタかナ/ひらがな
|
|
||||||
button(ng-click='press("KEYCODE_YEN")').btn.btn-default.btn-xs
|
|
||||||
i.fa ¥
|
|
||||||
button(ng-click='press("KEYCODE_RO")').btn.btn-default.btn-xs
|
|
||||||
i.fa RO
|
|
||||||
button(ng-click='press("KEYCODE_KANA")').btn.btn-default.btn-xs
|
|
||||||
i.fa かな
|
|
||||||
button(ng-click='press("KEYCODE_ASSIST")').btn.btn-default.btn-xs
|
|
||||||
i.fa Assist
|
|
||||||
//button(tooltip='{{ "Switch Charset" | translate }}', ng-click='press(80)').btn.btn-primary
|
|
||||||
i.fa TST
|
|
||||||
//button(ng-click='press("KEYCODE_CLEAR")').btn.btn-primary.btn-sm
|
|
||||||
i.fa Clear
|
|
||||||
h6 D-pad
|
|
||||||
table.special-keys-dpad-buttons
|
|
||||||
tr
|
tr
|
||||||
td
|
td
|
||||||
td
|
td
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.widget-container.fluid-height.stf-port-forwarding(ng-controller='PortForwardingCtrl')
|
.widget-container.fluid-height.stf-port-forwarding(ng-controller='PortForwardingCtrl')
|
||||||
.heading
|
.heading
|
||||||
span
|
span
|
||||||
stacked-icon(icon='fa-random', color='color-darkgreen')
|
stacked-icon(icon='fa-random', color='color-orange')
|
||||||
span(translate, ng-click='isCollapsed = !isCollapsed').pointer Port Forwarding
|
span(translate, ng-click='isCollapsed = !isCollapsed').pointer Port Forwarding
|
||||||
|
|
||||||
button.btn.pull-right.btn-sm.btn-primary-outline(
|
button.btn.pull-right.btn-sm.btn-primary-outline(
|
||||||
|
@ -44,7 +44,3 @@
|
||||||
td
|
td
|
||||||
button.btn.btn-sm.btn-danger-outline(ng-click='removeRow(forward)')
|
button.btn.btn-sm.btn-danger-outline(ng-click='removeRow(forward)')
|
||||||
i.fa.fa-trash-o
|
i.fa.fa-trash-o
|
||||||
//.checkbox
|
|
||||||
label
|
|
||||||
input(type='checkbox', value='')
|
|
||||||
span(translate) Always forward on connect
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module.exports = function RunJsCtrl($scope) {
|
module.exports = function RunJsCtrl() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,7 @@
|
||||||
.heading
|
.heading
|
||||||
i.fa.fa-code
|
i.fa.fa-code
|
||||||
span(translate) Run JavaScript
|
span(translate) Run JavaScript
|
||||||
//form.form-inline
|
|
||||||
.btn-group
|
.btn-group
|
||||||
//button(ng-disabled='true').btn.btn-sm.btn-default-outline
|
|
||||||
i.fa.fa-upload
|
|
||||||
| Load File...
|
|
||||||
script(type='text/ng-template', id='saveSnippetModal.html')
|
script(type='text/ng-template', id='saveSnippetModal.html')
|
||||||
.modal-header
|
.modal-header
|
||||||
h2 Save snippet
|
h2 Save snippet
|
||||||
|
@ -29,24 +25,6 @@
|
||||||
li.divider
|
li.divider
|
||||||
li
|
li
|
||||||
a(ng-click='clearSnippets()', type='button', translate).btn-link Clear
|
a(ng-click='clearSnippets()', type='button', translate).btn-link Clear
|
||||||
//span.form-inline.form-group.unselectable
|
|
||||||
.checkbox
|
|
||||||
label
|
|
||||||
input(type='checkbox', ng-model='snippet.safe')
|
|
||||||
span(tooltip='Execute code in a safe way') Safe
|
|
||||||
span
|
|
||||||
.checkbox
|
|
||||||
label
|
|
||||||
input(type='checkbox', nxg-model='snippet.evaluate')
|
|
||||||
span(tooltip='Evaluate code') Evaluate
|
|
||||||
.checkbox
|
|
||||||
label
|
|
||||||
input(type='checkbox', ng-model='snippet.async')
|
|
||||||
span(tooltip='Execute code in an async way') Async
|
|
||||||
.checkbox
|
|
||||||
label
|
|
||||||
input(type='checkbox', ng-model='snippet.scriptTag', ng-disabled='true')
|
|
||||||
span(tooltip='{{scriptTagPopover}}') Script tag
|
|
||||||
.btn-group.pull-right
|
.btn-group.pull-right
|
||||||
button.btn.btn-sm.btn-primary-outline(ng-click='injectJS()', ng-disabled='!snippet.editorText')
|
button.btn.btn-sm.btn-primary-outline(ng-click='injectJS()', ng-disabled='!snippet.editorText')
|
||||||
i.fa.fa-play
|
i.fa.fa-play
|
||||||
|
@ -64,14 +42,12 @@
|
||||||
span {{ result.deviceName }}
|
span {{ result.deviceName }}
|
||||||
td(width='30%', title="'Returns'", sortable='prettyValue')
|
td(width='30%', title="'Returns'", sortable='prettyValue')
|
||||||
div(ng-show='result.isObject')
|
div(ng-show='result.isObject')
|
||||||
//ace-json-viewer(ng-model='result.prettyValue')
|
|
||||||
//div(ui-ace="miniAceOptions", ng-model='result.prettyValue').stf-mini-ace-viewer
|
|
||||||
code.value-next-to-progress {{ result.prettyValue }}
|
code.value-next-to-progress {{ result.prettyValue }}
|
||||||
div(ng-hide='result.isObject')
|
div(ng-hide='result.isObject')
|
||||||
.value-next-to-progress {{ result.value }}
|
.value-next-to-progress {{ result.value }}
|
||||||
td(width='40%', ng-show='result.isSpecialValue')
|
td(width='40%', ng-show='result.isSpecialValue')
|
||||||
div(ng-show='result.isNumber')
|
div(ng-show='result.isNumber')
|
||||||
//progressbar.table-progress(value='result.percentage', max='100')
|
progressbar.table-progress(value='result.percentage', max='100')
|
||||||
div(ng-show='result.isObject')
|
div(ng-show='result.isObject')
|
||||||
div.label.label-info Object
|
div.label.label-info Object
|
||||||
div(ng-show='result.isFunction')
|
div(ng-show='result.isFunction')
|
||||||
|
@ -84,7 +60,6 @@
|
||||||
div.label(style='width=100%', ng-class="{'label-success': result.value, 'label-important': !result.value}")
|
div.label(style='width=100%', ng-class="{'label-success': result.value, 'label-important': !result.value}")
|
||||||
i.fa(ng-class="{'fa-check': result.value, 'fa-times-circle': !result.value }")
|
i.fa(ng-class="{'fa-check': result.value, 'fa-times-circle': !result.value }")
|
||||||
span {{ result.value.toString() }}
|
span {{ result.value.toString() }}
|
||||||
//span {{ result.value.toString() | capitalize }}
|
|
||||||
tab(heading='Raw')
|
tab(heading='Raw')
|
||||||
pre.selectable {{results | json}}
|
pre.selectable {{results | json}}
|
||||||
clear-button(ng-click='clear()', ng-disabled='!results.length')
|
clear-button(ng-click='clear()', ng-disabled='!results.length')
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module.exports = function UsbCtrl($scope) {
|
module.exports = function UsbCtrl() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
12
res/app/control-panes/advanced/vnc/index.js
Normal file
12
res/app/control-panes/advanced/vnc/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
require('./vnc.css')
|
||||||
|
|
||||||
|
module.exports = angular.module('stf.vnc', [
|
||||||
|
require('gettext').name
|
||||||
|
])
|
||||||
|
.run(["$templateCache", function ($templateCache) {
|
||||||
|
$templateCache.put(
|
||||||
|
'control-panes/advanced/vnc/vnc.jade',
|
||||||
|
require('./vnc.jade')
|
||||||
|
)
|
||||||
|
}])
|
||||||
|
.controller('VNCCtrl', require('./vnc-controller'))
|
11
res/app/control-panes/advanced/vnc/vnc-controller.js
Normal file
11
res/app/control-panes/advanced/vnc/vnc-controller.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = function RemoteDebugCtrl($scope) {
|
||||||
|
$scope.vnc = {}
|
||||||
|
|
||||||
|
$scope.generateVNCLogin = function () {
|
||||||
|
$scope.vnc = {
|
||||||
|
serverHost: 'localhost'
|
||||||
|
, serverPort: '7042'
|
||||||
|
, serverPassword: '12345678'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue