From c6a4f7809743fcd161b989cee7a919044451658f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 16 Sep 2019 15:56:50 +0200 Subject: [PATCH 1/4] Chat V3 --- www/common/common-messaging.js | 26 +- www/common/common-messenger.js | 1 + www/common/migrate-user-object.js | 6 +- www/common/outer/async-store.js | 5 +- www/common/outer/mailbox-handlers.js | 2 +- www/common/outer/messenger.js | 927 +++++++++++++++++++++++++++ 6 files changed, 959 insertions(+), 8 deletions(-) create mode 100644 www/common/outer/messenger.js diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index 8e27b3fd9..a9d618225 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -79,9 +79,6 @@ define([ }; Msg.updateMyData = function (store, curve) { - if (store.messenger) { - store.messenger.updateMyData(); - } var myData = createData(store.proxy); if (store.proxy.friends) { store.proxy.friends.me = myData; @@ -103,5 +100,28 @@ define([ eachFriend(store.proxy.friends || {}, todo); }; + Msg.removeFriend = function (store, curvePublic, cb) { + var proxy = store.proxy; + var friend = proxy.friends[curvePublic]; + if (!friend) { return void cb({error: 'ENOENT'}); } + if (!friend.notifications || !friend.channel) { return void cb({error: 'EINVAL'}); } + + store.mailbox.sendTo('UNFRIEND', { + curvePublic: proxy.curvePublic + }, { + channel: friend.notifications, + curvePublic: friend.curvePublic + }, function (obj) { + if (obj && obj.error) { + return void cb(obj); + } + store.messenger.onFriendRemoved(curvePublic, friend.channel); + delete proxy.friends[curvePublic]; + Realtime.whenRealtimeSyncs(store.realtime, function () { + cb(obj); + }); + }); + }; + return Msg; }); diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js index be4fa9aa2..c02d9d4eb 100644 --- a/www/common/common-messenger.js +++ b/www/common/common-messenger.js @@ -464,6 +464,7 @@ define([ if (!req.cb) { // This is the initial history for a pad chat + // XXX delete range request if (type === 'HISTORY_RANGE') { if (!getChannel(req.chanId)) { return; } if (!Array.isArray(parsed[2])) { return; } diff --git a/www/common/migrate-user-object.js b/www/common/migrate-user-object.js index fe42868d7..4684b200d 100644 --- a/www/common/migrate-user-object.js +++ b/www/common/migrate-user-object.js @@ -3,11 +3,11 @@ define([ '/common/common-feedback.js', '/common/common-hash.js', '/common/common-util.js', - '/common/common-messenger.js', + '/common/common-messaging.js', '/common/outer/mailbox.js', '/bower_components/nthen/index.js', '/bower_components/chainpad-crypto/crypto.js', -], function (AppConfig, Feedback, Hash, Util, Messenger, Mailbox, nThen, Crypto) { +], function (AppConfig, Feedback, Hash, Util, Messaging, Mailbox, nThen, Crypto) { // Start migration check // Versions: // 1: migrate pad attributes @@ -198,7 +198,7 @@ define([ var ctx = { store: store }; - var myData = Messenger.createData(userObject); + var myData = Messaging.createData(userObject); var close = function (chan) { var channel = channels[chan]; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index d2db7413c..1b1824597 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -16,6 +16,7 @@ define([ '/common/outer/mailbox.js', '/common/outer/profile.js', '/common/outer/team.js', + '/common/outer/messenger.js', '/common/outer/network-config.js', '/customize/application_config.js', @@ -27,7 +28,8 @@ define([ '/bower_components/saferphore/index.js', ], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger, - SF, Cursor, OnlyOffice, Mailbox, Profile, Team, NetConfig, AppConfig, + SF, Cursor, OnlyOffice, Mailbox, Profile, Team, _Messenger, + NetConfig, AppConfig, Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) { var create = function () { @@ -2028,6 +2030,7 @@ define([ loadMessenger(); loadCursor(); loadOnlyOffice(); + loadUniversal(_Messenger, 'messenger', waitFor); loadUniversal(Profile, 'profile', waitFor); loadUniversal(Team, 'team', waitFor); cleanFriendRequests(); diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index aa65d6cfd..487eaecdc 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -154,7 +154,7 @@ define([ friend[key] = msg.content[key]; }); if (ctx.store.messenger) { - ctx.store.messenger.onFriendUpdate(curve, friend); + ctx.store.messenger.onFriendUpdate(curve); } ctx.updateMetadata(); cb(true); diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js new file mode 100644 index 000000000..9f3e9aad0 --- /dev/null +++ b/www/common/outer/messenger.js @@ -0,0 +1,927 @@ +define([ + '/bower_components/chainpad-crypto/crypto.js', + '/common/common-hash.js', + '/common/common-util.js', + '/common/common-realtime.js', + '/common/common-messaging.js', + '/common/common-constants.js', + '/customize/messages.js', + '/customize/application_config.js', + + '/bower_components/nthen/index.js', +], function (Crypto, Hash, Util, Realtime, Messaging, Constants, Messages, AppConfig, nThen) { + 'use strict'; + var Curve = Crypto.Curve; + + var Msg = {}; + + var Types = { + message: 'MSG', + unfriend: 'UNFRIEND', + mapId: 'MAP_ID', + mapIdAck: 'MAP_ID_ACK' + }; + + var clone = function (o) { + return JSON.parse(JSON.stringify(o)); + }; + + var convertToUint8 = function (obj) { + var l = Object.keys(obj).length; + var u = new Uint8Array(l); + for (var i = 0; i we asked for an invalid last known hash + if (parsed.error && parsed.error === "EINVAL") { + setChannelHead(ctx, parsed.channel, '', function () { + getChannelMessagesSince(ctx, channel, {}, {}); + }); + return; + } + + // End of initial history + if (parsed.state && parsed.state === 1 && parsed.channel) { + return void onChannelReady(ctx, parsed.channel); + } + } + + // Initial history message + channel = ctx.channels[parsed[3]]; + if (!channel) { return; } + pushMsg(ctx, channel, parsed[4]); + }; + + var onMessage = function (ctx, msg, sender, chan) { + var channel = ctx.channels[chan.id]; + if (!channel) { return; } + pushMsg(ctx, channel, msg); + }; + + var removeFriend = function (ctx, curvePublic, _cb) { + var cb = Util.once(_cb); + if (typeof(cb) !== 'function') { return void console.error('NO_CALLBACK'); } + var proxy = ctx.store.proxy; + var data = getFriend(proxy, curvePublic); + + if (!data) { + // friend is not valid + console.error('friend is not valid'); + return void cb({error: 'INVALID_FRIEND'}); + } + + var channel = ctx.channels[data.channel]; + if (!channel) { + return void cb({error: "NO_SUCH_CHANNEL"}); + } + + // Unfriend with mailbox + if (ctx.store.mailbox && data.curvePublic && data.notifications) { + Messaging.removeFriend(ctx.store, curvePublic, function (obj) { + if (obj && obj.error) { return void cb({error:obj.error}); } + cb(obj); + }); + return; + } + + // Unfriend with channel + try { + var msg = [Types.unfriend, proxy.curvePublic, +new Date()]; + var msgStr = JSON.stringify(msg); + var cryptMsg = channel.encrypt(msgStr); + channel.wc.bcast(cryptMsg).then(function () {}, function (err) { + if (err) { return void cb({error:err}); } + // XXX call onFriendRemoved here (leave the channel and emit notification to inner) + removeFromFriendList(ctx, curvePublic, function () { + cb(); + }); + }); + } catch (e) { + cb({error: e}); + } + }; + + var openChannel = function (ctx, data) { + var proxy = ctx.store.proxy; + var network = ctx.store.network; + var hk = network.historyKeeper; + + var keys = data.keys; + var encryptor = data.encryptor || Curve.createEncryptor(keys); // XXX encryption + var channel = { + id: data.channel, + isFriendChat: data.isFriendChat, + isPadChat: data.isPadChat, + padChan: data.padChan, // Channel ID of the pad linked to this pad chat + readOnly: data.readOnly, + ready: false, + onReady: Util.mkEvent(true), + sending: false, + messages: [], + clients: data.clients || [], + mapId: {}, + }; + + if (data.onReady) { channel.onReady.reg(data.onReady); } + + channel.encrypt = function (msg) { + if (channel.readOnly) { return; } + return encryptor.encrypt(msg); + }; + channel.decrypt = data.decrypt || function (msg) { + return encryptor.decrypt(msg); + }; + + var onJoining = function (peer) { + if (peer === hk) { return; } + if (channel.readOnly) { return; } + + // Join event will be sent once we are able to ID this peer + var myData = createData(proxy); + delete myData.channel; + var msg = [Types.mapId, myData, channel.wc.myID]; + var msgStr = JSON.stringify(msg); + var cryptMsg = channel.encrypt(msgStr); + var data = { + channel: channel.id, + msg: cryptMsg + }; + network.sendto(peer, JSON.stringify(data)); + }; + + var onLeaving = function (peer) { + if (peer === hk) { return; } + + // update status + var otherData = channel.mapId[peer]; + if (!otherData) { return; } + + // Make sure the leaving user is not connected with another netflux id + if (channel.wc.members.some(function (nId) { + return channel.mapId[nId] + && channel.mapId[nId].curvePublic === otherData.curvePublic; + })) { return; } + + // Send the notification + ctx.emit('LEAVE', { + info: otherData, + id: channel.id + }, channel.clients); + }; + + var onOpen = function (chan) { + channel.wc = chan; + ctx.channels[data.channel] = channel; + + chan.on('message', function (msg, sender) { + onMessage(ctx, msg, sender, chan); + }); + + chan.on('join', onJoining); + chan.on('leave', onLeaving); + + getChannelMessagesSince(ctx, channel, data, keys); + }; + network.join(data.channel).then(onOpen, function (err) { + console.error(err); + }); + network.on('reconnect', function () { + if (channel && channel.stopped) { return; } + if (!ctx.channels[data.channel]) { return; } + + network.join(data.channel).then(onOpen, function (err) { + console.error(err); + }); + }); + }; + + var sendMessage = function (ctx, id, payload, cb) { + var channel = ctx.channels[id]; + if (!channel) { return void cb({error: 'NO_CHANNEL'}); } + if (channel.readOnly) { return void cb({error: 'FORBIDDEN'}); } + + var network = ctx.store.network; + if (!network.webChannels.some(function (wc) { + if (wc.id === channel.wc.id) { return true; } + })) { + return void cb({error: 'NO_SUCH_CHANNEL'}); + } + + var proxy = ctx.store.proxy; + var msg = [Types.message, proxy.curvePublic, +new Date(), payload]; + if (!channel.isFriendChat) { + var name = proxy[Constants.displayNameKey] || + Messages.anonymous + '#' + proxy.uid.slice(0,5); + msg.push(name); + } + // XXX encryption + var msgStr = JSON.stringify(msg); + var cryptMsg = channel.encrypt(msgStr); + + channel.wc.bcast(cryptMsg).then(function () { + pushMsg(ctx, channel, cryptMsg); + cb(); + }, function (err) { + cb({error: err}); + }); + }; + + // Display green status if one member is not me + var getStatus = function (ctx, chanId, cb) { + var channel = ctx.channels[chanId]; + if (!channel) { return void cb('NO_SUCH_CHANNEL'); } + var proxy = ctx.store.proxy; + var online = channel.wc.members.some(function (nId) { + if (nId === ctx.store.network.historyKeeper) { return; } + var data = channel.mapId[nId] || undefined; + if (!data) { return false; } + return data.curvePublic !== proxy.curvePublic; + }); + cb(online); + }; + + var getMyInfo = function (ctx, cb) { + var proxy = ctx.store.proxy; + cb({ + curvePublic: proxy.curvePublic, + displayName: proxy[Constants.displayNameKey] + }); + }; + + var loadFriend = function (ctx, clientId, friend, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + + var chanId = friend.channel; + var channel = ctx.channels[chanId]; + if (channel) { + // Fired instantly if already ready + return void channel.onReady.reg(function () { + if (channel.clients.indexOf(clientId) === -1) { + channel.clients.push(clientId); + } + cb(); + }); + } + + // XXX encryption + var proxy = ctx.store.proxy; + var keys = Curve.deriveKeys(friend.curvePublic, proxy.curvePrivate); + var data = { + keys: keys, + channel: friend.channel, + lastKnownHash: friend.lastKnownHash, + owners: [proxy.edPublic, friend.edPublic], + isFriendChat: true, + clients: clientId ? [clientId] : ctx.friendsClients, + onReady: cb + }; + openChannel(data); + }; + + var initFriends = function (ctx, clientId, cb) { + var friends = getFriendList(ctx.store.proxy); + + nThen(function (waitFor) { + // Load or get all friends channels + Object.keys(friends).forEach(function (key) { + if (key === 'me') { return; } + var friend = clone(friends[key]); + if (typeof(friend) !== 'object') { return; } + if (!friend.channel) { return; } + loadFriend(ctx, clientId, friend, waitFor()); + }); + }).nThen(function () { + if (ctx.friendsClients.indexOf(clientId) === -1) { + ctx.friendsClients.push(clientId); + } + cb(); + }); + }; + + var getRooms = function (ctx, data, cb) { + var proxy = ctx.store.proxy; + + // Get a single friend's room (on friend added) + if (data && data.curvePublic) { + var curvePublic = data.curvePublic; + // We need to get data about a new friend's room + var friend = getFriend(proxy, curvePublic); + if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); } + var channel = ctx.channels[friend.channel]; + if (!channel) { return void cb({error: 'NO_SUCH_CHANNEL'}); } + return void cb([{ + id: channel.id, + isFriendChat: true, + name: friend.displayName, + lastKnownHash: friend.lastKnownHash, + curvePublic: friend.curvePublic, + messages: channel.messages + }]); + } + + // Pad chat room + if (data && data.padChat) { + var pCChannel = ctx.channels[data.padChat]; + if (!pCChannel) { return void cb({error: 'NO_SUCH_CHANNEL'}); } + return void cb([{ + id: pCChannel.id, + isPadChat: true, + messages: pCChannel.messages + }]); + } + + // Existing friends... + var rooms = Object.keys(ctx.channels).map(function (id) { + var r = ctx.channels[id]; + var name, lastKnownHash, curvePublic; + if (r.isFriendChat) { + var friend = getFriendFromChannel(ctx, id); + if (!friend) { return null; } + name = friend.displayName; + lastKnownHash = friend.lastKnownHash; + curvePublic = friend.curvePublic; + } else if (r.isPadChat) { + return; + } else { + // TODO room get metadata (name) && lastKnownHash + } + return { + id: r.id, + isFriendChat: r.isFriendChat, + name: name, + lastKnownHash: lastKnownHash, + curvePublic: curvePublic, + messages: r.messages + }; + }).filter(function (x) { return x; }); + cb(rooms); + }; + + // Gte the static userlist of a room (eveyrone who has accessed to the room) + var getUserList = function (ctx, data, cb) { + var room = ctx.channels[data.id]; + if (!room) { return void cb({error: 'NO_SUCH_CHANNEL'}); } + if (room.isFriendChat) { + var friend = getFriendFromChannel(ctx, data.id); + if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); } + cb([friend]); + } else { + // TODO room userlist in rooms... + // (this is the static userlist, not the netflux one) + cb([]); + } + }; + + var openPadChat = function (ctx, clientId, data, _cb) { + var chanId = data.channel; + + var cb = Util.once(Util.mkAsync(function () { + ctx.emit('PADCHAT_READY', chanId, [clientId]); + _cb(); + })); + + var channel = ctx.channels[chanId]; + if (channel) { + return void channel.onReady.reg(function () { + if (channel.clients.indexOf(clientId) === -1) { + channel.clients.push(clientId); + } + cb(); + }); + } + + var secret = data.secret; + if (secret.keys.cryptKey) { + secret.keys.cryptKey = convertToUint8(secret.keys.cryptKey); + } + var encryptor = Crypto.createEncryptor(secret.keys); + var vKey = (secret.keys && secret.keys.validateKey) || ctx.validateKeys[secret.channel]; + var chanData = { + padChan: data.secret && data.secret.channel, + readOnly: typeof(secret.keys) === "object" && !secret.keys.validateKey, + encryptor: encryptor, + channel: data.channel, + isPadChat: true, + decrypt: function (msg) { + return encryptor.decrypt(msg, vKey); + }, + clients: [clientId], + onReady: cb + }; + openChannel(chanData); + }; + + var clearOwnedChannel = function (ctx, id, cb) { + var channel = ctx.clients[id]; + if (!channel) { return void cb({error: 'NO_CHANNEL'}); } + if (!ctx.store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + ctx.store.rpc.clearOwnedChannel(id, function (err) { + cb({error:err}); + if (!err) { + channel.messages = []; + ctx.emit('CLEAR_CHANNEL', id, channel.clients); + } + }); + }; + + // Remove a client from all the team they're subscribed to + var removeClient = function (ctx, cId) { + var friendsIdx = ctx.friendsClients.indexOf(cId); + if (friendsIdx !== -1) { + ctx.friendsClients.splice(friendsIdx, 1); + } + Object.keys(ctx.channels).forEach(function (id) { + var channel = ctx.channels[id]; + var clients = channel.clients; + var idx = clients.indexOf(cId); + if (idx !== -1) { clients.splice(idx, 1); } + + if (clients.length === 0) { + if (channel.wc) { channel.wc.leave(); } + channel.stopped = true; + delete ctx.channels[id]; + return true; + } + + }); + }; + + var getAllClients = function (ctx) { + var all = []; + Array.prototype.push.apply(all, ctx.friendsClients); + Object.keys(ctx.channels).forEach(function (id) { + Array.prototype.push.apply(all, ctx.channels[id].clients); + }); + return Util.deduplicateString(all); + }; + + Msg.init = function (cfg, waitFor, emit) { + var messenger = {}; + var store = cfg.store; + if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; } + if (!store.loggedIn || !store.proxy.edPublic) { return; } + var ctx = { + store: store, + updateMetadata: cfg.updateMetadata, + pinPads: cfg.pinPads, + emit: emit, + friendsClients: [], + channels: {}, + validateKeys: {} // XXX encryption + }; + + + ctx.store.network.on('message', function(msg, sender) { + onDirectMessage(ctx, msg, sender); + }); + ctx.store.network.on('disconnect', function () { + ctx.emit('DISCONNECT', null, getAllClients()); + }); + ctx.store.network.on('reconnect', function () { + ctx.emit('RECONNECT', null, getAllClients()); + }); + + messenger.onFriendUpdate = function (curve) { + var friend = getFriend(store.proxy, curve); + if (!friend || !friend.channel) { return; } + var chan = ctx.channels[friend.channel]; + if (chan) { + ctx.emit('UPDATE_DATA', { + info: clone(friend), + channel: friend.channel + }, chan.clients); + } + }; + // Friend added in our contacts in the current worker + messenger.onFriendAdded = function (friendData) { + if (!ctx.friendsClients.length) { return; } + + var friend = getFriend(ctx.store.proxy, friendData.curvePublic); + if (typeof(friend) !== 'object') { return; } + var channel = friend.channel; + if (!channel) { return; } + + loadFriend(ctx, null, friend, function () { + emit('FRIEND', { + curvePublic: friend.curvePublic, + }, ctx.friendsClients); + }); + }; + messenger.onFriendRemoved = function (curvePublic, chanId) { + var channel = ctx.channels[chanId]; + if (!channel) { return; } + if (channel.wc) { + channel.wc.leave(Types.unfriend); + } + delete ctx.channels[channel.id]; + emit('UNFRIEND', { + curvePublic: curvePublic, + fromMe: true + }, ctx.friendsClients); + }; + + messenger.storeValidateKey = function (chan, key) { + ctx.validateKeys[chan] = key; + }; + messenger.leavePad = function (padChan) { + // Leave chat and prevent reconnect when we leave a pad + delete ctx.validateKeys[padChan]; + Object.keys(ctx.channels).some(function (chatChan) { + var channel = ctx.channels[chatChan]; + if (channel.padChan !== padChan) { return; } + if (channel.wc) { channel.wc.leave(); } + channel.stopped = true; + delete ctx.channels[chatChan]; + return true; + }); + }; + + messenger.removeClient = function (clientId) { + removeClient(ctx, clientId); + }; + messenger.execCommand = function (clientId, obj, cb) { + console.log(obj); + var cmd = obj.cmd; + var data = obj.data; + if (cmd === 'INIT_FRIENDS') { + return void initFriends(ctx, clientId, cb); + } + if (cmd === 'GET_ROOMS') { + return void getRooms(ctx, data, cb); + } + if (cmd === 'GET_USERLIST') { + return void getUserList(ctx, data, cb); + } + if (cmd === 'OPEN_PAD_CHAT') { + return void openPadChat(ctx, clientId, data, cb); + } + if (cmd === 'GET_MY_INFO') { + return void getMyInfo(ctx, cb); + } + if (cmd === 'REMOVE_FRIEND') { + return void removeFriend(ctx, data, cb); + } + if (cmd === 'GET_STATUS') { + return void getStatus(ctx, data, cb); + } + if (cmd === 'GET_MORE_HISTORY') { + return void getMoreHistory(ctx, data.id, data.sig, data.count, cb); + } + if (cmd === 'SEND_MESSAGE') { + return void sendMessage(ctx, data.id, data.content, cb); + } + if (cmd === 'SET_CHANNEL_HEAD') { + return void setChannelHead(ctx, data.id, data.sig, cb); + } + if (cmd === 'CLEAR_OWNED_CHANNEL') { + return void clearOwnedChannel(ctx, data, cb); + } + }; + + return messenger; + }; + + return Msg; +}); From 641b69446d516cd15a950d6d49a8644da6f2fc06 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 16 Sep 2019 16:48:32 +0200 Subject: [PATCH 2/4] Fix issues and replace chat module --- www/common/common-ui-elements.js | 2 +- www/common/messenger-ui.js | 44 +++++++++++++++++++++---------- www/common/outer/async-store.js | 44 +++---------------------------- www/common/outer/messenger.js | 13 +++++---- www/common/outer/noworker.js | 3 --- www/common/outer/serviceworker.js | 6 ----- www/common/outer/sharedworker.js | 6 ----- www/common/outer/store-rpc.js | 3 --- www/common/outer/webworker.js | 3 --- www/common/sframe-common-outer.js | 13 ++++++--- 10 files changed, 50 insertions(+), 87 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 89bc248b2..cd2c1e370 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -774,7 +774,7 @@ define([ edPublic: t.edPublic, avatar: t.avatar, id: id - } + }; }); var teamsList = UIElements.getFriendsList('Share with a team', { common: common, diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 223f67bb3..202e25cec 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -40,7 +40,6 @@ define([ }; MessengerUI.create = function ($container, common, toolbar) { - var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); var origin = metadataMgr.getPrivateData().origin; var readOnly = metadataMgr.getPrivateData().readOnly; @@ -76,12 +75,17 @@ define([ ]), ]); + var execCommand = function () { + console.warn(arguments); + }; + /* var execCommand = function (cmd, data, cb) { sframeChan.query('Q_CHAT_COMMAND', {cmd: cmd, data: data}, function (err, obj) { if (err || (obj && obj.error)) { return void cb(err || (obj && obj.error)); } cb(void 0, obj); }); }; + */ var $userlist = $(friendList).appendTo($container); var $messages = $(messaging).appendTo($container); @@ -780,10 +784,6 @@ define([ // var onLeaveRoom - execCommand('GET_MY_INFO', null, function (e, info) { - contactsData[info.curvePublic] = info; - }); - var ready = false; var onMessengerReady = function () { if (isApp) { return; } @@ -826,15 +826,8 @@ define([ $messages.find('.cp-app-contacts-input textarea').prop('disabled', false); }; - // Initialize chat when outer is ready (all channels loaded) - // TODO: try again in outer if fail to load a channel - if (!isApp) { - execCommand('INIT_FRIENDS', null, function () {}); - execCommand('IS_READY', null, function (err, yes) { - if (yes) { onMessengerReady(); } - }); - } - sframeChan.on('EV_CHAT_EVENT', function (obj) { + //sframeChan.on('EV_CHAT_EVENT', function (obj) { + var onEvent = function (obj) { var cmd = obj.ev; var data = obj.data; if (cmd === 'READY') { @@ -881,7 +874,30 @@ define([ onUnfriend(data); return; } + }; + var module = common.makeUniversal('messenger', { + onEvent: onEvent }); + execCommand = function (cmd, data, cb) { + module.execCommand(cmd, data, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(void 0, obj); + }); + }; + //}); + + execCommand('GET_MY_INFO', null, function (e, info) { + contactsData[info.curvePublic] = info; + }); + + + // Initialize chat when outer is ready (all channels loaded) + // TODO: try again in outer if fail to load a channel + if (!isApp) { + execCommand('INIT_FRIENDS', null, function () { + onMessengerReady(); + }); + } }; return MessengerUI; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 1b1824597..b766370c3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -9,7 +9,6 @@ define([ '/common/common-feedback.js', '/common/common-realtime.js', '/common/common-messaging.js', - '/common/common-messenger.js', '/common/outer/sharedfolder.js', '/common/outer/cursor.js', '/common/outer/onlyoffice.js', @@ -27,8 +26,8 @@ define([ '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', ], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, - Realtime, Messaging, Messenger, - SF, Cursor, OnlyOffice, Mailbox, Profile, Team, _Messenger, + Realtime, Messaging, + SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, NetConfig, AppConfig, Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) { @@ -1243,14 +1242,6 @@ define([ }); }; - // Messenger - Store.messenger = { - execCommand: function (clientId, data, cb) { - if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); } - store.messenger.execCommand(data, cb); - } - }; - // OnlyOffice Store.onlyoffice = { execCommand: function (clientId, data, cb) { @@ -1755,7 +1746,6 @@ define([ // Clients management var driveEventClients = []; - var messengerEventClients = []; var dropChannel = function (chanId) { try { @@ -1781,10 +1771,6 @@ define([ if (driveIdx !== -1) { driveEventClients.splice(driveIdx, 1); } - var messengerIdx = messengerEventClients.indexOf(clientId); - if (messengerIdx !== -1) { - messengerEventClients.splice(messengerIdx, 1); - } try { store.cursor.removeClient(clientId); } catch (e) { console.error(e); } @@ -1875,28 +1861,6 @@ define([ } }; - var sendMessengerEvent = function (q, data) { - messengerEventClients.forEach(function (cId) { - postMessage(cId, q, data); - }); - }; - Store._subscribeToMessenger = function (clientId) { - if (messengerEventClients.indexOf(clientId) === -1) { - messengerEventClients.push(clientId); - } - }; - var loadMessenger = function () { - if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; } - var messenger = store.messenger = Messenger.messenger(store, function () { - broadcast([], "UPDATE_METADATA"); - }); - messenger.on('event', function (ev, data) { - sendMessengerEvent('CHAT_EVENT', { - ev: ev, - data: data - }); - }); - }; /* var loadProfile = function (waitFor) { @@ -2027,10 +1991,10 @@ define([ }); userObject.fixFiles(); SF.loadSharedFolders(Store, store.network, store, userObject, waitFor); - loadMessenger(); loadCursor(); loadOnlyOffice(); - loadUniversal(_Messenger, 'messenger', waitFor); + loadUniversal(Messenger, 'messenger', waitFor); + store.messenger = store.modules['messenger']; loadUniversal(Profile, 'profile', waitFor); loadUniversal(Team, 'team', waitFor); cleanFriendRequests(); diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index 9f3e9aad0..2c951da6b 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -160,8 +160,8 @@ define([ var setChannelHead = function (ctx, id, hash, cb) { var channel = ctx.channels[id]; - if (channel.friend) { - var friend = channel.friend; + if (channel.isFriendChat) { + var friend = getFriendFromChannel(ctx, id); if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); } friend.lastKnownHash = hash; } else if (channel.isPadChat) { @@ -292,7 +292,7 @@ define([ }; var onDirectMessage = function (ctx, msg, sender) { - var hk = ctx.store.etwork.historyKeeper; + var hk = ctx.store.network.historyKeeper; if (sender !== hk) { return void onIdMessage(ctx, msg, sender); } var parsed = JSON.parse(msg); @@ -356,7 +356,7 @@ define([ orderMessages(channel, decrypted); req.cb(decrypted); - return deleteRangeRequest(txid); + return deleteRangeRequest(ctx, txid); } else { console.log(parsed); } @@ -617,7 +617,7 @@ define([ clients: clientId ? [clientId] : ctx.friendsClients, onReady: cb }; - openChannel(data); + openChannel(ctx, data); }; var initFriends = function (ctx, clientId, cb) { @@ -750,7 +750,7 @@ define([ clients: [clientId], onReady: cb }; - openChannel(chanData); + openChannel(ctx, chanData); }; var clearOwnedChannel = function (ctx, id, cb) { @@ -882,7 +882,6 @@ define([ removeClient(ctx, clientId); }; messenger.execCommand = function (clientId, obj, cb) { - console.log(obj); var cmd = obj.cmd; var data = obj.data; if (cmd === 'INIT_FRIENDS') { diff --git a/www/common/outer/noworker.js b/www/common/outer/noworker.js index a412f2da0..a1507c2d0 100644 --- a/www/common/outer/noworker.js +++ b/www/common/outer/noworker.js @@ -54,9 +54,6 @@ define([ if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); - } cb(data); }); }); diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js index 54bfecaf5..d9ff9e096 100644 --- a/www/common/outer/serviceworker.js +++ b/www/common/outer/serviceworker.js @@ -75,9 +75,6 @@ var init = function (client, cb) { if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); - } return void cb(self.store); } @@ -104,9 +101,6 @@ var init = function (client, cb) { if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); - } if (data && data.state === "ALREADY_INIT") { self.store = data.returned; return void cb(data.returned); diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js index 8f59e603e..706512847 100644 --- a/www/common/outer/sharedworker.js +++ b/www/common/outer/sharedworker.js @@ -75,9 +75,6 @@ var init = function (client, cb) { if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); - } return void cb(self.store); } @@ -104,9 +101,6 @@ var init = function (client, cb) { if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); - } if (data && data.state === "ALREADY_INIT") { self.store = data.returned; return void cb(data.returned); diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 36ec9541c..c75e69b70 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -59,8 +59,6 @@ define([ // Messaging ANSWER_FRIEND_REQUEST: Store.answerFriendRequest, SEND_FRIEND_REQUEST: Store.sendFriendRequest, - // Chat - CHAT_COMMAND: Store.messenger.execCommand, // OnlyOffice OO_COMMAND: Store.onlyoffice.execCommand, // Cursor @@ -100,7 +98,6 @@ define([ // Internal calls Rpc._removeClient = Store._removeClient; Rpc._subscribeToDrive = Store._subscribeToDrive; - Rpc._subscribeToMessenger = Store._subscribeToMessenger; return Rpc; }; diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js index f1cc1a3a2..30dff7d0d 100644 --- a/www/common/outer/webworker.js +++ b/www/common/outer/webworker.js @@ -63,9 +63,6 @@ require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) { if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); - } cb(data); }); }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0a233dcf8..a4a452521 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1091,20 +1091,25 @@ define([ Notifier.getPermission(); sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) { - Cryptpad.messenger.execCommand({ - cmd: 'OPEN_PAD_CHAT', + Cryptpad.universal.execCommand({ + type: 'messenger', data: { - channel: data, - secret: secret + cmd: 'OPEN_PAD_CHAT', + data: { + channel: data, + secret: secret + } } }, cb); }); + /* DEPRECATED sframeChan.on('Q_CHAT_COMMAND', function (data, cb) { Cryptpad.messenger.execCommand(data, cb); }); Cryptpad.messenger.onEvent.reg(function (data) { sframeChan.event('EV_CHAT_EVENT', data); }); + */ } // Chrome 68 on Mac contains a bug resulting in the page turning white after a few seconds From 08b105bc1072af7d02ecdae68929bbee1ec03f9f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 16 Sep 2019 17:27:52 +0200 Subject: [PATCH 3/4] Remove dead code --- www/common/cryptpad-common.js | 10 ---------- www/common/messenger-ui.js | 1 - www/common/outer/messenger.js | 3 +-- www/common/sframe-common-outer.js | 9 --------- 4 files changed, 1 insertion(+), 22 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index c672ee8b1..1dc553b59 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -763,13 +763,6 @@ define([ }; onlyoffice.onEvent = Util.mkEvent(); - // Messenger - var messenger = common.messenger = {}; - messenger.execCommand = function (data, cb) { - postMessage("CHAT_COMMAND", data, cb); - }; - messenger.onEvent = Util.mkEvent(); - // Cursor var cursor = common.cursor = {}; cursor.execCommand = function (data, cb) { @@ -1284,8 +1277,6 @@ define([ }, // OnlyOffice OO_EVENT: common.onlyoffice.onEvent.fire, - // Chat - CHAT_EVENT: common.messenger.onEvent.fire, // Cursor CURSOR_EVENT: common.cursor.onEvent.fire, // Mailbox @@ -1439,7 +1430,6 @@ define([ anonHash: LocalStore.getFSHash(), localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), // TODO move this to LocalStore ? language: common.getLanguage(), - messenger: rdyCfg.messenger, // Boolean driveEvents: rdyCfg.driveEvents // Boolean }; // if a pad is created from a file diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index 202e25cec..eb9c33bb0 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -623,7 +623,6 @@ define([ var channel = obj.id; var chan = state.channels[channel]; var data = obj.info; - // XXX Teams: if someone leaves a room, don't remove their data if they're also a friend if (contactsData[data.curvePublic] && !(chan && chan.isFriendChat)) { delete contactsData[data.curvePublic]; } diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index 2c951da6b..d19f3d809 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -112,8 +112,7 @@ define([ var network = ctx.store.network; network.sendto(network.historyKeeper, JSON.stringify(msg)).then(function () { }, function (err) { - // XXX throw? - throw new Error(err); + console.error(err); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index a4a452521..134534eff 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -134,7 +134,6 @@ define([ }); } }), { - messenger: cfg.messaging, driveEvents: cfg.driveEvents }); })); @@ -1102,14 +1101,6 @@ define([ } }, cb); }); - /* DEPRECATED - sframeChan.on('Q_CHAT_COMMAND', function (data, cb) { - Cryptpad.messenger.execCommand(data, cb); - }); - Cryptpad.messenger.onEvent.reg(function (data) { - sframeChan.event('EV_CHAT_EVENT', data); - }); - */ } // Chrome 68 on Mac contains a bug resulting in the page turning white after a few seconds From 81c2df389a5816c26d9c6ff0f5001a38bda37f84 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Sep 2019 11:05:32 +0200 Subject: [PATCH 4/4] Team chat --- .../src/less2/include/messenger.less | 6 +- www/common/messenger-ui.js | 32 ++++++-- www/common/outer/messenger.js | 73 ++++++++++++++++++- www/common/outer/team.js | 23 +++++- www/common/sframe-common.js | 9 +++ www/team/app-team.less | 10 +++ www/team/inner.js | 20 ++++- 7 files changed, 159 insertions(+), 14 deletions(-) diff --git a/customize.dist/src/less2/include/messenger.less b/customize.dist/src/less2/include/messenger.less index 4570297d7..7f4f7c342 100644 --- a/customize.dist/src/less2/include/messenger.less +++ b/customize.dist/src/less2/include/messenger.less @@ -369,12 +369,12 @@ } } button { - height: 54px; + height: 54px !important; border-radius: 0; border: none; - background-color: darken(@bg-color, 15%); + background-color: darken(@bg-color, 15%) !important; &:hover { - background-color: darken(@bg-color, 20%); + background-color: darken(@bg-color, 20%) !important; } } } diff --git a/www/common/messenger-ui.js b/www/common/messenger-ui.js index eb9c33bb0..e11b33b93 100644 --- a/www/common/messenger-ui.js +++ b/www/common/messenger-ui.js @@ -21,7 +21,7 @@ define([ }; var initChannel = function (state, info) { - console.log('initializing channel for [%s]', info.id); + console.debug('initializing channel for [%s]', info.id); var h, t; if (Array.isArray(info.messages) && info.messages.length) { h = info.messages[info.messages.length -1].sig; @@ -31,8 +31,9 @@ define([ messages: info.messages || [], name: info.name, isFriendChat: info.isFriendChat, - needMoreHistory: !info.isPadChat, isPadChat: info.isPadChat, + isTeamChat: info.isTeamChat, + needMoreHistory: !info.isPadChat && !info.isTeamChat, curvePublic: info.curvePublic, HEAD: h || info.lastKnownHash, TAIL: t || null, @@ -507,7 +508,7 @@ define([ var rightCol = h('span.cp-app-contacts-right-col', [ h('span.cp-app-contacts-name', [room.name]), room.isFriendChat ? remove : - room.isPadChat ? undefined : leaveRoom, + (room.isPadChat || room.isTeamChat) ? undefined : leaveRoom, ]); var friendData = room.isFriendChat ? userlist[0] : {}; @@ -685,7 +686,7 @@ define([ execCommand('GET_USERLIST', {id: id}, function (e, list) { if (e || list.error) { return void console.error(e || list.error); } - if (!room.isPadChat && (!Array.isArray(list) || !list.length)) { + if (!room.isPadChat && !room.isTeamChat && (!Array.isArray(list) || !list.length)) { return void console.error("Empty room!"); } debug('userlist: ' + JSON.stringify(list), id); @@ -714,6 +715,8 @@ define([ var $parentEl; if (room.isFriendChat) { $parentEl = $userlist.find('.cp-app-contacts-friends'); + } else if (room.isTeamChat) { + $parentEl = $userlist.find('.cp-app-contacts-padchat'); // XXX } else if (room.isPadChat) { $parentEl = $userlist.find('.cp-app-contacts-padchat'); } else { @@ -725,7 +728,7 @@ define([ updateStatus(id); - if (isApp && room.isPadChat) { + if (isApp && (room.isPadChat || room.isTeamChat)) { $container.removeClass('cp-app-contacts-initializing'); display(room.id); } @@ -816,6 +819,21 @@ define([ }); }; + var onTeamChatReady = function (data) { + var teamChat = common.getTeamChat(); + if (data !== teamChat) { return; } + if (state.channels[data]) { return; } + execCommand('GET_ROOMS', {teamChat: data}, function (err, rooms) { + if (err) { return void console.error(err); } + if (!Array.isArray(rooms) || rooms.length !== 1) { + return void console.error('Invalid team chat'); + } + var room = rooms[0]; + room.name = 'TEAMS'; // XXX + rooms.forEach(initializeRoom); + }); + }; + var onDisconnect = function () { debug('disconnected'); $messages.find('.cp-app-contacts-input textarea').prop('disabled', true); @@ -841,6 +859,10 @@ define([ onPadChatReady(data); return; } + if (cmd === 'TEAMCHAT_READY') { + onTeamChatReady(data); + return; + } if (cmd === 'DISCONNECT') { onDisconnect(); return; diff --git a/www/common/outer/messenger.js b/www/common/outer/messenger.js index d19f3d809..d8ced9637 100644 --- a/www/common/outer/messenger.js +++ b/www/common/outer/messenger.js @@ -120,7 +120,7 @@ define([ var network = ctx.store.network; console.log('Fetching [%s] messages since [%s]', channel.id, data.lastKnownHash || ''); - if (channel.isPadChat) { + if (channel.isPadChat || channel.isTeamChat) { // We need to use GET_HISTORY_RANGE to make sure we won't get the full history var txid = Util.uid(); initRangeRequest(ctx, txid, channel.id, undefined); @@ -137,8 +137,6 @@ define([ return; } - // XXX team chat - // Friend chat, intial history var proxy = ctx.store.proxy; var friend = getFriendFromChannel(ctx, channel.id) || {}; @@ -165,6 +163,8 @@ define([ friend.lastKnownHash = hash; } else if (channel.isPadChat) { // Nothing to do + } else if (channel.isTeamChat) { + // Nothing to do } else { // TODO room return void cb({error: 'NOT_IMPLEMENTED'}); @@ -453,6 +453,7 @@ define([ id: data.channel, isFriendChat: data.isFriendChat, isPadChat: data.isPadChat, + isTeamChat: data.isTeamChat, padChan: data.padChan, // Channel ID of the pad linked to this pad chat readOnly: data.readOnly, ready: false, @@ -671,6 +672,17 @@ define([ }]); } + // Team chat room + if (data && data.teamChat) { + var tCChannel = ctx.channels[data.teamChat]; + if (!tCChannel) { return void cb({error: 'NO_SUCH_CHANNEL'}); } + return void cb([{ + id: tCChannel.id, + isTeamChat: true, + messages: tCChannel.messages + }]); + } + // Existing friends... var rooms = Object.keys(ctx.channels).map(function (id) { var r = ctx.channels[id]; @@ -683,6 +695,8 @@ define([ curvePublic = friend.curvePublic; } else if (r.isPadChat) { return; + } else if (r.isTeamChat) { + return; } else { // TODO room get metadata (name) && lastKnownHash } @@ -752,6 +766,52 @@ define([ openChannel(ctx, chanData); }; + var openTeamChat = function (ctx, clientId, data, _cb) { + var teams = ctx.store.modules['team']; + var team = teams.getTeam(data.teamId); + if (!team) { return; } + + var chatData = team.getChatData(); + var chanId = chatData.channel; + + var cb = Util.once(Util.mkAsync(function () { + ctx.emit('TEAMCHAT_READY', chanId, [clientId]); + _cb({ + channel: chanId + }); + })); + + var channel = ctx.channels[chanId]; + if (channel) { + return void channel.onReady.reg(function () { + if (channel.clients.indexOf(clientId) === -1) { + channel.clients.push(clientId); + } + cb(); + }); + } + + var secret = chatData.secret; + if (secret.keys.cryptKey) { + secret.keys.cryptKey = convertToUint8(secret.keys.cryptKey); + } + var encryptor = Crypto.createEncryptor(secret.keys); + var vKey = (secret.keys && secret.keys.validateKey) || chatData.validateKey; + var chanData = { + teamId: data.teamId, + readOnly: typeof(secret.keys) === "object" && !secret.keys.validateKey, + encryptor: encryptor, + channel: chanId, + isTeamChat: true, + decrypt: function (msg) { + return encryptor.decrypt(msg, vKey); + }, + clients: [clientId], + onReady: cb + }; + openChannel(ctx, chanData); + }; + var clearOwnedChannel = function (ctx, id, cb) { var channel = ctx.clients[id]; if (!channel) { return void cb({error: 'NO_CHANNEL'}); } @@ -877,6 +937,10 @@ define([ }); }; + messenger.openTeamChat = function (data, cId, cb) { + openTeamChat(ctx, cId, data, cb); + }; + messenger.removeClient = function (clientId) { removeClient(ctx, clientId); }; @@ -892,6 +956,9 @@ define([ if (cmd === 'GET_USERLIST') { return void getUserList(ctx, data, cb); } + if (cmd === 'OPEN_TEAM_CHAT') { + return void openTeamChat(ctx, clientId, data, cb); + } if (cmd === 'OPEN_PAD_CHAT') { return void openPadChat(ctx, clientId, data, cb); } diff --git a/www/common/outer/team.js b/www/common/outer/team.js index a683e9aaa..a70097b80 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -138,6 +138,19 @@ define([ })); }; + team.getChatData = function () { + var hash = Util.find(proxy, ['metadata', 'chat']); + if (!hash) { + hash = proxy.metadata.chat = Hash.createRandomHash('chat'); + } + var secret = Hash.getSecrets('chat', hash); + return { + channel: secret.channel, + secret: secret, + validateKey: secret.keys.validateKey + }; + }; + team.pin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; team.unpin = function (data, cb) { return void cb({error: 'EFORBIDDEN'}); }; nThen(function (waitFor) { @@ -276,6 +289,9 @@ define([ var membersSecret = Hash.getSecrets('members'); var membersHashes = Hash.getHashes(membersSecret); + var chatSecret = Hash.getSecrets('chat'); + var chatHashes = Hash.getHashes(chatSecret); + var config = { network: ctx.store.network, channel: secret.channel, @@ -319,6 +335,7 @@ define([ proxy.drive = {}; // Create metadata proxy.metadata = { + chat: chatHashes.editHash, name: name, members: membersHashes.viewHash, }; @@ -424,7 +441,7 @@ define([ Object.keys(teams).forEach(function (id) { ctx.onReadyHandlers[id] = []; openChannel(ctx, teams[id], id, waitFor(function () { - console.error('team '+id+' ready'); + console.debug('Team '+id+' ready'); })); }); @@ -448,7 +465,6 @@ define([ removeClient(ctx, clientId); }; team.execCommand = function (clientId, obj, cb) { - console.log(obj); var cmd = obj.cmd; var data = obj.data; if (cmd === 'SUBSCRIBE') { @@ -458,6 +474,9 @@ define([ if (cmd === 'LIST_TEAMS') { return void cb(store.proxy.teams); } + if (cmd === 'OPEN_TEAM_CHAT') { + return void ctx.store.messenger.openTeamChat(data, clientId, cb); + } if (cmd === 'CREATE_TEAM') { return void createTeam(ctx, data, clientId, cb); } diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 57571b6b0..8f83c4443 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -212,6 +212,15 @@ define([ }); }; + // Team Chat + var teamChatChannel; + funcs.setTeamChat = function (channel) { + teamChatChannel = channel; + }; + funcs.getTeamChat = function () { + return teamChatChannel; + }; + var cursorChannel; // common-ui-elements needs to be able to get the cursor channel to put it in metadata when // importing a template diff --git a/www/team/app-team.less b/www/team/app-team.less index 4a25cefe3..c1412b4dc 100644 --- a/www/team/app-team.less +++ b/www/team/app-team.less @@ -1,5 +1,6 @@ @import (reference) '../../customize/src/less2/include/framework.less'; @import (reference) '../../customize/src/less2/include/drive.less'; +@import (reference) '../../customize/src/less2/include/messenger.less'; @import (reference) '../../customize/src/less2/include/sidebar-layout.less'; &.cp-app-team { @@ -10,11 +11,20 @@ ); .drive_main(); + .messenger_main(); .sidebar-layout_main(); #cp-sidebarlayout-container { div#cp-sidebarlayout-rightside.cp-rightside-drive { padding: 0; + & > .cp-team-chat { + display: flex; + height: 100%; + margin: 0; + .cp-app-contacts-container { + height: 100%; + } + } & > .cp-team-drive { display: flex; height: 100%; diff --git a/www/team/inner.js b/www/team/inner.js index d39bc2fc7..1c99bd11f 100644 --- a/www/team/inner.js +++ b/www/team/inner.js @@ -10,6 +10,7 @@ define([ '/common/proxy-manager.js', '/common/hyperscript.js', '/customize/application_config.js', + '/common/messenger-ui.js', '/customize/messages.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -27,6 +28,7 @@ define([ ProxyManager, h, AppConfig, + MessengerUI, Messages) { var APP = {}; @@ -101,6 +103,9 @@ define([ 'members': [ 'cp-team-roster' ], + 'chat': [ + 'cp-team-chat' + ], }; var create = {}; @@ -131,6 +136,7 @@ define([ if (key === 'create') { $category.append($('', {'class': 'fa fa-plus-circle'})); } if (key === 'back') { $category.append($('', {'class': 'fa fa-arrow-left'})); } if (key === 'members') { $category.append($('', {'class': 'fa fa-users'})); } + if (key === 'chat') { $category.append($('', {'class': 'fa fa-comments'})); } if (key === 'drive') { $category.append($('', {'class': 'fa fa-hdd-o'})); } if (key === active) { @@ -144,7 +150,7 @@ define([ } if (active === key) { return; } active = key; - if (key === 'drive') { + if (key === 'drive' || key === 'chat') { APP.$rightside.addClass('cp-rightside-drive'); } else { APP.$rightside.removeClass('cp-rightside-drive'); @@ -326,6 +332,18 @@ define([ loadTeam(common, APP.team, false); }); + makeBlock('chat', function (common, cb) { + var container = h('div#cp-app-contacts-container.cp-app-contacts-inapp'); + var content = [container]; + APP.module.execCommand('OPEN_TEAM_CHAT', { + teamId: APP.team + }, function (obj) { + console.warn(obj); + common.setTeamChat(obj.channel); + MessengerUI.create($(container), common, true); + cb(content); + }); + }); var onEvent = function (obj) { var ev = obj.ev;