Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Reports window error event. Better log messages. Global eval

  • Loading branch information...
commit 59c09d90de479b4fe3f2872123c62863394a4b2d 1 parent 5ad08ef
@claudioc authored
Showing with 179 additions and 111 deletions.
  1. +17 −5 README.md
  2. +162 −106 bin/jecho
View
22 README.md
@@ -17,11 +17,23 @@ asynchronous messages to the server. The `jecho.log()` obviously resembles the c
Features
--------
-jecho support bidirectional communication thanks to WebSockets. The support is provided for the legacy and current WebSocket specifications (tested on iOS 4, 5, 6 and some versions of Android).
-
-If for some reasons WebSockets are not supported, then you can still use the `jecho.log()` thanks to XMLHttpRequest and CORS. In this case you'll not be able to send commands to the browser, but just receive the output from jecho.log.
-
-The command line is handled by readline and the history is saved in ~/.jecho-history
+- supports bidirectional communication, thanks to WebSockets. The support is provided for the legacy and current WebSocket specifications (tested on iOS 4, 5, 6 and some versions of Android).
+- if for some reasons WebSockets are not supported, then you can still use the `jecho.log()` thanks to XMLHttpRequest and CORS. In this case you'll not be able to send commands to the browser, but just receive the output from jecho.log.
+- command line handled by readline with history (saved in ~/.jecho-history)
+- uses smart eval() - meaning that "var foobar" will create a real, global foobar
+- automatically catches and report JavaScript errors (not using window.onerror, so joy with Firefox)
+
+Output
+------
+
+Each output line is composed by four elements: a timestamp, the IP address of the client, and indicator and the message
+The indicator element is a symbol with the following meanings:
+- `>>` a log message has been received from the client (unattended)
+- `<>` a response to a query has been received by the client
+- `>!` an error has been captured by the client
+- `--` internal message (not from the client)
+
+Please note the `undefined` and `null` values are printed as `JS UNDEFINED` and `JS NULL`
Limitations
-----------
View
268 bin/jecho
@@ -47,81 +47,6 @@ if (program.info) {
process.exit(0);
}
-var Readliner = function() {
-
- events.EventEmitter.call(this);
-
- try {
- this.interface = readline.createInterface({
- input: process.stdin,
- output: process.stdout
- // completer: completer
- });
- } catch (e) { // For node 0.6.x
- this.interface = readline.createInterface(process.stdin, process.stdout, null);
- }
-
- this.historyFile = '.jecho-history';
-
- this.previousLine = null;
-
- this.interface.on('SIGINT', (function() {
- this.interface.close();
- console.log("\n<Press ^C again to exit>");
- }).bind(this));
-
- this.interface.on('line', (function(line) {
- this.emit("line", line);
- this.writeHistory(line);
- }).bind(this));
-
- /*
- function completer(line) {
- var completions = ''.split(' ')
- var hits = completions.filter(function(c) { return c.indexOf(line) == 0 })
- return [hits.length ? hits : completions, line]
- }
- */
-}
-
-util.inherits(Readliner, events.EventEmitter);
-
-Readliner.prototype.prompt = function() {
- this.interface.prompt(true);
-}
-
-Readliner.prototype.loadHistory = function() {
- // 0.8.x moved exists on fs
- var existsSync = (fs.existsSync) ? fs.existsSync : path.existsSync;
-
- var filePath = path.join(process.env.HOME, this.historyFile);
- if (!existsSync(filePath)) {
- return [];
- }
- var cmdHistory = fs.readFileSync(filePath, 'utf8').split('\n');
- // filter and reverse and limit
- cmdHistory = cmdHistory.filter(function(line) { return line.trim().length > 0; });
- // @todo: also filter two identical commands one after another
- return cmdHistory.reverse().slice(0, 200);
-}
-
-Readliner.prototype.writeHistory = function(line) {
- if (line.trim().length>0 && !(this.previousLine && this.previousLine == line)) {
- this.previousLine = line;
- this.history.write(line + '\n');
- }
-}
-
-Readliner.prototype.start = function() {
- this.history = fs.createWriteStream(path.join(process.env.HOME, this.historyFile), { flags: 'a' });
- this.interface.history = this.loadHistory();
- this.interface.prompt(true);
-}
-
-Readliner.prototype.setPrompt = function(p) {
- this.interface.setPrompt(p);
-}
-
function main() {
var port = program.port || 6767;
@@ -156,7 +81,7 @@ function main() {
body += data;
});
request.on('end', function () {
- output(body, request.connection.remoteAddress);
+ output(decodeMsg(body), request.connection.remoteAddress);
});
break;
@@ -166,15 +91,14 @@ function main() {
lines[i++] = ";(function(window) {";
lines[i++] = "var jecho = {";
- for (var x in jecho) {
- asString = jecho[x].toString();
+ for (var x in jechoClient) {
+ asString = jechoClient[x].toString();
asString = asString.replace("\"%USEXHR%\"", !!program.xhr);
asString = asString.replace("%VERSION%", "\"" + version + "\"");
- lines[i++] = " " + x + ": " + ( util.isArray(jecho[x]) ? ( "[" + asString + "]" ) : asString ) + ",";
+ lines[i++] = " " + x + ": " + ( util.isArray(jechoClient[x]) ? ( "[" + asString + "]" ) : asString ) + ",";
}
lines[i++] = "};";
- lines[i++] = "jecho.init();";
- lines[i++] = "window.jecho = jecho;";
+ lines[i++] = "window.jecho = jecho.init();";
lines[i++] = "})(this);";
content = lines.join("\n");
mime = "text/javascript";
@@ -211,7 +135,7 @@ function main() {
currentClient = conn;
- output(msg, conn.remoteAddress);
+ output(decodeMsg(msg), conn.remoteAddress);
});
@@ -257,7 +181,7 @@ function main() {
prompt(true);
if (currentClient) {
- currentClient.send( '? ' + line );
+ currentClient.send( '! ' + line );
}
});
@@ -284,9 +208,41 @@ function main() {
subject = "<System>";
}
+ var dir, text;
+
+ if (typeof msg === 'string' || typeof msg === 'undefined') {
+ dir = '--';
+ text = msg;
+ } else {
+ switch ( msg.type ) {
+
+ case '!':
+ dir = '<>';
+ text = JSON.stringify(msg.data, undefined, 2);
+ break;
+
+ case 'l':
+ dir = '>>';
+ text = JSON.stringify(msg.data, undefined, 2);
+ break;
+
+ case 'e':
+ dir = '>!';
+ text = msg.data;
+ break;
+
+ default:
+ dir = '??';
+ text = "" + msg.data;
+ break;
+
+ }
+
+ }
+
var d = (new Date()).toString().match(/\d+:\d+:\d+/)[0].color('white');
- util.print((useNL ? "\n" : "") + d + " - " + subject.color("cyan") + " - " + msg.color("yellow") + "\n");
+ util.print((useNL ? "\n" : "") + d + " - " + subject.color("cyan") + " " + dir + " " + text.color("yellow") + "\n");
clearTimeout(prompto);
prompto = setTimeout(function() {
@@ -295,11 +251,21 @@ function main() {
useNL = false;
}
+ function decodeMsg(msg) {
+ var payload;
+ try {
+ payload = JSON.parse(msg);
+ } catch(e) {
+ payload = 'ERR: decoding message';
+ }
+ return payload;
+ }
+
}
// This is going to run in the Browser
-var jecho = {
+var jechoClient = {
initialized: false,
@@ -315,16 +281,28 @@ var jecho = {
this.serverURL = this.getServerURL();
+ if (window.addEventListener) {
+ window.addEventListener('error', function( e ) {
+ var text = "JS error captured: ";
+ if (e.message) {
+ text += "[" + e.message + "] in [" + e.filename + ":" + e.lineno + "]";
+ } else {
+ text += "[unable to fetch error details. Try listening to window.onerror]"
+ }
+ _self.send('e', text);
+ }, false);
+ }
+
if (!this.useXHR) {
this.wsocket = new WebSocket(this.serverURL.replace("http", "ws"));
this.wsocket.addEventListener("open", function(event) {
- _self.wsocket.send(navigator.userAgent);
+ _self.wsSend('l', navigator.userAgent);
for (var i=0; i < _self.buffer.length; i++) {
- _self.wsocket.send( _self.buffer[i] );
+ _self.wsSend( _self.buffer[i].type, _self.buffer[i].data );
}
_self.buffer.length = 0;
@@ -333,24 +311,24 @@ var jecho = {
this.wsocket.addEventListener("message", function(event) {
- var command = event.data.split(" ");
- var result;
+ var command = event.data.split(" "), args;
+ var result, fn;
switch (command[0]) {
- case '?':
+ case '!':
+ args = command.slice(1).join(" ");
try {
- result = eval( command.slice(1).join(" ") );
- _self.wsSend(JSON.stringify(result, undefined, 2));
+ (function() { result = window.eval.call(window, args); })();
+ _self.wsSend( '!', result );
} catch(e) {
if (typeof result != 'undefined') {
- _self.wsSend(JSON.stringify(Object.getOwnPropertyNames(result).sort(), undefined, 2));
+ _self.wsSend( '!' , Object.getOwnPropertyNames(result).sort() );
} else {
- _self.wsSend("ERR: " + e.message);
+ _self.wsSend( 'e', "Error: " + e.message);
}
}
break;
-
}
});
@@ -359,32 +337,35 @@ var jecho = {
});
} else {
- this.xhrSend(navigator.userAgent);
+ this.send('l', navigator.userAgent);
}
this.initialized = true;
+
+ return this;
},
- wsSend: function(m) {
+ wsSend: function(type, data) {
+
+ var msg = { type: type, data: (typeof data == 'undefined' ? 'JS UNDEFINED' : ( data === null ? 'JS NULL' : data ) ) };
if (this.wsocket.readyState === 0) {
- this.buffer.push(m);
+ this.buffer.push(msg);
return;
}
- this.wsocket.send(m);
+ this.wsocket.send( JSON.stringify( msg ) );
},
- xhrSend: function(m) {
- var xhr;
- xhr = new XMLHttpRequest();
+ xhrSend: function( type, data ) {
+ var xhr = new XMLHttpRequest();
xhr.open('POST', this.serverURL + 'log', true);
xhr.setRequestHeader('Content-Type', 'text/plain');
- xhr.send(m);
+ xhr.send( JSON.stringify( { type: type, data: data } ) );
},
- send: function(m) {
- this.useXHR ? this.xhrSend(m) : this.wsSend(m);
+ send: function( type, data ) {
+ this.useXHR ? this.xhrSend( type, data ) : this.wsSend( type, data );
},
log: function(m) {
@@ -393,7 +374,7 @@ var jecho = {
this.init();
}
- this.send(JSON.stringify(m, undefined, 2));
+ this.send( 'l', m );
},
safariVersion: function() {
@@ -433,6 +414,81 @@ var jecho = {
}
};
+var Readliner = function() {
+
+ events.EventEmitter.call(this);
+
+ try {
+ this.interface = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout
+ // completer: completer
+ });
+ } catch (e) { // For node 0.6.x
+ this.interface = readline.createInterface(process.stdin, process.stdout, null);
+ }
+
+ this.historyFile = '.jecho-history';
+
+ this.previousLine = null;
+
+ this.interface.on('SIGINT', (function() {
+ this.interface.close();
+ console.log("\n<Press ^C again to exit>");
+ }).bind(this));
+
+ this.interface.on('line', (function(line) {
+ this.emit("line", line);
+ this.writeHistory(line);
+ }).bind(this));
+
+ /*
+ function completer(line) {
+ var completions = ''.split(' ')
+ var hits = completions.filter(function(c) { return c.indexOf(line) == 0 })
+ return [hits.length ? hits : completions, line]
+ }
+ */
+}
+
+util.inherits(Readliner, events.EventEmitter);
+
+Readliner.prototype.prompt = function() {
+ this.interface.prompt(true);
+}
+
+Readliner.prototype.loadHistory = function() {
+ // 0.8.x moved exists on fs
+ var existsSync = (fs.existsSync) ? fs.existsSync : path.existsSync;
+
+ var filePath = path.join(process.env.HOME, this.historyFile);
+ if (!existsSync(filePath)) {
+ return [];
+ }
+ var cmdHistory = fs.readFileSync(filePath, 'utf8').split('\n');
+ // filter and reverse and limit
+ cmdHistory = cmdHistory.filter(function(line) { return line.trim().length > 0; });
+ // @todo: also filter two identical commands one after another
+ return cmdHistory.reverse().slice(0, 200);
+}
+
+Readliner.prototype.writeHistory = function(line) {
+ if (line.trim().length>0 && !(this.previousLine && this.previousLine == line)) {
+ this.previousLine = line;
+ this.history.write(line + '\n');
+ }
+}
+
+Readliner.prototype.start = function() {
+ this.history = fs.createWriteStream(path.join(process.env.HOME, this.historyFile), { flags: 'a' });
+ this.interface.history = this.loadHistory();
+ this.interface.prompt(true);
+}
+
+Readliner.prototype.setPrompt = function(p) {
+ this.interface.setPrompt(p);
+}
+
// Extends the core String object to add a simple ANSI colouring
// https://github.com/Yuffster/npm-string-ansi/blob/master/string-ansi.js
String.prototype.color = function() {
Please sign in to comment.
Something went wrong with that request. Please try again.