diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 831ed632f..58137d898 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -60,7 +60,7 @@ define([ } }; - var onSync = function (teamId, cb) { + var onSync = Store.onSync = function (teamId, cb) { var s = getStore(teamId); if (!s) { return void cb({ error: 'ENOTFOUND' }); } nThen(function (waitFor) { @@ -735,7 +735,7 @@ define([ store.proxy[Constants.displayNameKey] = value; broadcast([clientId], "UPDATE_METADATA"); Messaging.updateMyData(store); - onSync(cb); + onSync(null, cb); }; // Reset the drive part of the userObject (from settings) @@ -747,7 +747,7 @@ define([ sendDriveEvent('DRIVE_CHANGE', { path: ['drive', 'filesData'] }, clientId); - onSync(cb); + onSync(null, cb); }); }; @@ -830,7 +830,7 @@ define([ var object = getAttributeObject(data.attr); object.obj[object.key] = data.value; } catch (e) { return void cb({error: e}); } - onSync(function () { + onSync(null, function () { cb(); broadcast([], "UPDATE_METADATA"); }); @@ -1209,6 +1209,7 @@ define([ var loadUniversal = function (Module, type, waitFor) { if (store.modules[type]) { return; } store.modules[type] = Module.init({ + Store: Store, store: store, updateMetadata: function () { broadcast([], "UPDATE_METADATA"); @@ -1286,7 +1287,7 @@ define([ store.mailbox.open('supportadmin', box, function () { console.log('ready'); }); - onSync(cb); + onSync(null, cb); }; ////////////////////////////////////////////////////////////////// @@ -1970,7 +1971,7 @@ define([ }; if (!proxy.settings) { proxy.settings = {}; } var manager = store.manager = ProxyManager.create(proxy.drive, { - onSync: onSync, + onSync: function (cb) { onSync(null, cb); }, edPublic: proxy.edPublic, pin: pin, unpin: unpin, diff --git a/www/common/outer/team.js b/www/common/outer/team.js index 2c3d59e1d..9e8d3750f 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -4,16 +4,21 @@ define([ '/common/common-constants.js', '/common/common-realtime.js', + '/common/proxy-manager.js', '/common/outer/sharedfolder.js', '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad/chainpad.dist.js', + '/bower_components/nthen/index.js', + '/bower_components/tweetnacl/nacl-fast.min.js', ], function (Util, Hash, Constants, Realtime, - SF, - Listmap, Crypto, ChainPad) { + ProxyManager, SF, + Listmap, Crypto, ChainPad, nThen) { var Team = {}; + var Nacl = window.nacl; + var initializeTeams = function (ctx, cb) { // XXX ? cb(); @@ -28,28 +33,146 @@ define([ // TODO: pin or unpin document added to a shared folder from someone who is not a member of the team }; - var onReady = function (ctx, team, id, cb) { + var initRpc = function (ctx, team, data, cb) { + if (team.rpc) { return void cb(); } + if (!data.edPrivate || !data.edPublic) { return void cb('EFORBIDDEN'); } + require(['/common/pinpad.js'], function (Pinpad) { + Pinpad.create(ctx.store.network, data, function (e, call) { + if (e) { return void cb(e); } + team.rpc = call; + // XXX get pin limit? + }); + }); + }; + + var onReady = function (ctx, id, lm, keys, cb) { // XXX // sanity check: do we have all the required keys? - // initialize team rpc with pin, unpin, ... - // team.rpc = rpc - // load manager with userObject + // [x] initialize team rpc with pin, unpin, ... + // [x] team.rpc = rpc + // [x] load manager with userObject // team.manager =... team.userObject = .... - // load shared folders - // register event for these folders - // ~resetPins for the team? - // getPinLimit - ctx.teams[id] = team; - cb(); + // [ ] load members pad + // [ ] load shared folders + // [ ] register listmap 'change' events for the drive and the shared folders + // [ ] ~resetPins for the team? + // [ ] getPinLimit + + var proxy = lm.proxy; + var team = { + id: id, + proxy: proxy, + listmap: lm, + clients: [], + realtime: lm.realtime, + handleSharedFolder: function (sfId, rt) { handleSharedFolder(ctx, id, sfId, rt); }, + sharedFolders: {}, // equivalent of store.sharedFolders in async-store + }; + + team.sendEvent = function (q, data, sender) { + ctx.emit(q, data, team.clients.filter(function (cId) { + return cId !== sender; + })); + }; + + var pin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; + var unpin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; + var removeOwnedChannel = function (data, cb) {}; + nThen(function (waitFor) { + if (!keys.edPrivate) { return; } + initRpc(ctx, team, keys, waitFor(function (err) { + if (err) { return; } + + pin = function (data, cb) { + if (!team.rpc) { return void cb({error: 'TEAM_RPC_NOT_READY'}); } + if (typeof(cb) !== 'function') { console.error('expected a callback'); } + team.rpc.pin(data, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; + + unpin = function (data, cb) { + if (!team.rpc) { return void cb({error: 'TEAM_RPC_NOT_READY'}); } + if (typeof(cb) !== 'function') { console.error('expected a callback'); } + team.rpc.unpin(data, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; + })); + }).nThen(function (waitFor) { + var loadSharedFolder = function (id, data, cb) { + SF.load({ + network: ctx.store.network, + store: team + }, id, data, cb); + }; + var manager = team.manager = ProxyManager.create(proxy.drive, { + onSync: function (cb) { ctx.Store.onSync(id, cb); }, + edPublic: proxy.edPublic, + pin: pin, + unpin: unpin, + loadSharedFolder: loadSharedFolder, + settings: { + drive: Util.find(ctx.store, ['proxy', 'settings', 'drive']) + } + }, { + outer: true, + removeOwnedChannel: function (channel, cb) { + var data; + if (typeof(channel) === "object") { + channel.teamId = id; + data = channel; + } else { + data = { + channel: channel, + teamId: id + }; + } + ctx.Store.removeOwnedChannel('', data, cb); + }, + edPublic: proxy.edPublic, + loggedIn: true, + log: function (msg) { + // broadcast to all drive apps + team.sendEvent("DRIVE_LOG", msg); + } + }); + team.userObject = manager.user.userObject; + team.userObject.fixFiles(); + }).nThen(function (waitFor) { + // XXX + // Load shared folders + // Load members pad + console.log('ok'); + }).nThen(function () { + ctx.teams[id] = team; + if (ctx.onReadyHandlers[id]) { + ctx.onReadyHandlers[id].forEach(function (obj) { + // Callback and subscribe the client to new notifications + if (typeof (obj.cb) === "function") { obj.cb(); } + if (!obj.cId) { return; } + var idx = team.clients.indexOf(obj.cId); + if (idx === -1) { + team.clients.push(obj.cId); + } + }); + } + delete ctx.onReadyHandlers[id]; + console.log(cb); + cb(); + }); + }; var openChannel = function (ctx, teamData, id, cb) { - // XXX team password? - var secret = Hash.getSecrets('team', teamData.href); + var secret = Hash.getSecrets('team', teamData.hash, teamData.password); var crypto = Crypto.createEncryptor(secret.keys); var cfg = { data: {}, + readOnly: Boolean(secret.keys.signKey), network: ctx.store.network, channel: secret.channel, crypto: crypto, @@ -63,48 +186,100 @@ define([ var lm = Listmap.create(cfg); lm.proxy.on('create', function () { }).on('ready', function () { - var sendEvent = function (type, data, sender) { - type = type; - data = data; - sender = sender; - // XXX emit UPDATE event to the inner iframe - // don't send the event back to the sender - // types are DRIVE_CHANGE, DRIVE_REMOVE and DRIVE_LOG - }; + onReady(ctx, id, lm, teamData.keys, cb); + }); + }; - var team = { - id: id, - proxy: lm.proxy, - listmap: lm, - clients: [], - manager: undefined, // XXX - userObject: undefined, // XXX - realtime: lm.realtime, - handleSharedFolder: function (sfId, rt) { handleSharedFolder(ctx, id, sfId, rt); }, - sharedFolders: {}, // equivalent of store.sharedFolders in async-store - sendEvent: sendEvent - }; + var createTeam = function (ctx, data, cId, _cb) { + var cb = Util.once(_cb); - onReady(ctx, team, id, function () { - // TODO - cb(); - }); - if (ctx.onReadyHandlers.length) { - ctx.onReadyHandlers.forEach(function (f) { - try { - f(lm.proxy); - } catch (e) { console.error(e); } +console.log(data); + + var teams = ctx.store.teams; + var password = Hash.createChannelId(); + var hash = Hash.createRandomHash('team', password); + var secret = Hash.getSecrets('team', hash, password); + var keyPair = Nacl.sign.keyPair(); // keyPair.secretKey , keyPair.publicKey + + var membersSecret = Hash.getSecrets('members'); + var membersHashes = Hash.getHashes(secret); + + var config = { + network: ctx.store.network, + channel: secret.channel, + data: {}, + validateKey: secret.keys.validateKey, // derived validation key + crypto: Crypto.createEncryptor(secret.keys), + logLevel: 1, + classic: true, + ChainPad: ChainPad, + owners: [ctx.store.proxy.edPublic] + }; + nThen(function (waitFor) { + console.log('pin..'); + ctx.pinPads([secret.channel, membersSecret.channel], waitFor(function (obj) { + if (obj && obj.error) { + waitFor.abort(); + return void cb(obj); + } + })); + // XXX initialize the members channel with yourself, and mark it as owned! + }).nThen(function () { + console.log('init proxy'); + var lm = Listmap.create(config); + var proxy = lm.proxy; + proxy.on('ready', function () { + console.log('ready'); + // Store keys in our drive + var id = Util.createRandomInteger(); + var keys = { + edPrivate: keyPair.secretKey, + edPublic: keyPair.publicKey + }; + ctx.store.proxy.teams[id] = { + hash: hash, + password: password, + keys: keys, + members: membersHashes.editHash, + name: data.name + }; + // Initialize the team drive + proxy.drive = {}; + // Create metadata + proxy.metadata = { + name: name, + members: membersHashes.viewHash, + }; + // Add rpc key + proxy.edPublic = keyPair.publicKey; + + onReady(ctx, id, lm, { + edPrivate: keyPair.secretKey, + edPublic: keyPair.publicKey + }, function () { + cb(); }); - ctx.onReadyHandlers = []; - } - }).on('change', [], function () { - // XXX team app event - //ctx.emit('UPDATE', lm.proxy, ctx.clients); + }).on('error', function (info) { + if (info && typeof (info.loaded) !== "undefined" && !info.loaded) { + cb({error:'ECONNECT'}); + } + }); }); }; var subscribe = function (ctx, id, cId, cb) { - // Subscribe to new notifications + // If the team is loading, as ourselves in the list + if (ctx.onReadyHandlers[id]) { + var _idx = ctx.onReadyHandlers[id].indexOf(cId); + if (_idx === -1) { + ctx.onReadyHandlers[id].push({ + cId: cId, + cb: cb + }); + } + return; + } + // Otherwise, subscribe to new notifications if (!id || !ctx.teams[id]) { return void cb({error: 'EINVAL'}); } @@ -119,9 +294,16 @@ define([ // Remove a client from all the team they're subscribed to var removeClient = function (ctx, cId) { Object.keys(ctx.teams).forEach(function (id) { + // Remove from the subscribers var clients = ctx.teams[id].clients; var idx = clients.indexOf(cId); - clients.splice(idx, 1); + if (idx !== -1) { clients.splice(idx, 1); } + + // And remove from the onReady handlers in case they haven't finished loading + if (ctx.onReadyHandlers[id]) { + var idx2 = ctx.onReadyHandlers.indexOf(cId); + if (idx2 !== -1) { ctx.onReadyHandlers.splice(idx2, 1); } + } }); }; @@ -131,10 +313,10 @@ define([ if (!store.loggedIn || !store.proxy.edPublic) { return; } var ctx = { store: store, + Store: cfg.Store, pinPads: cfg.pinPads, - updateMetadata: cfg.updateMetadata, emit: emit, - onReadyHandlers: [], + onReadyHandlers: {}, teams: {} }; @@ -145,7 +327,7 @@ define([ })); Object.keys(teams).forEach(function (id) { - // only if we want to make sure teams are loaded before remore the loading screen + ctx.onReadyHandlers[id] = []; openChannel(ctx, teams[id], id, waitFor(function () { console.error('team '+id+' ready'); })); @@ -169,7 +351,10 @@ define([ return void subscribe(ctx, data, clientId, cb); } if (cmd === 'LIST_TEAMS') { - return void cb(ctx.teams); + return void cb(store.proxy.teams); + } + if (cmd === 'CREATE_TEAM') { + return void createTeam(ctx, data, clientId, cb); } }; diff --git a/www/team/inner.js b/www/team/inner.js index 1882a4008..c5c0100e4 100644 --- a/www/team/inner.js +++ b/www/team/inner.js @@ -98,7 +98,7 @@ define([ var $div = $('