|
|
|
@ -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);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|