Add ability to close livechat rooms

pull/3108/head
Diego Sampaio 10 years ago
parent cfde676386
commit 33da587c93
  1. 3
      packages/rocketchat-lib/i18n/en.i18n.json
  2. 3
      packages/rocketchat-lib/i18n/pt.i18n.json
  3. 6
      packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js
  4. 6
      packages/rocketchat-lib/server/models/Rooms.coffee
  5. 3
      packages/rocketchat-lib/server/models/Subscriptions.coffee
  6. 31
      packages/rocketchat-livechat/app/client/lib/chatMessages.coffee
  7. 3
      packages/rocketchat-livechat/app/client/views/message.coffee
  8. 2
      packages/rocketchat-livechat/app/client/views/register.html
  9. 3
      packages/rocketchat-livechat/app/i18n/en.i18n.json
  10. 21
      packages/rocketchat-livechat/client/ui.js
  11. 4
      packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html
  12. 50
      packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.js
  13. 1
      packages/rocketchat-livechat/package.js
  14. 2
      packages/rocketchat-livechat/server/api.js
  15. 124
      packages/rocketchat-livechat/server/lib/Livechat.js
  16. 13
      packages/rocketchat-livechat/server/methods/closeRoom.js
  17. 2
      packages/rocketchat-livechat/server/methods/getInitialData.js
  18. 13
      packages/rocketchat-livechat/server/models/Rooms.js
  19. 1
      packages/rocketchat-livechat/server/models/indexes.js
  20. 9
      packages/rocketchat-livechat/server/startup.js
  21. 7
      server/startup/migrations/v050.js

@ -225,6 +225,7 @@
"Compact_View" : "Compact View",
"Confirm_password" : "Confirm your password",
"Conversation" : "Conversation",
"Conversation_closed" : "Conversation closed: __comment__.",
"Convert_Ascii_Emojis" : "Convert ASCII to Emoji",
"Copied" : "Copied",
"Copy_to_clipboard" : "Copy to clipboard",
@ -747,6 +748,7 @@
"Placeholder_for_email_or_username_login_field" : "Placeholder for email or username login field",
"Placeholder_for_password_login_field" : "Placeholder for password login field",
"Please_add_a_comment" : "Please add a comment",
"Please_add_a_comment_to_close_the_room" : "Please, add a comment to close the room",
"Please_answer_survey" : "Please take a moment to answer a quick survey about this chat",
"Please_enter_value_for_url" : "Please enter a value for the url of your avatar.",
"Please_enter_your_new_password_below" : "Please enter your new password below:",
@ -1112,6 +1114,7 @@
"You_can_change_a_different_avatar_too" : "You can override the avatar used to post from this integration.",
"You_can_search_using_RegExp_eg" : "You can search using RegExp. e.g.",
"You_can_use_an_emoji_as_avatar" : "You can also use an emoji as an avatar.",
"You_cant_leave_a_livechat_room_Please_use_the_close_button" : "You can't leave a livechat room. Please, use the close button.",
"You_have_been_muted" : "You have been muted and cannot speak in this room",
"You_have_not_verified_your_email" : "You have not verified your email.",
"You_have_successfully_unsubscribed" : "You have successfully unsubscribed from our Mailling List.",

@ -1100,6 +1100,7 @@
"You_are_not_authorized_to_view_this_page" : "Você não possui permissão para visualizar esta página.",
"You_can_change_a_different_avatar_too" : "Você pode substituir o avatar usado para enviar a partir desta integração.",
"You_can_use_an_emoji_as_avatar" : "Você também pode usar um emoji como um avatar.",
"You_cant_leave_a_livechat_room_Please_use_the_close_button" : "Você não pode sair de uma sala de livechat. Por favor, use o botão de fechar sala.",
"You_have_been_muted" : "Você foi silenciado e não pode falar nesta sala",
"You_have_not_verified_your_email" : "Você ainda não verificou o seu e-mail.",
"You_have_successfully_unsubscribed" : "A partir de agora você não está mais cadastrado em nossa lista de e-mails.",
@ -1120,4 +1121,4 @@
"Your_Open_Source_solution" : "Sua própria solução Open Source",
"Your_password_is_wrong" : "Sua senha está errada!",
"Your_push_was_sent_to_s_devices" : "Sua natificação foi enviada para %s dispositivos"
}
}

@ -271,8 +271,12 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
if (userIdsToNotify.length > 0) {
for (j = 0, len1 = userIdsToNotify.length; j < len1; j++) {
usersOfMentionId = userIdsToNotify[j];
let title = '@' + user.username;
if (room.name) {
title += ' @ #' + room.name;
}
RocketChat.Notifications.notifyUser(usersOfMentionId, 'notification', {
title: '@' + user.username + ' @ #' + room.name,
title: title,
text: message.msg,
payload: {
rid: message.rid,

@ -176,12 +176,6 @@ RocketChat.models.Rooms = new class extends RocketChat.models._Base
return @find query, options
findByVisitorToken: (visitorToken, options) ->
query =
"v.token": visitorToken
return @find query, options
# UPDATE
archiveById: (_id) ->

@ -5,7 +5,8 @@ RocketChat.models.Subscriptions = new class extends RocketChat.models._Base
@tryEnsureIndex { 'rid': 1, 'u._id': 1 }, { unique: 1 }
@tryEnsureIndex { 'rid': 1, 'alert': 1, 'u._id': 1 }
@tryEnsureIndex { 'rid': 1, 'roles': 1 }
@tryEnsureIndex { 'u._id': 1, 'name': 1, 't': 1 }, { unique: 1 }
@tryEnsureIndex { 'u._id': 1, 'name': 1, 't': 1 }
@tryEnsureIndex { 'u._id': 1, 'name': 1, 't': 1, 'code': 1 }, { unique: 1 }
@tryEnsureIndex { 'open': 1 }
@tryEnsureIndex { 'alert': 1 }
@tryEnsureIndex { 'unread': 1 }

@ -77,22 +77,15 @@ class @ChatMessages
sendMessage = (callback) ->
msgObject = { _id: Random.id(), rid: rid, msg: msg, token: visitor.getToken() }
MsgTyping.stop(rid)
#Check if message starts with /command
if msg[0] is '/'
match = msg.match(/^\/([^\s]+)(?:\s+(.*))?$/m)
if(match?)
command = match[1]
param = match[2]
Meteor.call 'slashCommand', {cmd: command, params: param, msg: msgObject }
else
#Run to allow local encryption
# Meteor.call 'onClientBeforeSendMessage', {}
Meteor.call 'sendMessageLivechat', msgObject, (error, result) ->
if error
ChatMessage.update msgObject._id, { $set: { error: true } }
showError error.reason
else
callback?(result)
Meteor.call 'sendMessageLivechat', msgObject, (error, result) ->
if error
ChatMessage.update msgObject._id, { $set: { error: true } }
showError error.reason
else if result.newRoom and result.rid?
ChatMessage.update result._id, _.omit(result, '_id')
visitor.subscribeToRoom(result.rid)
visitor.setRoom(result.rid)
if not Meteor.userId()
Meteor.call 'livechat:registerGuest', { token: visitor.getToken() }, (error, result) ->
@ -103,11 +96,7 @@ class @ChatMessages
if error
return showError error.reason
sendMessage (message) ->
ChatMessage.update message._id, _.omit(message, '_id')
if message.rid?
visitor.subscribeToRoom(message.rid)
visitor.setRoom(message.rid)
sendMessage()
else
sendMessage()

@ -25,6 +25,7 @@ Template.message.helpers
when 'ul' then tr('User_left', { context: this.u.gender }, { user_left: this.u.username })
when 'uj' then tr('User_joined_channel', { context: this.u.gender }, { user: this.u.username })
when 'wm' then t('Welcome', { user: this.u.username })
when 'livechat-close' then t('Conversation_finished')
# when 'rtc' then RocketChat.callbacks.run 'renderRtcMessage', this
else
this.html = this.msg
@ -36,7 +37,7 @@ Template.message.helpers
return this.html
system: ->
return 'system' if this.t in ['s', 'p', 'f', 'r', 'au', 'ru', 'ul', 'wm', 'uj']
return 'system' if this.t in ['s', 'p', 'f', 'r', 'au', 'ru', 'ul', 'wm', 'uj', 'livechat-close']
Template.message.onViewRendered = (context) ->

@ -1,7 +1,7 @@
<template name="register">
<form id="livechat-registration" class="livechat-form">
<div class="error">
<span>{{error}}</span>
<span>{{{error}}}</span>
</div>
<label>

@ -1,6 +1,7 @@
{
"Additional_Feedback" : "Additional Feedback",
"Appearance" : "Appearance",
"Conversation_finished" : "Conversation finished",
"How_friendly_was_the_chat_agent" : "How friendly was the chat agent?",
"How_knowledgeable_was_the_chat_agent" : "How knowledgeable was the chat agent?",
"How_responsive_was_the_chat_agent" : "How responsive was the chat agent?",
@ -15,4 +16,4 @@
"Survey_instructions" : "Rate each question according to your satisfaction, 1 meaning you are completely unsatisfied and 5 meaning you are completely satisfied.",
"Thank_you_for_your_feedback" : "Thank you for your feedback",
"We_are_offline_Sorry_for_the_inconvenience" : "We are offline. Sorry for the inconvenience."
}
}

@ -22,7 +22,15 @@ RocketChat.roomTypes.add('l', 5, {
},
roomName(roomData) {
return roomData.name;
console.log('roomName.roomData ->', roomData);
if (!roomData.name) {
const sub = ChatSubscription.findOne({ rid: roomData._id }, { fields: { name: 1 } });
if (sub) {
return sub.name;
}
} else {
return roomData.name;
}
},
condition: () => {
@ -80,3 +88,14 @@ RocketChat.TabBar.addButton({
template: 'externalSearch',
order: 10
});
RocketChat.MessageTypes.registerType({
id: 'livechat-close',
system: true,
message: 'Conversation_closed',
data(message) {
return {
comment: message.msg
};
}
});

@ -42,7 +42,9 @@
<nav class="centered-buttons">
<button class='button lightblue edit-livechat button-block'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
<button class='button close-livechat button-block'><span><i class='icon-download'></i> {{_ "Close"}}</span></button>
{{#if roomOpen}}
<button class='button close-livechat button-block'><span><i class='icon-download'></i> {{_ "Close"}}</span></button>
{{/if}}
<!-- <button class="button pvt-msg"><span><i class="icon-forward"></i> {{_ "Forward"}}</span></button> -->
</nav>

@ -89,6 +89,12 @@ Template.visitorInfo.helpers({
instance.editing.set(false);
}
};
},
roomOpen() {
const room = ChatRoom.findOne({ _id: this.rid });
return room.open;
}
});
@ -106,30 +112,30 @@ Template.visitorInfo.events({
type: 'input',
inputPlaceholder: t('Please_add_a_comment'),
showCancelButton: true,
// confirmButtonColor: '#DD6B55',
// confirmButtonText: t('Yes'),
// cancelButtonText: t('Cancel'),
closeOnConfirm: false
}, () => {
swal({
title: t('Chat_closed'),
text: t('Chat_closed_successfully'),
type: 'success',
timer: 1000,
showConfirmButton: false
}, (inputValue) => {
if (!inputValue) {
swal.showInputError(t('Please_add_a_comment_to_close_the_room'));
return false;
}
if (s.trim(inputValue) === '') {
swal.showInputError(t('Please_add_a_comment_to_close_the_room'));
return false;
}
Meteor.call('livechat:closeRoom', this.rid, inputValue, function(error/*, result*/) {
if (error) {
return handleError(error);
}
swal({
title: t('Chat_closed'),
text: t('Chat_closed_successfully'),
type: 'success',
timer: 1000,
showConfirmButton: false
});
});
// Meteor.call('livechat:removeDepartment', this._id, function(error/*, result*/) {
// if (error) {
// return handleError(error);
// }
// swal({
// title: t('Removed'),
// text: t('Department_removed'),
// type: 'success',
// timer: 1000,
// showConfirmButton: false
// });
// });
});
}
});

@ -98,6 +98,7 @@ Package.onUse(function(api) {
api.addFiles('server/methods/addAgent.js', 'server');
api.addFiles('server/methods/addManager.js', 'server');
api.addFiles('server/methods/changeLivechatStatus.js', 'server');
api.addFiles('server/methods/closeRoom.js', 'server');
api.addFiles('server/methods/getCustomFields.js', 'server');
api.addFiles('server/methods/getInitialData.js', 'server');
api.addFiles('server/methods/pageVisited.js', 'server');

@ -20,7 +20,7 @@ Api.addRoute('sms-incoming/:service', {
};
if (visitor) {
const rooms = RocketChat.models.Rooms.findByVisitorToken(visitor.profile.token).fetch();
const rooms = RocketChat.models.Rooms.findOpenByVisitorToken(visitor.profile.token).fetch();
if (rooms && rooms.length > 0) {
sendMessage.message.rid = rooms[0]._id;

@ -7,9 +7,14 @@ RocketChat.Livechat = {
}
},
sendMessage({ guest, message, roomInfo }) {
var agent, room;
var room = RocketChat.models.Rooms.findOneById(message.rid);
var newRoom = false;
if (room && !room.open) {
message.rid = Random.id();
room = null;
}
room = RocketChat.models.Rooms.findOneById(message.rid);
if (room == null) {
// if no department selected verify if there is only one active and use it
@ -20,7 +25,7 @@ RocketChat.Livechat = {
}
}
agent = RocketChat.Livechat.getNextAgent(guest.department);
const agent = RocketChat.Livechat.getNextAgent(guest.department);
if (!agent) {
throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
}
@ -29,7 +34,6 @@ RocketChat.Livechat = {
room = _.extend({
_id: message.rid,
name: guest.username,
msgs: 1,
lm: new Date(),
code: roomCode,
@ -38,7 +42,8 @@ RocketChat.Livechat = {
ts: new Date(),
v: {
token: message.token
}
},
open: true
}, roomInfo);
let subscriptionData = {
rid: message.rid,
@ -60,13 +65,15 @@ RocketChat.Livechat = {
RocketChat.models.Rooms.insert(room);
RocketChat.models.Subscriptions.insert(subscriptionData);
newRoom = true;
} else {
room = Meteor.call('canAccessRoom', message.rid, guest._id);
}
if (!room) {
throw new Meteor.Error('cannot-acess-room');
}
return RocketChat.sendMessage(guest, message, room);
return _.extend(RocketChat.sendMessage(guest, message, room), { newRoom: newRoom });
},
registerGuest({ token, name, email, department, phone, loginToken, username } = {}) {
check(token, String);
@ -79,58 +86,75 @@ RocketChat.Livechat = {
if (!username) {
username = RocketChat.models.Users.getNextVisitorUsername();
} else {
// const existingUser = RocketChat.models.Users.findOneByUsername(username);
// if (existingUser) {
// throw new Meteor.Error
// }
}
var userData = {
username: username,
globalRoles: ['livechat-guest'],
department: department,
type: 'visitor'
let updateUser = {
$set: {
profile: {
guest: true,
token: token
}
}
};
if (this.connection) {
userData.userAgent = this.connection.httpHeaders['user-agent'];
userData.ip = this.connection.httpHeaders['x-real-ip'] || this.connection.clientAddress;
userData.host = this.connection.httpHeaders.host;
}
var existingUser = null;
const userId = Accounts.insertUserDoc({}, userData);
var userId;
let updateUser = {
name: name,
profile: {
guest: true,
token: token
if (s.trim(email) !== '' && (existingUser = RocketChat.models.Users.findOneByEmailAddress(email))) {
if (existingUser.type !== 'visitor') {
throw new Meteor.Error('error-invalid-user', 'This email belongs to a registered user.');
}
};
updateUser.$addToSet = {
globalRoles: 'livechat-guest'
};
if (loginToken) {
updateUser.$addToSet['services.resume.loginTokens'] = loginToken;
}
userId = existingUser._id;
} else {
updateUser.$set.name = name;
var userData = {
username: username,
globalRoles: ['livechat-guest'],
department: department,
type: 'visitor'
};
if (this.connection) {
userData.userAgent = this.connection.httpHeaders['user-agent'];
userData.ip = this.connection.httpHeaders['x-real-ip'] || this.connection.clientAddress;
userData.host = this.connection.httpHeaders.host;
}
userId = Accounts.insertUserDoc({}, userData);
if (loginToken) {
updateUser.$set.services = {
resume: {
loginTokens: [ loginToken ]
}
};
}
}
if (phone) {
updateUser.phone = [
updateUser.$set.phone = [
{ phoneNumber: phone.number }
];
}
if (email && email.trim() !== '') {
updateUser.emails = [{ address: email }];
}
if (loginToken) {
updateUser.services = {
resume: {
loginTokens: [ loginToken ]
}
};
updateUser.$set.emails = [
{ address: email }
];
}
Meteor.users.update(userId, {
$set: updateUser
});
Meteor.users.update(userId, updateUser);
return userId;
},
@ -148,5 +172,21 @@ RocketChat.Livechat = {
updateData.phone = phone;
}
return RocketChat.models.Users.saveUserById(_id, updateData);
},
closeRoom({ user, room, comment }) {
RocketChat.models.Rooms.closeByRoomId(room._id);
const message = {
t: 'livechat-close',
msg: comment,
groupable: false
};
RocketChat.sendMessage(user, message, room);
RocketChat.models.Subscriptions.hideByRoomIdAndUserId(room._id, user._id);
return true;
}
};

@ -0,0 +1,13 @@
Meteor.methods({
'livechat:closeRoom'(roomId, comment) {
const room = RocketChat.models.Rooms.findOneById(roomId);
// @TODO add validations
return RocketChat.Livechat.closeRoom({
user: Meteor.user(),
room: room,
comment: comment
});
}
});

@ -10,7 +10,7 @@ Meteor.methods({
departments: []
};
const room = RocketChat.models.Rooms.findByVisitorToken(visitorToken, {
const room = RocketChat.models.Rooms.findOpenByVisitorToken(visitorToken, {
fields: {
name: 1,
t: 1,

@ -75,3 +75,16 @@ RocketChat.models.Rooms.getNextLivechatRoomCode = function() {
return livechatCount.value;
};
RocketChat.models.Rooms.findOpenByVisitorToken = function(visitorToken, options) {
const query = {
open: true,
'v.token': visitorToken
};
return this.find(query, options);
};
RocketChat.models.Rooms.closeByRoomId = function(roomId) {
return this.update({ _id: roomId }, { $unset: { open: 1 } });
};

@ -1,3 +1,4 @@
Meteor.startup(function() {
RocketChat.models.Rooms.tryEnsureIndex({ code: 1 });
RocketChat.models.Rooms.tryEnsureIndex({ open: 1 }, { sparse: 1 });
});

@ -11,11 +11,18 @@ Meteor.startup(() => {
topic: 1,
tags: 1,
sms: 1,
code: 1
code: 1,
open: 1
});
});
RocketChat.authz.addRoomAccessValidator(function(room, user) {
return room.t === 'l' && RocketChat.authz.hasPermission(user._id, 'view-livechat-rooms');
});
RocketChat.callbacks.add('beforeLeaveRoom', function(user) {
throw new Meteor.Error(TAPi18n.__('You_cant_leave_a_livechat_room_Please_use_the_close_button', {
lng: user.language || RocketChat.settings.get('language') || 'en'
}));
}, RocketChat.callbacks.priority.LOW);
});

@ -0,0 +1,7 @@
RocketChat.Migrations.add({
version: 50,
up: function() {
RocketChat.models.Subscriptions.tryDropIndex('u._id_1_name_1_t_1');
RocketChat.models.Subscriptions.tryEnsureIndex({ 'u._id': 1, 'name': 1, 't': 1 });
}
});
Loading…
Cancel
Save