more WIP for roster

pull/1/head
ansuz 5 years ago
parent 84518c0775
commit 073543fe3d

@ -10,6 +10,7 @@ var Rpc = require("../../www/common/rpc");
var Hash = require("../../www/common/common-hash"); var Hash = require("../../www/common/common-hash");
var CpNetflux = require("../../www/bower_components/chainpad-netflux"); var CpNetflux = require("../../www/bower_components/chainpad-netflux");
var Roster = require("./roster"); var Roster = require("./roster");
var Util = require("../../lib/common-util");
var createMailbox = function (config, cb) { var createMailbox = function (config, cb) {
var webchannel; var webchannel;
@ -68,6 +69,10 @@ var createUser = function (config, cb) {
return void cb(err); return void cb(err);
} }
user = client; user = client;
user.destroy = Util.mkEvent(true);
user.destroy.reg(function () {
user.network.disconnect();
});
})); }));
}).nThen(function (w) { }).nThen(function (w) {
// make all the parameters you'll need // make all the parameters you'll need
@ -87,6 +92,9 @@ var createUser = function (config, cb) {
return void console.error('ANON_RPC_CONNECT_ERR'); return void console.error('ANON_RPC_CONNECT_ERR');
} }
user.anonRpc = rpc; user.anonRpc = rpc;
user.destroy.reg(function () {
user.anonRpc.destroy();
});
})); }));
Pinpad.create(network, user.edKeys, w(function (err, rpc) { Pinpad.create(network, user.edKeys, w(function (err, rpc) {
@ -97,6 +105,9 @@ var createUser = function (config, cb) {
return console.log('RPC_CONNECT_ERR'); return console.log('RPC_CONNECT_ERR');
} }
user.rpc = rpc; user.rpc = rpc;
user.destroy.reg(function () {
user.rpc.destroy();
});
})); }));
Pinpad.create(network, config.teamEdKeys, w(function (err, rpc) { Pinpad.create(network, config.teamEdKeys, w(function (err, rpc) {
@ -106,6 +117,9 @@ var createUser = function (config, cb) {
return console.log('RPC_CONNECT_ERR'); return console.log('RPC_CONNECT_ERR');
} }
user.team_rpc = rpc; user.team_rpc = rpc;
user.destroy.reg(function () {
user.team_rpc.destroy();
});
})); }));
}).nThen(function (w) { }).nThen(function (w) {
user.rpc.reset([], w(function (err, hash) { user.rpc.reset([], w(function (err, hash) {
@ -169,7 +183,7 @@ var createUser = function (config, cb) {
return void cb(err); return void cb(err);
} }
console.log('PIN_RESPONSE', hash); //console.log('PIN_RESPONSE', hash);
if (hash[0] === EMPTY_ARRAY_HASH) { throw new Error("PIN_DIDNT_WORK"); } if (hash[0] === EMPTY_ARRAY_HASH) { throw new Error("PIN_DIDNT_WORK"); }
user.latestPinHash = hash; user.latestPinHash = hash;
@ -201,7 +215,7 @@ var createUser = function (config, cb) {
} }
if (hash[0] !== EMPTY_ARRAY_HASH) { if (hash[0] !== EMPTY_ARRAY_HASH) {
console.log('UNPIN_RESPONSE', hash); //console.log('UNPIN_RESPONSE', hash);
throw new Error("UNPIN_DIDNT_WORK"); throw new Error("UNPIN_DIDNT_WORK");
} }
user.latestPinHash = hash[0]; user.latestPinHash = hash[0];
@ -215,15 +229,14 @@ var createUser = function (config, cb) {
} }
})); }));
}).nThen(function () { }).nThen(function () {
user.cleanup = function (cb) { user.cleanup = function (cb) {
//console.log("Destroying user");
// TODO remove your mailbox // TODO remove your mailbox
user.destroy.fire();
cb = cb; cb = cb;
}; };
cb(void 0, user); cb(void 0, user);
}); });
}; };
@ -233,8 +246,7 @@ var alice, bob, oscar;
var sharedConfig = { var sharedConfig = {
teamEdKeys: makeEdKeys(), teamEdKeys: makeEdKeys(),
teamCurveKeys: makeCurveKeys(), teamCurveKeys: makeCurveKeys(),
rosterChannel: Hash.createChannelId(), rosterSeed: Crypto.Team.createSeed(),
//rosterHash: makeRosterHash(),
}; };
nThen(function (w) { nThen(function (w) {
@ -249,7 +261,6 @@ nThen(function (w) {
})); }));
}).nThen(function (w) { }).nThen(function (w) {
// TODO oscar creates the team roster // TODO oscar creates the team roster
//Roster = Roster;
// user edPublic (for ownership) // user edPublic (for ownership)
// user curve keys (for encryption and authentication) // user curve keys (for encryption and authentication)
@ -261,19 +272,11 @@ nThen(function (w) {
// network // network
// owners: // owners:
/* var rosterKeys = Crypto.Team.deriveMemberKeys(sharedConfig.rosterSeed, oscar.curveKeys);
var team = {
curve: sharedConfig.teamCurveKeys,
ed: sharedConfig.teamEdKeys,
};
*/
var rosterSeed = Crypto.Team.createSeed();
var rosterKeys = Crypto.Team.deriveMemberKeys(rosterSeed, oscar.curveKeys);
Roster.create({ Roster.create({
network: oscar.network, network: oscar.network,
channel: rosterKeys.channel, //sharedConfig.rosterChannel, channel: rosterKeys.channel,
owners: [ owners: [
oscar.edKeys.edPublic oscar.edKeys.edPublic
], ],
@ -286,32 +289,37 @@ nThen(function (w) {
return void console.trace(err); return void console.trace(err);
} }
oscar.roster = roster; oscar.roster = roster;
oscar.destroy.reg(function () {
roster.stop();
});
})); }));
}).nThen(function (w) { }).nThen(function (w) {
var roster = oscar.roster; var roster = oscar.roster;
roster.on('change', function () { oscar.lastKnownHash = -1;
setTimeout(function () {
console.log("\nCHANGE"); roster.on('change', function () {
console.log("roster.getState()", roster.getState()); oscar.currentRoster = roster.getState();
console.log(); //console.log("new state = %s\n", JSON.stringify(oscar.currentRoster));
}); }).on('checkpoint', function (hash) {
oscar.lastKnownHash = hash;
}); });
var state = roster.getState(); //var state = roster.getState();
console.log("CURRENT ROSTER STATE:", state); //console.log("CURRENT ROSTER STATE:", state);
roster.init({ roster.init({
name: oscar.name, displayName: oscar.name,
//profile: '', //profile: '',
// mailbox: '', // mailbox: '',
//title: '', //title: '',
}, w(function (err) { }, w(function (err) {
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
//console.log("INITIALIZED");
})); }));
}).nThen(function (w) { }).nThen(function (w) {
console.log("ALICE && BOB"); //console.log("ALICE && BOB");
createUser(sharedConfig, w(function (err, _alice) { createUser(sharedConfig, w(function (err, _alice) {
if (err) { if (err) {
w.abort(); w.abort();
@ -319,7 +327,7 @@ nThen(function (w) {
} }
alice = _alice; alice = _alice;
alice.name = 'alice'; alice.name = 'alice';
console.log("Initialized Alice"); //console.log("Initialized Alice");
})); }));
createUser(sharedConfig, w(function (err, _bob) { createUser(sharedConfig, w(function (err, _bob) {
if (err) { if (err) {
@ -328,29 +336,63 @@ nThen(function (w) {
} }
bob = _bob; bob = _bob;
bob.name = 'bob'; bob.name = 'bob';
console.log("Initialized Bob"); //console.log("Initialized Bob");
})); }));
}).nThen(function () { }).nThen(function (w) {
setTimeout(w(), 500);
}).nThen(function (w) {
// Alice loads the roster...
var rosterKeys = Crypto.Team.deriveMemberKeys(sharedConfig.rosterSeed, alice.curveKeys);
Roster.create({
network: alice.network,
channel: rosterKeys.channel,
//owners: [], // Alice doesn't know who the owners might be...
keys: rosterKeys,
anon_rpc: alice.anonRpc,
lastKnownHash: void 0, // alice should fetch everything from the beginning of time...
}, w(function (err, roster) {
if (err) {
w.abort();
return void console.error(err);
}
alice.roster = roster;
alice.destroy.reg(function () {
roster.stop();
});
if (JSON.stringify(alice.roster.getState()) !== JSON.stringify(oscar.roster.getState())) {
console.error("Alice and Oscar have different roster states!");
throw new Error();
} else {
console.log("Alice and Oscar have the same roster state");
}
}));
}).nThen(function (w) {
// TODO oscar adds alice and bob to the team as members // TODO oscar adds alice and bob to the team as members
var roster = oscar.roster; var roster = oscar.roster;
var data = {}; var data = {};
data[alice.curveKeys.curvePublic] = { data[alice.curveKeys.curvePublic] = {
name: alice.name, displayName: alice.name,
role: 'MEMBER', // role: 'MEMBER', // MEMBER is implicit
notifications: '',
}; };
data[bob.curveKeys.curvePublic] = { data[bob.curveKeys.curvePublic] = {
name: bob.name, displayName: bob.name,
role: 'MEMBER', //role: 'MEMBER',
notifications: '',
}; };
roster.add(data, function (err) { roster.add(data, w(function (err) {
if (err) { if (err) { return void console.error(err); }
return void console.error(err); //console.log("SENT ADD COMMAND");
} }));
console.log("SENT ADD COMMAND");
});
}).nThen(function () { }).nThen(function () {
// TODO alice and bob describe themselves... // TODO alice and bob describe themselves...
}).nThen(function () { }).nThen(function () {
@ -368,7 +410,6 @@ nThen(function (w) {
text: "CAMEMBERT", text: "CAMEMBERT",
} }
}), bob.curveKeys.curvePublic); }), bob.curveKeys.curvePublic);
alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [bob.mailboxChannel, message], w(function (err, response) { alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [bob.mailboxChannel, message], w(function (err, response) {
if (err) { if (err) {
return void console.error(err); return void console.error(err);
@ -410,8 +451,11 @@ nThen(function (w) {
}); });
}).nThen(function () { }).nThen(function () {
alice.shutdown(); //alice.shutdown();
bob.shutdown(); //bob.shutdown();
alice.cleanup();
bob.cleanup();
oscar.cleanup();
}); });

@ -22,6 +22,10 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
} }
*/ */
var isMap = function (obj) {
return Boolean(obj && typeof(obj) === 'object' && !Array.isArray(obj));
};
var canCheckpoint = function (author, state) { var canCheckpoint = function (author, state) {
// if you're here then you've received a checkpoint message // if you're here then you've received a checkpoint message
// that you don't necessarily trust. // that you don't necessarily trust.
@ -90,6 +94,11 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
return false; return false;
}; };
var canUpdateMetadata = function (author, state) {
var authorRole = Util.find(state, [author, 'role']);
return Boolean(authorRole && ['OWNER', 'ADMIN'].indexOf(authorRole) !== -1);
};
var shouldCheckpoint = function (state) { var shouldCheckpoint = function (state) {
// //
@ -123,30 +132,29 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
throw new Error("CANNOT_ADD_TO_UNITIALIZED_ROSTER"); throw new Error("CANNOT_ADD_TO_UNITIALIZED_ROSTER");
} }
// XXX reject if not all of these are present // iterate over everything and make sure it is valid, throw if not
// displayName
// notifications (channel)
// XXX if no role is passed, assume MEMBER
var changed = false;
Object.keys(args).forEach(function (curve) { Object.keys(args).forEach(function (curve) {
// FIXME only allow valid curve keys, anything else is pollution // FIXME only allow valid curve keys, anything else is pollution
if (curve.length !== 44) { if (!isValidId(curve)) {
console.log(curve, curve.length); console.log(curve, curve.length);
throw new Error("INVALID_CURVE_KEY"); throw new Error("INVALID_CURVE_KEY");
} }
// reject commands where the members are not proper objects
if (!isMap(args[curve])) { throw new Error("INVALID_CONTENT"); }
if (roster.state[curve]) { throw new Error("ALREADY_PRESENT"); }
var data = args[curve]; var data = args[curve];
// if no role was provided, assume MEMBER
if (typeof(data.role) !== 'string') { data.role = 'MEMBER'; }
if (typeof(data.displayName) !== 'string') { throw new Error("DISPLAYNAME_REQUIRED"); }
if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); }
});
// ignore anything that isn't a proper object var changed = false;
if (!data || typeof(data) !== 'object' || Array.isArray(data)) { // then iterate again and apply it
return; Object.keys(args).forEach(function (curve) {
} var data = args[curve];
// ignore instructions to ADD someone who is already in the roster
if (roster.state[curve]) { return; }
if (!canAddRole(author, data.role, roster.state)) { return; } if (!canAddRole(author, data.role, roster.state)) { return; }
// this will result in a change // this will result in a change
@ -166,15 +174,12 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
var changed = false; var changed = false;
args.forEach(function (curve) { args.forEach(function (curve) {
if (isValidId(curve)) { throw new Error("INVALID_CURVE_KEY"); } if (!isValidId(curve)) { throw new Error("INVALID_CURVE_KEY"); }
// don't try to remove something that isn't there // don't try to remove something that isn't there
if (!roster.state[curve]) { return; } if (!roster.state[curve]) { return; }
var role = roster.state[curve].role; var role = roster.state[curve].role;
if (!canRemoveRole(author, role, roster.state)) { return; } if (!canRemoveRole(author, role, roster.state)) { return; }
changed = true; changed = true;
delete roster.state[curve]; delete roster.state[curve];
}); });
@ -187,42 +192,47 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
} }
if (typeof(roster.state) === 'undefined') { if (typeof(roster.state) === 'undefined') {
throw new Error("CANNOT_DESCRIBE_MEMBERS_OF_UNITIALIZED_ROSTER"); throw new Error("NOT_READY");
} }
var changed = false; // iterate over all the data and make sure it is valid, throw otherwise
Object.keys(args).forEach(function (curve) { Object.keys(args).forEach(function (curve) {
if (!isValidId(curve)) { return; } if (!isValidId(curve)) { throw new Error("INVALID_ID"); }
if (!roster.state[curve]) { return; } if (!roster.state[curve]) { throw new Error("NOT_PRESENT"); }
if (!canDescribeTarget(author, curve, roster.state)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
var data = args[curve];
if (!isMap(data)) { throw new Error("INVALID_ARGUMENTS"); }
var current = Util.clone(roster.state[curve]);
if (!canDescribeTarget(author, curve, roster.state)) { return; } // DESCRIBE commands must initialize a displayName if it isn't already present
if (typeof(current.displayName) !== 'string' && typeof(data.displayName) !== 'string') { throw new Error('DISPLAYNAME_REQUIRED'); }
// DESCRIBE commands must initialize a mailbox channel if it isn't already present
if (typeof(current.notifications) !== 'string' && typeof(data.displayName) !== 'string') { throw new Error('NOTIFICATIONS_REQUIRED'); }
});
var changed = false;
// then do a second pass and apply it if there were changes
Object.keys(args).forEach(function (curve) {
var current = Util.clone(roster.state[curve]);
var data = args[curve]; var data = args[curve];
if (!data || typeof(data) !== 'object' || Array.isArray(data)) { return; }
var current = roster.state[curve];
Object.keys(data).forEach(function (key) { Object.keys(data).forEach(function (key) {
if (current[key] === data[key]) { return; } if (current[key] === data[key]) { return; }
changed = true;
current[key] = data[key]; current[key] = data[key];
}); });
});
return changed;
/* if (Sortify(current) !== Sortify(roster.state[curve])) {
args: { changed = true;
userkey: { roster.state[curve] = current;
field: newValue
},
} }
*/ });
// owners can update information about any team member return changed;
// admins can update information about members
// members can update information about themselves
// non-members cannot update anything
//roster = roster;
}; };
// XXX what about concurrent checkpoints? Let's solve for race conditions... // XXX what about concurrent checkpoints? Let's solve for race conditions...
@ -255,14 +265,29 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
return true; return true;
}; };
// describe the team {name, description}, (only admin/owner) // only admin/owner can change group metadata
commands.TOPIC = function (/* args, author, roster */) { commands.METADATA = function (args, author, roster) {
if (!isMap(args)) { throw new Error("INVALID_ARGS"); }
}; if (!canUpdateMetadata(author, roster.state)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
// validate inputs
Object.keys(args).forEach(function (k) {
// can't set metadata to anything other than strings
// use empty string to unset a value if you must
if (typeof(args[k]) !== 'string') { throw new Error("INVALID_ARGUMENTS"); }
});
// add a link to an avatar (only owner/admin can do this) var changed = false;
commands.AVATAR = function (/* args, author, roster */) { // {topic, name, avatar} are all strings...
Object.keys(args).forEach(function (k) {
// ignore things that won't cause changes
if (args[k] === roster.metadata[k]) { return; }
changed = true;
roster.metadata[k] = args[k];
});
return changed;
}; };
var handleCommand = function (content, author, roster) { var handleCommand = function (content, author, roster) {
@ -276,12 +301,8 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
return commands[command](content[1], author, roster); return commands[command](content[1], author, roster);
}; };
var clone = function (o) {
return JSON.parse(JSON.stringify(o));
};
var simulate = function (content, author, roster) { var simulate = function (content, author, roster) {
return handleCommand(content, author, clone(roster)); return handleCommand(content, author, Util.clone(roster));
}; };
var getMessageId = function (msgString) { var getMessageId = function (msgString) {
@ -298,8 +319,7 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
if (!config.anon_rpc) { return void cb("EXPECTED_ANON_RPC"); } if (!config.anon_rpc) { return void cb("EXPECTED_ANON_RPC"); }
var response = Util.response();
var anon_rpc = config.anon_rpc; var anon_rpc = config.anon_rpc;
@ -308,7 +328,16 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
var me = keys.myCurvePublic; var me = keys.myCurvePublic;
var channel = config.channel; var channel = config.channel;
var ref = {}; var ref = {
// topic, name, and avatar are all guaranteed to be strings, though they might be empty
metadata: {
topic: '',
name: '',
avatar: '',
},
internal: {},
};
var roster = {}; var roster = {};
var events = { var events = {
@ -319,25 +348,46 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
roster.on = function (key, handler) { roster.on = function (key, handler) {
if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); } if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); }
events[key].reg(handler); events[key].reg(handler);
return roster;
}; };
roster.off = function (key, handler) { roster.off = function (key, handler) {
if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); } if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); }
events[key].unreg(handler); events[key].unreg(handler);
return roster;
};
roster.once = function (key, handler) {
if (typeof(events[key]) !== 'object') { throw new Error("unsupported event"); }
var f = function () {
handler.apply(null, Array.prototype.slice.call(arguments));
events[key].unreg(f);
};
events[key].reg(f);
return roster;
}; };
roster.getState = function () { roster.getState = function () {
return ref.state; if (!isMap(ref.state)) { return; }
// XXX return parent element instead of .state ?
return Util.clone(ref.state);
}; };
// XXX you must be able to 'leave' a roster session var webChannel;
roster.stop = function () { roster.stop = function () {
// shut down the chainpad-netflux session and... if (webChannel && typeof(webChannel.leave) === 'function') {
// cpNf.leave(); webChannel.leave();
} else {
console.log("FAILED TO LEAVE");
}
}; };
var ready = false; var ready = false;
var onReady = function (/* info */) { var onReady = function (info) {
//console.log("READY");
webChannel = info;
ready = true; ready = true;
cb(void 0, roster); cb(void 0, roster);
}; };
@ -359,50 +409,60 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
console.log("ROSTER CONNECTED"); console.log("ROSTER CONNECTED");
}; };
// XXX reuse code from RPC ? var isReady = function () {
var pending = {}; return Boolean(ready && me);
//var timeouts = {}; };
var onMessage = function (msg, user, vKey, isCp , hash, author) { var onMessage = function (msg, user, vKey, isCp , hash, author) {
//console.log("onMessage");
//console.log(typeof(msg), msg);
var parsed = Util.tryParse(msg); var parsed = Util.tryParse(msg);
if (!parsed) { return void console.error("could not parse"); } if (!parsed) { return void console.error("could not parse"); }
var changed; var changed;
var error;
try { try {
changed = handleCommand(parsed, author, ref); changed = handleCommand(parsed, author, ref);
} catch (err) { } catch (err) {
console.error(err); error = err;
} }
var id = getMessageId(hash); var id = getMessageId(hash);
if (typeof(pending[id]) === 'function') {
// it was your message, execute a callback if (response.expected(id)) {
if (!changed) { if (error) { return void response.handle(id, [error]); }
pending[id]("NO_CHANGE"); try {
} else { if (!changed) {
pending[id](void 0, clone(roster.state)); response.handle(id, ['NO_CHANGE']);
} else {
response.handle(id, [void 0, roster.getState()]);
}
} catch (err) {
console.log('CAUGHT', err);
} }
} else {
// it was not your message, or it timed out...
// execute change ?
console.log("HASH", hash);
} }
if (changed) { events.change.fire(); } /*
else {
if (isReady()) {
console.log("unexpected message [%s]", hash, msg);
console.log("received by %s", me);
}
// it was not your message, or it timed out...
}*/
return void console.log(msg); // if a checkpoint was successfully applied, emit an event
if (parsed[0] === 'CHECKPOINT' && changed) {
events.checkpoint.fire(hash);
} else if (changed) {
events.change.fire();
}
}; };
var isReady = function () {
return Boolean(ready && me);
};
var metadata, crypto; var metadata, crypto;
var send = function (msg, _cb) { var send = function (msg, cb) {
var cb = Util.once(Util.mkAsync(_cb)); if (!isReady()) {
if (!isReady()) { return void cb("NOT_READY"); } return void cb("NOT_READY");
}
var changed = false; var changed = false;
try { try {
@ -411,23 +471,30 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
} catch (err) { } catch (err) {
return void cb(err); return void cb(err);
} }
if (!changed) { return void cb("NO_CHANGE"); } if (!changed) {
return void cb("NO_CHANGE");
}
var ciphertext = crypto.encrypt(Sortify(msg)); var ciphertext = crypto.encrypt(Sortify(msg));
var id = getMessageId(ciphertext); var id = getMessageId(ciphertext);
//console.log("Sending with id [%s]", id, msg);
//console.log();
response.expect(id, cb, 3000);
anon_rpc.send('WRITE_PRIVATE_MESSAGE', [ anon_rpc.send('WRITE_PRIVATE_MESSAGE', [
channel, channel,
ciphertext ciphertext
], function (err) { ], function (err) {
if (err) { return void cb(err); } if (err) { return response.handle(id, [err]); }
pending[id] = cb;
}); });
}; };
roster.init = function (_data, cb) { roster.init = function (_data, _cb) {
var data = clone(_data); var cb = Util.once(Util.mkAsync(_cb));
if (ref.state) { return void cb("ALREADY_INITIALIZED"); }
var data = Util.clone(_data);
data.role = 'OWNER'; data.role = 'OWNER';
var state = {}; var state = {};
state[me] = data; state[me] = data;
@ -435,22 +502,75 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
}; };
// commands // commands
roster.checkpoint = function () { roster.checkpoint = function (_cb) {
var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
send([ 'CHECKPOINT', ref.state], cb); send([ 'CHECKPOINT', ref.state], cb);
}; };
roster.add = function (data, cb) { roster.add = function (data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
if (!isMap(data)) { return void cb("INVALID_ARGUMENTS"); }
// don't add members that are already present
// use DESCRIBE to amend
Object.keys(data).forEach(function (curve) {
if (!isValidId(curve) || isMap(state[curve])) { return delete data[curve]; }
});
send([ 'ADD', data ], cb); send([ 'ADD', data ], cb);
}; };
roster.remove = function (data, cb) { roster.remove = function (data, _cb) {
send([ 'REMOVE', data ], cb); var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
if (!Array.isArray(data)) { return void cb("INVALID_ARGUMENTS"); }
var toRemove = [];
var current = Object.keys(state);
data.forEach(function (curve) {
// don't try to remove elements which are not in the current state
if (current.indexOf(curve) === -1) { return; }
toRemove.push(curve);
});
send([ 'RM', toRemove ], cb);
}; };
roster.describe = function (data, cb) { roster.describe = function (data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var state = ref.state;
if (!state) { return cb("UNINITIALIZED"); }
if (!isMap(data)) { return void cb("INVALID_ARGUMENTS"); }
Object.keys(data).forEach(function (curve) {
var member = data[curve];
if (!isMap(member)) { delete data[curve]; }
// don't send fields that won't result in a change
Object.keys(member).forEach(function (k) {
if (member[k] === state[curve][k]) { delete member[k]; }
});
});
send(['DESCRIBE', data], cb); send(['DESCRIBE', data], cb);
}; };
roster.metadata = function (data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var metadata = ref.metadata;
if (!isMap(data)) { return void cb("INVALID_ARGUMENTS"); }
Object.keys(data).forEach(function (k) {
if (data[k] === metadata[k]) { delete data[k]; }
});
send(['METADATA', data], cb);
};
nThen(function (w) { nThen(function (w) {
// get metadata so we know the owners and validateKey // get metadata so we know the owners and validateKey
anon_rpc.send('GET_METADATA', channel, function (err, data) { anon_rpc.send('GET_METADATA', channel, function (err, data) {
@ -458,7 +578,7 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
w.abort(); w.abort();
return void console.error(err); return void console.error(err);
} }
metadata = ref.metadata = (data && data[0]) || undefined; metadata = ref.internal.metadata = (data && data[0]) || undefined;
console.log("TEAM_METADATA", metadata); console.log("TEAM_METADATA", metadata);
}); });
}).nThen(function (w) { }).nThen(function (w) {

Loading…
Cancel
Save