Browse Source

add babel for backwards compatibility in hterm (#15)

* add babel for backwards compatible

* wrong pre-commit

* wrong pre-commit

* fix code-climate errors

* fix utf error

* re add mobile support

* use classes

* testing

* move #!
pull/126/head
Cian Butler 8 years ago
committed by GitHub
parent
commit
667888a44d
  1. 11
      .babelrc
  2. 100
      .eslintrc
  3. 36
      Gruntfile.js
  4. 60
      app.js
  5. 3
      bin/wetty.js
  6. 37
      gulpfile.js
  7. 46
      package.json
  8. 16076
      public/wetty/hterm_all.js
  9. 3
      public/wetty/index.html
  10. 76
      public/wetty/wetty.js
  11. 1
      public/wetty/wetty.min.js
  12. 67
      src/.eslintrc
  13. 83
      src/wetty.js
  14. 61
      wetty.js
  15. 3028
      yarn.lock

11
.babelrc

@ -0,0 +1,11 @@
{
"presets": [
[
"es2015",
{
"modules": false
}
]
],
"compact": true,
}

100
.eslintrc

@ -1,47 +1,73 @@
{
"extends": "standard",
"env": {
"es6": true,
"jest": true,
"node": true
'env': {
'es6' : true,
'node' : true,
},
"globals": {
"GENTLY": true
},
"rules": {
"no-multi-spaces": 0,
"comma-dangle": [
"error",
"always-multiline"
],
"key-spacing": [
"error",
"extends": [
'eslint:recommended'
],
'rules' : {
'indent': [
'error',
2,
],
'linebreak-style': [
'error',
'unix',
],
'quotes': [
'error',
'single',
],
'semi': [
'error',
'always',
],
'comma-dangle': [
'error',
'always-multiline',
],
'key-spacing': [
'error',
{
"multiLine": {
"beforeColon": false,
"afterColon": true
'multiLine': {
'beforeColon': false,
'afterColon' : true,
},
"align": {
"beforeColon": false,
"afterColon": true,
"on": "colon",
"mode": "strict"
}
}
'align': {
'beforeColon': false,
'afterColon' : true,
'on' : 'colon',
'mode' : 'strict',
},
},
],
"semi": [
"error",
"always"
'no-var': [
'error',
],
"no-var": [
"error"
'no-console': [
'error',
{
'allow': [
'warn',
'trace',
'log',
'error'
]
}
],
"prefer-const": [
"error",
'prefer-arrow-callback': [
'error',
{
"destructuring": "any",
"ignoreReadBeforeAssign": false
'allowNamedFunctions': true
}
]
}
],
'prefer-const': [
'error',
{
'destructuring' : 'any',
'ignoreReadBeforeAssign': false,
},
],
},
}

36
Gruntfile.js

@ -1,36 +0,0 @@
module.exports = grunt => {
require('load-grunt-tasks')(grunt);
const config = {
mkdir: {
tmp: {
options: {
create: ['tmp'],
},
},
},
gitclone: {
hterm: {
options: {
cwd : './tmp',
repository: 'https://chromium.googlesource.com/apps/libapps',
},
},
},
shell: {
build_hterm: {
command: 'LIBDOT_SEARCH_PATH=$(pwd) ./libdot/bin/concat.sh -i ./hterm/concat/hterm_all.concat -o ../../public/wetty/hterm_all.js',
options: {
execOptions: {
cwd: './tmp/libapps',
},
},
},
},
clean: ['./tmp'],
};
grunt.initConfig(config);
grunt.registerTask('default', ['mkdir:tmp', 'gitclone:hterm', 'shell:build_hterm', 'clean']);
};

60
app.js

@ -1,43 +1,43 @@
const wetty = require('./package.js');
const fs = require('fs');
#! /usr/bin/env node
const wetty = require('./wetty.js');
const fs = require('fs-extra');
const path = require('path');
const optimist = require('optimist');
const opts = optimist
.options({
sslkey: {
demand : false,
demand: false,
description: 'path to SSL key',
},
sslcert: {
demand : false,
demand: false,
description: 'path to SSL certificate',
},
sshhost: {
demand : false,
demand: false,
description: 'ssh server host',
},
sshport: {
demand : false,
demand: false,
description: 'ssh server port',
},
sshuser: {
demand : false,
demand: false,
description: 'ssh user',
},
sshauth: {
demand : false,
demand: false,
description: 'defaults to "password", you can use "publickey,password" instead',
},
port: {
demand : false,
alias : 'p',
demand: false,
alias: 'p',
description: 'wetty listen port',
},
help: {
demand : false,
alias : 'h',
demand: false,
alias: 'h',
description: 'Print help message',
},
})
@ -48,26 +48,38 @@ if (opts.help) {
process.exit(0);
}
const globalsshuser = opts.sshuser || process.env.SSHUSER || '';
const sshuser = opts.sshuser || process.env.SSHUSER || '';
const sshhost = opts.sshhost || process.env.SSHHOST || 'localhost';
const sshauth = opts.sshauth || process.env.SSHAUTH || 'password';
const sshport = opts.sshport || process.env.SSHPOST || 22;
const port = opts.port || process.env.PORT || 3000;
if (opts.sslkey && opts.sslcert) {
opts['ssl'] = {};
opts.ssl['key'] = fs.readFileSync(path.resolve(opts.sslkey));
opts.ssl['cert'] = fs.readFileSync(path.resolve(opts.sslcert));
}
loadSSL(opts).then(ssl => {
opts.ssl = ssl;
});
process.on('uncaughtException', e => {
console.error(`Error: ${e}`);
process.on('uncaughtException', err => {
console.error(`Error: ${err}`);
});
const e = wetty.serve(port, globalsshuser, sshhost, sshport, sshauth, opts.ssl);
e.on('exit', code => {
const tty = wetty.serve(port, sshuser, sshhost, sshport, sshauth, opts.ssl);
tty.on('exit', code => {
console.log(`exit with code: ${code}`);
});
e.on('disconnect', () => {
tty.on('disconnect', () => {
console.log('disconnect');
});
async function loadSSL({ sslkey, sslcert }) {
try {
return sslkey && sslcert
? {
key: await fs.readFile(path.resolve(sslkey)),
cert: await fs.readFile(path.resolve(sslcert)),
}
: {};
} catch (err) {
console.err(err);
process.exit(1);
}
}

3
bin/wetty.js

@ -1,3 +0,0 @@
#!/usr/bin/env node
require('../app');

37
gulpfile.js

@ -0,0 +1,37 @@
const gulp = require('gulp');
const concat = require('gulp-concat');
const minify = require('gulp-minify');
const babel = require('gulp-babel');
const shell = require('gulp-shell');
const del = require('del');
gulp.task('compress', ['hterm'], () => gulp
.src([
'./libapps/hterm_all.js',
'./src/wetty.js',
])
.pipe(concat('wetty.js'))
.pipe(babel())
.pipe(
minify({
ext: {
min: '.min.js',
},
exclude : ['tasks'],
noSource : true,
ignoreFiles: ['.combo.js', '*.min.js'],
})
)
.pipe(gulp.dest('./public/wetty')));
gulp.task('hterm',
shell.task([
'git clone https://chromium.googlesource.com/apps/libapps',
'LIBDOT_SEARCH_PATH=$(pwd)/libapps ./libapps/libdot/bin/concat.sh -i ./libapps/hterm/concat/hterm_all.concat -o ./libapps/hterm_all.js',
], {
verbose: true,
}));
gulp.task('default', ['compress'], () => {
return del(['./libapps']);
});

46
package.json

@ -4,22 +4,37 @@
"dependencies": {
"express": "^4.15.3",
"optimist": "^0.6",
"pre-commit": "^1.2.2",
"pty.js": "^0.3.1",
"serve-favicon": "^2.4.3",
"socket.io": "^1.3.7"
},
"devDependencies": {
"eslint": "3.16.1",
"eslint-config-standard": "6.2.1",
"babel-cli": "6.24.1",
"babel-core": "6.24.1",
"babel-eslint": "7.2.3",
"babel-plugin-add-module-exports": "0.2.1",
"babel-plugin-es6-promise": "1.1.1",
"babel-plugin-syntax-async-functions": "6.13.0",
"babel-plugin-transform-async-to-generator": "6.24.1",
"babel-plugin-transform-object-assign": "6.22.0",
"babel-preset-es2015": "6.24.1",
"del": "^3.0.0",
"es6-promise": "^4.1.1",
"eslint": "3.19.0",
"eslint-config-standard": "10.2.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-node": "^4.2.2",
"eslint-plugin-promise": "3.5.0",
"eslint-plugin-standard": "2.1.0",
"grunt": "^0.4",
"grunt-contrib-clean": "^0.6",
"grunt-git": "^0.3",
"grunt-mkdir": "^0.1",
"grunt-shell": "^1.1",
"load-grunt-tasks": "^3.0"
"eslint-plugin-react": "6.10.3",
"eslint-plugin-standard": "3.0.1",
"gulp": "^3.9.1",
"gulp-babel": "^6.1.2",
"gulp-concat": "^2.6.1",
"gulp-minify": "^1.0.0",
"gulp-shell": "^0.6.3"
},
"pre-commit": ["fix", "commit"],
"description": "Wetty = Web + tty. Terminal access in browser over http/https ",
"repository": {
"type": "git",
@ -34,16 +49,15 @@
"preferGlobal": "true",
"main": "app.js",
"bin": {
"wetty": "./bin/wetty.js"
"wetty": "./app.js"
},
"files": [
"bin",
"public"
],
"files": ["bin", "public"],
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"build": "grunt",
"start": "node app.js"
"build": "gulp",
"start": "node app.js",
"commit": "git add public",
"fix": "eslint . --fix"
}
}

16076
public/wetty/hterm_all.js

File diff suppressed because it is too large

3
public/wetty/index.html

@ -3,9 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Wetty - The WebTTY Terminal Emulator</title>
<script src="/wetty/hterm_all.js"></script>
<script src="/wetty/socket.io/socket.io.js"></script>
<script src="/wetty/wetty.js"></script>
<style>
html, body {
height: 100%;
@ -38,5 +36,6 @@
<body>
<div id="overlay"><input type="button" onclick="location.reload();" value="reconnect" /></div>
<div id="terminal" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></div>
<script src="/wetty/wetty.min.js"></script>
</body>
</html>

76
public/wetty/wetty.js

@ -1,76 +0,0 @@
var term;
var socket = io(location.origin, {path: '/wetty/socket.io'})
var buf = '';
function Wetty(argv) {
this.argv_ = argv;
this.io = null;
this.pid_ = -1;
}
Wetty.prototype.run = function() {
this.io = this.argv_.io.push();
this.io.onVTKeystroke = this.sendString_.bind(this);
this.io.sendString = this.sendString_.bind(this);
this.io.onTerminalResize = this.onTerminalResize.bind(this);
}
Wetty.prototype.sendString_ = function(str) {
socket.emit('input', str);
};
Wetty.prototype.onTerminalResize = function(col, row) {
socket.emit('resize', { col: col, row: row });
};
socket.on('connect', function() {
document.getElementById("overlay").style.display = "none";
window.addEventListener('beforeunload', handler, false);
lib.init(function() {
hterm.defaultStorage = new lib.Storage.Local();
term = new hterm.Terminal();
window.term = term;
term.decorate(document.getElementById('terminal'));
term.setCursorPosition(0, 0);
term.setCursorVisible(true);
term.prefs_.set('ctrl-c-copy', true);
term.prefs_.set('ctrl-v-paste', true);
term.prefs_.set('use-default-window-copy', true);
term.runCommandClass(Wetty, document.location.hash.substr(1));
socket.emit('resize', {
col: term.screenSize.width,
row: term.screenSize.height
});
if (buf && buf != '') {
term.io.writeUTF16(buf);
buf = '';
}
});
});
socket.on('output', function(data) {
if (!term) {
buf += data;
return;
}
term.io.writeUTF16(data);
});
socket.on('logout', function(data) {
document.getElementById("overlay").style.display = "block";
window.removeEventListener('beforeunload', handler, false);
});
socket.on('disconnect', function() {
document.getElementById("overlay").style.display = "block";
window.removeEventListener('beforeunload', handler, false);
});
function handler (e) {
e.returnValue = "Are you sure?";
return e.returnValue;
}

1
public/wetty/wetty.min.js

File diff suppressed because one or more lines are too long

67
src/.eslintrc

@ -0,0 +1,67 @@
{
'env': {
'es6' : true,
'browser': true,
},
'globals': {
'hterm': true,
'lib': true,
'io': true
},
"extends": [
'eslint:recommended'
],
'rules' : {
'indent': [
'error',
2,
],
'linebreak-style': [
'error',
'unix',
],
'quotes': [
'error',
'single',
],
'semi': [
'error',
'always',
],
'comma-dangle': [
'error',
'always-multiline',
],
'key-spacing': [
'error',
{
'multiLine': {
'beforeColon': false,
'afterColon' : true,
},
'align': {
'beforeColon': false,
'afterColon' : true,
'on' : 'colon',
'mode' : 'strict',
},
},
],
'no-var': [
'error',
],
'prefer-arrow-callback': [
'error',
{
'allowNamedFunctions': true
}
],
'prefer-const': [
'error',
{
'destructuring' : 'any',
'ignoreReadBeforeAssign': false,
},
],
},
}

83
src/wetty.js

@ -0,0 +1,83 @@
const socket = io(location.origin, {path: '/wetty/socket.io'});
let term;
let buf = '';
class Wetty {
constructor(argv) {
this.argv_ = argv;
this.io = null;
this.pid_ = -1;
}
run() {
this.io = this.argv_.io.push();
this.io.onVTKeystroke = this.sendString_.bind(this);
this.io.sendString = this.sendString_.bind(this);
this.io.onTerminalResize = this.onTerminalResize.bind(this);
}
sendString_(str) {
socket.emit('input', str);
}
onTerminalResize(col, row) {
socket.emit('resize', { col, row });
}
}
socket.on('connect', () => {
document.getElementById('overlay').style.display = 'none';
window.addEventListener('beforeunload', handler, false);
lib.init(() => {
hterm.defaultStorage = new lib.Storage.Local();
term = new hterm.Terminal();
window.term = term;
term.decorate(document.getElementById('terminal'));
term.setCursorPosition(0, 0);
term.setCursorVisible(true);
term.prefs_.set('ctrl-c-copy', true);
term.prefs_.set('ctrl-v-paste', true);
term.prefs_.set('use-default-window-copy', true);
term.prefs_.set('send-encoding', 'raw');
term.prefs_.set('receive-encoding', 'raw');
term.scrollPort_.screen_.setAttribute('spellcheck', 'false');
term.scrollPort_.screen_.setAttribute('autocorrect', 'false');
term.scrollPort_.screen_.setAttribute('autocomplete', 'false');
term.scrollPort_.screen_.setAttribute('contenteditable', 'true');
term.runCommandClass(Wetty, document.location.hash.substr(1));
socket.emit('resize', {
col: term.screenSize.width,
row: term.screenSize.height,
});
if (buf && buf !== '') {
term.io.writeUTF8(buf);
buf = '';
}
});
});
socket.on('output', (data) => {
if (!term) {
buf += data;
return;
}
term.io.writeUTF8(data);
});
socket.on('logout', () => {
document.getElementById('overlay').style.display = 'block';
window.removeEventListener('beforeunload', handler, false);
});
socket.on('disconnect', () => {
document.getElementById('overlay').style.display = 'block';
window.removeEventListener('beforeunload', handler, false);
});
function handler (event) {
event.returnValue = 'Are you sure?';
return event.returnValue;
}

61
package.js → wetty.js

@ -5,9 +5,10 @@ const path = require('path');
const server = require('socket.io');
const pty = require('pty.js');
const EventEmitter = require('events');
const favicon = require('serve-favicon');
const app = express();
app.use(require('serve-favicon')(`${__dirname}/public/favicon.ico`));
app.use(favicon(`${__dirname}/public/favicon.ico`));
// For using wetty at /wetty on a vhost
app.get('/wetty/ssh/:user', (req, res) => {
res.sendfile(`${__dirname}/public/wetty/index.html`);
@ -25,56 +26,38 @@ app.get('/', (req, res) => {
// For serving css and javascript
app.use('/', express.static(path.join(__dirname, 'public')));
function createServer (port, sslopts) {
if (sslopts && sslopts.key && sslopts.cert) {
return https.createServer(sslopts, app).listen(port, () => {
function createServer(port, sslopts) {
return sslopts && sslopts.key && sslopts.cert
? https.createServer(sslopts, app).listen(port, () => {
console.log(`https on port ${port}`);
})
: http.createServer(app).listen(port, () => {
console.log(`http on port ${port}`);
});
}
return http.createServer(app).listen(port, () => {
console.log(`http on port ${port}`);
});
}
exports.serve = function (port, globalsshuser, sshhost, sshport, sshauth, sslopts) {
exports.serve = (port, globalsshuser, sshhost, sshport, sshauth, sslopts) => {
const httpserv = createServer(port, sslopts);
const events = new EventEmitter();
const io = server(httpserv, { path: '/wetty/socket.io' });
io.on('connection', socket => {
let sshuser = '';
const request = socket.request;
console.log(`${new Date()} Connection accepted.`);
const match = request.headers.referer.match('.+/ssh/.+$');
if (match) {
sshuser = `${match[0].split('/ssh/').pop()}@`;
} else if (globalsshuser) {
sshuser = `${globalsshuser}@`;
}
const ssh = match
? `${match[0].split('/ssh/').pop()}@${sshhost}`
: globalsshuser ? `${globalsshuser}@${sshhost}` : sshhost;
let term;
if (process.getuid() === 0 && sshhost === 'localhost') {
term = pty.spawn('/bin/login', [], {
name: 'xterm-256color',
cols: 80,
rows: 30,
});
} else if (sshuser) {
term = pty.spawn('ssh', [sshuser + sshhost, '-p', sshport, '-o', `PreferredAuthentications=${sshauth}`], {
name: 'xterm-256color',
cols: 80,
rows: 30,
});
} else {
term = pty.spawn('./bin/ssh', [sshhost, '-p', sshport, '-o', `PreferredAuthentications=${sshauth}`], {
name: 'xterm-256color',
cols: 80,
rows: 30,
});
}
const cmd = process.getuid() === 0 && sshhost === 'localhost' ? '/bin/login' : './bin/ssh';
const args = cmd === './bin/ssh' ? [ssh, '-p', sshport, '-o', `PreferredAuthentications=${sshauth}`] : [];
const term = pty.spawn(cmd, args, {
name: 'xterm-256color',
cols: 80,
rows: 30,
});
console.log(`${new Date()} PID=${term.pid} STARTED on behalf of user=${sshuser}`);
console.log(`${new Date()} PID=${term.pid} STARTED on behalf of user=${ssh}`);
term.on('data', data => {
socket.emit('output', data);
});
@ -86,9 +69,7 @@ exports.serve = function (port, globalsshuser, sshhost, sshport, sshauth, sslopt
socket.on('resize', ({ col, row }) => {
term.resize(col, row);
});
socket.on('input', data => {
term.write(data);
});
socket.on('input', term.write);
socket.on('disconnect', () => {
term.end();
events.emit('disconnect');

3028
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save