diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 278e8dda8..a7c5d3994 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -272,25 +272,30 @@ define([ var $friends = $div.find('.cp-usergrid-user.cp-selected'); $friends.each(function (i, el) { var curve = $(el).attr('data-curve'); - // Check if the selected element is a friend or a team - if (curve) { // Friend - if (!curve || !friends[curve]) { return; } - var friend = friends[curve]; - if (!friend.notifications || !friend.curvePublic) { return; } - common.mailbox.sendTo("SHARE_PAD", { - href: href, - password: config.password, - isTemplate: config.isTemplate, - name: myName, - title: title - }, { - channel: friend.notifications, - curvePublic: friend.curvePublic - }); - return; - } - // Team var ed = $(el).attr('data-ed'); + var friend = curve && friends[curve]; + var team = teams[ed]; + // If the selected element is a friend or a team without edit right, + // send a notification + var mailbox = friend || ((team && team.viewer) ? team : undefined); + if (mailbox) { // Friend + if (friends[curve] && !mailbox.notifications) { return; } + if (mailbox.notifications && mailbox.curvePublic) { + // XXX we should mark this notification as "viewed" in our own proxy + common.mailbox.sendTo("SHARE_PAD", { + href: href, + password: config.password, + isTemplate: config.isTemplate, + name: myName, + title: title + }, { + channel: mailbox.notifications, + curvePublic: mailbox.curvePublic + }); + return; + } + } + // If it's a team with edit right, add the pad directly var team = teams[ed]; if (!team) { return; } sframeChan.query('Q_STORE_IN_TEAM', { @@ -409,10 +414,11 @@ define([ // config.teamId only exists when we're trying to share a pad from a team drive // In this case, we don't want to share the pad with the current team if (config.teamId && config.teamId === id) { return; } - if (!teamsData[id].hasSecondaryKey) { return; } var t = teamsData[id]; teams[t.edPublic] = { - notifications: true, + viewer: !teamsData[id].hasSecondaryKey, + notifications: t.notifications, + curvePublic: t.curvePublic, displayName: t.name, edPublic: t.edPublic, avatar: t.avatar, diff --git a/www/common/notifications.js b/www/common/notifications.js index 3abc40ca8..348b0cc44 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -84,6 +84,10 @@ define([ // Share pad + Messages.notification_padSharedTeam = "{0} has shared a pad with the team {2}: {1}"; // XXX + Messages.notification_fileSharedTeam = "{0} has shared a file with the team {2}: {1}"; // XXX + Messages.notification_folderSharedTeam = "{0} has shared a pad with the team {2}: {1}"; // XXX + handlers['SHARE_PAD'] = function(common, data) { var content = data.content; var msg = content.msg; @@ -91,10 +95,22 @@ define([ var key = type === 'drive' ? 'notification_folderShared' : (type === 'file' ? 'notification_fileShared' : 'notification_padShared'); + + var teamNotification = /^team-/.test(data.type) && Number(data.type.slice(5)); + var teamName = ''; + if (teamNotification) { + var privateData = common.getMetadataMgr().getPrivateData(); + var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {}; + var team = teamsData[teamNotification]; + if (!team || !team.name) { return; } + key += "Team"; + teamName = Util.fixHTML(team.name); + } + var name = Util.fixHTML(msg.content.name) || Messages.anonymous; var title = Util.fixHTML(msg.content.title); content.getFormatText = function() { - return Messages._getKey(key, [name, title]); + return Messages._getKey(key, [name, title, teamName]); }; content.handler = function() { var todo = function() { @@ -105,6 +121,9 @@ define([ if (msg.content.isTemplate) { common.sessionStorage.put(Constants.newPadPathKey, ['template'], waitFor()); } + if (teamNotification) { + common.sessionStorage.put(Constants.newPadTeamKey, teamNotification, waitFor()); + } common.sessionStorage.put('newPadPassword', msg.content.password || '', waitFor()); }).nThen(function() { todo(); diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 706f1c8c2..ccae95a7e 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -467,6 +467,15 @@ proxy.mailboxes = { } }); + Object.keys(store.proxy.teams || {}).forEach(function (teamId) { + var team = store.proxy.teams[teamId]; + var teamMailbox = team.keys.mailbox || {}; + if (!teamMailbox.channel) { return; } + openChannel(ctx, 'team-'+teamId, teamMailbox, function () { + //console.log('Mailbox team', teamId); + }); + }); + mailbox.post = function (box, type, content) { var b = ctx.boxes[box]; if (!b) { return; } @@ -477,8 +486,8 @@ proxy.mailboxes = { }); }; - mailbox.open = function (key, m, cb) { - if (TYPES.indexOf(key) === -1) { return; } + mailbox.open = function (key, m, cb, team) { + if (TYPES.indexOf(key) === -1 && !team) { return; } openChannel(ctx, key, m, cb); }; diff --git a/www/common/outer/team.js b/www/common/outer/team.js index c151de004..92dc1deda 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -494,6 +494,9 @@ define([ var roHash = Hash.getViewHashFromKeys(secret); var keyPair = Nacl.sign.keyPair(); // keyPair.secretKey , keyPair.publicKey + var curveSeed = Nacl.randomBytes(32); + var curvePair = Nacl.box.keyPair.fromSecretKey(curveSeed); + var rosterSeed = Crypto.Team.createSeed(); var rosterKeys = Crypto.Team.deriveMemberKeys(rosterSeed, { curvePublic: ctx.store.proxy.curvePublic, @@ -585,6 +588,14 @@ define([ proxy.on('ready', function () { // Store keys in our drive var keys = { + mailbox: { + channel: Hash.createChannelId(), + viewed: [], + keys: { + curvePrivate: Nacl.util.encodeBase64(curvePair.secretKey), + curvePublic: Nacl.util.encodeBase64(curvePair.publicKey) + } + }, drive: { edPrivate: Nacl.util.encodeBase64(keyPair.secretKey), edPublic: Nacl.util.encodeBase64(keyPair.publicKey) @@ -1566,6 +1577,26 @@ define([ }); }; + var deriveMailbox = function (team) { + if (!team) { return; } + if (team.keys && team.keys.mailbox) { return team.keys.mailbox; } + var channel = team.channel; + if (!channel) { return; } + // XXX maybe use something else than channel? + var hash = Nacl.hash(Nacl.util.decodeUTF8(channel)); + var seed = hash.slice(0,32); + var channel = Util.uint8ArrayToHex(hash.slice(32,48)); + var curvePair = Nacl.box.keyPair.fromSecretKey(seed); + return { + channel: channel, + viewed: [], + keys: { + curvePrivate: Nacl.util.encodeBase64(curvePair.secretKey), + curvePublic: Nacl.util.encodeBase64(curvePair.publicKey) + } + }; + }; + Team.init = function (cfg, waitFor, emit) { var team = {}; var store = cfg.store; @@ -1595,6 +1626,9 @@ define([ Object.keys(teams).forEach(function (id) { ctx.onReadyHandlers[id] = []; + if (!Util.find(teams, id, 'keys', 'mailbox')) { + teams[id].keys.mailbox = deriveMailbox(teams[id]); + } openChannel(ctx, teams[id], id, waitFor(function (err) { if (err) { return void console.error(err); } console.debug('Team '+id+' ready'); @@ -1615,6 +1649,8 @@ define([ edPublic: Util.find(teams[id], ['keys', 'drive', 'edPublic']), avatar: Util.find(teams[id], ['metadata', 'avatar']), viewer: !Util.find(teams[id], ['keys', 'drive', 'edPrivate']), + notifications: Util.find(teams[id], ['keys', 'mailbox', 'channel']), + curvePublic: Util.find(teams[id], ['keys', 'mailbox', 'keys', 'curvePublic']), }; if (safe && ctx.teams[id]) { diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index a5602fc3f..eac59462e 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -105,11 +105,14 @@ define([ }); // Call the onMessage handlers + var isNotification = function (type) { + return type === "notificatons" || /^team-/.test(type); + }; var pushMessage = function (data, handler) { var todo = function (f) { try { var el; - if (data.type === 'notifications') { + if (isNotification(data.type)) { Notifications.add(Common, data); el = createElement(data); } @@ -129,7 +132,7 @@ define([ onViewedHandlers.forEach(function (f) { try { f(data); - if (data.type === 'notifications') { + if (isNotification(data.type)) { Notifications.remove(Common, data); } } catch (e) { @@ -173,20 +176,23 @@ define([ execCommand('SUBSCRIBE', null, function () {}); subscribed = true; } + var teams = types.indexOf('team') !== -1; if (typeof(cfg.onViewed) === "function") { onViewedHandlers.push(function (data) { - if (types.indexOf(data.type) === -1) { return; } + var type = data.type; + if (types.indexOf(type) === -1 && !(teams && /^team-/.test(type))) { return; } cfg.onViewed(data); }); } if (typeof(cfg.onMessage) === "function") { onMessageHandlers.push(function (data, el) { - if (types.indexOf(data.type) === -1) { return; } + var type = data.type; + if (types.indexOf(type) === -1 && !(teams && /^team-/.test(type))) { return; } cfg.onMessage(data, el); }); } Object.keys(history).forEach(function (type) { - if (types.indexOf(type) === -1) { return; } + if (types.indexOf(type) === -1 && !(teams && /^team-/.test(type))) { return; } history[type].forEach(function (data) { pushMessage({ type: type, diff --git a/www/common/toolbar.js b/www/common/toolbar.js index a4683a884..e55cdcf82 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1025,7 +1025,7 @@ MessengerUI, Messages) { $button.addClass('fa-bell'); }; - Common.mailbox.subscribe(['notifications'], { + Common.mailbox.subscribe(['notifications', 'team'], { onMessage: function (data, el) { if (el) { $(div).prepend(el);