diff --git a/www/common/outer/roster.js b/www/common/outer/roster.js index d5cfc570a..8b038569f 100644 --- a/www/common/outer/roster.js +++ b/www/common/outer/roster.js @@ -364,6 +364,98 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { return changed; }; + commands.INVITE = function (args, author, roster) { + // an invitation is created with an ephemeral curve public key + // that key is ultimately given to the user you'd like on your team + // that user can exploit their possession of the public key to remove + // the pending invitation with their actual data. + if (!isMap(args)) { throw new Error('INVALID_ARGS'); } + if (!roster.internal.initialized) { throw new Error("UNINITIALIED"); } + if (typeof(roster.state.members) === 'undefined') { + throw new Error("CANNOT+INVITE_TO_UNINITIALIED_ROSTER"); + } + + var members = roster.state.members; + + Object.keys(args).forEach(function (curve) { + if (!isValidId(curve)) { + console.log(curve, curve.length); + throw new Error("INVALID_CURVE_KEY"); + } + // reject commandws wehere the members are not proper objects + if (!isMap(args[curve])) { throw new Error("INVALID_CONTENT"); } + if (members[curve]) { throw new Error("ARLEADY_PRESNET"); } + + var data = args[curve]; + // if no role was provided, assume VIEWER + if (typeof(data.role) !== 'string') { data.role = "VIEWER"; } + + // assume that invitations are 'pending' unless stated otherwise + if (typeof(data.pending) === 'undefined') { data.pending = true; } + + if (!canAddRole(author, data.role, members)) { + throw new Error("INSUFFICIENT_PERMISSIONS"); + } + + if (typeof(data.displayName) !== 'string' || !data.displayName) { throw new Error("DISPLAYNAME_REQUIRED"); } + //if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); } + }); + + + /* + { + : { + role: ??? || 'VIEWER', + displayName: '', + pending: true, + } + } + */ + + var changed = false; + + Object.keys(args).forEach(function (curve) { + changed = true; + members[curve] = args[curve]; + }); + + return changed; + }; + + commands.ACCEPT = function (args, author, roster) { + if (!roster.internal.initialized) { throw new Error("UNINITIALIED"); } + if (typeof(roster.state.members) === 'undefined') { + throw new Error("CANNOT_ADD_TO_UNINITIALIED_ROSTER"); + } + + // an ACCEPT command replaces a pending invitation's curve key with a new one + // after which the invited member can use their actual curve key to describe themselves + + // the author must have been invited already... + var members = roster.state.members; + + // so you must already be in the members list + if (!isMap(members[author])) { throw new Error("INSUFFICIENT_PERMISSIONS"); } + // and your membership must indicate that you are 'pending' + if (!members[author].pending) { throw new Errror("ALREADY_PRESENT"); } + + // args should be a string + if (typeof(args) !== 'string') { throw new Error("INVALID_ARGS"); } + // ...and a valid curve key + if (!isValidId(args)) { throw new Error("INVALID_CURVE_KEY"); } + + var curve = args; + + // and the curve key must not already be a member + if (typeof(members[curve]) !== 'undefined') { throw new Error("MEMBER_ALREADY_PRESENT"); } + + // copy the new profile from the old one + members[curve] = Util.clone(members[author]); + // and erase the old one + delete members[author]; + return true; + }; + var handleCommand = function (content, author, roster) { if (!(Array.isArray(content) && typeof(author) === 'string')) { throw new Error("INVALID ARGUMENTS"); @@ -671,6 +763,31 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) { send(['METADATA', data], cb); }; + // supports multiple invite + roster.invite = function (_data, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + var state = ref.state; + if (!state) { return cb("UNINITIALIZED"); } + if (!ref.internal.initialized) { return cb("UNINITIALIZED"); } + if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); } + var data = Util.clone(_data); + + Object.keys(data).forEach(function (curve) { + if (!isValidId(curve) || isMap(ref.state.members[curve])) { return delete data[curve]; } + }); + + send(['INVITE', data], cb); + }; + + roster.accept = function (_data, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + if (typeof(_data) !== 'string' || !isValidId(_data)) { + return void cb("INVALID_ARGUMENTS"); + } + + send([ 'ACCEPT', _data ], cb); + }; + nThen(function (w) { // get metadata so we know the owners and validateKey anon_rpc.send('GET_METADATA', channel, function (err, data) {