Merge branch 'develop' into feature/rest-api-message-read-receipts

pull/9907/head
Marcos Defendi 8 years ago
commit daf6b03433
  1. 2
      .docker/Dockerfile
  2. 3
      .eslintignore
  3. 11
      .openshift/rocket-chat-ephemeral.json
  4. 9
      .openshift/rocket-chat-persistent.json
  5. 2
      .sandstorm/sandstorm-pkgdef.capnp
  6. 2
      .travis/snap.sh
  7. 262
      HISTORY.md
  8. 6
      README.md
  9. 13
      client/routes/router.js
  10. 5
      imports/message-read-receipt/server/hooks.js
  11. 10
      package-lock.json
  12. 3
      package.json
  13. 25
      packages/rocketchat-2fa/server/lib/totp.js
  14. 1
      packages/rocketchat-api/package.js
  15. 116
      packages/rocketchat-api/server/v1/channels.js
  16. 7
      packages/rocketchat-api/server/v1/emoji-custom.js
  17. 23
      packages/rocketchat-api/server/v1/misc.js
  18. 30
      packages/rocketchat-api/server/v1/users.js
  19. 4
      packages/rocketchat-apps/client/admin/appInstall.js
  20. 51
      packages/rocketchat-apps/client/admin/appManage.js
  21. 35
      packages/rocketchat-apps/client/admin/apps.js
  22. 4
      packages/rocketchat-apps/client/communication/index.js
  23. 58
      packages/rocketchat-apps/client/communication/websockets.js
  24. 4
      packages/rocketchat-apps/package.js
  25. 30
      packages/rocketchat-apps/server/bridges/activation.js
  26. 12
      packages/rocketchat-apps/server/bridges/bridges.js
  27. 13
      packages/rocketchat-apps/server/bridges/details.js
  28. 2
      packages/rocketchat-apps/server/bridges/index.js
  29. 15
      packages/rocketchat-apps/server/bridges/listeners.js
  30. 26
      packages/rocketchat-apps/server/bridges/messages.js
  31. 28
      packages/rocketchat-apps/server/bridges/persistence.js
  32. 6
      packages/rocketchat-apps/server/communication/index.js
  33. 14
      packages/rocketchat-apps/server/communication/methods.js
  34. 2
      packages/rocketchat-apps/server/communication/rest.js
  35. 142
      packages/rocketchat-apps/server/communication/websockets.js
  36. 19
      packages/rocketchat-apps/server/orchestrator.js
  37. 3
      packages/rocketchat-apps/server/storage/logs-storage.js
  38. 2
      packages/rocketchat-authorization/server/startup.js
  39. 7
      packages/rocketchat-autolinker/client/client.js
  40. 7
      packages/rocketchat-cors/cors.js
  41. 3
      packages/rocketchat-cors/package.js
  42. 7
      packages/rocketchat-custom-oauth/server/custom_oauth_server.js
  43. 12
      packages/rocketchat-emoji-custom/client/models/EmojiCustom.js
  44. 2
      packages/rocketchat-file-upload/server/lib/requests.js
  45. 32
      packages/rocketchat-i18n/i18n/en.i18n.json
  46. 2
      packages/rocketchat-i18n/i18n/fr.i18n.json
  47. 2
      packages/rocketchat-i18n/i18n/pt-BR.i18n.json
  48. 2
      packages/rocketchat-importer/server/classes/ImporterBase.js
  49. 19
      packages/rocketchat-lib/client/MessageAction.js
  50. 2
      packages/rocketchat-lib/client/lib/RestApiClient.js
  51. 2
      packages/rocketchat-lib/client/lib/cachedCollection.js
  52. 2
      packages/rocketchat-lib/package.js
  53. 2
      packages/rocketchat-lib/rocketchat.info
  54. 55
      packages/rocketchat-lib/server/functions/deleteUser.js
  55. 71
      packages/rocketchat-lib/server/functions/saveUser.js
  56. 30
      packages/rocketchat-lib/server/functions/sendMessage.js
  57. 5
      packages/rocketchat-lib/server/functions/setEmail.js
  58. 2
      packages/rocketchat-lib/server/methods/leaveRoom.js
  59. 2
      packages/rocketchat-lib/server/methods/sendMessage.js
  60. 16
      packages/rocketchat-lib/server/models/Messages.js
  61. 10
      packages/rocketchat-lib/server/models/Rooms.js
  62. 43
      packages/rocketchat-lib/server/models/Subscriptions.js
  63. 10
      packages/rocketchat-lib/server/models/Users.js
  64. 63
      packages/rocketchat-lib/server/oauth/facebook.js
  65. 58
      packages/rocketchat-lib/server/oauth/twitter.js
  66. 83
      packages/rocketchat-lib/server/startup/settings.js
  67. 8
      packages/rocketchat-livechat/.app/client/views/loading.html
  68. 4
      packages/rocketchat-livechat/.app/client/views/messages.js
  69. 12
      packages/rocketchat-livechat/client/views/app/integrations/livechatIntegrationWebhook.html
  70. 16
      packages/rocketchat-livechat/client/views/app/integrations/livechatIntegrationWebhook.js
  71. 12
      packages/rocketchat-livechat/client/views/app/livechatIntegrations.html
  72. 14
      packages/rocketchat-livechat/config.js
  73. 2
      packages/rocketchat-livechat/roomType.js
  74. 40
      packages/rocketchat-livechat/server/hooks/sendToCRM.js
  75. 1
      packages/rocketchat-livechat/server/lib/Livechat.js
  76. 8
      packages/rocketchat-livechat/server/methods/saveIntegration.js
  77. 1
      packages/rocketchat-livechat/server/models/Users.js
  78. 2
      packages/rocketchat-livechat/server/publications/livechatIntegration.js
  79. 1
      packages/rocketchat-mentions/package.js
  80. 19
      packages/rocketchat-mentions/server/methods/getUserMentionsByChannel.js
  81. 10
      packages/rocketchat-message-attachments/client/messageAttachment.html
  82. 2
      packages/rocketchat-message-attachments/client/messageAttachment.js
  83. 68
      packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js
  84. 7
      packages/rocketchat-reactions/client/init.js
  85. 2
      packages/rocketchat-reactions/client/methods/setReaction.js
  86. 4
      packages/rocketchat-reactions/setReaction.js
  87. 3
      packages/rocketchat-theme/client/imports/components/file-list.css
  88. 2
      packages/rocketchat-theme/client/imports/components/main-content.css
  89. 47
      packages/rocketchat-theme/client/imports/components/message-box.css
  90. 14
      packages/rocketchat-theme/client/imports/components/popover.css
  91. 45
      packages/rocketchat-theme/client/imports/components/sidebar/sidebar-item.css
  92. 2
      packages/rocketchat-theme/client/imports/forms/switch.css
  93. 51
      packages/rocketchat-theme/client/imports/general/base.css
  94. 43
      packages/rocketchat-theme/client/imports/general/base_old.css
  95. 1
      packages/rocketchat-theme/client/main.css
  96. 4
      packages/rocketchat-theme/server/colors.less
  97. 15
      packages/rocketchat-ui-account/client/accountPreferences.html
  98. 6
      packages/rocketchat-ui-account/client/accountPreferences.js
  99. 2
      packages/rocketchat-ui-account/client/accountProfile.html
  100. 2
      packages/rocketchat-ui-flextab/client/tabs/uploadedFilesList.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,6 +1,6 @@
FROM rocketchat/base:8
ENV RC_VERSION 0.62.0-develop
ENV RC_VERSION 0.63.0-develop
MAINTAINER buildmaster@rocket.chat

@ -18,4 +18,5 @@ packages/rocketchat-videobridge/client/public/external_api.js
packages/rocketchat-theme/client/vendor/
private/moment-locales/
public/livechat/
public/recorderWorker.js
public/mp3-realtime-worker.js
public/lame.min.js

@ -151,7 +151,7 @@
"name": "rocketchat"
},
"spec": {
"dockerImageRepository": "registry.connect.redhat.com/rocketchat",
"dockerImageRepository": "registry.connect.redhat.com/rocketchat/rocketchat",
"tags": [
{
"name": "latest",
@ -208,7 +208,6 @@
],
"from": {
"kind": "ImageStreamTag",
"namespace": "${NAMESPACE}",
"name": "rocketchat:latest"
}
}
@ -395,12 +394,6 @@
"description": "Maximum amount of memory the container can use.",
"value": "512Mi"
},
{
"name": "NAMESPACE",
"displayName": "Namespace",
"description": "The OpenShift Namespace where the ImageStream resides.",
"value": "openshift"
},
{
"name": "DATABASE_SERVICE_NAME",
"displayName": "Database Service Name",
@ -428,7 +421,7 @@
"name": "MONGODB_DATABASE",
"displayName": "MongoDB Database Name",
"description": "Name of the MongoDB database accessed.",
"value": "sampledb",
"value": "rocketchatdb",
"required": true
},
{

@ -228,7 +228,6 @@
],
"from": {
"kind": "ImageStreamTag",
"namespace": "${NAMESPACE}",
"name": "rocketchat:latest"
}
}
@ -415,12 +414,6 @@
"description": "Maximum amount of memory the container can use.",
"value": "512Mi"
},
{
"name": "NAMESPACE",
"displayName": "Namespace",
"description": "The OpenShift Namespace where the ImageStream resides.",
"value": "openshift"
},
{
"name": "DATABASE_SERVICE_NAME",
"displayName": "Database Service Name",
@ -448,7 +441,7 @@
"name": "MONGODB_DATABASE",
"displayName": "MongoDB Database Name",
"description": "Name of the MongoDB database accessed.",
"value": "sampledb",
"value": "rocketchatdb",
"required": true
},
{

@ -21,7 +21,7 @@ const pkgdef :Spk.PackageDefinition = (
appVersion = 62, # Increment this for every release.
appMarketingVersion = (defaultText = "0.62.0-develop"),
appMarketingVersion = (defaultText = "0.63.0-develop"),
# Human-readable representation of appVersion. Should match the way you
# identify versions of your app in documentation and marketing.

@ -17,7 +17,7 @@ elif [[ $TRAVIS_TAG ]]; then
RC_VERSION=$TRAVIS_TAG
else
CHANNEL=edge
RC_VERSION=0.62.0-develop
RC_VERSION=0.63.0-develop
fi
echo "Preparing to trigger a snap release for $CHANNEL channel"

@ -1,3 +1,265 @@
<a name="0.62.0"></a>
# 0.62.0 (2018-02-28)
### BREAKING CHANGES
- [#9711](https://github.com/RocketChat/Rocket.Chat/pull/9711) Remove Graphics/Image Magick support
### New Features
- [#9549](https://github.com/RocketChat/Rocket.Chat/pull/9549) Add route to get user shield/badge
- [#9457](https://github.com/RocketChat/Rocket.Chat/pull/9457) Add user settings / preferences API endpoint
- [#7098](https://github.com/RocketChat/Rocket.Chat/pull/7098) Alert admins when user requires approval & alert users when the account is approved/activated/deactivated
- [#9527](https://github.com/RocketChat/Rocket.Chat/pull/9527) Allow configuration of SAML logout behavior
- [#8193](https://github.com/RocketChat/Rocket.Chat/pull/8193) Allow request avatar placeholders as PNG or JPG instead of SVG
- [#9312](https://github.com/RocketChat/Rocket.Chat/pull/9312) Allow sounds when conversation is focused
- [#9519](https://github.com/RocketChat/Rocket.Chat/pull/9519) API to fetch permissions & user roles
- [#9642](https://github.com/RocketChat/Rocket.Chat/pull/9642) Browse more channels / Directory
- [#9778](https://github.com/RocketChat/Rocket.Chat/pull/9778) General alert banner
- [#9687](https://github.com/RocketChat/Rocket.Chat/pull/9687) Global message search (beta: disabled by default)
- [#8158](https://github.com/RocketChat/Rocket.Chat/pull/8158) GraphQL API
- [#9298](https://github.com/RocketChat/Rocket.Chat/pull/9298) Improved default welcome message
- [#8933](https://github.com/RocketChat/Rocket.Chat/pull/8933) Internal hubot support for Direct Messages and Private Groups
- [#9255](https://github.com/RocketChat/Rocket.Chat/pull/9255) Livestream tab
- [#9746](https://github.com/RocketChat/Rocket.Chat/pull/9746) Makes shield icon configurable
- [#9717](https://github.com/RocketChat/Rocket.Chat/pull/9717) Message read receipts
- [#9507](https://github.com/RocketChat/Rocket.Chat/pull/9507) New REST API to mark channel as read
- [#9608](https://github.com/RocketChat/Rocket.Chat/pull/9608) New sidebar layout
- [#9699](https://github.com/RocketChat/Rocket.Chat/pull/9699) Option to proxy files and avatars through the server
- [#9509](https://github.com/RocketChat/Rocket.Chat/pull/9509) REST API to use Spotlight
- [#9793](https://github.com/RocketChat/Rocket.Chat/pull/9793) Version update check
- [#9934](https://github.com/RocketChat/Rocket.Chat/pull/9934) Typo on french translation for "Open"
### Bug Fixes
- [#9424](https://github.com/RocketChat/Rocket.Chat/pull/9424) 'Query' support for channels.list.joined, groups.list, groups.listAll, im.list
- [#9737](https://github.com/RocketChat/Rocket.Chat/pull/9737) API to retrive rooms was returning empty objects
- [#9487](https://github.com/RocketChat/Rocket.Chat/pull/9487) Chat Message Reactions REST API End Point
- [#9560](https://github.com/RocketChat/Rocket.Chat/pull/9560) Chrome 64 breaks jitsi-meet iframe
- [#9662](https://github.com/RocketChat/Rocket.Chat/pull/9662) Close button on file upload bar was not working
- [#9714](https://github.com/RocketChat/Rocket.Chat/pull/9714) Close Livechat conversation by visitor not working in version 0.61.0
- [#9676](https://github.com/RocketChat/Rocket.Chat/pull/9676) Custom emoji was cropping sometimes
- [#9747](https://github.com/RocketChat/Rocket.Chat/pull/9747) DeprecationWarning: prom-client ... when starting Rocket Chat server
- [#9639](https://github.com/RocketChat/Rocket.Chat/pull/9639) Desktop notification not showing when avatar came from external storage service
- [#9776](https://github.com/RocketChat/Rocket.Chat/pull/9776) Emoji rendering on last message
- [#9640](https://github.com/RocketChat/Rocket.Chat/pull/9640) Facebook integration in livechat not working on version 0.61.0
- [#9067](https://github.com/RocketChat/Rocket.Chat/pull/9067) Formal pronouns and some small mistakes in German texts
- [#9716](https://github.com/RocketChat/Rocket.Chat/pull/9716) GitLab OAuth does not work when GitLab’s URL ends with slash
- [#9697](https://github.com/RocketChat/Rocket.Chat/pull/9697) Harmonize channel-related actions
- [#9772](https://github.com/RocketChat/Rocket.Chat/pull/9772) Livechat conversation not receiving messages when start without form
- [#9599](https://github.com/RocketChat/Rocket.Chat/pull/9599) Livechat is not working when running in a sub path
- [#9750](https://github.com/RocketChat/Rocket.Chat/pull/9750) Livechat issues on external queue and lead capture
- [#9720](https://github.com/RocketChat/Rocket.Chat/pull/9720) Messages can't be quoted sometimes
- [#9454](https://github.com/RocketChat/Rocket.Chat/pull/9454) Missing link Site URLs in enrollment e-mails
- [#9610](https://github.com/RocketChat/Rocket.Chat/pull/9610) Missing string 'Username_already_exist' on the accountProfile page
- [#9520](https://github.com/RocketChat/Rocket.Chat/pull/9520) Rest API helpers only applying to v1
- [#9696](https://github.com/RocketChat/Rocket.Chat/pull/9696) Show custom room types icon in channel header
- [#9570](https://github.com/RocketChat/Rocket.Chat/pull/9570) SVG avatars are not been displayed correctly when load in non HTML containers
- [#9623](https://github.com/RocketChat/Rocket.Chat/pull/9623) Weird rendering of emojis at sidebar when `last message` is activated
- [#9665](https://github.com/RocketChat/Rocket.Chat/pull/9665) Wrong behavior of rooms info's *Read Only* and *Collaborative* buttons
- [#9802](https://github.com/RocketChat/Rocket.Chat/pull/9802) Not receiving sound notifications in rooms created by new LiveChats
- [#9858](https://github.com/RocketChat/Rocket.Chat/pull/9858) Silence the update check error message
- [#9850](https://github.com/RocketChat/Rocket.Chat/pull/9850) Importers no longer working due to the FileUpload changes
- [#9888](https://github.com/RocketChat/Rocket.Chat/pull/9888) Misplaced "Save Changes" button in user account panel
- [#9877](https://github.com/RocketChat/Rocket.Chat/pull/9877) Not Translated Phrases
- [#9884](https://github.com/RocketChat/Rocket.Chat/pull/9884) Parsing messages with multiple markdown matches ignore some tokens
- [#9879](https://github.com/RocketChat/Rocket.Chat/pull/9879) Snap build was failing
<details>
<summary>Others</summary>
- [#9218](https://github.com/RocketChat/Rocket.Chat/pull/9218) [NEW] Image preview as 32x32 base64 jpeg
- [#9753](https://github.com/RocketChat/Rocket.Chat/pull/9753) Move NRR package to inside the project and convert from CoffeeScript
- [#9666](https://github.com/RocketChat/Rocket.Chat/pull/9666) Rocket.Chat Apps
- [#9796](https://github.com/RocketChat/Rocket.Chat/pull/9796) Sync from Master
- [#9546](https://github.com/RocketChat/Rocket.Chat/pull/9546) Update to meteor 1.6.1
- [#9811](https://github.com/RocketChat/Rocket.Chat/pull/9811) Dependencies update
- [#9797](https://github.com/RocketChat/Rocket.Chat/pull/9797) Develop fix sync from master
- [#9821](https://github.com/RocketChat/Rocket.Chat/pull/9821) Fix: Custom fields not showing on user info panel
- [#9843](https://github.com/RocketChat/Rocket.Chat/pull/9843) Regression: Avatar now open account related options
- [#9837](https://github.com/RocketChat/Rocket.Chat/pull/9837) Regression: Open search using ctrl/cmd + p and ctrl/cmd + k
- [#9804](https://github.com/RocketChat/Rocket.Chat/pull/9804) Regression: Page was not respecting the window height on Firefox
- [#9839](https://github.com/RocketChat/Rocket.Chat/pull/9839) Regression: Search bar is now full width
- [#9851](https://github.com/RocketChat/Rocket.Chat/pull/9851) Regression: Change create channel icon
- [#9845](https://github.com/RocketChat/Rocket.Chat/pull/9845) Regression: Fix admin/user settings item text
- [#9852](https://github.com/RocketChat/Rocket.Chat/pull/9852) Regression: Fix channel icons on safari
- [#9902](https://github.com/RocketChat/Rocket.Chat/pull/9902) Fix Apps not working on multi-instance deployments
- [#9905](https://github.com/RocketChat/Rocket.Chat/pull/9905) Regression: Improve sidebar filter
- [#9889](https://github.com/RocketChat/Rocket.Chat/pull/9889) Regression: Overlapping header in user profile panel
- [#9897](https://github.com/RocketChat/Rocket.Chat/pull/9897) Regression: sort on room's list not working correctly
- [#9908](https://github.com/RocketChat/Rocket.Chat/pull/9908) Improve link handling for attachments
- [#9931](https://github.com/RocketChat/Rocket.Chat/pull/9931) Regression: Directory now list default channel
- [#9928](https://github.com/RocketChat/Rocket.Chat/pull/9928) Regression: Fix livechat queue link
- [#9883](https://github.com/RocketChat/Rocket.Chat/pull/9883) Regression: Misplaced language dropdown in user preferences panel
</details>
<details>
<summary>Detils</summary>
## 0.62.0 (2018-02-28)
### Bug Fixes
- [#9934](https://github.com/RocketChat/Rocket.Chat/pull/9934) Typo on french translation for "Open"
<details>
<summary>Others</summary>
- [#9908](https://github.com/RocketChat/Rocket.Chat/pull/9908) Improve link handling for attachments
- [#9931](https://github.com/RocketChat/Rocket.Chat/pull/9931) Regression: Directory now list default channel
- [#9928](https://github.com/RocketChat/Rocket.Chat/pull/9928) Regression: Fix livechat queue link
- [#9883](https://github.com/RocketChat/Rocket.Chat/pull/9883) Regression: Misplaced language dropdown in user preferences panel
</details>
## 0.62.0-rc.3 (2018-02-27)
### Bug Fixes
- [#9850](https://github.com/RocketChat/Rocket.Chat/pull/9850) Importers no longer working due to the FileUpload changes
- [#9888](https://github.com/RocketChat/Rocket.Chat/pull/9888) Misplaced "Save Changes" button in user account panel
- [#9877](https://github.com/RocketChat/Rocket.Chat/pull/9877) Not Translated Phrases
- [#9884](https://github.com/RocketChat/Rocket.Chat/pull/9884) Parsing messages with multiple markdown matches ignore some tokens
- [#9879](https://github.com/RocketChat/Rocket.Chat/pull/9879) Snap build was failing
<details>
<summary>Others</summary>
- [#9902](https://github.com/RocketChat/Rocket.Chat/pull/9902) Fix Apps not working on multi-instance deployments
- [#9905](https://github.com/RocketChat/Rocket.Chat/pull/9905) Regression: Improve sidebar filter
- [#9889](https://github.com/RocketChat/Rocket.Chat/pull/9889) Regression: Overlapping header in user profile panel
- [#9897](https://github.com/RocketChat/Rocket.Chat/pull/9897) Regression: sort on room's list not working correctly
</details>
## 0.62.0-rc.2 (2018-02-23)
### Bug Fixes
- [#9858](https://github.com/RocketChat/Rocket.Chat/pull/9858) Silence the update check error message
<details>
<summary>Others</summary>
- [#9851](https://github.com/RocketChat/Rocket.Chat/pull/9851) Regression: Change create channel icon
- [#9845](https://github.com/RocketChat/Rocket.Chat/pull/9845) Regression: Fix admin/user settings item text
- [#9852](https://github.com/RocketChat/Rocket.Chat/pull/9852) Regression: Fix channel icons on safari
</details>
## 0.62.0-rc.1 (2018-02-22)
### Bug Fixes
- [#9802](https://github.com/RocketChat/Rocket.Chat/pull/9802) Not receiving sound notifications in rooms created by new LiveChats
<details>
<summary>Others</summary>
- [#9811](https://github.com/RocketChat/Rocket.Chat/pull/9811) Dependencies update
- [#9797](https://github.com/RocketChat/Rocket.Chat/pull/9797) Develop fix sync from master
- [#9821](https://github.com/RocketChat/Rocket.Chat/pull/9821) Fix: Custom fields not showing on user info panel
- [#9843](https://github.com/RocketChat/Rocket.Chat/pull/9843) Regression: Avatar now open account related options
- [#9837](https://github.com/RocketChat/Rocket.Chat/pull/9837) Regression: Open search using ctrl/cmd + p and ctrl/cmd + k
- [#9804](https://github.com/RocketChat/Rocket.Chat/pull/9804) Regression: Page was not respecting the window height on Firefox
- [#9839](https://github.com/RocketChat/Rocket.Chat/pull/9839) Regression: Search bar is now full width
</details>
## 0.62.0-rc.0 (2018-02-21)
### BREAKING CHANGES
- [#9711](https://github.com/RocketChat/Rocket.Chat/pull/9711) Remove Graphics/Image Magick support
### New Features
- [#9549](https://github.com/RocketChat/Rocket.Chat/pull/9549) Add route to get user shield/badge
- [#9457](https://github.com/RocketChat/Rocket.Chat/pull/9457) Add user settings / preferences API endpoint
- [#7098](https://github.com/RocketChat/Rocket.Chat/pull/7098) Alert admins when user requires approval & alert users when the account is approved/activated/deactivated
- [#9527](https://github.com/RocketChat/Rocket.Chat/pull/9527) Allow configuration of SAML logout behavior
- [#8193](https://github.com/RocketChat/Rocket.Chat/pull/8193) Allow request avatar placeholders as PNG or JPG instead of SVG
- [#9312](https://github.com/RocketChat/Rocket.Chat/pull/9312) Allow sounds when conversation is focused
- [#9519](https://github.com/RocketChat/Rocket.Chat/pull/9519) API to fetch permissions & user roles
- [#9642](https://github.com/RocketChat/Rocket.Chat/pull/9642) Browse more channels / Directory
- [#9778](https://github.com/RocketChat/Rocket.Chat/pull/9778) General alert banner
- [#9687](https://github.com/RocketChat/Rocket.Chat/pull/9687) Global message search (beta: disabled by default)
- [#8158](https://github.com/RocketChat/Rocket.Chat/pull/8158) GraphQL API
- [#9298](https://github.com/RocketChat/Rocket.Chat/pull/9298) Improved default welcome message
- [#8933](https://github.com/RocketChat/Rocket.Chat/pull/8933) Internal hubot support for Direct Messages and Private Groups
- [#9255](https://github.com/RocketChat/Rocket.Chat/pull/9255) Livestream tab
- [#9746](https://github.com/RocketChat/Rocket.Chat/pull/9746) Makes shield icon configurable
- [#9717](https://github.com/RocketChat/Rocket.Chat/pull/9717) Message read receipts
- [#9507](https://github.com/RocketChat/Rocket.Chat/pull/9507) New REST API to mark channel as read
- [#9608](https://github.com/RocketChat/Rocket.Chat/pull/9608) New sidebar layout
- [#9699](https://github.com/RocketChat/Rocket.Chat/pull/9699) Option to proxy files and avatars through the server
- [#9509](https://github.com/RocketChat/Rocket.Chat/pull/9509) REST API to use Spotlight
- [#9793](https://github.com/RocketChat/Rocket.Chat/pull/9793) Version update check
### Bug Fixes
- [#9424](https://github.com/RocketChat/Rocket.Chat/pull/9424) 'Query' support for channels.list.joined, groups.list, groups.listAll, im.list
- [#9737](https://github.com/RocketChat/Rocket.Chat/pull/9737) API to retrive rooms was returning empty objects
- [#9487](https://github.com/RocketChat/Rocket.Chat/pull/9487) Chat Message Reactions REST API End Point
- [#9560](https://github.com/RocketChat/Rocket.Chat/pull/9560) Chrome 64 breaks jitsi-meet iframe
- [#9662](https://github.com/RocketChat/Rocket.Chat/pull/9662) Close button on file upload bar was not working
- [#9714](https://github.com/RocketChat/Rocket.Chat/pull/9714) Close Livechat conversation by visitor not working in version 0.61.0
- [#9676](https://github.com/RocketChat/Rocket.Chat/pull/9676) Custom emoji was cropping sometimes
- [#9747](https://github.com/RocketChat/Rocket.Chat/pull/9747) DeprecationWarning: prom-client ... when starting Rocket Chat server
- [#9639](https://github.com/RocketChat/Rocket.Chat/pull/9639) Desktop notification not showing when avatar came from external storage service
- [#9776](https://github.com/RocketChat/Rocket.Chat/pull/9776) Emoji rendering on last message
- [#9640](https://github.com/RocketChat/Rocket.Chat/pull/9640) Facebook integration in livechat not working on version 0.61.0
- [#9067](https://github.com/RocketChat/Rocket.Chat/pull/9067) Formal pronouns and some small mistakes in German texts
- [#9716](https://github.com/RocketChat/Rocket.Chat/pull/9716) GitLab OAuth does not work when GitLab’s URL ends with slash
- [#9697](https://github.com/RocketChat/Rocket.Chat/pull/9697) Harmonize channel-related actions
- [#9772](https://github.com/RocketChat/Rocket.Chat/pull/9772) Livechat conversation not receiving messages when start without form
- [#9599](https://github.com/RocketChat/Rocket.Chat/pull/9599) Livechat is not working when running in a sub path
- [#9750](https://github.com/RocketChat/Rocket.Chat/pull/9750) Livechat issues on external queue and lead capture
- [#9720](https://github.com/RocketChat/Rocket.Chat/pull/9720) Messages can't be quoted sometimes
- [#9454](https://github.com/RocketChat/Rocket.Chat/pull/9454) Missing link Site URLs in enrollment e-mails
- [#9610](https://github.com/RocketChat/Rocket.Chat/pull/9610) Missing string 'Username_already_exist' on the accountProfile page
- [#9520](https://github.com/RocketChat/Rocket.Chat/pull/9520) Rest API helpers only applying to v1
- [#9696](https://github.com/RocketChat/Rocket.Chat/pull/9696) Show custom room types icon in channel header
- [#9570](https://github.com/RocketChat/Rocket.Chat/pull/9570) SVG avatars are not been displayed correctly when load in non HTML containers
- [#9623](https://github.com/RocketChat/Rocket.Chat/pull/9623) Weird rendering of emojis at sidebar when `last message` is activated
- [#9665](https://github.com/RocketChat/Rocket.Chat/pull/9665) Wrong behavior of rooms info's *Read Only* and *Collaborative* buttons
<details>
<summary>Others</summary>
- [#9218](https://github.com/RocketChat/Rocket.Chat/pull/9218) [NEW] Image preview as 32x32 base64 jpeg
- [#9753](https://github.com/RocketChat/Rocket.Chat/pull/9753) Move NRR package to inside the project and convert from CoffeeScript
- [#9666](https://github.com/RocketChat/Rocket.Chat/pull/9666) Rocket.Chat Apps
- [#9796](https://github.com/RocketChat/Rocket.Chat/pull/9796) Sync from Master
- [#9546](https://github.com/RocketChat/Rocket.Chat/pull/9546) Update to meteor 1.6.1
</details>
</details>
<a name="0.61.2"></a>
## 0.61.2 (2018-02-20)

@ -363,7 +363,11 @@ Read about [how it all started](https://blog.blackducksoftware.com/rocket-chat-e
## Issues
[Github Issues](https://github.com/RocketChat/Rocket.Chat/issues) are used to track todos, bugs, feature requests, and more.
[Github Issues](https://github.com/RocketChat/Rocket.Chat/issues) are used to track bugs and tasks on the roadmap.
## Feature Requests
[Feature Request Forums](https://forums.rocket.chat/c/feature-requests) are used to suggest, discuss and upvote feature suggestions.
### Stack Overflow

@ -95,19 +95,6 @@ FlowRouter.route('/account/:group?', {
}]
});
FlowRouter.route('/history/private', {
name: 'privateHistory',
subscriptions(/*params, queryParams*/) {
this.register('privateHistory', Meteor.subscribe('privateHistory'));
},
action() {
Session.setDefault('historyFilter', '');
BlazeLayout.render('main', {center: 'privateHistory'});
}
});
FlowRouter.route('/terms-of-service', {
name: 'terms-of-service',

@ -2,6 +2,11 @@ import { ReadReceipt } from './lib/ReadReceipt';
RocketChat.callbacks.add('afterSaveMessage', (message, room) => {
// skips this callback if the message was edited
if (message.editedAt) {
return message;
}
// set subscription as read right after message was sent
RocketChat.models.Subscriptions.setAsReadByRoomIdAndUserId(room._id, message.u._id);

10
package-lock.json generated

@ -12521,6 +12521,16 @@
}
}
},
"twit": {
"version": "2.2.9",
"resolved": "https://registry.npmjs.org/twit/-/twit-2.2.9.tgz",
"integrity": "sha1-ZxBXT4FkHaoDeWobS457eNPXVnY=",
"requires": {
"bluebird": "3.5.1",
"mime": "1.6.0",
"request": "2.83.0"
}
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",

@ -1,7 +1,7 @@
{
"name": "Rocket.Chat",
"description": "The Ultimate Open Source WebChat Platform",
"version": "0.62.0-develop",
"version": "0.63.0-develop",
"author": {
"name": "Rocket.Chat",
"url": "https://rocket.chat/"
@ -166,6 +166,7 @@
"tar-stream": "^1.5.5",
"toastr": "^2.1.4",
"twilio": "^2.9.1",
"twit": "^2.2.9",
"ua-parser-js": "^0.7.17",
"underscore": "^1.8.3",
"underscore.string": "^3.3.4",

@ -13,30 +13,39 @@ RocketChat.TOTP = {
},
verify({ secret, token, backupTokens, userId }) {
let verified;
// validates a backup code
if (token.length === 8 && backupTokens) {
const hashedCode = SHA256(token);
const usedCode = backupTokens.indexOf(hashedCode);
if (usedCode !== -1) {
verified = true;
backupTokens.splice(usedCode, 1);
// mark the code as used (remove it from the list)
RocketChat.models.Users.update2FABackupCodesByUserId(userId, backupTokens);
return true;
}
} else {
verified = speakeasy.totp.verify({
return false;
}
const maxDelta = RocketChat.settings.get('Accounts_TwoFactorAuthentication_MaxDelta');
if (maxDelta) {
const verifiedDelta = speakeasy.totp.verifyDelta({
secret,
encoding: 'base32',
token
token,
window: maxDelta
});
return verifiedDelta !== undefined;
}
return verified;
return speakeasy.totp.verify({
secret,
encoding: 'base32',
token
});
},
generateCodes() {

@ -33,6 +33,7 @@ Package.onUse(function(api) {
api.addFiles('server/v1/subscriptions.js', 'server');
api.addFiles('server/v1/chat.js', 'server');
api.addFiles('server/v1/commands.js', 'server');
api.addFiles('server/v1/emoji-custom.js', 'server');
api.addFiles('server/v1/groups.js', 'server');
api.addFiles('server/v1/im.js', 'server');
api.addFiles('server/v1/integrations.js', 'server');

@ -330,7 +330,14 @@ RocketChat.API.v1.addRoute('channels.history', { authRequired: true }, {
let result;
Meteor.runAsUser(this.userId, () => {
result = Meteor.call('getChannelHistory', { rid: findResult._id, latest: latestDate, oldest: oldestDate, inclusive, count, unreads });
result = Meteor.call('getChannelHistory', {
rid: findResult._id,
latest: latestDate,
oldest: oldestDate,
inclusive,
count,
unreads
});
});
if (!result) {
@ -417,15 +424,15 @@ RocketChat.API.v1.addRoute('channels.list', { authRequired: true }, {
action() {
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const hasPermissionToSeeAllPublicChannels = RocketChat.authz.hasPermission(this.userId, 'view-c-room');
const ourQuery = Object.assign({}, query, { t: 'c' });
//Special check for the permissions
if (RocketChat.authz.hasPermission(this.userId, 'view-joined-room')) {
if (RocketChat.authz.hasPermission(this.userId, 'view-joined-room') && !hasPermissionToSeeAllPublicChannels) {
ourQuery.usernames = {
$in: [ this.user.username ]
$in: [this.user.username]
};
} else if (!RocketChat.authz.hasPermission(this.userId, 'view-c-room')) {
} else if (!hasPermissionToSeeAllPublicChannels) {
return RocketChat.API.v1.unauthorized();
}
@ -476,7 +483,11 @@ RocketChat.API.v1.addRoute('channels.list.joined', { authRequired: true }, {
RocketChat.API.v1.addRoute('channels.members', { authRequired: true }, {
get() {
const findResult = findChannelByIdOrName({ params: this.requestParams(), checkedArchived: false, returnUsernames: true });
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
returnUsernames: true
});
const { offset, count } = this.getPaginationItems();
const { sort } = this.parseJsonQuery();
@ -625,7 +636,7 @@ RocketChat.API.v1.addRoute('channels.rename', { authRequired: true }, {
return RocketChat.API.v1.failure('The bodyParam "name" is required');
}
const findResult = findChannelByIdOrName({ params: { roomId: this.bodyParams.roomId} });
const findResult = findChannelByIdOrName({ params: { roomId: this.bodyParams.roomId } });
if (findResult.name === this.bodyParams.name) {
return RocketChat.API.v1.failure('The channel name is the same as what it would be renamed to.');
@ -747,6 +758,24 @@ RocketChat.API.v1.addRoute('channels.setTopic', { authRequired: true }, {
}
});
RocketChat.API.v1.addRoute('channels.setAnnouncement', { authRequired: true }, {
post() {
if (!this.bodyParams.announcement || !this.bodyParams.announcement.trim()) {
return RocketChat.API.v1.failure('The bodyParam "announcement" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomAnnouncement', this.bodyParams.announcement);
});
return RocketChat.API.v1.success({
announcement: this.bodyParams.announcement
});
}
});
RocketChat.API.v1.addRoute('channels.setType', { authRequired: true }, {
post() {
if (!this.bodyParams.type || !this.bodyParams.type.trim()) {
@ -784,3 +813,76 @@ RocketChat.API.v1.addRoute('channels.unarchive', { authRequired: true }, {
return RocketChat.API.v1.success();
}
});
RocketChat.API.v1.addRoute('channels.getAllUserMentionsByChannel', { authRequired: true }, {
get() {
const { roomId } = this.requestParams();
const { offset, count } = this.getPaginationItems();
const { sort } = this.parseJsonQuery();
if (!roomId) {
return RocketChat.API.v1.failure('The request param "roomId" is required');
}
const mentions = Meteor.runAsUser(this.userId, () => Meteor.call('getUserMentionsByChannel', {
roomId,
options: {
sort: sort ? sort : { ts: 1 },
skip: offset,
limit: count
}
}));
const allMentions = Meteor.runAsUser(this.userId, () => Meteor.call('getUserMentionsByChannel', {
roomId,
options: {}
}));
return RocketChat.API.v1.success({
mentions,
count: mentions.length,
offset,
total: allMentions.length
});
}
});
RocketChat.API.v1.addRoute('channels.notifications', { authRequired: true }, {
get() {
const { roomId } = this.requestParams();
if (!roomId) {
return RocketChat.API.v1.failure('The \'roomId\' param is required');
}
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, {
fields: {
_room: 0,
_user: 0,
$loki: 0
}
});
return RocketChat.API.v1.success({
subscription
});
},
post() {
const saveNotifications = (notifications, roomId) => {
Object.keys(notifications).map((notificationKey) => {
Meteor.runAsUser(this.userId, () => Meteor.call('saveNotificationSettings', roomId, notificationKey, notifications[notificationKey]));
});
};
const { roomId, notifications } = this.bodyParams;
if (!roomId) {
return RocketChat.API.v1.failure('The \'roomId\' param is required');
}
if (!notifications || Object.keys(notifications).length === 0) {
return RocketChat.API.v1.failure('The \'notifications\' param is required');
}
saveNotifications(notifications, roomId);
}
});

@ -0,0 +1,7 @@
RocketChat.API.v1.addRoute('emoji-custom', { authRequired: true }, {
get() {
const emojis = Meteor.call('listEmojiCustom');
return RocketChat.API.v1.success({ emojis });
}
});

@ -18,6 +18,29 @@ RocketChat.API.v1.addRoute('info', { authRequired: false }, {
}
});
RocketChat.API.v1.addRoute('settings.oauth', { authRequired: false }, {
get() {
const mountOAuthServices = () => {
const oAuthServicesEnabled = ServiceConfiguration.configurations.find({}).fetch();
return oAuthServicesEnabled.map((service) => {
return {
id: service._id,
name: service.service,
appId: service.appId || service.clientId,
buttonLabelText: service.buttonLabelText || '',
buttonColor: service.buttonColor || '',
buttonLabelColor: service.buttonLabelColor || ''
};
});
};
return RocketChat.API.v1.success({
services: mountOAuthServices()
});
}
});
RocketChat.API.v1.addRoute('me', { authRequired: true }, {
get() {
const me = _.pick(this.user, [

@ -260,6 +260,33 @@ RocketChat.API.v1.addRoute('users.update', { authRequired: true }, {
}
});
RocketChat.API.v1.addRoute('users.updateOwnBasicInfo', { authRequired: true }, {
post() {
check(this.bodyParams, {
data: Match.ObjectIncluding({
email: Match.Maybe(String),
name: Match.Maybe(String),
username: Match.Maybe(String),
currentPassword: Match.Maybe(String),
newPassword: Match.Maybe(String)
}),
customFields: Match.Maybe(Object)
});
const userData = {
email: this.bodyParams.data.email,
realname: this.bodyParams.data.name,
username: this.bodyParams.data.username,
newPassword: this.bodyParams.data.newPassword,
typedPassword: this.bodyParams.data.currentPassword
};
Meteor.runAsUser(this.userId, () => Meteor.call('saveUserProfile', userData, this.bodyParams.customFields));
return RocketChat.API.v1.success({ user: RocketChat.models.Users.findOneById(this.userId, { fields: RocketChat.API.v1.defaultFieldsToExclude }) });
}
});
RocketChat.API.v1.addRoute('users.createToken', { authRequired: true }, {
post() {
const user = this.getUserFromParams();
@ -267,7 +294,7 @@ RocketChat.API.v1.addRoute('users.createToken', { authRequired: true }, {
Meteor.runAsUser(this.userId, () => {
data = Meteor.call('createToken', user._id);
});
return data ? RocketChat.API.v1.success({data}) : RocketChat.API.v1.unauthorized();
return data ? RocketChat.API.v1.success({ data }) : RocketChat.API.v1.unauthorized();
}
});
@ -308,7 +335,6 @@ RocketChat.API.v1.addRoute('users.setPreferences', { authRequired: true }, {
enableAutoAway: Match.Maybe(Boolean),
highlights: Match.Maybe(Array),
desktopNotificationDuration: Match.Maybe(Number),
viewMode: Match.Maybe(Number),
hideUsernames: Match.Maybe(Boolean),
hideRoles: Match.Maybe(Boolean),
hideAvatars: Match.Maybe(Boolean),

@ -24,7 +24,6 @@ Template.appInstall.onCreated(function() {
// Allow passing in a url as a query param to show installation of
if (FlowRouter.getQueryParam('url')) {
console.log('Url:', FlowRouter.getQueryParam('url'));
instance.appUrl.set(FlowRouter.getQueryParam('url'));
FlowRouter.setQueryParams({ url: null });
}
@ -36,11 +35,8 @@ Template.appInstall.events({
// Handle url installations
if (url) {
console.log('Installing via url.');
t.isInstalling.set(true);
RocketChat.API.post('apps', { url }).then((result) => {
console.log('result', result);
FlowRouter.go(`/admin/apps/${ result.app.id }`);
}).catch((err) => {
console.warn('err', err);

@ -1,6 +1,8 @@
import _ from 'underscore';
import s from 'underscore.string';
import { AppEvents } from '../communication';
Template.appManage.onCreated(function() {
const instance = this;
this.id = new ReactiveVar(FlowRouter.getParam('appId'));
@ -13,24 +15,58 @@ Template.appManage.onCreated(function() {
const id = this.id.get();
function _morphSettings(settings) {
Object.keys(settings).forEach((k) => {
settings[k].i18nPlaceholder = settings[k].i18nPlaceholder || ' ';
settings[k].value = settings[k].value || settings[k].packageValue;
settings[k].oldValue = settings[k].value;
});
instance.settings.set(settings);
}
Promise.all([
RocketChat.API.get(`apps/${ id }`),
RocketChat.API.get(`apps/${ id }/settings`)
]).then((results) => {
instance.app.set(results[0].app);
_morphSettings(results[1].settings);
Object.keys(results[1].settings).forEach((k) => {
results[1].settings[k].i18nPlaceholder = results[1].settings[k].i18nPlaceholder || ' ';
results[1].settings[k].value = results[1].settings[k].value || results[1].settings[k].packageValue;
results[1].settings[k].oldValue = results[1].settings[k].value;
});
instance.settings.set(results[1].settings);
this.ready.set(true);
}).catch((e) => {
instance.hasError.set(true);
instance.theError.set(e.message);
});
instance.onStatusChanged = function _onStatusChanged({ appId, status }) {
if (appId !== id) {
return;
}
const app = instance.app.get();
app.status = status;
instance.app.set(app);
};
instance.onSettingUpdated = function _onSettingUpdated({ appId }) {
if (appId !== id) {
return;
}
RocketChat.API.get(`apps/${ id }/settings`).then((result) => {
_morphSettings(result.settings);
});
};
window.Apps.getWsListener().registerListener(AppEvents.APP_STATUS_CHANGE, instance.onStatusChanged);
window.Apps.getWsListener().registerListener(AppEvents.APP_SETTING_UPDATED, instance.onSettingUpdated);
});
Template.apps.onDestroyed(function() {
const instance = this;
window.Apps.getWsListener().unregisterListener(AppEvents.APP_STATUS_CHANGE, instance.onStatusChanged);
window.Apps.getWsListener().unregisterListener(AppEvents.APP_SETTING_UPDATED, instance.onSettingUpdated);
});
Template.appManage.helpers({
@ -173,7 +209,6 @@ Template.appManage.events({
'change .input-monitor, keyup .input-monitor': _.throttle(function(e, t) {
let value = s.trim($(e.target).val());
console.log(value);
switch (this.type) {
case 'int':
value = parseInt(value);

@ -1,3 +1,5 @@
import { AppEvents } from '../communication';
Template.apps.onCreated(function() {
const instance = this;
this.ready = new ReactiveVar(false);
@ -7,6 +9,39 @@ Template.apps.onCreated(function() {
instance.apps.set(result.apps);
instance.ready.set(true);
});
instance.onAppAdded = function _appOnAppAdded(appId) {
RocketChat.API.get(`apps/${ appId }`).then((result) => {
const apps = instance.apps.get();
apps.push(result.app);
instance.apps.set(apps);
});
};
instance.onAppRemoved = function _appOnAppRemoved(appId) {
const apps = instance.apps.get();
let index = -1;
apps.find((item, i) => {
if (item.id === appId) {
index = i;
return true;
}
});
apps.splice(index, 1);
instance.apps.set(apps);
};
window.Apps.getWsListener().registerListener(AppEvents.APP_ADDED, instance.onAppAdded);
window.Apps.getWsListener().registerListener(AppEvents.APP_REMOVED, instance.onAppAdded);
});
Template.apps.onDestroyed(function() {
const instance = this;
window.Apps.getWsListener().unregisterListener(AppEvents.APP_ADDED, instance.onAppAdded);
window.Apps.getWsListener().unregisterListener(AppEvents.APP_REMOVED, instance.onAppAdded);
});
Template.apps.helpers({

@ -1,3 +1,3 @@
import { AppWebsocketReceiver } from './websockets';
import { AppWebsocketReceiver, AppEvents } from './websockets';
export { AppWebsocketReceiver };
export { AppWebsocketReceiver, AppEvents };

@ -1,19 +1,67 @@
export const AppEvents = Object.freeze({
APP_ADDED: 'app/added',
APP_REMOVED: 'app/removed',
APP_UPDATED: 'app/updated',
APP_STATUS_CHANGE: 'app/statusUpdate',
APP_SETTING_UPDATED: 'app/settingUpdated',
COMMAND_ADDED: 'command/added',
COMMAND_DISABLED: 'command/disabled',
COMMAND_UPDATED: 'command/updated',
COMMAND_REMOVED: 'command/removed'
});
export class AppWebsocketReceiver {
constructor(orch) {
this.orch = orch;
this.streamer = new Meteor.Streamer('apps');
this.streamer.on('app/added', this.onAppAdded.bind(this));
this.streamer.on('command/added', this.onCommandAdded.bind(this));
this.streamer.on('command/disabled', this.onCommandDisabled.bind(this));
this.streamer.on('command/updated', this.onCommandUpdated.bind(this));
this.streamer.on('command/removed', this.onCommandDisabled.bind(this));
this.streamer.on(AppEvents.APP_ADDED, this.onAppAdded.bind(this));
this.streamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this));
this.streamer.on(AppEvents.APP_UPDATED, this.onAppUpdated.bind(this));
this.streamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this));
this.streamer.on(AppEvents.APP_SETTING_UPDATED, this.onAppSettingUpdated.bind(this));
this.streamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this));
this.streamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this));
this.streamer.on(AppEvents.COMMAND_UPDATED, this.onCommandUpdated.bind(this));
this.streamer.on(AppEvents.COMMAND_REMOVED, this.onCommandDisabled.bind(this));
this.listeners = {};
Object.keys(AppEvents).forEach((v) => {
this.listeners[AppEvents[v]] = [];
});
}
registerListener(event, listener) {
this.listeners[event].push(listener);
}
unregisterListener(event, listener) {
this.listeners[event].splice(this.listeners[event].indexOf(listener), 1);
}
onAppAdded(appId) {
RocketChat.API.get(`apps/${ appId }/languages`).then((result) => {
this.orch.parseAndLoadLanguages(result.languages);
});
this.listeners[AppEvents.APP_ADDED].forEach((listener) => listener(appId));
}
onAppRemoved(appId) {
this.listeners[AppEvents.APP_REMOVED].forEach((listener) => listener(appId));
}
onAppUpdated(appId) {
this.listeners[AppEvents.APP_UPDATED].forEach((listener) => listener(appId));
}
onAppStatusUpdated({ appId, status }) {
this.listeners[AppEvents.APP_STATUS_CHANGE].forEach((listener) => listener({ appId, status }));
}
onAppSettingUpdated({ appId }) {
this.listeners[AppEvents.APP_SETTING_UPDATED].forEach((listener) => listener({ appId }));
}
onCommandAdded(command) {

@ -87,6 +87,6 @@ Package.onUse(function(api) {
Npm.depends({
'busboy': '0.2.13',
'@rocket.chat/apps-engine': '0.3.5',
'@rocket.chat/apps-ts-definition': '0.7.6'
'@rocket.chat/apps-engine': '0.4.8',
'@rocket.chat/apps-ts-definition': '0.7.15'
});

@ -3,33 +3,19 @@ export class AppActivationBridge {
this.orch = orch;
}
appEnabled(app) {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been enabled.`);
appAdded(app) {
this.orch.getNotifier().appAdded(app.getID());
}
appDisabled(app) {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been disabled.`);
}
appLoaded(app, enabled) {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been loaded and enabled? ${ enabled }`);
if (enabled) {
this.orch.getNotifier().appAdded(app.getID());
}
}
appUpdated(app, enabled) {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been updated and enabled? ${ enabled }`);
if (enabled) {
this.orch.getNotifier().appUpdated(app.getID());
}
appUpdated(app) {
this.orch.getNotifier().appUpdated(app.getID());
}
appRemoved(app) {
console.log(`The App ${ app.getName() } (${ app.getID() }) has been removed.`);
this.orch.getNotifier().appRemoved(app.getID());
}
appStatusChanged(app, status) {
this.orch.getNotifier().appStatusUpdated(app.getID(), status);
}
}

@ -1,9 +1,11 @@
import { AppBridges } from '@rocket.chat/apps-engine/server/bridges';
import { AppActivationBridge } from './activation';
import { AppDetailChangesBridge } from './details';
import { AppCommandsBridge } from './commands';
import { AppEnvironmentalVariableBridge } from './environmental';
import { AppHttpBridge } from './http';
import { AppListenerBridge } from './listeners';
import { AppMessageBridge } from './messages';
import { AppPersistenceBridge } from './persistence';
import { AppRoomBridge } from './rooms';
@ -16,8 +18,10 @@ export class RealAppBridges extends AppBridges {
this._actBridge = new AppActivationBridge(orch);
this._cmdBridge = new AppCommandsBridge(orch);
this._detBridge = new AppDetailChangesBridge(orch);
this._envBridge = new AppEnvironmentalVariableBridge(orch);
this._httpBridge = new AppHttpBridge();
this._lisnBridge = new AppListenerBridge(orch);
this._msgBridge = new AppMessageBridge(orch);
this._persistBridge = new AppPersistenceBridge(orch);
this._roomBridge = new AppRoomBridge(orch);
@ -37,6 +41,10 @@ export class RealAppBridges extends AppBridges {
return this._httpBridge;
}
getListenerBridge() {
return this._lisnBridge;
}
getMessageBridge() {
return this._msgBridge;
}
@ -49,6 +57,10 @@ export class RealAppBridges extends AppBridges {
return this._actBridge;
}
getAppDetailChangesBridge() {
return this._detBridge;
}
getRoomBridge() {
return this._roomBridge;
}

@ -0,0 +1,13 @@
export class AppDetailChangesBridge {
constructor(orch) {
this.orch = orch;
}
onAppSettingsChange(appId, setting) {
try {
this.orch.getNotifier().appSettingsChange(appId, setting);
} catch (e) {
console.warn('failed to notify about the setting change.', appId);
}
}
}

@ -3,6 +3,7 @@ import { AppActivationBridge } from './activation';
import { AppCommandsBridge } from './commands';
import { AppEnvironmentalVariableBridge } from './environmental';
import { AppHttpBridge } from './http';
import { AppListenerBridge } from './listeners';
import { AppMessageBridge } from './messages';
import { AppPersistenceBridge } from './persistence';
import { AppRoomBridge } from './rooms';
@ -15,6 +16,7 @@ export {
AppCommandsBridge,
AppEnvironmentalVariableBridge,
AppHttpBridge,
AppListenerBridge,
AppMessageBridge,
AppPersistenceBridge,
AppRoomBridge,

@ -0,0 +1,15 @@
export class AppListenerBridge {
constructor(orch) {
this.orch = orch;
}
messageEvent(inte, message) {
const msg = this.orch.getConverters().get('messages').convertMessage(message);
return this.orch.getManager().getListenerManager().executeListener(inte, msg);
}
roomEvent(inte, room) {
const rm = this.orch.getConverters().get('rooms').convertRoom(room);
return this.orch.getManager().getListenerManager().executeListener(inte, rm);
}
}

@ -37,4 +37,30 @@ export class AppMessageBridge {
RocketChat.updateMessage(msg, editor);
}
notifyUser(user, message, appId) {
console.log(`The App ${ appId } is notifying a user.`);
const msg = this.orch.getConverters().get('messages').convertAppMessage(message);
RocketChat.Notifications.notifyUser(user.id, 'message', Object.assign(msg, {
_id: Random.id(),
ts: new Date(),
u: undefined,
editor: undefined
}));
}
notifyRoom(room, message, appId) {
console.log(`The App ${ appId } is notifying a room's users.`);
const msg = this.orch.getConverters().get('messages').convertAppMessage(message);
RocketChat.Notifications.notifyUsersOfRoom(room.id, 'message', Object.assign(msg, {
_id: Random.id(),
ts: new Date(),
u: undefined,
editor: undefined
}));
}
}

@ -40,19 +40,24 @@ export class AppPersistenceBridge {
readByAssociations(associations, appId) {
console.log(`The App ${ appId } is searching for records that are associated with the following:`, associations);
throw new Error('Not implemented.');
const records = this.orch.getPersistenceModel().find({
appId,
associations: { $all: associations }
}).fetch();
return Array.isArray(records) ? records.map((r) => r.data) : [];
}
remove(id, appId) {
console.log(`The App ${ appId } is removing one of their records by the id: "${ id }"`);
const record = this.orch.getPersistenceModel().findOneById(id);
const record = this.orch.getPersistenceModel().findOne({ _id: id, appId });
if (!record) {
return undefined;
}
this.orch.getPersistenceModel().remove({ _id: id });
this.orch.getPersistenceModel().remove({ _id: id, appId });
return record.data;
}
@ -60,7 +65,22 @@ export class AppPersistenceBridge {
removeByAssociations(associations, appId) {
console.log(`The App ${ appId } is removing records with the following associations:`, associations);
throw new Error('Not implemented.');
const query = {
appId,
associations: {
$all: associations
}
};
const records = this.orch.getPersistenceModel().find(query).fetch();
if (!records) {
return undefined;
}
this.orch.getPersistenceModel().remove(query);
return Array.isArray(records) ? records.map((r) => r.data) : [];
}
update(id, data, upsert, appId) {

@ -1,9 +1,11 @@
import { AppMethods} from './methods';
import { AppsRestApi } from './rest';
import { AppWebsocketNotifier } from './websockets';
import { AppEvents, AppServerNotifier, AppServerListener } from './websockets';
export {
AppMethods,
AppsRestApi,
AppWebsocketNotifier
AppEvents,
AppServerNotifier,
AppServerListener
};

@ -5,16 +5,24 @@ export class AppMethods {
this._addMethods();
}
isEnabled() {
return typeof this._manager !== 'undefined';
}
isLoaded() {
return typeof this._manager !== 'undefined' && this.manager.areAppsLoaded();
}
_addMethods() {
const manager = this._manager;
const instance = this;
Meteor.methods({
'apps/is-enabled'() {
return typeof manager !== 'undefined';
return instance.isEnabled();
},
'apps/is-loaded'() {
return typeof manager !== 'undefined' || manager.areAppsLoaded();
return instance.isLoaded();
}
});
}

@ -158,7 +158,7 @@ export class AppsRestApi {
}
});
this.api.addRoute(':id/languages', { authRequired: true }, {
this.api.addRoute(':id/languages', { authRequired: false }, {
get() {
console.log(`Getting ${ this.urlParams.id }'s languages..`);
const prl = manager.getOneById(this.urlParams.id);

@ -1,36 +1,152 @@
export class AppWebsocketNotifier {
constructor() {
this.streamer = new Meteor.Streamer('apps', { retransmit: false });
this.streamer.allowRead('all');
this.streamer.allowEmit('all');
this.streamer.allowWrite('none');
import { AppStatus, AppStatusUtils } from '@rocket.chat/apps-ts-definition/AppStatus';
export const AppEvents = Object.freeze({
APP_ADDED: 'app/added',
APP_REMOVED: 'app/removed',
APP_UPDATED: 'app/updated',
APP_STATUS_CHANGE: 'app/statusUpdate',
APP_SETTING_UPDATED: 'app/settingUpdated',
COMMAND_ADDED: 'command/added',
COMMAND_DISABLED: 'command/disabled',
COMMAND_UPDATED: 'command/updated',
COMMAND_REMOVED: 'command/removed'
});
export class AppServerListener {
constructor(orch, engineStreamer, clientStreamer, recieved) {
this.orch = orch;
this.engineStreamer = engineStreamer;
this.clientStreamer = clientStreamer;
this.recieved = recieved;
this.engineStreamer.on(AppEvents.APP_ADDED, this.onAppAdded.bind(this));
this.engineStreamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this));
this.engineStreamer.on(AppEvents.APP_SETTING_UPDATED, this.onAppSettingUpdated.bind(this));
this.engineStreamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_UPDATED, this.onCommandUpdated.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_REMOVED, this.onCommandRemoved.bind(this));
}
onAppAdded(appId) {
this.orch.getManager().loadOne(appId).then(() => this.clientStreamer.emit(AppEvents.APP_ADDED, appId));
}
onAppStatusUpdated({ appId, status }) {
this.recieved.set(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`, { appId, status, when: new Date() });
if (AppStatusUtils.isEnabled(status)) {
this.orch.getManager().enable(appId)
.then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status }));
} else if (AppStatusUtils.isDisabled(status)) {
this.orch.getManager().disable(appId, AppStatus.MANUALLY_DISABLED === status)
.then(() => this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status }));
}
}
onAppSettingUpdated({ appId, setting }) {
this.recieved.set(`${ AppEvents.APP_SETTING_UPDATED }_${ appId }_${ setting.id }`, { appId, setting, when: new Date() });
this.orch.getManager().getSettingsManager().updateAppSetting(appId, setting)
.then(() => this.clientStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId }));
}
onAppRemoved(appId) {
this.orch.getManager().remove(appId).then(() => this.clientStreamer.emit(AppEvents.APP_REMOVED, appId));
}
onCommandAdded(command) {
this.clientStreamer.emit(AppEvents.COMMAND_ADDED, command);
}
onCommandDisabled(command) {
this.clientStreamer.emit(AppEvents.COMMAND_DISABLED, command);
}
onCommandUpdated(command) {
this.clientStreamer.emit(AppEvents.COMMAND_UPDATED, command);
}
onCommandRemoved(command) {
this.clientStreamer.emit(AppEvents.COMMAND_REMOVED, command);
}
}
export class AppServerNotifier {
constructor(orch) {
this.engineStreamer = new Meteor.Streamer('apps-engine', { retransmit: false });
this.engineStreamer.serverOnly = true;
this.engineStreamer.allowRead('none');
this.engineStreamer.allowEmit('all');
this.engineStreamer.allowWrite('none');
// This is used to broadcast to the web clients
this.clientStreamer = new Meteor.Streamer('apps', { retransmit: false });
this.clientStreamer.serverOnly = true;
this.clientStreamer.allowRead('all');
this.clientStreamer.allowEmit('all');
this.clientStreamer.allowWrite('none');
this.recieved = new Map();
this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer, this.recieved);
}
appAdded(appId) {
this.streamer.emit('app/added', appId);
this.engineStreamer.emit(AppEvents.APP_ADDED, appId);
this.clientStreamer.emit(AppEvents.APP_ADDED, appId);
}
appRemoved(appId) {
this.streamer.emit('app/removed', appId);
this.engineStreamer.emit(AppEvents.APP_REMOVED, appId);
this.clientStreamer.emit(AppEvents.APP_REMOVED, appId);
}
appUpdated(appId) {
this.streamer.emit('app/updated', appId);
this.engineStreamer.emit(AppEvents.APP_UPDATED, appId);
this.clientStreamer.emit(AppEvents.APP_UPDATED, appId);
}
appStatusUpdated(appId, status) {
if (this.recieved.has(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`)) {
const details = this.recieved.get(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`);
if (details.status === status) {
this.recieved.delete(`${ AppEvents.APP_STATUS_CHANGE }_${ appId }`);
return;
}
}
this.engineStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status });
this.clientStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status });
}
appSettingsChange(appId, setting) {
if (this.recieved.has(`${ AppEvents.APP_SETTING_UPDATED }_${ appId }_${ setting.id }`)) {
this.recieved.delete(`${ AppEvents.APP_SETTING_UPDATED }_${ appId }_${ setting.id }`);
return;
}
this.engineStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId, setting });
this.clientStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId });
}
commandAdded(command) {
this.streamer.emit('command/added', command);
this.engineStreamer.emit(AppEvents.COMMAND_ADDED, command);
this.clientStreamer.emit(AppEvents.COMMAND_ADDED, command);
}
commandDisabled(command) {
this.streamer.emit('command/disabled', command);
this.engineStreamer.emit(AppEvents.COMMAND_DISABLED, command);
this.clientStreamer.emit(AppEvents.COMMAND_DISABLED, command);
}
commandUpdated(command) {
this.streamer.emit('command/updated', command);
this.engineStreamer.emit(AppEvents.COMMAND_UPDATED, command);
this.clientStreamer.emit(AppEvents.COMMAND_UPDATED, command);
}
commandRemoved(command) {
this.streamer.emit('command/removed', command);
this.engineStreamer.emit(AppEvents.COMMAND_REMOVED, command);
this.clientStreamer.emit(AppEvents.COMMAND_REMOVED, command);
}
}

@ -1,5 +1,5 @@
import { RealAppBridges } from './bridges';
import { AppMethods, AppsRestApi, AppWebsocketNotifier } from './communication';
import { AppMethods, AppsRestApi, AppServerNotifier } from './communication';
import { AppMessagesConverter, AppRoomsConverter, AppSettingsConverter, AppUsersConverter } from './converters';
import { AppsLogsModel, AppsModel, AppsPersistenceModel, AppRealStorage, AppRealLogsStorage } from './storage';
@ -15,7 +15,7 @@ class AppServerOrchestrator {
this._logModel = new AppsLogsModel();
this._persistModel = new AppsPersistenceModel();
this._storage = new AppRealStorage(this._model);
this._logStorage = new AppRealLogsStorage(this._persistModel);
this._logStorage = new AppRealLogsStorage(this._logModel);
this._converters = new Map();
this._converters.set('messages', new AppMessagesConverter(this));
@ -29,7 +29,7 @@ class AppServerOrchestrator {
this._communicators = new Map();
this._communicators.set('methods', new AppMethods(this._manager));
this._communicators.set('notifier', new AppWebsocketNotifier());
this._communicators.set('notifier', new AppServerNotifier(this));
this._communicators.set('restapi', new AppsRestApi(this, this._manager));
}
@ -64,18 +64,27 @@ class AppServerOrchestrator {
getManager() {
return this._manager;
}
isEnabled() {
return true;
}
isLoaded() {
return this.getManager().areAppsLoaded();
}
}
Meteor.startup(function _appServerOrchestrator() {
// Ensure that everything is setup
if (process.env[AppManager.ENV_VAR_NAME_FOR_ENABLING] !== 'true' && process.env[AppManager.SUPER_FUN_ENV_ENABLEMENT_NAME] !== 'true') {
return new AppMethods();
global.Apps = new AppMethods();
return;
}
console.log('Orchestrating the app piece...');
global.Apps = new AppServerOrchestrator();
global.Apps.getManager().load()
.then(() => console.log('...done! ;)'))
.then(() => console.log('...done! :)'))
.catch((err) => console.warn('...failed!', err));
});

@ -22,14 +22,11 @@ export class AppRealLogsStorage extends AppLogStorage {
}
storeEntries(appId, logger) {
console.log(appId);
return new Promise((resolve, reject) => {
const item = AppConsole.toStorageEntry(appId, logger);
try {
console.log(item);
const id = this.db.insert(item);
console.log(id);
resolve(this.db.findOneById(id));
} catch (e) {

@ -34,6 +34,8 @@ Meteor.startup(function() {
{ _id: 'edit-room', roles : ['admin', 'owner', 'moderator'] },
{ _id: 'force-delete-message', roles : ['admin', 'owner'] },
{ _id: 'join-without-join-code', roles : ['admin', 'bot'] },
{ _id: 'leave-c', roles : ['admin', 'user', 'bot', 'anonymous'] },
{ _id: 'leave-p', roles : ['admin', 'user', 'bot', 'anonymous'] },
{ _id: 'manage-assets', roles : ['admin'] },
{ _id: 'manage-emoji', roles : ['admin'] },
{ _id: 'manage-integrations', roles : ['admin'] },

@ -7,7 +7,14 @@ import s from 'underscore.string';
import Autolinker from 'autolinker';
function htmlDecode(input) {
const e = document.createElement('div');
e.innerHTML = input;
return e.childNodes.length === 0 ? '' : e.childNodes[0].nodeValue;
}
function AutoLinker(message) {
message.html = htmlDecode(message.html);
if (RocketChat.settings.get('AutoLinker') !== true) {
return message;
}

@ -3,11 +3,18 @@ import _ from 'underscore';
import url from 'url';
import { Mongo } from 'meteor/mongo';
import tls from 'tls';
// FIX For TLS error see more here https://github.com/RocketChat/Rocket.Chat/issues/9316
// TODO: Remove after NodeJS fix it, more information https://github.com/nodejs/node/issues/16196 https://github.com/nodejs/node/pull/16853
tls.DEFAULT_ECDH_CURVE = 'auto';
// Revert change from Meteor 1.6.1 who set ignoreUndefined: true
// more information https://github.com/meteor/meteor/pull/9444
Mongo.setConnectionOptions({
ignoreUndefined: false
});
WebApp.rawConnectHandlers.use(Meteor.bindEnvironment(function(req, res, next) {
if (req._body) {
return next();

@ -8,7 +8,8 @@ Package.describe({
Package.onUse(function(api) {
api.use([
'ecmascript',
'webapp'
'webapp',
'mongo'
]);
api.addFiles('cors.js', 'server');

@ -223,6 +223,13 @@ export class CustomOAuth {
identity.id = identity.userid;
}
// Fix Nextcloud provider
if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
identity.id = identity.ocs.data.id;
identity.name = identity.ocs.data.display-name;
identity.email = identity.ocs.data.email;
}
// Fix when authenticating from a meteor app with 'emails' field
if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;

@ -3,6 +3,18 @@ class EmojiCustom extends RocketChat.models._Base {
super();
this._initModel('custom_emoji');
}
//find
findByNameOrAlias(name, options) {
const query = {
$or: [
{name},
{aliases: name}
]
};
return this.find(query, options);
}
}
RocketChat.models.EmojiCustom = new EmojiCustom();

@ -1,6 +1,6 @@
/* globals FileUpload, WebApp */
WebApp.connectHandlers.use(`${ __meteor_runtime_config__.ROOT_URL_PATH_PREFIX }/file-upload/`, function(req, res, next) {
WebApp.connectHandlers.use('/file-upload/', function(req, res, next) {
const match = /^\/([^\/]+)\/(.*)/.exec(req.url);

@ -147,6 +147,8 @@
"Accounts_SetDefaultAvatar": "Set Default Avatar",
"Accounts_SetDefaultAvatar_Description": "Tries to determine default avatar based on OAuth Account or Gravatar",
"Accounts_ShowFormLogin": "Show Form-Based Login",
"Accounts_TwoFactorAuthentication_MaxDelta": "Maximum Delta",
"Accounts_TwoFactorAuthentication_MaxDelta_Description": "The Maximum Delta determines how many tokens are valid at any given time. Tokens are generated every 30 seconds, and are valid for (30 * Maximum Delta) seconds. <br/>Example: With a Maximum Delta set to 10, each token can be used up to 300 seconds before or after it's timestamp. This is useful when the client's clock is not properly synced with the server.",
"Accounts_UseDefaultBlockedDomainsList": "Use Default Blocked Domains List",
"Accounts_UseDNSDomainCheck": "Use DNS Domain Check",
"Accounts_UserAddedEmail_Default": "<h2>Welcome to <h1>[Site_Name]</h1></h2><p>Go to <a href=\"[Site_URL]\">[Site_URL]</a> and try the best open source chat solution available today!</p><p>You may login using your email: [email] and password: [password]. You may be required to change it after your first login.",
@ -510,6 +512,9 @@
"Delete_my_account": "Delete my account",
"Delete_Room_Warning": "Deleting a room will delete all messages posted within the room. This cannot be undone.",
"Delete_User_Warning": "Deleting a user will delete all messages from that user as well. This cannot be undone.",
"Delete_User_Warning_Keep": "The user will be deleted, but their messages will remain visible. This cannot be undone.",
"Delete_User_Warning_Delete": "Deleting a user will delete all messages from that user as well. This cannot be undone.",
"Delete_User_Warning_Unlink": "Deleting a user will remove the user name from all their messages. This cannot be undone.",
"Deleted": "Deleted!",
"Department": "Department",
"Department_removed": "Department removed",
@ -551,6 +556,7 @@
"Display_offline_form": "Display Offline Form",
"Displays_action_text": "Displays action text",
"Do_not_display_unread_counter": "Do not display any counter of this channel",
"Do_you_want_to_accept": "Do you want to accept?",
"Do_you_want_to_change_to_s_question": "Do you want to change to <strong>%s</strong>?",
"Domain": "Domain",
"Domain_added": "domain Added",
@ -716,7 +722,6 @@
"Facebook_Page": "Facebook Page",
"False": "False",
"Favorite_Rooms": "Enable Favorite Rooms",
"Favorite": "Favorite",
"Favorites": "Favorites",
"Features_Enabled": "Features Enabled",
"Field": "Field",
@ -816,6 +821,7 @@
"GoogleVision_Type_SafeSearch": "SafeSearch Detection",
"GoogleVision_Type_Similar": "Search Similar Images",
"Group_by_Type": "Group by Type",
"Group_favorites": "Group favorites",
"Group_mentions_only": "Group mentions only",
"Guest_Pool": "Guest Pool",
"Hash": "Hash",
@ -1132,6 +1138,8 @@
"Lead_capture_email_regex": "Lead capture email regex",
"Lead_capture_phone_regex": "Lead capture phone regex",
"Least_Amount": "Least Amount",
"leave-c": "Leave Channels",
"leave-p": "Leave Private Groups",
"Leave_Group_Warning": "Are you sure you want to leave the group \"%s\"?",
"Leave_Livechat_Warning": "Are you sure you want to leave the livechat with \"%s\"?",
"Leave_Private_Warning": "Are you sure you want to leave the discussion with \"%s\"?",
@ -1263,14 +1271,21 @@
"Message_Attachments": "Message Attachments",
"Message_Attachments_GroupAttach": "Group Attachment Buttons",
"Message_Attachments_GroupAttachDescription": "This groups the icons under an expandable menu. Takes up less screen space.",
"Message_Audio": "Audio Message",
"Message_Audio_bitRate": "Audio Message Bit Rate",
"Message_AudioRecorderEnabled": "Audio Recorder Enabled",
"Message_AudioRecorderEnabledDescription": "Requires 'audio/wav' files to be an accepted media type within 'File Upload' settings.",
"Message_AudioRecorderEnabled_Description": "Requires 'audio/mp3' files to be an accepted media type within 'File Upload' settings.",
"Message_BadWordsFilterList": "Add Bad Words to the Blacklist",
"Message_BadWordsFilterListDescription": "Add List of Comma-separated list of bad words to filter",
"Message_DateFormat": "Date Format",
"Message_DateFormat_Description": "See also: <a href=\"http://momentjs.com/docs/#/displaying/format/\" target=\"momemt\">Moment.js</a>",
"Message_deleting_blocked": "This message cannot be deleted anymore",
"Message_editing": "Message editing",
"Message_ErasureType" : "Message Erasure Type",
"Message_ErasureType_Description" : "Determine what to do with messages of users who remove their account.",
"Message_ErasureType_Keep" : "Keep Messages and User Name",
"Message_ErasureType_Delete" : "Delete All Messages",
"Message_ErasureType_Unlink" : "Remove Link Between User and Messages",
"Message_GlobalSearch": "Global Search",
"Message_GroupingPeriod": "Grouping Period (in seconds)",
"Message_GroupingPeriodDescription": "Messages will be grouped with previous message if both are from the same user and the elapsed time was less than the informed time in seconds.",
@ -1425,6 +1440,7 @@
"Offline_unavailable": "Offline unavailable",
"On": "On",
"Online": "Online",
"online": "online",
"Only_authorized_users_can_write_new_messages": "Only authorized users can write new messages",
"Only_On_Desktop": "Desktop mode (only sends with enter on desktop)",
"Only_you_can_see_this_message": "Only you can see this message",
@ -1588,6 +1604,7 @@
"Remove_last_admin": "Removing last admin",
"Remove_someone_from_room": "Remove someone from the room",
"Removed": "Removed",
"Removed_User": "Removed User",
"Reply": "Reply",
"Report_Abuse": "Report Abuse",
"Report_exclamation_mark": "Report!",
@ -1709,6 +1726,8 @@
"Send_request_on_chat_close": "Send Request on Chat Close",
"Send_request_on_lead_capture": "Send request on lead capture",
"Send_request_on_offline_messages": "Send Request on Offline Messages",
"Send_request_on_visitor_message": "Send Request on Visitor Messages",
"Send_request_on_agent_message": "Send Request on Agent Messages",
"Send_Test": "Send Test",
"Send_welcome_email": "Send welcome email",
"Send_your_JSON_payloads_to_this_URL": "Send your JSON payloads to this URL.",
@ -1992,7 +2011,7 @@
"Unmute_user": "Unmute user",
"Unnamed": "Unnamed",
"Unpin_Message": "Unpin Message",
"Unread" : "Unread",
"Unread_on_top" : "Unread on top",
"Unread_Count": "Unread Count",
"Unread_Count_DM": "Unread Count for Direct Messages",
"Unread_Messages": "Unread Messages",
@ -2161,6 +2180,11 @@
"We_have_sent_registration_email": "We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.",
"Webhook_URL": "Webhook URL",
"Webhooks": "Webhooks",
"WebRTC_direct_audio_call_from_%s": "Direct audio call from %s",
"WebRTC_direct_video_call_from_%s": "Direct video call from %s",
"WebRTC_group_audio_call_from_%s": "Group audio call from %s",
"WebRTC_group_video_call_from_%s": "Group video call from %s",
"WebRTC_monitor_call_from_%s": "Monitor call from %s",
"WebRTC_Enable_Channel": "Enable for Public Channels",
"WebRTC_Enable_Direct": "Enable for Direct Messages",
"WebRTC_Enable_Private": "Enable for Private Channels",
@ -2218,4 +2242,4 @@
"your_message_optional": "your message (optional)",
"Your_password_is_wrong": "Your password is wrong!",
"Your_push_was_sent_to_s_devices": "Your push was sent to %s devices"
}
}

@ -1042,7 +1042,7 @@
"Only_On_Desktop": "Mode Bureau (envoyé seulement quand Entrée sur le bureau)",
"Only_you_can_see_this_message": "Vous seul pouvez voir ce message",
"Oops!": "Oups",
"Open": "Ouvrerture",
"Open": "Ouverture",
"Open_days_of_the_week": "Jours d'ouverture",
"Open_Livechats": "Ouvrir les chats en direct",
"Opened": "Ouvert",

@ -1033,6 +1033,8 @@
"Send_invitation_email_success": "Você enviou com sucesso um convite por e-mail para os seguintes endereços:",
"Send_request_on_chat_close": "Enviar requisição ao fechar conversa",
"Send_request_on_offline_messages": "Enviar requisição para mensagens off-line",
"Send_request_on_visitor_message": "Enviar requisição para mensagens do Visitante",
"Send_request_on_agent_message": "Enviar requisição para mensagens do Agente",
"Send_Test": "Enviar teste",
"Send_welcome_email": "Enviar e-mail de boas-vindas",
"Send_your_JSON_payloads_to_this_URL": "Envie seu payload JSON para esta URL.",

@ -26,7 +26,7 @@ export class Base {
* @static
*/
static getBSONSize(item) {
const { BSON } = require('bson').native();
const { BSON } = require('bson');
const bson = new BSON();
return bson.calculateObjectSize(item);
}

@ -103,25 +103,10 @@ Meteor.startup(function() {
action() {
const message = this._arguments[1];
const {input} = chatMessages[message.rid];
const url = RocketChat.MessageAction.getPermaLink(message._id);
const roomInfo = RocketChat.models.Rooms.findOne(message.rid, { fields: { t: 1 } });
let text = `[ ](${ url }) `;
let inputValue = '';
if (roomInfo.t !== 'd' && message.u.username !== Meteor.user().username) {
text += `@${ message.u.username } `;
}
if (input.value && !input.value.endsWith(' ')) {
inputValue += ' ';
}
inputValue += text;
$(input)
.focus()
.val(inputValue)
.trigger('change')
.trigger('input');
.data('reply', message)
.trigger('dataChange');
},
condition(message) {
if (RocketChat.models.Subscriptions.findOne({rid: message.rid}) == null) {

@ -44,7 +44,7 @@ RocketChat.API = {
return new Promise(function _rlRestApiGet(resolve, reject) {
jQuery.ajax({
method,
url: `${ Meteor.absoluteUrl() }api/${ endpoint }${ query }`,
url: `${ window.location.origin }/api/${ endpoint }${ query }`,
headers: {
'Content-Type': 'application/json',
'X-User-Id': localStorage['Meteor.userId'],

@ -99,7 +99,7 @@ class CachedCollection {
useSync = true,
useCache = true,
debug = false,
version = 6,
version = 7,
maxCacheTime = 60*60*24*30,
onSyncData = (/* action, record */) => {}
}) {

@ -128,6 +128,8 @@ Package.onUse(function(api) {
api.addFiles('server/models/Users.js', 'server');
api.addFiles('server/oauth/oauth.js', 'server');
api.addFiles('server/oauth/facebook.js', 'server');
api.addFiles('server/oauth/twitter.js', 'server');
api.addFiles('server/oauth/google.js', 'server');
api.addFiles('server/oauth/proxy.js', 'server');

@ -1,3 +1,3 @@
{
"version": "0.62.0-develop"
"version": "0.63.0-develop"
}

@ -1,30 +1,45 @@
RocketChat.deleteUser = function(userId) {
const user = RocketChat.models.Users.findOneById(userId);
RocketChat.models.Messages.removeByUserId(userId); // Remove user messages
RocketChat.models.Subscriptions.db.findByUserId(userId).forEach((subscription) => {
const room = RocketChat.models.Rooms.findOneById(subscription.rid);
if (room) {
if (room.t !== 'c' && room.usernames.length === 1) {
RocketChat.models.Rooms.removeById(subscription.rid); // Remove non-channel rooms with only 1 user (the one being deleted)
}
if (room.t === 'd') {
RocketChat.models.Subscriptions.removeByRoomId(subscription.rid);
RocketChat.models.Messages.removeByRoomId(subscription.rid);
}
// Users without username can't do anything, so there is nothing to remove
if (user.username != null) {
const messageErasureType = RocketChat.settings.get('Message_ErasureType');
switch (messageErasureType) {
case 'Delete' :
RocketChat.models.Messages.removeByUserId(userId);
break;
case 'Unlink' :
const rocketCat = RocketChat.models.Users.findById('rocket.cat').fetch()[0];
const nameAlias = TAPi18n.__('Removed_User');
RocketChat.models.Messages.unlinkUserId(userId, rocketCat._id, rocketCat.username, nameAlias);
break;
}
});
RocketChat.models.Subscriptions.removeByUserId(userId); // Remove user subscriptions
RocketChat.models.Rooms.removeByTypeContainingUsername('d', user.username); // Remove direct rooms with the user
RocketChat.models.Rooms.removeUsernameFromAll(user.username); // Remove user from all other rooms
RocketChat.models.Subscriptions.db.findByUserId(userId).forEach((subscription) => {
const room = RocketChat.models.Rooms.findOneById(subscription.rid);
if (room) {
if (room.t !== 'c' && room.usernames.length === 1) {
RocketChat.models.Rooms.removeById(subscription.rid); // Remove non-channel rooms with only 1 user (the one being deleted)
}
if (room.t === 'd') {
RocketChat.models.Subscriptions.removeByRoomId(subscription.rid);
RocketChat.models.Messages.removeByRoomId(subscription.rid);
}
}
});
RocketChat.models.Subscriptions.removeByUserId(userId); // Remove user subscriptions
RocketChat.models.Rooms.removeByTypeContainingUsername('d', user.username); // Remove direct rooms with the user
RocketChat.models.Rooms.removeUsernameFromAll(user.username); // Remove user from all other rooms
// removes user's avatar
if (user.avatarOrigin === 'upload' || user.avatarOrigin === 'url') {
FileUpload.getStore('Avatars').deleteByName(user.username);
}
// removes user's avatar
if (user.avatarOrigin === 'upload' || user.avatarOrigin === 'url') {
FileUpload.getStore('Avatars').deleteByName(user.username);
}
RocketChat.models.Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted.
RocketChat.models.Integrations.disableByUserId(userId); // Disables all the integrations which rely on the user being deleted.
}
RocketChat.models.Users.removeById(userId); // Remove user from users database
};

@ -7,27 +7,45 @@ RocketChat.saveUser = function(userId, userData) {
const existingRoles = _.pluck(RocketChat.authz.getRoles(), '_id');
if (userData._id && userId !== userData._id && !RocketChat.authz.hasPermission(userId, 'edit-other-user-info')) {
throw new Meteor.Error('error-action-not-allowed', 'Editing user is not allowed', { method: 'insertOrUpdateUser', action: 'Editing_user' });
throw new Meteor.Error('error-action-not-allowed', 'Editing user is not allowed', {
method: 'insertOrUpdateUser',
action: 'Editing_user'
});
}
if (!userData._id && !RocketChat.authz.hasPermission(userId, 'create-user')) {
throw new Meteor.Error('error-action-not-allowed', 'Adding user is not allowed', { method: 'insertOrUpdateUser', action: 'Adding_user' });
throw new Meteor.Error('error-action-not-allowed', 'Adding user is not allowed', {
method: 'insertOrUpdateUser',
action: 'Adding_user'
});
}
if (userData.roles && _.difference(userData.roles, existingRoles).length > 0) {
throw new Meteor.Error('error-action-not-allowed', 'The field Roles consist invalid role name', { method: 'insertOrUpdateUser', action: 'Assign_role' });
throw new Meteor.Error('error-action-not-allowed', 'The field Roles consist invalid role name', {
method: 'insertOrUpdateUser',
action: 'Assign_role'
});
}
if (userData.roles && _.indexOf(userData.roles, 'admin') >= 0 && !RocketChat.authz.hasPermission(userId, 'assign-admin-role')) {
throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', { method: 'insertOrUpdateUser', action: 'Assign_admin' });
throw new Meteor.Error('error-action-not-allowed', 'Assigning admin is not allowed', {
method: 'insertOrUpdateUser',
action: 'Assign_admin'
});
}
if (!userData._id && !s.trim(userData.name)) {
throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', { method: 'insertOrUpdateUser', field: 'Name' });
throw new Meteor.Error('error-the-field-is-required', 'The field Name is required', {
method: 'insertOrUpdateUser',
field: 'Name'
});
}
if (!userData._id && !s.trim(userData.username)) {
throw new Meteor.Error('error-the-field-is-required', 'The field Username is required', { method: 'insertOrUpdateUser', field: 'Username' });
throw new Meteor.Error('error-the-field-is-required', 'The field Username is required', {
method: 'insertOrUpdateUser',
field: 'Username'
});
}
let nameValidation;
@ -39,20 +57,33 @@ RocketChat.saveUser = function(userId, userData) {
}
if (userData.username && !nameValidation.test(userData.username)) {
throw new Meteor.Error('error-input-is-not-a-valid-field', `${ _.escape(userData.username) } is not a valid username`, { method: 'insertOrUpdateUser', input: userData.username, field: 'Username' });
throw new Meteor.Error('error-input-is-not-a-valid-field', `${ _.escape(userData.username) } is not a valid username`, {
method: 'insertOrUpdateUser',
input: userData.username,
field: 'Username'
});
}
if (!userData._id && !userData.password) {
throw new Meteor.Error('error-the-field-is-required', 'The field Password is required', { method: 'insertOrUpdateUser', field: 'Password' });
throw new Meteor.Error('error-the-field-is-required', 'The field Password is required', {
method: 'insertOrUpdateUser',
field: 'Password'
});
}
if (!userData._id) {
if (!RocketChat.checkUsernameAvailability(userData.username)) {
throw new Meteor.Error('error-field-unavailable', `${ _.escape(userData.username) } is already in use :(`, { method: 'insertOrUpdateUser', field: userData.username });
throw new Meteor.Error('error-field-unavailable', `${ _.escape(userData.username) } is already in use :(`, {
method: 'insertOrUpdateUser',
field: userData.username
});
}
if (userData.email && !RocketChat.checkEmailAvailability(userData.email)) {
throw new Meteor.Error('error-field-unavailable', `${ _.escape(userData.email) } is already in use :(`, { method: 'insertOrUpdateUser', field: userData.email });
throw new Meteor.Error('error-field-unavailable', `${ _.escape(userData.email) } is already in use :(`, {
method: 'insertOrUpdateUser',
field: userData.email
});
}
RocketChat.validateEmailDomain(userData.email);
@ -73,7 +104,7 @@ RocketChat.saveUser = function(userId, userData) {
$set: {
name: userData.name,
roles: userData.roles || ['user'],
settings: userData.settings
settings: userData.settings || {}
}
};
@ -81,8 +112,8 @@ RocketChat.saveUser = function(userId, userData) {
updateUser.$set.requirePasswordChange = userData.requirePasswordChange;
}
if (userData.verified) {
updateUser.$set['emails.0.verified'] = true;
if (typeof userData.verified === 'boolean') {
updateUser.$set['emails.0.verified'] = userData.verified;
}
Meteor.users.update({ _id }, updateUser);
@ -120,7 +151,10 @@ RocketChat.saveUser = function(userId, userData) {
try {
Email.send(email);
} catch (error) {
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, { function: 'RocketChat.saveUser', message: error.message });
throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${ error.message }`, {
function: 'RocketChat.saveUser',
message: error.message
});
}
});
}
@ -128,7 +162,7 @@ RocketChat.saveUser = function(userId, userData) {
userData._id = _id;
if (RocketChat.settings.get('Accounts_SetDefaultAvatar') === true && userData.email) {
const gravatarUrl = Gravatar.imageUrl(userData.email, {default: '404', size: 200, secure: true});
const gravatarUrl = Gravatar.imageUrl(userData.email, { default: '404', size: 200, secure: true });
try {
RocketChat.setUserAvatar(userData, gravatarUrl, '', 'url');
@ -149,7 +183,8 @@ RocketChat.saveUser = function(userId, userData) {
}
if (userData.email) {
RocketChat.setEmail(userData._id, userData.email);
const shouldSendVerificationEmailToUser = userData.verified !== true;
RocketChat.setEmail(userData._id, userData.email, shouldSendVerificationEmailToUser);
}
if (userData.password && userData.password.trim() && RocketChat.authz.hasPermission(userId, 'edit-other-user-password')) {
@ -172,7 +207,9 @@ RocketChat.saveUser = function(userId, userData) {
updateUser.$set.requirePasswordChange = userData.requirePasswordChange;
}
updateUser.$set['emails.0.verified'] = !!userData.verified;
if (typeof userData.verified === 'boolean') {
updateUser.$set['emails.0.verified'] = userData.verified;
}
Meteor.users.update({ _id: userData._id }, updateUser);

@ -4,22 +4,27 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) {
if (!user || !message || !room._id) {
return false;
}
if (message.ts == null) {
message.ts = new Date();
}
message.u = _.pick(user, ['_id', 'username', 'name']);
if (!Match.test(message.msg, String)) {
message.msg = '';
}
if (message.ts == null) {
message.ts = new Date();
}
message.rid = room._id;
message.u = _.pick(user, ['_id', 'username', 'name']);
if (!room.usernames || room.usernames.length === 0) {
const updated_room = RocketChat.models.Rooms.findOneById(room._id);
if (updated_room != null) {
if (updated_room) {
room = updated_room;
} else {
room.usernames = [];
}
}
if (message.parseUrls !== false) {
const urls = message.msg.match(/([A-Za-z]{3,9}):\/\/([-;:&=\+\$,\w]+@{1})?([-A-Za-z0-9\.]+)+:?(\d+)?((\/[-\+=!:~%\/\.@\,\(\)\w]*)?\??([-\+=&!:;%@\/\.\,\w]+)?(?:#([^\s\)]+))?)?/g);
@ -44,6 +49,17 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) {
sandstormSessionId = message.sandstormSessionId;
delete message.sandstormSessionId;
}
// For the Rocket.Chat Apps :)
if (Apps && Apps.isLoaded()) {
const prevent = Apps.getBridges().getListenerBridge().messageEvent('IPreMessageSentPrevent', message);
if (prevent) {
return false;
}
// TODO: The rest of the IPreMessageSent events
}
if (message._id && upsert) {
const _id = message._id;
delete message._id;
@ -56,6 +72,10 @@ RocketChat.sendMessage = function(user, message, room, upsert = false) {
message._id = RocketChat.models.Messages.insert(message);
}
if (Apps && Apps.isLoaded()) {
Apps.getBridges().getListenerBridge().messageEvent('IPostMessageSent', message);
}
/*
Defer other updates as their return is not interesting to the user
*/

@ -1,6 +1,6 @@
import s from 'underscore.string';
RocketChat._setEmail = function(userId, email) {
RocketChat._setEmail = function(userId, email, shouldSendVerificationEmail = true) {
email = s.trim(email);
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { function: '_setEmail' });
@ -27,6 +27,9 @@ RocketChat._setEmail = function(userId, email) {
// Set new email
RocketChat.models.Users.setEmail(user._id, email);
user.email = email;
if (shouldSendVerificationEmail === true) {
Meteor.call('sendConfirmationEmail', user.email);
}
return user;
};

@ -12,7 +12,7 @@ Meteor.methods({
const room = RocketChat.models.Rooms.findOneById(rid);
const user = Meteor.user();
if (room.t === 'd') {
if (room.t === 'd' || (room.t === 'c' && !RocketChat.authz.hasPermission(user._id, 'leave-c')) || (room.t === 'p' && !RocketChat.authz.hasPermission(user._id, 'leave-p'))) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'leaveRoom' });
}

@ -44,7 +44,7 @@ Meteor.methods({
}
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId());
if (subscription && subscription.blocked || subscription.blocker) {
if (subscription && (subscription.blocked || subscription.blocker)) {
RocketChat.Notifications.notifyUser(Meteor.userId(), 'message', {
_id: Random.id(),
rid: room._id,

@ -500,6 +500,22 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base {
return this.update(query, update);
}
unlinkUserId(userId, newUserId, newUsername, newNameAlias) {
const query = {
'u._id': userId
};
const update = {
$set: {
'alias': newNameAlias,
'u._id': newUserId,
'u.username' : newUsername,
'u.name' : undefined
}
};
return this.update(query, update, { multi: true });
}
// INSERT
createWithTypeRoomIdMessageAndUser(type, roomId, message, user, extraData) {

@ -246,6 +246,16 @@ class ModelRooms extends RocketChat.models._Base {
return this.find(query, options);
}
findByNameAndType(name, type, options) {
const query = {
t: type,
name
};
// do not use cache
return this._db.find(query, options);
}
findByNameAndTypeNotDefault(name, type, options) {
const query = {
t: type,

@ -30,16 +30,16 @@ class ModelSubscriptions extends RocketChat.models._Base {
// FIND ONE
findOneByRoomIdAndUserId(roomId, userId) {
findOneByRoomIdAndUserId(roomId, userId, options) {
if (this.useCache) {
return this.cache.findByIndex('rid,u._id', [roomId, userId]).fetch();
return this.cache.findByIndex('rid,u._id', [roomId, userId], options).fetch();
}
const query = {
rid: roomId,
'u._id': userId
};
return this.findOne(query);
return this.findOne(query, options);
}
findOneByRoomNameAndUserId(roomName, userId) {
@ -61,7 +61,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
}
const query =
{'u._id': userId};
{ 'u._id': userId };
return this.find(query, options);
}
@ -123,7 +123,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
}
const query =
{rid: roomId};
{ rid: roomId };
return this.find(query, options);
}
@ -140,7 +140,9 @@ class ModelSubscriptions extends RocketChat.models._Base {
}
getLastSeen(options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
const query = { ls: { $exists: 1 } };
options.sort = { ls: -1 };
options.limit = 1;
@ -198,7 +200,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
// UPDATE
archiveByRoomId(roomId) {
const query =
{rid: roomId};
{ rid: roomId };
const update = {
$set: {
@ -213,7 +215,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
unarchiveByRoomId(roomId) {
const query =
{rid: roomId};
{ rid: roomId };
const update = {
$set: {
@ -311,7 +313,9 @@ class ModelSubscriptions extends RocketChat.models._Base {
}
setFavoriteByRoomIdAndUserId(roomId, userId, favorite) {
if (favorite == null) { favorite = true; }
if (favorite == null) {
favorite = true;
}
const query = {
rid: roomId,
'u._id': userId
@ -328,7 +332,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
updateNameAndAlertByRoomId(roomId, name, fname) {
const query =
{rid: roomId};
{ rid: roomId };
const update = {
$set: {
@ -343,7 +347,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
updateNameByRoomId(roomId, name) {
const query =
{rid: roomId};
{ rid: roomId };
const update = {
$set: {
@ -356,7 +360,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
setUserUsernameByUserId(userId, username) {
const query =
{'u._id': userId};
{ 'u._id': userId };
const update = {
$set: {
@ -383,7 +387,9 @@ class ModelSubscriptions extends RocketChat.models._Base {
}
incUnreadForRoomIdExcludingUserId(roomId, userId, inc) {
if (inc == null) { inc = 1; }
if (inc == null) {
inc = 1;
}
const query = {
rid: roomId,
'u._id': {
@ -447,6 +453,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
return this.update(query, update, { multi: true });
}
setAlertForRoomIdExcludingUserId(roomId, userId) {
const query = {
rid: roomId,
@ -522,7 +529,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
updateTypeByRoomId(roomId, type) {
const query =
{rid: roomId};
{ rid: roomId };
const update = {
$set: {
@ -535,7 +542,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
addRoleById(_id, role) {
const query =
{_id};
{ _id };
const update = {
$addToSet: {
@ -548,7 +555,7 @@ class ModelSubscriptions extends RocketChat.models._Base {
removeRoleById(_id, role) {
const query =
{_id};
{ _id };
const update = {
$pull: {
@ -604,14 +611,14 @@ class ModelSubscriptions extends RocketChat.models._Base {
// REMOVE
removeByUserId(userId) {
const query =
{'u._id': userId};
{ 'u._id': userId };
return this.remove(query);
}
removeByRoomId(roomId) {
const query =
{rid: roomId};
{ rid: roomId };
return this.remove(query);
}

@ -435,6 +435,16 @@ class ModelUsers extends RocketChat.models._Base {
return this.update(_id, update);
}
clearSettings(_id) {
const update = {
$set: {
settings: {}
}
};
return this.update(_id, update);
}
setPreferences(_id, preferences) {
const settings = Object.assign(
{},

@ -0,0 +1,63 @@
import _ from 'underscore';
import { OAuth } from 'meteor/oauth';
const crypto = Npm.require('crypto');
const whitelisted = [
'id',
'email',
'name',
'first_name',
'last_name',
'link',
'gender',
'locale',
'age_range'];
const FB_API_VERSION = 'v2.9';
const FB_URL = 'https://graph.facebook.com';
const getIdentity = function(accessToken, fields, secret) {
const hmac = crypto.createHmac('sha256', OAuth.openSecret(secret));
hmac.update(accessToken);
try {
return HTTP.get(`${ FB_URL }/${ FB_API_VERSION }/me`, {
params: {
access_token: accessToken,
appsecret_proof: hmac.digest('hex'),
fields: fields.join(',')
}
}).data;
} catch (err) {
throw _.extend(new Error(`Failed to fetch identity from Facebook. ${ err.message }`),
{response: err.response});
}
};
RocketChat.registerAccessTokenService('facebook', function(options) {
check(options, Match.ObjectIncluding({
accessToken: String,
secret: String,
expiresIn: Match.Integer,
identity: Match.Maybe(Object)
}));
const identity = options.identity || getIdentity(options.accessToken, whitelisted, options.secret);
const serviceData = {
accessToken: options.accessToken,
expiresAt: (+new Date) + (1000 * parseInt(options.expiresIn, 10))
};
const fields = _.pick(identity, whitelisted);
_.extend(serviceData, fields);
return {
serviceData,
options: {
profile: {
name: identity.name
}
}
};
});

@ -0,0 +1,58 @@
import Twit from 'twit';
import _ from 'underscore';
const whitelistedFields = [
'id',
'name',
'description',
'profile_image_url',
'profile_image_url_https',
'lang',
'email'
];
const getIdentity = function(accessToken, appId, appSecret, accessTokenSecret) {
const Twitter = new Twit({
consumer_key: appId,
consumer_secret: appSecret,
access_token: accessToken,
access_token_secret: accessTokenSecret
});
const syncTwitter = Meteor.wrapAsync(Twitter.get, Twitter);
try {
return syncTwitter('account/verify_credentials.json?include_email=true');
} catch (err) {
throw _.extend(new Error(`Failed to fetch identity from Twwiter. ${ err.message }`),
{response: err.response});
}
};
RocketChat.registerAccessTokenService('twitter', function(options) {
check(options, Match.ObjectIncluding({
accessToken: String,
appSecret: String,
appId: String,
accessTokenSecret: String,
expiresIn: Match.Integer,
identity: Match.Maybe(Object)
}));
const identity = options.identity || getIdentity(options.accessToken, options.appId, options.appSecret, options.accessTokenSecret);
const serviceData = {
accessToken: options.accessToken,
expiresAt: (+new Date) + (1000 * parseInt(options.expiresIn, 10))
};
const fields = _.pick(identity, whitelistedFields);
_.extend(serviceData, fields);
return {
serviceData,
options: {
profile: {
name: identity.name
}
}
};
});

@ -83,6 +83,14 @@ RocketChat.settings.addGroup('Accounts', function() {
public: true
});
this.section('Two Factor Authentication', function() {
this.add('Accounts_TwoFactorAuthentication_MaxDelta', 1, {
type: 'int',
public: true,
i18nLabel: 'Accounts_TwoFactorAuthentication_MaxDelta'
});
});
this.section('Registration', function() {
this.add('Accounts_DefaultUsernamePrefixSuggestion', 'user', {
type: 'string'
@ -310,48 +318,58 @@ RocketChat.settings.addGroup('Accounts', function() {
'public': true,
i18nLabel: 'Sidebar_list_mode'
});
this.add('Accounts_Default_User_Preferences_mergeChannels', false, {
type: 'boolean',
'public': true,
i18nLabel: 'UI_Merge_Channels_Groups'
});
this.add('Accounts_Default_User_Preferences_sendOnEnter', 'normal', {
this.add('Accounts_Default_User_Preferences_sidebarViewMode', 'medium', {
type: 'select',
values: [
{
key: 'normal',
i18nLabel: 'Enter_Normal'
key: 'extended',
i18nLabel: 'Extended'
},
{
key: 'alternative',
i18nLabel: 'Enter_Alternative'
key: 'medium',
i18nLabel: 'Medium'
},
{
key: 'desktop',
i18nLabel: 'Only_On_Desktop'
key: 'condensed',
i18nLabel: 'Condensed'
}
],
'public': true,
i18nLabel: 'Enter_Behaviour'
i18nLabel: 'Sidebar_list_mode'
});
this.add('Accounts_Default_User_Preferences_sidebarHideAvatar', false, {
type: 'boolean',
'public': true,
i18nLabel: 'Hide_Avatars'
});
this.add('Accounts_Default_User_Preferences_sidebarShowUnread', false, {
type: 'boolean',
'public': true,
i18nLabel: 'Unread_on_top'
});
this.add('Accounts_Default_User_Preferences_viewMode', 0, {
this.add('Accounts_Default_User_Preferences_sidebarShowFavorites', true, {
type: 'boolean',
'public': true,
i18nLabel: 'Group_favorites'
});
this.add('Accounts_Default_User_Preferences_sendOnEnter', 'normal', {
type: 'select',
values: [
{
key: 0,
i18nLabel: 'Normal'
key: 'normal',
i18nLabel: 'Enter_Normal'
},
{
key: 1,
i18nLabel: 'Cozy'
key: 'alternative',
i18nLabel: 'Enter_Alternative'
},
{
key: 2,
i18nLabel: 'Compact'
key: 'desktop',
i18nLabel: 'Only_On_Desktop'
}
],
'public': true,
i18nLabel: 'View_mode'
i18nLabel: 'Enter_Behaviour'
});
this.add('Accounts_Default_User_Preferences_emailNotificationMode', 'all', {
type: 'select',
@ -1177,11 +1195,17 @@ RocketChat.settings.addGroup('Message', function() {
'public': true,
i18nDescription: 'Message_Attachments_GroupAttachDescription'
});
});
this.section('Message_Audio', function() {
this.add('Message_AudioRecorderEnabled', true, {
type: 'boolean',
'public': true,
i18nDescription: 'Message_AudioRecorderEnabledDescription'
});
this.add('Message_Audio_bitRate', 32, {
type: 'int',
'public': true
});
});
this.add('Message_AllowEditing', true, {
type: 'boolean',
@ -1329,6 +1353,23 @@ RocketChat.settings.addGroup('Message', function() {
'public': true,
alert: 'This feature is currently in beta and could decrease the application performance! Please report bugs to github.com/RocketChat/Rocket.Chat/issues'
});
this.add('Message_ErasureType', 'Delete', {
type: 'select',
'public': true,
values: [
{
key: 'Keep',
i18nLabel: 'Message_ErasureType_Keep'
}, {
key: 'Delete',
i18nLabel: 'Message_ErasureType_Delete'
}, {
key: 'Unlink',
i18nLabel: 'Message_ErasureType_Unlink'
}
]
});
});
RocketChat.settings.addGroup('Meta', function() {

@ -1,8 +1,8 @@
<template name="loading">
<div class="loading-animation">
{{_ "Connecting to an Agent"}}
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
<div class="bounce bounce1"></div>
<div class="bounce bounce2"></div>
<div class="bounce bounce3"></div>
</div>
</template>
</template>

@ -79,7 +79,9 @@ Template.messages.helpers({
agentData.email = agent.emails[0].address;
}
if (agent.customFields && agent.customFields.phone) {
if (agent.phone && agent.phone.length > 0) {
agentData.phone = agent.phone[0].phoneNumber;
} else if (agent.customFields && agent.customFields.phone) {
agentData.phone = agent.customFields.phone;
}

@ -32,6 +32,18 @@
{{_ "Send_request_on_offline_messages"}}
</label>
</div>
<div class="input-line">
<label for="sendOnVisitorMessage">
<input type="checkbox" name="sendOnVisitorMessage" id="sendOnVisitorMessage" value="1" checked="{{sendOnVisitorMessageChecked}}">
{{_ "Send_request_on_visitor_message"}}
</label>
</div>
<div class="input-line">
<label for="sendOnAgentMessage">
<input type="checkbox" name="sendOnAgentMessage" id="sendOnAgentMessage" value="1" checked="{{sendOnAgentMessageChecked}}">
{{_ "Send_request_on_agent_message"}}
</label>
</div>
<div class="submit">
<button class="button danger reset-settings" type="button"><i class="icon-ccw"></i>{{_ "Reset"}}</button>
<button class="button secondary test" type="button" disabled="{{disableTest}}">{{_ "Send_Test"}}</button>

@ -22,6 +22,14 @@ Template.livechatIntegrationWebhook.helpers({
sendOnOfflineChecked() {
const setting = LivechatIntegration.findOne('Livechat_webhook_on_offline_msg');
return setting && setting.value;
},
sendOnVisitorMessageChecked() {
const setting = LivechatIntegration.findOne('Livechat_webhook_on_visitor_message');
return setting && setting.value;
},
sendOnAgentMessageChecked() {
const setting = LivechatIntegration.findOne('Livechat_webhook_on_agent_message');
return setting && setting.value;
}
});
@ -62,11 +70,15 @@ Template.livechatIntegrationWebhook.events({
const secretToken = LivechatIntegration.findOne('Livechat_secret_token');
const webhookOnClose = LivechatIntegration.findOne('Livechat_webhook_on_close');
const webhookOnOfflineMsg = LivechatIntegration.findOne('Livechat_webhook_on_offline_msg');
const webhookOnVisitorMessage = LivechatIntegration.findOne('Livechat_webhook_on_visitor_message');
const webhookOnAgentMessage = LivechatIntegration.findOne('Livechat_webhook_on_agent_message');
instance.$('#webhookUrl').val(webhookUrl && webhookUrl.value);
instance.$('#secretToken').val(secretToken && secretToken.value);
instance.$('#sendOnClose').get(0).checked = webhookOnClose && webhookOnClose.value;
instance.$('#sendOnOffline').get(0).checked = webhookOnOfflineMsg && webhookOnOfflineMsg.value;
instance.$('#sendOnVisitorMessage').get(0).checked = webhookOnVisitorMessage && webhookOnVisitorMessage.value;
instance.$('#sendOnAgentMessage').get(0).checked = webhookOnAgentMessage && webhookOnAgentMessage.value;
instance.disableTest.set(!webhookUrl || _.isEmpty(webhookUrl.value));
},
@ -77,7 +89,9 @@ Template.livechatIntegrationWebhook.events({
'Livechat_webhookUrl': s.trim(instance.$('#webhookUrl').val()),
'Livechat_secret_token': s.trim(instance.$('#secretToken').val()),
'Livechat_webhook_on_close': instance.$('#sendOnClose').get(0).checked,
'Livechat_webhook_on_offline_msg': instance.$('#sendOnOffline').get(0).checked
'Livechat_webhook_on_offline_msg': instance.$('#sendOnOffline').get(0).checked,
'Livechat_webhook_on_visitor_message': instance.$('#sendOnVisitorMessage').get(0).checked,
'Livechat_webhook_on_agent_message': instance.$('#sendOnAgentMessage').get(0).checked
};
Meteor.call('livechat:saveIntegration', settings, (err) => {
if (err) {

@ -32,6 +32,18 @@
{{_ "Send_request_on_offline_messages"}}
</label>
</div>
<div class="input-line">
<label for="sendOnVisitorMessage">
<input type="checkbox" name="sendOnVisitorMessage" id="sendOnVisitorMessage" value="1" checked="{{sendOnVisitorMessageChecked}}">
{{_ "Send_request_on_visitor_message"}}
</label>
</div>
<div class="input-line">
<label for="sendOnAgentMessage">
<input type="checkbox" name="sendOnAgentMessage" id="sendOnAgentMessage" value="1" checked="{{sendOnAgentMessageChecked}}">
{{_ "Send_request_on_agent_message"}}
</label>
</div>
<div class="submit">
<button class="button danger reset-settings" type="button"><i class="icon-ccw"></i>{{_ "Reset"}}</button>
<button class="button secondary test" type="button" disabled="{{disableTest}}">{{_ "Send_Test"}}</button>

@ -131,6 +131,20 @@ Meteor.startup(function() {
i18nLabel: 'Send_request_on_offline_messages'
});
RocketChat.settings.add('Livechat_webhook_on_visitor_message', false, {
type: 'boolean',
group: 'Livechat',
section: 'CRM_Integration',
i18nLabel: 'Send_request_on_visitor_message'
});
RocketChat.settings.add('Livechat_webhook_on_agent_message', false, {
type: 'boolean',
group: 'Livechat',
section: 'CRM_Integration',
i18nLabel: 'Send_request_on_agent_message'
});
RocketChat.settings.add('Livechat_webhook_on_capture', false, {
type: 'boolean',
group: 'Livechat',

@ -25,7 +25,7 @@ class LivechatRoomType extends RoomTypeConfig {
super({
identifier: 'l',
order: 5,
icon: 'livechat',
// icon: 'livechat',
label: 'Livechat',
route: new LivechatRoomRoute()
});

@ -5,15 +5,24 @@ function sendToCRM(type, room, includeMessages = true) {
postData.messages = [];
if (includeMessages) {
RocketChat.models.Messages.findVisibleByRoomId(room._id, { sort: { ts: 1 } }).forEach((message) => {
let messages;
if (typeof includeMessages === 'boolean' && includeMessages) {
messages = RocketChat.models.Messages.findVisibleByRoomId(room._id, { sort: { ts: 1 } });
} else if (includeMessages instanceof Array) {
messages = includeMessages;
}
if (messages) {
messages.forEach((message) => {
if (message.t) {
return;
}
const msg = {
_id: message._id,
username: message.u.username,
msg: message.msg,
ts: message.ts
ts: message.ts,
editedAt: message.editedAt
};
if (message.u.username !== postData.visitor.username) {
@ -49,6 +58,31 @@ RocketChat.callbacks.add('livechat.saveInfo', (room) => {
return sendToCRM('LivechatEdit', room);
}, RocketChat.callbacks.priority.MEDIUM, 'livechat-send-crm-save-info');
RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
// only call webhook if it is a livechat room
if (room.t !== 'l' || room.v == null || room.v.token == null) {
return message;
}
// if the message has a token, it was sent from the visitor
// if not, it was sent from the agent
if (message.token) {
if (!RocketChat.settings.get('Livechat_webhook_on_visitor_message')) {
return message;
}
} else if (!RocketChat.settings.get('Livechat_webhook_on_agent_message')) {
return message;
}
// if the message has a type means it is a special message (like the closing comment), so skips
if (message.t) {
return message;
}
sendToCRM('Message', room, [message]);
return message;
}, RocketChat.callbacks.priority.MEDIUM, 'livechat-send-crm-message');
RocketChat.callbacks.add('livechat.leadCapture', (room) => {
if (!RocketChat.settings.get('Livechat_webhook_on_capture')) {
return room;

@ -415,6 +415,7 @@ RocketChat.Livechat = {
customFields: room.livechatData,
visitor: {
_id: visitor._id,
token: visitor.token,
name: visitor.name,
username: visitor.username,
email: null,

@ -22,6 +22,14 @@ Meteor.methods({
RocketChat.settings.updateById('Livechat_webhook_on_offline_msg', !!values['Livechat_webhook_on_offline_msg']);
}
if (typeof values['Livechat_webhook_on_visitor_message'] !== 'undefined') {
RocketChat.settings.updateById('Livechat_webhook_on_visitor_message', !!values['Livechat_webhook_on_visitor_message']);
}
if (typeof values['Livechat_webhook_on_agent_message'] !== 'undefined') {
RocketChat.settings.updateById('Livechat_webhook_on_agent_message', !!values['Livechat_webhook_on_agent_message']);
}
return;
}
});

@ -167,6 +167,7 @@ RocketChat.models.Users.getAgentInfo = function(agentId) {
fields: {
name: 1,
username: 1,
phone: 1,
customFields: 1
}
};

@ -9,7 +9,7 @@ Meteor.publish('livechat:integration', function() {
const self = this;
const handle = RocketChat.models.Settings.findByIds(['Livechat_webhookUrl', 'Livechat_secret_token', 'Livechat_webhook_on_close', 'Livechat_webhook_on_offline_msg']).observeChanges({
const handle = RocketChat.models.Settings.findByIds(['Livechat_webhookUrl', 'Livechat_secret_token', 'Livechat_webhook_on_close', 'Livechat_webhook_on_offline_msg', 'Livechat_webhook_on_visitor_message', 'Livechat_webhook_on_agent_message']).observeChanges({
added(id, fields) {
self.added('livechatIntegration', id, fields);
},

@ -12,5 +12,6 @@ Package.onUse(function(api) {
]);
api.addFiles('server/server.js', 'server');
api.addFiles('server/methods/getUserMentionsByChannel.js', 'server');
api.addFiles('client/client.js', 'client');
});

@ -0,0 +1,19 @@
Meteor.methods({
getUserMentionsByChannel({ roomId, options }) {
check(roomId, String);
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'getUserMentionsByChannel' });
}
const room = RocketChat.models.Rooms.findOneById(roomId);
if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'getUserMentionsByChannel' });
}
const user = RocketChat.models.Users.findOneById(Meteor.userId());
return RocketChat.models.Messages.findVisibleByMentionAndRoomId(user.username, roomId, options).fetch();
}
});

@ -10,9 +10,9 @@
{{#if author_icon}}
<img src="{{fixCordova author_icon}}">
{{/if}}
<a href="{{fixCordova author_link}}" target="_blank">{{author_name}}</a>
<a href="{{fixCordova author_link}}" target="_blank" rel="noopener noreferrer">{{author_name}}</a>
{{#if ts}}
<span class="time">{{#if message_link}}<a href="{{message_link}}">{{time}}</a>{{else}}{{time}}{{/if}}</span>
<span class="time">{{#if message_link}}<a href="{{message_link}}" rel="noopener noreferrer">{{time}}</a>{{else}}{{time}}{{/if}}</span>
{{/if}}
</div>
{{else}}
@ -22,7 +22,7 @@
{{/if}}
{{author_name}}
{{#if ts}}
<span class="time">{{#if message_link}}<a href="{{message_link}}">{{time}}</a>{{else}}{{time}}{{/if}}</span>
<span class="time">{{#if message_link}}<a href="{{message_link}}" rel="noopener noreferrer">{{time}}</a>{{else}}{{time}}{{/if}}</span>
{{/if}}
</div>
{{/if}}
@ -30,9 +30,9 @@
{{#if title}}
<div class="attachment-title">
{{#if title_link}}
<a href="{{fixCordova title_link}}" target="_blank">{{#if isFile}} {{_ "Attachment_File_Uploaded"}}: {{/if}}{{title}}</a>
<a href="{{fixCordova title_link}}" target="_blank" rel="noopener noreferrer">{{#if isFile}} {{_ "Attachment_File_Uploaded"}}: {{/if}}{{title}}</a>
{{#if title_link_download}}
<a class="icon-download attachment-download-icon" href="{{fixCordova title_link}}" target="_blank" download=""></a>
<a class="icon-download attachment-download-icon" href="{{fixCordova title_link}}" target="_blank" download="" rel="noopener noreferrer"></a>
{{/if}}
{{else}}
{{title}}

@ -22,7 +22,7 @@ const fixCordova = function(url) {
} else if (navigator.userAgent.indexOf('Electron') > -1) {
return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
} else {
return Meteor.absoluteUrl().replace(/\/$/, '') + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
return Meteor.absoluteUrl().replace(/\/$/, '') + url;
}
};
/*globals renderMessageBody*/

@ -1,49 +1,59 @@
Meteor.methods({
saveNotificationSettings(rid, field, value) {
saveNotificationSettings(roomId, field, value) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'saveNotificationSettings' });
}
check(rid, String);
check(roomId, String);
check(field, String);
check(value, String);
if (['audioNotifications', 'desktopNotifications', 'mobilePushNotifications', 'emailNotifications', 'unreadAlert', 'disableNotifications', 'hideUnreadStatus'].indexOf(field) === -1) {
const notifications = {
'audioNotifications': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateAudioNotificationsById(subscription._id, value)
},
'desktopNotifications': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, value)
},
'mobilePushNotifications': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, value)
},
'emailNotifications': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, value)
},
'unreadAlert': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateUnreadAlertById(subscription._id, value)
},
'disableNotifications': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDisableNotificationsById(subscription._id, value === '1')
},
'hideUnreadStatus': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateHideUnreadStatusById(subscription._id, value === '1')
},
'desktopNotificationDuration': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDesktopNotificationDurationById(subscription._id, value)
},
'audioNotificationValue': {
updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateAudioNotificationValueById(subscription._id, value)
}
};
const isInvalidNotification = !Object.keys(notifications).includes(field);
const basicValuesForNotifications = ['all', 'mentions', 'nothing', 'default'];
const fieldsMustHaveBasicValues = ['emailNotifications', 'audioNotifications', 'mobilePushNotifications', 'desktopNotifications'];
if (isInvalidNotification) {
throw new Meteor.Error('error-invalid-settings', 'Invalid settings field', { method: 'saveNotificationSettings' });
}
if (field !== 'hideUnreadStatus' && field !== 'disableNotifications' && ['all', 'mentions', 'nothing', 'default'].indexOf(value) === -1) {
if (fieldsMustHaveBasicValues.includes(field) && !basicValuesForNotifications.includes(value)) {
throw new Meteor.Error('error-invalid-settings', 'Invalid settings value', { method: 'saveNotificationSettings' });
}
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, Meteor.userId());
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, Meteor.userId());
if (!subscription) {
throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'saveNotificationSettings' });
}
switch (field) {
case 'audioNotifications':
RocketChat.models.Subscriptions.updateAudioNotificationsById(subscription._id, value);
break;
case 'desktopNotifications':
RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, value);
break;
case 'mobilePushNotifications':
RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, value);
break;
case 'emailNotifications':
RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, value);
break;
case 'unreadAlert':
RocketChat.models.Subscriptions.updateUnreadAlertById(subscription._id, value);
break;
case 'disableNotifications':
RocketChat.models.Subscriptions.updateDisableNotificationsById(subscription._id, value === '1' ? true : false);
break;
case 'hideUnreadStatus':
RocketChat.models.Subscriptions.updateHideUnreadStatusById(subscription._id, value === '1' ? true : false);
break;
}
notifications[field].updateMethod(subscription, value);
return true;
},

@ -45,13 +45,8 @@ Meteor.startup(function() {
'message-mobile'
],
action(event) {
const data = Blaze.getData(event.currentTarget);
event.stopPropagation();
RocketChat.EmojiPicker.open(event.currentTarget, (emoji) => {
Meteor.call('setReaction', `:${ emoji }:`, data._arguments[1]._id);
});
RocketChat.EmojiPicker.open(event.currentTarget, emoji => Meteor.call('setReaction', `:${ emoji }:`, this._arguments[1]._id));
},
condition(message) {
const room = RocketChat.models.Rooms.findOne({ _id: message.rid });

@ -17,6 +17,8 @@ Meteor.methods({
return false;
} else if (message.private) {
return false;
} else if (!RocketChat.emoji.list[reaction] && RocketChat.models.EmojiCustom.findByNameOrAlias(reaction).count() === 0) {
return false;
}
if (message.reactions && message.reactions[reaction] && message.reactions[reaction].usernames.indexOf(user.username) !== -1) {

@ -19,6 +19,10 @@ Meteor.methods({
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' });
}
if (!RocketChat.emoji.list[reaction] && RocketChat.models.EmojiCustom.findByNameOrAlias(reaction).count() === 0) {
throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', { method: 'setReaction' });
}
const user = Meteor.user();
if (Array.isArray(room.muted) && room.muted.indexOf(user.username) !== -1 && !room.reactWhenReadOnly) {

@ -0,0 +1,3 @@
.attachments__content .attachments__name {
overflow: unset;
}

@ -4,6 +4,8 @@
width: 1vw;
height: 100%;
position: relative;
}
.messages-container .room-icon {

@ -138,6 +138,26 @@
& .rc-input__icon-svg--plus {
transition: transform 0.1s linear;
}
&.cross {
color: red;
}
&.loading {
cursor: pointer;
display: none;
&.active {
display: flex;
}
}
&.mic {
display: none;
&.active {
display: flex;
}
}
}
&__action-menu {
@ -151,6 +171,33 @@
& [data-small] {
display: none;
}
&__audio-recording {
display: none;
position: relative;
z-index: -1;
&.active{
display: flex;
z-index: 2;
}
}
&__audio-message{
&.hidden{
z-index: -1;
}
}
&__timer-box{
display: flex;
width: 50px;
}
&__timer-dot{
font-size: 1.5em;
color: #d60000;
line-height: 0.8em;
}
}
@media (width <= 500px) {

@ -65,6 +65,13 @@
}
}
&__content-scroll {
.rc-popover__item {
display:block;
}
}
&__title {
flex: 1;
@ -113,6 +120,13 @@
}
}
&__label {
display: flex;
cursor: pointer;
align-items: center;
}
&__icon {
display: flex;

@ -18,22 +18,6 @@
display: none;
}
.sidebar--medium .sidebar-item {
height: var(--sidebar-item-height-medium);
&__picture {
flex: 0 0 var(--sidebar-item-thumb-size-medium);
width: var(--sidebar-item-thumb-size-medium);
height: var(--sidebar-item-thumb-size-medium);
}
&__user-thumb {
width: var(--sidebar-item-thumb-size-medium);
height: var(--sidebar-item-thumb-size-medium);
}
}
.sidebar--extended .sidebar-item {
height: var(--sidebar-item-height-extended);
@ -44,10 +28,6 @@
height: var(--sidebar-item-thumb-size-extended);
}
&__icon {
margin: 0 auto;
}
&__user-thumb {
width: var(--sidebar-item-thumb-size-extended);
height: var(--sidebar-item-thumb-size-extended);
@ -68,6 +48,22 @@
}
}
.sidebar--medium .sidebar-item {
height: var(--sidebar-item-height-medium);
&__picture {
flex: 0 0 var(--sidebar-item-thumb-size-medium);
width: var(--sidebar-item-thumb-size-medium);
height: var(--sidebar-item-thumb-size-medium);
}
&__user-thumb {
width: var(--sidebar-item-thumb-size-medium);
height: var(--sidebar-item-thumb-size-medium);
}
}
.sidebar-item {
position: relative;
@ -196,16 +192,11 @@
&__message {
display: flex;
overflow: hidden;
flex-direction: row;
flex: 1;
margin: 0 -3px;
align-items: center;
justify-content: space-between;
&-top {
overflow: hidden;
}
}
&__name {
@ -299,6 +290,10 @@
}
}
.flex-nav .sidebar-item__message {
flex-direction: row;
}
.rtl .sidebar-item {
&__menu {
right: auto;

@ -18,7 +18,7 @@
& .rc-switch__input {
&:checked {
& + .rc-switch__button {
border-color: #1757B7;
border-color: var(--button-primary-background);
background-color: var(--button-primary-background);
}
}

@ -30,6 +30,8 @@ body {
font-size: var(--text-small-size);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--rc-color-primary)
}
:focus {
@ -205,3 +207,52 @@ button {
.hidden {
display: none;
}
.loading-animation {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
}
.loading-animation > .bounce {
display: inline-block;
width: 10px;
height: 10px;
margin: 2px;
animation: loading-bouncedelay 1.4s infinite ease-in-out both;
border-radius: 100%;
background-color: rgba(255, 255, 255, 0.6);
}
.loading-animation .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loading-animation .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@keyframes loading-bouncedelay {
0%,
80%,
100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
}

@ -1702,8 +1702,7 @@
white-space: nowrap;
text-overflow: ellipsis;
color: white;
background-color: #04436a;
color: var(--rc-color-content);
font-size: 1.2em;
line-height: 40px;
@ -2609,6 +2608,44 @@
position: relative;
}
.rc-old .rc-message-box .reply-preview {
display: flex;
position: relative;
background-color:#fff;
padding-left: 15px;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
align-items: center;
justify-content: space-between;
}
.rc-old .rc-message-box .reply-preview-with-popup {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 5px;
border-bottom-left-radius: 5px;
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2), 0 1px 1px rgba(0, 0, 0, 0.16);;
}
.rc-old .reply-preview .cancel-reply {
padding: 10px;
}
.rc-old .reply-preview .mention-link.mention-link-all {
color: #fff;
}
.rc-old .reply-preview .mention-link.mention-link-me {
color: #fff;
}
.rc-old .message-popup.popup-with-reply-preview {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.rc-old .message-popup {
position: absolute;
z-index: 101;
@ -4558,8 +4595,6 @@ body:not(.is-cordova) {
}
.rc-old.full-page {
z-index: 101;
display: flex;
width: 100%;

@ -42,6 +42,7 @@
@import 'imports/components/contextual-bar.css';
@import 'imports/components/emojiPicker.css';
@import 'imports/components/table.css';
@import 'imports/components/file-list.css';
/* Modal */
@import 'imports/components/modal/full-modal.css';

@ -947,3 +947,7 @@ label.required::after {
.range-slider-range::-moz-range-track {
background-color: @tertiary-background-color;
}
.announcement {
background-color: @primary-background-color;
}

@ -14,8 +14,8 @@
<div class="section-content border-component-color">
<div class="input-line double-col">
<label for="language">{{_ "Language"}}</label>
<div class="rc-select">
<select id="language" class="required rc-select__element">
<div>
<select id="language" class="required rc-input__element">
{{#each languages}}
<option value="{{key}}" selected="{{userLanguage key}}" dir="auto">{{name}}</option>
{{/each}}
@ -202,17 +202,6 @@
<div class="info">{{_ "Enter_Behaviour_Description"}}</div>
</div>
</div>
<div class="input-line double-col" id="viewMode">
<label>{{_ "View_mode"}}</label>
<div>
<select class="input-monitor rc-input__element" name="viewMode">
<option value="0" selected="{{selected 'viewMode' '0'}}">{{_ "Normal"}}</option>
<option value="1" selected="{{selected 'viewMode' '1'}}">{{_ "Cozy"}}</option>
<option value="2" selected="{{selected 'viewMode' '2'}}">{{_ "Compact"}}</option>
</select>
<div class="info">{{_ "View_mode_info"}}</div>
</div>
</div>
<div class="input-line double-col" id="emailNotificationMode">
<label>{{_ "Email_Notification_Mode"}}</label>
<div>

@ -133,9 +133,7 @@ Template.accountPreferences.onCreated(function() {
data.saveMobileBandwidth = JSON.parse($('input[name=saveMobileBandwidth]:checked').val());
data.collapseMediaByDefault = JSON.parse($('input[name=collapseMediaByDefault]:checked').val());
data.muteFocusedConversations = JSON.parse($('#muteFocusedConversations').find('input:checked').val());
data.viewMode = parseInt($('#viewMode').find('select').val());
data.hideUsernames = JSON.parse($('#hideUsernames').find('input:checked').val());
data.hideRoles = JSON.parse($('#hideRoles').find('input:checked').val());
data.hideFlexTab = JSON.parse($('#hideFlexTab').find('input:checked').val());
data.hideAvatars = JSON.parse($('#hideAvatars').find('input:checked').val());
data.sendOnEnter = $('#sendOnEnter').find('select').val();
@ -153,6 +151,10 @@ Template.accountPreferences.onCreated(function() {
let reload = false;
if (RocketChat.settings.get('UI_DisplayRoles')) {
data.hideRoles = JSON.parse($('#hideRoles').find('input:checked').val());
}
// if highlights changed we need page reload
const highlights = RocketChat.getUserPreference(Meteor.user(), 'highlights');
if (highlights && highlights.join('\n') !== data.highlights.join('\n')) {

@ -42,7 +42,7 @@
<label class="rc-select-avatar__upload-label avatar" for="upload-avatar">
{{> icon block="rc-select-avatar__upload-icon" icon="upload"}}
</label>
<input type="file" name="" value="" id="upload-avatar" style="display:none;">
<input type="file" name="" value="" id="upload-avatar" style="display:none;" accept="image/x-png,image/gif,image/jpeg">
</div>
<div class="rc-select-avatar__list-item rc-tooltip js-select-avatar-url {{selectUrl}}" aria-label="{{_ "Use_url_for_avatar" }}">
<label class="rc-select-avatar__upload-label avatar">

@ -23,7 +23,7 @@ const fixCordova = (url) => {
} else if (navigator.userAgent.indexOf('Electron') > -1) {
return __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
} else {
return Meteor.absoluteUrl().replace(/\/$/, '') + __meteor_runtime_config__.ROOT_URL_PATH_PREFIX + url;
return Meteor.absoluteUrl().replace(/\/$/, '') + url;
}
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save