|
|
define([
|
|
|
'/common/common-messaging.js',
|
|
|
'/common/common-hash.js',
|
|
|
'/common/common-util.js',
|
|
|
], function (Messaging, Hash, Util) {
|
|
|
|
|
|
var getRandomTimeout = function (ctx) {
|
|
|
var lag = ctx.store.realtime.getLag().lag || 0;
|
|
|
return (Math.max(0, lag) + 300) * 20 * (0.5 + Math.random());
|
|
|
};
|
|
|
|
|
|
var handlers = {};
|
|
|
var removeHandlers = {};
|
|
|
|
|
|
// Store the friend request displayed to avoid duplicates
|
|
|
var friendRequest = {};
|
|
|
handlers['FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
|
|
|
|
|
// Check if the request is valid (send by the correct user)
|
|
|
if (data.msg.author !== data.msg.content.curvePublic) {
|
|
|
return void cb(true);
|
|
|
}
|
|
|
|
|
|
// Don't show duplicate friend request: if we already have a friend request
|
|
|
// in memory from the same user, dismiss the new one
|
|
|
if (friendRequest[data.msg.author]) { return void cb(true); }
|
|
|
|
|
|
friendRequest[data.msg.author] = true;
|
|
|
|
|
|
// If the user is already in our friend list, automatically accept the request
|
|
|
if (Messaging.getFriend(ctx.store.proxy, data.msg.author) ||
|
|
|
ctx.store.proxy.friends_pending[data.msg.author]) {
|
|
|
Messaging.acceptFriendRequest(ctx.store, data.msg.content, function (obj) {
|
|
|
if (obj && obj.error) {
|
|
|
return void cb();
|
|
|
}
|
|
|
cb(true);
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
cb();
|
|
|
};
|
|
|
removeHandlers['FRIEND_REQUEST'] = function (ctx, box, data) {
|
|
|
if (friendRequest[data.content.curvePublic]) {
|
|
|
delete friendRequest[data.content.curvePublic];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var friendRequestDeclined = {};
|
|
|
handlers['DECLINE_FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
|
|
setTimeout(function () {
|
|
|
// Our friend request was declined.
|
|
|
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return; }
|
|
|
|
|
|
// Remove the pending message and display the "declined" state in the UI
|
|
|
delete ctx.store.proxy.friends_pending[data.msg.author];
|
|
|
ctx.updateMetadata();
|
|
|
if (friendRequestDeclined[data.msg.author]) { return; }
|
|
|
box.sendMessage({
|
|
|
type: 'FRIEND_REQUEST_DECLINED',
|
|
|
content: {
|
|
|
user: data.msg.author,
|
|
|
name: data.msg.content.displayName
|
|
|
}
|
|
|
}, function () {
|
|
|
if (friendRequestDeclined[data.msg.author]) {
|
|
|
// TODO remove our message because another one was sent first?
|
|
|
}
|
|
|
friendRequestDeclined[data.msg.author] = true;
|
|
|
});
|
|
|
}, getRandomTimeout(ctx));
|
|
|
cb(true);
|
|
|
};
|
|
|
handlers['FRIEND_REQUEST_DECLINED'] = function (ctx, box, data, cb) {
|
|
|
ctx.updateMetadata();
|
|
|
if (friendRequestDeclined[data.msg.content.user]) { return void cb(true); }
|
|
|
friendRequestDeclined[data.msg.content.user] = true;
|
|
|
cb();
|
|
|
};
|
|
|
removeHandlers['FRIEND_REQUEST_DECLINED'] = function (ctx, box, data) {
|
|
|
if (friendRequestDeclined[data.content.user]) {
|
|
|
delete friendRequestDeclined[data.content.user];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var friendRequestAccepted = {};
|
|
|
handlers['ACCEPT_FRIEND_REQUEST'] = function (ctx, box, data, cb) {
|
|
|
// Our friend request was accepted.
|
|
|
setTimeout(function () {
|
|
|
// Make sure we really sent it
|
|
|
if (!ctx.store.proxy.friends_pending[data.msg.author]) { return; }
|
|
|
// And add the friend
|
|
|
Messaging.addToFriendList({
|
|
|
proxy: ctx.store.proxy,
|
|
|
realtime: ctx.store.realtime,
|
|
|
pinPads: ctx.pinPads
|
|
|
}, data.msg.content, function (err) {
|
|
|
if (err) { console.error(err); }
|
|
|
delete ctx.store.proxy.friends_pending[data.msg.author];
|
|
|
if (ctx.store.messenger) {
|
|
|
ctx.store.messenger.onFriendAdded(data.msg.content);
|
|
|
}
|
|
|
ctx.updateMetadata();
|
|
|
// If you have a profile page open, update it
|
|
|
if (ctx.store.modules['profile']) { ctx.store.modules['profile'].update(); }
|
|
|
if (friendRequestAccepted[data.msg.author]) { return; }
|
|
|
// Display the "accepted" state in the UI
|
|
|
box.sendMessage({
|
|
|
type: 'FRIEND_REQUEST_ACCEPTED',
|
|
|
content: {
|
|
|
user: data.msg.author,
|
|
|
name: data.msg.content.displayName
|
|
|
}
|
|
|
}, function () {
|
|
|
if (friendRequestAccepted[data.msg.author]) {
|
|
|
// TODO remove our message because another one was sent first?
|
|
|
}
|
|
|
friendRequestAccepted[data.msg.author] = true;
|
|
|
});
|
|
|
});
|
|
|
}, getRandomTimeout(ctx));
|
|
|
cb(true);
|
|
|
};
|
|
|
handlers['FRIEND_REQUEST_ACCEPTED'] = function (ctx, box, data, cb) {
|
|
|
ctx.updateMetadata();
|
|
|
if (friendRequestAccepted[data.msg.content.user]) { return void cb(true); }
|
|
|
friendRequestAccepted[data.msg.content.user] = true;
|
|
|
cb();
|
|
|
};
|
|
|
removeHandlers['FRIEND_REQUEST_ACCEPTED'] = function (ctx, box, data) {
|
|
|
if (friendRequestAccepted[data.content.user]) {
|
|
|
delete friendRequestAccepted[data.content.user];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
handlers['UNFRIEND'] = function (ctx, box, data, cb) {
|
|
|
var curve = data.msg.content.curvePublic;
|
|
|
var friend = Messaging.getFriend(ctx.store.proxy, curve);
|
|
|
if (!friend) { return void cb(true); }
|
|
|
delete ctx.store.proxy.friends[curve];
|
|
|
if (ctx.store.messenger) {
|
|
|
ctx.store.messenger.onFriendRemoved(curve, friend.channel);
|
|
|
}
|
|
|
ctx.updateMetadata();
|
|
|
cb(true);
|
|
|
};
|
|
|
|
|
|
handlers['UPDATE_DATA'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var curve = msg.author;
|
|
|
var friend = ctx.store.proxy.friends && ctx.store.proxy.friends[curve];
|
|
|
if (!friend || typeof msg.content !== "object") { return void cb(true); }
|
|
|
Object.keys(msg.content).forEach(function (key) {
|
|
|
friend[key] = msg.content[key];
|
|
|
});
|
|
|
if (ctx.store.messenger) {
|
|
|
ctx.store.messenger.onFriendUpdate(curve);
|
|
|
}
|
|
|
ctx.updateMetadata();
|
|
|
cb(true);
|
|
|
};
|
|
|
|
|
|
// Hide duplicates when receiving a SHARE_PAD notification:
|
|
|
// Keep only one notification per channel: the stronger and more recent one
|
|
|
var channels = {};
|
|
|
handlers['SHARE_PAD'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var hash = data.hash;
|
|
|
var content = msg.content;
|
|
|
// content.name, content.title, content.href, content.password
|
|
|
|
|
|
var channel = Hash.hrefToHexChannelId(content.href, content.password);
|
|
|
var parsed = Hash.parsePadUrl(content.href);
|
|
|
var mode = parsed.hashData && parsed.hashData.mode || 'n/a';
|
|
|
|
|
|
var old = channels[channel];
|
|
|
var toRemove;
|
|
|
if (old) {
|
|
|
// New hash is weaker, ignore
|
|
|
if (old.mode === 'edit' && mode === 'view') {
|
|
|
return void cb(true);
|
|
|
}
|
|
|
// New hash is not weaker, clear the old one
|
|
|
toRemove = old.data;
|
|
|
}
|
|
|
|
|
|
// Update the data
|
|
|
channels[channel] = {
|
|
|
mode: mode,
|
|
|
data: {
|
|
|
type: box.type,
|
|
|
hash: hash
|
|
|
}
|
|
|
};
|
|
|
|
|
|
cb(false, toRemove);
|
|
|
};
|
|
|
removeHandlers['SHARE_PAD'] = function (ctx, box, data, hash) {
|
|
|
var content = data.content;
|
|
|
var channel = Hash.hrefToHexChannelId(content.href, content.password);
|
|
|
var old = channels[channel];
|
|
|
if (old && old.data && old.data.hash === hash) {
|
|
|
delete channels[channel];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// Hide duplicates when receiving a SUPPORT_MESSAGE notification
|
|
|
var supportMessage = false;
|
|
|
handlers['SUPPORT_MESSAGE'] = function (ctx, box, data, cb) {
|
|
|
if (supportMessage) { return void cb(true); }
|
|
|
supportMessage = true;
|
|
|
cb();
|
|
|
};
|
|
|
|
|
|
// Incoming edit rights request: add data before sending it to inner
|
|
|
handlers['REQUEST_PAD_ACCESS'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
|
|
|
var channel = content.channel;
|
|
|
var res = ctx.store.manager.findChannel(channel);
|
|
|
|
|
|
if (!res.length) { return void cb(true); }
|
|
|
|
|
|
var edPublic = ctx.store.proxy.edPublic;
|
|
|
var title, href;
|
|
|
if (!res.some(function (obj) {
|
|
|
if (obj.data &&
|
|
|
Array.isArray(obj.data.owners) && obj.data.owners.indexOf(edPublic) !== -1 &&
|
|
|
obj.data.href) {
|
|
|
href = obj.data.href;
|
|
|
title = obj.data.filename || obj.data.title;
|
|
|
return true;
|
|
|
}
|
|
|
})) { return void cb(true); }
|
|
|
|
|
|
content.title = title;
|
|
|
content.href = href;
|
|
|
cb(false);
|
|
|
};
|
|
|
|
|
|
handlers['GIVE_PAD_ACCESS'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
|
|
|
var channel = content.channel;
|
|
|
var res = ctx.store.manager.findChannel(channel);
|
|
|
|
|
|
var title;
|
|
|
res.forEach(function (obj) {
|
|
|
if (obj.data && !obj.data.href) {
|
|
|
if (!title) { title = obj.data.filename || obj.data.title; }
|
|
|
obj.data.href = content.href;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
content.title = title || content.title;
|
|
|
cb(false);
|
|
|
};
|
|
|
|
|
|
// Hide duplicates when receiving an ADD_OWNER notification:
|
|
|
var addOwners = {};
|
|
|
handlers['ADD_OWNER'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
if (!content.teamChannel && !(content.href && content.title && content.channel)) {
|
|
|
console.log('Remove invalid notification');
|
|
|
return void cb(true);
|
|
|
}
|
|
|
|
|
|
var channel = content.channel || content.teamChannel;
|
|
|
|
|
|
if (addOwners[channel]) { return void cb(true); }
|
|
|
addOwners[channel] = {
|
|
|
type: box.type,
|
|
|
hash: data.hash
|
|
|
};
|
|
|
|
|
|
cb(false);
|
|
|
};
|
|
|
removeHandlers['ADD_OWNER'] = function (ctx, box, data) {
|
|
|
var channel = data.content.channel || data.content.teamChannel;
|
|
|
if (addOwners[channel]) {
|
|
|
delete addOwners[channel];
|
|
|
}
|
|
|
};
|
|
|
|
|
|
handlers['RM_OWNER'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
if (!content.channel && !content.teamChannel) {
|
|
|
console.log('Remove invalid notification');
|
|
|
return void cb(true);
|
|
|
}
|
|
|
|
|
|
var channel = content.channel || content.teamChannel;
|
|
|
|
|
|
// If our ownership rights for a team have been removed, update the owner flag
|
|
|
if (content.teamChannel) {
|
|
|
var teams = ctx.store.proxy.teams || {};
|
|
|
Object.keys(teams).some(function (id) {
|
|
|
if (teams[id].channel === channel) {
|
|
|
teams[id].owner = false;
|
|
|
return true;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (addOwners[channel] && content.pending) {
|
|
|
return void cb(false, addOwners[channel]);
|
|
|
}
|
|
|
cb(false);
|
|
|
};
|
|
|
|
|
|
var invitedTo = {};
|
|
|
handlers['INVITE_TO_TEAM'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
if (!content.team) {
|
|
|
console.log('Remove invalid notification');
|
|
|
return void cb(true);
|
|
|
}
|
|
|
|
|
|
var invited = invitedTo[content.team.channel];
|
|
|
if (invited) {
|
|
|
console.log('removing old invitation');
|
|
|
cb(false, invited);
|
|
|
invitedTo[content.team.channel] = {
|
|
|
type: box.type,
|
|
|
hash: data.hash
|
|
|
};
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
var myTeams = Util.find(ctx, ['store', 'proxy', 'teams']) || {};
|
|
|
var alreadyMember = Object.keys(myTeams).some(function (k) {
|
|
|
var team = myTeams[k];
|
|
|
return team.channel === content.team.channel;
|
|
|
});
|
|
|
if (alreadyMember) { return void cb(true); }
|
|
|
|
|
|
invitedTo[content.team.channel] = {
|
|
|
type: box.type,
|
|
|
hash: data.hash
|
|
|
};
|
|
|
|
|
|
cb(false);
|
|
|
};
|
|
|
removeHandlers['INVITE_TO_TEAM'] = function (ctx, box, data) {
|
|
|
var channel = Util.find(data, ['content', 'team', 'channel']);
|
|
|
delete invitedTo[channel];
|
|
|
};
|
|
|
|
|
|
handlers['KICKED_FROM_TEAM'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
if (!content.teamChannel) {
|
|
|
console.log('Remove invalid notification');
|
|
|
return void cb(true);
|
|
|
}
|
|
|
|
|
|
if (invitedTo[content.teamChannel] && content.pending) {
|
|
|
return void cb(true, invitedTo[content.teamChannel]);
|
|
|
}
|
|
|
|
|
|
cb(false);
|
|
|
};
|
|
|
|
|
|
handlers['INVITE_TO_TEAM_ANSWER'] = function (ctx, box, data, cb) {
|
|
|
var msg = data.msg;
|
|
|
var content = msg.content;
|
|
|
|
|
|
if (msg.author !== content.user.curvePublic) { return void cb(true); }
|
|
|
if (!content.teamChannel) {
|
|
|
console.log('Remove invalid notification');
|
|
|
return void cb(true);
|
|
|
}
|
|
|
|
|
|
var myTeams = Util.find(ctx, ['store', 'proxy', 'teams']) || {};
|
|
|
var teamId;
|
|
|
var team;
|
|
|
Object.keys(myTeams).some(function (k) {
|
|
|
var _team = myTeams[k];
|
|
|
if (_team.channel === content.teamChannel) {
|
|
|
teamId = k;
|
|
|
team = _team;
|
|
|
return true;
|
|
|
}
|
|
|
});
|
|
|
if (!teamId) { return void cb(true); }
|
|
|
|
|
|
content.team = team;
|
|
|
|
|
|
if (!content.answer) {
|
|
|
// If they declined the invitation, remove them from the roster (as a pending member)
|
|
|
try {
|
|
|
var module = ctx.store.modules['team'];
|
|
|
module.removeFromTeam(teamId, msg.author);
|
|
|
} catch (e) { console.error(e); }
|
|
|
}
|
|
|
|
|
|
cb(false);
|
|
|
};
|
|
|
|
|
|
|
|
|
return {
|
|
|
add: function (ctx, box, data, cb) {
|
|
|
/**
|
|
|
* data = {
|
|
|
msg: {
|
|
|
type: 'STRING',
|
|
|
author: 'curvePublicString',
|
|
|
content: {} (depend on the "type")
|
|
|
},
|
|
|
hash: 'string'
|
|
|
}
|
|
|
*/
|
|
|
if (!data.msg) { return void cb(true); }
|
|
|
var type = data.msg.type;
|
|
|
|
|
|
if (handlers[type]) {
|
|
|
try {
|
|
|
handlers[type](ctx, box, data, cb);
|
|
|
} catch (e) {
|
|
|
console.error(e);
|
|
|
cb();
|
|
|
}
|
|
|
} else {
|
|
|
cb();
|
|
|
}
|
|
|
},
|
|
|
remove: function (ctx, box, data, h) {
|
|
|
// We sometimes try to delete non-existant data (with "delete box.content[h]")
|
|
|
// In this case, we don't have the data in memory so we don't need to call
|
|
|
// any "remove" handler
|
|
|
if (!data) { return; }
|
|
|
var type = data.type;
|
|
|
|
|
|
if (removeHandlers[type]) {
|
|
|
try {
|
|
|
removeHandlers[type](ctx, box, data, h);
|
|
|
} catch (e) {
|
|
|
console.error(e);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
});
|
|
|
|