Merge branch 'chat3' into team

This commit is contained in:
yflory 2019-09-17 11:33:26 +02:00
commit fb22a4a792
19 changed files with 1152 additions and 114 deletions

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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,
@ -40,7 +41,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 +76,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);
@ -503,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] : {};
@ -619,7 +624,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];
}
@ -682,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);
@ -711,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 {
@ -722,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);
}
@ -780,10 +786,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; }
@ -817,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);
@ -826,15 +843,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') {
@ -849,6 +859,10 @@ define([
onPadChatReady(data);
return;
}
if (cmd === 'TEAMCHAT_READY') {
onTeamChatReady(data);
return;
}
if (cmd === 'DISCONNECT') {
onDisconnect();
return;
@ -881,7 +895,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;

View file

@ -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];

View file

@ -9,13 +9,13 @@ 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',
'/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',
@ -26,8 +26,9 @@ 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, NetConfig, AppConfig,
Realtime, Messaging,
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger,
NetConfig, AppConfig,
Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) {
var create = function () {
@ -1241,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) {
@ -1753,7 +1746,6 @@ define([
// Clients management
var driveEventClients = [];
var messengerEventClients = [];
var dropChannel = function (chanId) {
try {
@ -1779,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); }
@ -1873,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) {
@ -2025,9 +1991,10 @@ define([
});
userObject.fixFiles();
SF.loadSharedFolders(Store, store.network, store, userObject, waitFor);
loadMessenger();
loadCursor();
loadOnlyOffice();
loadUniversal(Messenger, 'messenger', waitFor);
store.messenger = store.modules['messenger'];
loadUniversal(Profile, 'profile', waitFor);
loadUniversal(Team, 'team', waitFor);
cleanFriendRequests();

View file

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

View file

@ -0,0 +1,992 @@
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<l; i++) {
u[i] = obj[i];
}
return u;
};
var createData = Messaging.createData;
var getFriend = function (proxy, pubkey) {
if (pubkey === proxy.curvePublic) {
var data = createData(proxy);
delete data.channel;
return data;
}
return proxy.friends ? proxy.friends[pubkey] : undefined;
};
var getFriendList = Msg.getFriendList = function (proxy) {
if (!proxy.friends) { proxy.friends = {}; }
return proxy.friends;
};
var msgAlreadyKnown = function (channel, sig) {
return channel.messages.some(function (message) {
return message.sig === sig;
});
};
var getFriendFromChannel = function (ctx, id) {
var proxy = ctx.store.proxy;
var friends = getFriendList(proxy);
var friend;
for (var k in friends) {
if (friends[k].channel === id) {
friend = friends[k];
break;
}
}
return friend;
};
var initRangeRequest = function (ctx, txid, chanId, cb) {
if (!ctx.range_requests) { ctx.range_requests = {}; }
ctx.range_requests[txid] = {
messages: [],
cb: cb,
chanId: chanId,
};
};
var getRangeRequest = function (ctx, txid) {
return ctx.range_requests[txid];
};
var deleteRangeRequest = function (ctx, txid) {
delete ctx.range_requests[txid];
};
// History
var getMoreHistory = function (ctx, chanId, hash, count, cb) {
if (typeof(cb) !== 'function') { return; }
if (typeof(hash) !== 'string') {
// Channel is empty!
return void cb([]);
}
var chan = ctx.channels[chanId];
if (typeof(chan) === 'undefined') {
console.error("chan is undefined. we're going to have a problem here");
return;
}
var txid = Util.uid();
initRangeRequest(ctx, txid, chanId, cb);
var msg = [ 'GET_HISTORY_RANGE', chan.id, {
from: hash,
count: count,
txid: txid,
}
];
var network = ctx.store.network;
network.sendto(network.historyKeeper, JSON.stringify(msg)).then(function () {
}, function (err) {
console.error(err);
});
};
var getChannelMessagesSince = function (ctx, channel, data, keys) {
var network = ctx.store.network;
console.log('Fetching [%s] messages since [%s]', channel.id, data.lastKnownHash || '');
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);
var msg0 = ['GET_HISTORY_RANGE', channel.id, {
//from: hash,
count: 10,
txid: txid,
}
];
network.sendto(network.historyKeeper, JSON.stringify(msg0)).then(function () {
}, function (err) {
console.error(err);
});
return;
}
// Friend chat, intial history
var proxy = ctx.store.proxy;
var friend = getFriendFromChannel(ctx, channel.id) || {};
var cfg = {
metadata: {
validateKey: keys ? keys.validateKey : undefined,
owners: [proxy.edPublic, friend.edPublic],
},
lastKnownHash: data.lastKnownHash
};
var msg = ['GET_HISTORY', channel.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then(function () {}, function (err) {
console.error(err);
});
};
var setChannelHead = function (ctx, id, hash, cb) {
var channel = ctx.channels[id];
if (channel.isFriendChat) {
var friend = getFriendFromChannel(ctx, id);
if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); }
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'});
}
cb();
};
var onChannelReady = function (ctx, chanId) {
var channel = ctx.channels[chanId];
if (!channel) { return; }
channel.ready = true;
channel.onReady.fire();
};
// Id message allows us to map a netfluxId with a public curve key
var onIdMessage = function (ctx, msg, sender) {
// Parse the history message and make sure it is related to an existing channel
var channel, parsed0;
try {
parsed0 = JSON.parse(msg);
channel = ctx.channels[parsed0.channel];
if (!channel || channel.wc.members.indexOf(sender) === -1) { return; }
} catch (e) {
return void console.error(e, msg);
}
// Decrypt and parse its content... // XXX decryption outer
var decryptedMsg = channel.decrypt(parsed0.msg);
if (!decryptedMsg) { return void console.error("Failed to decrypt message"); }
var parsed = Util.tryParse(decryptedMsg);
if (!parsed) { return void console.error(decryptedMsg); }
// Keep only ID messages here
if (parsed[0] !== Types.mapId && parsed[0] !== Types.mapIdAck) { return; }
// check that the responding peer's encrypted netflux id matches
// the sender field. This is to prevent replay attacks.
if (parsed[2] !== sender || !parsed[1]) { return; }
channel.mapId[sender] = parsed[1];
ctx.emit('JOIN', {
info: parsed[1],
id: channel.id
}, channel.clients);
if (channel.readOnly) { return; } // Don't send your key if you're a reader
if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK
// Answer with your own key
var proxy = ctx.store.proxy;
var myData = createData(proxy);
delete myData.channel;
var rMsg = [Types.mapIdAck, myData, channel.wc.myID];
var rMsgStr = JSON.stringify(rMsg);
var cryptMsg = channel.encrypt(rMsgStr);
var data = {
channel: channel.id,
msg: cryptMsg
};
var network = ctx.store.network;
network.sendto(sender, JSON.stringify(data));
};
var orderMessages = function (channel, new_messages) {
var messages = channel.messages;
// TODO improve performance, guarantee correct ordering
new_messages.reverse().forEach(function (msg) {
messages.unshift(msg);
});
};
var removeFromFriendList = function (ctx, curvePublic, cb) {
var proxy = ctx.store.proxy;
var friends = proxy.friends;
if (!friends) { return; }
delete friends[curvePublic];
Realtime.whenRealtimeSyncs(ctx.store.realtime, function () {
ctx.updateMetadata();
cb();
});
};
var pushMsg = function (ctx, channel, cryptMsg) {
var sig = cryptMsg.slice(0, 64);
if (msgAlreadyKnown(channel, sig)) { return; }
var msg = channel.decrypt(cryptMsg);
var parsedMsg = JSON.parse(msg);
var curvePublic;
// Chat message
if (parsedMsg[0] === Types.message) {
var res = {
type: parsedMsg[0],
sig: sig,
author: parsedMsg[1],
time: parsedMsg[2],
text: parsedMsg[3],
channel: channel.id,
name: parsedMsg[4] // Display name for multi-user rooms
};
channel.messages.push(res);
if (channel.ready) { ctx.emit('MESSAGE', res, channel.clients); }
return true;
}
var proxy = ctx.store.proxy;
if (parsedMsg[0] === Types.unfriend) {
curvePublic = parsedMsg[1];
// If this a removal from our part in another tab, do nothing.
// The channel is already closed in the proxy.on('remove') part
if (curvePublic === proxy.curvePublic) { return; }
removeFromFriendList(ctx, curvePublic, function () {
channel.wc.leave(Types.unfriend);
delete ctx.channels[channel.id];
ctx.emit('UNFRIEND', {
curvePublic: curvePublic,
fromMe: false
}, channel.clients);
});
return;
}
};
var onDirectMessage = function (ctx, msg, sender) {
var hk = ctx.store.network.historyKeeper;
if (sender !== hk) { return void onIdMessage(ctx, msg, sender); }
var parsed = JSON.parse(msg);
if ((parsed.validateKey || parsed.owners) && parsed.channel) {
// Metadata message
return;
}
var channel;
if (/HISTORY_RANGE/.test(parsed[0])) {
var txid = parsed[1];
var req = getRangeRequest(ctx, txid);
var type = parsed[0];
if (!req) { return; }
channel = ctx.channels[req.chanId];
if (!channel) { return; }
if (!req.cb) { // This is the initial history for a pad chat
if (type === 'HISTORY_RANGE') {
if (!Array.isArray(parsed[2])) { return; }
pushMsg(ctx, channel, parsed[2][4]);
} else if (type === 'HISTORY_RANGE_END') {
onChannelReady(ctx, req.chanId);
return deleteRangeRequest(ctx, txid);
}
return;
}
// "More history": we need to store all the messages and cb all of them at once
// to make sure we won't display them in the wrong order (we receive the oldest first...)
if (type === 'HISTORY_RANGE') {
req.messages.push(parsed[2]);
} else if (type === 'HISTORY_RANGE_END') {
// process all the messages (decrypt)
var decrypted = req.messages.map(function (msg) {
if (msg[2] !== 'MSG') { return; }
try {
return {
d: JSON.parse(channel.decrypt(msg[4])),
sig: msg[4].slice(0, 64),
};
} catch (e) {
return void console.log('failed to decrypt');
}
}).filter(function (decrypted) {
if (!decrypted || !decrypted.d || decrypted.d[0] !== Types.message) { return; }
if (msgAlreadyKnown(channel, decrypted.sig)) { return; }
return decrypted;
}).map(function (O) {
return {
type: O.d[0],
sig: O.sig,
author: O.d[1],
time: O.d[2],
text: O.d[3],
channel: req.chanId,
name: O.d[4]
};
});
orderMessages(channel, decrypted);
req.cb(decrypted);
return deleteRangeRequest(ctx, txid);
} else {
console.log(parsed);
}
return;
}
if (parsed.channel && ctx.channels[parsed.channel]) {
channel = ctx.channels[parsed.channel];
// Error in initial history
// History cleared while we're in the channel
if (parsed.error === 'ECLEARED') {
setChannelHead(ctx, parsed.channel, '', function () {});
channel.messages = [];
ctx.emit('CLEAR_CHANNEL', parsed.channel, channel.clients);
return;
}
// History cleared while we were offline
// ==> 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,
isTeamChat: data.isTeamChat,
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(ctx, 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
}]);
}
// 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];
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 if (r.isTeamChat) {
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(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'}); }
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.openTeamChat = function (data, cId, cb) {
openTeamChat(ctx, cId, data, cb);
};
messenger.removeClient = function (clientId) {
removeClient(ctx, clientId);
};
messenger.execCommand = function (clientId, obj, cb) {
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_TEAM_CHAT') {
return void openTeamChat(ctx, clientId, 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;
});

View file

@ -54,9 +54,6 @@ define([
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -134,7 +134,6 @@ define([
});
}
}), {
messenger: cfg.messaging,
driveEvents: cfg.driveEvents
});
}));
@ -1091,20 +1090,17 @@ 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);
});
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

View file

@ -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

View file

@ -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%;

View file

@ -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 = {};
@ -103,6 +105,9 @@ define([
'members': [
'cp-team-roster'
],
'chat': [
'cp-team-chat'
],
};
var create = {};
@ -133,6 +138,7 @@ define([
if (key === 'create') { $category.append($('<span>', {'class': 'fa fa-plus-circle'})); }
if (key === 'back') { $category.append($('<span>', {'class': 'fa fa-arrow-left'})); }
if (key === 'members') { $category.append($('<span>', {'class': 'fa fa-users'})); }
if (key === 'chat') { $category.append($('<span>', {'class': 'fa fa-comments'})); }
if (key === 'drive') { $category.append($('<span>', {'class': 'fa fa-hdd-o'})); }
if (key === active) {
@ -146,7 +152,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');
@ -329,6 +335,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;