Skip to content

Commit

Permalink
new version includes the field test app and more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
quartzjer committed Dec 4, 2013
1 parent c22b1a7 commit 1542009
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 85 deletions.
5 changes: 2 additions & 3 deletions .gitignore
@@ -1,5 +1,4 @@
node_modules
npm-debug.log
seed/*.json
fieldtest/*.json
fieldtest/debug.log
*.json
*.log
8 changes: 2 additions & 6 deletions README.md
Expand Up @@ -6,17 +6,13 @@ This module presents a simple high-level API for using [telehash](https://github

# Seeds

Telehash apps always need one or more seeds to bootstrap from, the default ones are in [seeds.json](seeds.json). You can run your own seed via:

`node seed/seed.js`
Telehash apps always need one or more seeds to bootstrap from, the default ones are in [seeds.json](seeds.json). You can run your own seed via `npm start` or manually via `node seed/seed.js`.

Take the output JSON, put it in an array and in your own seeds.json file, then load it with `.addSeeds("./seeds.json")`.

# "Field Test" Utility

A way to explore telehash is using the field test app which provides a command line of utilities to explore the DHT and connect to other hashnames:

`node fieldtest/tft.js`
There is a field test command line utility included to explore the DHT and connect to other hashnames, just run `node fieldtest/tft.js`.

# Library Interface

Expand Down
6 changes: 6 additions & 0 deletions fieldtest/README.md
@@ -0,0 +1,6 @@
Command-line telehash fieldtest utility
=======================================

Just run `node tft.js` and the first time it runs it will ask you for a nickname and store your identity in ./id.json (which can be overridden with `--id ./foo.json`).

Once running you will have a command line to look at the DHT, seek, ping, send test messages to other instances, create groups, etc. Type `help` to see a list of commands. All debug output is saved in ./debug.log.
File renamed without changes.
216 changes: 149 additions & 67 deletions fieldtest/tft.js
Expand Up @@ -6,10 +6,11 @@ var tele = require("../index");
var argv = require("optimist")
.usage("Usage: $0 --id id.json --seeds seeds.json")
.default("id", "./id.json")
.default("v", "./debug.log")
.argv;

// write all debug output to a log
var vlog = fs.createWriteStream(path.join(__dirname,"debug.log"), {flags:"a"});
var vlog = fs.createWriteStream(path.join(process.cwd(),argv.v), {flags:"a"});
vlog.write("starting with "+JSON.stringify(argv)+"\n");
tele.debug(function(){
var args = arguments;
Expand All @@ -20,16 +21,22 @@ tele.debug(function(){

// set up our readline interface
rl = require("readline").createInterface(process.stdin, process.stdout, null);
function log(line){
function log(){
// hacks!
rl.output.write("\x1b[2K\r");
console.log(line);
var args = arguments;
args = Object.keys(arguments).map(function(k){return args[k]});
console.log(args.join(" "));
rl._refreshLine()
}
process.stdin.on("keypress", function(s, key){
if(key && key.ctrl && key.name == "c") process.exit(0);
if(key && key.ctrl && key.name == "d") process.exit(0);
})

// load or generate our crypto id
var id;
var idfile = path.join(__dirname, argv.id);
var idfile = path.join(process.cwd(),argv.id);
if(fs.existsSync(idfile))
{
id = require(idfile);
Expand All @@ -51,35 +58,44 @@ var groups = {
get:function(id){
if(groups.ids[id]) return groups.ids[id];
var group = groups.ids[id] = {id:id,members:{}};
group.add = function(chan){ chan.group = group; };
group.add = function(chan){
chan.group = group;
group.members[chan.hashname] = chan;
};
return group;
}
};
var members = {};// TODO finish converting
var chat;

var me;
function init()
{
rl.setPrompt(id.nick+"> ");
rl.prompt();

chat = tele.hashname(id);
if(argv.seeds) chat.addSeeds(argv.seeds);
me = tele.hashname(id);
if(argv.seeds) me.addSeeds(argv.seeds);

chat.online(function(err){
log((err?err:"online as "+chat.hashname));
if(err) process.exit(0);
me.online(function(err){
log((err?err:"online as "+me.hashname));
});

chat.listen("chat", function(err, arg, chan, cb){
if(arg.js.group) groups.get(arg.js.group).add(chan);
handshake(false, arg, chan, cb);
me.listen("message", function(err, arg, chan, cb){
messageInit(false, arg, chan, cb);
chan.send({js:{nick:id.nick}});
});
chat.listen("members", function(err, arg, chan, cb){
me.listen("group", function(err, arg, chan, cb){
if(!arg.js.group) return log("missing group error from",chan.hashname);
groups.get(arg.js.group).add(chan);
groupInit(arg, chan);
chan.send({js:{nick:id.nick}});
cb();
});
me.listen("members", function(err, arg, chan, cb){
// send members in chunks
cb();
var group = groups.get(arg.js.group);
var mlist = Object.keys(group.members);
mlist.push(chat.hashname);
mlist.push(me.hashname); // always include yourself
while(mlist.length > 0)
{
var chunk = mlist.slice(0, 10);
Expand All @@ -90,84 +106,150 @@ function init()
});
}

function memberMesh(err, arg, chan, cb)
// intitial incoming or answer to outgoing message channel
var nicks = {};
function messageInit(err, arg, chan, cb)
{
if(err && err !== true) return log("error fetching members: "+err);
if(Array.isArray(arg.js.members)) arg.js.members.forEach(function(member){
if(members[member]) return;
if(member == chat.hashname) return;
var hn = chat.whois(member);
if(hn) hn.start("chat", {js:{nick:id.nick, room:room}}, handshake);
});
if(err) return log("message handshake err",err);
chan.nick = (arg.js.nick) ? arg.js.nick : chan.hashname.substr(0,6);
nicks[chan.nick] = chan;
log("m["+chan.nick+"] connected");
chan.callback = function(err, arg, chan, cbMessage){
if(arg && arg.js.message) log("m["+chan.nick+"]:",arg.js.message);
if(err){
log("m["+chan.nick+"] disconnected",err);
delete nicks[chan.nick];
}
cbMessage();
};
cb();
}

// intitial incoming or answer to outgoing chats
var nicks = {};
function handshake(err, arg, chan, cb)
// configure a channel for group mode
function groupInit(arg, chan)
{
if(err) return console.log("handshake err",err);
chan.nick = (arg.js.nick) ? arg.js.nick : chan.hashname.substr(0,6);
nicks[chan.nick] = chan.hashname;
if(!members[chan.hashname]) log(chan.nick+" joined");
members[chan.hashname] = chan;
log("g["+chan.group.id+"/"+chan.nick+"] joined");
chan.callback = function(err, arg, chan, cbMessage){
if(arg && arg.js.message) log("["+chan.nick+"] "+arg.js.message);
if(arg && arg.js.message) log("g["+chan.group.id+"/"+chan.nick+"]: "+arg.js.message);
if(err)
{
var msg = (err !== true)?" ("+err+")":"";
log(chan.nick+" left"+msg);
delete members[chan.hashname];
log("g["+chan.group.id+"/"+chan.nick+"] left",err);
delete chan.group.members[chan.hashname];
}
cbMessage();
};
cb();
}

function blast(msg)
{
Object.keys(members).forEach(function(member){
members[member].send({js:{"message":msg}});
});
}

// our chat handler
rl.on('line', function(line) {
if(line.indexOf("/") == 0) {
var parts = line.split(" ");
var cmd = parts.shift().substr(1);
if(cmds[cmd]) cmds[cmd](parts.join(" "));
else log("I don't know how to "+cmd);
}else if(line != "") blast(line);
var parts = line.split(" ");
var cmd = parts.shift();
if(cmds[cmd]) cmds[cmd](parts);
else log("I don't know how to "+cmd);
rl.prompt();
});

var cmds = {};
cmds.nick = function(nick){
id.nick = nick;
blast(false, nick);
rl.setPrompt(id.nick+"> ");
rl.prompt();
cmds.help = cmds["?"] = function(arg){
log("'quit|done'","exit the app");
log("'whoami'","your info");
log("'seek hashname'","look for that hashname in the DHT");
log("'ping hashname'","try to connect to and get response from that hashname");
log("'a|all'","show all connected hashnames");
log("'add hashname'","add a hashname to send messages to");
log("'m|message nick'","send a message to the nickname");
log("'w|who'","which nicknames are attached");
log("'join group'","create a group that others can join");
log("'join group hashname'","join a group that exists via that hashname");
log("'gw|gwho group'","see who's in the group");
log("'gm group'","send a message to the group");
}
cmds.quit = function(err){
log(err||"poof");
cmds.quit = cmds.exit = function(arg){
if(arg[0]) log(arg[0]);
process.exit();
}
cmds.whoami = function(){
log(room+"@"+chat.hashname);
}
cmds.who = cmds.whois = function(arg){
if(!arg) return Object.keys(members).forEach(cmds.who);
if(nicks[arg]) log(arg+" is "+nicks[arg]);
if(members[arg]) log(arg+" is "+members[arg].nick);
log("I am",id.nick,me.address);
}
cmds["42"] = function(){
log("I hash, therefore I am.");
}
cmds.add = function(arg){
var host = me.whois(arg[0]);
if(!host) return log("invalid hashname",arg[0]);
log("adding",host.hashname);
host.start("message", {js:{nick:id.nick}}, messageInit);
}
cmds.message = cmds.m = function(arg){
if(!nicks[arg[0]]) return log("unknown recipient",arg[0]);
nicks[arg.shift()].send({js:{message:arg.join(" ")}});
}
cmds.who = cmds.w = function()
{
Object.keys(nicks).forEach(function(nick){
log(nick,nicks[nick].hashname);
});
}
cmds.all = cmds.a = function()
{
Object.keys(me.lines).forEach(function(line){
var hn = me.lines[line];
log(hn.address,Object.keys(hn.chans).length);
});
}
cmds.gw = cmds.gwho = function(arg){
var group = groups.get(arg.shift());
Object.keys(group.members).forEach(function(member){
log(group.members[member].nick,group.members[member].hashname);
});
}
cmds.g = function(arg){
var group = groups.get(arg.shift());
Object.keys(group.members).forEach(function(member){
group.members[member].send({js:{message:arg.join(" ")}});
});
}
cmds.join = function(arg)
{
var parts = arg.split("@");
var host = chat.whois(parts[1]);
if(!host) return log("invalid id to join");
host.start("members", {js:{group:parts[0]}}, memberMesh);
var group = groups.get(arg[0]);
if(!arg[1]) return log("g["+group.id+"] created");
var host = me.whois(arg[1]);
if(!host) return log("invalid group hashname",arg[1]);
log("g["+group.id+"] fetching members");
host.start("members", {js:{group:group.id}}, function(err, arg, chan, cb)
{
if(err && err !== true) return log("group",group.id,"error fetching members",err);
if(Array.isArray(arg.js.members)) arg.js.members.forEach(function(member){
if(group.members[member]) return;
if(member == me.hashname) return;
var hn = me.whois(member);
if(!hn) return log("g["+group.id+"] invalid member",member);
hn.start("group", {js:{nick:id.nick, group:group.id}}, function(err, arg, chan, cb){
if(err) return log("message handshake err",err);
group.add(chan);
groupInit(arg, chan);
cb();
});
});
cb();
});
}
cmds.seek = function(arg)
{
var hn = me.whois(arg[0]);
if(!hn) return log("invalid hashname",arg[0]);
me.seek(hn, function(err){
if(err) return log("seek failed",hn.hashname,err);
log("seek",hn.hashname,JSON.stringify(hn.vias));
});
}
cmds.ping = function(arg)
{
var hn = me.whois(arg[0]);
if(!hn) return log("invalid hashname",arg[0]);
hn.seekping(function(err){
if(err) return log("ping failed",hn.hashname,err);
log("ping",hn.address);
});
}
7 changes: 6 additions & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "telehash",
"version": "0.1.3",
"version": "0.1.4",
"dependencies": {
"node-forge": "0.2.x",
"thjs":"0.0.x"
Expand All @@ -16,6 +16,11 @@
"tseed": "./seed/seed.js",
"tft": "./fieldtest/tft.js"
},
"scripts": {
"start":"./seed/seed.js",
"seed":"./seed/seed.js",
"tft":"./fieldtest/tft.js"
},
"keywords": [
"telehash",
"crypto",
Expand Down
13 changes: 5 additions & 8 deletions seed/README.md
@@ -1,11 +1,8 @@
hash-seed
=======
A minimal telehash seed
=======================

A minimal [telehash](http://telehash.org) seed.
Just run `node seed.js` to start it, and it will give you a JSON entry of it's information.

```
npm install
npm start
```
To be a seed you need a well-known / accessible IP address and port, so it tries to guess your network address and use that by default. To manually set either use `--ip 1.2.3.4 --port 5678`.

You can also do a `node seed.js -v` to see debug output, and `--seeds ./seeds.json` to have this seed connect/mesh with others.
You can also do a `-v` to see debug output.

0 comments on commit 1542009

Please sign in to comment.