Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

refactor, remove hooks, config.

  • Loading branch information...
commit d7b14c8bbc17e0cfa2a2ac8cda81226d643cc8ea 1 parent 247b452
@chjj authored
View
39 README.md
@@ -31,6 +31,28 @@ Bellard's vt100 for [jslinux](http://bellard.org/jslinux/).
$ npm install tty.js
```
+## Usage
+
+tty.js is an app, but it's also possible to hook into it programatically
+
+``` js
+var tty = require('tty.js');
+
+var app = tty.createServer({
+ shell: 'bash',
+ users: {
+ foo: 'bar'
+ },
+ port: 8000
+});
+
+app.get('/foo', function(req, res, next) {
+ res.send('bar');
+});
+
+app.listen();
+```
+
## Configuration
Configuration is stored in `~/.tty.js/config.json` or `~/.tty.js` as a single
@@ -52,8 +74,10 @@ JSON file. An example configuration file looks like:
"static": "./static",
"limitGlobal": 10000,
"limitPerUser": 1000,
- "hooks": "./hooks.js",
"cwd": ".",
+ "syncSession": true,
+ "sessionTimeout": 600000,
+ "log": true,
"term": {
"termName": "xterm",
"geometry": [80, 30],
@@ -91,19 +115,6 @@ Usernames and passwords can be plaintext or sha1 hashes.
If tty.js fails to check your terminfo properly, you can force your `TERM`
to `xterm-256color` by setting `"termName": "xterm-256color"` in your config.
-### Example Hooks File
-
-``` js
-var db = require('./db');
-
-module.exports = {
- auth: function(user, pass, next) {
- // Do database auth
- next(null, pass === password);
- }
-};
-```
-
## Security
tty.js currently has https as an option. It also has express' default basic
View
39 lib/config.js
@@ -4,7 +4,8 @@
*/
var path = require('path')
- , fs = require('fs');
+ , fs = require('fs')
+ , logger = require('./logger');
/**
* Default Config
@@ -23,7 +24,9 @@ var schema = {
// static: './static',
// limitGlobal: 10000,
// limitPerUser: 1000,
- // hooks: './hooks.js',
+ // syncSession: true,
+ // sessionTimeout: 10 * 60 * 1000,
+ // log: true,
// cwd: '.',
term: {
// termName: 'xterm',
@@ -157,9 +160,11 @@ function checkConfig(conf) {
conf.term.termName = conf.termName || conf.term.termName;
if (!conf.term.termName) {
// tput -Txterm-256color longname
- conf.term.termName = exists('/usr/share/terminfo/x/xterm+256color')
- ? 'xterm-256color'
- : 'xterm';
+ conf.term.termName =
+ exists('/usr/share/terminfo/x/xterm+256color')
+ || exists('/usr/share/terminfo/x/xterm-256color')
+ ? 'xterm-256color'
+ : 'xterm';
}
conf.termName = conf.term.termName;
@@ -172,9 +177,6 @@ function checkConfig(conf) {
delete conf.users;
}
- // hooks
- conf.hooks = conf.hooks || tryRequire(dir, 'hooks.js');
-
// cwd
if (conf.cwd) {
conf.cwd = path.resolve(dir, conf.cwd);
@@ -194,7 +196,7 @@ function checkLegacy(conf) {
if (conf.auth && conf.auth.username && !conf.auth.disabled) {
conf.users[conf.auth.username] = conf.auth.password;
}
- console.error('`auth` is deprecated, please use `users` instead.');
+ logger('error', '`auth` is deprecated, please use `users` instead.');
}
if (conf.https && conf.https.key) {
@@ -227,13 +229,16 @@ function checkLegacy(conf) {
}
if (conf.hooks) {
- conf.hooks = tryRequire(conf.dir, conf.hooks);
+ out.push(''
+ + '`hooks` is deprecated, please programmatically '
+ + 'hook into your tty.js server instead.');
}
if (out.length) {
- out = out.join('\n');
- console.error(out);
- console.error('Exiting...');
+ out.forEach(function(out) {
+ logger('error', out);
+ });
+ logger('error', 'Exiting.');
process.exit(1);
}
}
@@ -297,12 +302,18 @@ function parseArg() {
function getarg() {
var arg = argv.shift();
- if (arg && arg.indexOf('--') === 0) {
+ if (!arg) return arg;
+ if (arg.indexOf('--') === 0) {
arg = arg.split('=');
if (arg.length > 1) {
argv.unshift(arg.slice(1).join('='));
}
return arg[0];
+ } else if (arg[0] === '-' && arg.length > 2) {
+ argv = arg.substring(1).split('').map(function(ch) {
+ return '-' + ch;
+ }).concat(argv);
+ arg = argv.shift();
}
return arg;
}
View
57 lib/logger.js
@@ -0,0 +1,57 @@
+/**
+ * tty.js: logger.js
+ * Copyright (c) 2012, Christopher Jeffrey (MIT License)
+ */
+
+/**
+ * Logger
+ */
+
+var slice = Array.prototype.slice;
+
+var isatty = require('tty').isatty;
+isatty = [isatty(0), isatty(1), isatty(2)];
+
+var levels = {
+ 'log': [34, 'log'],
+ 'error': [41, 'error'],
+ 'warning': [31, 'error'] // 31, 33, 91
+};
+
+function logger(level) {
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ if (typeof args[0] !== 'string') args.unshift('');
+
+ level = levels[level];
+
+ args[0] = '\x1b['
+ + level[0]
+ + 'm['
+ + logger.prefix
+ + ']\x1b[m '
+ + args[0];
+
+ if ((level[1] === 'log' && !isatty[1])
+ || (level[1] === 'error' && !isatty[2])) {
+ args[0] = args[0].replace(/\x1b\[(?:\d+(?:;\d+)*)?m/g, '');
+ }
+
+ return console[level[1]].apply(console, args);
+}
+
+logger.prefix = 'tty.js';
+
+logger.log = function() {
+ return logger('log', slice.call(arguments));
+};
+
+logger.warning = function() {
+ return logger('warning', slice.call(arguments));
+};
+
+logger.error = function() {
+ return logger('error', slice.call(arguments));
+};
+
+module.exports = logger;
View
195 lib/tty.js
@@ -9,15 +9,14 @@
var path = require('path')
, fs = require('fs')
- , EventEmitter = require('events').EventEmitter;
+ , Stream = require('stream').Stream;
var express = require('express')
, io = require('socket.io')
, pty = require('pty.js');
-var proto = express.application || express.HTTPServer.prototype;
-
-var config = require('./config');
+var config = require('./config')
+ , logger = require('./logger');
/**
* Server
@@ -56,10 +55,13 @@ function Server(conf) {
});
this._init();
+
+ this.on('listening', function() {
+ self.log('Listening on port %s.', self.conf.port);
+ });
}
Server.prototype.__proto__ = express.createServer().__proto__;
-// Server.prototype.__proto__ = proto;
Server.prototype._init = function() {
this._init = function() {};
@@ -69,6 +71,7 @@ Server.prototype._init = function() {
};
Server.prototype.initMiddleware = function() {
+ var self = this;
var conf = this.conf;
this.use(function(req, res, next) {
@@ -85,18 +88,14 @@ Server.prototype.initMiddleware = function() {
next();
});
- this.use(this._auth);
+ this.use(function(req, res, next) {
+ return self._auth(req, res, next);
+ });
if (conf.static) {
this.use(express.static(conf.static));
}
- // var icon = conf.static + '/favicon.ico';
- // if (!conf.static || !path.existsSync(icon)) {
- // icon = __dirname + '/../static/favicon.ico';
- // }
- // this.use(express.favicon(icon));
-
// If there is a custom favicon in the custom
// static directory, this will be ignored.
this.use(express.favicon(__dirname + '/../static/favicon.ico'));
@@ -106,6 +105,10 @@ Server.prototype.initMiddleware = function() {
this.use(express.static(__dirname + '/../static'));
};
+Server.prototype.setAuth = function(func) {
+ this._auth = func;
+};
+
Server.prototype.initRoutes = function() {
var self = this;
this.get('/options.js', function(req, res, next) {
@@ -159,7 +162,7 @@ Server.prototype.initIO = function() {
Server.prototype.handleAuth = function(data, next) {
var io = this.io;
- data.__proto__ = EventEmitter.prototype;
+ data.__proto__ = Stream.prototype;
this._auth(data, null, function(err) {
data.user = data.remoteUser || data.user;
return !err
@@ -171,9 +174,6 @@ Server.prototype.handleAuth = function(data, next) {
Server.prototype.handleConnection = function(socket) {
var session = new Session(this, socket);
- //this.sessions[session.id] = session;
- //this.sessions.push(session);
-
socket.on('create', function(cols, rows, func) {
return session.handleCreate(cols, rows, func);
});
@@ -195,7 +195,6 @@ Server.prototype.handleConnection = function(socket) {
});
socket.on('disconnect', function() {
- //delete sessions[session.id];
return session.handleDisconnect();
});
};
@@ -210,11 +209,9 @@ Server.prototype._basicAuth = function() {
};
}
- if (conf.hooks && conf.hooks.auth) {
- return express.basicAuth(conf.hooks.auth);
- }
-
var crypto = require('crypto')
+ , users = conf.users
+ , hashedUsers = {}
, saidWarning;
function sha1(text) {
@@ -230,22 +227,24 @@ Server.prototype._basicAuth = function() {
}
function verify(user, pass, next) {
- var user = sha1(user)
+ var username = sha1(user)
, password;
- if (!Object.hasOwnProperty.call(conf.users, user)) {
+ if (!Object.hasOwnProperty.call(hashedUsers, username)) {
return next();
}
- password = conf.users[user];
+ password = hashedUsers[username];
+
+ if (sha1(pass) !== password) return next(true);
- next(null, sha1(pass) === password);
+ next(null, user);
}
// Hash everything for consistency.
- Object.keys(conf.users).forEach(function(name) {
- if (!saidWarning && !hashed(conf.users[name])) {
- self.log('Warning: You should sha1 your usernames/passwords.');
+ Object.keys(users).forEach(function(name) {
+ if (!saidWarning && !hashed(users[name])) {
+ self.warning('You should sha1 your user information.');
saidWarning = true;
}
@@ -253,36 +252,37 @@ Server.prototype._basicAuth = function() {
? sha1(name)
: name;
- conf.users[username] = !hashed(conf.users[name])
- ? sha1(conf.users[name])
- : conf.users[name];
-
- if (username !== name) delete conf.users[name];
+ hashedUsers[username] = !hashed(users[name])
+ ? sha1(users[name])
+ : users[name];
});
return express.basicAuth(verify);
};
Server.prototype.log = function() {
- if (this.conf.log === false) return;
- var args = Array.prototype.slice.call(arguments);
- args[0] = 'tty.js: ' + args[0];
- console.log.apply(console, args);
+ return this._log('log', slice.call(arguments));
};
Server.prototype.error = function() {
+ return this._log('error', slice.call(arguments));
+};
+
+Server.prototype.warning = function() {
+ return this._log('warning', slice.call(arguments));
+};
+
+Server.prototype._log = function(level, args) {
if (this.conf.log === false) return;
- var args = Array.prototype.slice.call(arguments);
- args[0] = 'tty.js: ' + args[0];
- console.error.apply(console, args);
+ args.unshift(level);
+ return logger.apply(null, args);
};
Server.prototype._listen = express.createServer().listen;
Server.prototype.listen = function(port, hostname, func) {
- return this._listen(
- port || this.conf.port || 8080,
- hostname || this.conf.hostname,
- func);
+ port = port || this.conf.port || 8080;
+ hostname = hostname || this.conf.hostname;
+ return this._listen(port, hostname, func);
};
/**
@@ -300,31 +300,64 @@ function Session(server, socket) {
var sessions = this.server.sessions;
var req = socket.handshake;
- this.id = req.user || Math.random() + '';
+ this.user = req.user;
+ this.id = req.user || this.uid();
- // Kill older session.
- if (conf.sessions && conf.users) {
+ // Kill/sync older session.
+ if (conf.syncSession) {
if (sessions[this.id]) {
- try {
- sessions[this.id].socket.disconnect();
- } catch (e) {
- ;
- }
+ this.sync(sessions[this.id].terms);
+ sessions[this.id].destroy();
}
- sessions[this.id] = session;
}
+
+ sessions[this.id] = this;
+
+ this.log('Session \x1b[1m%s\x1b[m created.', this.id);
}
+Session.uid = 0;
+Session.prototype.uid = function() {
+ if (this.server.conf.syncSession) {
+ var req = this.req;
+ return req.address.address
+ + ':' + req.address.port
+ + ':' + req.headers['user-agent'];
+ }
+ return Session.uid++ + '';
+};
+
+Session.prototype.destroy = function() {
+ try {
+ this.socket.disconnect();
+ } catch (e) {
+ ;
+ }
+};
+
Session.prototype.log = function() {
- var args = Array.prototype.slice.call(arguments);
- args[0] = 'Session [' + this.id + ']: ' + args[0];
- return this.server.log.apply(this.server, arguments);
+ return this._log('log', slice.call(arguments));
};
Session.prototype.error = function() {
- var args = Array.prototype.slice.call(arguments);
- args[0] = 'Session [' + this.id + ']: ' + args[0];
- return this.server.log.apply(this.server, arguments);
+ return this._log('error', slice.call(arguments));
+};
+
+Session.prototype.warning = function() {
+ return this._log('warning', slice.call(arguments));
+};
+
+Session.prototype._log = function(level, args) {
+ if (typeof args[0] !== 'string') args.unshift('');
+ var id = this.id.split(':')[0];
+ //args[0] = '\x1b[35m(' + id + ')\x1b[m ' + args[0];
+ args[0] = '\x1b[1m' + id + '\x1b[m ' + args[0];
+ return this.server._log(level, args);
+};
+
+Session.prototype.sync = function(terms) {
+ if (terms) this.terms = terms;
+ this.socket.emit('sync', this.terms);
};
Session.prototype.handleCreate = function(cols, rows, func) {
@@ -338,7 +371,8 @@ Session.prototype.handleCreate = function(cols, rows, func) {
, id;
if (len >= conf.limitPerUser || pty.total >= conf.limitGlobal) {
- return func('Terminal limit.');
+ this.warning('Terminal limit reached.');
+ return func({ error: 'Terminal limit.' });
}
term = pty.fork(conf.shell, conf.shellArgs, {
@@ -356,11 +390,11 @@ Session.prototype.handleCreate = function(cols, rows, func) {
});
term.on('close', function() {
- // make sure it closes
- // on the clientside
+ // Make sure it closes
+ // on the clientside.
socket.emit('kill', id);
- // ensure removal
+ // Ensure removal.
if (terms[id]) delete terms[id];
self.log(
@@ -368,10 +402,9 @@ Session.prototype.handleCreate = function(cols, rows, func) {
term.pty, term.fd);
});
- this.log(''
- + 'Created shell with pty (%s) master/slave'
- + ' pair (master: %d, pid: %d)',
- term.pty, term.fd, term.pid);
+ this.log(
+ 'Created pty (id: %s, master: %d, pid: %d).',
+ id, term.fd, term.pid);
return func(null, {
id: id,
@@ -383,8 +416,8 @@ Session.prototype.handleCreate = function(cols, rows, func) {
Session.prototype.handleData = function(id, data) {
var terms = this.terms;
if (!terms[id]) {
- this.error(''
- + 'Warning: Client attempting to'
+ this.warning(''
+ + 'Client attempting to'
+ ' write to a non-existent terminal.'
+ ' (id: %s)', id);
return;
@@ -413,6 +446,7 @@ Session.prototype.handleProcess = function(id, func) {
};
Session.prototype.handleDisconnect = function() {
+ var self = this;
var terms = this.terms;
var req = this.req;
var sessions = this.server.sessions;
@@ -426,15 +460,29 @@ Session.prototype.handleDisconnect = function() {
term.destroy();
}
- if (sessions[req.user]) delete sessions[this.id];
+ if (!this.server.conf.syncSession) {
+ if (sessions[req.user]) {
+ delete sessions[this.id];
+ }
+ } else {
+ // XXX This could be done differently.
+ var timeout = this.server.conf.sessionTimeout || 10 * 60 * 1000;
+ setTimeout(function() {
+ if (sessions[req.user]) {
+ delete sessions[self.id];
+ }
+ }, timeout);
+ }
- this.log('Client disconnected. Killing all pty\'s...');
+ this.log('Client disconnected. Killing all pty\'s.');
};
/**
* Helpers
*/
+var slice = Array.prototype.slice;
+
function sanitize(file) {
if (!file) return '';
file = file.split(' ')[0] || '';
@@ -463,7 +511,10 @@ function applyConfig() {
*/
exports = Server;
-exports.createServer = Server;
+exports.Server = Server;
+exports.Session = Session;
exports.config = config;
+exports.logger = logger;
+exports.createServer = Server;
module.exports = exports;
View
39 static/term.js
@@ -41,10 +41,11 @@ var window = this
* EventEmitter
*/
-function EventEmitter() {}
+function EventEmitter() {
+ this._events = {};
+}
EventEmitter.prototype.addListener = function(type, listener) {
- this._events = this._events || {};
this._events[type] = this._events[type] || [];
this._events[type].push(listener);
};
@@ -52,13 +53,13 @@ EventEmitter.prototype.addListener = function(type, listener) {
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.removeListener = function(type, listener) {
- if (!this._events || !this._events[type]) return;
+ if (!this._events[type]) return;
var obj = this._events[type]
, i = obj.length;
while (i--) {
- if (obj[i] === listener) {
+ if (obj[i] === listener || obj[i].listener === listener) {
obj.splice(i, 1);
return;
}
@@ -67,16 +68,22 @@ EventEmitter.prototype.removeListener = function(type, listener) {
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
+EventEmitter.prototype.removeAllListeners = function(type) {
+ if (this._events[type]) delete this._events[type];
+};
+
EventEmitter.prototype.once = function(type, listener) {
- this.on(type, function on() {
+ function on() {
var args = Array.prototype.slice.call(arguments);
this.removeListener(type, on);
return listener.apply(this, args);
- });
+ }
+ on.listener = listener;
+ return this.on(type, on);
};
EventEmitter.prototype.emit = function(type) {
- if (!this._events || !this._events[type]) return;
+ if (!this._events[type]) return;
var args = Array.prototype.slice.call(arguments, 1)
, obj = this._events[type]
@@ -88,6 +95,10 @@ EventEmitter.prototype.emit = function(type) {
}
};
+EventEmitter.prototype.listeners = function(type) {
+ return this._events[type] = this._events[type] || [];
+};
+
/**
* States
*/
@@ -107,11 +118,21 @@ var normal = 0
function Terminal(cols, rows, handler) {
EventEmitter.call(this);
+ var options;
+ if (typeof cols === 'object') {
+ options = cols;
+ cols = options.cols;
+ rows = options.rows;
+ handler = options.handler;
+ }
+ this._options = options || {};
+
this.cols = cols || Terminal.geometry[0];
this.rows = rows || Terminal.geometry[1];
- //if (handler) this.handler = handler;
- if (handler) this.on('data', handler);
+ if (handler) {
+ this.on('data', handler);
+ }
this.ybase = 0;
this.ydisp = 0;
View
19 static/tty.js
@@ -100,6 +100,21 @@ tty.open = function() {
tty.terms[id]._destroy();
});
+ tty.socket.on('sync', function(terms) {
+ console.log('Attempting to sync...');
+ console.log(terms);
+ tty.reset();
+ terms.forEach(function(term) {
+ var emit = tty.socket.emit;
+ tty.socket.emit = function() {};
+ var win = new Window;
+ Object.keys(term).forEach(function(key) {
+ win.tabs[0][key] = term[key];
+ });
+ tty.socket.emit = emit;
+ });
+ });
+
// We would need to poll the os on the serverside
// anyway. there's really no clean way to do this.
// This is just easier to do on the
@@ -762,6 +777,10 @@ Tab.prototype._ignoreNext = function() {
this.handler = function() {
this.handler = handler;
};
+ var showCursor = this.showCursor;
+ this.showCursor = function() {
+ this.showCursor = showCursor;
+ };
};
/**
Please sign in to comment.
Something went wrong with that request. Please try again.