diff --git a/.meteor/versions b/.meteor/versions
index 8476a4b0200..52749ad62a9 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -178,6 +178,7 @@ rocketchat:slashcommands-join@0.0.1
rocketchat:slashcommands-kick@0.0.1
rocketchat:slashcommands-leave@0.0.1
rocketchat:slashcommands-mute@0.0.1
+rocketchat:sms@0.0.1
rocketchat:spotify@0.0.1
rocketchat:statistics@0.0.1
rocketchat:streamer@0.3.2
diff --git a/packages/rocketchat-lib/i18n/en.i18n.json b/packages/rocketchat-lib/i18n/en.i18n.json
index 158a1251fda..b8680af6882 100644
--- a/packages/rocketchat-lib/i18n/en.i18n.json
+++ b/packages/rocketchat-lib/i18n/en.i18n.json
@@ -9,6 +9,7 @@
"Access_online_demo" : "Access the online demo",
"Access_Online_Demo" : "Access the Online Demo",
"Access_Token_URL" : "Access Token URL",
+ "Account_SID" : "Account SID",
"Accounts" : "Accounts",
"Accounts_AllowDeleteOwnAccount" : "Allow users to delete own account",
"Accounts_AllowedDomainsList" : "Allowed Domains List",
@@ -143,6 +144,7 @@
"are_typing" : "are typing",
"Are_you_sure" : "Are you sure?",
"Are_you_sure_you_want_to_delete_your_account" : "Are you sure you want to delete your account?",
+ "Auth_Token" : "Auth Token",
"Authorization_URL" : "Authorization URL",
"Authorize" : "Authorize",
"Auto_Load_Images" : "Auto Load Images",
@@ -339,6 +341,7 @@
"From_Email" : "From Email",
"From_email_is_required" : "From email is required",
"From_email_warning" : "Warning: The field From is subject to your mail server settings.",
+ "From_Number" : "From Number",
"General" : "General",
"Get_to_know_the_team" : "Get to know the Rocket.Team",
"github_no_public_email" : "You don't have any email as public email in your GitHub account",
@@ -823,6 +826,7 @@
"Send_Message" : "Send Message",
"Send_your_JSON_payloads_to_this_URL" : "Send your JSON payloads to this URL.",
"Sending" : "Sending...",
+ "Service" : "Service",
"Set_as_moderator" : "Set as moderator",
"Set_as_owner" : "Set as owner",
"Settings" : "Settings",
@@ -843,6 +847,7 @@
"Site_Url_Description" : "Example: https://chat.domain.com/",
"Skip" : "Skip",
"Smileys_and_People" : "Smileys & People",
+ "SMS_Enabled" : "SMS Enabled",
"SMTP" : "SMTP",
"SMTP_Host" : "SMTP Host",
"SMTP_Password" : "SMTP Password",
diff --git a/packages/rocketchat-livechat/app/client/lib/_visitor.coffee b/packages/rocketchat-livechat/app/client/lib/_visitor.coffee
index 8164dafc1a1..9bb1e6ca7d1 100644
--- a/packages/rocketchat-livechat/app/client/lib/_visitor.coffee
+++ b/packages/rocketchat-livechat/app/client/lib/_visitor.coffee
@@ -1,6 +1,7 @@
@visitor = new class
token = new ReactiveVar null
room = new ReactiveVar null
+ roomToSubscribe = new ReactiveVar null
register = ->
if not localStorage.getItem 'visitorToken'
@@ -22,7 +23,16 @@
return roomId
+ getRoomToSubscribe = ->
+ return roomToSubscribe.get()
+
+ setRoomToSubscribe = (rid) ->
+ room.set(rid)
+ return roomToSubscribe.set(rid)
+
register: register
getToken: getToken
setRoom: setRoom
getRoom: getRoom
+ setRoomToSubscribe: setRoomToSubscribe
+ getRoomToSubscribe: getRoomToSubscribe
diff --git a/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee b/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee
index 522360a54df..eee24e108f6 100644
--- a/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee
+++ b/packages/rocketchat-livechat/app/client/lib/chatMessages.coffee
@@ -74,7 +74,7 @@ class @ChatMessages
input.value = ''
rid ?= visitor.getRoom(true)
- sendMessage = ->
+ sendMessage = (callback) ->
msgObject = { _id: Random.id(), rid: rid, msg: msg, token: visitor.getToken() }
MsgTyping.stop(rid)
#Check if message starts with /command
@@ -91,6 +91,8 @@ class @ChatMessages
if error
ChatMessage.update msgObject._id, { $set: { error: true } }
showError error.reason
+ else
+ callback?(result)
if not Meteor.userId()
Meteor.call 'livechat:registerGuest', { token: visitor.getToken() }, (error, result) ->
@@ -101,7 +103,10 @@ class @ChatMessages
if error
return showError error.reason
- sendMessage()
+ sendMessage (message) ->
+ ChatMessage.update message._id, _.omit(message, '_id')
+ if message.rid?
+ visitor.setRoomToSubscribe(message.rid)
else
sendMessage()
diff --git a/packages/rocketchat-livechat/app/client/lib/fromApp/Notifications.coffee b/packages/rocketchat-livechat/app/client/lib/fromApp/Notifications.coffee
new file mode 100644
index 00000000000..56a4585af5d
--- /dev/null
+++ b/packages/rocketchat-livechat/app/client/lib/fromApp/Notifications.coffee
@@ -0,0 +1,56 @@
+@Notifications = new class
+ constructor: ->
+ @debug = false
+ @streamAll = new Meteor.Streamer 'notify-all'
+ @streamRoom = new Meteor.Streamer 'notify-room'
+ @streamUser = new Meteor.Streamer 'notify-user'
+
+ if @debug is true
+ @onAll -> console.log "RocketChat.Notifications: onAll", arguments
+ @onUser -> console.log "RocketChat.Notifications: onAll", arguments
+
+
+ notifyRoom: (room, eventName, args...) ->
+ console.log "RocketChat.Notifications: notifyRoom", arguments if @debug is true
+
+ args.unshift "#{room}/#{eventName}"
+ @streamRoom.emit.apply @streamRoom, args
+
+ notifyUser: (userId, eventName, args...) ->
+ console.log "RocketChat.Notifications: notifyUser", arguments if @debug is true
+
+ args.unshift "#{userId}/#{eventName}"
+ @streamUser.emit.apply @streamUser, args
+
+ notifyUsersOfRoom: (room, eventName, args...) ->
+ console.log "RocketChat.Notifications: notifyUsersOfRoom", arguments if @debug is true
+
+ onlineUsers = RoomManager.onlineUsers.get()
+ room = ChatRoom.findOne(room)
+ for username in room?.usernames or []
+ if onlineUsers[username]?
+ argsToSend = ["#{onlineUsers[username]._id}/#{eventName}"].concat args
+ @streamUser.emit.apply @streamUser, argsToSend
+
+
+ onAll: (eventName, callback) ->
+ @streamAll.on eventName, callback
+
+ onRoom: (room, eventName, callback) ->
+ if @debug is true
+ @streamRoom.on room, -> console.log "RocketChat.Notifications: onRoom #{room}", arguments
+
+ @streamRoom.on "#{room}/#{eventName}", callback
+
+ onUser: (eventName, callback) ->
+ @streamUser.on "#{Meteor.userId()}/#{eventName}", callback
+
+
+ unAll: (callback) ->
+ @streamAll.removeListener 'notify', callback
+
+ unRoom: (room, eventName, callback) ->
+ @streamRoom.removeListener "#{room}/#{eventName}", callback
+
+ unUser: (callback) ->
+ @streamUser.removeListener Meteor.userId(), callback
diff --git a/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee b/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee
index 722e3e9b5a1..45c68aeb73e 100644
--- a/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee
+++ b/packages/rocketchat-livechat/app/client/lib/msgTyping.coffee
@@ -1,5 +1,4 @@
@MsgTyping = do ->
- stream = new Meteor.Streamer 'typing'
timeout = 15000
timeouts = {}
renew = true
@@ -11,20 +10,20 @@
addStream = (room) ->
if _.isEmpty usersTyping[room]?.users
usersTyping[room] = { users: {} }
- stream.on room, (typing) ->
- unless typing?.username is Meteor.user()?.username
- if typing.start
+ Notifications.onRoom room, 'typing', (username, typing) ->
+ unless username is Meteor.user()?.username
+ if typing is true
users = usersTyping[room].users
- users[typing.username] = Meteor.setTimeout ->
- delete users[typing.username]
+ users[username] = Meteor.setTimeout ->
+ delete users[username]
usersTyping[room].users = users
dep.changed()
, timeout
usersTyping[room].users = users
dep.changed()
- else if typing.stop
+ else
users = usersTyping[room].users
- delete users[typing.username]
+ delete users[username]
usersTyping[room].users = users
dep.changed()
@@ -41,7 +40,7 @@
renew = false
selfTyping.set true
- stream.emit 'typing', { room: room, username: Meteor.user()?.username, start: true }
+ Notifications.notifyRoom room, 'typing', Meteor.user()?.username, true
clearTimeout timeouts[room]
timeouts[room] = Meteor.setTimeout ->
stop(room)
@@ -53,7 +52,7 @@
if timeouts?[room]?
clearTimeout(timeouts[room])
timeouts[room] = null
- stream.emit 'typing', { room: room, username: Meteor.user()?.username, stop: true }
+ Notifications.notifyRoom room, 'typing', Meteor.user()?.username, false
get = (room) ->
dep.depend()
diff --git a/packages/rocketchat-livechat/app/client/startup/room.coffee b/packages/rocketchat-livechat/app/client/startup/room.coffee
index 2dcab3056ea..cc1e9bfddb3 100644
--- a/packages/rocketchat-livechat/app/client/startup/room.coffee
+++ b/packages/rocketchat-livechat/app/client/startup/room.coffee
@@ -1,7 +1,7 @@
msgStream = new Meteor.Streamer 'room-messages'
Tracker.autorun ->
- if visitor.getRoom()?
- msgStream.on visitor.getRoom(), (msg) ->
+ if visitor.getRoomToSubscribe()?
+ msgStream.on visitor.getRoomToSubscribe(), (msg) ->
if msg.t is 'command'
if msg.msg is 'survey'
unless $('body #survey').length
diff --git a/packages/rocketchat-livechat/app/client/views/messages.js b/packages/rocketchat-livechat/app/client/views/messages.js
index e87742dca71..3f367ddbd5a 100644
--- a/packages/rocketchat-livechat/app/client/views/messages.js
+++ b/packages/rocketchat-livechat/app/client/views/messages.js
@@ -46,7 +46,7 @@ Template.messages.onCreated(function() {
var room;
room = ChatRoom.findOne();
if (room != null) {
- visitor.setRoom(room._id);
+ visitor.setRoomToSubscribe(room._id);
RoomHistoryManager.getMoreIfIsEmpty(room._id);
}
});
diff --git a/packages/rocketchat-livechat/config.js b/packages/rocketchat-livechat/config.js
index 424768e39a9..f242fb942fe 100644
--- a/packages/rocketchat-livechat/config.js
+++ b/packages/rocketchat-livechat/config.js
@@ -2,6 +2,7 @@ Meteor.startup(function() {
RocketChat.settings.addGroup('Livechat');
RocketChat.settings.add('Livechat_title' , 'Rocket.Chat', { type: 'string', group: 'Livechat', public: true });
RocketChat.settings.add('Livechat_title_color' , '#C1272D', { type: 'string', group: 'Livechat', public: true });
- RocketChat.settings.add('Livechat_enabled' , false, { type: 'boolean', group: 'Livechat', public: true });
+ RocketChat.settings.add('Livechat_enabled' , false, { type: 'boolean', group: 'Livechat', public: true });
RocketChat.settings.add('Livechat_registration_form' , true, { type: 'boolean', group: 'Livechat', public: true, i18nLabel: 'Show_preregistration_form' });
+ RocketChat.settings.add('Livechat_guest_count' , 1, { type: 'int', group: 'Livechat' });
});
diff --git a/packages/rocketchat-livechat/package.js b/packages/rocketchat-livechat/package.js
index a29cc6915fe..fc0eaf66e54 100644
--- a/packages/rocketchat-livechat/package.js
+++ b/packages/rocketchat-livechat/package.js
@@ -5,7 +5,7 @@ Package.describe({
});
Package.registerBuildPlugin({
- name: 'builLivechat',
+ name: 'Livechat',
use: [],
sources: [
'plugin/build-livechat.js'
@@ -26,6 +26,7 @@ Package.onUse(function(api) {
api.use('kadira:flow-router', 'client');
api.use('templating', 'client');
api.use('mongo');
+ api.use('rocketchat:sms');
api.use('less@2.5.1');
api.addFiles('livechat.js', 'server');
@@ -113,7 +114,8 @@ Package.onUse(function(api) {
api.addFiles('server/models/LivechatTrigger.js', 'server');
// server lib
- api.addFiles('server/lib/getNextAgent.js', 'server');
+ api.addFiles('server/lib/Livechat.js', 'server');
+ api.addFiles('server/sendMessageBySMS.js', 'server');
// publications
api.addFiles('server/publications/availableDepartments.js', 'server');
@@ -127,6 +129,9 @@ Package.onUse(function(api) {
api.addFiles('server/publications/visitorPageVisited.js', 'server');
api.addFiles('server/publications/visitorRoom.js', 'server');
+ // api
+ api.addFiles('server/api.js', 'server');
+
// livechat app
api.addAssets('assets/demo.html', 'client');
api.addAssets('assets/rocket-livechat.js', 'client');
diff --git a/packages/rocketchat-livechat/server/api.js b/packages/rocketchat-livechat/server/api.js
new file mode 100644
index 00000000000..a4578bb0351
--- /dev/null
+++ b/packages/rocketchat-livechat/server/api.js
@@ -0,0 +1,54 @@
+/* globals Restivus */
+const Api = new Restivus({
+ apiPath: 'livechat-api/',
+ useDefaultAuth: true,
+ prettyJson: true
+});
+
+Api.addRoute('sms-incoming/:service', {
+ post() {
+ const SMSService = RocketChat.SMS.getService(this.urlParams.service);
+
+ const sms = SMSService.parse(this.bodyParams);
+
+ var visitor = RocketChat.models.Users.findOneVisitorByPhone(sms.from);
+
+ let sendMessage = {
+ message: {
+ _id: Random.id()
+ }
+ };
+
+ if (visitor) {
+ const rooms = RocketChat.models.Rooms.findByVisitorToken(visitor.profile.token).fetch();
+
+ if (rooms && rooms.length > 0) {
+ sendMessage.message.rid = rooms[0]._id;
+ } else {
+ sendMessage.message.rid = Random.id();
+ }
+ sendMessage.message.token = visitor.profile.token;
+ } else {
+ sendMessage.message.rid = Random.id();
+ sendMessage.message.token = Random.id();
+
+ let userId = RocketChat.Livechat.registerGuest({
+ token: sendMessage.message.token,
+ phone: {
+ number: sms.from
+ }
+ });
+
+ visitor = RocketChat.models.Users.findOneById(userId);
+
+ sendMessage.roomInfo = {
+ sms: true
+ };
+ }
+ sendMessage.message.msg = sms.body;
+
+ sendMessage.guest = visitor;
+
+ return RocketChat.Livechat.sendMessage(sendMessage);
+ }
+});
diff --git a/packages/rocketchat-livechat/server/lib/Livechat.js b/packages/rocketchat-livechat/server/lib/Livechat.js
new file mode 100644
index 00000000000..2ccbe6fa82f
--- /dev/null
+++ b/packages/rocketchat-livechat/server/lib/Livechat.js
@@ -0,0 +1,121 @@
+RocketChat.Livechat = {
+ getNextAgent(department) {
+ if (department) {
+ return RocketChat.models.LivechatDepartmentAgents.getNextAgentForDepartment(department);
+ } else {
+ return RocketChat.models.Users.getNextAgent();
+ }
+ },
+ sendMessage({ guest, message, roomInfo }) {
+ var agent, room;
+
+ room = RocketChat.models.Rooms.findOneById(message.rid);
+ if (room == null) {
+
+ // if no department selected verify if there is only one active and use it
+ if (!guest.department) {
+ var departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents();
+ if (departments.count() === 1) {
+ guest.department = departments.fetch()[0]._id;
+ }
+ }
+
+ agent = RocketChat.Livechat.getNextAgent(guest.department);
+ if (!agent) {
+ throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
+ }
+ let roomData = _.extend({
+ _id: message.rid,
+ name: guest.username,
+ msgs: 1,
+ lm: new Date(),
+ usernames: [agent.username, guest.username],
+ t: 'l',
+ ts: new Date(),
+ v: {
+ token: message.token
+ }
+ }, roomInfo);
+ let subscriptionData = {
+ rid: message.rid,
+ name: guest.username,
+ alert: true,
+ open: true,
+ unread: 1,
+ answered: false,
+ u: {
+ _id: agent.agentId,
+ username: agent.username
+ },
+ t: 'l',
+ desktopNotifications: 'all',
+ mobilePushNotifications: 'all',
+ emailNotifications: 'all'
+ };
+
+ RocketChat.models.Rooms.insert(roomData);
+ RocketChat.models.Subscriptions.insert(subscriptionData);
+ }
+ room = Meteor.call('canAccessRoom', message.rid, guest._id);
+ if (!room) {
+ throw new Meteor.Error('cannot-acess-room');
+ }
+ return RocketChat.sendMessage(guest, message, room);
+ },
+ registerGuest({ token, name, email, department, phone, loginToken } = {}) {
+ check(token, String);
+
+ const user = RocketChat.models.Users.getVisitorByToken(token, { fields: { _id: 1 } });
+
+ if (user) {
+ throw new Meteor.Error('token-already-exists', 'Token already exists');
+ }
+
+ const username = RocketChat.models.Users.getNextVisitorUsername();
+
+ 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;
+ }
+
+ const userId = Accounts.insertUserDoc({}, userData);
+
+ let updateUser = {
+ name: name || username,
+ profile: {
+ guest: true,
+ token: token
+ }
+ };
+
+ if (phone) {
+ updateUser.profile.phones = [ phone ];
+ }
+
+ if (email && email.trim() !== '') {
+ updateUser.emails = [{ address: email }];
+ }
+
+ if (loginToken) {
+ updateUser.services = {
+ resume: {
+ loginTokens: [ loginToken ]
+ }
+ };
+ }
+
+ Meteor.users.update(userId, {
+ $set: updateUser
+ });
+
+ return userId;
+ }
+};
diff --git a/packages/rocketchat-livechat/server/lib/getNextAgent.js b/packages/rocketchat-livechat/server/lib/getNextAgent.js
deleted file mode 100644
index b7b966a2032..00000000000
--- a/packages/rocketchat-livechat/server/lib/getNextAgent.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* exported getNextAgent */
-
-this.getNextAgent = function(department) {
- if (department) {
- return RocketChat.models.LivechatDepartmentAgents.getNextAgentForDepartment(department);
- } else {
- return RocketChat.models.Users.getNextAgent();
- }
-};
diff --git a/packages/rocketchat-livechat/server/methods/registerGuest.js b/packages/rocketchat-livechat/server/methods/registerGuest.js
index 7849c66bfb8..ee2708e6905 100644
--- a/packages/rocketchat-livechat/server/methods/registerGuest.js
+++ b/packages/rocketchat-livechat/server/methods/registerGuest.js
@@ -1,74 +1,14 @@
Meteor.methods({
'livechat:registerGuest': function({ token, name, email, department } = {}) {
- var qt, user, userData, userExists, userId, inc = 0;
-
- check(token, String);
-
- user = Meteor.users.findOne({
- 'profile.token': token
- }, {
- fields: {
- _id: 1
- }
- });
-
- if (user != null) {
- throw new Meteor.Error('token-already-exists', 'Token already exists');
- }
-
- while (true) {
- qt = Meteor.users.find({
- 'profile.guest': true
- }).count() + 1;
-
- user = 'guest-' + (qt + inc++);
-
- userExists = Meteor.users.findOne({
- 'username': user
- }, {
- fields: {
- _id: 1
- }
- });
-
- if (!userExists) {
- break;
- }
- }
- userData = {
- username: user,
- globalRoles: ['livechat-guest'],
- department: department,
- type: 'visitor'
- };
-
- 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);
-
- const updateUser = {
- name: name || user,
- 'profile.guest': true,
- 'profile.token': token
- };
-
- if (email && email.trim() !== '') {
- updateUser.emails = [{ address: email }];
- }
-
var stampedToken = Accounts._generateStampedLoginToken();
var hashStampedToken = Accounts._hashStampedToken(stampedToken);
- updateUser.services = {
- resume: {
- loginTokens: [ hashStampedToken ]
- }
- };
-
- Meteor.users.update(userId, {
- $set: updateUser
+ let userId = RocketChat.Livechat.registerGuest({
+ token: token,
+ name: name,
+ email: email,
+ department: department,
+ loginToken: hashStampedToken
});
// update visited page history to not expire
diff --git a/packages/rocketchat-livechat/server/methods/sendMessageLivechat.js b/packages/rocketchat-livechat/server/methods/sendMessageLivechat.js
index cdcd7435e5e..35debd0aaee 100644
--- a/packages/rocketchat-livechat/server/methods/sendMessageLivechat.js
+++ b/packages/rocketchat-livechat/server/methods/sendMessageLivechat.js
@@ -1,6 +1,6 @@
Meteor.methods({
sendMessageLivechat: function(message) {
- var guest, agent, room;
+ var guest;
check(message.rid, String);
check(message.token, String);
@@ -12,54 +12,6 @@ Meteor.methods({
}
});
- room = RocketChat.models.Rooms.findOneById(message.rid);
- if (room == null) {
-
- // if no department selected verify if there is only one active and use it
- if (!guest.department) {
- var departments = RocketChat.models.LivechatDepartment.findEnabledWithAgents();
- if (departments.count() === 1) {
- guest.department = departments.fetch()[0]._id;
- }
- }
-
- agent = getNextAgent(guest.department);
- if (!agent) {
- throw new Meteor.Error('no-agent-online', 'Sorry, no online agents');
- }
- RocketChat.models.Rooms.insert({
- _id: message.rid,
- name: guest.username,
- msgs: 1,
- lm: new Date(),
- usernames: [agent.username, guest.username],
- t: 'l',
- ts: new Date(),
- v: {
- token: message.token
- }
- });
- RocketChat.models.Subscriptions.insert({
- rid: message.rid,
- name: guest.username,
- alert: true,
- open: true,
- unread: 1,
- answered: false,
- u: {
- _id: agent.agentId,
- username: agent.username
- },
- t: 'l',
- desktopNotifications: 'all',
- mobilePushNotifications: 'all',
- emailNotifications: 'all'
- });
- }
- room = Meteor.call('canAccessRoom', message.rid, guest._id);
- if (!room) {
- throw new Meteor.Error('cannot-acess-room');
- }
- return RocketChat.sendMessage(guest, message, room);
+ return RocketChat.Livechat.sendMessage({ guest: guest, message: message });
}
});
diff --git a/packages/rocketchat-livechat/server/models/Users.js b/packages/rocketchat-livechat/server/models/Users.js
index 9090d7f59f6..b0d78558f52 100644
--- a/packages/rocketchat-livechat/server/models/Users.js
+++ b/packages/rocketchat-livechat/server/models/Users.js
@@ -137,3 +137,37 @@ RocketChat.models.Users.updateLivechatDataByToken = function(token, key, value)
return this.upsert(query, update);
};
+/**
+ * Find a visitor by their phone number
+ * @return {object} User from db
+ */
+RocketChat.models.Users.findOneVisitorByPhone = function(phone) {
+ const query = {
+ 'profile.phones.number': phone
+ };
+
+ return this.findOne(query);
+};
+
+/**
+ * Get the next visitor name
+ * @return {string} The next visitor name
+ */
+RocketChat.models.Users.getNextVisitorUsername = function() {
+ const settingsRaw = RocketChat.models.Settings.model.rawCollection();
+ const findAndModify = Meteor.wrapAsync(settingsRaw.findAndModify, settingsRaw);
+
+ const query = {
+ _id: 'Livechat_guest_count'
+ };
+
+ const update = {
+ $inc: {
+ value: 1
+ }
+ };
+
+ const livechatCount = findAndModify(query, null, update);
+
+ return 'guest-' + (livechatCount.value + 1);
+};
diff --git a/packages/rocketchat-livechat/server/sendMessageBySMS.js b/packages/rocketchat-livechat/server/sendMessageBySMS.js
new file mode 100644
index 00000000000..4eddb6a3821
--- /dev/null
+++ b/packages/rocketchat-livechat/server/sendMessageBySMS.js
@@ -0,0 +1,37 @@
+RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
+ // skips this callback if the message was edited
+ if (message.editedAt) {
+ return message;
+ }
+
+ if (!RocketChat.SMS.enabled) {
+ return message;
+ }
+
+ // only send the sms by SMS if it is a livechat room with SMS set to true
+ if (typeof room.t === 'undefined' || room.t !== 'l' || !room.sms || !room.v || !room.v.token) {
+ return message;
+ }
+
+ // if the message has a token, it was sent from the visitor, so ignore it
+ if (message.token) {
+ return message;
+ }
+
+ const SMSService = RocketChat.SMS.getService(RocketChat.settings.get('SMS_Service'));
+
+ if (!SMSService) {
+ return message;
+ }
+
+ const visitor = RocketChat.models.Users.getVisitorByToken(room.v.token);
+
+ if (!visitor || !visitor.profile || !visitor.profile.phones || visitor.profile.phones.length === 0) {
+ return message;
+ }
+
+ SMSService.send(visitor.profile.phones[0].number, message.msg);
+
+ return message;
+
+}, RocketChat.callbacks.priority.LOW);
diff --git a/packages/rocketchat-sms/.npm/package/.gitignore b/packages/rocketchat-sms/.npm/package/.gitignore
new file mode 100644
index 00000000000..3c3629e647f
--- /dev/null
+++ b/packages/rocketchat-sms/.npm/package/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/packages/rocketchat-sms/.npm/package/README b/packages/rocketchat-sms/.npm/package/README
new file mode 100644
index 00000000000..3d492553a43
--- /dev/null
+++ b/packages/rocketchat-sms/.npm/package/README
@@ -0,0 +1,7 @@
+This directory and the files immediately inside it are automatically generated
+when you change this package's NPM dependencies. Commit the files in this
+directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
+so that others run the same versions of sub-dependencies.
+
+You should NOT check in the node_modules directory that Meteor automatically
+creates; if you are using git, the .gitignore file tells git to ignore it.
diff --git a/packages/rocketchat-sms/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-sms/.npm/package/npm-shrinkwrap.json
new file mode 100644
index 00000000000..4fd68394fd6
--- /dev/null
+++ b/packages/rocketchat-sms/.npm/package/npm-shrinkwrap.json
@@ -0,0 +1,312 @@
+{
+ "dependencies": {
+ "twilio": {
+ "version": "2.9.1",
+ "dependencies": {
+ "request": {
+ "version": "2.55.0",
+ "dependencies": {
+ "bl": {
+ "version": "0.9.5",
+ "dependencies": {
+ "readable-stream": {
+ "version": "1.0.34",
+ "dependencies": {
+ "core-util-is": {
+ "version": "1.0.2"
+ },
+ "isarray": {
+ "version": "0.0.1"
+ },
+ "string_decoder": {
+ "version": "0.10.31"
+ },
+ "inherits": {
+ "version": "2.0.1"
+ }
+ }
+ }
+ }
+ },
+ "caseless": {
+ "version": "0.9.0"
+ },
+ "forever-agent": {
+ "version": "0.6.1"
+ },
+ "form-data": {
+ "version": "0.2.0",
+ "dependencies": {
+ "async": {
+ "version": "0.9.2"
+ }
+ }
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1"
+ },
+ "mime-types": {
+ "version": "2.0.14",
+ "dependencies": {
+ "mime-db": {
+ "version": "1.12.0"
+ }
+ }
+ },
+ "node-uuid": {
+ "version": "1.4.7"
+ },
+ "qs": {
+ "version": "2.4.2"
+ },
+ "tunnel-agent": {
+ "version": "0.4.2"
+ },
+ "tough-cookie": {
+ "version": "2.2.2"
+ },
+ "http-signature": {
+ "version": "0.10.1",
+ "dependencies": {
+ "assert-plus": {
+ "version": "0.1.5"
+ },
+ "asn1": {
+ "version": "0.1.11"
+ },
+ "ctype": {
+ "version": "0.5.3"
+ }
+ }
+ },
+ "oauth-sign": {
+ "version": "0.6.0"
+ },
+ "hawk": {
+ "version": "2.3.1",
+ "dependencies": {
+ "hoek": {
+ "version": "2.16.3"
+ },
+ "boom": {
+ "version": "2.10.1"
+ },
+ "cryptiles": {
+ "version": "2.0.5"
+ },
+ "sntp": {
+ "version": "1.0.9"
+ }
+ }
+ },
+ "aws-sign2": {
+ "version": "0.5.0"
+ },
+ "stringstream": {
+ "version": "0.0.5"
+ },
+ "combined-stream": {
+ "version": "0.0.7",
+ "dependencies": {
+ "delayed-stream": {
+ "version": "0.0.5"
+ }
+ }
+ },
+ "isstream": {
+ "version": "0.1.2"
+ },
+ "har-validator": {
+ "version": "1.8.0",
+ "dependencies": {
+ "bluebird": {
+ "version": "2.10.2"
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1"
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5"
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.0.0"
+ }
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.0.0"
+ }
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0"
+ }
+ }
+ },
+ "commander": {
+ "version": "2.9.0",
+ "dependencies": {
+ "graceful-readlink": {
+ "version": "1.0.1"
+ }
+ }
+ },
+ "is-my-json-valid": {
+ "version": "2.13.1",
+ "dependencies": {
+ "generate-function": {
+ "version": "2.0.0"
+ },
+ "generate-object-property": {
+ "version": "1.2.0",
+ "dependencies": {
+ "is-property": {
+ "version": "1.0.2"
+ }
+ }
+ },
+ "jsonpointer": {
+ "version": "2.0.0"
+ },
+ "xtend": {
+ "version": "4.0.1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "underscore": {
+ "version": "1.8.3"
+ },
+ "jsonwebtoken": {
+ "version": "5.4.1",
+ "dependencies": {
+ "jws": {
+ "version": "3.1.3",
+ "dependencies": {
+ "base64url": {
+ "version": "1.0.6",
+ "dependencies": {
+ "concat-stream": {
+ "version": "1.4.10",
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1"
+ },
+ "typedarray": {
+ "version": "0.0.6"
+ },
+ "readable-stream": {
+ "version": "1.1.14",
+ "dependencies": {
+ "core-util-is": {
+ "version": "1.0.2"
+ },
+ "isarray": {
+ "version": "0.0.1"
+ },
+ "string_decoder": {
+ "version": "0.10.31"
+ }
+ }
+ }
+ }
+ },
+ "meow": {
+ "version": "2.0.0",
+ "dependencies": {
+ "camelcase-keys": {
+ "version": "1.0.0",
+ "dependencies": {
+ "camelcase": {
+ "version": "1.2.1"
+ },
+ "map-obj": {
+ "version": "1.0.1"
+ }
+ }
+ },
+ "indent-string": {
+ "version": "1.2.2",
+ "dependencies": {
+ "get-stdin": {
+ "version": "4.0.1"
+ },
+ "repeating": {
+ "version": "1.1.3",
+ "dependencies": {
+ "is-finite": {
+ "version": "1.0.1",
+ "dependencies": {
+ "number-is-nan": {
+ "version": "1.0.0"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "minimist": {
+ "version": "1.2.0"
+ },
+ "object-assign": {
+ "version": "1.0.0"
+ }
+ }
+ }
+ }
+ },
+ "jwa": {
+ "version": "1.1.3",
+ "dependencies": {
+ "buffer-equal-constant-time": {
+ "version": "1.0.1"
+ },
+ "ecdsa-sig-formatter": {
+ "version": "1.0.5",
+ "dependencies": {
+ "base64-url": {
+ "version": "1.2.2"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "0.7.1"
+ }
+ }
+ },
+ "jwt-simple": {
+ "version": "0.1.0"
+ },
+ "q": {
+ "version": "0.9.7"
+ },
+ "scmp": {
+ "version": "0.0.3"
+ },
+ "deprecate": {
+ "version": "0.1.0"
+ },
+ "string.prototype.startswith": {
+ "version": "0.2.0"
+ }
+ }
+ }
+ }
+}
diff --git a/packages/rocketchat-sms/README.md b/packages/rocketchat-sms/README.md
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/rocketchat-sms/SMS.js b/packages/rocketchat-sms/SMS.js
new file mode 100644
index 00000000000..46e8130a0ab
--- /dev/null
+++ b/packages/rocketchat-sms/SMS.js
@@ -0,0 +1,23 @@
+/* globals RocketChat */
+RocketChat.SMS = {
+ enabled: false,
+ services: {},
+ accountSid: null,
+ authToken: null,
+ fromNumber: null,
+
+ registerService(name, service) {
+ this.services[name] = service;
+ },
+
+ getService(name) {
+ if (!this.services[name]) {
+ throw new Meteor.Error('error-sms-service-not-configured');
+ }
+ return new this.services[name](this.accountSid, this.authToken, this.fromNumber);
+ }
+};
+
+RocketChat.settings.get('SMS_Enabled', function(key, value) {
+ RocketChat.SMS.enabled = value;
+});
diff --git a/packages/rocketchat-sms/package.js b/packages/rocketchat-sms/package.js
new file mode 100644
index 00000000000..00cbeb3be4d
--- /dev/null
+++ b/packages/rocketchat-sms/package.js
@@ -0,0 +1,21 @@
+Package.describe({
+ name: 'rocketchat:sms',
+ version: '0.0.1',
+ summary: '',
+ git: '',
+ documentation: 'README.md'
+});
+
+Package.onUse(function(api) {
+ api.versionsFrom('1.2.1');
+ api.use('ecmascript');
+ api.use('rocketchat:lib');
+
+ api.addFiles('settings.js', 'server');
+ api.addFiles('SMS.js', 'server');
+ api.addFiles('services/twilio.js', 'server');
+});
+
+Npm.depends({
+ 'twilio': '2.9.1'
+});
diff --git a/packages/rocketchat-sms/services/twilio.js b/packages/rocketchat-sms/services/twilio.js
new file mode 100644
index 00000000000..b94fc4c2205
--- /dev/null
+++ b/packages/rocketchat-sms/services/twilio.js
@@ -0,0 +1,37 @@
+/* globals RocketChat */
+class Twilio {
+ constructor() {
+ this.accountSid = RocketChat.settings.get('SMS_Twilio_Account_SID');
+ this.authToken = RocketChat.settings.get('SMS_Twilio_authToken');
+ this.fromNumber = RocketChat.settings.get('SMS_Twilio_fromNumber');
+ }
+ parse(data) {
+ return {
+ from: data.From,
+ to: data.To,
+ body: data.Body,
+
+ extra: {
+ toCountry: data.ToCountry,
+ toState: data.ToState,
+ toCity: data.ToCity,
+ toZip: data.ToZip,
+ fromCountry: data.FromCountry,
+ fromState: data.FromState,
+ fromCity: data.FromCity,
+ fromZip: data.FromZip
+ }
+ };
+ }
+ send(to, message) {
+ var client = Npm.require('twilio')(this.accountSid, this.authToken);
+
+ client.messages.create({
+ to: to,
+ from: this.fromNumber,
+ body: message,
+ });
+ }
+}
+
+RocketChat.SMS.registerService('twilio', Twilio);
diff --git a/packages/rocketchat-sms/settings.js b/packages/rocketchat-sms/settings.js
new file mode 100644
index 00000000000..915131d6553
--- /dev/null
+++ b/packages/rocketchat-sms/settings.js
@@ -0,0 +1,43 @@
+Meteor.startup(function() {
+ RocketChat.settings.addGroup('SMS', function() {
+ this.add('SMS_Enabled', false, {
+ type: 'boolean'
+ });
+
+ this.add('SMS_Service', 'twilio', {
+ type: 'select',
+ values: [{
+ key: 'twilio',
+ i18nLabel: 'Twilio'
+ }],
+ i18nLabel: 'Service'
+ });
+
+ this.section('Twilio', function() {
+ this.add('SMS_Twilio_fromNumber', '', {
+ type: 'string',
+ enableQuery: {
+ _id: 'SMS_Service',
+ value: 'twilio'
+ },
+ i18nLabel: 'From_Number'
+ });
+ this.add('SMS_Twilio_Account_SID', '', {
+ type: 'string',
+ enableQuery: {
+ _id: 'SMS_Service',
+ value: 'twilio'
+ },
+ i18nLabel: 'Account_SID'
+ });
+ this.add('SMS_Twilio_authToken', '', {
+ type: 'string',
+ enableQuery: {
+ _id: 'SMS_Service',
+ value: 'twilio'
+ },
+ i18nLabel: 'Auth_Token'
+ });
+ });
+ });
+});
diff --git a/server/methods/canAccessRoom.coffee b/server/methods/canAccessRoom.coffee
index f79e6b7ab48..79e509c3058 100644
--- a/server/methods/canAccessRoom.coffee
+++ b/server/methods/canAccessRoom.coffee
@@ -8,7 +8,7 @@ Meteor.methods
unless rid
throw new Meteor.Error 'invalid-room', '[methods] canAccessRoom -> Cannot access empty room'
- room = RocketChat.models.Rooms.findOneById rid, { fields: { usernames: 1, t: 1, name: 1, muted: 1 } }
+ room = RocketChat.models.Rooms.findOneById rid, { fields: { usernames: 1, t: 1, name: 1, muted: 1, sms: 1, v: 1 } }
if room
if room.usernames.indexOf(user.username) isnt -1
@@ -19,6 +19,6 @@ Meteor.methods
if canAccess isnt true
return false
else
- return _.pick room, ['_id', 't', 'name', 'usernames', 'muted']
+ return _.pick room, ['_id', 't', 'name', 'usernames', 'muted', 'sms', 'v']
else
throw new Meteor.Error 'invalid-room', '[methods] canAccessRoom -> Room ID is invalid'
diff --git a/server/startup/migrations/v045.js b/server/startup/migrations/v045.js
new file mode 100644
index 00000000000..c3df7f9bf0e
--- /dev/null
+++ b/server/startup/migrations/v045.js
@@ -0,0 +1,14 @@
+RocketChat.Migrations.add({
+ version: 45,
+ up: function() {
+
+ // finds the latest created visitor
+ var lastVisitor = RocketChat.models.Users.find({ type: 'visitor' }, { fields: { username: 1 }, sort: { createdAt: -1 }, limit: 1 }).fetch();
+
+ if (lastVisitor && lastVisitor.length > 0) {
+ var lastNumber = lastVisitor[0].username.replace(/^guest\-/, '');
+
+ RocketChat.settings.add('Livechat_guest_count' , (parseInt(lastNumber) + 1), { type: 'int', group: 'Livechat' });
+ }
+ }
+});