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' }); + } + } +});